aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/channels
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/channels')
-rw-r--r--trunk/channels/DialTone.h257
-rw-r--r--trunk/channels/Makefile105
-rw-r--r--trunk/channels/chan_agent.c2399
-rw-r--r--trunk/channels/chan_alsa.c925
-rw-r--r--trunk/channels/chan_console.c1103
-rw-r--r--trunk/channels/chan_features.c570
-rw-r--r--trunk/channels/chan_gtalk.c1956
-rw-r--r--trunk/channels/chan_h323.c3353
-rw-r--r--trunk/channels/chan_iax2.c11726
-rw-r--r--trunk/channels/chan_jingle.c1862
-rw-r--r--trunk/channels/chan_local.c761
-rw-r--r--trunk/channels/chan_mgcp.c4398
-rw-r--r--trunk/channels/chan_misdn.c5747
-rw-r--r--trunk/channels/chan_nbs.c292
-rw-r--r--trunk/channels/chan_oss.c1470
-rw-r--r--trunk/channels/chan_phone.c1450
-rw-r--r--trunk/channels/chan_sip.c21227
-rw-r--r--trunk/channels/chan_skinny.c6066
-rw-r--r--trunk/channels/chan_unistim.c5668
-rw-r--r--trunk/channels/chan_usbradio.c2494
-rw-r--r--trunk/channels/chan_vpb.cc2899
-rw-r--r--trunk/channels/chan_zap.c14253
-rw-r--r--trunk/channels/console_board.c329
-rw-r--r--trunk/channels/console_gui.c1036
-rw-r--r--trunk/channels/console_video.c1035
-rw-r--r--trunk/channels/console_video.h127
-rw-r--r--trunk/channels/h323/ChangeLog43
-rw-r--r--trunk/channels/h323/INSTALL.openh32318
-rw-r--r--trunk/channels/h323/Makefile.in48
-rw-r--r--trunk/channels/h323/README144
-rw-r--r--trunk/channels/h323/TODO9
-rw-r--r--trunk/channels/h323/ast_h323.cxx2637
-rw-r--r--trunk/channels/h323/ast_h323.h189
-rw-r--r--trunk/channels/h323/caps_h323.cxx383
-rw-r--r--trunk/channels/h323/caps_h323.h172
-rw-r--r--trunk/channels/h323/chan_h323.h269
-rw-r--r--trunk/channels/h323/cisco-h225.asn74
-rw-r--r--trunk/channels/h323/cisco-h225.cxx853
-rw-r--r--trunk/channels/h323/cisco-h225.h299
-rw-r--r--trunk/channels/h323/compat_h323.cxx138
-rw-r--r--trunk/channels/h323/compat_h323.h94
-rw-r--r--trunk/channels/h323/noexport.map5
-rw-r--r--trunk/channels/iax2-parser.c1080
-rw-r--r--trunk/channels/iax2-parser.h163
-rw-r--r--trunk/channels/iax2-provision.c538
-rw-r--r--trunk/channels/iax2-provision.h53
-rw-r--r--trunk/channels/iax2.h277
-rw-r--r--trunk/channels/misdn/Makefile17
-rw-r--r--trunk/channels/misdn/chan_misdn_config.h160
-rw-r--r--trunk/channels/misdn/ie.c1422
-rw-r--r--trunk/channels/misdn/isdn_lib.c4582
-rw-r--r--trunk/channels/misdn/isdn_lib.h488
-rw-r--r--trunk/channels/misdn/isdn_lib_intern.h124
-rw-r--r--trunk/channels/misdn/isdn_msg_parser.c1353
-rw-r--r--trunk/channels/misdn/portinfo.c202
-rw-r--r--trunk/channels/misdn_config.c1158
-rw-r--r--trunk/channels/vcodecs.c1253
-rw-r--r--trunk/channels/vgrabbers.c346
-rwxr-xr-xtrunk/channels/xpmr/sinetabx.h290
-rwxr-xr-xtrunk/channels/xpmr/xpmr.c2256
-rwxr-xr-xtrunk/channels/xpmr/xpmr.h543
-rwxr-xr-xtrunk/channels/xpmr/xpmr_coef.h951
62 files changed, 116139 insertions, 0 deletions
diff --git a/trunk/channels/DialTone.h b/trunk/channels/DialTone.h
new file mode 100644
index 000000000..e71ba0c7d
--- /dev/null
+++ b/trunk/channels/DialTone.h
@@ -0,0 +1,257 @@
+/*
+ * 8-bit raw data
+ *
+ * Source: DialTone.ulaw
+ *
+ * Copyright (C) 1999, Mark Spencer
+ *
+ * Distributed under the terms of the GNU General Public License
+ *
+ */
+
+/*! \file
+ * \brief
+ * 8-bit raw data
+ */
+
+static unsigned char DialTone[] = {
+0xff, 0xab, 0x9d, 0x96, 0x91, 0x90, 0x91, 0x96, 0x9c, 0xaa,
+0xd9, 0x2f, 0x1f, 0x19, 0x15, 0x14, 0x15, 0x19, 0x1f, 0x2c,
+0x4e, 0xb9, 0xa7, 0x9f, 0x9c, 0x9b, 0x9c, 0x9f, 0xa7, 0xb3,
+0xcf, 0x47, 0x34, 0x2d, 0x2a, 0x2a, 0x2c, 0x31, 0x3a, 0x47,
+0x5f, 0xe4, 0xd8, 0xd9, 0xe9, 0x64, 0x4f, 0x49, 0x46, 0x49,
+0x58, 0xde, 0xc2, 0xb5, 0xad, 0xa8, 0xa6, 0xa6, 0xa9, 0xaf,
+0xbf, 0x56, 0x32, 0x26, 0x1e, 0x1b, 0x19, 0x1a, 0x1d, 0x24,
+0x33, 0xdd, 0xad, 0x9f, 0x98, 0x94, 0x92, 0x93, 0x97, 0x9e,
+0xac, 0xf8, 0x2c, 0x1d, 0x16, 0x11, 0xf, 0x10, 0x15, 0x1b,
+0x29, 0x55, 0xae, 0x9e, 0x97, 0x92, 0x90, 0x91, 0x95, 0x9c,
+0xa8, 0xca, 0x35, 0x22, 0x1a, 0x16, 0x15, 0x16, 0x19, 0x1f,
+0x2b, 0x47, 0xbe, 0xab, 0xa2, 0x9e, 0x9d, 0x9e, 0xa2, 0xa9,
+0xb4, 0xcc, 0x4f, 0x3a, 0x32, 0x2f, 0x2f, 0x32, 0x39, 0x41,
+0x4f, 0x67, 0xf5, 0xf5, 0x67, 0x51, 0x46, 0x3e, 0x3b, 0x3b,
+0x3f, 0x4d, 0xe2, 0xbe, 0xb0, 0xa9, 0xa4, 0xa1, 0xa1, 0xa5,
+0xab, 0xba, 0x64, 0x33, 0x25, 0x1d, 0x19, 0x17, 0x18, 0x1b,
+0x20, 0x2e, 0x72, 0xae, 0x9f, 0x98, 0x94, 0x91, 0x92, 0x96,
+0x9c, 0xa9, 0xd4, 0x2f, 0x1e, 0x17, 0x11, 0xf, 0x10, 0x14,
+0x1a, 0x26, 0x48, 0xb2, 0x9f, 0x98, 0x93, 0x91, 0x92, 0x95,
+0x9b, 0xa7, 0xc1, 0x3a, 0x25, 0x1c, 0x18, 0x16, 0x17, 0x1a,
+0x1f, 0x2b, 0x42, 0xc6, 0xae, 0xa6, 0xa0, 0x9f, 0xa0, 0xa5,
+0xab, 0xb6, 0xcb, 0x5a, 0x40, 0x39, 0x36, 0x37, 0x3b, 0x43,
+0x4e, 0x60, 0x7b, 0x7c, 0x60, 0x4e, 0x41, 0x3a, 0x34, 0x32,
+0x33, 0x39, 0x45, 0xed, 0xbd, 0xae, 0xa6, 0xa0, 0x9e, 0x9e,
+0xa0, 0xa8, 0xb4, 0xef, 0x34, 0x24, 0x1c, 0x18, 0x16, 0x16,
+0x19, 0x1e, 0x2b, 0x54, 0xb1, 0x9f, 0x98, 0x93, 0x91, 0x91,
+0x95, 0x9b, 0xa6, 0xc6, 0x33, 0x1f, 0x17, 0x12, 0xf, 0x10,
+0x13, 0x1a, 0x24, 0x3e, 0xb8, 0xa2, 0x99, 0x94, 0x92, 0x92,
+0x96, 0x9b, 0xa6, 0xbc, 0x40, 0x29, 0x1e, 0x1a, 0x18, 0x19,
+0x1b, 0x20, 0x2b, 0x3f, 0xcf, 0xb3, 0xa9, 0xa5, 0xa3, 0xa4,
+0xa8, 0xae, 0xb9, 0xcc, 0x67, 0x49, 0x40, 0x3f, 0x42, 0x4a,
+0x59, 0x79, 0xe5, 0xe4, 0x7a, 0x54, 0x43, 0x39, 0x31, 0x2d,
+0x2c, 0x2d, 0x32, 0x3e, 0x71, 0xbd, 0xad, 0xa4, 0x9e, 0x9c,
+0x9c, 0x9e, 0xa4, 0xaf, 0xd7, 0x36, 0x24, 0x1c, 0x17, 0x15,
+0x15, 0x17, 0x1d, 0x28, 0x47, 0xb5, 0xa1, 0x98, 0x93, 0x90,
+0x90, 0x93, 0x99, 0xa4, 0xbd, 0x38, 0x21, 0x18, 0x13, 0x10,
+0x10, 0x13, 0x19, 0x22, 0x39, 0xbe, 0xa5, 0x9b, 0x96, 0x93,
+0x93, 0x96, 0x9b, 0xa5, 0xb9, 0x4a, 0x2c, 0x20, 0x1c, 0x1a,
+0x1a, 0x1d, 0x22, 0x2c, 0x3d, 0xdc, 0xb8, 0xad, 0xa9, 0xa8,
+0xa9, 0xac, 0xb2, 0xbd, 0xce, 0x78, 0x54, 0x4d, 0x4f, 0x5a,
+0xff, 0xda, 0xcf, 0xcd, 0xd4, 0xf8, 0x4e, 0x3d, 0x32, 0x2c,
+0x29, 0x28, 0x29, 0x2d, 0x38, 0x5c, 0xbd, 0xac, 0xa2, 0x9d,
+0x9a, 0x9a, 0x9c, 0xa0, 0xac, 0xca, 0x39, 0x25, 0x1b, 0x16,
+0x13, 0x13, 0x16, 0x1b, 0x25, 0x3e, 0xb9, 0xa2, 0x99, 0x93,
+0x90, 0x90, 0x93, 0x98, 0xa1, 0xb8, 0x3d, 0x24, 0x19, 0x13,
+0x10, 0x10, 0x13, 0x18, 0x21, 0x35, 0xc7, 0xa8, 0x9d, 0x97,
+0x95, 0x95, 0x97, 0x9c, 0xa4, 0xb6, 0x57, 0x2f, 0x24, 0x1e,
+0x1c, 0x1c, 0x1e, 0x24, 0x2d, 0x3d, 0xf1, 0xbe, 0xb2, 0xad,
+0xac, 0xad, 0xb1, 0xb9, 0xc3, 0xd4, 0xfa, 0x64, 0x65, 0xf9,
+0xd9, 0xca, 0xc2, 0xbf, 0xc0, 0xc9, 0xe7, 0x4c, 0x39, 0x2e,
+0x28, 0x24, 0x23, 0x25, 0x29, 0x33, 0x4f, 0xbf, 0xab, 0xa0,
+0x9b, 0x99, 0x98, 0x9a, 0x9e, 0xa9, 0xc0, 0x3c, 0x26, 0x1b,
+0x16, 0x12, 0x12, 0x14, 0x19, 0x22, 0x38, 0xbe, 0xa4, 0x9a,
+0x93, 0x90, 0x8f, 0x92, 0x97, 0x9f, 0xb3, 0x46, 0x26, 0x1b,
+0x15, 0x11, 0x11, 0x13, 0x18, 0x1f, 0x31, 0xd4, 0xab, 0x9e,
+0x99, 0x96, 0x96, 0x98, 0x9c, 0xa4, 0xb4, 0x6f, 0x34, 0x28,
+0x20, 0x1e, 0x1e, 0x20, 0x26, 0x2e, 0x3d, 0x6d, 0xc5, 0xb9,
+0xb3, 0xb2, 0xb4, 0xba, 0xc1, 0xcd, 0xe0, 0xfc, 0xfb, 0xe0,
+0xce, 0xc3, 0xbb, 0xb7, 0xb6, 0xb9, 0xc0, 0xda, 0x4b, 0x36,
+0x2b, 0x25, 0x20, 0x1f, 0x20, 0x26, 0x2e, 0x46, 0xc2, 0xab,
+0x9f, 0x9a, 0x97, 0x96, 0x98, 0x9c, 0xa5, 0xba, 0x41, 0x27,
+0x1b, 0x15, 0x12, 0x11, 0x13, 0x18, 0x1f, 0x32, 0xc8, 0xa6,
+0x9a, 0x94, 0x90, 0x8f, 0x91, 0x97, 0x9e, 0xaf, 0x54, 0x29,
+0x1c, 0x16, 0x12, 0x11, 0x14, 0x18, 0x1f, 0x2e, 0xf2, 0xae,
+0xa0, 0x9b, 0x98, 0x97, 0x99, 0x9d, 0xa5, 0xb3, 0xe4, 0x3a,
+0x2b, 0x25, 0x21, 0x21, 0x24, 0x29, 0x30, 0x3e, 0x62, 0xcd,
+0xbf, 0xbb, 0xbb, 0xbe, 0xc6, 0xd1, 0xe7, 0x76, 0x75, 0xe7,
+0xcf, 0xc1, 0xb9, 0xb2, 0xaf, 0xaf, 0xb2, 0xba, 0xcf, 0x4c,
+0x34, 0x29, 0x22, 0x1e, 0x1d, 0x1e, 0x22, 0x2b, 0x3e, 0xc7,
+0xab, 0x9f, 0x99, 0x96, 0x95, 0x96, 0x9a, 0xa2, 0xb5, 0x4a,
+0x28, 0x1c, 0x15, 0x11, 0x10, 0x12, 0x17, 0x1e, 0x2e, 0xd5,
+0xa9, 0x9b, 0x95, 0x90, 0x8f, 0x91, 0x96, 0x9d, 0xac, 0x78,
+0x2c, 0x1e, 0x17, 0x13, 0x12, 0x14, 0x18, 0x1f, 0x2d, 0x5d,
+0xb3, 0xa4, 0x9d, 0x9a, 0x99, 0x9b, 0x9e, 0xa6, 0xb2, 0xd6,
+0x3f, 0x2f, 0x29, 0x26, 0x26, 0x28, 0x2d, 0x35, 0x42, 0x5e,
+0xd8, 0xca, 0xc6, 0xc9, 0xcf, 0xe4, 0x69, 0x59, 0x58, 0x64,
+0xdf, 0xc7, 0xba, 0xb1, 0xac, 0xaa, 0xaa, 0xad, 0xb4, 0xc7,
+0x4f, 0x33, 0x27, 0x1f, 0x1c, 0x1b, 0x1c, 0x1f, 0x27, 0x39,
+0xce, 0xac, 0x9f, 0x99, 0x95, 0x94, 0x95, 0x99, 0x9f, 0xaf,
+0x59, 0x2a, 0x1c, 0x16, 0x11, 0x10, 0x11, 0x16, 0x1d, 0x2b,
+0xff, 0xab, 0x9d, 0x96, 0x91, 0x90, 0x91, 0x96, 0x9c, 0xaa,
+0xd9, 0x2f, 0x1f, 0x19, 0x15, 0x14, 0x15, 0x19, 0x1f, 0x2c,
+0x4e, 0xb9, 0xa7, 0x9f, 0x9c, 0x9b, 0x9c, 0x9f, 0xa7, 0xb3,
+0xcf, 0x47, 0x34, 0x2d, 0x2a, 0x2a, 0x2c, 0x31, 0x3a, 0x47,
+0x5f, 0xe4, 0xd8, 0xd9, 0xe9, 0x64, 0x4f, 0x49, 0x46, 0x49,
+0x58, 0xde, 0xc2, 0xb5, 0xad, 0xa8, 0xa6, 0xa6, 0xa9, 0xaf,
+0xbf, 0x56, 0x32, 0x26, 0x1e, 0x1b, 0x19, 0x1a, 0x1d, 0x24,
+0x33, 0xdd, 0xad, 0x9f, 0x98, 0x94, 0x92, 0x93, 0x97, 0x9e,
+0xac, 0xf8, 0x2c, 0x1d, 0x16, 0x11, 0xf, 0x10, 0x15, 0x1b,
+0x29, 0x55, 0xae, 0x9e, 0x97, 0x92, 0x90, 0x91, 0x95, 0x9c,
+0xa8, 0xca, 0x35, 0x22, 0x1a, 0x16, 0x15, 0x16, 0x19, 0x1f,
+0x2b, 0x47, 0xbe, 0xab, 0xa2, 0x9e, 0x9d, 0x9e, 0xa2, 0xa9,
+0xb4, 0xcc, 0x4f, 0x3a, 0x32, 0x2f, 0x2f, 0x32, 0x39, 0x41,
+0x4f, 0x67, 0xf5, 0xf5, 0x67, 0x51, 0x46, 0x3e, 0x3b, 0x3b,
+0x3f, 0x4d, 0xe2, 0xbe, 0xb0, 0xa9, 0xa4, 0xa1, 0xa1, 0xa5,
+0xab, 0xba, 0x64, 0x33, 0x25, 0x1d, 0x19, 0x17, 0x18, 0x1b,
+0x20, 0x2e, 0x72, 0xae, 0x9f, 0x98, 0x94, 0x91, 0x92, 0x96,
+0x9c, 0xa9, 0xd4, 0x2f, 0x1e, 0x17, 0x11, 0xf, 0x10, 0x14,
+0x1a, 0x26, 0x48, 0xb2, 0x9f, 0x98, 0x93, 0x91, 0x92, 0x95,
+0x9b, 0xa7, 0xc1, 0x3a, 0x25, 0x1c, 0x18, 0x16, 0x17, 0x1a,
+0x1f, 0x2b, 0x42, 0xc6, 0xae, 0xa6, 0xa0, 0x9f, 0xa0, 0xa5,
+0xab, 0xb6, 0xcb, 0x5a, 0x40, 0x39, 0x36, 0x37, 0x3b, 0x43,
+0x4e, 0x60, 0x7b, 0x7c, 0x60, 0x4e, 0x41, 0x3a, 0x34, 0x32,
+0x33, 0x39, 0x45, 0xed, 0xbd, 0xae, 0xa6, 0xa0, 0x9e, 0x9e,
+0xa0, 0xa8, 0xb4, 0xef, 0x34, 0x24, 0x1c, 0x18, 0x16, 0x16,
+0x19, 0x1e, 0x2b, 0x54, 0xb1, 0x9f, 0x98, 0x93, 0x91, 0x91,
+0x95, 0x9b, 0xa6, 0xc6, 0x33, 0x1f, 0x17, 0x12, 0xf, 0x10,
+0x13, 0x1a, 0x24, 0x3e, 0xb8, 0xa2, 0x99, 0x94, 0x92, 0x92,
+0x96, 0x9b, 0xa6, 0xbc, 0x40, 0x29, 0x1e, 0x1a, 0x18, 0x19,
+0x1b, 0x20, 0x2b, 0x3f, 0xcf, 0xb3, 0xa9, 0xa5, 0xa3, 0xa4,
+0xa8, 0xae, 0xb9, 0xcc, 0x67, 0x49, 0x40, 0x3f, 0x42, 0x4a,
+0x59, 0x79, 0xe5, 0xe4, 0x7a, 0x54, 0x43, 0x39, 0x31, 0x2d,
+0x2c, 0x2d, 0x32, 0x3e, 0x71, 0xbd, 0xad, 0xa4, 0x9e, 0x9c,
+0x9c, 0x9e, 0xa4, 0xaf, 0xd7, 0x36, 0x24, 0x1c, 0x17, 0x15,
+0x15, 0x17, 0x1d, 0x28, 0x47, 0xb5, 0xa1, 0x98, 0x93, 0x90,
+0x90, 0x93, 0x99, 0xa4, 0xbd, 0x38, 0x21, 0x18, 0x13, 0x10,
+0x10, 0x13, 0x19, 0x22, 0x39, 0xbe, 0xa5, 0x9b, 0x96, 0x93,
+0x93, 0x96, 0x9b, 0xa5, 0xb9, 0x4a, 0x2c, 0x20, 0x1c, 0x1a,
+0x1a, 0x1d, 0x22, 0x2c, 0x3d, 0xdc, 0xb8, 0xad, 0xa9, 0xa8,
+0xa9, 0xac, 0xb2, 0xbd, 0xce, 0x78, 0x54, 0x4d, 0x4f, 0x5a,
+0xff, 0xda, 0xcf, 0xcd, 0xd4, 0xf8, 0x4e, 0x3d, 0x32, 0x2c,
+0x29, 0x28, 0x29, 0x2d, 0x38, 0x5c, 0xbd, 0xac, 0xa2, 0x9d,
+0x9a, 0x9a, 0x9c, 0xa0, 0xac, 0xca, 0x39, 0x25, 0x1b, 0x16,
+0x13, 0x13, 0x16, 0x1b, 0x25, 0x3e, 0xb9, 0xa2, 0x99, 0x93,
+0x90, 0x90, 0x93, 0x98, 0xa1, 0xb8, 0x3d, 0x24, 0x19, 0x13,
+0x10, 0x10, 0x13, 0x18, 0x21, 0x35, 0xc7, 0xa8, 0x9d, 0x97,
+0x95, 0x95, 0x97, 0x9c, 0xa4, 0xb6, 0x57, 0x2f, 0x24, 0x1e,
+0x1c, 0x1c, 0x1e, 0x24, 0x2d, 0x3d, 0xf1, 0xbe, 0xb2, 0xad,
+0xac, 0xad, 0xb1, 0xb9, 0xc3, 0xd4, 0xfa, 0x64, 0x65, 0xf9,
+0xd9, 0xca, 0xc2, 0xbf, 0xc0, 0xc9, 0xe7, 0x4c, 0x39, 0x2e,
+0x28, 0x24, 0x23, 0x25, 0x29, 0x33, 0x4f, 0xbf, 0xab, 0xa0,
+0x9b, 0x99, 0x98, 0x9a, 0x9e, 0xa9, 0xc0, 0x3c, 0x26, 0x1b,
+0x16, 0x12, 0x12, 0x14, 0x19, 0x22, 0x38, 0xbe, 0xa4, 0x9a,
+0x93, 0x90, 0x8f, 0x92, 0x97, 0x9f, 0xb3, 0x46, 0x26, 0x1b,
+0x15, 0x11, 0x11, 0x13, 0x18, 0x1f, 0x31, 0xd4, 0xab, 0x9e,
+0x99, 0x96, 0x96, 0x98, 0x9c, 0xa4, 0xb4, 0x6f, 0x34, 0x28,
+0x20, 0x1e, 0x1e, 0x20, 0x26, 0x2e, 0x3d, 0x6d, 0xc5, 0xb9,
+0xb3, 0xb2, 0xb4, 0xba, 0xc1, 0xcd, 0xe0, 0xfc, 0xfb, 0xe0,
+0xce, 0xc3, 0xbb, 0xb7, 0xb6, 0xb9, 0xc0, 0xda, 0x4b, 0x36,
+0x2b, 0x25, 0x20, 0x1f, 0x20, 0x26, 0x2e, 0x46, 0xc2, 0xab,
+0x9f, 0x9a, 0x97, 0x96, 0x98, 0x9c, 0xa5, 0xba, 0x41, 0x27,
+0x1b, 0x15, 0x12, 0x11, 0x13, 0x18, 0x1f, 0x32, 0xc8, 0xa6,
+0x9a, 0x94, 0x90, 0x8f, 0x91, 0x97, 0x9e, 0xaf, 0x54, 0x29,
+0x1c, 0x16, 0x12, 0x11, 0x14, 0x18, 0x1f, 0x2e, 0xf2, 0xae,
+0xa0, 0x9b, 0x98, 0x97, 0x99, 0x9d, 0xa5, 0xb3, 0xe4, 0x3a,
+0x2b, 0x25, 0x21, 0x21, 0x24, 0x29, 0x30, 0x3e, 0x62, 0xcd,
+0xbf, 0xbb, 0xbb, 0xbe, 0xc6, 0xd1, 0xe7, 0x76, 0x75, 0xe7,
+0xcf, 0xc1, 0xb9, 0xb2, 0xaf, 0xaf, 0xb2, 0xba, 0xcf, 0x4c,
+0x34, 0x29, 0x22, 0x1e, 0x1d, 0x1e, 0x22, 0x2b, 0x3e, 0xc7,
+0xab, 0x9f, 0x99, 0x96, 0x95, 0x96, 0x9a, 0xa2, 0xb5, 0x4a,
+0x28, 0x1c, 0x15, 0x11, 0x10, 0x12, 0x17, 0x1e, 0x2e, 0xd5,
+0xa9, 0x9b, 0x95, 0x90, 0x8f, 0x91, 0x96, 0x9d, 0xac, 0x78,
+0x2c, 0x1e, 0x17, 0x13, 0x12, 0x14, 0x18, 0x1f, 0x2d, 0x5d,
+0xb3, 0xa4, 0x9d, 0x9a, 0x99, 0x9b, 0x9e, 0xa6, 0xb2, 0xd6,
+0x3f, 0x2f, 0x29, 0x26, 0x26, 0x28, 0x2d, 0x35, 0x42, 0x5e,
+0xd8, 0xca, 0xc6, 0xc9, 0xcf, 0xe4, 0x69, 0x59, 0x58, 0x64,
+0xdf, 0xc7, 0xba, 0xb1, 0xac, 0xaa, 0xaa, 0xad, 0xb4, 0xc7,
+0x4f, 0x33, 0x27, 0x1f, 0x1c, 0x1b, 0x1c, 0x1f, 0x27, 0x39,
+0xce, 0xac, 0x9f, 0x99, 0x95, 0x94, 0x95, 0x99, 0x9f, 0xaf,
+0x59, 0x2a, 0x1c, 0x16, 0x11, 0x10, 0x11, 0x16, 0x1d, 0x2b,
+0xff, 0xab, 0x9d, 0x96, 0x91, 0x90, 0x91, 0x96, 0x9c, 0xaa,
+0xd9, 0x2f, 0x1f, 0x19, 0x15, 0x14, 0x15, 0x19, 0x1f, 0x2c,
+0x4e, 0xb9, 0xa7, 0x9f, 0x9c, 0x9b, 0x9c, 0x9f, 0xa7, 0xb3,
+0xcf, 0x47, 0x34, 0x2d, 0x2a, 0x2a, 0x2c, 0x31, 0x3a, 0x47,
+0x5f, 0xe4, 0xd8, 0xd9, 0xe9, 0x64, 0x4f, 0x49, 0x46, 0x49,
+0x58, 0xde, 0xc2, 0xb5, 0xad, 0xa8, 0xa6, 0xa6, 0xa9, 0xaf,
+0xbf, 0x56, 0x32, 0x26, 0x1e, 0x1b, 0x19, 0x1a, 0x1d, 0x24,
+0x33, 0xdd, 0xad, 0x9f, 0x98, 0x94, 0x92, 0x93, 0x97, 0x9e,
+0xac, 0xf8, 0x2c, 0x1d, 0x16, 0x11, 0xf, 0x10, 0x15, 0x1b,
+0x29, 0x55, 0xae, 0x9e, 0x97, 0x92, 0x90, 0x91, 0x95, 0x9c,
+0xa8, 0xca, 0x35, 0x22, 0x1a, 0x16, 0x15, 0x16, 0x19, 0x1f,
+0x2b, 0x47, 0xbe, 0xab, 0xa2, 0x9e, 0x9d, 0x9e, 0xa2, 0xa9,
+0xb4, 0xcc, 0x4f, 0x3a, 0x32, 0x2f, 0x2f, 0x32, 0x39, 0x41,
+0x4f, 0x67, 0xf5, 0xf5, 0x67, 0x51, 0x46, 0x3e, 0x3b, 0x3b,
+0x3f, 0x4d, 0xe2, 0xbe, 0xb0, 0xa9, 0xa4, 0xa1, 0xa1, 0xa5,
+0xab, 0xba, 0x64, 0x33, 0x25, 0x1d, 0x19, 0x17, 0x18, 0x1b,
+0x20, 0x2e, 0x72, 0xae, 0x9f, 0x98, 0x94, 0x91, 0x92, 0x96,
+0x9c, 0xa9, 0xd4, 0x2f, 0x1e, 0x17, 0x11, 0xf, 0x10, 0x14,
+0x1a, 0x26, 0x48, 0xb2, 0x9f, 0x98, 0x93, 0x91, 0x92, 0x95,
+0x9b, 0xa7, 0xc1, 0x3a, 0x25, 0x1c, 0x18, 0x16, 0x17, 0x1a,
+0x1f, 0x2b, 0x42, 0xc6, 0xae, 0xa6, 0xa0, 0x9f, 0xa0, 0xa5,
+0xab, 0xb6, 0xcb, 0x5a, 0x40, 0x39, 0x36, 0x37, 0x3b, 0x43,
+0x4e, 0x60, 0x7b, 0x7c, 0x60, 0x4e, 0x41, 0x3a, 0x34, 0x32,
+0x33, 0x39, 0x45, 0xed, 0xbd, 0xae, 0xa6, 0xa0, 0x9e, 0x9e,
+0xa0, 0xa8, 0xb4, 0xef, 0x34, 0x24, 0x1c, 0x18, 0x16, 0x16,
+0x19, 0x1e, 0x2b, 0x54, 0xb1, 0x9f, 0x98, 0x93, 0x91, 0x91,
+0x95, 0x9b, 0xa6, 0xc6, 0x33, 0x1f, 0x17, 0x12, 0xf, 0x10,
+0x13, 0x1a, 0x24, 0x3e, 0xb8, 0xa2, 0x99, 0x94, 0x92, 0x92,
+0x96, 0x9b, 0xa6, 0xbc, 0x40, 0x29, 0x1e, 0x1a, 0x18, 0x19,
+0x1b, 0x20, 0x2b, 0x3f, 0xcf, 0xb3, 0xa9, 0xa5, 0xa3, 0xa4,
+0xa8, 0xae, 0xb9, 0xcc, 0x67, 0x49, 0x40, 0x3f, 0x42, 0x4a,
+0x59, 0x79, 0xe5, 0xe4, 0x7a, 0x54, 0x43, 0x39, 0x31, 0x2d,
+0x2c, 0x2d, 0x32, 0x3e, 0x71, 0xbd, 0xad, 0xa4, 0x9e, 0x9c,
+0x9c, 0x9e, 0xa4, 0xaf, 0xd7, 0x36, 0x24, 0x1c, 0x17, 0x15,
+0x15, 0x17, 0x1d, 0x28, 0x47, 0xb5, 0xa1, 0x98, 0x93, 0x90,
+0x90, 0x93, 0x99, 0xa4, 0xbd, 0x38, 0x21, 0x18, 0x13, 0x10,
+0x10, 0x13, 0x19, 0x22, 0x39, 0xbe, 0xa5, 0x9b, 0x96, 0x93,
+0x93, 0x96, 0x9b, 0xa5, 0xb9, 0x4a, 0x2c, 0x20, 0x1c, 0x1a,
+0x1a, 0x1d, 0x22, 0x2c, 0x3d, 0xdc, 0xb8, 0xad, 0xa9, 0xa8,
+0xa9, 0xac, 0xb2, 0xbd, 0xce, 0x78, 0x54, 0x4d, 0x4f, 0x5a,
+0xff, 0xda, 0xcf, 0xcd, 0xd4, 0xf8, 0x4e, 0x3d, 0x32, 0x2c,
+0x29, 0x28, 0x29, 0x2d, 0x38, 0x5c, 0xbd, 0xac, 0xa2, 0x9d,
+0x9a, 0x9a, 0x9c, 0xa0, 0xac, 0xca, 0x39, 0x25, 0x1b, 0x16,
+0x13, 0x13, 0x16, 0x1b, 0x25, 0x3e, 0xb9, 0xa2, 0x99, 0x93,
+0x90, 0x90, 0x93, 0x98, 0xa1, 0xb8, 0x3d, 0x24, 0x19, 0x13,
+0x10, 0x10, 0x13, 0x18, 0x21, 0x35, 0xc7, 0xa8, 0x9d, 0x97,
+0x95, 0x95, 0x97, 0x9c, 0xa4, 0xb6, 0x57, 0x2f, 0x24, 0x1e,
+0x1c, 0x1c, 0x1e, 0x24, 0x2d, 0x3d, 0xf1, 0xbe, 0xb2, 0xad,
+0xac, 0xad, 0xb1, 0xb9, 0xc3, 0xd4, 0xfa, 0x64, 0x65, 0xf9,
+0xd9, 0xca, 0xc2, 0xbf, 0xc0, 0xc9, 0xe7, 0x4c, 0x39, 0x2e,
+0x28, 0x24, 0x23, 0x25, 0x29, 0x33, 0x4f, 0xbf, 0xab, 0xa0,
+0x9b, 0x99, 0x98, 0x9a, 0x9e, 0xa9, 0xc0, 0x3c, 0x26, 0x1b,
+0x16, 0x12, 0x12, 0x14, 0x19, 0x22, 0x38, 0xbe, 0xa4, 0x9a,
+0x93, 0x90, 0x8f, 0x92, 0x97, 0x9f, 0xb3, 0x46, 0x26, 0x1b,
+0x15, 0x11, 0x11, 0x13, 0x18, 0x1f, 0x31, 0xd4, 0xab, 0x9e,
+0x99, 0x96, 0x96, 0x98, 0x9c, 0xa4, 0xb4, 0x6f, 0x34, 0x28,
+0x20, 0x1e, 0x1e, 0x20, 0x26, 0x2e, 0x3d, 0x6d, 0xc5, 0xb9,
+0xb3, 0xb2, 0xb4, 0xba, 0xc1, 0xcd, 0xe0, 0xfc, 0xfb, 0xe0,
+0xce, 0xc3, 0xbb, 0xb7, 0xb6, 0xb9, 0xc0, 0xda, 0x4b, 0x36,
+0x2b, 0x25, 0x20, 0x1f, 0x20, 0x26, 0x2e, 0x46, 0xc2, 0xab,
+0x9f, 0x9a, 0x97, 0x96, 0x98, 0x9c, 0xa5, 0xba, 0x41, 0x27,
+0x1b, 0x15, 0x12, 0x11, 0x13, 0x18, 0x1f, 0x32, 0xc8, 0xa6,
+0x9a, 0x94, 0x90, 0x8f, 0x91, 0x97, 0x9e, 0xaf, 0x54, 0x29,
+0x1c, 0x16, 0x12, 0x11, 0x14, 0x18, 0x1f, 0x2e, 0xf2, 0xae,
+0xa0, 0x9b, 0x98, 0x97, 0x99, 0x9d, 0xa5, 0xb3, 0xe4, 0x3a,
+0x2b, 0x25, 0x21, 0x21, 0x24, 0x29, 0x30, 0x3e, 0x62, 0xcd,
+0xbf, 0xbb, 0xbb, 0xbe, 0xc6, 0xd1, 0xe7, 0x76, 0x75, 0xe7,
+0xcf, 0xc1, 0xb9, 0xb2, 0xaf, 0xaf, 0xb2, 0xba, 0xcf, 0x4c,
+0x34, 0x29, 0x22, 0x1e, 0x1d, 0x1e, 0x22, 0x2b, 0x3e, 0xc7,
+0xab, 0x9f, 0x99, 0x96, 0x95, 0x96, 0x9a, 0xa2, 0xb5, 0x4a,
+0x28, 0x1c, 0x15, 0x11, 0x10, 0x12, 0x17, 0x1e, 0x2e, 0xd5,
+0xa9, 0x9b, 0x95, 0x90, 0x8f, 0x91, 0x96, 0x9d, 0xac, 0x78,
+0x2c, 0x1e, 0x17, 0x13, 0x12, 0x14, 0x18, 0x1f, 0x2d, 0x5d,
+0xb3, 0xa4, 0x9d, 0x9a, 0x99, 0x9b, 0x9e, 0xa6, 0xb2, 0xd6,
+0x3f, 0x2f, 0x29, 0x26, 0x26, 0x28, 0x2d, 0x35, 0x42, 0x5e,
+0xd8, 0xca, 0xc6, 0xc9, 0xcf, 0xe4, 0x69, 0x59, 0x58, 0x64,
+0xdf, 0xc7, 0xba, 0xb1, 0xac, 0xaa, 0xaa, 0xad, 0xb4, 0xc7,
+0x4f, 0x33, 0x27, 0x1f, 0x1c, 0x1b, 0x1c, 0x1f, 0x27, 0x39,
+0xce, 0xac, 0x9f, 0x99, 0x95, 0x94, 0x95, 0x99, 0x9f, 0xaf,
+0x59, 0x2a, 0x1c, 0x16, 0x11, 0x10, 0x11, 0x16, 0x1d, 0x2b };
diff --git a/trunk/channels/Makefile b/trunk/channels/Makefile
new file mode 100644
index 000000000..467fc8031
--- /dev/null
+++ b/trunk/channels/Makefile
@@ -0,0 +1,105 @@
+#
+# Asterisk -- A telephony toolkit for Linux.
+#
+# Makefile for channel drivers
+#
+# Copyright (C) 1999-2006, Digium, Inc.
+#
+# This program is free software, distributed under the terms of
+# the GNU General Public License
+#
+
+-include $(ASTTOPDIR)/menuselect.makeopts $(ASTTOPDIR)/menuselect.makedeps
+
+MODULE_PREFIX=chan
+MENUSELECT_CATEGORY=CHANNELS
+MENUSELECT_DESCRIPTION=Channel Drivers
+
+ifeq ($(OSARCH),OpenBSD)
+ PTLIB=-lpt_OpenBSD_x86_r
+ H323LIB=-lh323_OpenBSD_x86_r
+endif
+
+ifeq ($(OSARCH),linux-gnu)
+ PTLIB=-lpt_linux_x86_r
+ H323LIB=-lh323_linux_x86_r
+ CHANH323LIB=-ldl
+endif
+
+ifeq ($(OSARCH),FreeBSD)
+ PTLIB=-lpt_FreeBSD_x86_r
+ H323LIB=-lh323_FreeBSD_x86_r
+ CHANH323LIB=-pthread
+endif
+
+ifeq ($(OSARCH),NetBSD)
+ PTLIB=-lpt_NetBSD_x86_r
+ H323LIB=-lh323_NetBSD_x86_r
+endif
+
+ifeq ($(wildcard h323/libchanh323.a),)
+ MODULE_EXCLUDE += chan_h323
+endif
+
+ifndef OPENH323DIR
+ OPENH323DIR=$(HOME)/openh323
+endif
+
+ifndef PWLIBDIR
+ PWLIBDIR=$(HOME)/pwlib
+endif
+
+all: _all
+
+include $(ASTTOPDIR)/Makefile.moddir_rules
+
+ifneq ($(findstring $(OSARCH), mingw32 cygwin ),)
+ LIBS+= -lres_monitor.so -lres_features.so
+endif
+
+clean::
+ $(MAKE) -C misdn clean
+
+ifneq ($(wildcard h323/Makefile.ast),)
+ include h323/Makefile.ast
+H323LDFLAGS+=-Wl,--version-script=h323/noexport.map
+clean::
+ if [ -f h323/Makefile ]; then $(MAKE) -C h323 clean; fi
+else
+h323/libchanh323.a h323/Makefile.ast:
+ $(CMD_PREFIX) $(MAKE) -C h323
+ $(CMD_PREFIX) rm -f ../main/asterisk
+ $(CMD_PREFIX) echo "***************************************************************"
+ $(CMD_PREFIX) echo
+ $(CMD_PREFIX) echo "********** Re-run 'make' to pick up H.323 parameters **********"
+ $(CMD_PREFIX) echo
+ $(CMD_PREFIX) echo "***************************************************************"
+ $(CMD_PREFIX) exit 1
+endif
+
+dist-clean::
+ rm -f h323/Makefile
+
+$(if $(filter chan_iax2,$(EMBEDDED_MODS)),modules.link,chan_iax2.so): iax2-parser.o iax2-provision.o
+
+ifeq ($(OSARCH),linux-gnu)
+chan_h323.so: chan_h323.o h323/libchanh323.a h323/Makefile.ast
+ $(ECHO_PREFIX) echo " [LD] $^ -> $@"
+ $(CMD_PREFIX) $(CXX) $(PTHREAD_CFLAGS) $(ASTLDFLAGS) $(SOLINK) $(H323LDFLAGS) -o $@ $< h323/libchanh323.a $(H323LDLIBS)
+else
+chan_h323.so: chan_h323.o h323/libchanh323.a
+ $(ECHO_PREFIX) echo " [LD] $^ -> $@"
+ $(CMD_PREFIX) $(CXX) $(PTHREAD_CFLAGS) $(ASTLDFLAGS) $(SOLINK) -o $@ $< h323/libchanh323.a $(CHANH323LIB) -L$(PWLIBDIR)/lib $(PTLIB) -L$(OPENH323DIR)/lib $(H323LIB) -L/usr/lib -lcrypto -lssl -lexpat
+endif
+
+chan_misdn.o: ASTCFLAGS+=-Imisdn
+
+misdn_config.o: ASTCFLAGS+=-Imisdn
+
+misdn/isdn_lib.o: ASTCFLAGS+=-Wno-strict-aliasing
+
+$(if $(filter chan_misdn,$(EMBEDDED_MODS)),modules.link,chan_misdn.so): misdn_config.o misdn/isdn_lib.o misdn/isdn_msg_parser.o
+
+chan_vpb.oo: ASTCFLAGS:=$(filter-out -Wdeclaration-after-statement,$(ASTCFLAGS))
+
+$(if $(filter chan_oss,$(EMBEDDED_MODS)),modules.link,chan_oss.so): console_video.o vgrabbers.o console_board.o
diff --git a/trunk/channels/chan_agent.c b/trunk/channels/chan_agent.c
new file mode 100644
index 000000000..56bb5561c
--- /dev/null
+++ b/trunk/channels/chan_agent.c
@@ -0,0 +1,2399 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+
+/*! \file
+ *
+ * \brief Implementation of Agents (proxy channel)
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * This file is the implementation of Agents modules.
+ * It is a dynamic module that is loaded by Asterisk.
+ * \par See also
+ * \arg \ref Config_agent
+ *
+ * \ingroup channel_drivers
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/signal.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/rtp.h"
+#include "asterisk/acl.h"
+#include "asterisk/callerid.h"
+#include "asterisk/file.h"
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/manager.h"
+#include "asterisk/features.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/astdb.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/monitor.h"
+#include "asterisk/stringfields.h"
+
+static const char tdesc[] = "Call Agent Proxy Channel";
+static const char config[] = "agents.conf";
+
+static const char app[] = "AgentLogin";
+static const char app3[] = "AgentMonitorOutgoing";
+
+static const char synopsis[] = "Call agent login";
+static const char synopsis3[] = "Record agent's outgoing call";
+
+static const char descrip[] =
+" AgentLogin([AgentNo][,options]):\n"
+"Asks the agent to login to the system. Always returns -1. While\n"
+"logged in, the agent can receive calls and will hear a 'beep'\n"
+"when a new call comes in. The agent can dump the call by pressing\n"
+"the star key.\n"
+"The option string may contain zero or more of the following characters:\n"
+" 's' -- silent login - do not announce the login ok segment after agent logged on/off\n";
+
+static const char descrip3[] =
+" AgentMonitorOutgoing([options]):\n"
+"Tries to figure out the id of the agent who is placing outgoing call based on\n"
+"comparison of the callerid of the current interface and the global variable \n"
+"placed by the AgentCallbackLogin application. That's why it should be used only\n"
+"with the AgentCallbackLogin app. Uses the monitoring functions in chan_agent \n"
+"instead of Monitor application. That has to be configured in the agents.conf file.\n"
+"\nReturn value:\n"
+"Normally the app returns 0 unless the options are passed.\n"
+"\nOptions:\n"
+" 'd' - make the app return -1 if there is an error condition\n"
+" 'c' - change the CDR so that the source of the call is 'Agent/agent_id'\n"
+" 'n' - don't generate the warnings when there is no callerid or the\n"
+" agentid is not known.\n"
+" It's handy if you want to have one context for agent and non-agent calls.\n";
+
+static const char mandescr_agents[] =
+"Description: Will list info about all possible agents.\n"
+"Variables: NONE\n";
+
+static const char mandescr_agent_logoff[] =
+"Description: Sets an agent as no longer logged in.\n"
+"Variables: (Names marked with * are required)\n"
+" *Agent: Agent ID of the agent to log off\n"
+" Soft: Set to 'true' to not hangup existing calls\n";
+
+static char moh[80] = "default";
+
+#define AST_MAX_AGENT 80 /*!< Agent ID or Password max length */
+#define AST_MAX_BUF 256
+#define AST_MAX_FILENAME_LEN 256
+
+static const char pa_family[] = "Agents"; /*!< Persistent Agents astdb family */
+#define PA_MAX_LEN 2048 /*!< The maximum length of each persistent member agent database entry */
+
+static int persistent_agents = 0; /*!< queues.conf [general] option */
+static void dump_agents(void);
+
+static ast_group_t group;
+static int autologoff;
+static int wrapuptime;
+static int ackcall;
+static int endcall;
+static int multiplelogin = 1;
+static int autologoffunavail = 0;
+
+static int maxlogintries = 3;
+static char agentgoodbye[AST_MAX_FILENAME_LEN] = "vm-goodbye";
+
+static int recordagentcalls = 0;
+static char recordformat[AST_MAX_BUF] = "";
+static char recordformatext[AST_MAX_BUF] = "";
+static char urlprefix[AST_MAX_BUF] = "";
+static char savecallsin[AST_MAX_BUF] = "";
+static int updatecdr = 0;
+static char beep[AST_MAX_BUF] = "beep";
+
+#define GETAGENTBYCALLERID "AGENTBYCALLERID"
+
+/*! \brief Structure representing an agent. */
+struct agent_pvt {
+ ast_mutex_t lock; /*!< Channel private lock */
+ int dead; /*!< Poised for destruction? */
+ int pending; /*!< Not a real agent -- just pending a match */
+ int abouttograb; /*!< About to grab */
+ int autologoff; /*!< Auto timeout time */
+ int ackcall; /*!< ackcall */
+ int deferlogoff; /*!< Defer logoff to hangup */
+ time_t loginstart; /*!< When agent first logged in (0 when logged off) */
+ time_t start; /*!< When call started */
+ struct timeval lastdisc; /*!< When last disconnected */
+ int wrapuptime; /*!< Wrapup time in ms */
+ ast_group_t group; /*!< Group memberships */
+ int acknowledged; /*!< Acknowledged */
+ char moh[80]; /*!< Which music on hold */
+ char agent[AST_MAX_AGENT]; /*!< Agent ID */
+ char password[AST_MAX_AGENT]; /*!< Password for Agent login */
+ char name[AST_MAX_AGENT];
+ ast_mutex_t app_lock; /**< Synchronization between owning applications */
+ volatile pthread_t owning_app; /**< Owning application thread id */
+ volatile int app_sleep_cond; /**< Sleep condition for the login app */
+ struct ast_channel *owner; /**< Agent */
+ char loginchan[80]; /**< channel they logged in from */
+ char logincallerid[80]; /**< Caller ID they had when they logged in */
+ struct ast_channel *chan; /**< Channel we use */
+ AST_LIST_ENTRY(agent_pvt) list; /**< Next Agent in the linked list. */
+};
+
+static AST_LIST_HEAD_STATIC(agents, agent_pvt); /*!< Holds the list of agents (loaded form agents.conf). */
+
+#define CHECK_FORMATS(ast, p) do { \
+ if (p->chan) {\
+ if (ast->nativeformats != p->chan->nativeformats) { \
+ ast_debug(1, "Native formats changing from %d to %d\n", ast->nativeformats, p->chan->nativeformats); \
+ /* Native formats changed, reset things */ \
+ ast->nativeformats = p->chan->nativeformats; \
+ ast_debug(1, "Resetting read to %d and write to %d\n", ast->readformat, ast->writeformat);\
+ ast_set_read_format(ast, ast->readformat); \
+ ast_set_write_format(ast, ast->writeformat); \
+ } \
+ if (p->chan->readformat != ast->rawreadformat && !p->chan->generator) \
+ ast_set_read_format(p->chan, ast->rawreadformat); \
+ if (p->chan->writeformat != ast->rawwriteformat && !p->chan->generator) \
+ ast_set_write_format(p->chan, ast->rawwriteformat); \
+ } \
+} while(0)
+
+/*! \brief Cleanup moves all the relevant FD's from the 2nd to the first, but retains things
+ properly for a timingfd XXX This might need more work if agents were logged in as agents or other
+ totally impractical combinations XXX */
+
+#define CLEANUP(ast, p) do { \
+ int x; \
+ if (p->chan) { \
+ for (x=0;x<AST_MAX_FDS;x++) {\
+ if (x != AST_TIMING_FD) \
+ ast_channel_set_fd(ast, x, p->chan->fds[x]); \
+ } \
+ ast_channel_set_fd(ast, AST_AGENT_FD, p->chan->fds[AST_TIMING_FD]); \
+ } \
+} while(0)
+
+/*--- Forward declarations */
+static struct ast_channel *agent_request(const char *type, int format, void *data, int *cause);
+static int agent_devicestate(void *data);
+static void agent_logoff_maintenance(struct agent_pvt *p, char *loginchan, long logintime, const char *uniqueid, char *logcommand);
+static int agent_digit_begin(struct ast_channel *ast, char digit);
+static int agent_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int agent_call(struct ast_channel *ast, char *dest, int timeout);
+static int agent_hangup(struct ast_channel *ast);
+static int agent_answer(struct ast_channel *ast);
+static struct ast_frame *agent_read(struct ast_channel *ast);
+static int agent_write(struct ast_channel *ast, struct ast_frame *f);
+static int agent_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
+static int agent_sendtext(struct ast_channel *ast, const char *text);
+static int agent_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+static int agent_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static struct ast_channel *agent_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge);
+static void set_agentbycallerid(const char *callerid, const char *agent);
+static char *complete_agent_logoff_cmd(const char *line, const char *word, int pos, int state);
+static struct ast_channel* agent_get_base_channel(struct ast_channel *chan);
+static int agent_set_base_channel(struct ast_channel *chan, struct ast_channel *base);
+
+/*! \brief Channel interface description for PBX integration */
+static const struct ast_channel_tech agent_tech = {
+ .type = "Agent",
+ .description = tdesc,
+ .capabilities = -1,
+ .requester = agent_request,
+ .devicestate = agent_devicestate,
+ .send_digit_begin = agent_digit_begin,
+ .send_digit_end = agent_digit_end,
+ .call = agent_call,
+ .hangup = agent_hangup,
+ .answer = agent_answer,
+ .read = agent_read,
+ .write = agent_write,
+ .write_video = agent_write,
+ .send_html = agent_sendhtml,
+ .send_text = agent_sendtext,
+ .exception = agent_read,
+ .indicate = agent_indicate,
+ .fixup = agent_fixup,
+ .bridged_channel = agent_bridgedchannel,
+ .get_base_channel = agent_get_base_channel,
+ .set_base_channel = agent_set_base_channel,
+};
+
+/*!
+ * Adds an agent to the global list of agents.
+ *
+ * \param agent A string with the username, password and real name of an agent. As defined in agents.conf. Example: "13,169,John Smith"
+ * \param pending If it is pending or not.
+ * @return The just created agent.
+ * \sa agent_pvt, agents.
+ */
+static struct agent_pvt *add_agent(const char *agent, int pending)
+{
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(agt);
+ AST_APP_ARG(password);
+ AST_APP_ARG(name);
+ );
+ char *password = NULL;
+ char *name = NULL;
+ char *agt = NULL;
+ struct agent_pvt *p;
+
+ parse = ast_strdupa(agent);
+
+ /* Extract username (agt), password and name from agent (args). */
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if(args.argc == 0) {
+ ast_log(LOG_WARNING, "A blank agent line!\n");
+ return NULL;
+ }
+
+ if(ast_strlen_zero(args.agt) ) {
+ ast_log(LOG_WARNING, "An agent line with no agentid!\n");
+ return NULL;
+ } else
+ agt = args.agt;
+
+ if(!ast_strlen_zero(args.password)) {
+ password = args.password;
+ while (*password && *password < 33) password++;
+ }
+ if(!ast_strlen_zero(args.name)) {
+ name = args.name;
+ while (*name && *name < 33) name++;
+ }
+
+ /* Are we searching for the agent here ? To see if it exists already ? */
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ if (!pending && !strcmp(p->agent, agt))
+ break;
+ }
+ if (!p) {
+ // Build the agent.
+ if (!(p = ast_calloc(1, sizeof(*p))))
+ return NULL;
+ ast_copy_string(p->agent, agt, sizeof(p->agent));
+ ast_mutex_init(&p->lock);
+ ast_mutex_init(&p->app_lock);
+ p->owning_app = (pthread_t) -1;
+ p->app_sleep_cond = 1;
+ p->group = group;
+ p->pending = pending;
+ AST_LIST_INSERT_TAIL(&agents, p, list);
+ }
+
+ ast_copy_string(p->password, password ? password : "", sizeof(p->password));
+ ast_copy_string(p->name, name ? name : "", sizeof(p->name));
+ ast_copy_string(p->moh, moh, sizeof(p->moh));
+ p->ackcall = ackcall;
+ p->autologoff = autologoff;
+
+ /* If someone reduces the wrapuptime and reloads, we want it
+ * to change the wrapuptime immediately on all calls */
+ if (p->wrapuptime > wrapuptime) {
+ struct timeval now = ast_tvnow();
+ /* XXX check what is this exactly */
+
+ /* We won't be pedantic and check the tv_usec val */
+ if (p->lastdisc.tv_sec > (now.tv_sec + wrapuptime/1000)) {
+ p->lastdisc.tv_sec = now.tv_sec + wrapuptime/1000;
+ p->lastdisc.tv_usec = now.tv_usec;
+ }
+ }
+ p->wrapuptime = wrapuptime;
+
+ if (pending)
+ p->dead = 1;
+ else
+ p->dead = 0;
+ return p;
+}
+
+/*!
+ * Deletes an agent after doing some clean up.
+ * Further documentation: How safe is this function ? What state should the agent be to be cleaned.
+ * \param p Agent to be deleted.
+ * \returns Always 0.
+ */
+static int agent_cleanup(struct agent_pvt *p)
+{
+ struct ast_channel *chan = p->owner;
+ p->owner = NULL;
+ chan->tech_pvt = NULL;
+ p->app_sleep_cond = 1;
+ /* Release ownership of the agent to other threads (presumably running the login app). */
+ ast_mutex_unlock(&p->app_lock);
+ if (chan)
+ ast_channel_free(chan);
+ if (p->dead) {
+ ast_mutex_destroy(&p->lock);
+ ast_mutex_destroy(&p->app_lock);
+ ast_free(p);
+ }
+ return 0;
+}
+
+static int check_availability(struct agent_pvt *newlyavailable, int needlock);
+
+static int agent_answer(struct ast_channel *ast)
+{
+ ast_log(LOG_WARNING, "Huh? Agent is being asked to answer?\n");
+ return -1;
+}
+
+static int __agent_start_monitoring(struct ast_channel *ast, struct agent_pvt *p, int needlock)
+{
+ char tmp[AST_MAX_BUF],tmp2[AST_MAX_BUF], *pointer;
+ char filename[AST_MAX_BUF];
+ int res = -1;
+ if (!p)
+ return -1;
+ if (!ast->monitor) {
+ snprintf(filename, sizeof(filename), "agent-%s-%s",p->agent, ast->uniqueid);
+ /* substitute . for - */
+ if ((pointer = strchr(filename, '.')))
+ *pointer = '-';
+ snprintf(tmp, sizeof(tmp), "%s%s", savecallsin, filename);
+ ast_monitor_start(ast, recordformat, tmp, needlock, X_REC_IN | X_REC_OUT);
+ ast_monitor_setjoinfiles(ast, 1);
+ snprintf(tmp2, sizeof(tmp2), "%s%s.%s", urlprefix, filename, recordformatext);
+#if 0
+ ast_verbose("name is %s, link is %s\n",tmp, tmp2);
+#endif
+ if (!ast->cdr)
+ ast->cdr = ast_cdr_alloc();
+ ast_cdr_setuserfield(ast, tmp2);
+ res = 0;
+ } else
+ ast_log(LOG_ERROR, "Recording already started on that call.\n");
+ return res;
+}
+
+static int agent_start_monitoring(struct ast_channel *ast, int needlock)
+{
+ return __agent_start_monitoring(ast, ast->tech_pvt, needlock);
+}
+
+static struct ast_frame *agent_read(struct ast_channel *ast)
+{
+ struct agent_pvt *p = ast->tech_pvt;
+ struct ast_frame *f = NULL;
+ static struct ast_frame answer_frame = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
+ const char *status;
+ ast_mutex_lock(&p->lock);
+ CHECK_FORMATS(ast, p);
+ if (p->chan) {
+ ast_copy_flags(p->chan, ast, AST_FLAG_EXCEPTION);
+ p->chan->fdno = (ast->fdno == AST_AGENT_FD) ? AST_TIMING_FD : ast->fdno;
+ f = ast_read(p->chan);
+ } else
+ f = &ast_null_frame;
+ if (!f) {
+ /* If there's a channel, hang it up (if it's on a callback) make it NULL */
+ if (p->chan) {
+ p->chan->_bridge = NULL;
+ /* Note that we don't hangup if it's not a callback because Asterisk will do it
+ for us when the PBX instance that called login finishes */
+ if (!ast_strlen_zero(p->loginchan)) {
+ if (p->chan)
+ ast_debug(1, "Bridge on '%s' being cleared (2)\n", p->chan->name);
+
+ status = pbx_builtin_getvar_helper(p->chan, "CHANLOCALSTATUS");
+ if (autologoffunavail && status && !strcasecmp(status, "CHANUNAVAIL")) {
+ long logintime = time(NULL) - p->loginstart;
+ p->loginstart = 0;
+ ast_log(LOG_NOTICE, "Agent read: '%s' is not available now, auto logoff\n", p->name);
+ agent_logoff_maintenance(p, p->loginchan, logintime, ast->uniqueid, "Chanunavail");
+ }
+ ast_hangup(p->chan);
+ if (p->wrapuptime && p->acknowledged)
+ p->lastdisc = ast_tvadd(ast_tvnow(), ast_samp2tv(p->wrapuptime, 1000));
+ }
+ p->chan = NULL;
+ p->acknowledged = 0;
+ }
+ } else {
+ /* if acknowledgement is not required, and the channel is up, we may have missed
+ an AST_CONTROL_ANSWER (if there was one), so mark the call acknowledged anyway */
+ if (!p->ackcall && !p->acknowledged && p->chan && (p->chan->_state == AST_STATE_UP))
+ p->acknowledged = 1;
+ switch (f->frametype) {
+ case AST_FRAME_CONTROL:
+ if (f->subclass == AST_CONTROL_ANSWER) {
+ if (p->ackcall) {
+ ast_verb(3, "%s answered, waiting for '#' to acknowledge\n", p->chan->name);
+ /* Don't pass answer along */
+ ast_frfree(f);
+ f = &ast_null_frame;
+ } else {
+ p->acknowledged = 1;
+ /* Use the builtin answer frame for the
+ recording start check below. */
+ ast_frfree(f);
+ f = &answer_frame;
+ }
+ }
+ break;
+ case AST_FRAME_DTMF_BEGIN:
+ /*ignore DTMF begin's as it can cause issues with queue announce files*/
+ if((!p->acknowledged && f->subclass == '#') || (f->subclass == '*' && endcall)){
+ ast_frfree(f);
+ f = &ast_null_frame;
+ }
+ break;
+ case AST_FRAME_DTMF_END:
+ if (!p->acknowledged && (f->subclass == '#')) {
+ ast_verb(3, "%s acknowledged\n", p->chan->name);
+ p->acknowledged = 1;
+ ast_frfree(f);
+ f = &answer_frame;
+ } else if (f->subclass == '*' && endcall) {
+ /* terminates call */
+ ast_frfree(f);
+ f = NULL;
+ }
+ break;
+ case AST_FRAME_VOICE:
+ case AST_FRAME_VIDEO:
+ /* don't pass voice or video until the call is acknowledged */
+ if (!p->acknowledged) {
+ ast_frfree(f);
+ f = &ast_null_frame;
+ }
+ default:
+ /* pass everything else on through */
+ break;
+ }
+ }
+
+ CLEANUP(ast,p);
+ if (p->chan && !p->chan->_bridge) {
+ if (strcasecmp(p->chan->tech->type, "Local")) {
+ p->chan->_bridge = ast;
+ if (p->chan)
+ ast_debug(1, "Bridge on '%s' being set to '%s' (3)\n", p->chan->name, p->chan->_bridge->name);
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ if (recordagentcalls && f == &answer_frame)
+ agent_start_monitoring(ast,0);
+ return f;
+}
+
+static int agent_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
+{
+ struct agent_pvt *p = ast->tech_pvt;
+ int res = -1;
+ ast_mutex_lock(&p->lock);
+ if (p->chan)
+ res = ast_channel_sendhtml(p->chan, subclass, data, datalen);
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static int agent_sendtext(struct ast_channel *ast, const char *text)
+{
+ struct agent_pvt *p = ast->tech_pvt;
+ int res = -1;
+ ast_mutex_lock(&p->lock);
+ if (p->chan)
+ res = ast_sendtext(p->chan, text);
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static int agent_write(struct ast_channel *ast, struct ast_frame *f)
+{
+ struct agent_pvt *p = ast->tech_pvt;
+ int res = -1;
+ CHECK_FORMATS(ast, p);
+ ast_mutex_lock(&p->lock);
+ if (!p->chan)
+ res = 0;
+ else {
+ if ((f->frametype != AST_FRAME_VOICE) ||
+ (f->frametype != AST_FRAME_VIDEO) ||
+ (f->subclass == p->chan->writeformat)) {
+ res = ast_write(p->chan, f);
+ } else {
+ ast_debug(1, "Dropping one incompatible %s frame on '%s' to '%s'\n",
+ f->frametype == AST_FRAME_VOICE ? "audio" : "video",
+ ast->name, p->chan->name);
+ res = 0;
+ }
+ }
+ CLEANUP(ast, p);
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static int agent_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct agent_pvt *p = newchan->tech_pvt;
+ ast_mutex_lock(&p->lock);
+ if (p->owner != oldchan) {
+ ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, p->owner);
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ p->owner = newchan;
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int agent_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+ struct agent_pvt *p = ast->tech_pvt;
+ int res = -1;
+ ast_mutex_lock(&p->lock);
+ if (p->chan)
+ res = p->chan->tech->indicate ? p->chan->tech->indicate(p->chan, condition, data, datalen) : -1;
+ else
+ res = 0;
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static int agent_digit_begin(struct ast_channel *ast, char digit)
+{
+ struct agent_pvt *p = ast->tech_pvt;
+ ast_mutex_lock(&p->lock);
+ ast_senddigit_begin(p->chan, digit);
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int agent_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct agent_pvt *p = ast->tech_pvt;
+ ast_mutex_lock(&p->lock);
+ ast_senddigit_end(p->chan, digit, duration);
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int agent_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ struct agent_pvt *p = ast->tech_pvt;
+ int res = -1;
+ int newstate=0;
+ ast_mutex_lock(&p->lock);
+ p->acknowledged = 0;
+ if (!p->chan) {
+ if (p->pending) {
+ ast_debug(1, "Pretending to dial on pending agent\n");
+ newstate = AST_STATE_DIALING;
+ res = 0;
+ } else {
+ ast_log(LOG_NOTICE, "Whoa, they hung up between alloc and call... what are the odds of that?\n");
+ res = -1;
+ }
+ ast_mutex_unlock(&p->lock);
+ if (newstate)
+ ast_setstate(ast, newstate);
+ return res;
+ } else if (!ast_strlen_zero(p->loginchan)) {
+ time(&p->start);
+ /* Call on this agent */
+ ast_verb(3, "outgoing agentcall, to agent '%s', on '%s'\n", p->agent, p->chan->name);
+ ast_set_callerid(p->chan,
+ ast->cid.cid_num, ast->cid.cid_name, NULL);
+ ast_channel_inherit_variables(ast, p->chan);
+ res = ast_call(p->chan, p->loginchan, 0);
+ CLEANUP(ast,p);
+ ast_mutex_unlock(&p->lock);
+ return res;
+ }
+ ast_verb(3, "agent_call, call to agent '%s' call on '%s'\n", p->agent, p->chan->name);
+ ast_debug(3, "Playing beep, lang '%s'\n", p->chan->language);
+ res = ast_streamfile(p->chan, beep, p->chan->language);
+ ast_debug(3, "Played beep, result '%d'\n", res);
+ if (!res) {
+ res = ast_waitstream(p->chan, "");
+ ast_debug(3, "Waited for stream, result '%d'\n", res);
+ }
+ if (!res) {
+ res = ast_set_read_format(p->chan, ast_best_codec(p->chan->nativeformats));
+ ast_debug(3, "Set read format, result '%d'\n", res);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set read format to %s\n", ast_getformatname(ast_best_codec(p->chan->nativeformats)));
+ } else {
+ /* Agent hung-up */
+ p->chan = NULL;
+ }
+
+ if (!res) {
+ res = ast_set_write_format(p->chan, ast_best_codec(p->chan->nativeformats));
+ ast_debug(3, "Set write format, result '%d'\n", res);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set write format to %s\n", ast_getformatname(ast_best_codec(p->chan->nativeformats)));
+ }
+ if(!res) {
+ /* Call is immediately up, or might need ack */
+ if (p->ackcall > 1)
+ newstate = AST_STATE_RINGING;
+ else {
+ newstate = AST_STATE_UP;
+ if (recordagentcalls)
+ agent_start_monitoring(ast, 0);
+ p->acknowledged = 1;
+ }
+ res = 0;
+ }
+ CLEANUP(ast, p);
+ ast_mutex_unlock(&p->lock);
+ if (newstate)
+ ast_setstate(ast, newstate);
+ return res;
+}
+
+/*! \brief store/clear the global variable that stores agentid based on the callerid */
+static void set_agentbycallerid(const char *callerid, const char *agent)
+{
+ char buf[AST_MAX_BUF];
+
+ /* if there is no Caller ID, nothing to do */
+ if (ast_strlen_zero(callerid))
+ return;
+
+ snprintf(buf, sizeof(buf), "%s_%s", GETAGENTBYCALLERID, callerid);
+ pbx_builtin_setvar_helper(NULL, buf, agent);
+}
+
+/*! \brief return the channel or base channel if one exists. This function assumes the channel it is called on is already locked */
+struct ast_channel* agent_get_base_channel(struct ast_channel *chan)
+{
+ struct agent_pvt *p = NULL;
+ struct ast_channel *base = chan;
+
+ /* chan is locked by the calling function */
+ if (!chan || !chan->tech_pvt) {
+ ast_log(LOG_ERROR, "whoa, you need a channel (0x%ld) with a tech_pvt (0x%ld) to get a base channel.\n", (long)chan, (chan)?(long)chan->tech_pvt:(long)NULL);
+ return NULL;
+ }
+ p = chan->tech_pvt;
+ if (p->chan)
+ base = p->chan;
+ return base;
+}
+
+int agent_set_base_channel(struct ast_channel *chan, struct ast_channel *base)
+{
+ struct agent_pvt *p = NULL;
+
+ if (!chan || !base) {
+ ast_log(LOG_ERROR, "whoa, you need a channel (0x%ld) and a base channel (0x%ld) for setting.\n", (long)chan, (long)base);
+ return -1;
+ }
+ p = chan->tech_pvt;
+ if (!p) {
+ ast_log(LOG_ERROR, "whoa, channel %s is missing his tech_pvt structure!!.\n", chan->name);
+ return -1;
+ }
+ p->chan = base;
+ return 0;
+}
+
+static int agent_hangup(struct ast_channel *ast)
+{
+ struct agent_pvt *p = ast->tech_pvt;
+ int howlong = 0;
+ const char *status;
+ ast_mutex_lock(&p->lock);
+ p->owner = NULL;
+ ast->tech_pvt = NULL;
+ p->app_sleep_cond = 1;
+ p->acknowledged = 0;
+
+ /* if they really are hung up then set start to 0 so the test
+ * later if we're called on an already downed channel
+ * doesn't cause an agent to be logged out like when
+ * agent_request() is followed immediately by agent_hangup()
+ * as in apps/app_chanisavail.c:chanavail_exec()
+ */
+
+ ast_debug(1, "Hangup called for state %s\n", ast_state2str(ast->_state));
+ if (p->start && (ast->_state != AST_STATE_UP)) {
+ howlong = time(NULL) - p->start;
+ p->start = 0;
+ } else if (ast->_state == AST_STATE_RESERVED)
+ howlong = 0;
+ else
+ p->start = 0;
+ if (p->chan) {
+ p->chan->_bridge = NULL;
+ /* If they're dead, go ahead and hang up on the agent now */
+ if (!ast_strlen_zero(p->loginchan)) {
+ /* Store last disconnect time */
+ if (p->wrapuptime)
+ p->lastdisc = ast_tvadd(ast_tvnow(), ast_samp2tv(p->wrapuptime, 1000));
+ else
+ p->lastdisc = ast_tv(0,0);
+ if (p->chan) {
+ status = pbx_builtin_getvar_helper(p->chan, "CHANLOCALSTATUS");
+ if (autologoffunavail && status && !strcasecmp(status, "CHANUNAVAIL")) {
+ long logintime = time(NULL) - p->loginstart;
+ p->loginstart = 0;
+ ast_log(LOG_NOTICE, "Agent hangup: '%s' is not available now, auto logoff\n", p->name);
+ agent_logoff_maintenance(p, p->loginchan, logintime, ast->uniqueid, "Chanunavail");
+ }
+ /* Recognize the hangup and pass it along immediately */
+ ast_hangup(p->chan);
+ p->chan = NULL;
+ }
+ ast_debug(1, "Hungup, howlong is %d, autologoff is %d\n", howlong, p->autologoff);
+ if ((p->deferlogoff) || (howlong && p->autologoff && (howlong > p->autologoff))) {
+ long logintime = time(NULL) - p->loginstart;
+ p->loginstart = 0;
+ if (!p->deferlogoff)
+ ast_log(LOG_NOTICE, "Agent '%s' didn't answer/confirm within %d seconds (waited %d)\n", p->name, p->autologoff, howlong);
+ p->deferlogoff = 0;
+ agent_logoff_maintenance(p, p->loginchan, logintime, ast->uniqueid, "Autologoff");
+ if (persistent_agents)
+ dump_agents();
+ }
+ } else if (p->dead) {
+ ast_channel_lock(p->chan);
+ ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
+ ast_channel_unlock(p->chan);
+ } else if (p->loginstart) {
+ ast_channel_lock(p->chan);
+ ast_indicate_data(p->chan, AST_CONTROL_HOLD,
+ S_OR(p->moh, NULL),
+ !ast_strlen_zero(p->moh) ? strlen(p->moh) + 1 : 0);
+ ast_channel_unlock(p->chan);
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+
+ /* Only register a device state change if the agent is still logged in */
+ if (!p->loginstart) {
+ p->loginchan[0] = '\0';
+ p->logincallerid[0] = '\0';
+ if (persistent_agents)
+ dump_agents();
+ } else {
+ ast_device_state_changed("Agent/%s", p->agent);
+ }
+
+ if (p->pending) {
+ AST_LIST_LOCK(&agents);
+ AST_LIST_REMOVE(&agents, p, list);
+ AST_LIST_UNLOCK(&agents);
+ }
+ if (p->abouttograb) {
+ /* Let the "about to grab" thread know this isn't valid anymore, and let it
+ kill it later */
+ p->abouttograb = 0;
+ } else if (p->dead) {
+ ast_mutex_destroy(&p->lock);
+ ast_mutex_destroy(&p->app_lock);
+ ast_free(p);
+ } else {
+ if (p->chan) {
+ /* Not dead -- check availability now */
+ ast_mutex_lock(&p->lock);
+ /* Store last disconnect time */
+ p->lastdisc = ast_tvadd(ast_tvnow(), ast_samp2tv(p->wrapuptime, 1000));
+ ast_mutex_unlock(&p->lock);
+ }
+ /* Release ownership of the agent to other threads (presumably running the login app). */
+ if (ast_strlen_zero(p->loginchan))
+ ast_mutex_unlock(&p->app_lock);
+ }
+ return 0;
+}
+
+static int agent_cont_sleep( void *data )
+{
+ struct agent_pvt *p;
+ int res;
+
+ p = (struct agent_pvt *)data;
+
+ ast_mutex_lock(&p->lock);
+ res = p->app_sleep_cond;
+ if (p->lastdisc.tv_sec) {
+ if (ast_tvdiff_ms(ast_tvnow(), p->lastdisc) > 0)
+ res = 1;
+ }
+ ast_mutex_unlock(&p->lock);
+
+ if (!res)
+ ast_debug(5, "agent_cont_sleep() returning %d\n", res );
+
+ return res;
+}
+
+static int agent_ack_sleep(void *data)
+{
+ struct agent_pvt *p;
+ int res=0;
+ int to = 1000;
+ struct ast_frame *f;
+
+ /* Wait a second and look for something */
+
+ p = (struct agent_pvt *) data;
+ if (!p->chan)
+ return -1;
+
+ for(;;) {
+ to = ast_waitfor(p->chan, to);
+ if (to < 0)
+ return -1;
+ if (!to)
+ return 0;
+ f = ast_read(p->chan);
+ if (!f)
+ return -1;
+ if (f->frametype == AST_FRAME_DTMF)
+ res = f->subclass;
+ else
+ res = 0;
+ ast_frfree(f);
+ ast_mutex_lock(&p->lock);
+ if (!p->app_sleep_cond) {
+ ast_mutex_unlock(&p->lock);
+ return 0;
+ } else if (res == '#') {
+ ast_mutex_unlock(&p->lock);
+ return 1;
+ }
+ ast_mutex_unlock(&p->lock);
+ res = 0;
+ }
+ return res;
+}
+
+static struct ast_channel *agent_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge)
+{
+ struct agent_pvt *p = bridge->tech_pvt;
+ struct ast_channel *ret = NULL;
+
+ if (p) {
+ if (chan == p->chan)
+ ret = bridge->_bridge;
+ else if (chan == bridge->_bridge)
+ ret = p->chan;
+ }
+
+ ast_debug(1, "Asked for bridged channel on '%s'/'%s', returning '%s'\n", chan->name, bridge->name, ret ? ret->name : "<none>");
+ return ret;
+}
+
+/*! \brief Create new agent channel */
+static struct ast_channel *agent_new(struct agent_pvt *p, int state)
+{
+ struct ast_channel *tmp;
+#if 0
+ if (!p->chan) {
+ ast_log(LOG_WARNING, "No channel? :(\n");
+ return NULL;
+ }
+#endif
+ if (p->pending)
+ tmp = ast_channel_alloc(0, state, 0, 0, "", p->chan ? p->chan->exten:"", p->chan ? p->chan->context:"", 0, "Agent/P%s-%d", p->agent, ast_random() & 0xffff);
+ else
+ tmp = ast_channel_alloc(0, state, 0, 0, "", p->chan ? p->chan->exten:"", p->chan ? p->chan->context:"", 0, "Agent/%s", p->agent);
+ if (!tmp) {
+ ast_log(LOG_WARNING, "Unable to allocate agent channel structure\n");
+ return NULL;
+ }
+
+ tmp->tech = &agent_tech;
+ if (p->chan) {
+ tmp->nativeformats = p->chan->nativeformats;
+ tmp->writeformat = p->chan->writeformat;
+ tmp->rawwriteformat = p->chan->writeformat;
+ tmp->readformat = p->chan->readformat;
+ tmp->rawreadformat = p->chan->readformat;
+ ast_string_field_set(tmp, language, p->chan->language);
+ ast_copy_string(tmp->context, p->chan->context, sizeof(tmp->context));
+ ast_copy_string(tmp->exten, p->chan->exten, sizeof(tmp->exten));
+ /* XXX Is this really all we copy form the originating channel?? */
+ } else {
+ tmp->nativeformats = AST_FORMAT_SLINEAR;
+ tmp->writeformat = AST_FORMAT_SLINEAR;
+ tmp->rawwriteformat = AST_FORMAT_SLINEAR;
+ tmp->readformat = AST_FORMAT_SLINEAR;
+ tmp->rawreadformat = AST_FORMAT_SLINEAR;
+ }
+ /* Safe, agentlock already held */
+ tmp->tech_pvt = p;
+ p->owner = tmp;
+ tmp->priority = 1;
+ /* Wake up and wait for other applications (by definition the login app)
+ * to release this channel). Takes ownership of the agent channel
+ * to this thread only.
+ * For signalling the other thread, ast_queue_frame is used until we
+ * can safely use signals for this purpose. The pselect() needs to be
+ * implemented in the kernel for this.
+ */
+ p->app_sleep_cond = 0;
+ if(ast_strlen_zero(p->loginchan) && ast_mutex_trylock(&p->app_lock)) {
+ if (p->chan) {
+ ast_queue_frame(p->chan, &ast_null_frame);
+ ast_mutex_unlock(&p->lock); /* For other thread to read the condition. */
+ ast_mutex_lock(&p->app_lock);
+ ast_mutex_lock(&p->lock);
+ } else {
+ ast_log(LOG_WARNING, "Agent disconnected while we were connecting the call\n");
+ p->owner = NULL;
+ tmp->tech_pvt = NULL;
+ p->app_sleep_cond = 1;
+ ast_channel_free( tmp );
+ ast_mutex_unlock(&p->lock); /* For other thread to read the condition. */
+ ast_mutex_unlock(&p->app_lock);
+ return NULL;
+ }
+ } else if (!ast_strlen_zero(p->loginchan)) {
+ if (p->chan)
+ ast_queue_frame(p->chan, &ast_null_frame);
+ if (!p->chan) {
+ ast_log(LOG_WARNING, "Agent disconnected while we were connecting the call\n");
+ p->owner = NULL;
+ tmp->tech_pvt = NULL;
+ p->app_sleep_cond = 1;
+ ast_channel_free( tmp );
+ ast_mutex_unlock(&p->lock); /* For other thread to read the condition. */
+ return NULL;
+ }
+ }
+ if (p->chan)
+ ast_indicate(p->chan, AST_CONTROL_UNHOLD);
+ p->owning_app = pthread_self();
+ /* After the above step, there should not be any blockers. */
+ if (p->chan) {
+ if (ast_test_flag(p->chan, AST_FLAG_BLOCKING)) {
+ ast_log( LOG_ERROR, "A blocker exists after agent channel ownership acquired\n" );
+ CRASH;
+ }
+ }
+ return tmp;
+}
+
+
+/*!
+ * Read configuration data. The file named agents.conf.
+ *
+ * \returns Always 0, or so it seems.
+ */
+static int read_agent_config(int reload)
+{
+ struct ast_config *cfg;
+ struct ast_config *ucfg;
+ struct ast_variable *v;
+ struct agent_pvt *p;
+ const char *general_val;
+ const char *catname;
+ const char *hasagent;
+ int genhasagent;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+
+ group = 0;
+ autologoff = 0;
+ wrapuptime = 0;
+ ackcall = 0;
+ endcall = 1;
+ cfg = ast_config_load(config, config_flags);
+ if (!cfg) {
+ ast_log(LOG_NOTICE, "No agent configuration found -- agent support disabled\n");
+ return 0;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+ return -1;
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ p->dead = 1;
+ }
+ strcpy(moh, "default");
+ /* set the default recording values */
+ recordagentcalls = 0;
+ strcpy(recordformat, "wav");
+ strcpy(recordformatext, "wav");
+ urlprefix[0] = '\0';
+ savecallsin[0] = '\0';
+
+ /* Read in [general] section for persistence */
+ if ((general_val = ast_variable_retrieve(cfg, "general", "persistentagents")))
+ persistent_agents = ast_true(general_val);
+ multiplelogin = ast_true(ast_variable_retrieve(cfg, "general", "multiplelogin"));
+
+ /* Read in the [agents] section */
+ v = ast_variable_browse(cfg, "agents");
+ while(v) {
+ /* Create the interface list */
+ if (!strcasecmp(v->name, "agent")) {
+ add_agent(v->value, 0);
+ } else if (!strcasecmp(v->name, "group")) {
+ group = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "autologoff")) {
+ autologoff = atoi(v->value);
+ if (autologoff < 0)
+ autologoff = 0;
+ } else if (!strcasecmp(v->name, "ackcall")) {
+ if (!strcasecmp(v->value, "always"))
+ ackcall = 2;
+ else if (ast_true(v->value))
+ ackcall = 1;
+ else
+ ackcall = 0;
+ } else if (!strcasecmp(v->name, "endcall")) {
+ endcall = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "wrapuptime")) {
+ wrapuptime = atoi(v->value);
+ if (wrapuptime < 0)
+ wrapuptime = 0;
+ } else if (!strcasecmp(v->name, "maxlogintries") && !ast_strlen_zero(v->value)) {
+ maxlogintries = atoi(v->value);
+ if (maxlogintries < 0)
+ maxlogintries = 0;
+ } else if (!strcasecmp(v->name, "goodbye") && !ast_strlen_zero(v->value)) {
+ strcpy(agentgoodbye,v->value);
+ } else if (!strcasecmp(v->name, "musiconhold")) {
+ ast_copy_string(moh, v->value, sizeof(moh));
+ } else if (!strcasecmp(v->name, "updatecdr")) {
+ if (ast_true(v->value))
+ updatecdr = 1;
+ else
+ updatecdr = 0;
+ } else if (!strcasecmp(v->name, "autologoffunavail")) {
+ if (ast_true(v->value))
+ autologoffunavail = 1;
+ else
+ autologoffunavail = 0;
+ } else if (!strcasecmp(v->name, "recordagentcalls")) {
+ recordagentcalls = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "recordformat")) {
+ ast_copy_string(recordformat, v->value, sizeof(recordformat));
+ if (!strcasecmp(v->value, "wav49"))
+ strcpy(recordformatext, "WAV");
+ else
+ ast_copy_string(recordformatext, v->value, sizeof(recordformatext));
+ } else if (!strcasecmp(v->name, "urlprefix")) {
+ ast_copy_string(urlprefix, v->value, sizeof(urlprefix));
+ if (urlprefix[strlen(urlprefix) - 1] != '/')
+ strncat(urlprefix, "/", sizeof(urlprefix) - strlen(urlprefix) - 1);
+ } else if (!strcasecmp(v->name, "savecallsin")) {
+ if (v->value[0] == '/')
+ ast_copy_string(savecallsin, v->value, sizeof(savecallsin));
+ else
+ snprintf(savecallsin, sizeof(savecallsin) - 2, "/%s", v->value);
+ if (savecallsin[strlen(savecallsin) - 1] != '/')
+ strncat(savecallsin, "/", sizeof(savecallsin) - strlen(savecallsin) - 1);
+ } else if (!strcasecmp(v->name, "custom_beep")) {
+ ast_copy_string(beep, v->value, sizeof(beep));
+ }
+ v = v->next;
+ }
+ if ((ucfg = ast_config_load("users.conf", config_flags)) && ucfg != CONFIG_STATUS_FILEUNCHANGED) {
+ genhasagent = ast_true(ast_variable_retrieve(ucfg, "general", "hasagent"));
+ catname = ast_category_browse(ucfg, NULL);
+ while(catname) {
+ if (strcasecmp(catname, "general")) {
+ hasagent = ast_variable_retrieve(ucfg, catname, "hasagent");
+ if (ast_true(hasagent) || (!hasagent && genhasagent)) {
+ char tmp[256];
+ const char *fullname = ast_variable_retrieve(ucfg, catname, "fullname");
+ const char *secret = ast_variable_retrieve(ucfg, catname, "secret");
+ if (!fullname)
+ fullname = "";
+ if (!secret)
+ secret = "";
+ snprintf(tmp, sizeof(tmp), "%s,%s,%s", catname, secret,fullname);
+ add_agent(tmp, 0);
+ }
+ }
+ catname = ast_category_browse(ucfg, catname);
+ }
+ ast_config_destroy(ucfg);
+ }
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&agents, p, list) {
+ if (p->dead) {
+ AST_LIST_REMOVE_CURRENT(list);
+ /* Destroy if appropriate */
+ if (!p->owner) {
+ if (!p->chan) {
+ ast_mutex_destroy(&p->lock);
+ ast_mutex_destroy(&p->app_lock);
+ ast_free(p);
+ } else {
+ /* Cause them to hang up */
+ ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
+ }
+ }
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&agents);
+ ast_config_destroy(cfg);
+ return 1;
+}
+
+static int check_availability(struct agent_pvt *newlyavailable, int needlock)
+{
+ struct ast_channel *chan=NULL, *parent=NULL;
+ struct agent_pvt *p;
+ int res;
+
+ ast_debug(1, "Checking availability of '%s'\n", newlyavailable->agent);
+ if (needlock)
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ if (p == newlyavailable) {
+ continue;
+ }
+ ast_mutex_lock(&p->lock);
+ if (!p->abouttograb && p->pending && ((p->group && (newlyavailable->group & p->group)) || !strcmp(p->agent, newlyavailable->agent))) {
+ ast_debug(1, "Call '%s' looks like a winner for agent '%s'\n", p->owner->name, newlyavailable->agent);
+ /* We found a pending call, time to merge */
+ chan = agent_new(newlyavailable, AST_STATE_DOWN);
+ parent = p->owner;
+ p->abouttograb = 1;
+ ast_mutex_unlock(&p->lock);
+ break;
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ if (needlock)
+ AST_LIST_UNLOCK(&agents);
+ if (parent && chan) {
+ if (newlyavailable->ackcall > 1) {
+ /* Don't do beep here */
+ res = 0;
+ } else {
+ ast_debug(3, "Playing beep, lang '%s'\n", newlyavailable->chan->language);
+ res = ast_streamfile(newlyavailable->chan, beep, newlyavailable->chan->language);
+ ast_debug(3, "Played beep, result '%d'\n", res);
+ if (!res) {
+ res = ast_waitstream(newlyavailable->chan, "");
+ ast_debug(1, "Waited for stream, result '%d'\n", res);
+ }
+ }
+ if (!res) {
+ /* Note -- parent may have disappeared */
+ if (p->abouttograb) {
+ newlyavailable->acknowledged = 1;
+ /* Safe -- agent lock already held */
+ ast_setstate(parent, AST_STATE_UP);
+ ast_setstate(chan, AST_STATE_UP);
+ ast_copy_string(parent->context, chan->context, sizeof(parent->context));
+ /* Go ahead and mark the channel as a zombie so that masquerade will
+ destroy it for us, and we need not call ast_hangup */
+ ast_set_flag(chan, AST_FLAG_ZOMBIE);
+ ast_channel_masquerade(parent, chan);
+ p->abouttograb = 0;
+ } else {
+ ast_debug(1, "Sneaky, parent disappeared in the mean time...\n");
+ agent_cleanup(newlyavailable);
+ }
+ } else {
+ ast_debug(1, "Ugh... Agent hung up at exactly the wrong time\n");
+ agent_cleanup(newlyavailable);
+ }
+ }
+ return 0;
+}
+
+static int check_beep(struct agent_pvt *newlyavailable, int needlock)
+{
+ struct agent_pvt *p;
+ int res=0;
+
+ ast_debug(1, "Checking beep availability of '%s'\n", newlyavailable->agent);
+ if (needlock)
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ if (p == newlyavailable) {
+ continue;
+ }
+ ast_mutex_lock(&p->lock);
+ if (!p->abouttograb && p->pending && ((p->group && (newlyavailable->group & p->group)) || !strcmp(p->agent, newlyavailable->agent))) {
+ ast_debug(1, "Call '%s' looks like a would-be winner for agent '%s'\n", p->owner->name, newlyavailable->agent);
+ ast_mutex_unlock(&p->lock);
+ break;
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ if (needlock)
+ AST_LIST_UNLOCK(&agents);
+ if (p) {
+ ast_mutex_unlock(&newlyavailable->lock);
+ ast_debug(3, "Playing beep, lang '%s'\n", newlyavailable->chan->language);
+ res = ast_streamfile(newlyavailable->chan, beep, newlyavailable->chan->language);
+ ast_debug(1, "Played beep, result '%d'\n", res);
+ if (!res) {
+ res = ast_waitstream(newlyavailable->chan, "");
+ ast_debug(1, "Waited for stream, result '%d'\n", res);
+ }
+ ast_mutex_lock(&newlyavailable->lock);
+ }
+ return res;
+}
+
+/*! \brief Part of the Asterisk PBX interface */
+static struct ast_channel *agent_request(const char *type, int format, void *data, int *cause)
+{
+ struct agent_pvt *p;
+ struct ast_channel *chan = NULL;
+ char *s;
+ ast_group_t groupmatch;
+ int groupoff;
+ int waitforagent=0;
+ int hasagent = 0;
+ struct timeval tv;
+
+ s = data;
+ if ((s[0] == '@') && (sscanf(s + 1, "%d", &groupoff) == 1)) {
+ groupmatch = (1 << groupoff);
+ } else if ((s[0] == ':') && (sscanf(s + 1, "%d", &groupoff) == 1)) {
+ groupmatch = (1 << groupoff);
+ waitforagent = 1;
+ } else
+ groupmatch = 0;
+
+ /* Check actual logged in agents first */
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ ast_mutex_lock(&p->lock);
+ if (!p->pending && ((groupmatch && (p->group & groupmatch)) || !strcmp(data, p->agent)) &&
+ ast_strlen_zero(p->loginchan)) {
+ if (p->chan)
+ hasagent++;
+ tv = ast_tvnow();
+ if (!p->lastdisc.tv_sec || (tv.tv_sec >= p->lastdisc.tv_sec)) {
+ p->lastdisc = ast_tv(0, 0);
+ /* Agent must be registered, but not have any active call, and not be in a waiting state */
+ if (!p->owner && p->chan) {
+ /* Fixed agent */
+ chan = agent_new(p, AST_STATE_DOWN);
+ }
+ if (chan) {
+ ast_mutex_unlock(&p->lock);
+ break;
+ }
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ if (!p) {
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ ast_mutex_lock(&p->lock);
+ if (!p->pending && ((groupmatch && (p->group & groupmatch)) || !strcmp(data, p->agent))) {
+ if (p->chan || !ast_strlen_zero(p->loginchan))
+ hasagent++;
+ tv = ast_tvnow();
+#if 0
+ ast_log(LOG_NOTICE, "Time now: %ld, Time of lastdisc: %ld\n", tv.tv_sec, p->lastdisc.tv_sec);
+#endif
+ if (!p->lastdisc.tv_sec || (tv.tv_sec >= p->lastdisc.tv_sec)) {
+ p->lastdisc = ast_tv(0, 0);
+ /* Agent must be registered, but not have any active call, and not be in a waiting state */
+ if (!p->owner && p->chan) {
+ /* Could still get a fixed agent */
+ chan = agent_new(p, AST_STATE_DOWN);
+ } else if (!p->owner && !ast_strlen_zero(p->loginchan)) {
+ /* Adjustable agent */
+ p->chan = ast_request("Local", format, p->loginchan, cause);
+ if (p->chan)
+ chan = agent_new(p, AST_STATE_DOWN);
+ }
+ if (chan) {
+ ast_mutex_unlock(&p->lock);
+ break;
+ }
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ }
+
+ if (!chan && waitforagent) {
+ /* No agent available -- but we're requesting to wait for one.
+ Allocate a place holder */
+ if (hasagent) {
+ ast_debug(1, "Creating place holder for '%s'\n", s);
+ p = add_agent(data, 1);
+ p->group = groupmatch;
+ chan = agent_new(p, AST_STATE_DOWN);
+ if (!chan)
+ ast_log(LOG_WARNING, "Weird... Fix this to drop the unused pending agent\n");
+ } else {
+ ast_debug(1, "Not creating place holder for '%s' since nobody logged in\n", s);
+ }
+ }
+ *cause = hasagent ? AST_CAUSE_BUSY : AST_CAUSE_UNREGISTERED;
+ AST_LIST_UNLOCK(&agents);
+ return chan;
+}
+
+static force_inline int powerof(unsigned int d)
+{
+ int x = ffs(d);
+
+ if (x)
+ return x - 1;
+
+ return 0;
+}
+
+/*!
+ * Lists agents and their status to the Manager API.
+ * It is registered on load_module() and it gets called by the manager backend.
+ * \param s
+ * \param m
+ * \returns
+ * \sa action_agent_logoff(), load_module().
+ */
+static int action_agents(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m,"ActionID");
+ char idText[256] = "";
+ char chanbuf[256];
+ struct agent_pvt *p;
+ char *username = NULL;
+ char *loginChan = NULL;
+ char *talkingto = NULL;
+ char *talkingtoChan = NULL;
+ char *status = NULL;
+
+ if (!ast_strlen_zero(id))
+ snprintf(idText, sizeof(idText) ,"ActionID: %s\r\n", id);
+ astman_send_ack(s, m, "Agents will follow");
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ ast_mutex_lock(&p->lock);
+
+ /* Status Values:
+ AGENT_LOGGEDOFF - Agent isn't logged in
+ AGENT_IDLE - Agent is logged in, and waiting for call
+ AGENT_ONCALL - Agent is logged in, and on a call
+ AGENT_UNKNOWN - Don't know anything about agent. Shouldn't ever get this. */
+
+ username = S_OR(p->name, "None");
+
+ /* Set a default status. It 'should' get changed. */
+ status = "AGENT_UNKNOWN";
+
+ if (!ast_strlen_zero(p->loginchan) && !p->chan) {
+ loginChan = p->loginchan;
+ talkingto = "n/a";
+ talkingtoChan = "n/a";
+ status = "AGENT_IDLE";
+ if (p->acknowledged) {
+ snprintf(chanbuf, sizeof(chanbuf), " %s (Confirmed)", p->loginchan);
+ loginChan = chanbuf;
+ }
+ } else if (p->chan) {
+ loginChan = ast_strdupa(p->chan->name);
+ if (p->owner && p->owner->_bridge) {
+ talkingto = p->chan->cid.cid_num;
+ if (ast_bridged_channel(p->owner))
+ talkingtoChan = ast_strdupa(ast_bridged_channel(p->owner)->name);
+ else
+ talkingtoChan = "n/a";
+ status = "AGENT_ONCALL";
+ } else {
+ talkingto = "n/a";
+ talkingtoChan = "n/a";
+ status = "AGENT_IDLE";
+ }
+ } else {
+ loginChan = "n/a";
+ talkingto = "n/a";
+ talkingtoChan = "n/a";
+ status = "AGENT_LOGGEDOFF";
+ }
+
+ astman_append(s, "Event: Agents\r\n"
+ "Agent: %s\r\n"
+ "Name: %s\r\n"
+ "Status: %s\r\n"
+ "LoggedInChan: %s\r\n"
+ "LoggedInTime: %d\r\n"
+ "TalkingTo: %s\r\n"
+ "TalkingToChan: %s\r\n"
+ "%s"
+ "\r\n",
+ p->agent, username, status, loginChan, (int)p->loginstart, talkingto, talkingtoChan, idText);
+ ast_mutex_unlock(&p->lock);
+ }
+ AST_LIST_UNLOCK(&agents);
+ astman_append(s, "Event: AgentsComplete\r\n"
+ "%s"
+ "\r\n",idText);
+ return 0;
+}
+
+static void agent_logoff_maintenance(struct agent_pvt *p, char *loginchan, long logintime, const char *uniqueid, char *logcommand)
+{
+ char *tmp = NULL;
+ char agent[AST_MAX_AGENT];
+
+ if (!ast_strlen_zero(logcommand))
+ tmp = logcommand;
+ else
+ tmp = ast_strdupa("");
+
+ snprintf(agent, sizeof(agent), "Agent/%s", p->agent);
+
+ if (!ast_strlen_zero(uniqueid)) {
+ manager_event(EVENT_FLAG_AGENT, "Agentcallbacklogoff",
+ "Agent: %s\r\n"
+ "Reason: %s\r\n"
+ "Loginchan: %s\r\n"
+ "Logintime: %ld\r\n"
+ "Uniqueid: %s\r\n",
+ p->agent, tmp, loginchan, logintime, uniqueid);
+ } else {
+ manager_event(EVENT_FLAG_AGENT, "Agentcallbacklogoff",
+ "Agent: %s\r\n"
+ "Reason: %s\r\n"
+ "Loginchan: %s\r\n"
+ "Logintime: %ld\r\n",
+ p->agent, tmp, loginchan, logintime);
+ }
+
+ ast_queue_log("NONE", ast_strlen_zero(uniqueid) ? "NONE" : uniqueid, agent, "AGENTCALLBACKLOGOFF", "%s|%ld|%s", loginchan, logintime, tmp);
+ set_agentbycallerid(p->logincallerid, NULL);
+ p->loginchan[0] ='\0';
+ p->logincallerid[0] = '\0';
+ ast_device_state_changed("Agent/%s", p->agent);
+ if (persistent_agents)
+ dump_agents();
+
+}
+
+static int agent_logoff(const char *agent, int soft)
+{
+ struct agent_pvt *p;
+ long logintime;
+ int ret = -1; /* Return -1 if no agent if found */
+
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ if (!strcasecmp(p->agent, agent)) {
+ ret = 0;
+ if (p->owner || p->chan) {
+ if (!soft) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_EXPLICIT);
+ if (p->chan)
+ ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
+ } else
+ p->deferlogoff = 1;
+ } else {
+ logintime = time(NULL) - p->loginstart;
+ p->loginstart = 0;
+ agent_logoff_maintenance(p, p->loginchan, logintime, NULL, "CommandLogoff");
+ }
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static char *agent_logoff_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int ret;
+ char *agent;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "agent logoff";
+ e->usage =
+ "Usage: agent logoff <channel> [soft]\n"
+ " Sets an agent as no longer logged in.\n"
+ " If 'soft' is specified, do not hangup existing calls.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_agent_logoff_cmd(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc < 3 || a->argc > 4)
+ return CLI_SHOWUSAGE;
+ if (a->argc == 4 && strcasecmp(a->argv[3], "soft"))
+ return CLI_SHOWUSAGE;
+
+ agent = a->argv[2] + 6;
+ ret = agent_logoff(agent, a->argc == 4);
+ if (ret == 0)
+ ast_cli(a->fd, "Logging out %s\n", agent);
+
+ return CLI_SUCCESS;
+}
+
+/*!
+ * Sets an agent as no longer logged in in the Manager API.
+ * It is registered on load_module() and it gets called by the manager backend.
+ * \param s
+ * \param m
+ * \returns
+ * \sa action_agents(), load_module().
+ */
+static int action_agent_logoff(struct mansession *s, const struct message *m)
+{
+ const char *agent = astman_get_header(m, "Agent");
+ const char *soft_s = astman_get_header(m, "Soft"); /* "true" is don't hangup */
+ int soft;
+ int ret; /* return value of agent_logoff */
+
+ if (ast_strlen_zero(agent)) {
+ astman_send_error(s, m, "No agent specified");
+ return 0;
+ }
+
+ soft = ast_true(soft_s) ? 1 : 0;
+ ret = agent_logoff(agent, soft);
+ if (ret == 0)
+ astman_send_ack(s, m, "Agent logged out");
+ else
+ astman_send_error(s, m, "No such agent");
+
+ return 0;
+}
+
+static char *complete_agent_logoff_cmd(const char *line, const char *word, int pos, int state)
+{
+ if (pos == 2) {
+ struct agent_pvt *p;
+ char name[AST_MAX_AGENT];
+ int which = 0, len = strlen(word);
+
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ snprintf(name, sizeof(name), "Agent/%s", p->agent);
+ if (!strncasecmp(word, name, len) && p->loginstart && ++which > state)
+ return ast_strdup(name);
+ }
+ } else if (pos == 3 && state == 0)
+ return ast_strdup("soft");
+
+ return NULL;
+}
+
+/*!
+ * Show agents in cli.
+ */
+static char *agents_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct agent_pvt *p;
+ char username[AST_MAX_BUF];
+ char location[AST_MAX_BUF] = "";
+ char talkingto[AST_MAX_BUF] = "";
+ char moh[AST_MAX_BUF];
+ int count_agents = 0; /*!< Number of agents configured */
+ int online_agents = 0; /*!< Number of online agents */
+ int offline_agents = 0; /*!< Number of offline agents */
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "agent show";
+ e->usage =
+ "Usage: agent show\n"
+ " Provides summary information on agents.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 2)
+ return CLI_SHOWUSAGE;
+
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ ast_mutex_lock(&p->lock);
+ if (p->pending) {
+ if (p->group)
+ ast_cli(a->fd, "-- Pending call to group %d\n", powerof(p->group));
+ else
+ ast_cli(a->fd, "-- Pending call to agent %s\n", p->agent);
+ } else {
+ if (!ast_strlen_zero(p->name))
+ snprintf(username, sizeof(username), "(%s) ", p->name);
+ else
+ username[0] = '\0';
+ if (p->chan) {
+ snprintf(location, sizeof(location), "logged in on %s", p->chan->name);
+ if (p->owner && ast_bridged_channel(p->owner))
+ snprintf(talkingto, sizeof(talkingto), " talking to %s", ast_bridged_channel(p->owner)->name);
+ else
+ strcpy(talkingto, " is idle");
+ online_agents++;
+ } else if (!ast_strlen_zero(p->loginchan)) {
+ if (ast_tvdiff_ms(ast_tvnow(), p->lastdisc) > 0 || !(p->lastdisc.tv_sec))
+ snprintf(location, sizeof(location) - 20, "available at '%s'", p->loginchan);
+ else
+ snprintf(location, sizeof(location) - 20, "wrapping up at '%s'", p->loginchan);
+ talkingto[0] = '\0';
+ online_agents++;
+ if (p->acknowledged)
+ strncat(location, " (Confirmed)", sizeof(location) - strlen(location) - 1);
+ } else {
+ strcpy(location, "not logged in");
+ talkingto[0] = '\0';
+ offline_agents++;
+ }
+ if (!ast_strlen_zero(p->moh))
+ snprintf(moh, sizeof(moh), " (musiconhold is '%s')", p->moh);
+ ast_cli(a->fd, "%-12.12s %s%s%s%s\n", p->agent,
+ username, location, talkingto, moh);
+ count_agents++;
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ AST_LIST_UNLOCK(&agents);
+ if ( !count_agents )
+ ast_cli(a->fd, "No Agents are configured in %s\n",config);
+ else
+ ast_cli(a->fd, "%d agents configured [%d online , %d offline]\n",count_agents, online_agents, offline_agents);
+ ast_cli(a->fd, "\n");
+
+ return CLI_SUCCESS;
+}
+
+
+static char *agents_show_online(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct agent_pvt *p;
+ char username[AST_MAX_BUF];
+ char location[AST_MAX_BUF] = "";
+ char talkingto[AST_MAX_BUF] = "";
+ char moh[AST_MAX_BUF];
+ int count_agents = 0; /* Number of agents configured */
+ int online_agents = 0; /* Number of online agents */
+ int agent_status = 0; /* 0 means offline, 1 means online */
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "agent show online";
+ e->usage =
+ "Usage: agent show online\n"
+ " Provides a list of all online agents.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ agent_status = 0; /* reset it to offline */
+ ast_mutex_lock(&p->lock);
+ if (!ast_strlen_zero(p->name))
+ snprintf(username, sizeof(username), "(%s) ", p->name);
+ else
+ username[0] = '\0';
+ if (p->chan) {
+ snprintf(location, sizeof(location), "logged in on %s", p->chan->name);
+ if (p->owner && ast_bridged_channel(p->owner))
+ snprintf(talkingto, sizeof(talkingto), " talking to %s", ast_bridged_channel(p->owner)->name);
+ else
+ strcpy(talkingto, " is idle");
+ agent_status = 1;
+ online_agents++;
+ } else if (!ast_strlen_zero(p->loginchan)) {
+ snprintf(location, sizeof(location) - 20, "available at '%s'", p->loginchan);
+ talkingto[0] = '\0';
+ agent_status = 1;
+ online_agents++;
+ if (p->acknowledged)
+ strncat(location, " (Confirmed)", sizeof(location) - strlen(location) - 1);
+ }
+ if (!ast_strlen_zero(p->moh))
+ snprintf(moh, sizeof(moh), " (musiconhold is '%s')", p->moh);
+ if (agent_status)
+ ast_cli(a->fd, "%-12.12s %s%s%s%s\n", p->agent, username, location, talkingto, moh);
+ count_agents++;
+ ast_mutex_unlock(&p->lock);
+ }
+ AST_LIST_UNLOCK(&agents);
+ if (!count_agents)
+ ast_cli(a->fd, "No Agents are configured in %s\n", config);
+ else
+ ast_cli(a->fd, "%d agents online\n", online_agents);
+ ast_cli(a->fd, "\n");
+ return CLI_SUCCESS;
+}
+
+static const char agent_logoff_usage[] =
+"Usage: agent logoff <channel> [soft]\n"
+" Sets an agent as no longer logged in.\n"
+" If 'soft' is specified, do not hangup existing calls.\n";
+
+static struct ast_cli_entry cli_agents[] = {
+ AST_CLI_DEFINE(agents_show, "Show status of agents"),
+ AST_CLI_DEFINE(agents_show_online, "Show all online agents"),
+ AST_CLI_DEFINE(agent_logoff_cmd, "Sets an agent offline"),
+};
+
+/*!
+ * Called by the AgentLogin application (from the dial plan).
+ *
+ * \brief Log in agent application.
+ *
+ * \param chan
+ * \param data
+ * \returns
+ * \sa agentmonitoroutgoing_exec(), load_module().
+ */
+static int login_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ int tries = 0;
+ int max_login_tries = maxlogintries;
+ struct agent_pvt *p;
+ struct ast_module_user *u;
+ int login_state = 0;
+ char user[AST_MAX_AGENT] = "";
+ char pass[AST_MAX_AGENT];
+ char agent[AST_MAX_AGENT] = "";
+ char xpass[AST_MAX_AGENT] = "";
+ char *errmsg;
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(agent_id);
+ AST_APP_ARG(options);
+ AST_APP_ARG(extension);
+ );
+ const char *tmpoptions = NULL;
+ int play_announcement = 1;
+ char agent_goodbye[AST_MAX_FILENAME_LEN];
+ int update_cdr = updatecdr;
+ char *filename = "agent-loginok";
+
+ u = ast_module_user_add(chan);
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ ast_copy_string(agent_goodbye, agentgoodbye, sizeof(agent_goodbye));
+
+ /* Set Channel Specific Login Overrides */
+ if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTLMAXLOGINTRIES"))) {
+ max_login_tries = atoi(pbx_builtin_getvar_helper(chan, "AGENTMAXLOGINTRIES"));
+ if (max_login_tries < 0)
+ max_login_tries = 0;
+ tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTMAXLOGINTRIES");
+ ast_verb(3, "Saw variable AGENTMAXLOGINTRIES=%s, setting max_login_tries to: %d on Channel '%s'.\n",tmpoptions,max_login_tries,chan->name);
+ }
+ if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR"))) {
+ if (ast_true(pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR")))
+ update_cdr = 1;
+ else
+ update_cdr = 0;
+ tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR");
+ ast_verb(3, "Saw variable AGENTUPDATECDR=%s, setting update_cdr to: %d on Channel '%s'.\n",tmpoptions,update_cdr,chan->name);
+ }
+ if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTGOODBYE"))) {
+ strcpy(agent_goodbye, pbx_builtin_getvar_helper(chan, "AGENTGOODBYE"));
+ tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTGOODBYE");
+ ast_verb(3, "Saw variable AGENTGOODBYE=%s, setting agent_goodbye to: %s on Channel '%s'.\n",tmpoptions,agent_goodbye,chan->name);
+ }
+ /* End Channel Specific Login Overrides */
+
+ if (!ast_strlen_zero(args.options)) {
+ if (strchr(args.options, 's')) {
+ play_announcement = 0;
+ }
+ }
+
+ if (chan->_state != AST_STATE_UP)
+ res = ast_answer(chan);
+ if (!res) {
+ if (!ast_strlen_zero(args.agent_id))
+ ast_copy_string(user, args.agent_id, AST_MAX_AGENT);
+ else
+ res = ast_app_getdata(chan, "agent-user", user, sizeof(user) - 1, 0);
+ }
+ while (!res && (max_login_tries==0 || tries < max_login_tries)) {
+ tries++;
+ /* Check for password */
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ if (!strcmp(p->agent, user) && !p->pending)
+ ast_copy_string(xpass, p->password, sizeof(xpass));
+ }
+ AST_LIST_UNLOCK(&agents);
+ if (!res) {
+ if (!ast_strlen_zero(xpass))
+ res = ast_app_getdata(chan, "agent-pass", pass, sizeof(pass) - 1, 0);
+ else
+ pass[0] = '\0';
+ }
+ errmsg = "agent-incorrect";
+
+#if 0
+ ast_log(LOG_NOTICE, "user: %s, pass: %s\n", user, pass);
+#endif
+
+ /* Check again for accuracy */
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ ast_mutex_lock(&p->lock);
+ if (!strcmp(p->agent, user) &&
+ !strcmp(p->password, pass) && !p->pending) {
+ login_state = 1; /* Successful Login */
+
+ /* Ensure we can't be gotten until we're done */
+ p->lastdisc = ast_tvnow();
+ p->lastdisc.tv_sec++;
+
+ /* Set Channel Specific Agent Overrides */
+ if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTACKCALL"))) {
+ if (!strcasecmp(pbx_builtin_getvar_helper(chan, "AGENTACKCALL"), "always"))
+ p->ackcall = 2;
+ else if (ast_true(pbx_builtin_getvar_helper(chan, "AGENTACKCALL")))
+ p->ackcall = 1;
+ else
+ p->ackcall = 0;
+ tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTACKCALL");
+ ast_verb(3, "Saw variable AGENTACKCALL=%s, setting ackcall to: %d for Agent '%s'.\n",tmpoptions,p->ackcall,p->agent);
+ }
+ if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTAUTOLOGOFF"))) {
+ p->autologoff = atoi(pbx_builtin_getvar_helper(chan, "AGENTAUTOLOGOFF"));
+ if (p->autologoff < 0)
+ p->autologoff = 0;
+ tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTAUTOLOGOFF");
+ ast_verb(3, "Saw variable AGENTAUTOLOGOFF=%s, setting autologff to: %d for Agent '%s'.\n",tmpoptions,p->autologoff,p->agent);
+ }
+ if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTWRAPUPTIME"))) {
+ p->wrapuptime = atoi(pbx_builtin_getvar_helper(chan, "AGENTWRAPUPTIME"));
+ if (p->wrapuptime < 0)
+ p->wrapuptime = 0;
+ tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTWRAPUPTIME");
+ ast_verb(3, "Saw variable AGENTWRAPUPTIME=%s, setting wrapuptime to: %d for Agent '%s'.\n",tmpoptions,p->wrapuptime,p->agent);
+ }
+ /* End Channel Specific Agent Overrides */
+ if (!p->chan) {
+ long logintime;
+ snprintf(agent, sizeof(agent), "Agent/%s", p->agent);
+
+ p->loginchan[0] = '\0';
+ p->logincallerid[0] = '\0';
+ p->acknowledged = 0;
+
+ ast_mutex_unlock(&p->lock);
+ AST_LIST_UNLOCK(&agents);
+ if( !res && play_announcement==1 )
+ res = ast_streamfile(chan, filename, chan->language);
+ if (!res)
+ ast_waitstream(chan, "");
+ AST_LIST_LOCK(&agents);
+ ast_mutex_lock(&p->lock);
+ if (!res) {
+ res = ast_set_read_format(chan, ast_best_codec(chan->nativeformats));
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set read format to %d\n", ast_best_codec(chan->nativeformats));
+ }
+ if (!res) {
+ res = ast_set_write_format(chan, ast_best_codec(chan->nativeformats));
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set write format to %d\n", ast_best_codec(chan->nativeformats));
+ }
+ /* Check once more just in case */
+ if (p->chan)
+ res = -1;
+ if (!res) {
+ ast_indicate_data(chan, AST_CONTROL_HOLD,
+ S_OR(p->moh, NULL),
+ !ast_strlen_zero(p->moh) ? strlen(p->moh) + 1 : 0);
+ if (p->loginstart == 0)
+ time(&p->loginstart);
+ manager_event(EVENT_FLAG_AGENT, "Agentlogin",
+ "Agent: %s\r\n"
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n",
+ p->agent, chan->name, chan->uniqueid);
+ if (update_cdr && chan->cdr)
+ snprintf(chan->cdr->channel, sizeof(chan->cdr->channel), "Agent/%s", p->agent);
+ ast_queue_log("NONE", chan->uniqueid, agent, "AGENTLOGIN", "%s", chan->name);
+ ast_verb(2, "Agent '%s' logged in (format %s/%s)\n", p->agent,
+ ast_getformatname(chan->readformat), ast_getformatname(chan->writeformat));
+ /* Login this channel and wait for it to go away */
+ p->chan = chan;
+ if (p->ackcall > 1)
+ check_beep(p, 0);
+ else
+ check_availability(p, 0);
+ ast_mutex_unlock(&p->lock);
+ AST_LIST_UNLOCK(&agents);
+ ast_device_state_changed("Agent/%s", p->agent);
+ while (res >= 0) {
+ ast_mutex_lock(&p->lock);
+ if (p->deferlogoff && p->chan) {
+ ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
+ p->deferlogoff = 0;
+ }
+ if (p->chan != chan)
+ res = -1;
+ ast_mutex_unlock(&p->lock);
+ /* Yield here so other interested threads can kick in. */
+ sched_yield();
+ if (res)
+ break;
+
+ AST_LIST_LOCK(&agents);
+ ast_mutex_lock(&p->lock);
+ if (p->lastdisc.tv_sec) {
+ if (ast_tvdiff_ms(ast_tvnow(), p->lastdisc) > 0) {
+ ast_debug(1, "Wrapup time for %s expired!\n", p->agent);
+ p->lastdisc = ast_tv(0, 0);
+ ast_device_state_changed("Agent/%s", p->agent);
+ if (p->ackcall > 1)
+ check_beep(p, 0);
+ else
+ check_availability(p, 0);
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ AST_LIST_UNLOCK(&agents);
+ /* Synchronize channel ownership between call to agent and itself. */
+ ast_mutex_lock( &p->app_lock );
+ ast_mutex_lock(&p->lock);
+ p->owning_app = pthread_self();
+ ast_mutex_unlock(&p->lock);
+ if (p->ackcall > 1)
+ res = agent_ack_sleep(p);
+ else
+ res = ast_safe_sleep_conditional( chan, 1000, agent_cont_sleep, p );
+ ast_mutex_unlock( &p->app_lock );
+ if ((p->ackcall > 1) && (res == 1)) {
+ AST_LIST_LOCK(&agents);
+ ast_mutex_lock(&p->lock);
+ check_availability(p, 0);
+ ast_mutex_unlock(&p->lock);
+ AST_LIST_UNLOCK(&agents);
+ res = 0;
+ }
+ sched_yield();
+ }
+ ast_mutex_lock(&p->lock);
+ if (res && p->owner)
+ ast_log(LOG_WARNING, "Huh? We broke out when there was still an owner?\n");
+ /* Log us off if appropriate */
+ if (p->chan == chan)
+ p->chan = NULL;
+ p->acknowledged = 0;
+ logintime = time(NULL) - p->loginstart;
+ p->loginstart = 0;
+ ast_mutex_unlock(&p->lock);
+ manager_event(EVENT_FLAG_AGENT, "Agentlogoff",
+ "Agent: %s\r\n"
+ "Logintime: %ld\r\n"
+ "Uniqueid: %s\r\n",
+ p->agent, logintime, chan->uniqueid);
+ ast_queue_log("NONE", chan->uniqueid, agent, "AGENTLOGOFF", "%s|%ld", chan->name, logintime);
+ ast_verb(2, "Agent '%s' logged out\n", p->agent);
+ /* If there is no owner, go ahead and kill it now */
+ ast_device_state_changed("Agent/%s", p->agent);
+ if (p->dead && !p->owner) {
+ ast_mutex_destroy(&p->lock);
+ ast_mutex_destroy(&p->app_lock);
+ ast_free(p);
+ }
+ }
+ else {
+ ast_mutex_unlock(&p->lock);
+ p = NULL;
+ }
+ res = -1;
+ } else {
+ ast_mutex_unlock(&p->lock);
+ errmsg = "agent-alreadyon";
+ p = NULL;
+ }
+ break;
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ if (!p)
+ AST_LIST_UNLOCK(&agents);
+
+ if (!res && (max_login_tries==0 || tries < max_login_tries))
+ res = ast_app_getdata(chan, errmsg, user, sizeof(user) - 1, 0);
+ }
+
+ if (!res)
+ res = ast_safe_sleep(chan, 500);
+
+ ast_module_user_remove(u);
+
+ return -1;
+}
+
+/*!
+ * \brief Called by the AgentMonitorOutgoing application (from the dial plan).
+ *
+ * \param chan
+ * \param data
+ * \returns
+ * \sa login_exec(), load_module().
+ */
+static int agentmonitoroutgoing_exec(struct ast_channel *chan, void *data)
+{
+ int exitifnoagentid = 0;
+ int nowarnings = 0;
+ int changeoutgoing = 0;
+ int res = 0;
+ char agent[AST_MAX_AGENT];
+
+ if (data) {
+ if (strchr(data, 'd'))
+ exitifnoagentid = 1;
+ if (strchr(data, 'n'))
+ nowarnings = 1;
+ if (strchr(data, 'c'))
+ changeoutgoing = 1;
+ }
+ if (chan->cid.cid_num) {
+ const char *tmp;
+ char agentvar[AST_MAX_BUF];
+ snprintf(agentvar, sizeof(agentvar), "%s_%s", GETAGENTBYCALLERID, chan->cid.cid_num);
+ if ((tmp = pbx_builtin_getvar_helper(NULL, agentvar))) {
+ struct agent_pvt *p;
+ ast_copy_string(agent, tmp, sizeof(agent));
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ if (!strcasecmp(p->agent, tmp)) {
+ if (changeoutgoing) snprintf(chan->cdr->channel, sizeof(chan->cdr->channel), "Agent/%s", p->agent);
+ __agent_start_monitoring(chan, p, 1);
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&agents);
+
+ } else {
+ res = -1;
+ if (!nowarnings)
+ ast_log(LOG_WARNING, "Couldn't find the global variable %s, so I can't figure out which agent (if it's an agent) is placing outgoing call.\n", agentvar);
+ }
+ } else {
+ res = -1;
+ if (!nowarnings)
+ ast_log(LOG_WARNING, "There is no callerid on that call, so I can't figure out which agent (if it's an agent) is placing outgoing call.\n");
+ }
+ if (res) {
+ if (exitifnoagentid)
+ return res;
+ }
+ return 0;
+}
+
+/*!
+ * \brief Dump AgentCallbackLogin agents to the ASTdb database for persistence
+ */
+static void dump_agents(void)
+{
+ struct agent_pvt *cur_agent = NULL;
+ char buf[256];
+
+ AST_LIST_TRAVERSE(&agents, cur_agent, list) {
+ if (cur_agent->chan)
+ continue;
+
+ if (!ast_strlen_zero(cur_agent->loginchan)) {
+ snprintf(buf, sizeof(buf), "%s;%s", cur_agent->loginchan, cur_agent->logincallerid);
+ if (ast_db_put(pa_family, cur_agent->agent, buf))
+ ast_log(LOG_WARNING, "failed to create persistent entry in ASTdb for %s!\n", buf);
+ else
+ ast_debug(1, "Saved Agent: %s on %s\n", cur_agent->agent, cur_agent->loginchan);
+ } else {
+ /* Delete - no agent or there is an error */
+ ast_db_del(pa_family, cur_agent->agent);
+ }
+ }
+}
+
+/*!
+ * \brief Reload the persistent agents from astdb.
+ */
+static void reload_agents(void)
+{
+ char *agent_num;
+ struct ast_db_entry *db_tree;
+ struct ast_db_entry *entry;
+ struct agent_pvt *cur_agent;
+ char agent_data[256];
+ char *parse;
+ char *agent_chan;
+ char *agent_callerid;
+
+ db_tree = ast_db_gettree(pa_family, NULL);
+
+ AST_LIST_LOCK(&agents);
+ for (entry = db_tree; entry; entry = entry->next) {
+ agent_num = entry->key + strlen(pa_family) + 2;
+ AST_LIST_TRAVERSE(&agents, cur_agent, list) {
+ ast_mutex_lock(&cur_agent->lock);
+ if (strcmp(agent_num, cur_agent->agent) == 0)
+ break;
+ ast_mutex_unlock(&cur_agent->lock);
+ }
+ if (!cur_agent) {
+ ast_db_del(pa_family, agent_num);
+ continue;
+ } else
+ ast_mutex_unlock(&cur_agent->lock);
+ if (!ast_db_get(pa_family, agent_num, agent_data, sizeof(agent_data)-1)) {
+ ast_debug(1, "Reload Agent from AstDB: %s on %s\n", cur_agent->agent, agent_data);
+ parse = agent_data;
+ agent_chan = strsep(&parse, ";");
+ agent_callerid = strsep(&parse, ";");
+ ast_copy_string(cur_agent->loginchan, agent_chan, sizeof(cur_agent->loginchan));
+ if (agent_callerid) {
+ ast_copy_string(cur_agent->logincallerid, agent_callerid, sizeof(cur_agent->logincallerid));
+ set_agentbycallerid(cur_agent->logincallerid, cur_agent->agent);
+ } else
+ cur_agent->logincallerid[0] = '\0';
+ if (cur_agent->loginstart == 0)
+ time(&cur_agent->loginstart);
+ ast_device_state_changed("Agent/%s", cur_agent->agent);
+ }
+ }
+ AST_LIST_UNLOCK(&agents);
+ if (db_tree) {
+ ast_log(LOG_NOTICE, "Agents successfully reloaded from database.\n");
+ ast_db_freetree(db_tree);
+ }
+}
+
+/*! \brief Part of PBX channel interface */
+static int agent_devicestate(void *data)
+{
+ struct agent_pvt *p;
+ char *s;
+ ast_group_t groupmatch;
+ int groupoff;
+ int res = AST_DEVICE_INVALID;
+
+ s = data;
+ if ((s[0] == '@') && (sscanf(s + 1, "%d", &groupoff) == 1))
+ groupmatch = (1 << groupoff);
+ else if ((s[0] == ':') && (sscanf(s + 1, "%d", &groupoff) == 1)) {
+ groupmatch = (1 << groupoff);
+ } else
+ groupmatch = 0;
+
+ /* Check actual logged in agents first */
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ ast_mutex_lock(&p->lock);
+ if (!p->pending && ((groupmatch && (p->group & groupmatch)) || !strcmp(data, p->agent))) {
+ if (p->owner) {
+ if (res != AST_DEVICE_INUSE)
+ res = AST_DEVICE_BUSY;
+ } else {
+ if (res == AST_DEVICE_BUSY)
+ res = AST_DEVICE_INUSE;
+ if (p->chan || !ast_strlen_zero(p->loginchan)) {
+ if (res == AST_DEVICE_INVALID)
+ res = AST_DEVICE_UNKNOWN;
+ } else if (res == AST_DEVICE_INVALID)
+ res = AST_DEVICE_UNAVAILABLE;
+ }
+ if (!strcmp(data, p->agent)) {
+ ast_mutex_unlock(&p->lock);
+ break;
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ AST_LIST_UNLOCK(&agents);
+ return res;
+}
+
+static struct agent_pvt *find_agent(char *agentid)
+{
+ struct agent_pvt *cur;
+
+ AST_LIST_TRAVERSE(&agents, cur, list) {
+ if (!strcmp(cur->agent, agentid))
+ break;
+ }
+
+ return cur;
+}
+
+static int function_agent(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(agentid);
+ AST_APP_ARG(item);
+ );
+ char *tmp;
+ struct agent_pvt *agent;
+
+ buf[0] = '\0';
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "The AGENT function requires an argument - agentid!\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+
+ AST_NONSTANDARD_APP_ARGS(args, parse, ':');
+ if (!args.item)
+ args.item = "status";
+
+ if (!(agent = find_agent(args.agentid))) {
+ ast_log(LOG_WARNING, "Agent '%s' not found!\n", args.agentid);
+ return -1;
+ }
+
+ if (!strcasecmp(args.item, "status")) {
+ char *status = "LOGGEDOUT";
+ if (agent->chan || !ast_strlen_zero(agent->loginchan))
+ status = "LOGGEDIN";
+ ast_copy_string(buf, status, len);
+ } else if (!strcasecmp(args.item, "password"))
+ ast_copy_string(buf, agent->password, len);
+ else if (!strcasecmp(args.item, "name"))
+ ast_copy_string(buf, agent->name, len);
+ else if (!strcasecmp(args.item, "mohclass"))
+ ast_copy_string(buf, agent->moh, len);
+ else if (!strcasecmp(args.item, "channel")) {
+ if (agent->chan) {
+ ast_copy_string(buf, agent->chan->name, len);
+ tmp = strrchr(buf, '-');
+ if (tmp)
+ *tmp = '\0';
+ }
+ } else if (!strcasecmp(args.item, "exten"))
+ ast_copy_string(buf, agent->loginchan, len);
+
+ return 0;
+}
+
+struct ast_custom_function agent_function = {
+ .name = "AGENT",
+ .synopsis = "Gets information about an Agent",
+ .syntax = "AGENT(<agentid>[:item])",
+ .read = function_agent,
+ .desc = "The valid items to retrieve are:\n"
+ "- status (default) The status of the agent\n"
+ " LOGGEDIN | LOGGEDOUT\n"
+ "- password The password of the agent\n"
+ "- name The name of the agent\n"
+ "- mohclass MusicOnHold class\n"
+ "- exten The callback extension for the Agent (AgentCallbackLogin)\n"
+ "- channel The name of the active channel for the Agent (AgentLogin)\n"
+};
+
+
+/*!
+ * \brief Initialize the Agents module.
+ * This function is being called by Asterisk when loading the module.
+ * Among other things it registers applications, cli commands and reads the cofiguration file.
+ *
+ * \returns int Always 0.
+ */
+static int load_module(void)
+{
+ /* Make sure we can register our agent channel type */
+ if (ast_channel_register(&agent_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'Agent'\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ /* Read in the config */
+ if (!read_agent_config(0))
+ return AST_MODULE_LOAD_DECLINE;
+ if (persistent_agents)
+ reload_agents();
+ /* Dialplan applications */
+ ast_register_application(app, login_exec, synopsis, descrip);
+ ast_register_application(app3, agentmonitoroutgoing_exec, synopsis3, descrip3);
+
+ /* Manager commands */
+ ast_manager_register2("Agents", EVENT_FLAG_AGENT, action_agents, "Lists agents and their status", mandescr_agents);
+ ast_manager_register2("AgentLogoff", EVENT_FLAG_AGENT, action_agent_logoff, "Sets an agent as no longer logged in", mandescr_agent_logoff);
+
+ /* CLI Commands */
+ ast_cli_register_multiple(cli_agents, sizeof(cli_agents) / sizeof(struct ast_cli_entry));
+
+ /* Dialplan Functions */
+ ast_custom_function_register(&agent_function);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload(void)
+{
+ if (!read_agent_config(1)) {
+ if (persistent_agents)
+ reload_agents();
+ }
+ return 0;
+}
+
+static int unload_module(void)
+{
+ struct agent_pvt *p;
+ /* First, take us out of the channel loop */
+ ast_channel_unregister(&agent_tech);
+ /* Unregister dialplan functions */
+ ast_custom_function_unregister(&agent_function);
+ /* Unregister CLI commands */
+ ast_cli_unregister_multiple(cli_agents, sizeof(cli_agents) / sizeof(struct ast_cli_entry));
+ /* Unregister dialplan applications */
+ ast_unregister_application(app);
+ ast_unregister_application(app3);
+ /* Unregister manager command */
+ ast_manager_unregister("Agents");
+ ast_manager_unregister("AgentLogoff");
+ /* Unregister channel */
+ AST_LIST_LOCK(&agents);
+ /* Hangup all interfaces if they have an owner */
+ while ((p = AST_LIST_REMOVE_HEAD(&agents, list))) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ ast_free(p);
+ }
+ AST_LIST_UNLOCK(&agents);
+ AST_LIST_HEAD_DESTROY(&agents);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Agent Proxy Channel",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/trunk/channels/chan_alsa.c b/trunk/channels/chan_alsa.c
new file mode 100644
index 000000000..ec1107695
--- /dev/null
+++ b/trunk/channels/chan_alsa.c
@@ -0,0 +1,925 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * By Matthew Fredrickson <creslin@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief ALSA sound card channel driver
+ *
+ * \author Matthew Fredrickson <creslin@digium.com>
+ *
+ * \par See also
+ * \arg Config_alsa
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <depend>asound</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#define ALSA_PCM_NEW_HW_PARAMS_API
+#define ALSA_PCM_NEW_SW_PARAMS_API
+#include <alsa/asoundlib.h>
+
+#include "asterisk/frame.h"
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/endian.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/musiconhold.h"
+
+/*! Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf = {
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = ""
+};
+static struct ast_jb_conf global_jbconf;
+
+#define DEBUG 0
+/* Which device to use */
+#define ALSA_INDEV "default"
+#define ALSA_OUTDEV "default"
+#define DESIRED_RATE 8000
+
+/* Lets use 160 sample frames, just like GSM. */
+#define FRAME_SIZE 160
+#define PERIOD_FRAMES 80 /* 80 Frames, at 2 bytes each */
+
+/* When you set the frame size, you have to come up with
+ the right buffer format as well. */
+/* 5 64-byte frames = one frame */
+#define BUFFER_FMT ((buffersize * 10) << 16) | (0x0006);
+
+/* Don't switch between read/write modes faster than every 300 ms */
+#define MIN_SWITCH_TIME 600
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+static snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
+#else
+static snd_pcm_format_t format = SND_PCM_FORMAT_S16_BE;
+#endif
+
+/* static int block = O_NONBLOCK; */
+static char indevname[50] = ALSA_INDEV;
+static char outdevname[50] = ALSA_OUTDEV;
+
+static int silencesuppression = 0;
+static int silencethreshold = 1000;
+
+AST_MUTEX_DEFINE_STATIC(alsalock);
+
+static const char tdesc[] = "ALSA Console Channel Driver";
+static const char config[] = "alsa.conf";
+
+static char context[AST_MAX_CONTEXT] = "default";
+static char language[MAX_LANGUAGE] = "";
+static char exten[AST_MAX_EXTENSION] = "s";
+static char mohinterpret[MAX_MUSICCLASS];
+
+static int hookstate = 0;
+
+static struct chan_alsa_pvt {
+ /* We only have one ALSA structure -- near sighted perhaps, but it
+ keeps this driver as simple as possible -- as it should be. */
+ struct ast_channel *owner;
+ char exten[AST_MAX_EXTENSION];
+ char context[AST_MAX_CONTEXT];
+ snd_pcm_t *icard, *ocard;
+
+} alsa;
+
+/* Number of buffers... Each is FRAMESIZE/8 ms long. For example
+ with 160 sample frames, and a buffer size of 3, we have a 60ms buffer,
+ usually plenty. */
+
+#define MAX_BUFFER_SIZE 100
+
+/* File descriptors for sound device */
+static int readdev = -1;
+static int writedev = -1;
+
+static int autoanswer = 1;
+
+static struct ast_channel *alsa_request(const char *type, int format, void *data, int *cause);
+static int alsa_digit(struct ast_channel *c, char digit, unsigned int duration);
+static int alsa_text(struct ast_channel *c, const char *text);
+static int alsa_hangup(struct ast_channel *c);
+static int alsa_answer(struct ast_channel *c);
+static struct ast_frame *alsa_read(struct ast_channel *chan);
+static int alsa_call(struct ast_channel *c, char *dest, int timeout);
+static int alsa_write(struct ast_channel *chan, struct ast_frame *f);
+static int alsa_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen);
+static int alsa_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+
+static const struct ast_channel_tech alsa_tech = {
+ .type = "Console",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_SLINEAR,
+ .requester = alsa_request,
+ .send_digit_end = alsa_digit,
+ .send_text = alsa_text,
+ .hangup = alsa_hangup,
+ .answer = alsa_answer,
+ .read = alsa_read,
+ .call = alsa_call,
+ .write = alsa_write,
+ .indicate = alsa_indicate,
+ .fixup = alsa_fixup,
+};
+
+static snd_pcm_t *alsa_card_init(char *dev, snd_pcm_stream_t stream)
+{
+ int err;
+ int direction;
+ snd_pcm_t *handle = NULL;
+ snd_pcm_hw_params_t *hwparams = NULL;
+ snd_pcm_sw_params_t *swparams = NULL;
+ struct pollfd pfd;
+ snd_pcm_uframes_t period_size = PERIOD_FRAMES * 4;
+ snd_pcm_uframes_t buffer_size = 0;
+ unsigned int rate = DESIRED_RATE;
+ snd_pcm_uframes_t start_threshold, stop_threshold;
+
+ err = snd_pcm_open(&handle, dev, stream, O_NONBLOCK);
+ if (err < 0) {
+ ast_log(LOG_ERROR, "snd_pcm_open failed: %s\n", snd_strerror(err));
+ return NULL;
+ } else {
+ ast_debug(1, "Opening device %s in %s mode\n", dev, (stream == SND_PCM_STREAM_CAPTURE) ? "read" : "write");
+ }
+
+ hwparams = alloca(snd_pcm_hw_params_sizeof());
+ memset(hwparams, 0, snd_pcm_hw_params_sizeof());
+ snd_pcm_hw_params_any(handle, hwparams);
+
+ err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
+ if (err < 0)
+ ast_log(LOG_ERROR, "set_access failed: %s\n", snd_strerror(err));
+
+ err = snd_pcm_hw_params_set_format(handle, hwparams, format);
+ if (err < 0)
+ ast_log(LOG_ERROR, "set_format failed: %s\n", snd_strerror(err));
+
+ err = snd_pcm_hw_params_set_channels(handle, hwparams, 1);
+ if (err < 0)
+ ast_log(LOG_ERROR, "set_channels failed: %s\n", snd_strerror(err));
+
+ direction = 0;
+ err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &rate, &direction);
+ if (rate != DESIRED_RATE)
+ ast_log(LOG_WARNING, "Rate not correct, requested %d, got %d\n", DESIRED_RATE, rate);
+
+ direction = 0;
+ err = snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period_size, &direction);
+ if (err < 0)
+ ast_log(LOG_ERROR, "period_size(%ld frames) is bad: %s\n", period_size, snd_strerror(err));
+ else {
+ ast_debug(1, "Period size is %d\n", err);
+ }
+
+ buffer_size = 4096 * 2; /* period_size * 16; */
+ err = snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &buffer_size);
+ if (err < 0)
+ ast_log(LOG_WARNING, "Problem setting buffer size of %ld: %s\n", buffer_size, snd_strerror(err));
+ else {
+ ast_debug(1, "Buffer size is set to %d frames\n", err);
+ }
+
+ err = snd_pcm_hw_params(handle, hwparams);
+ if (err < 0)
+ ast_log(LOG_ERROR, "Couldn't set the new hw params: %s\n", snd_strerror(err));
+
+ swparams = alloca(snd_pcm_sw_params_sizeof());
+ memset(swparams, 0, snd_pcm_sw_params_sizeof());
+ snd_pcm_sw_params_current(handle, swparams);
+
+ if (stream == SND_PCM_STREAM_PLAYBACK)
+ start_threshold = period_size;
+ else
+ start_threshold = 1;
+
+ err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold);
+ if (err < 0)
+ ast_log(LOG_ERROR, "start threshold: %s\n", snd_strerror(err));
+
+ if (stream == SND_PCM_STREAM_PLAYBACK)
+ stop_threshold = buffer_size;
+ else
+ stop_threshold = buffer_size;
+
+ err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold);
+ if (err < 0)
+ ast_log(LOG_ERROR, "stop threshold: %s\n", snd_strerror(err));
+
+ err = snd_pcm_sw_params(handle, swparams);
+ if (err < 0)
+ ast_log(LOG_ERROR, "sw_params: %s\n", snd_strerror(err));
+
+ err = snd_pcm_poll_descriptors_count(handle);
+ if (err <= 0)
+ ast_log(LOG_ERROR, "Unable to get a poll descriptors count, error is %s\n", snd_strerror(err));
+ if (err != 1) {
+ ast_debug(1, "Can't handle more than one device\n");
+ }
+
+ snd_pcm_poll_descriptors(handle, &pfd, err);
+ ast_debug(1, "Acquired fd %d from the poll descriptor\n", pfd.fd);
+
+ if (stream == SND_PCM_STREAM_CAPTURE)
+ readdev = pfd.fd;
+ else
+ writedev = pfd.fd;
+
+ return handle;
+}
+
+static int soundcard_init(void)
+{
+ alsa.icard = alsa_card_init(indevname, SND_PCM_STREAM_CAPTURE);
+ alsa.ocard = alsa_card_init(outdevname, SND_PCM_STREAM_PLAYBACK);
+
+ if (!alsa.icard || !alsa.ocard) {
+ ast_log(LOG_ERROR, "Problem opening ALSA I/O devices\n");
+ return -1;
+ }
+
+ return readdev;
+}
+
+static int alsa_digit(struct ast_channel *c, char digit, unsigned int duration)
+{
+ ast_mutex_lock(&alsalock);
+ ast_verbose(" << Console Received digit %c of duration %u ms >> \n",
+ digit, duration);
+ ast_mutex_unlock(&alsalock);
+
+ return 0;
+}
+
+static int alsa_text(struct ast_channel *c, const char *text)
+{
+ ast_mutex_lock(&alsalock);
+ ast_verbose(" << Console Received text %s >> \n", text);
+ ast_mutex_unlock(&alsalock);
+
+ return 0;
+}
+
+static void grab_owner(void)
+{
+ while (alsa.owner && ast_channel_trylock(alsa.owner)) {
+ ast_mutex_unlock(&alsalock);
+ usleep(1);
+ ast_mutex_lock(&alsalock);
+ }
+}
+
+static int alsa_call(struct ast_channel *c, char *dest, int timeout)
+{
+ struct ast_frame f = { AST_FRAME_CONTROL };
+
+ ast_mutex_lock(&alsalock);
+ ast_verbose(" << Call placed to '%s' on console >> \n", dest);
+ if (autoanswer) {
+ ast_verbose(" << Auto-answered >> \n");
+ grab_owner();
+ if (alsa.owner) {
+ f.subclass = AST_CONTROL_ANSWER;
+ ast_queue_frame(alsa.owner, &f);
+ ast_channel_unlock(alsa.owner);
+ }
+ } else {
+ ast_verbose(" << Type 'answer' to answer, or use 'autoanswer' for future calls >> \n");
+ grab_owner();
+ if (alsa.owner) {
+ f.subclass = AST_CONTROL_RINGING;
+ ast_queue_frame(alsa.owner, &f);
+ ast_channel_unlock(alsa.owner);
+ ast_indicate(alsa.owner, AST_CONTROL_RINGING);
+ }
+ }
+ snd_pcm_prepare(alsa.icard);
+ snd_pcm_start(alsa.icard);
+ ast_mutex_unlock(&alsalock);
+
+ return 0;
+}
+
+static int alsa_answer(struct ast_channel *c)
+{
+ ast_mutex_lock(&alsalock);
+ ast_verbose(" << Console call has been answered >> \n");
+ ast_setstate(c, AST_STATE_UP);
+ snd_pcm_prepare(alsa.icard);
+ snd_pcm_start(alsa.icard);
+ ast_mutex_unlock(&alsalock);
+
+ return 0;
+}
+
+static int alsa_hangup(struct ast_channel *c)
+{
+ ast_mutex_lock(&alsalock);
+ c->tech_pvt = NULL;
+ alsa.owner = NULL;
+ ast_verbose(" << Hangup on console >> \n");
+ ast_module_unref(ast_module_info->self);
+ hookstate = 0;
+ snd_pcm_drop(alsa.icard);
+ ast_mutex_unlock(&alsalock);
+
+ return 0;
+}
+
+static int alsa_write(struct ast_channel *chan, struct ast_frame *f)
+{
+ static char sizbuf[8000];
+ static int sizpos = 0;
+ int len = sizpos;
+ int pos;
+ int res = 0;
+ /* size_t frames = 0; */
+ snd_pcm_state_t state;
+
+ ast_mutex_lock(&alsalock);
+
+ /* We have to digest the frame in 160-byte portions */
+ if (f->datalen > sizeof(sizbuf) - sizpos) {
+ ast_log(LOG_WARNING, "Frame too large\n");
+ res = -1;
+ } else {
+ memcpy(sizbuf + sizpos, f->data, f->datalen);
+ len += f->datalen;
+ pos = 0;
+ state = snd_pcm_state(alsa.ocard);
+ if (state == SND_PCM_STATE_XRUN)
+ snd_pcm_prepare(alsa.ocard);
+ res = snd_pcm_writei(alsa.ocard, sizbuf, len / 2);
+ if (res == -EPIPE) {
+#if DEBUG
+ ast_debug(1, "XRUN write\n");
+#endif
+ snd_pcm_prepare(alsa.ocard);
+ res = snd_pcm_writei(alsa.ocard, sizbuf, len / 2);
+ if (res != len / 2) {
+ ast_log(LOG_ERROR, "Write error: %s\n", snd_strerror(res));
+ res = -1;
+ } else if (res < 0) {
+ ast_log(LOG_ERROR, "Write error %s\n", snd_strerror(res));
+ res = -1;
+ }
+ } else {
+ if (res == -ESTRPIPE)
+ ast_log(LOG_ERROR, "You've got some big problems\n");
+ else if (res < 0)
+ ast_log(LOG_NOTICE, "Error %d on write\n", res);
+ }
+ }
+ ast_mutex_unlock(&alsalock);
+
+ return res >= 0 ? 0 : res;
+}
+
+
+static struct ast_frame *alsa_read(struct ast_channel *chan)
+{
+ static struct ast_frame f;
+ static short __buf[FRAME_SIZE + AST_FRIENDLY_OFFSET / 2];
+ short *buf;
+ static int readpos = 0;
+ static int left = FRAME_SIZE;
+ snd_pcm_state_t state;
+ int r = 0;
+ int off = 0;
+
+ ast_mutex_lock(&alsalock);
+ f.frametype = AST_FRAME_NULL;
+ f.subclass = 0;
+ f.samples = 0;
+ f.datalen = 0;
+ f.data = NULL;
+ f.offset = 0;
+ f.src = "Console";
+ f.mallocd = 0;
+ f.delivery.tv_sec = 0;
+ f.delivery.tv_usec = 0;
+
+ state = snd_pcm_state(alsa.icard);
+ if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING)) {
+ snd_pcm_prepare(alsa.icard);
+ }
+
+ buf = __buf + AST_FRIENDLY_OFFSET / 2;
+
+ r = snd_pcm_readi(alsa.icard, buf + readpos, left);
+ if (r == -EPIPE) {
+#if DEBUG
+ ast_log(LOG_ERROR, "XRUN read\n");
+#endif
+ snd_pcm_prepare(alsa.icard);
+ } else if (r == -ESTRPIPE) {
+ ast_log(LOG_ERROR, "-ESTRPIPE\n");
+ snd_pcm_prepare(alsa.icard);
+ } else if (r < 0) {
+ ast_log(LOG_ERROR, "Read error: %s\n", snd_strerror(r));
+ } else if (r >= 0) {
+ off -= r;
+ }
+ /* Update positions */
+ readpos += r;
+ left -= r;
+
+ if (readpos >= FRAME_SIZE) {
+ /* A real frame */
+ readpos = 0;
+ left = FRAME_SIZE;
+ if (chan->_state != AST_STATE_UP) {
+ /* Don't transmit unless it's up */
+ ast_mutex_unlock(&alsalock);
+ return &f;
+ }
+ f.frametype = AST_FRAME_VOICE;
+ f.subclass = AST_FORMAT_SLINEAR;
+ f.samples = FRAME_SIZE;
+ f.datalen = FRAME_SIZE * 2;
+ f.data = buf;
+ f.offset = AST_FRIENDLY_OFFSET;
+ f.src = "Console";
+ f.mallocd = 0;
+
+ }
+ ast_mutex_unlock(&alsalock);
+
+ return &f;
+}
+
+static int alsa_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct chan_alsa_pvt *p = newchan->tech_pvt;
+
+ ast_mutex_lock(&alsalock);
+ p->owner = newchan;
+ ast_mutex_unlock(&alsalock);
+
+ return 0;
+}
+
+static int alsa_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen)
+{
+ int res = 0;
+
+ ast_mutex_lock(&alsalock);
+
+ switch (cond) {
+ case AST_CONTROL_BUSY:
+ case AST_CONTROL_CONGESTION:
+ case AST_CONTROL_RINGING:
+ case -1:
+ res = -1; /* Ask for inband indications */
+ break;
+ case AST_CONTROL_PROGRESS:
+ case AST_CONTROL_PROCEEDING:
+ case AST_CONTROL_VIDUPDATE:
+ break;
+ case AST_CONTROL_HOLD:
+ ast_verbose(" << Console Has Been Placed on Hold >> \n");
+ ast_moh_start(chan, data, mohinterpret);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_verbose(" << Console Has Been Retrieved from Hold >> \n");
+ ast_moh_stop(chan);
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, chan->name);
+ res = -1;
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static struct ast_channel *alsa_new(struct chan_alsa_pvt *p, int state)
+{
+ struct ast_channel *tmp = NULL;
+
+ if (!(tmp = ast_channel_alloc(1, state, 0, 0, "", p->exten, p->context, 0, "ALSA/%s", indevname)))
+ return NULL;
+
+ tmp->tech = &alsa_tech;
+ ast_channel_set_fd(tmp, 0, readdev);
+ tmp->nativeformats = AST_FORMAT_SLINEAR;
+ tmp->readformat = AST_FORMAT_SLINEAR;
+ tmp->writeformat = AST_FORMAT_SLINEAR;
+ tmp->tech_pvt = p;
+ if (!ast_strlen_zero(p->context))
+ ast_copy_string(tmp->context, p->context, sizeof(tmp->context));
+ if (!ast_strlen_zero(p->exten))
+ ast_copy_string(tmp->exten, p->exten, sizeof(tmp->exten));
+ if (!ast_strlen_zero(language))
+ ast_string_field_set(tmp, language, language);
+ p->owner = tmp;
+ ast_module_ref(ast_module_info->self);
+ ast_jb_configure(tmp, &global_jbconf);
+ if (state != AST_STATE_DOWN) {
+ if (ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ ast_hangup(tmp);
+ tmp = NULL;
+ }
+ }
+
+ return tmp;
+}
+
+static struct ast_channel *alsa_request(const char *type, int format, void *data, int *cause)
+{
+ int oldformat = format;
+ struct ast_channel *tmp = NULL;
+
+ if (!(format &= AST_FORMAT_SLINEAR)) {
+ ast_log(LOG_NOTICE, "Asked to get a channel of format '%d'\n", oldformat);
+ return NULL;
+ }
+
+ ast_mutex_lock(&alsalock);
+
+ if (alsa.owner) {
+ ast_log(LOG_NOTICE, "Already have a call on the ALSA channel\n");
+ *cause = AST_CAUSE_BUSY;
+ } else if (!(tmp = alsa_new(&alsa, AST_STATE_DOWN))) {
+ ast_log(LOG_WARNING, "Unable to create new ALSA channel\n");
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return tmp;
+}
+
+static char *autoanswer_complete(const char *line, const char *word, int pos, int state)
+{
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+ switch (state) {
+ case 0:
+ if (!ast_strlen_zero(word) && !strncasecmp(word, "on", MIN(strlen(word), 2)))
+ return ast_strdup("on");
+ case 1:
+ if (!ast_strlen_zero(word) && !strncasecmp(word, "off", MIN(strlen(word), 3)))
+ return ast_strdup("off");
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+static char *console_autoanswer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *res = CLI_SUCCESS;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console autoanswer";
+ e->usage =
+ "Usage: console autoanswer [on|off]\n"
+ " Enables or disables autoanswer feature. If used without\n"
+ " argument, displays the current on/off status of autoanswer.\n"
+ " The default value of autoanswer is in 'alsa.conf'.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return autoanswer_complete(a->line, a->word, a->pos, a->n);
+ }
+
+ if ((a->argc != 2) && (a->argc != 3))
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+ if (a->argc == 2) {
+ ast_cli(a->fd, "Auto answer is %s.\n", autoanswer ? "on" : "off");
+ } else {
+ if (!strcasecmp(a->argv[2], "on"))
+ autoanswer = -1;
+ else if (!strcasecmp(a->argv[2], "off"))
+ autoanswer = 0;
+ else
+ res = CLI_SHOWUSAGE;
+ }
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static char *console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *res = CLI_SUCCESS;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console answer";
+ e->usage =
+ "Usage: console answer\n"
+ " Answers an incoming call on the console (ALSA) channel.\n";
+
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 2)
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (!alsa.owner) {
+ ast_cli(a->fd, "No one is calling us\n");
+ res = CLI_FAILURE;
+ } else {
+ hookstate = 1;
+ grab_owner();
+ if (alsa.owner) {
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
+
+ ast_queue_frame(alsa.owner, &f);
+ ast_channel_unlock(alsa.owner);
+ }
+ }
+
+ snd_pcm_prepare(alsa.icard);
+ snd_pcm_start(alsa.icard);
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static char *console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int tmparg = 3;
+ char *res = CLI_SUCCESS;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console send text";
+ e->usage =
+ "Usage: console send text <message>\n"
+ " Sends a text message for display on the remote terminal.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc < 3)
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (!alsa.owner) {
+ ast_cli(a->fd, "No channel active\n");
+ res = CLI_FAILURE;
+ } else {
+ struct ast_frame f = { AST_FRAME_TEXT, 0 };
+ char text2send[256] = "";
+
+ while (tmparg < a->argc) {
+ strncat(text2send, a->argv[tmparg++], sizeof(text2send) - strlen(text2send) - 1);
+ strncat(text2send, " ", sizeof(text2send) - strlen(text2send) - 1);
+ }
+
+ text2send[strlen(text2send) - 1] = '\n';
+ f.data = text2send;
+ f.datalen = strlen(text2send) + 1;
+ grab_owner();
+ if (alsa.owner) {
+ ast_queue_frame(alsa.owner, &f);
+ f.frametype = AST_FRAME_CONTROL;
+ f.subclass = AST_CONTROL_ANSWER;
+ f.data = NULL;
+ f.datalen = 0;
+ ast_queue_frame(alsa.owner, &f);
+ ast_channel_unlock(alsa.owner);
+ }
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static char *console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *res = CLI_SUCCESS;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console hangup";
+ e->usage =
+ "Usage: console hangup\n"
+ " Hangs up any call currently placed on the console.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+
+ if (a->argc != 2)
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (!alsa.owner && !hookstate) {
+ ast_cli(a->fd, "No call to hangup\n");
+ res = CLI_FAILURE;
+ } else {
+ hookstate = 0;
+ grab_owner();
+ if (alsa.owner) {
+ ast_queue_hangup(alsa.owner);
+ ast_channel_unlock(alsa.owner);
+ }
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static char *console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char tmp[256], *tmp2;
+ char *mye, *myc;
+ char *d;
+ char *res = CLI_SUCCESS;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console dial";
+ e->usage =
+ "Usage: console dial [extension[@context]]\n"
+ " Dials a given extension (and context if specified)\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if ((a->argc != 2) && (a->argc != 3))
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (alsa.owner) {
+ if (a->argc == 3) {
+ if (alsa.owner) {
+ for (d = a->argv[2]; *d; d++) {
+ struct ast_frame f = { .frametype = AST_FRAME_DTMF, .subclass = *d };
+
+ ast_queue_frame(alsa.owner, &f);
+ }
+ }
+ } else {
+ ast_cli(a->fd, "You're already in a call. You can use this only to dial digits until you hangup\n");
+ res = CLI_FAILURE;
+ }
+ } else {
+ mye = exten;
+ myc = context;
+ if (a->argc == 3) {
+ char *stringp = NULL;
+
+ ast_copy_string(tmp, a->argv[2], sizeof(tmp));
+ stringp = tmp;
+ strsep(&stringp, "@");
+ tmp2 = strsep(&stringp, "@");
+ if (!ast_strlen_zero(tmp))
+ mye = tmp;
+ if (!ast_strlen_zero(tmp2))
+ myc = tmp2;
+ }
+ if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
+ ast_copy_string(alsa.exten, mye, sizeof(alsa.exten));
+ ast_copy_string(alsa.context, myc, sizeof(alsa.context));
+ hookstate = 1;
+ alsa_new(&alsa, AST_STATE_RINGING);
+ } else
+ ast_cli(a->fd, "No such extension '%s' in context '%s'\n", mye, myc);
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static struct ast_cli_entry cli_alsa[] = {
+ AST_CLI_DEFINE(console_answer, "Answer an incoming console call"),
+ AST_CLI_DEFINE(console_hangup, "Hangup a call on the console"),
+ AST_CLI_DEFINE(console_dial, "Dial an extension on the console"),
+ AST_CLI_DEFINE(console_sendtext, "Send text to the remote device"),
+ AST_CLI_DEFINE(console_autoanswer, "Sets/displays autoanswer"),
+};
+
+static int load_module(void)
+{
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ struct ast_flags config_flags = { 0 };
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ strcpy(mohinterpret, "default");
+
+ if (!(cfg = ast_config_load(config, config_flags)))
+ return AST_MODULE_LOAD_DECLINE;
+
+ v = ast_variable_browse(cfg, "general");
+ for (; v; v = v->next) {
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
+ continue;
+
+ if (!strcasecmp(v->name, "autoanswer"))
+ autoanswer = ast_true(v->value);
+ else if (!strcasecmp(v->name, "silencesuppression"))
+ silencesuppression = ast_true(v->value);
+ else if (!strcasecmp(v->name, "silencethreshold"))
+ silencethreshold = atoi(v->value);
+ else if (!strcasecmp(v->name, "context"))
+ ast_copy_string(context, v->value, sizeof(context));
+ else if (!strcasecmp(v->name, "language"))
+ ast_copy_string(language, v->value, sizeof(language));
+ else if (!strcasecmp(v->name, "extension"))
+ ast_copy_string(exten, v->value, sizeof(exten));
+ else if (!strcasecmp(v->name, "input_device"))
+ ast_copy_string(indevname, v->value, sizeof(indevname));
+ else if (!strcasecmp(v->name, "output_device"))
+ ast_copy_string(outdevname, v->value, sizeof(outdevname));
+ else if (!strcasecmp(v->name, "mohinterpret"))
+ ast_copy_string(mohinterpret, v->value, sizeof(mohinterpret));
+ }
+ ast_config_destroy(cfg);
+
+ if (soundcard_init() < 0) {
+ ast_verb(2, "No sound card detected -- console channel will be unavailable\n");
+ ast_verb(2, "Turn off ALSA support by adding 'noload=chan_alsa.so' in /etc/asterisk/modules.conf\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ if (ast_channel_register(&alsa_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'Console'\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ ast_cli_register_multiple(cli_alsa, sizeof(cli_alsa) / sizeof(struct ast_cli_entry));
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_channel_unregister(&alsa_tech);
+ ast_cli_unregister_multiple(cli_alsa, sizeof(cli_alsa) / sizeof(struct ast_cli_entry));
+
+ if (alsa.icard)
+ snd_pcm_close(alsa.icard);
+ if (alsa.ocard)
+ snd_pcm_close(alsa.ocard);
+ if (alsa.owner)
+ ast_softhangup(alsa.owner, AST_SOFTHANGUP_APPUNLOAD);
+ if (alsa.owner)
+ return -1;
+
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "ALSA Console Channel Driver");
diff --git a/trunk/channels/chan_console.c b/trunk/channels/chan_console.c
new file mode 100644
index 000000000..58f456c39
--- /dev/null
+++ b/trunk/channels/chan_console.c
@@ -0,0 +1,1103 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006 - 2007, Digium, Inc.
+ *
+ * Russell Bryant <russell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Cross-platform console channel driver
+ *
+ * \author Russell Bryant <russell@digium.com>
+ *
+ * \note Some of the code in this file came from chan_oss and chan_alsa.
+ * chan_oss, Mark Spencer <markster@digium.com>
+ * chan_oss, Luigi Rizzo
+ * chan_alsa, Matthew Fredrickson <creslin@digium.com>
+ *
+ * \ingroup channel_drivers
+ *
+ * \extref Portaudio http://www.portaudio.com/
+ *
+ * To install portaudio v19 from svn, check it out using the following command:
+ * - svn co https://www.portaudio.com/repos/portaudio/branches/v19-devel
+ *
+ * \note Since this works with any audio system that libportaudio supports,
+ * including ALSA and OSS, this may someday deprecate chan_alsa and chan_oss.
+ * However, before that can be done, it needs to *at least* have all of the
+ * features that these other channel drivers have. The features implemented
+ * in at least one of the other console channel drivers that are not yet
+ * implemented here are:
+ *
+ * - Multiple device support
+ * - with "active" CLI command
+ * - Set Auto-answer from the dialplan
+ * - transfer CLI command
+ * - boost CLI command and .conf option
+ * - console_video support
+ */
+
+/*** MODULEINFO
+ <depend>portaudio</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/signal.h> /* SIGURG */
+
+#include <portaudio.h>
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/causes.h"
+#include "asterisk/cli.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/callerid.h"
+
+/*!
+ * \brief The sample rate to request from PortAudio
+ *
+ * \todo Make this optional. If this is only going to talk to 8 kHz endpoints,
+ * then it makes sense to use 8 kHz natively.
+ */
+#define SAMPLE_RATE 16000
+
+/*!
+ * \brief The number of samples to configure the portaudio stream for
+ *
+ * 320 samples (20 ms) is the most common frame size in Asterisk. So, the code
+ * in this module reads 320 sample frames from the portaudio stream and queues
+ * them up on the Asterisk channel. Frames of any size can be written to a
+ * portaudio stream, but the portaudio documentation does say that for high
+ * performance applications, the data should be written to Pa_WriteStream in
+ * the same size as what is used to initialize the stream.
+ */
+#define NUM_SAMPLES 320
+
+/*! \brief Mono Input */
+#define INPUT_CHANNELS 1
+
+/*! \brief Mono Output */
+#define OUTPUT_CHANNELS 1
+
+/*!
+ * \brief Maximum text message length
+ * \note This should be changed if there is a common definition somewhere
+ * that defines the maximum length of a text message.
+ */
+#define TEXT_SIZE 256
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+/*! \brief Dance, Kirby, Dance! @{ */
+#define V_BEGIN " --- <(\"<) --- "
+#define V_END " --- (>\")> ---\n"
+/*! @} */
+
+static const char config_file[] = "console.conf";
+
+/*!
+ * \brief Console pvt structure
+ *
+ * Currently, this is a singleton object. However, multiple instances will be
+ * needed when this module is updated for multiple device support.
+ */
+static struct console_pvt {
+ AST_DECLARE_STRING_FIELDS(
+ /*! Name of the device */
+ AST_STRING_FIELD(name);
+ /*! Default context for outgoing calls */
+ AST_STRING_FIELD(context);
+ /*! Default extension for outgoing calls */
+ AST_STRING_FIELD(exten);
+ /*! Default CallerID number */
+ AST_STRING_FIELD(cid_num);
+ /*! Default CallerID name */
+ AST_STRING_FIELD(cid_name);
+ /*! Default MOH class to listen to, if:
+ * - No MOH class set on the channel
+ * - Peer channel putting this device on hold did not suggest a class */
+ AST_STRING_FIELD(mohinterpret);
+ /*! Default language */
+ AST_STRING_FIELD(language);
+ );
+ /*! Current channel for this device */
+ struct ast_channel *owner;
+ /*! Current PortAudio stream for this device */
+ PaStream *stream;
+ /*! A frame for preparing to queue on to the channel */
+ struct ast_frame fr;
+ /*! Running = 1, Not running = 0 */
+ unsigned int streamstate:1;
+ /*! On-hook = 0, Off-hook = 1 */
+ unsigned int hookstate:1;
+ /*! Unmuted = 0, Muted = 1 */
+ unsigned int muted:1;
+ /*! Automatically answer incoming calls */
+ unsigned int autoanswer:1;
+ /*! Ignore context in the console dial CLI command */
+ unsigned int overridecontext:1;
+ /*! Lock to protect data in this struct */
+ ast_mutex_t __lock;
+ /*! ID for the stream monitor thread */
+ pthread_t thread;
+} console_pvt = {
+ .__lock = AST_MUTEX_INIT_VALUE,
+ .thread = AST_PTHREADT_NULL,
+};
+
+/*!
+ * \brief Global jitterbuffer configuration
+ *
+ * \note Disabled by default.
+ */
+static struct ast_jb_conf default_jbconf = {
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = ""
+};
+static struct ast_jb_conf global_jbconf;
+
+/*! Channel Technology Callbacks @{ */
+static struct ast_channel *console_request(const char *type, int format,
+ void *data, int *cause);
+static int console_digit_begin(struct ast_channel *c, char digit);
+static int console_digit_end(struct ast_channel *c, char digit, unsigned int duration);
+static int console_text(struct ast_channel *c, const char *text);
+static int console_hangup(struct ast_channel *c);
+static int console_answer(struct ast_channel *c);
+static struct ast_frame *console_read(struct ast_channel *chan);
+static int console_call(struct ast_channel *c, char *dest, int timeout);
+static int console_write(struct ast_channel *chan, struct ast_frame *f);
+static int console_indicate(struct ast_channel *chan, int cond,
+ const void *data, size_t datalen);
+static int console_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+/*! @} */
+
+/*!
+ * \brief Formats natively supported by this module.
+ */
+#define SUPPORTED_FORMATS ( AST_FORMAT_SLINEAR16 )
+
+static const struct ast_channel_tech console_tech = {
+ .type = "Console",
+ .description = "Console Channel Driver",
+ .capabilities = SUPPORTED_FORMATS,
+ .requester = console_request,
+ .send_digit_begin = console_digit_begin,
+ .send_digit_end = console_digit_end,
+ .send_text = console_text,
+ .hangup = console_hangup,
+ .answer = console_answer,
+ .read = console_read,
+ .call = console_call,
+ .write = console_write,
+ .indicate = console_indicate,
+ .fixup = console_fixup,
+};
+
+/*! \brief lock a console_pvt struct */
+#define console_pvt_lock(pvt) ast_mutex_lock(&(pvt)->__lock)
+
+/*! \brief unlock a console_pvt struct */
+#define console_pvt_unlock(pvt) ast_mutex_unlock(&(pvt)->__lock)
+
+/*!
+ * \brief Stream monitor thread
+ *
+ * \arg data A pointer to the console_pvt structure that contains the portaudio
+ * stream that needs to be monitored.
+ *
+ * This function runs in its own thread to monitor data coming in from a
+ * portaudio stream. When enough data is available, it is queued up to
+ * be read from the Asterisk channel.
+ */
+static void *stream_monitor(void *data)
+{
+ struct console_pvt *pvt = data;
+ char buf[NUM_SAMPLES * sizeof(int16_t)];
+ PaError res;
+ struct ast_frame f = {
+ .frametype = AST_FRAME_VOICE,
+ .subclass = AST_FORMAT_SLINEAR16,
+ .src = "console_stream_monitor",
+ .data = buf,
+ .datalen = sizeof(buf),
+ .samples = sizeof(buf) / sizeof(int16_t),
+ };
+
+ for (;;) {
+ pthread_testcancel();
+ res = Pa_ReadStream(pvt->stream, buf, sizeof(buf) / sizeof(int16_t));
+ pthread_testcancel();
+
+ if (res == paNoError)
+ ast_queue_frame(pvt->owner, &f);
+ }
+
+ return NULL;
+}
+
+static int start_stream(struct console_pvt *pvt)
+{
+ PaError res;
+ int ret_val = 0;
+
+ console_pvt_lock(pvt);
+
+ if (pvt->streamstate)
+ goto return_unlock;
+
+ pvt->streamstate = 1;
+ ast_debug(1, "Starting stream\n");
+
+ res = Pa_OpenDefaultStream(&pvt->stream, INPUT_CHANNELS, OUTPUT_CHANNELS,
+ paInt16, SAMPLE_RATE, NUM_SAMPLES, NULL, NULL);
+ if (res != paNoError) {
+ ast_log(LOG_WARNING, "Failed to open default audio device - (%d) %s\n",
+ res, Pa_GetErrorText(res));
+ ret_val = -1;
+ goto return_unlock;
+ }
+
+ res = Pa_StartStream(pvt->stream);
+ if (res != paNoError) {
+ ast_log(LOG_WARNING, "Failed to start stream - (%d) %s\n",
+ res, Pa_GetErrorText(res));
+ ret_val = -1;
+ goto return_unlock;
+ }
+
+ if (ast_pthread_create_background(&pvt->thread, NULL, stream_monitor, pvt)) {
+ ast_log(LOG_ERROR, "Failed to start stream monitor thread\n");
+ ret_val = -1;
+ }
+
+return_unlock:
+ console_pvt_unlock(pvt);
+
+ return ret_val;
+}
+
+static int stop_stream(struct console_pvt *pvt)
+{
+ if (!pvt->streamstate)
+ return 0;
+
+ pthread_cancel(pvt->thread);
+ pthread_kill(pvt->thread, SIGURG);
+ pthread_join(pvt->thread, NULL);
+
+ console_pvt_lock(pvt);
+ Pa_AbortStream(pvt->stream);
+ Pa_CloseStream(pvt->stream);
+ pvt->stream = NULL;
+ pvt->streamstate = 0;
+ console_pvt_unlock(pvt);
+
+ return 0;
+}
+
+/*!
+ * \note Called with the pvt struct locked
+ */
+static struct ast_channel *console_new(struct console_pvt *pvt, const char *ext, const char *ctx, int state)
+{
+ struct ast_channel *chan;
+
+ if (!(chan = ast_channel_alloc(1, state, pvt->cid_num, pvt->cid_name, NULL,
+ ext, ctx, 0, "Console/%s", pvt->name))) {
+ return NULL;
+ }
+
+ chan->tech = &console_tech;
+ chan->nativeformats = AST_FORMAT_SLINEAR16;
+ chan->readformat = AST_FORMAT_SLINEAR16;
+ chan->writeformat = AST_FORMAT_SLINEAR16;
+ chan->tech_pvt = pvt;
+
+ pvt->owner = chan;
+
+ if (!ast_strlen_zero(pvt->language))
+ ast_string_field_set(chan, language, pvt->language);
+
+ ast_jb_configure(chan, &global_jbconf);
+
+ if (state != AST_STATE_DOWN) {
+ if (ast_pbx_start(chan)) {
+ chan->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ ast_hangup(chan);
+ chan = NULL;
+ } else
+ start_stream(pvt);
+ }
+
+ return chan;
+}
+
+static struct ast_channel *console_request(const char *type, int format, void *data, int *cause)
+{
+ int oldformat = format;
+ struct ast_channel *chan;
+ struct console_pvt *pvt = &console_pvt;
+
+ format &= SUPPORTED_FORMATS;
+ if (!format) {
+ ast_log(LOG_NOTICE, "Channel requested with unsupported format(s): '%d'\n", oldformat);
+ return NULL;
+ }
+
+ if (pvt->owner) {
+ ast_log(LOG_NOTICE, "Console channel already active!\n");
+ *cause = AST_CAUSE_BUSY;
+ return NULL;
+ }
+
+ console_pvt_lock(pvt);
+ chan = console_new(pvt, NULL, NULL, AST_STATE_DOWN);
+ console_pvt_unlock(pvt);
+
+ if (!chan)
+ ast_log(LOG_WARNING, "Unable to create new Console channel!\n");
+
+ return chan;
+}
+
+static int console_digit_begin(struct ast_channel *c, char digit)
+{
+ ast_verb(1, V_BEGIN "Console Received Beginning of Digit %c" V_END, digit);
+
+ return -1; /* non-zero to request inband audio */
+}
+
+static int console_digit_end(struct ast_channel *c, char digit, unsigned int duration)
+{
+ ast_verb(1, V_BEGIN "Console Received End of Digit %c (duration %u)" V_END,
+ digit, duration);
+
+ return -1; /* non-zero to request inband audio */
+}
+
+static int console_text(struct ast_channel *c, const char *text)
+{
+ ast_verb(1, V_BEGIN "Console Received Text '%s'" V_END, text);
+
+ return 0;
+}
+
+static int console_hangup(struct ast_channel *c)
+{
+ struct console_pvt *pvt = &console_pvt;
+
+ ast_verb(1, V_BEGIN "Hangup on Console" V_END);
+
+ pvt->hookstate = 0;
+ c->tech_pvt = NULL;
+ pvt->owner = NULL;
+
+ stop_stream(pvt);
+
+ return 0;
+}
+
+static int console_answer(struct ast_channel *c)
+{
+ struct console_pvt *pvt = &console_pvt;
+
+ ast_verb(1, V_BEGIN "Call from Console has been Answered" V_END);
+
+ ast_setstate(c, AST_STATE_UP);
+
+ return start_stream(pvt);
+}
+
+/*
+ * \brief Implementation of the ast_channel_tech read() callback
+ *
+ * Calling this function is harmless. However, if it does get called, it
+ * is an indication that something weird happened that really shouldn't
+ * have and is worth looking into.
+ *
+ * Why should this function not get called? Well, let me explain. There are
+ * a couple of ways to pass on audio that has come from this channel. The way
+ * that this channel driver uses is that once the audio is available, it is
+ * wrapped in an ast_frame and queued onto the channel using ast_queue_frame().
+ *
+ * The other method would be signalling to the core that there is audio waiting,
+ * and that it needs to call the channel's read() callback to get it. The way
+ * the channel gets signalled is that one or more file descriptors are placed
+ * in the fds array on the ast_channel which the core will poll() on. When the
+ * fd indicates that input is available, the read() callback is called. This
+ * is especially useful when there is a dedicated file descriptor where the
+ * audio is read from. An example would be the socket for an RTP stream.
+ */
+static struct ast_frame *console_read(struct ast_channel *chan)
+{
+ ast_debug(1, "I should not be called ...\n");
+
+ return &ast_null_frame;
+}
+
+static int console_call(struct ast_channel *c, char *dest, int timeout)
+{
+ struct ast_frame f = { 0, };
+ struct console_pvt *pvt = &console_pvt;
+
+ ast_verb(1, V_BEGIN "Call to device '%s' on console from '%s' <%s>" V_END,
+ dest, c->cid.cid_name, c->cid.cid_num);
+
+ console_pvt_lock(pvt);
+
+ if (pvt->autoanswer) {
+ pvt->hookstate = 1;
+ console_pvt_unlock(pvt);
+ ast_verb(1, V_BEGIN "Auto-answered" V_END);
+ f.frametype = AST_FRAME_CONTROL;
+ f.subclass = AST_CONTROL_ANSWER;
+ } else {
+ console_pvt_unlock(pvt);
+ ast_verb(1, V_BEGIN "Type 'console answer' to answer, or use the 'autoanswer' option "
+ "for future calls" V_END);
+ f.frametype = AST_FRAME_CONTROL;
+ f.subclass = AST_CONTROL_RINGING;
+ ast_indicate(c, AST_CONTROL_RINGING);
+ }
+
+ ast_queue_frame(c, &f);
+
+ return start_stream(pvt);
+}
+
+static int console_write(struct ast_channel *chan, struct ast_frame *f)
+{
+ struct console_pvt *pvt = &console_pvt;
+
+ Pa_WriteStream(pvt->stream, f->data, f->samples);
+
+ return 0;
+}
+
+static int console_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen)
+{
+ struct console_pvt *pvt = chan->tech_pvt;
+ int res = 0;
+
+ switch (cond) {
+ case AST_CONTROL_BUSY:
+ case AST_CONTROL_CONGESTION:
+ case AST_CONTROL_RINGING:
+ case -1:
+ res = -1; /* Ask for inband indications */
+ break;
+ case AST_CONTROL_PROGRESS:
+ case AST_CONTROL_PROCEEDING:
+ case AST_CONTROL_VIDUPDATE:
+ break;
+ case AST_CONTROL_HOLD:
+ ast_verb(1, V_BEGIN "Console Has Been Placed on Hold" V_END);
+ ast_moh_start(chan, data, pvt->mohinterpret);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_verb(1, V_BEGIN "Console Has Been Retrieved from Hold" V_END);
+ ast_moh_stop(chan);
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n",
+ cond, chan->name);
+ /* The core will play inband indications for us if appropriate */
+ res = -1;
+ }
+
+ return res;
+}
+
+static int console_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct console_pvt *pvt = &console_pvt;
+
+ pvt->owner = newchan;
+
+ return 0;
+}
+
+/*!
+ * split a string in extension-context, returns pointers to malloc'ed
+ * strings.
+ * If we do not have 'overridecontext' then the last @ is considered as
+ * a context separator, and the context is overridden.
+ * This is usually not very necessary as you can play with the dialplan,
+ * and it is nice not to need it because you have '@' in SIP addresses.
+ * Return value is the buffer address.
+ *
+ * \note came from chan_oss
+ */
+static char *ast_ext_ctx(struct console_pvt *pvt, const char *src, char **ext, char **ctx)
+{
+ if (ext == NULL || ctx == NULL)
+ return NULL; /* error */
+
+ *ext = *ctx = NULL;
+
+ if (src && *src != '\0')
+ *ext = ast_strdup(src);
+
+ if (*ext == NULL)
+ return NULL;
+
+ if (!pvt->overridecontext) {
+ /* parse from the right */
+ *ctx = strrchr(*ext, '@');
+ if (*ctx)
+ *(*ctx)++ = '\0';
+ }
+
+ return *ext;
+}
+
+static char *cli_console_autoanswer(struct ast_cli_entry *e, int cmd,
+ struct ast_cli_args *a)
+{
+ struct console_pvt *pvt = &console_pvt;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console set autoanswer [on|off]";
+ e->usage =
+ "Usage: console set autoanswer [on|off]\n"
+ " Enables or disables autoanswer feature. If used without\n"
+ " argument, displays the current on/off status of autoanswer.\n"
+ " The default value of autoanswer is in 'oss.conf'.\n";
+ return NULL;
+
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc == e->args - 1) {
+ ast_cli(a->fd, "Auto answer is %s.\n", pvt->autoanswer ? "on" : "off");
+ return CLI_SUCCESS;
+ }
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+
+ if (!pvt) {
+ ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
+ pvt->name);
+ return CLI_FAILURE;
+ }
+
+ if (!strcasecmp(a->argv[e->args-1], "on"))
+ pvt->autoanswer = 1;
+ else if (!strcasecmp(a->argv[e->args - 1], "off"))
+ pvt->autoanswer = 0;
+ else
+ return CLI_SHOWUSAGE;
+
+ return CLI_SUCCESS;
+}
+
+static char *cli_console_flash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_FLASH };
+ struct console_pvt *pvt = &console_pvt;
+
+ if (cmd == CLI_INIT) {
+ e->command = "console flash";
+ e->usage =
+ "Usage: console flash\n"
+ " Flashes the call currently placed on the console.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+
+ if (!pvt->owner) {
+ ast_cli(a->fd, "No call to flash\n");
+ return CLI_FAILURE;
+ }
+
+ pvt->hookstate = 0;
+
+ ast_queue_frame(pvt->owner, &f);
+
+ return CLI_SUCCESS;
+}
+
+static char *cli_console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *s = NULL;
+ const char *mye = NULL, *myc = NULL;
+ struct console_pvt *pvt = &console_pvt;
+
+ if (cmd == CLI_INIT) {
+ e->command = "console dial";
+ e->usage =
+ "Usage: console dial [extension[@context]]\n"
+ " Dials a given extension (and context if specified)\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc > e->args + 1)
+ return CLI_SHOWUSAGE;
+
+ if (pvt->owner) { /* already in a call */
+ int i;
+ struct ast_frame f = { AST_FRAME_DTMF, 0 };
+
+ if (a->argc == e->args) { /* argument is mandatory here */
+ ast_cli(a->fd, "Already in a call. You can only dial digits until you hangup.\n");
+ return CLI_FAILURE;
+ }
+ s = a->argv[e->args];
+ /* send the string one char at a time */
+ for (i = 0; i < strlen(s); i++) {
+ f.subclass = s[i];
+ ast_queue_frame(pvt->owner, &f);
+ }
+ return CLI_SUCCESS;
+ }
+
+ /* if we have an argument split it into extension and context */
+ if (a->argc == e->args + 1) {
+ char *ext = NULL, *con = NULL;
+ s = ast_ext_ctx(pvt, a->argv[e->args], &ext, &con);
+ ast_debug(1, "provided '%s', exten '%s' context '%s'\n",
+ a->argv[e->args], mye, myc);
+ mye = ext;
+ myc = con;
+ }
+
+ /* supply default values if needed */
+ if (ast_strlen_zero(mye))
+ mye = pvt->exten;
+ if (ast_strlen_zero(myc))
+ myc = pvt->context;
+
+ if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
+ console_pvt_lock(pvt);
+ pvt->hookstate = 1;
+ console_new(pvt, mye, myc, AST_STATE_RINGING);
+ console_pvt_unlock(pvt);
+ } else
+ ast_cli(a->fd, "No such extension '%s' in context '%s'\n", mye, myc);
+
+ if (s)
+ free(s);
+
+ return CLI_SUCCESS;
+}
+
+static char *cli_console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct console_pvt *pvt = &console_pvt;
+
+ if (cmd == CLI_INIT) {
+ e->command = "console hangup";
+ e->usage =
+ "Usage: console hangup\n"
+ " Hangs up any call currently placed on the console.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+
+ if (!pvt->owner && !pvt->hookstate) {
+ ast_cli(a->fd, "No call to hang up\n");
+ return CLI_FAILURE;
+ }
+
+ pvt->hookstate = 0;
+ if (pvt->owner)
+ ast_queue_hangup(pvt->owner);
+
+ return CLI_SUCCESS;
+}
+
+static char *cli_console_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *s;
+ struct console_pvt *pvt = &console_pvt;
+
+ if (cmd == CLI_INIT) {
+ e->command = "console {mute|unmute}";
+ e->usage =
+ "Usage: console {mute|unmute}\n"
+ " Mute/unmute the microphone.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+
+ s = a->argv[e->args-1];
+ if (!strcasecmp(s, "mute"))
+ pvt->muted = 1;
+ else if (!strcasecmp(s, "unmute"))
+ pvt->muted = 0;
+ else
+ return CLI_SHOWUSAGE;
+
+ ast_verb(1, V_BEGIN "The Console is now %s" V_END,
+ pvt->muted ? "Muted" : "Unmuted");
+
+ return CLI_SUCCESS;
+}
+
+static char *cli_list_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ PaDeviceIndex index, num, def_input, def_output;
+
+ if (cmd == CLI_INIT) {
+ e->command = "console list devices";
+ e->usage =
+ "Usage: console list devices\n"
+ " List all available devices.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+
+ ast_cli(a->fd, "\n"
+ "=============================================================\n"
+ "=== Available Devices =======================================\n"
+ "=============================================================\n"
+ "===\n");
+
+ num = Pa_GetDeviceCount();
+ if (!num) {
+ ast_cli(a->fd, "(None)\n");
+ return CLI_SUCCESS;
+ }
+
+ def_input = Pa_GetDefaultInputDevice();
+ def_output = Pa_GetDefaultOutputDevice();
+ for (index = 0; index < num; index++) {
+ const PaDeviceInfo *dev = Pa_GetDeviceInfo(index);
+ if (!dev)
+ continue;
+ ast_cli(a->fd, "=== ---------------------------------------------------------\n"
+ "=== Device Name: %s\n", dev->name);
+ if (dev->maxInputChannels)
+ ast_cli(a->fd, "=== ---> %sInput Device\n", (index == def_input) ? "Default " : "");
+ if (dev->maxOutputChannels)
+ ast_cli(a->fd, "=== ---> %sOutput Device\n", (index == def_output) ? "Default " : "");
+ ast_cli(a->fd, "=== ---------------------------------------------------------\n===\n");
+ }
+
+ ast_cli(a->fd, "=============================================================\n\n");
+
+ return CLI_SUCCESS;
+}
+
+/*!
+ * \brief answer command from the console
+ */
+static char *cli_console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
+ struct console_pvt *pvt = &console_pvt;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console answer";
+ e->usage =
+ "Usage: console answer\n"
+ " Answers an incoming call on the console channel.\n";
+ return NULL;
+
+ case CLI_GENERATE:
+ return NULL; /* no completion */
+ }
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+
+ if (!pvt->owner) {
+ ast_cli(a->fd, "No one is calling us\n");
+ return CLI_FAILURE;
+ }
+
+ pvt->hookstate = 1;
+
+ ast_indicate(pvt->owner, -1);
+
+ ast_queue_frame(pvt->owner, &f);
+
+ return CLI_SUCCESS;
+}
+
+/*!
+ * \brief Console send text CLI command
+ *
+ * \note concatenate all arguments into a single string. argv is NULL-terminated
+ * so we can use it right away
+ */
+static char *cli_console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char buf[TEXT_SIZE];
+ struct console_pvt *pvt = &console_pvt;
+ struct ast_frame f = {
+ .frametype = AST_FRAME_TEXT,
+ .data = buf,
+ .src = "console_send_text",
+ };
+ int len;
+
+ if (cmd == CLI_INIT) {
+ e->command = "console send text";
+ e->usage =
+ "Usage: console send text <message>\n"
+ " Sends a text message for display on the remote terminal.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc < e->args + 1)
+ return CLI_SHOWUSAGE;
+
+ if (!pvt->owner) {
+ ast_cli(a->fd, "Not in a call\n");
+ return CLI_FAILURE;
+ }
+
+ ast_join(buf, sizeof(buf) - 1, a->argv + e->args);
+ if (ast_strlen_zero(buf))
+ return CLI_SHOWUSAGE;
+
+ len = strlen(buf);
+ buf[len] = '\n';
+ f.datalen = len + 1;
+
+ ast_queue_frame(pvt->owner, &f);
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_console[] = {
+ AST_CLI_DEFINE(cli_console_dial, "Dial an extension from the console"),
+ AST_CLI_DEFINE(cli_console_hangup, "Hangup a call on the console"),
+ AST_CLI_DEFINE(cli_console_mute, "Disable/Enable mic input"),
+ AST_CLI_DEFINE(cli_console_answer, "Answer an incoming console call"),
+ AST_CLI_DEFINE(cli_console_sendtext, "Send text to a connected party"),
+ AST_CLI_DEFINE(cli_console_flash, "Send a flash to the connected party"),
+ AST_CLI_DEFINE(cli_console_autoanswer, "Turn autoanswer on or off"),
+ AST_CLI_DEFINE(cli_list_devices, "List available devices"),
+};
+
+/*!
+ * \brief Set default values for a pvt struct
+ *
+ * \note This function expects the pvt lock to be held.
+ */
+static void set_pvt_defaults(struct console_pvt *pvt, int reload)
+{
+ if (!reload) {
+ /* This should be changed for multiple device support. Right now,
+ * there is no way to change the name of a device. The default
+ * input and output sound devices are the only ones supported. */
+ ast_string_field_set(pvt, name, "default");
+ }
+
+ ast_string_field_set(pvt, mohinterpret, "default");
+ ast_string_field_set(pvt, context, "default");
+ ast_string_field_set(pvt, exten, "s");
+ ast_string_field_set(pvt, language, "");
+ ast_string_field_set(pvt, cid_num, "");
+ ast_string_field_set(pvt, cid_name, "");
+
+ pvt->overridecontext = 0;
+ pvt->autoanswer = 0;
+}
+
+static void store_callerid(struct console_pvt *pvt, const char *value)
+{
+ char cid_name[256];
+ char cid_num[256];
+
+ ast_callerid_split(value, cid_name, sizeof(cid_name),
+ cid_num, sizeof(cid_num));
+
+ ast_string_field_set(pvt, cid_name, cid_name);
+ ast_string_field_set(pvt, cid_num, cid_num);
+}
+
+/*!
+ * \brief Store a configuration parameter in a pvt struct
+ *
+ * \note This function expects the pvt lock to be held.
+ */
+static void store_config_core(struct console_pvt *pvt, const char *var, const char *value)
+{
+ if (!ast_jb_read_conf(&global_jbconf, var, value))
+ return;
+
+ CV_START(var, value);
+
+ CV_STRFIELD("context", pvt, context);
+ CV_STRFIELD("extension", pvt, exten);
+ CV_STRFIELD("mohinterpret", pvt, mohinterpret);
+ CV_STRFIELD("language", pvt, language);
+ CV_F("callerid", store_callerid(pvt, value));
+ CV_BOOL("overridecontext", pvt->overridecontext);
+ CV_BOOL("autoanswer", pvt->autoanswer);
+
+ ast_log(LOG_WARNING, "Unknown option '%s'\n", var);
+
+ CV_END;
+}
+
+/*!
+ * \brief Load the configuration
+ * \param reload if this was called due to a reload
+ * \retval 0 succcess
+ * \retval -1 failure
+ */
+static int load_config(int reload)
+{
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ struct console_pvt *pvt = &console_pvt;
+ struct ast_flags config_flags = { 0 };
+ int res = -1;
+
+ /* default values */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(global_jbconf));
+
+ console_pvt_lock(pvt);
+
+ set_pvt_defaults(pvt, reload);
+
+ if (!(cfg = ast_config_load(config_file, config_flags))) {
+ ast_log(LOG_NOTICE, "Unable to open configuration file %s!\n", config_file);
+ goto return_unlock;
+ }
+
+ for (v = ast_variable_browse(cfg, "general"); v; v = v->next)
+ store_config_core(pvt, v->name, v->value);
+
+ ast_config_destroy(cfg);
+
+ res = 0;
+
+return_unlock:
+ console_pvt_unlock(pvt);
+ return res;
+}
+
+static int init_pvt(struct console_pvt *pvt)
+{
+ if (ast_string_field_init(pvt, 32))
+ return -1;
+
+ if (ast_mutex_init(&pvt->__lock)) {
+ ast_log(LOG_ERROR, "Failed to initialize mutex\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void destroy_pvt(struct console_pvt *pvt)
+{
+ ast_string_field_free_memory(pvt);
+
+ ast_mutex_destroy(&pvt->__lock);
+}
+
+static int unload_module(void)
+{
+ struct console_pvt *pvt = &console_pvt;
+
+ if (pvt->hookstate)
+ stop_stream(pvt);
+
+ Pa_Terminate();
+
+ ast_channel_unregister(&console_tech);
+ ast_cli_unregister_multiple(cli_console, ARRAY_LEN(cli_console));
+
+ destroy_pvt(pvt);
+
+ return 0;
+}
+
+static int load_module(void)
+{
+ PaError res;
+ struct console_pvt *pvt = &console_pvt;
+
+ if (init_pvt(pvt))
+ goto return_error;
+
+ if (load_config(0))
+ goto return_error;
+
+ res = Pa_Initialize();
+ if (res != paNoError) {
+ ast_log(LOG_WARNING, "Failed to initialize audio system - (%d) %s\n",
+ res, Pa_GetErrorText(res));
+ goto return_error_pa_init;
+ }
+
+ if (ast_channel_register(&console_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel type 'Console'\n");
+ goto return_error_chan_reg;
+ }
+
+ if (ast_cli_register_multiple(cli_console, ARRAY_LEN(cli_console)))
+ goto return_error_cli_reg;
+
+ return AST_MODULE_LOAD_SUCCESS;
+
+return_error_cli_reg:
+ ast_cli_unregister_multiple(cli_console, ARRAY_LEN(cli_console));
+return_error_chan_reg:
+ ast_channel_unregister(&console_tech);
+return_error_pa_init:
+ Pa_Terminate();
+return_error:
+ destroy_pvt(pvt);
+
+ return AST_MODULE_LOAD_DECLINE;
+}
+
+static int reload(void)
+{
+ return load_config(1);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Console Channel Driver",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+);
diff --git a/trunk/channels/chan_features.c b/trunk/channels/chan_features.c
new file mode 100644
index 000000000..65146f463
--- /dev/null
+++ b/trunk/channels/chan_features.c
@@ -0,0 +1,570 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief feature Proxy Channel
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \note *** Experimental code ****
+ *
+ * \ingroup channel_drivers
+ */
+/*** MODULEINFO
+ <defaultenabled>no</defaultenabled>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <fcntl.h>
+#include <sys/signal.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/rtp.h"
+#include "asterisk/acl.h"
+#include "asterisk/callerid.h"
+#include "asterisk/file.h"
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/manager.h"
+#include "asterisk/stringfields.h"
+
+static const char tdesc[] = "Feature Proxy Channel Driver";
+
+#define IS_OUTBOUND(a,b) (a == b->chan ? 1 : 0)
+
+struct feature_sub {
+ struct ast_channel *owner;
+ int inthreeway;
+ int pfd;
+ int timingfdbackup;
+ int alertpipebackup[2];
+};
+
+struct feature_pvt {
+ ast_mutex_t lock; /* Channel private lock */
+ char tech[AST_MAX_EXTENSION]; /* Technology to abstract */
+ char dest[AST_MAX_EXTENSION]; /* Destination to abstract */
+ struct ast_channel *subchan;
+ struct feature_sub subs[3]; /* Subs */
+ struct ast_channel *owner; /* Current Master Channel */
+ AST_LIST_ENTRY(feature_pvt) list; /* Next entity */
+};
+
+static AST_LIST_HEAD_STATIC(features, feature_pvt);
+
+#define SUB_REAL 0 /* Active call */
+#define SUB_CALLWAIT 1 /* Call-Waiting call on hold */
+#define SUB_THREEWAY 2 /* Three-way call */
+
+static struct ast_channel *features_request(const char *type, int format, void *data, int *cause);
+static int features_digit_begin(struct ast_channel *ast, char digit);
+static int features_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int features_call(struct ast_channel *ast, char *dest, int timeout);
+static int features_hangup(struct ast_channel *ast);
+static int features_answer(struct ast_channel *ast);
+static struct ast_frame *features_read(struct ast_channel *ast);
+static int features_write(struct ast_channel *ast, struct ast_frame *f);
+static int features_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+static int features_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+
+static const struct ast_channel_tech features_tech = {
+ .type = "Feature",
+ .description = tdesc,
+ .capabilities = -1,
+ .requester = features_request,
+ .send_digit_begin = features_digit_begin,
+ .send_digit_end = features_digit_end,
+ .call = features_call,
+ .hangup = features_hangup,
+ .answer = features_answer,
+ .read = features_read,
+ .write = features_write,
+ .exception = features_read,
+ .indicate = features_indicate,
+ .fixup = features_fixup,
+};
+
+static inline void init_sub(struct feature_sub *sub)
+{
+ sub->inthreeway = 0;
+ sub->pfd = -1;
+ sub->timingfdbackup = -1;
+ sub->alertpipebackup[0] = sub->alertpipebackup[1] = -1;
+}
+
+static inline int indexof(struct feature_pvt *p, struct ast_channel *owner, int nullok)
+{
+ int x;
+ if (!owner) {
+ ast_log(LOG_WARNING, "indexof called on NULL owner??\n");
+ return -1;
+ }
+ for (x=0; x<3; x++) {
+ if (owner == p->subs[x].owner)
+ return x;
+ }
+ return -1;
+}
+
+#if 0
+static void wakeup_sub(struct feature_pvt *p, int a)
+{
+ struct ast_frame null = { AST_FRAME_NULL, };
+ for (;;) {
+ if (p->subs[a].owner) {
+ if (ast_mutex_trylock(&p->subs[a].owner->lock)) {
+ ast_mutex_unlock(&p->lock);
+ usleep(1);
+ ast_mutex_lock(&p->lock);
+ } else {
+ ast_queue_frame(p->subs[a].owner, &null);
+ ast_mutex_unlock(&p->subs[a].owner->lock);
+ break;
+ }
+ } else
+ break;
+ }
+}
+#endif
+
+static void restore_channel(struct feature_pvt *p, int index)
+{
+ /* Restore timing/alertpipe */
+ p->subs[index].owner->timingfd = p->subs[index].timingfdbackup;
+ p->subs[index].owner->alertpipe[0] = p->subs[index].alertpipebackup[0];
+ p->subs[index].owner->alertpipe[1] = p->subs[index].alertpipebackup[1];
+ ast_channel_set_fd(p->subs[index].owner, AST_ALERT_FD, p->subs[index].alertpipebackup[0]);
+ ast_channel_set_fd(p->subs[index].owner, AST_TIMING_FD, p->subs[index].timingfdbackup);
+}
+
+static void update_features(struct feature_pvt *p, int index)
+{
+ int x;
+ if (p->subs[index].owner) {
+ for (x=0; x<AST_MAX_FDS; x++) {
+ if (index)
+ ast_channel_set_fd(p->subs[index].owner, x, -1);
+ else
+ ast_channel_set_fd(p->subs[index].owner, x, p->subchan->fds[x]);
+ }
+ if (!index) {
+ /* Copy timings from master channel */
+ p->subs[index].owner->timingfd = p->subchan->timingfd;
+ p->subs[index].owner->alertpipe[0] = p->subchan->alertpipe[0];
+ p->subs[index].owner->alertpipe[1] = p->subchan->alertpipe[1];
+ if (p->subs[index].owner->nativeformats != p->subchan->readformat) {
+ p->subs[index].owner->nativeformats = p->subchan->readformat;
+ if (p->subs[index].owner->readformat)
+ ast_set_read_format(p->subs[index].owner, p->subs[index].owner->readformat);
+ if (p->subs[index].owner->writeformat)
+ ast_set_write_format(p->subs[index].owner, p->subs[index].owner->writeformat);
+ }
+ } else{
+ restore_channel(p, index);
+ }
+ }
+}
+
+#if 0
+static void swap_subs(struct feature_pvt *p, int a, int b)
+{
+ int tinthreeway;
+ struct ast_channel *towner;
+
+ ast_debug(1, "Swapping %d and %d\n", a, b);
+
+ towner = p->subs[a].owner;
+ tinthreeway = p->subs[a].inthreeway;
+
+ p->subs[a].owner = p->subs[b].owner;
+ p->subs[a].inthreeway = p->subs[b].inthreeway;
+
+ p->subs[b].owner = towner;
+ p->subs[b].inthreeway = tinthreeway;
+ update_features(p,a);
+ update_features(p,b);
+ wakeup_sub(p, a);
+ wakeup_sub(p, b);
+}
+#endif
+
+static int features_answer(struct ast_channel *ast)
+{
+ struct feature_pvt *p = ast->tech_pvt;
+ int res = -1;
+ int x;
+
+ ast_mutex_lock(&p->lock);
+ x = indexof(p, ast, 0);
+ if (!x && p->subchan)
+ res = ast_answer(p->subchan);
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static struct ast_frame *features_read(struct ast_channel *ast)
+{
+ struct feature_pvt *p = ast->tech_pvt;
+ struct ast_frame *f;
+ int x;
+
+ f = &ast_null_frame;
+ ast_mutex_lock(&p->lock);
+ x = indexof(p, ast, 0);
+ if (!x && p->subchan) {
+ update_features(p, x);
+ f = ast_read(p->subchan);
+ }
+ ast_mutex_unlock(&p->lock);
+ return f;
+}
+
+static int features_write(struct ast_channel *ast, struct ast_frame *f)
+{
+ struct feature_pvt *p = ast->tech_pvt;
+ int res = -1;
+ int x;
+
+ ast_mutex_lock(&p->lock);
+ x = indexof(p, ast, 0);
+ if (!x && p->subchan)
+ res = ast_write(p->subchan, f);
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static int features_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct feature_pvt *p = newchan->tech_pvt;
+ int x;
+
+ ast_mutex_lock(&p->lock);
+ if (p->owner == oldchan)
+ p->owner = newchan;
+ for (x = 0; x < 3; x++) {
+ if (p->subs[x].owner == oldchan)
+ p->subs[x].owner = newchan;
+ }
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int features_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+ struct feature_pvt *p = ast->tech_pvt;
+ int res = -1;
+ int x;
+
+ /* Queue up a frame representing the indication as a control frame */
+ ast_mutex_lock(&p->lock);
+ x = indexof(p, ast, 0);
+ if (!x && p->subchan)
+ res = ast_indicate(p->subchan, condition);
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static int features_digit_begin(struct ast_channel *ast, char digit)
+{
+ struct feature_pvt *p = ast->tech_pvt;
+ int res = -1;
+ int x;
+
+ /* Queue up a frame representing the indication as a control frame */
+ ast_mutex_lock(&p->lock);
+ x = indexof(p, ast, 0);
+ if (!x && p->subchan)
+ res = ast_senddigit_begin(p->subchan, digit);
+ ast_mutex_unlock(&p->lock);
+
+ return res;
+}
+
+static int features_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct feature_pvt *p = ast->tech_pvt;
+ int res = -1;
+ int x;
+
+ /* Queue up a frame representing the indication as a control frame */
+ ast_mutex_lock(&p->lock);
+ x = indexof(p, ast, 0);
+ if (!x && p->subchan)
+ res = ast_senddigit_end(p->subchan, digit, duration);
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static int features_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ struct feature_pvt *p = ast->tech_pvt;
+ int res = -1;
+ int x;
+ char *dest2;
+
+ dest2 = strchr(dest, '/');
+ if (dest2) {
+ ast_mutex_lock(&p->lock);
+ x = indexof(p, ast, 0);
+ if (!x && p->subchan) {
+ p->subchan->cid.cid_num = ast_strdup(p->owner->cid.cid_num);
+ p->subchan->cid.cid_name = ast_strdup(p->owner->cid.cid_name);
+ p->subchan->cid.cid_rdnis = ast_strdup(p->owner->cid.cid_rdnis);
+ p->subchan->cid.cid_ani = ast_strdup(p->owner->cid.cid_ani);
+
+ p->subchan->cid.cid_pres = p->owner->cid.cid_pres;
+ ast_string_field_set(p->subchan, language, p->owner->language);
+ ast_string_field_set(p->subchan, accountcode, p->owner->accountcode);
+ p->subchan->cdrflags = p->owner->cdrflags;
+ res = ast_call(p->subchan, dest2, timeout);
+ update_features(p, x);
+ } else
+ ast_log(LOG_NOTICE, "Uhm yah, not quite there with the call waiting...\n");
+ ast_mutex_unlock(&p->lock);
+ }
+ return res;
+}
+
+static int features_hangup(struct ast_channel *ast)
+{
+ struct feature_pvt *p = ast->tech_pvt;
+ int x;
+
+ ast_mutex_lock(&p->lock);
+ x = indexof(p, ast, 0);
+ if (x > -1) {
+ restore_channel(p, x);
+ p->subs[x].owner = NULL;
+ /* XXX Re-arrange, unconference, etc XXX */
+ }
+ ast->tech_pvt = NULL;
+
+ if (!p->subs[SUB_REAL].owner && !p->subs[SUB_CALLWAIT].owner && !p->subs[SUB_THREEWAY].owner) {
+ ast_mutex_unlock(&p->lock);
+ /* Remove from list */
+ AST_LIST_LOCK(&features);
+ AST_LIST_REMOVE(&features, p, list);
+ AST_LIST_UNLOCK(&features);
+ ast_mutex_lock(&p->lock);
+ /* And destroy */
+ if (p->subchan)
+ ast_hangup(p->subchan);
+ ast_mutex_unlock(&p->lock);
+ ast_mutex_destroy(&p->lock);
+ ast_free(p);
+ return 0;
+ }
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static struct feature_pvt *features_alloc(char *data, int format)
+{
+ struct feature_pvt *tmp;
+ char *dest=NULL;
+ char *tech;
+ int x;
+ int status;
+ struct ast_channel *chan;
+
+ tech = ast_strdupa(data);
+ if (tech) {
+ dest = strchr(tech, '/');
+ if (dest) {
+ *dest = '\0';
+ dest++;
+ }
+ }
+ if (!tech || !dest) {
+ ast_log(LOG_NOTICE, "Format for feature channel is Feature/Tech/Dest ('%s' not valid)!\n",
+ data);
+ return NULL;
+ }
+ AST_LIST_LOCK(&features);
+ AST_LIST_TRAVERSE(&features, tmp, list) {
+ if (!strcasecmp(tmp->tech, tech) && !strcmp(tmp->dest, dest))
+ break;
+ }
+ AST_LIST_UNLOCK(&features);
+ if (!tmp) {
+ chan = ast_request(tech, format, dest, &status);
+ if (!chan) {
+ ast_log(LOG_NOTICE, "Unable to allocate subchannel '%s/%s'\n", tech, dest);
+ return NULL;
+ }
+ tmp = ast_calloc(1, sizeof(*tmp));
+ if (tmp) {
+ for (x=0;x<3;x++)
+ init_sub(tmp->subs + x);
+ ast_mutex_init(&tmp->lock);
+ ast_copy_string(tmp->tech, tech, sizeof(tmp->tech));
+ ast_copy_string(tmp->dest, dest, sizeof(tmp->dest));
+ tmp->subchan = chan;
+ AST_LIST_LOCK(&features);
+ AST_LIST_INSERT_HEAD(&features, tmp, list);
+ AST_LIST_UNLOCK(&features);
+ }
+ }
+ return tmp;
+}
+
+static struct ast_channel *features_new(struct feature_pvt *p, int state, int index)
+{
+ struct ast_channel *tmp;
+ int x,y;
+ char *b2 = 0;
+ if (!p->subchan) {
+ ast_log(LOG_WARNING, "Called upon channel with no subchan:(\n");
+ return NULL;
+ }
+ if (p->subs[index].owner) {
+ ast_log(LOG_WARNING, "Called to put index %d already there!\n", index);
+ return NULL;
+ }
+ /* figure out what you want the name to be */
+ for (x=1;x<4;x++) {
+ if (b2)
+ ast_free(b2);
+ asprintf(&b2, "%s/%s-%d", p->tech, p->dest, x);
+ for (y=0;y<3;y++) {
+ if (y == index)
+ continue;
+ if (p->subs[y].owner && !strcasecmp(p->subs[y].owner->name, b2))
+ break;
+ }
+ if (y >= 3)
+ break;
+ }
+ tmp = ast_channel_alloc(0, state, 0,0, "", "", "", 0, "Feature/%s", b2);
+ /* free up the name, it was copied into the channel name */
+ if (b2)
+ ast_free(b2);
+ if (!tmp) {
+ ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+ return NULL;
+ }
+ tmp->tech = &features_tech;
+ tmp->writeformat = p->subchan->writeformat;
+ tmp->rawwriteformat = p->subchan->rawwriteformat;
+ tmp->readformat = p->subchan->readformat;
+ tmp->rawreadformat = p->subchan->rawreadformat;
+ tmp->nativeformats = p->subchan->readformat;
+ tmp->tech_pvt = p;
+ p->subs[index].owner = tmp;
+ if (!p->owner)
+ p->owner = tmp;
+ ast_module_ref(ast_module_info->self);
+ return tmp;
+}
+
+
+static struct ast_channel *features_request(const char *type, int format, void *data, int *cause)
+{
+ struct feature_pvt *p;
+ struct ast_channel *chan = NULL;
+
+ p = features_alloc(data, format);
+ if (p && !p->subs[SUB_REAL].owner)
+ chan = features_new(p, AST_STATE_DOWN, SUB_REAL);
+ if (chan)
+ update_features(p,SUB_REAL);
+ return chan;
+}
+
+static char *features_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct feature_pvt *p;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "feature show channels";
+ e->usage =
+ "Usage: feature show channels\n"
+ " Provides summary information on feature channels.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ if (AST_LIST_EMPTY(&features)) {
+ ast_cli(a->fd, "No feature channels in use\n");
+ return CLI_SUCCESS;
+ }
+
+ AST_LIST_LOCK(&features);
+ AST_LIST_TRAVERSE(&features, p, list) {
+ ast_mutex_lock(&p->lock);
+ ast_cli(a->fd, "%s -- %s/%s\n", p->owner ? p->owner->name : "<unowned>", p->tech, p->dest);
+ ast_mutex_unlock(&p->lock);
+ }
+ AST_LIST_UNLOCK(&features);
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_features[] = {
+ AST_CLI_DEFINE(features_show, "List status of feature channels"),
+};
+
+static int load_module(void)
+{
+ /* Make sure we can register our sip channel type */
+ if (ast_channel_register(&features_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'Feature'\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ ast_cli_register_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry));
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ struct feature_pvt *p;
+
+ /* First, take us out of the channel loop */
+ ast_cli_unregister_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry));
+ ast_channel_unregister(&features_tech);
+
+ if (!AST_LIST_LOCK(&features))
+ return -1;
+ /* Hangup all interfaces if they have an owner */
+ while ((p = AST_LIST_REMOVE_HEAD(&features, list))) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ ast_free(p);
+ }
+ AST_LIST_UNLOCK(&features);
+
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Feature Proxy Channel");
+
diff --git a/trunk/channels/chan_gtalk.c b/trunk/channels/chan_gtalk.c
new file mode 100644
index 000000000..05e7de704
--- /dev/null
+++ b/trunk/channels/chan_gtalk.c
@@ -0,0 +1,1956 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Matt O'Gorman <mogorman@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \author Matt O'Gorman <mogorman@digium.com>
+ *
+ * \brief Gtalk Channel Driver, until google/libjingle works with jingle spec
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <depend>iksemel</depend>
+ <depend>res_jabber</depend>
+ <use>openssl</use>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/signal.h>
+#include <iksemel.h>
+#include <pthread.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/rtp.h"
+#include "asterisk/acl.h"
+#include "asterisk/callerid.h"
+#include "asterisk/file.h"
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/manager.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/astobj.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/jabber.h"
+
+#define GOOGLE_CONFIG "gtalk.conf"
+
+#define GOOGLE_NS "http://www.google.com/session"
+
+
+/*! Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = ""
+};
+static struct ast_jb_conf global_jbconf;
+
+enum gtalk_protocol {
+ AJI_PROTOCOL_UDP = 1,
+ AJI_PROTOCOL_SSLTCP = 2,
+};
+
+enum gtalk_connect_type {
+ AJI_CONNECT_STUN = 1,
+ AJI_CONNECT_LOCAL = 2,
+ AJI_CONNECT_RELAY = 3,
+};
+
+struct gtalk_pvt {
+ ast_mutex_t lock; /*!< Channel private lock */
+ time_t laststun;
+ struct gtalk *parent; /*!< Parent client */
+ char sid[100];
+ char us[AJI_MAX_JIDLEN];
+ char them[AJI_MAX_JIDLEN];
+ char ring[10]; /*!< Message ID of ring */
+ iksrule *ringrule; /*!< Rule for matching RING request */
+ int initiator; /*!< If we're the initiator */
+ int alreadygone;
+ int capability;
+ struct ast_codec_pref prefs;
+ struct gtalk_candidate *theircandidates;
+ struct gtalk_candidate *ourcandidates;
+ char cid_num[80]; /*!< Caller ID num */
+ char cid_name[80]; /*!< Caller ID name */
+ char exten[80]; /*!< Called extension */
+ struct ast_channel *owner; /*!< Master Channel */
+ struct ast_rtp *rtp; /*!< RTP audio session */
+ struct ast_rtp *vrtp; /*!< RTP video session */
+ int jointcapability; /*!< Supported capability at both ends (codecs ) */
+ int peercapability;
+ struct gtalk_pvt *next; /* Next entity */
+};
+
+struct gtalk_candidate {
+ char name[100];
+ enum gtalk_protocol protocol;
+ double preference;
+ char username[100];
+ char password[100];
+ enum gtalk_connect_type type;
+ char network[6];
+ int generation;
+ char ip[16];
+ int port;
+ int receipt;
+ struct gtalk_candidate *next;
+};
+
+struct gtalk {
+ ASTOBJ_COMPONENTS(struct gtalk);
+ struct aji_client *connection;
+ struct aji_buddy *buddy;
+ struct gtalk_pvt *p;
+ struct ast_codec_pref prefs;
+ int amaflags; /*!< AMA Flags */
+ char user[AJI_MAX_JIDLEN];
+ char context[AST_MAX_CONTEXT];
+ char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Account code */
+ int capability;
+ ast_group_t callgroup; /*!< Call group */
+ ast_group_t pickupgroup; /*!< Pickup group */
+ int callingpres; /*!< Calling presentation */
+ int allowguest;
+ char language[MAX_LANGUAGE]; /*!< Default language for prompts */
+ char musicclass[MAX_MUSICCLASS]; /*!< Music on Hold class */
+};
+
+struct gtalk_container {
+ ASTOBJ_CONTAINER_COMPONENTS(struct gtalk);
+};
+
+static const char desc[] = "Gtalk Channel";
+
+static int global_capability = AST_FORMAT_ULAW | AST_FORMAT_ALAW | AST_FORMAT_GSM | AST_FORMAT_H263;
+
+AST_MUTEX_DEFINE_STATIC(gtalklock); /*!< Protect the interface list (of gtalk_pvt's) */
+
+/* Forward declarations */
+static struct ast_channel *gtalk_request(const char *type, int format, void *data, int *cause);
+static int gtalk_digit(struct ast_channel *ast, char digit, unsigned int duration);
+static int gtalk_digit_begin(struct ast_channel *ast, char digit);
+static int gtalk_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int gtalk_call(struct ast_channel *ast, char *dest, int timeout);
+static int gtalk_hangup(struct ast_channel *ast);
+static int gtalk_answer(struct ast_channel *ast);
+static int gtalk_newcall(struct gtalk *client, ikspak *pak);
+static struct ast_frame *gtalk_read(struct ast_channel *ast);
+static int gtalk_write(struct ast_channel *ast, struct ast_frame *f);
+static int gtalk_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+static int gtalk_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static int gtalk_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
+static struct gtalk_pvt *gtalk_alloc(struct gtalk *client, const char *us, const char *them, const char *sid);
+static char *gtalk_do_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *gtalk_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+/*----- RTP interface functions */
+static int gtalk_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp,
+ struct ast_rtp *vrtp, struct ast_rtp *trtp, int codecs, int nat_active);
+static enum ast_rtp_get_result gtalk_get_rtp_peer(struct ast_channel *chan, struct ast_rtp **rtp);
+static int gtalk_get_codec(struct ast_channel *chan);
+
+/*! \brief PBX interface structure for channel registration */
+static const struct ast_channel_tech gtalk_tech = {
+ .type = "Gtalk",
+ .description = "Gtalk Channel Driver",
+ .capabilities = AST_FORMAT_AUDIO_MASK,
+ .requester = gtalk_request,
+ .send_digit_begin = gtalk_digit_begin,
+ .send_digit_end = gtalk_digit_end,
+ .bridge = ast_rtp_bridge,
+ .call = gtalk_call,
+ .hangup = gtalk_hangup,
+ .answer = gtalk_answer,
+ .read = gtalk_read,
+ .write = gtalk_write,
+ .exception = gtalk_read,
+ .indicate = gtalk_indicate,
+ .fixup = gtalk_fixup,
+ .send_html = gtalk_sendhtml,
+ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER
+};
+
+static struct sockaddr_in bindaddr = { 0, }; /*!< The address we bind to */
+
+static struct sched_context *sched; /*!< The scheduling context */
+static struct io_context *io; /*!< The IO context */
+static struct in_addr __ourip;
+
+/*! \brief RTP driver interface */
+static struct ast_rtp_protocol gtalk_rtp = {
+ type: "Gtalk",
+ get_rtp_info: gtalk_get_rtp_peer,
+ set_rtp_peer: gtalk_set_rtp_peer,
+ get_codec: gtalk_get_codec,
+};
+
+static struct ast_cli_entry gtalk_cli[] = {
+ AST_CLI_DEFINE(gtalk_do_reload, "Reload GoogleTalk configuration"),
+ AST_CLI_DEFINE(gtalk_show_channels, "Show GoogleTalk channels"),
+};
+
+static char externip[16];
+
+static struct gtalk_container gtalk_list;
+
+static void gtalk_member_destroy(struct gtalk *obj)
+{
+ ast_free(obj);
+}
+
+static struct gtalk *find_gtalk(char *name, char *connection)
+{
+ struct gtalk *gtalk = NULL;
+ char *domain = NULL , *s = NULL;
+
+ if (strchr(connection, '@')) {
+ s = ast_strdupa(connection);
+ domain = strsep(&s, "@");
+ ast_verbose("OOOOH domain = %s\n", domain);
+ }
+ gtalk = ASTOBJ_CONTAINER_FIND(&gtalk_list, name);
+ if (!gtalk && strchr(name, '@'))
+ gtalk = ASTOBJ_CONTAINER_FIND_FULL(&gtalk_list, name, user,,, strcasecmp);
+
+ if (!gtalk) { /* guest call */
+ ASTOBJ_CONTAINER_TRAVERSE(&gtalk_list, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ if (!strcasecmp(iterator->name, "guest")) {
+ if (!strcasecmp(iterator->connection->jid->partial, connection)) {
+ gtalk = iterator;
+ } else if (!strcasecmp(iterator->connection->name, connection)) {
+ gtalk = iterator;
+ } else if (iterator->connection->component && !strcasecmp(iterator->connection->user,domain)) {
+ gtalk = iterator;
+ }
+ }
+ ASTOBJ_UNLOCK(iterator);
+
+ if (gtalk)
+ break;
+ });
+
+ }
+ return gtalk;
+}
+
+
+static int add_codec_to_answer(const struct gtalk_pvt *p, int codec, iks *dcodecs)
+{
+ char *format = ast_getformatname(codec);
+
+ if (!strcasecmp("ulaw", format)) {
+ iks *payload_eg711u, *payload_pcmu;
+ payload_pcmu = iks_new("payload-type");
+ payload_eg711u = iks_new("payload-type");
+
+ if(!payload_eg711u || !payload_pcmu) {
+ if(payload_pcmu)
+ iks_delete(payload_pcmu);
+ if(payload_eg711u)
+ iks_delete(payload_eg711u);
+ ast_log(LOG_WARNING,"Failed to allocate iks node");
+ return -1;
+ }
+ iks_insert_attrib(payload_pcmu, "id", "0");
+ iks_insert_attrib(payload_pcmu, "name", "PCMU");
+ iks_insert_attrib(payload_pcmu, "clockrate","8000");
+ iks_insert_attrib(payload_pcmu, "bitrate","64000");
+ iks_insert_attrib(payload_eg711u, "id", "100");
+ iks_insert_attrib(payload_eg711u, "name", "EG711U");
+ iks_insert_attrib(payload_eg711u, "clockrate","8000");
+ iks_insert_attrib(payload_eg711u, "bitrate","64000");
+ iks_insert_node(dcodecs, payload_pcmu);
+ iks_insert_node(dcodecs, payload_eg711u);
+ }
+ if (!strcasecmp("alaw", format)) {
+ iks *payload_eg711a, *payload_pcma;
+ payload_pcma = iks_new("payload-type");
+ payload_eg711a = iks_new("payload-type");
+ if(!payload_eg711a || !payload_pcma) {
+ if(payload_eg711a)
+ iks_delete(payload_eg711a);
+ if(payload_pcma)
+ iks_delete(payload_pcma);
+ ast_log(LOG_WARNING,"Failed to allocate iks node");
+ return -1;
+ }
+ iks_insert_attrib(payload_pcma, "id", "8");
+ iks_insert_attrib(payload_pcma, "name", "PCMA");
+ iks_insert_attrib(payload_pcma, "clockrate","8000");
+ iks_insert_attrib(payload_pcma, "bitrate","64000");
+ payload_eg711a = iks_new("payload-type");
+ iks_insert_attrib(payload_eg711a, "id", "101");
+ iks_insert_attrib(payload_eg711a, "name", "EG711A");
+ iks_insert_attrib(payload_eg711a, "clockrate","8000");
+ iks_insert_attrib(payload_eg711a, "bitrate","64000");
+ iks_insert_node(dcodecs, payload_pcma);
+ iks_insert_node(dcodecs, payload_eg711a);
+ }
+ if (!strcasecmp("ilbc", format)) {
+ iks *payload_ilbc = iks_new("payload-type");
+ if(!payload_ilbc) {
+ ast_log(LOG_WARNING,"Failed to allocate iks node");
+ return -1;
+ }
+ iks_insert_attrib(payload_ilbc, "id", "97");
+ iks_insert_attrib(payload_ilbc, "name", "iLBC");
+ iks_insert_attrib(payload_ilbc, "clockrate","8000");
+ iks_insert_attrib(payload_ilbc, "bitrate","13300");
+ iks_insert_node(dcodecs, payload_ilbc);
+ }
+ if (!strcasecmp("g723", format)) {
+ iks *payload_g723 = iks_new("payload-type");
+ if(!payload_g723) {
+ ast_log(LOG_WARNING,"Failed to allocate iks node");
+ return -1;
+ }
+ iks_insert_attrib(payload_g723, "id", "4");
+ iks_insert_attrib(payload_g723, "name", "G723");
+ iks_insert_attrib(payload_g723, "clockrate","8000");
+ iks_insert_attrib(payload_g723, "bitrate","6300");
+ iks_insert_node(dcodecs, payload_g723);
+ }
+ if (!strcasecmp("speex", format)) {
+ iks *payload_speex = iks_new("payload-type");
+ if(!payload_speex) {
+ ast_log(LOG_WARNING,"Failed to allocate iks node");
+ return -1;
+ }
+ iks_insert_attrib(payload_speex, "id", "110");
+ iks_insert_attrib(payload_speex, "name", "speex");
+ iks_insert_attrib(payload_speex, "clockrate","8000");
+ iks_insert_attrib(payload_speex, "bitrate","11000");
+ iks_insert_node(dcodecs, payload_speex);
+ }
+ ast_rtp_lookup_code(p->rtp, 1, codec);
+ return 0;
+}
+
+static int gtalk_invite(struct gtalk_pvt *p, char *to, char *from, char *sid, int initiator)
+{
+ struct gtalk *client = p->parent;
+ iks *iq, *gtalk, *dcodecs, *payload_telephone, *transport;
+ int x;
+ int pref_codec = 0;
+ int alreadysent = 0;
+
+
+ iq = iks_new("iq");
+ gtalk = iks_new("session");
+ dcodecs = iks_new("description");
+ transport = iks_new("transport");
+ payload_telephone = iks_new("payload-type");
+ if (!(iq && gtalk && dcodecs && transport && payload_telephone)){
+ if(iq)
+ iks_delete(iq);
+ if(gtalk)
+ iks_delete(gtalk);
+ if(dcodecs)
+ iks_delete(dcodecs);
+ if(transport)
+ iks_delete(transport);
+ if(payload_telephone)
+ iks_delete(payload_telephone);
+
+ ast_log(LOG_ERROR, "Could not allocate iksemel nodes\n");
+ return 0;
+ }
+ iks_insert_attrib(dcodecs, "xmlns", "http://www.google.com/session/phone");
+ iks_insert_attrib(dcodecs, "xml:lang", "en");
+
+ for (x = 0; x < 32; x++) {
+ if (!(pref_codec = ast_codec_pref_index(&client->prefs, x)))
+ break;
+ if (!(client->capability & pref_codec))
+ continue;
+ if (alreadysent & pref_codec)
+ continue;
+ add_codec_to_answer(p, pref_codec, dcodecs);
+ alreadysent |= pref_codec;
+ }
+
+ iks_insert_attrib(payload_telephone, "id", "106");
+ iks_insert_attrib(payload_telephone, "name", "telephone-event");
+ iks_insert_attrib(payload_telephone, "clockrate", "8000");
+
+ iks_insert_attrib(transport,"xmlns","http://www.google.com/transport/p2p");
+
+ iks_insert_attrib(iq, "type", "set");
+ iks_insert_attrib(iq, "to", to);
+ iks_insert_attrib(iq, "from", from);
+ iks_insert_attrib(iq, "id", client->connection->mid);
+ ast_aji_increment_mid(client->connection->mid);
+
+ iks_insert_attrib(gtalk, "xmlns", "http://www.google.com/session");
+ iks_insert_attrib(gtalk, "type",initiator ? "initiate": "accept");
+ iks_insert_attrib(gtalk, "initiator", initiator ? from : to);
+ iks_insert_attrib(gtalk, "id", sid);
+ iks_insert_node(iq, gtalk);
+ iks_insert_node(gtalk, dcodecs);
+ iks_insert_node(gtalk, transport);
+ iks_insert_node(dcodecs, payload_telephone);
+
+ ast_aji_send(client->connection, iq);
+ iks_delete(payload_telephone);
+ iks_delete(transport);
+ iks_delete(dcodecs);
+ iks_delete(gtalk);
+ iks_delete(iq);
+ return 1;
+}
+
+static int gtalk_invite_response(struct gtalk_pvt *p, char *to , char *from, char *sid, int initiator)
+{
+ iks *iq, *session, *transport;
+ iq = iks_new("iq");
+ session = iks_new("session");
+ transport = iks_new("transport");
+ if(!(iq && session && transport)) {
+ if(iq)
+ iks_delete(iq);
+ if(session)
+ iks_delete(session);
+ if(transport)
+ iks_delete(transport);
+ ast_log(LOG_ERROR, " Unable to allocate IKS node\n");
+ return -1;
+ }
+ iks_insert_attrib(iq, "from", from);
+ iks_insert_attrib(iq, "to", to);
+ iks_insert_attrib(iq, "type", "set");
+ iks_insert_attrib(iq, "id",p->parent->connection->mid);
+ ast_aji_increment_mid(p->parent->connection->mid);
+ iks_insert_attrib(session, "type", "transport-accept");
+ iks_insert_attrib(session, "id", sid);
+ iks_insert_attrib(session, "initiator", initiator ? from : to);
+ iks_insert_attrib(session, "xmlns", "http://www.google.com/session");
+ iks_insert_attrib(transport, "xmlns", "http://www.google.com/transport/p2p");
+ iks_insert_node(iq,session);
+ iks_insert_node(session,transport);
+ ast_aji_send(p->parent->connection, iq);
+ iks_delete(transport);
+ iks_delete(session);
+ iks_delete(iq);
+ return 1;
+
+}
+
+static int gtalk_ringing_ack(void *data, ikspak *pak)
+{
+ struct gtalk_pvt *p = data;
+
+ if (p->ringrule)
+ iks_filter_remove_rule(p->parent->connection->f, p->ringrule);
+ p->ringrule = NULL;
+ if (p->owner)
+ ast_queue_control(p->owner, AST_CONTROL_RINGING);
+ return IKS_FILTER_EAT;
+}
+
+static int gtalk_answer(struct ast_channel *ast)
+{
+ struct gtalk_pvt *p = ast->tech_pvt;
+ int res = 0;
+
+ ast_debug(1, "Answer!\n");
+ ast_mutex_lock(&p->lock);
+ gtalk_invite(p, p->them, p->us,p->sid, 0);
+ manager_event(EVENT_FLAG_SYSTEM, "ChannelUpdate", "Channel: %s\r\nChanneltype: %s\r\nGtalk-SID: %s\r\n",
+ ast->name, "GTALK", p->sid);
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static enum ast_rtp_get_result gtalk_get_rtp_peer(struct ast_channel *chan, struct ast_rtp **rtp)
+{
+ struct gtalk_pvt *p = chan->tech_pvt;
+ enum ast_rtp_get_result res = AST_RTP_GET_FAILED;
+
+ if (!p)
+ return res;
+
+ ast_mutex_lock(&p->lock);
+ if (p->rtp){
+ *rtp = p->rtp;
+ res = AST_RTP_TRY_PARTIAL;
+ }
+ ast_mutex_unlock(&p->lock);
+
+ return res;
+}
+
+static int gtalk_get_codec(struct ast_channel *chan)
+{
+ struct gtalk_pvt *p = chan->tech_pvt;
+ return p->peercapability;
+}
+
+static int gtalk_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp, struct ast_rtp *vrtp, struct ast_rtp *trtp, int codecs, int nat_active)
+{
+ struct gtalk_pvt *p;
+
+ p = chan->tech_pvt;
+ if (!p)
+ return -1;
+ ast_mutex_lock(&p->lock);
+
+/* if (rtp)
+ ast_rtp_get_peer(rtp, &p->redirip);
+ else
+ memset(&p->redirip, 0, sizeof(p->redirip));
+ p->redircodecs = codecs; */
+
+ /* Reset lastrtprx timer */
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int gtalk_response(struct gtalk *client, char *from, ikspak *pak, const char *reasonstr, const char *reasonstr2)
+{
+ iks *response = NULL, *error = NULL, *reason = NULL;
+ int res = -1;
+
+ response = iks_new("iq");
+ if (response) {
+ iks_insert_attrib(response, "type", "result");
+ iks_insert_attrib(response, "from", from);
+ iks_insert_attrib(response, "to", iks_find_attrib(pak->x, "from"));
+ iks_insert_attrib(response, "id", iks_find_attrib(pak->x, "id"));
+ if (reasonstr) {
+ error = iks_new("error");
+ if (error) {
+ iks_insert_attrib(error, "type", "cancel");
+ reason = iks_new(reasonstr);
+ if (reason)
+ iks_insert_node(error, reason);
+ iks_insert_node(response, error);
+ }
+ }
+ ast_aji_send(client->connection, response);
+ if (reason)
+ iks_delete(reason);
+ if (error)
+ iks_delete(error);
+ iks_delete(response);
+ res = 0;
+ }
+ return res;
+}
+
+static int gtalk_is_answered(struct gtalk *client, ikspak *pak)
+{
+ struct gtalk_pvt *tmp;
+ char *from;
+ ast_debug(1, "The client is %s\n", client->name);
+ /* Make sure our new call doesn't exist yet */
+ for (tmp = client->p; tmp; tmp = tmp->next) {
+ if (iks_find_with_attrib(pak->x, "session", "id", tmp->sid))
+ break;
+ }
+
+ from = iks_find_attrib(pak->x, "to");
+ if(!from)
+ from = client->connection->jid->full;
+
+ if (tmp) {
+ if (tmp->owner)
+ ast_queue_control(tmp->owner, AST_CONTROL_ANSWER);
+ } else
+ ast_log(LOG_NOTICE, "Whoa, didn't find call!\n");
+ gtalk_response(client, from, pak, NULL, NULL);
+ return 1;
+}
+
+static int gtalk_is_accepted(struct gtalk *client, ikspak *pak)
+{
+ struct gtalk_pvt *tmp;
+ char *from;
+
+ ast_log(LOG_DEBUG, "The client is %s\n", client->name);
+ /* find corresponding call */
+ for (tmp = client->p; tmp; tmp = tmp->next) {
+ if (iks_find_with_attrib(pak->x, "session", "id", tmp->sid))
+ break;
+ }
+
+ from = iks_find_attrib(pak->x, "to");
+ if(!from)
+ from = client->connection->jid->full;
+
+ if (!tmp)
+ ast_log(LOG_NOTICE, "Whoa, didn't find call!\n");
+
+ /* answer 'iq' packet to let the remote peer know that we're alive */
+ gtalk_response(client, from, pak, NULL, NULL);
+ return 1;
+}
+
+static int gtalk_handle_dtmf(struct gtalk *client, ikspak *pak)
+{
+ struct gtalk_pvt *tmp;
+ iks *dtmfnode = NULL, *dtmfchild = NULL;
+ char *dtmf;
+ char *from;
+ /* Make sure our new call doesn't exist yet */
+ for (tmp = client->p; tmp; tmp = tmp->next) {
+ if (iks_find_with_attrib(pak->x, "session", "id", tmp->sid) || iks_find_with_attrib(pak->x, "gtalk", "sid", tmp->sid))
+ break;
+ }
+ from = iks_find_attrib(pak->x, "to");
+ if(!from)
+ from = client->connection->jid->full;
+
+
+ if (tmp) {
+ if(iks_find_with_attrib(pak->x, "dtmf-method", "method", "rtp")) {
+ gtalk_response(client, from, pak,
+ "feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'",
+ "unsupported-dtmf-method xmlns='http://jabber.org/protocol/gtalk/info/dtmf#errors'");
+ return -1;
+ }
+ if ((dtmfnode = iks_find(pak->x, "dtmf"))) {
+ if((dtmf = iks_find_attrib(dtmfnode, "code"))) {
+ if(iks_find_with_attrib(pak->x, "dtmf", "action", "button-up")) {
+ struct ast_frame f = {AST_FRAME_DTMF_BEGIN, };
+ f.subclass = dtmf[0];
+ ast_queue_frame(tmp->owner, &f);
+ ast_verbose("GOOGLE! DTMF-relay event received: %c\n", f.subclass);
+ } else if(iks_find_with_attrib(pak->x, "dtmf", "action", "button-down")) {
+ struct ast_frame f = {AST_FRAME_DTMF_END, };
+ f.subclass = dtmf[0];
+ ast_queue_frame(tmp->owner, &f);
+ ast_verbose("GOOGLE! DTMF-relay event received: %c\n", f.subclass);
+ } else if(iks_find_attrib(pak->x, "dtmf")) { /* 250 millasecond default */
+ struct ast_frame f = {AST_FRAME_DTMF, };
+ f.subclass = dtmf[0];
+ ast_queue_frame(tmp->owner, &f);
+ ast_verbose("GOOGLE! DTMF-relay event received: %c\n", f.subclass);
+ }
+ }
+ } else if ((dtmfnode = iks_find_with_attrib(pak->x, "gtalk", "action", "session-info"))) {
+ if((dtmfchild = iks_find(dtmfnode, "dtmf"))) {
+ if((dtmf = iks_find_attrib(dtmfchild, "code"))) {
+ if(iks_find_with_attrib(dtmfnode, "dtmf", "action", "button-up")) {
+ struct ast_frame f = {AST_FRAME_DTMF_END, };
+ f.subclass = dtmf[0];
+ ast_queue_frame(tmp->owner, &f);
+ ast_verbose("GOOGLE! DTMF-relay event received: %c\n", f.subclass);
+ } else if(iks_find_with_attrib(dtmfnode, "dtmf", "action", "button-down")) {
+ struct ast_frame f = {AST_FRAME_DTMF_BEGIN, };
+ f.subclass = dtmf[0];
+ ast_queue_frame(tmp->owner, &f);
+ ast_verbose("GOOGLE! DTMF-relay event received: %c\n", f.subclass);
+ }
+ }
+ }
+ }
+ gtalk_response(client, from, pak, NULL, NULL);
+ return 1;
+ } else
+ ast_log(LOG_NOTICE, "Whoa, didn't find call!\n");
+
+ gtalk_response(client, from, pak, NULL, NULL);
+ return 1;
+}
+
+static int gtalk_hangup_farend(struct gtalk *client, ikspak *pak)
+{
+ struct gtalk_pvt *tmp;
+ char *from;
+
+ ast_debug(1, "The client is %s\n", client->name);
+ /* Make sure our new call doesn't exist yet */
+ for (tmp = client->p; tmp; tmp = tmp->next) {
+ if (iks_find_with_attrib(pak->x, "session", "id", tmp->sid))
+ break;
+ }
+ from = iks_find_attrib(pak->x, "to");
+ if(!from)
+ from = client->connection->jid->full;
+
+ if (tmp) {
+ tmp->alreadygone = 1;
+ if (tmp->owner)
+ ast_queue_hangup(tmp->owner);
+ } else
+ ast_log(LOG_NOTICE, "Whoa, didn't find call!\n");
+ gtalk_response(client, from, pak, NULL, NULL);
+ return 1;
+}
+
+static int gtalk_create_candidates(struct gtalk *client, struct gtalk_pvt *p, char *sid, char *from, char *to)
+{
+ struct gtalk_candidate *tmp;
+ struct aji_client *c = client->connection;
+ struct gtalk_candidate *ours1 = NULL, *ours2 = NULL;
+ struct sockaddr_in sin;
+ struct sockaddr_in dest;
+ struct in_addr us;
+ iks *iq, *gtalk, *candidate, *transport;
+ char user[17], pass[17], preference[5], port[7];
+
+
+ iq = iks_new("iq");
+ gtalk = iks_new("session");
+ candidate = iks_new("candidate");
+ transport = iks_new("transport");
+ if (!iq || !gtalk || !candidate || !transport) {
+ ast_log(LOG_ERROR, "Memory allocation error\n");
+ goto safeout;
+ }
+ ours1 = ast_calloc(1, sizeof(*ours1));
+ ours2 = ast_calloc(1, sizeof(*ours2));
+ if (!ours1 || !ours2)
+ goto safeout;
+
+ iks_insert_attrib(transport, "xmlns","http://www.google.com/transport/p2p");
+ iks_insert_node(iq, gtalk);
+ iks_insert_node(gtalk,transport);
+ iks_insert_node(transport, candidate);
+
+ for (; p; p = p->next) {
+ if (!strcasecmp(p->sid, sid))
+ break;
+ }
+
+ if (!p) {
+ ast_log(LOG_NOTICE, "No matching gtalk session - SID %s!\n", sid);
+ goto safeout;
+ }
+
+ ast_rtp_get_us(p->rtp, &sin);
+ ast_find_ourip(&us, bindaddr);
+
+ /* Setup our gtalk candidates */
+ ast_copy_string(ours1->name, "rtp", sizeof(ours1->name));
+ ours1->port = ntohs(sin.sin_port);
+ ours1->preference = 1;
+ snprintf(user, sizeof(user), "%08lx%08lx", ast_random(), ast_random());
+ snprintf(pass, sizeof(pass), "%08lx%08lx", ast_random(), ast_random());
+ ast_copy_string(ours1->username, user, sizeof(ours1->username));
+ ast_copy_string(ours1->password, pass, sizeof(ours1->password));
+ ast_copy_string(ours1->ip, ast_inet_ntoa(us), sizeof(ours1->ip));
+ ours1->protocol = AJI_PROTOCOL_UDP;
+ ours1->type = AJI_CONNECT_LOCAL;
+ ours1->generation = 0;
+ p->ourcandidates = ours1;
+
+ if (!ast_strlen_zero(externip)) {
+ /* XXX We should really stun for this one not just go with externip XXX */
+ snprintf(user, sizeof(user), "%08lx%08lx", ast_random(), ast_random());
+ snprintf(pass, sizeof(pass), "%08lx%08lx", ast_random(), ast_random());
+ ast_copy_string(ours2->username, user, sizeof(ours2->username));
+ ast_copy_string(ours2->password, pass, sizeof(ours2->password));
+ ast_copy_string(ours2->ip, externip, sizeof(ours2->ip));
+ ast_copy_string(ours2->name, "rtp", sizeof(ours1->name));
+ ours2->port = ntohs(sin.sin_port);
+ ours2->preference = 0.9;
+ ours2->protocol = AJI_PROTOCOL_UDP;
+ ours2->type = AJI_CONNECT_STUN;
+ ours2->generation = 0;
+ ours1->next = ours2;
+ ours2 = NULL;
+ }
+ ours1 = NULL;
+ dest.sin_addr = __ourip;
+ dest.sin_port = sin.sin_port;
+
+
+ for (tmp = p->ourcandidates; tmp; tmp = tmp->next) {
+ snprintf(port, sizeof(port), "%d", tmp->port);
+ snprintf(preference, sizeof(preference), "%.2f", tmp->preference);
+ iks_insert_attrib(iq, "from", to);
+ iks_insert_attrib(iq, "to", from);
+ iks_insert_attrib(iq, "type", "set");
+ iks_insert_attrib(iq, "id", c->mid);
+ ast_aji_increment_mid(c->mid);
+ iks_insert_attrib(gtalk, "type", "transport-info");
+ iks_insert_attrib(gtalk, "id", sid);
+ iks_insert_attrib(gtalk, "initiator", (p->initiator) ? to : from);
+ iks_insert_attrib(gtalk, "xmlns", GOOGLE_NS);
+ iks_insert_attrib(candidate, "name", tmp->name);
+ iks_insert_attrib(candidate, "address", tmp->ip);
+ iks_insert_attrib(candidate, "port", port);
+ iks_insert_attrib(candidate, "username", tmp->username);
+ iks_insert_attrib(candidate, "password", tmp->password);
+ iks_insert_attrib(candidate, "preference", preference);
+ if (tmp->protocol == AJI_PROTOCOL_UDP)
+ iks_insert_attrib(candidate, "protocol", "udp");
+ if (tmp->protocol == AJI_PROTOCOL_SSLTCP)
+ iks_insert_attrib(candidate, "protocol", "ssltcp");
+ if (tmp->type == AJI_CONNECT_STUN)
+ iks_insert_attrib(candidate, "type", "stun");
+ if (tmp->type == AJI_CONNECT_LOCAL)
+ iks_insert_attrib(candidate, "type", "local");
+ if (tmp->type == AJI_CONNECT_RELAY)
+ iks_insert_attrib(candidate, "type", "relay");
+ iks_insert_attrib(candidate, "network", "0");
+ iks_insert_attrib(candidate, "generation", "0");
+ ast_aji_send(c, iq);
+ }
+ p->laststun = 0;
+
+safeout:
+ if (ours1)
+ ast_free(ours1);
+ if (ours2)
+ ast_free(ours2);
+ if (iq)
+ iks_delete(iq);
+ if (gtalk)
+ iks_delete(gtalk);
+ if (candidate)
+ iks_delete(candidate);
+ if(transport)
+ iks_delete(transport);
+ return 1;
+}
+
+static struct gtalk_pvt *gtalk_alloc(struct gtalk *client, const char *us, const char *them, const char *sid)
+{
+ struct gtalk_pvt *tmp = NULL;
+ struct aji_resource *resources = NULL;
+ struct aji_buddy *buddy;
+ char idroster[200];
+ char *data, *exten = NULL;
+
+ ast_debug(1, "The client is %s for alloc\n", client->name);
+ if (!sid && !strchr(them, '/')) { /* I started call! */
+ if (!strcasecmp(client->name, "guest")) {
+ buddy = ASTOBJ_CONTAINER_FIND(&client->connection->buddies, them);
+ if (buddy)
+ resources = buddy->resources;
+ } else if (client->buddy)
+ resources = client->buddy->resources;
+ while (resources) {
+ if (resources->cap->jingle) {
+ break;
+ }
+ resources = resources->next;
+ }
+ if (resources)
+ snprintf(idroster, sizeof(idroster), "%s/%s", them, resources->resource);
+ else {
+ ast_log(LOG_ERROR, "no gtalk capable clients to talk to.\n");
+ return NULL;
+ }
+ }
+ if (!(tmp = ast_calloc(1, sizeof(*tmp)))) {
+ return NULL;
+ }
+
+ memcpy(&tmp->prefs, &client->prefs, sizeof(struct ast_codec_pref));
+
+ if (sid) {
+ ast_copy_string(tmp->sid, sid, sizeof(tmp->sid));
+ ast_copy_string(tmp->them, them, sizeof(tmp->them));
+ ast_copy_string(tmp->us, us, sizeof(tmp->us));
+ } else {
+ snprintf(tmp->sid, sizeof(tmp->sid), "%08lx%08lx", ast_random(), ast_random());
+ ast_copy_string(tmp->them, idroster, sizeof(tmp->them));
+ ast_copy_string(tmp->us, us, sizeof(tmp->us));
+ tmp->initiator = 1;
+ }
+ tmp->rtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
+ tmp->parent = client;
+ if (!tmp->rtp) {
+ ast_log(LOG_WARNING, "Out of RTP sessions?\n");
+ ast_free(tmp);
+ return NULL;
+ }
+
+ /* Set CALLERID(name) to the full JID of the remote peer */
+ ast_copy_string(tmp->cid_name, tmp->them, sizeof(tmp->cid_name));
+
+ if(strchr(tmp->us, '/')) {
+ data = ast_strdupa(tmp->us);
+ exten = strsep(&data, "/");
+ } else
+ exten = tmp->us;
+ ast_copy_string(tmp->exten, exten, sizeof(tmp->exten));
+ ast_mutex_init(&tmp->lock);
+ ast_mutex_lock(&gtalklock);
+ tmp->next = client->p;
+ client->p = tmp;
+ ast_mutex_unlock(&gtalklock);
+ return tmp;
+}
+
+/*! \brief Start new gtalk channel */
+static struct ast_channel *gtalk_new(struct gtalk *client, struct gtalk_pvt *i, int state, const char *title)
+{
+ struct ast_channel *tmp;
+ int fmt;
+ int what;
+ const char *n2;
+
+ if (title)
+ n2 = title;
+ else
+ n2 = i->us;
+ tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, client->accountcode, i->exten, client->context, client->amaflags, "Gtalk/%s-%04lx", n2, ast_random() & 0xffff);
+ if (!tmp) {
+ ast_log(LOG_WARNING, "Unable to allocate Gtalk channel structure!\n");
+ return NULL;
+ }
+ tmp->tech = &gtalk_tech;
+
+ /* Select our native format based on codec preference until we receive
+ something from another device to the contrary. */
+/* ast_verbose("XXXXXXXXXXXXX\nXXX i->jointcapability = %X\nXXX i->capability = %X\nXXX global_capability %X\n XXXXXXXXXXXX\n",i->jointcapability,i->capability,global_capability); */
+ if (i->jointcapability)
+ what = i->jointcapability;
+ else if (i->capability)
+ what = i->capability;
+ else
+ what = global_capability;
+
+ /* Set Frame packetization */
+ if (i->rtp)
+ ast_rtp_codec_setpref(i->rtp, &i->prefs);
+
+ tmp->nativeformats = ast_codec_choose(&i->prefs, what, 1) | (i->jointcapability & AST_FORMAT_VIDEO_MASK);
+ fmt = ast_best_codec(tmp->nativeformats);
+
+ if (i->rtp) {
+ ast_rtp_setstun(i->rtp, 1);
+ ast_channel_set_fd(tmp, 0, ast_rtp_fd(i->rtp));
+ ast_channel_set_fd(tmp, 1, ast_rtcp_fd(i->rtp));
+ }
+ if (i->vrtp) {
+ ast_rtp_setstun(i->rtp, 1);
+ ast_channel_set_fd(tmp, 2, ast_rtp_fd(i->vrtp));
+ ast_channel_set_fd(tmp, 3, ast_rtcp_fd(i->vrtp));
+ }
+ if (state == AST_STATE_RING)
+ tmp->rings = 1;
+ tmp->adsicpe = AST_ADSI_UNAVAILABLE;
+ tmp->writeformat = fmt;
+ tmp->rawwriteformat = fmt;
+ tmp->readformat = fmt;
+ tmp->rawreadformat = fmt;
+ tmp->tech_pvt = i;
+
+ tmp->callgroup = client->callgroup;
+ tmp->pickupgroup = client->pickupgroup;
+ tmp->cid.cid_pres = client->callingpres;
+ if (!ast_strlen_zero(client->accountcode))
+ ast_string_field_set(tmp, accountcode, client->accountcode);
+ if (client->amaflags)
+ tmp->amaflags = client->amaflags;
+ if (!ast_strlen_zero(client->language))
+ ast_string_field_set(tmp, language, client->language);
+ if (!ast_strlen_zero(client->musicclass))
+ ast_string_field_set(tmp, musicclass, client->musicclass);
+ i->owner = tmp;
+ ast_module_ref(ast_module_info->self);
+ ast_copy_string(tmp->context, client->context, sizeof(tmp->context));
+ ast_copy_string(tmp->exten, i->exten, sizeof(tmp->exten));
+
+ if (!ast_strlen_zero(i->exten) && strcmp(i->exten, "s"))
+ tmp->cid.cid_dnid = ast_strdup(i->exten);
+ tmp->priority = 1;
+ if (i->rtp)
+ ast_jb_configure(tmp, &global_jbconf);
+ if (state != AST_STATE_DOWN && ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ tmp->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ ast_hangup(tmp);
+ tmp = NULL;
+ } else {
+ manager_event(EVENT_FLAG_SYSTEM, "ChannelUpdate",
+ "Channel: %s\r\nChanneltype: %s\r\nGtalk-SID: %s\r\n",
+ i->owner ? i->owner->name : "", "Gtalk", i->sid);
+ }
+ return tmp;
+}
+
+static int gtalk_action(struct gtalk *client, struct gtalk_pvt *p, const char *action)
+{
+ iks *request, *session = NULL;
+ int res = -1;
+
+ request = iks_new("iq");
+ if (request) {
+ iks_insert_attrib(request, "type", "set");
+ iks_insert_attrib(request, "from", p->us);
+ iks_insert_attrib(request, "to", p->them);
+ iks_insert_attrib(request, "id", client->connection->mid);
+ ast_aji_increment_mid(client->connection->mid);
+ session = iks_new("session");
+ if (session) {
+ iks_insert_attrib(session, "type", action);
+ iks_insert_attrib(session, "id", p->sid);
+ iks_insert_attrib(session, "initiator", p->initiator ? p->us : p->them);
+ iks_insert_attrib(session, "xmlns", "http://www.google.com/session");
+ iks_insert_node(request, session);
+ ast_aji_send(client->connection, request);
+ iks_delete(session);
+ res = 0;
+ }
+ iks_delete(request);
+ }
+ return res;
+}
+
+static void gtalk_free_candidates(struct gtalk_candidate *candidate)
+{
+ struct gtalk_candidate *last;
+ while (candidate) {
+ last = candidate;
+ candidate = candidate->next;
+ ast_free(last);
+ }
+}
+
+static void gtalk_free_pvt(struct gtalk *client, struct gtalk_pvt *p)
+{
+ struct gtalk_pvt *cur, *prev = NULL;
+ cur = client->p;
+ while (cur) {
+ if (cur == p) {
+ if (prev)
+ prev->next = p->next;
+ else
+ client->p = p->next;
+ break;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+ if (p->ringrule)
+ iks_filter_remove_rule(p->parent->connection->f, p->ringrule);
+ if (p->owner)
+ ast_log(LOG_WARNING, "Uh oh, there's an owner, this is going to be messy.\n");
+ if (p->rtp)
+ ast_rtp_destroy(p->rtp);
+ if (p->vrtp)
+ ast_rtp_destroy(p->vrtp);
+ gtalk_free_candidates(p->theircandidates);
+ ast_free(p);
+}
+
+
+static int gtalk_newcall(struct gtalk *client, ikspak *pak)
+{
+ struct gtalk_pvt *p, *tmp = client->p;
+ struct ast_channel *chan;
+ int res;
+ iks *codec;
+ char *from = NULL;
+ /* Make sure our new call doesn't exist yet */
+ from = iks_find_attrib(pak->x,"to");
+ if(!from)
+ from = client->connection->jid->full;
+
+ while (tmp) {
+ if (iks_find_with_attrib(pak->x, "session", "id", tmp->sid)) {
+ ast_log(LOG_NOTICE, "Ignoring duplicate call setup on SID %s\n", tmp->sid);
+ gtalk_response(client, from, pak, "out-of-order", NULL);
+ return -1;
+ }
+ tmp = tmp->next;
+ }
+
+ p = gtalk_alloc(client, from, pak->from->full, iks_find_attrib(pak->query, "id"));
+ if (!p) {
+ ast_log(LOG_WARNING, "Unable to allocate gtalk structure!\n");
+ return -1;
+ }
+ chan = gtalk_new(client, p, AST_STATE_DOWN, pak->from->user);
+ if (chan) {
+ ast_mutex_lock(&p->lock);
+ ast_copy_string(p->them, pak->from->full, sizeof(p->them));
+ if (iks_find_attrib(pak->query, "id")) {
+ ast_copy_string(p->sid, iks_find_attrib(pak->query, "id"),
+ sizeof(p->sid));
+ }
+
+ codec = iks_child(iks_child(iks_child(pak->x)));
+ while (codec) {
+ ast_rtp_set_m_type(p->rtp, atoi(iks_find_attrib(codec, "id")));
+ ast_rtp_set_rtpmap_type(p->rtp, atoi(iks_find_attrib(codec, "id")), "audio",
+ iks_find_attrib(codec, "name"), 0);
+ codec = iks_next(codec);
+ }
+
+ ast_mutex_unlock(&p->lock);
+ ast_setstate(chan, AST_STATE_RING);
+ res = ast_pbx_start(chan);
+
+ switch (res) {
+ case AST_PBX_FAILED:
+ ast_log(LOG_WARNING, "Failed to start PBX :(\n");
+ gtalk_response(client, from, pak, "service-unavailable", NULL);
+ break;
+ case AST_PBX_CALL_LIMIT:
+ ast_log(LOG_WARNING, "Failed to start PBX (call limit reached) \n");
+ gtalk_response(client, from, pak, "service-unavailable", NULL);
+ break;
+ case AST_PBX_SUCCESS:
+ gtalk_response(client, from, pak, NULL, NULL);
+ gtalk_invite_response(p, p->them, p->us,p->sid, 0);
+ gtalk_create_candidates(client, p, p->sid, p->them, p->us);
+ /* nothing to do */
+ break;
+ }
+ } else {
+ gtalk_free_pvt(client, p);
+ }
+ return 1;
+}
+
+static int gtalk_update_stun(struct gtalk *client, struct gtalk_pvt *p)
+{
+ struct gtalk_candidate *tmp;
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ struct sockaddr_in sin;
+ struct sockaddr_in aux;
+
+ if (time(NULL) == p->laststun)
+ return 0;
+
+ tmp = p->theircandidates;
+ p->laststun = time(NULL);
+ while (tmp) {
+ char username[256];
+
+ /* Find the IP address of the host */
+ hp = ast_gethostbyname(tmp->ip, &ahp);
+ sin.sin_family = AF_INET;
+ memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+ sin.sin_port = htons(tmp->port);
+ snprintf(username, sizeof(username), "%s%s", tmp->username,
+ p->ourcandidates->username);
+
+ /* Find out the result of the STUN */
+ ast_rtp_get_peer(p->rtp, &aux);
+
+ /* If the STUN result is different from the IP of the hostname,
+ lock on the stun IP of the hostname advertised by the
+ remote client */
+ if (aux.sin_addr.s_addr &&
+ aux.sin_addr.s_addr != sin.sin_addr.s_addr)
+ ast_rtp_stun_request(p->rtp, &aux, username);
+ else
+ ast_rtp_stun_request(p->rtp, &sin, username);
+
+ if (aux.sin_addr.s_addr) {
+ ast_debug(4, "Receiving RTP traffic from IP %s, matches with remote candidate's IP %s\n", ast_inet_ntoa(aux.sin_addr), tmp->ip);
+ ast_debug(4, "Sending STUN request to %s\n", tmp->ip);
+ }
+
+ tmp = tmp->next;
+ }
+ return 1;
+}
+
+static int gtalk_add_candidate(struct gtalk *client, ikspak *pak)
+{
+ struct gtalk_pvt *p = NULL, *tmp = NULL;
+ struct aji_client *c = client->connection;
+ struct gtalk_candidate *newcandidate = NULL;
+ iks *traversenodes = NULL, *receipt = NULL;
+ char *from;
+
+ from = iks_find_attrib(pak->x,"to");
+ if(!from)
+ from = c->jid->full;
+
+ for (tmp = client->p; tmp; tmp = tmp->next) {
+ if (iks_find_with_attrib(pak->x, "session", "id", tmp->sid)) {
+ p = tmp;
+ break;
+ }
+ }
+
+ if (!p)
+ return -1;
+
+ traversenodes = pak->query;
+ while(traversenodes) {
+ if(!strcasecmp(iks_name(traversenodes), "session")) {
+ traversenodes = iks_child(traversenodes);
+ continue;
+ }
+ if(!strcasecmp(iks_name(traversenodes), "transport")) {
+ traversenodes = iks_child(traversenodes);
+ continue;
+ }
+ if(!strcasecmp(iks_name(traversenodes), "candidate")) {
+ newcandidate = ast_calloc(1, sizeof(*newcandidate));
+ if (!newcandidate)
+ return 0;
+ ast_copy_string(newcandidate->name, iks_find_attrib(traversenodes, "name"),
+ sizeof(newcandidate->name));
+ ast_copy_string(newcandidate->ip, iks_find_attrib(traversenodes, "address"),
+ sizeof(newcandidate->ip));
+ newcandidate->port = atoi(iks_find_attrib(traversenodes, "port"));
+ ast_copy_string(newcandidate->username, iks_find_attrib(traversenodes, "username"),
+ sizeof(newcandidate->username));
+ ast_copy_string(newcandidate->password, iks_find_attrib(traversenodes, "password"),
+ sizeof(newcandidate->password));
+ newcandidate->preference = atof(iks_find_attrib(traversenodes, "preference"));
+ if (!strcasecmp(iks_find_attrib(traversenodes, "protocol"), "udp"))
+ newcandidate->protocol = AJI_PROTOCOL_UDP;
+ if (!strcasecmp(iks_find_attrib(traversenodes, "protocol"), "ssltcp"))
+ newcandidate->protocol = AJI_PROTOCOL_SSLTCP;
+
+ if (!strcasecmp(iks_find_attrib(traversenodes, "type"), "stun"))
+ newcandidate->type = AJI_CONNECT_STUN;
+ if (!strcasecmp(iks_find_attrib(traversenodes, "type"), "local"))
+ newcandidate->type = AJI_CONNECT_LOCAL;
+ if (!strcasecmp(iks_find_attrib(traversenodes, "type"), "relay"))
+ newcandidate->type = AJI_CONNECT_RELAY;
+ ast_copy_string(newcandidate->network, iks_find_attrib(traversenodes, "network"),
+ sizeof(newcandidate->network));
+ newcandidate->generation = atoi(iks_find_attrib(traversenodes, "generation"));
+ newcandidate->next = NULL;
+
+ newcandidate->next = p->theircandidates;
+ p->theircandidates = newcandidate;
+ p->laststun = 0;
+ gtalk_update_stun(p->parent, p);
+ newcandidate = NULL;
+ }
+ traversenodes = iks_next(traversenodes);
+ }
+
+ receipt = iks_new("iq");
+ iks_insert_attrib(receipt, "type", "result");
+ iks_insert_attrib(receipt, "from", from);
+ iks_insert_attrib(receipt, "to", iks_find_attrib(pak->x, "from"));
+ iks_insert_attrib(receipt, "id", iks_find_attrib(pak->x, "id"));
+ ast_aji_send(c, receipt);
+ iks_delete(receipt);
+
+ return 1;
+}
+
+static struct ast_frame *gtalk_rtp_read(struct ast_channel *ast, struct gtalk_pvt *p)
+{
+ struct ast_frame *f;
+
+ if (!p->rtp)
+ return &ast_null_frame;
+ f = ast_rtp_read(p->rtp);
+ gtalk_update_stun(p->parent, p);
+ if (p->owner) {
+ /* We already hold the channel lock */
+ if (f->frametype == AST_FRAME_VOICE) {
+ if (f->subclass != (p->owner->nativeformats & AST_FORMAT_AUDIO_MASK)) {
+ ast_debug(1, "Oooh, format changed to %d\n", f->subclass);
+ p->owner->nativeformats =
+ (p->owner->nativeformats & AST_FORMAT_VIDEO_MASK) | f->subclass;
+ ast_set_read_format(p->owner, p->owner->readformat);
+ ast_set_write_format(p->owner, p->owner->writeformat);
+ }
+/* if ((ast_test_flag(p, SIP_DTMF) == SIP_DTMF_INBAND) && p->vad) {
+ f = ast_dsp_process(p->owner, p->vad, f);
+ if (option_debug && f && (f->frametype == AST_FRAME_DTMF))
+ ast_debug(1, "* Detected inband DTMF '%c'\n", f->subclass);
+ } */
+ }
+ }
+ return f;
+}
+
+static struct ast_frame *gtalk_read(struct ast_channel *ast)
+{
+ struct ast_frame *fr;
+ struct gtalk_pvt *p = ast->tech_pvt;
+
+ ast_mutex_lock(&p->lock);
+ fr = gtalk_rtp_read(ast, p);
+ ast_mutex_unlock(&p->lock);
+ return fr;
+}
+
+/*! \brief Send frame to media channel (rtp) */
+static int gtalk_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct gtalk_pvt *p = ast->tech_pvt;
+ int res = 0;
+
+ switch (frame->frametype) {
+ case AST_FRAME_VOICE:
+ if (!(frame->subclass & ast->nativeformats)) {
+ ast_log(LOG_WARNING,
+ "Asked to transmit frame type %d, while native formats is %d (read/write = %d/%d)\n",
+ frame->subclass, ast->nativeformats, ast->readformat,
+ ast->writeformat);
+ return 0;
+ }
+ if (p) {
+ ast_mutex_lock(&p->lock);
+ if (p->rtp) {
+ res = ast_rtp_write(p->rtp, frame);
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ break;
+ case AST_FRAME_VIDEO:
+ if (p) {
+ ast_mutex_lock(&p->lock);
+ if (p->vrtp) {
+ res = ast_rtp_write(p->vrtp, frame);
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ break;
+ case AST_FRAME_IMAGE:
+ return 0;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Can't send %d type frames with Gtalk write\n",
+ frame->frametype);
+ return 0;
+ }
+
+ return res;
+}
+
+static int gtalk_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct gtalk_pvt *p = newchan->tech_pvt;
+ ast_mutex_lock(&p->lock);
+
+ if ((p->owner != oldchan)) {
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ if (p->owner == oldchan)
+ p->owner = newchan;
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int gtalk_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+ int res = 0;
+
+ switch (condition) {
+ case AST_CONTROL_HOLD:
+ ast_moh_start(ast, data, NULL);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_moh_stop(ast);
+ break;
+ default:
+ ast_log(LOG_NOTICE, "Don't know how to indicate condition '%d'\n", condition);
+ res = -1;
+ }
+
+ return res;
+}
+
+static int gtalk_digit_begin(struct ast_channel *chan, char digit)
+{
+ return gtalk_digit(chan, digit, 0);
+}
+
+static int gtalk_digit_end(struct ast_channel *chan, char digit, unsigned int duration)
+{
+ return gtalk_digit(chan, digit, duration);
+}
+
+static int gtalk_digit(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct gtalk_pvt *p = ast->tech_pvt;
+ struct gtalk *client = p->parent;
+ iks *iq, *gtalk, *dtmf;
+ char buffer[2] = {digit, '\0'};
+ iq = iks_new("iq");
+ gtalk = iks_new("gtalk");
+ dtmf = iks_new("dtmf");
+ if(!iq || !gtalk || !dtmf) {
+ if(iq)
+ iks_delete(iq);
+ if(gtalk)
+ iks_delete(gtalk);
+ if(dtmf)
+ iks_delete(dtmf);
+ ast_log(LOG_ERROR, "Did not send dtmf do to memory issue\n");
+ return -1;
+ }
+
+ iks_insert_attrib(iq, "type", "set");
+ iks_insert_attrib(iq, "to", p->them);
+ iks_insert_attrib(iq, "from", p->us);
+ iks_insert_attrib(iq, "id", client->connection->mid);
+ ast_aji_increment_mid(client->connection->mid);
+ iks_insert_attrib(gtalk, "xmlns", "http://jabber.org/protocol/gtalk");
+ iks_insert_attrib(gtalk, "action", "session-info");
+ iks_insert_attrib(gtalk, "initiator", p->initiator ? p->us: p->them);
+ iks_insert_attrib(gtalk, "sid", p->sid);
+ iks_insert_attrib(dtmf, "xmlns", "http://jabber.org/protocol/gtalk/info/dtmf");
+ iks_insert_attrib(dtmf, "code", buffer);
+ iks_insert_node(iq, gtalk);
+ iks_insert_node(gtalk, dtmf);
+
+ ast_mutex_lock(&p->lock);
+ if (ast->dtmff.frametype == AST_FRAME_DTMF_BEGIN || duration == 0) {
+ iks_insert_attrib(dtmf, "action", "button-down");
+ } else if (ast->dtmff.frametype == AST_FRAME_DTMF_END || duration != 0) {
+ iks_insert_attrib(dtmf, "action", "button-up");
+ }
+ ast_aji_send(client->connection, iq);
+ iks_delete(iq);
+ iks_delete(gtalk);
+ iks_delete(dtmf);
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int gtalk_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
+{
+ ast_log(LOG_NOTICE, "XXX Implement gtalk sendhtml XXX\n");
+
+ return -1;
+}
+
+/* Not in use right now.
+static int gtalk_auto_congest(void *nothing)
+{
+ struct gtalk_pvt *p = nothing;
+
+ ast_mutex_lock(&p->lock);
+ if (p->owner) {
+ if (!ast_channel_trylock(p->owner)) {
+ ast_log(LOG_NOTICE, "Auto-congesting %s\n", p->owner->name);
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ ast_channel_unlock(p->owner);
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+*/
+
+/*! \brief Initiate new call, part of PBX interface
+ * dest is the dial string */
+static int gtalk_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ struct gtalk_pvt *p = ast->tech_pvt;
+
+ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "gtalk_call called on %s, neither down nor reserved\n", ast->name);
+ return -1;
+ }
+
+ ast_setstate(ast, AST_STATE_RING);
+ p->jointcapability = p->capability;
+ if (!p->ringrule) {
+ ast_copy_string(p->ring, p->parent->connection->mid, sizeof(p->ring));
+ p->ringrule = iks_filter_add_rule(p->parent->connection->f, gtalk_ringing_ack, p,
+ IKS_RULE_ID, p->ring, IKS_RULE_DONE);
+ } else
+ ast_log(LOG_WARNING, "Whoa, already have a ring rule!\n");
+
+ gtalk_invite(p, p->them, p->us, p->sid, 1);
+ gtalk_create_candidates(p->parent, p, p->sid, p->them, p->us);
+
+ return 0;
+}
+
+/*! \brief Hangup a call through the gtalk proxy channel */
+static int gtalk_hangup(struct ast_channel *ast)
+{
+ struct gtalk_pvt *p = ast->tech_pvt;
+ struct gtalk *client;
+
+ ast_mutex_lock(&p->lock);
+ client = p->parent;
+ p->owner = NULL;
+ ast->tech_pvt = NULL;
+ if (!p->alreadygone)
+ gtalk_action(client, p, "terminate");
+ ast_mutex_unlock(&p->lock);
+
+ gtalk_free_pvt(client, p);
+ ast_module_unref(ast_module_info->self);
+
+ return 0;
+}
+
+/*! \brief Part of PBX interface */
+static struct ast_channel *gtalk_request(const char *type, int format, void *data, int *cause)
+{
+ struct gtalk_pvt *p = NULL;
+ struct gtalk *client = NULL;
+ char *sender = NULL, *to = NULL, *s = NULL;
+ struct ast_channel *chan = NULL;
+
+ if (data) {
+ s = ast_strdupa(data);
+ if (s) {
+ sender = strsep(&s, "/");
+ if (sender && (sender[0] != '\0'))
+ to = strsep(&s, "/");
+ if (!to) {
+ ast_log(LOG_ERROR, "Bad arguments in Gtalk Dialstring: %s\n", (char*) data);
+ return NULL;
+ }
+ }
+ }
+ client = find_gtalk(to, sender);
+ if (!client) {
+ ast_log(LOG_WARNING, "Could not find recipient.\n");
+ return NULL;
+ }
+ ASTOBJ_WRLOCK(client);
+ p = gtalk_alloc(client, strchr(sender, '@') ? sender : client->connection->jid->full, strchr(to, '@') ? to : client->user, NULL);
+ if (p)
+ chan = gtalk_new(client, p, AST_STATE_DOWN, to);
+
+ ASTOBJ_UNLOCK(client);
+ return chan;
+}
+
+/*! \brief CLI command "gtalk show channels" */
+static char *gtalk_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT "%-30.30s %-30.30s %-15.15s %-5.5s %-5.5s \n"
+ struct gtalk_pvt *p;
+ struct ast_channel *chan;
+ int numchans = 0;
+ char them[AJI_MAX_JIDLEN];
+ char *jid = NULL;
+ char *resource = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "gtalk show channels";
+ e->usage =
+ "Usage: gtalk show channels\n"
+ " Shows current state of the Gtalk channels.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&gtalklock);
+ ast_cli(a->fd, FORMAT, "Channel", "Jabber ID", "Resource", "Read", "Write");
+ ASTOBJ_CONTAINER_TRAVERSE(&gtalk_list, 1, {
+ ASTOBJ_WRLOCK(iterator);
+ p = iterator->p;
+ while(p) {
+ chan = p->owner;
+ ast_copy_string(them, p->them, sizeof(them));
+ jid = them;
+ resource = strchr(them, '/');
+ if (!resource)
+ resource = "None";
+ else {
+ *resource = '\0';
+ resource ++;
+ }
+ if (chan)
+ ast_cli(a->fd, FORMAT,
+ chan->name,
+ jid,
+ resource,
+ ast_getformatname(chan->readformat),
+ ast_getformatname(chan->writeformat)
+ );
+ else
+ ast_log(LOG_WARNING, "No available channel\n");
+ numchans ++;
+ p = p->next;
+ }
+ ASTOBJ_UNLOCK(iterator);
+ });
+
+ ast_mutex_unlock(&gtalklock);
+
+ ast_cli(a->fd, "%d active gtalk channel%s\n", numchans, (numchans != 1) ? "s" : "");
+ return CLI_SUCCESS;
+#undef FORMAT
+}
+
+/*! \brief CLI command "gtalk reload" */
+static char *gtalk_do_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "gtalk reload";
+ e->usage =
+ "Usage: gtalk reload\n"
+ " Reload gtalk channel driver.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ ast_verbose("IT DOES WORK!\n");
+ return CLI_SUCCESS;
+}
+
+static int gtalk_parser(void *data, ikspak *pak)
+{
+ struct gtalk *client = ASTOBJ_REF((struct gtalk *) data);
+
+ if (iks_find_with_attrib(pak->x, "session", "type", "initiate")) {
+ /* New call */
+ gtalk_newcall(client, pak);
+ } else if (iks_find_with_attrib(pak->x, "session", "type", "candidates") || iks_find_with_attrib(pak->x, "session", "type", "transport-info")) {
+ ast_debug(3, "About to add candidate!\n");
+ gtalk_add_candidate(client, pak);
+ ast_debug(3, "Candidate Added!\n");
+ } else if (iks_find_with_attrib(pak->x, "session", "type", "accept")) {
+ gtalk_is_answered(client, pak);
+ } else if (iks_find_with_attrib(pak->x, "session", "type", "transport-accept")) {
+ gtalk_is_accepted(client, pak);
+ } else if (iks_find_with_attrib(pak->x, "session", "type", "content-info") || iks_find_with_attrib(pak->x, "gtalk", "action", "session-info")) {
+ gtalk_handle_dtmf(client, pak);
+ } else if (iks_find_with_attrib(pak->x, "session", "type", "terminate")) {
+ gtalk_hangup_farend(client, pak);
+ } else if (iks_find_with_attrib(pak->x, "session", "type", "reject")) {
+ gtalk_hangup_farend(client, pak);
+ }
+ ASTOBJ_UNREF(client, gtalk_member_destroy);
+ return IKS_FILTER_EAT;
+}
+
+/* Not using this anymore probably take out soon
+static struct gtalk_candidate *gtalk_create_candidate(char *args)
+{
+ char *name, *type, *preference, *protocol;
+ struct gtalk_candidate *res;
+ res = ast_calloc(1, sizeof(*res));
+ if (args)
+ name = args;
+ if ((args = strchr(args, ','))) {
+ *args = '\0';
+ args++;
+ preference = args;
+ }
+ if ((args = strchr(args, ','))) {
+ *args = '\0';
+ args++;
+ protocol = args;
+ }
+ if ((args = strchr(args, ','))) {
+ *args = '\0';
+ args++;
+ type = args;
+ }
+ if (name)
+ ast_copy_string(res->name, name, sizeof(res->name));
+ if (preference) {
+ res->preference = atof(preference);
+ }
+ if (protocol) {
+ if (!strcasecmp("udp", protocol))
+ res->protocol = AJI_PROTOCOL_UDP;
+ if (!strcasecmp("ssltcp", protocol))
+ res->protocol = AJI_PROTOCOL_SSLTCP;
+ }
+ if (type) {
+ if (!strcasecmp("stun", type))
+ res->type = AJI_CONNECT_STUN;
+ if (!strcasecmp("local", type))
+ res->type = AJI_CONNECT_LOCAL;
+ if (!strcasecmp("relay", type))
+ res->type = AJI_CONNECT_RELAY;
+ }
+
+ return res;
+}
+*/
+
+static int gtalk_create_member(char *label, struct ast_variable *var, int allowguest,
+ struct ast_codec_pref prefs, char *context,
+ struct gtalk *member)
+{
+ struct aji_client *client;
+
+ if (!member)
+ ast_log(LOG_WARNING, "Out of memory.\n");
+
+ ast_copy_string(member->name, label, sizeof(member->name));
+ ast_copy_string(member->user, label, sizeof(member->user));
+ ast_copy_string(member->context, context, sizeof(member->context));
+ member->allowguest = allowguest;
+ member->prefs = prefs;
+ while (var) {
+#if 0
+ struct gtalk_candidate *candidate = NULL;
+#endif
+ if (!strcasecmp(var->name, "username"))
+ ast_copy_string(member->user, var->value, sizeof(member->user));
+ else if (!strcasecmp(var->name, "disallow"))
+ ast_parse_allow_disallow(&member->prefs, &member->capability, var->value, 0);
+ else if (!strcasecmp(var->name, "allow"))
+ ast_parse_allow_disallow(&member->prefs, &member->capability, var->value, 1);
+ else if (!strcasecmp(var->name, "context"))
+ ast_copy_string(member->context, var->value, sizeof(member->context));
+#if 0
+ else if (!strcasecmp(var->name, "candidate")) {
+ candidate = gtalk_create_candidate(var->value);
+ if (candidate) {
+ candidate->next = member->ourcandidates;
+ member->ourcandidates = candidate;
+ }
+ }
+#endif
+ else if (!strcasecmp(var->name, "connection")) {
+ if ((client = ast_aji_get_client(var->value))) {
+ member->connection = client;
+ iks_filter_add_rule(client->f, gtalk_parser, member,
+ IKS_RULE_TYPE, IKS_PAK_IQ,
+ IKS_RULE_FROM_PARTIAL, member->user,
+ IKS_RULE_NS, "http://www.google.com/session",
+ IKS_RULE_DONE);
+
+ } else {
+ ast_log(LOG_ERROR, "connection referenced not found!\n");
+ return 0;
+ }
+ }
+ var = var->next;
+ }
+ if (member->connection && member->user)
+ member->buddy = ASTOBJ_CONTAINER_FIND(&member->connection->buddies, member->user);
+ else {
+ ast_log(LOG_ERROR, "No Connection or Username!\n");
+ }
+ return 1;
+}
+
+static int gtalk_load_config(void)
+{
+ char *cat = NULL;
+ struct ast_config *cfg = NULL;
+ char context[AST_MAX_CONTEXT];
+ int allowguest = 1;
+ struct ast_variable *var;
+ struct gtalk *member;
+ struct ast_codec_pref prefs;
+ struct aji_client_container *clients;
+ struct gtalk_candidate *global_candidates = NULL;
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ struct ast_flags config_flags = { 0 };
+
+ cfg = ast_config_load(GOOGLE_CONFIG, config_flags);
+ if (!cfg)
+ return 0;
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ cat = ast_category_browse(cfg, NULL);
+ for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, var->name, var->value))
+ continue;
+
+ if (!strcasecmp(var->name, "allowguest"))
+ allowguest =
+ (ast_true(ast_variable_retrieve(cfg, "general", "allowguest"))) ? 1 : 0;
+ else if (!strcasecmp(var->name, "disallow"))
+ ast_parse_allow_disallow(&prefs, &global_capability, var->value, 0);
+ else if (!strcasecmp(var->name, "allow"))
+ ast_parse_allow_disallow(&prefs, &global_capability, var->value, 1);
+ else if (!strcasecmp(var->name, "context"))
+ ast_copy_string(context, var->value, sizeof(context));
+ else if (!strcasecmp(var->name, "bindaddr")) {
+ if (!(hp = ast_gethostbyname(var->value, &ahp))) {
+ ast_log(LOG_WARNING, "Invalid address: %s\n", var->value);
+ } else {
+ memcpy(&bindaddr.sin_addr, hp->h_addr, sizeof(bindaddr.sin_addr));
+ }
+ }
+/* Idea to allow for custom candidates */
+/*
+ else if (!strcasecmp(var->name, "candidate")) {
+ candidate = gtalk_create_candidate(var->value);
+ if (candidate) {
+ candidate->next = global_candidates;
+ global_candidates = candidate;
+ }
+ }
+*/
+ }
+ while (cat) {
+ if (strcasecmp(cat, "general")) {
+ var = ast_variable_browse(cfg, cat);
+ member = ast_calloc(1, sizeof(*member));
+ ASTOBJ_INIT(member);
+ ASTOBJ_WRLOCK(member);
+ if (!strcasecmp(cat, "guest")) {
+ ast_copy_string(member->name, "guest", sizeof(member->name));
+ ast_copy_string(member->user, "guest", sizeof(member->user));
+ ast_copy_string(member->context, context, sizeof(member->context));
+ member->allowguest = allowguest;
+ member->prefs = prefs;
+ while (var) {
+ if (!strcasecmp(var->name, "disallow"))
+ ast_parse_allow_disallow(&member->prefs, &member->capability,
+ var->value, 0);
+ else if (!strcasecmp(var->name, "allow"))
+ ast_parse_allow_disallow(&member->prefs, &member->capability,
+ var->value, 1);
+ else if (!strcasecmp(var->name, "context"))
+ ast_copy_string(member->context, var->value,
+ sizeof(member->context));
+/* Idea to allow for custom candidates */
+/*
+ else if (!strcasecmp(var->name, "candidate")) {
+ candidate = gtalk_create_candidate(var->value);
+ if (candidate) {
+ candidate->next = member->ourcandidates;
+ member->ourcandidates = candidate;
+ }
+ }
+*/
+ var = var->next;
+ }
+ ASTOBJ_UNLOCK(member);
+ clients = ast_aji_get_clients();
+ if (clients) {
+ ASTOBJ_CONTAINER_TRAVERSE(clients, 1, {
+ ASTOBJ_WRLOCK(iterator);
+ ASTOBJ_WRLOCK(member);
+ member->connection = iterator;
+ iks_filter_add_rule(iterator->f, gtalk_parser, member, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_NS, "http://www.google.com/session", IKS_RULE_DONE);
+ iks_filter_add_rule(iterator->f, gtalk_parser, member, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_NS, "http://jabber.org/protocol/gtalk", IKS_RULE_DONE);
+ ASTOBJ_UNLOCK(member);
+ ASTOBJ_CONTAINER_LINK(&gtalk_list, member);
+ ASTOBJ_UNLOCK(iterator);
+ });
+ } else {
+ ASTOBJ_UNLOCK(member);
+ ASTOBJ_UNREF(member, gtalk_member_destroy);
+ }
+ } else {
+ ASTOBJ_UNLOCK(member);
+ if (gtalk_create_member(cat, var, allowguest, prefs, context, member))
+ ASTOBJ_CONTAINER_LINK(&gtalk_list, member);
+ ASTOBJ_UNREF(member, gtalk_member_destroy);
+ }
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+ gtalk_free_candidates(global_candidates);
+ return 1;
+}
+
+/*! \brief Load module into PBX, register channel */
+static int load_module(void)
+{
+ ASTOBJ_CONTAINER_INIT(&gtalk_list);
+ if (!gtalk_load_config()) {
+ ast_log(LOG_ERROR, "Unable to read config file %s. Not loading module.\n", GOOGLE_CONFIG);
+ return 0;
+ }
+
+ sched = sched_context_create();
+ if (!sched)
+ ast_log(LOG_WARNING, "Unable to create schedule context\n");
+
+ io = io_context_create();
+ if (!io)
+ ast_log(LOG_WARNING, "Unable to create I/O context\n");
+
+ if (ast_find_ourip(&__ourip, bindaddr)) {
+ ast_log(LOG_WARNING, "Unable to get own IP address, Gtalk disabled\n");
+ return 0;
+ }
+
+ ast_rtp_proto_register(&gtalk_rtp);
+ ast_cli_register_multiple(gtalk_cli, sizeof(gtalk_cli) / sizeof(gtalk_cli[0]));
+
+ /* Make sure we can register our channel type */
+ if (ast_channel_register(&gtalk_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class %s\n", gtalk_tech.type);
+ return -1;
+ }
+ return 0;
+}
+
+/*! \brief Reload module */
+static int reload(void)
+{
+ return 0;
+}
+
+/*! \brief Unload the gtalk channel from Asterisk */
+static int unload_module(void)
+{
+ struct gtalk_pvt *privates = NULL;
+ ast_cli_unregister_multiple(gtalk_cli, sizeof(gtalk_cli) / sizeof(gtalk_cli[0]));
+ /* First, take us out of the channel loop */
+ ast_channel_unregister(&gtalk_tech);
+ ast_rtp_proto_unregister(&gtalk_rtp);
+
+ if (!ast_mutex_lock(&gtalklock)) {
+ /* Hangup all interfaces if they have an owner */
+ ASTOBJ_CONTAINER_TRAVERSE(&gtalk_list, 1, {
+ ASTOBJ_WRLOCK(iterator);
+ privates = iterator->p;
+ while(privates) {
+ if (privates->owner)
+ ast_softhangup(privates->owner, AST_SOFTHANGUP_APPUNLOAD);
+ privates = privates->next;
+ }
+ iterator->p = NULL;
+ ASTOBJ_UNLOCK(iterator);
+ });
+ ast_mutex_unlock(&gtalklock);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the monitor\n");
+ return -1;
+ }
+ ASTOBJ_CONTAINER_DESTROYALL(&gtalk_list, gtalk_member_destroy);
+ ASTOBJ_CONTAINER_DESTROY(&gtalk_list);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Gtalk Channel Driver",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/trunk/channels/chan_h323.c b/trunk/channels/chan_h323.c
new file mode 100644
index 000000000..8761026d0
--- /dev/null
+++ b/trunk/channels/chan_h323.c
@@ -0,0 +1,3353 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005
+ *
+ * OpenH323 Channel Driver for ASTERISK PBX.
+ * By Jeremy McNamara
+ * For The NuFone Network
+ *
+ * chan_h323 has been derived from code created by
+ * Michael Manousos and Mark Spencer
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief This file is part of the chan_h323 driver for Asterisk
+ *
+ * \author Jeremy McNamara
+ *
+ * \par See also
+ * \arg Config_h323
+ * \extref OpenH323 http://www.voxgratia.org/
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <depend>openh323</depend>
+ <defaultenabled>yes</defaultenabled>
+ ***/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#ifdef __cplusplus
+}
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/signal.h>
+#include <sys/param.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netdb.h>
+#include <fcntl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/rtp.h"
+#include "asterisk/acl.h"
+#include "asterisk/callerid.h"
+#include "asterisk/cli.h"
+#include "asterisk/dsp.h"
+#include "asterisk/causes.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/astobj.h"
+
+#ifdef __cplusplus
+}
+#endif
+
+#include "h323/chan_h323.h"
+
+receive_digit_cb on_receive_digit;
+on_rtp_cb on_external_rtp_create;
+start_rtp_cb on_start_rtp_channel;
+setup_incoming_cb on_incoming_call;
+setup_outbound_cb on_outgoing_call;
+chan_ringing_cb on_chan_ringing;
+con_established_cb on_connection_established;
+clear_con_cb on_connection_cleared;
+answer_call_cb on_answer_call;
+progress_cb on_progress;
+rfc2833_cb on_set_rfc2833_payload;
+hangup_cb on_hangup;
+setcapabilities_cb on_setcapabilities;
+setpeercapabilities_cb on_setpeercapabilities;
+onhold_cb on_hold;
+
+int h323debug; /*!< global debug flag */
+
+/*! \brief Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = ""
+};
+static struct ast_jb_conf global_jbconf;
+
+/** Variables required by Asterisk */
+static const char tdesc[] = "The NuFone Network's Open H.323 Channel Driver";
+static const char config[] = "h323.conf";
+static char default_context[AST_MAX_CONTEXT] = "default";
+static struct sockaddr_in bindaddr;
+
+#define GLOBAL_CAPABILITY (AST_FORMAT_G723_1 | AST_FORMAT_GSM | AST_FORMAT_ULAW | AST_FORMAT_ALAW | AST_FORMAT_G729A | AST_FORMAT_G726_AAL2 | AST_FORMAT_H261)
+
+/** H.323 configuration values */
+static int h323_signalling_port = 1720;
+static char gatekeeper[100];
+static int gatekeeper_disable = 1;
+static int gatekeeper_discover = 0;
+static int gkroute = 0;
+/* Find user by alias (h.323 id) is default, alternative is the incomming call's source IP address*/
+static int userbyalias = 1;
+static int acceptAnonymous = 1;
+static unsigned int tos = 0;
+static unsigned int cos = 0;
+static char secret[50];
+static unsigned int unique = 0;
+
+static call_options_t global_options;
+
+/*! \brief Private structure of a OpenH323 channel */
+struct oh323_pvt {
+ ast_mutex_t lock; /*!< Channel private lock */
+ call_options_t options; /*!<!< Options to be used during call setup */
+ int alreadygone; /*!< Whether or not we've already been destroyed by our peer */
+ int needdestroy; /*!< if we need to be destroyed */
+ call_details_t cd; /*!< Call details */
+ struct ast_channel *owner; /*!< Who owns us */
+ struct sockaddr_in sa; /*!< Our peer */
+ struct sockaddr_in redirip; /*!< Where our RTP should be going if not to us */
+ int nonCodecCapability; /*!< non-audio capability */
+ int outgoing; /*!< Outgoing or incoming call? */
+ char exten[AST_MAX_EXTENSION]; /*!< Requested extension */
+ char context[AST_MAX_CONTEXT]; /*!< Context where to start */
+ char accountcode[256]; /*!< Account code */
+ char rdnis[80]; /*!< Referring DNIS, if available */
+ int amaflags; /*!< AMA Flags */
+ struct ast_rtp *rtp; /*!< RTP Session */
+ struct ast_dsp *vad; /*!< Used for in-band DTMF detection */
+ int nativeformats; /*!< Codec formats supported by a channel */
+ int needhangup; /*!< Send hangup when Asterisk is ready */
+ int hangupcause; /*!< Hangup cause from OpenH323 layer */
+ int newstate; /*!< Pending state change */
+ int newcontrol; /*!< Pending control to send */
+ int newdigit; /*!< Pending DTMF digit to send */
+ int newduration; /*!< Pending DTMF digit duration to send */
+ int pref_codec; /*!< Preferred codec */
+ int peercapability; /*!< Capabilities learned from peer */
+ int jointcapability; /*!< Common capabilities for local and remote side */
+ struct ast_codec_pref peer_prefs; /*!< Preferenced list of codecs which remote side supports */
+ int dtmf_pt[2]; /*!< Payload code used for RFC2833/CISCO messages */
+ int curDTMF; /*!< DTMF tone being generated to Asterisk side */
+ int DTMFsched; /*!< Scheduler descriptor for DTMF */
+ int update_rtp_info; /*!< Configuration of fd's array is pending */
+ int recvonly; /*!< Peer isn't wish to receive our voice stream */
+ int txDtmfDigit; /*!< DTMF digit being to send to H.323 side */
+ int noInbandDtmf; /*!< Inband DTMF processing by DSP isn't available */
+ int connection_established; /*!< Call got CONNECT message */
+ int got_progress; /*!< Call got PROGRESS message, pass inband audio */
+ struct oh323_pvt *next; /*!< Next channel in list */
+} *iflist = NULL;
+
+/*! \brief H323 User list */
+static struct h323_user_list {
+ ASTOBJ_CONTAINER_COMPONENTS(struct oh323_user);
+} userl;
+
+/*! \brief H323 peer list */
+static struct h323_peer_list {
+ ASTOBJ_CONTAINER_COMPONENTS(struct oh323_peer);
+} peerl;
+
+/*! \brief H323 alias list */
+static struct h323_alias_list {
+ ASTOBJ_CONTAINER_COMPONENTS(struct oh323_alias);
+} aliasl;
+
+/* Asterisk RTP stuff */
+static struct sched_context *sched;
+static struct io_context *io;
+
+AST_MUTEX_DEFINE_STATIC(iflock); /*!< Protect the interface list (oh323_pvt) */
+
+/*! \brief Protect the H.323 monitoring thread, so only one process can kill or start it, and not
+ when it's doing something critical. */
+AST_MUTEX_DEFINE_STATIC(monlock);
+
+/*! \brief Protect the H.323 capabilities list, to avoid more than one channel to set the capabilities simultaneaously in the h323 stack. */
+AST_MUTEX_DEFINE_STATIC(caplock);
+
+/*! \brief Protect the reload process */
+AST_MUTEX_DEFINE_STATIC(h323_reload_lock);
+static int h323_reloading = 0;
+
+/*! \brief This is the thread for the monitor which checks for input on the channels
+ which are not currently in use. */
+static pthread_t monitor_thread = AST_PTHREADT_NULL;
+static int restart_monitor(void);
+static int h323_do_reload(void);
+
+static void delete_users(void);
+static void delete_aliases(void);
+static void prune_peers(void);
+
+static struct ast_channel *oh323_request(const char *type, int format, void *data, int *cause);
+static int oh323_digit_begin(struct ast_channel *c, char digit);
+static int oh323_digit_end(struct ast_channel *c, char digit, unsigned int duration);
+static int oh323_call(struct ast_channel *c, char *dest, int timeout);
+static int oh323_hangup(struct ast_channel *c);
+static int oh323_answer(struct ast_channel *c);
+static struct ast_frame *oh323_read(struct ast_channel *c);
+static int oh323_write(struct ast_channel *c, struct ast_frame *frame);
+static int oh323_indicate(struct ast_channel *c, int condition, const void *data, size_t datalen);
+static int oh323_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+
+static const struct ast_channel_tech oh323_tech = {
+ .type = "H323",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_AUDIO_MASK,
+ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
+ .requester = oh323_request,
+ .send_digit_begin = oh323_digit_begin,
+ .send_digit_end = oh323_digit_end,
+ .call = oh323_call,
+ .hangup = oh323_hangup,
+ .answer = oh323_answer,
+ .read = oh323_read,
+ .write = oh323_write,
+ .indicate = oh323_indicate,
+ .fixup = oh323_fixup,
+ /* disable, for now */
+#if 0
+ .bridge = ast_rtp_bridge,
+#endif
+};
+
+static const char* redirectingreason2str(int redirectingreason)
+{
+ switch (redirectingreason) {
+ case 0:
+ return "UNKNOWN";
+ case 1:
+ return "BUSY";
+ case 2:
+ return "NO_REPLY";
+ case 0xF:
+ return "UNCONDITIONAL";
+ default:
+ return "NOREDIRECT";
+ }
+}
+
+static void oh323_destroy_alias(struct oh323_alias *alias)
+{
+ if (h323debug)
+ ast_debug(1, "Destroying alias '%s'\n", alias->name);
+ ast_free(alias);
+}
+
+static void oh323_destroy_user(struct oh323_user *user)
+{
+ if (h323debug)
+ ast_debug(1, "Destroying user '%s'\n", user->name);
+ ast_free_ha(user->ha);
+ ast_free(user);
+}
+
+static void oh323_destroy_peer(struct oh323_peer *peer)
+{
+ if (h323debug)
+ ast_debug(1, "Destroying peer '%s'\n", peer->name);
+ ast_free_ha(peer->ha);
+ ast_free(peer);
+}
+
+static int oh323_simulate_dtmf_end(const void *data)
+{
+ struct oh323_pvt *pvt = (struct oh323_pvt *)data;
+
+ if (pvt) {
+ ast_mutex_lock(&pvt->lock);
+ /* Don't hold pvt lock while trying to lock the channel */
+ while(pvt->owner && ast_channel_trylock(pvt->owner)) {
+ ast_mutex_unlock(&pvt->lock);
+ usleep(1);
+ ast_mutex_lock(&pvt->lock);
+ }
+
+ if (pvt->owner) {
+ struct ast_frame f = {
+ .frametype = AST_FRAME_DTMF_END,
+ .subclass = pvt->curDTMF,
+ .samples = 0,
+ .src = "SIMULATE_DTMF_END",
+ };
+ ast_queue_frame(pvt->owner, &f);
+ ast_channel_unlock(pvt->owner);
+ }
+
+ pvt->DTMFsched = -1;
+ ast_mutex_unlock(&pvt->lock);
+ }
+
+ return 0;
+}
+
+/*! \brief Channel and private structures should be already locked */
+static void __oh323_update_info(struct ast_channel *c, struct oh323_pvt *pvt)
+{
+ if (c->nativeformats != pvt->nativeformats) {
+ if (h323debug)
+ ast_debug(1, "Preparing %s for new native format\n", c->name);
+ c->nativeformats = pvt->nativeformats;
+ ast_set_read_format(c, c->readformat);
+ ast_set_write_format(c, c->writeformat);
+ }
+ if (pvt->needhangup) {
+ if (h323debug)
+ ast_debug(1, "Process pending hangup for %s\n", c->name);
+ c->_softhangup |= AST_SOFTHANGUP_DEV;
+ c->hangupcause = pvt->hangupcause;
+ ast_queue_hangup(c);
+ pvt->needhangup = 0;
+ pvt->newstate = pvt->newcontrol = pvt->newdigit = pvt->DTMFsched = -1;
+ }
+ if (pvt->newstate >= 0) {
+ ast_setstate(c, pvt->newstate);
+ pvt->newstate = -1;
+ }
+ if (pvt->newcontrol >= 0) {
+ ast_queue_control(c, pvt->newcontrol);
+ pvt->newcontrol = -1;
+ }
+ if (pvt->newdigit >= 0) {
+ struct ast_frame f = {
+ .frametype = AST_FRAME_DTMF_END,
+ .subclass = pvt->newdigit,
+ .samples = pvt->newduration * 8,
+ .len = pvt->newduration,
+ .src = "UPDATE_INFO",
+ };
+ if (pvt->newdigit == ' ') { /* signalUpdate message */
+ f.subclass = pvt->curDTMF;
+ if (pvt->DTMFsched >= 0) {
+ ast_sched_del(sched, pvt->DTMFsched);
+ pvt->DTMFsched = -1;
+ }
+ } else { /* Regular input or signal message */
+ if (pvt->newduration) { /* This is a signal, signalUpdate follows */
+ f.frametype = AST_FRAME_DTMF_BEGIN;
+ if (pvt->DTMFsched >= 0)
+ ast_sched_del(sched, pvt->DTMFsched);
+ pvt->DTMFsched = ast_sched_add(sched, pvt->newduration, oh323_simulate_dtmf_end, pvt);
+ if (h323debug)
+ ast_log(LOG_DTMF, "Scheduled DTMF END simulation for %d ms, id=%d\n", pvt->newduration, pvt->DTMFsched);
+ }
+ pvt->curDTMF = pvt->newdigit;
+ }
+ ast_queue_frame(c, &f);
+ pvt->newdigit = -1;
+ }
+ if (pvt->update_rtp_info > 0) {
+ if (pvt->rtp) {
+ ast_jb_configure(c, &global_jbconf);
+ ast_channel_set_fd(c, 0, ast_rtp_fd(pvt->rtp));
+ ast_channel_set_fd(c, 1, ast_rtcp_fd(pvt->rtp));
+ ast_queue_frame(pvt->owner, &ast_null_frame); /* Tell Asterisk to apply changes */
+ }
+ pvt->update_rtp_info = -1;
+ }
+}
+
+/*! \brief Only channel structure should be locked */
+static void oh323_update_info(struct ast_channel *c)
+{
+ struct oh323_pvt *pvt = c->tech_pvt;
+
+ if (pvt) {
+ ast_mutex_lock(&pvt->lock);
+ __oh323_update_info(c, pvt);
+ ast_mutex_unlock(&pvt->lock);
+ }
+}
+
+static void cleanup_call_details(call_details_t *cd)
+{
+ if (cd->call_token) {
+ ast_free(cd->call_token);
+ cd->call_token = NULL;
+ }
+ if (cd->call_source_aliases) {
+ ast_free(cd->call_source_aliases);
+ cd->call_source_aliases = NULL;
+ }
+ if (cd->call_dest_alias) {
+ ast_free(cd->call_dest_alias);
+ cd->call_dest_alias = NULL;
+ }
+ if (cd->call_source_name) {
+ ast_free(cd->call_source_name);
+ cd->call_source_name = NULL;
+ }
+ if (cd->call_source_e164) {
+ ast_free(cd->call_source_e164);
+ cd->call_source_e164 = NULL;
+ }
+ if (cd->call_dest_e164) {
+ ast_free(cd->call_dest_e164);
+ cd->call_dest_e164 = NULL;
+ }
+ if (cd->sourceIp) {
+ ast_free(cd->sourceIp);
+ cd->sourceIp = NULL;
+ }
+ if (cd->redirect_number) {
+ ast_free(cd->redirect_number);
+ cd->redirect_number = NULL;
+ }
+}
+
+static void __oh323_destroy(struct oh323_pvt *pvt)
+{
+ struct oh323_pvt *cur, *prev = NULL;
+
+ if (pvt->DTMFsched >= 0) {
+ ast_sched_del(sched, pvt->DTMFsched);
+ pvt->DTMFsched = -1;
+ }
+
+ if (pvt->rtp) {
+ ast_rtp_destroy(pvt->rtp);
+ }
+
+ /* Free dsp used for in-band DTMF detection */
+ if (pvt->vad) {
+ ast_dsp_free(pvt->vad);
+ }
+ cleanup_call_details(&pvt->cd);
+
+ /* Unlink us from the owner if we have one */
+ if (pvt->owner) {
+ ast_channel_lock(pvt->owner);
+ if (h323debug)
+ ast_debug(1, "Detaching from %s\n", pvt->owner->name);
+ pvt->owner->tech_pvt = NULL;
+ ast_channel_unlock(pvt->owner);
+ }
+ cur = iflist;
+ while(cur) {
+ if (cur == pvt) {
+ if (prev)
+ prev->next = cur->next;
+ else
+ iflist = cur->next;
+ break;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+ if (!cur) {
+ ast_log(LOG_WARNING, "%p is not in list?!?! \n", cur);
+ } else {
+ ast_mutex_unlock(&pvt->lock);
+ ast_mutex_destroy(&pvt->lock);
+ ast_free(pvt);
+ }
+}
+
+static void oh323_destroy(struct oh323_pvt *pvt)
+{
+ if (h323debug) {
+ ast_debug(1, "Destroying channel %s\n", (pvt->owner ? pvt->owner->name : "<unknown>"));
+ }
+ ast_mutex_lock(&iflock);
+ ast_mutex_lock(&pvt->lock);
+ __oh323_destroy(pvt);
+ ast_mutex_unlock(&iflock);
+}
+
+static int oh323_digit_begin(struct ast_channel *c, char digit)
+{
+ struct oh323_pvt *pvt = (struct oh323_pvt *) c->tech_pvt;
+ char *token;
+
+ if (!pvt) {
+ ast_log(LOG_ERROR, "No private structure?! This is bad\n");
+ return -1;
+ }
+ ast_mutex_lock(&pvt->lock);
+ if (pvt->rtp &&
+ (((pvt->options.dtmfmode & H323_DTMF_RFC2833) && pvt->dtmf_pt[0])
+ /*|| ((pvt->options.dtmfmode & H323_DTMF_CISCO) && pvt->dtmf_pt[1]))*/)) {
+ /* out-of-band DTMF */
+ if (h323debug) {
+ ast_log(LOG_DTMF, "Begin sending out-of-band digit %c on %s\n", digit, c->name);
+ }
+ ast_rtp_senddigit_begin(pvt->rtp, digit);
+ ast_mutex_unlock(&pvt->lock);
+ } else if (pvt->txDtmfDigit != digit) {
+ /* in-band DTMF */
+ if (h323debug) {
+ ast_log(LOG_DTMF, "Begin sending inband digit %c on %s\n", digit, c->name);
+ }
+ pvt->txDtmfDigit = digit;
+ token = pvt->cd.call_token ? ast_strdup(pvt->cd.call_token) : NULL;
+ ast_mutex_unlock(&pvt->lock);
+ h323_send_tone(token, digit);
+ if (token) {
+ ast_free(token);
+ }
+ } else
+ ast_mutex_unlock(&pvt->lock);
+ oh323_update_info(c);
+ return 0;
+}
+
+/*! \brief
+ * Send (play) the specified digit to the channel.
+ *
+ */
+static int oh323_digit_end(struct ast_channel *c, char digit, unsigned int duration)
+{
+ struct oh323_pvt *pvt = (struct oh323_pvt *) c->tech_pvt;
+ char *token;
+
+ if (!pvt) {
+ ast_log(LOG_ERROR, "No private structure?! This is bad\n");
+ return -1;
+ }
+ ast_mutex_lock(&pvt->lock);
+ if (pvt->rtp && (pvt->options.dtmfmode & H323_DTMF_RFC2833) && ((pvt->dtmf_pt[0] > 0) || (pvt->dtmf_pt[0] > 0))) {
+ /* out-of-band DTMF */
+ if (h323debug) {
+ ast_log(LOG_DTMF, "End sending out-of-band digit %c on %s, duration %d\n", digit, c->name, duration);
+ }
+ ast_rtp_senddigit_end(pvt->rtp, digit);
+ ast_mutex_unlock(&pvt->lock);
+ } else {
+ /* in-band DTMF */
+ if (h323debug) {
+ ast_log(LOG_DTMF, "End sending inband digit %c on %s, duration %d\n", digit, c->name, duration);
+ }
+ pvt->txDtmfDigit = ' ';
+ token = pvt->cd.call_token ? ast_strdup(pvt->cd.call_token) : NULL;
+ ast_mutex_unlock(&pvt->lock);
+ h323_send_tone(token, ' ');
+ if (token) {
+ ast_free(token);
+ }
+ }
+ oh323_update_info(c);
+ return 0;
+}
+
+/*! \brief
+ * Make a call over the specified channel to the specified
+ * destination.
+ * Returns -1 on error, 0 on success.
+ */
+static int oh323_call(struct ast_channel *c, char *dest, int timeout)
+{
+ int res = 0;
+ struct oh323_pvt *pvt = (struct oh323_pvt *)c->tech_pvt;
+ const char *addr;
+ char called_addr[1024];
+
+ if (h323debug) {
+ ast_debug(1, "Calling to %s on %s\n", dest, c->name);
+ }
+ if ((c->_state != AST_STATE_DOWN) && (c->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "Line is already in use (%s)\n", c->name);
+ return -1;
+ }
+ ast_mutex_lock(&pvt->lock);
+ if (!gatekeeper_disable) {
+ if (ast_strlen_zero(pvt->exten)) {
+ ast_copy_string(called_addr, dest, sizeof(called_addr));
+ } else {
+ snprintf(called_addr, sizeof(called_addr), "%s@%s", pvt->exten, dest);
+ }
+ } else {
+ res = htons(pvt->sa.sin_port);
+ addr = ast_inet_ntoa(pvt->sa.sin_addr);
+ if (ast_strlen_zero(pvt->exten)) {
+ snprintf(called_addr, sizeof(called_addr), "%s:%d", addr, res);
+ } else {
+ snprintf(called_addr, sizeof(called_addr), "%s@%s:%d", pvt->exten, addr, res);
+ }
+ }
+ /* make sure null terminated */
+ called_addr[sizeof(called_addr) - 1] = '\0';
+
+ if (c->cid.cid_num)
+ ast_copy_string(pvt->options.cid_num, c->cid.cid_num, sizeof(pvt->options.cid_num));
+
+ if (c->cid.cid_name)
+ ast_copy_string(pvt->options.cid_name, c->cid.cid_name, sizeof(pvt->options.cid_name));
+
+ if (c->cid.cid_rdnis) {
+ ast_copy_string(pvt->options.cid_rdnis, c->cid.cid_rdnis, sizeof(pvt->options.cid_rdnis));
+ }
+
+ pvt->options.presentation = c->cid.cid_pres;
+ pvt->options.type_of_number = c->cid.cid_ton;
+
+ if ((addr = pbx_builtin_getvar_helper(c, "PRIREDIRECTREASON"))) {
+ if (!strcasecmp(addr, "UNKNOWN"))
+ pvt->options.redirect_reason = 0;
+ else if (!strcasecmp(addr, "BUSY"))
+ pvt->options.redirect_reason = 1;
+ else if (!strcasecmp(addr, "NO_REPLY"))
+ pvt->options.redirect_reason = 2;
+ else if (!strcasecmp(addr, "UNCONDITIONAL"))
+ pvt->options.redirect_reason = 15;
+ else
+ pvt->options.redirect_reason = -1;
+ } else
+ pvt->options.redirect_reason = -1;
+
+ pvt->options.transfer_capability = c->transfercapability;
+
+ /* indicate that this is an outgoing call */
+ pvt->outgoing = 1;
+
+ ast_verb(3, "Requested transfer capability: 0x%.2x - %s\n", c->transfercapability, ast_transfercapability2str(c->transfercapability));
+ if (h323debug)
+ ast_debug(1, "Placing outgoing call to %s, %d/%d\n", called_addr, pvt->options.dtmfcodec[0], pvt->options.dtmfcodec[1]);
+ ast_mutex_unlock(&pvt->lock);
+ res = h323_make_call(called_addr, &(pvt->cd), &pvt->options);
+ if (res) {
+ ast_log(LOG_NOTICE, "h323_make_call failed(%s)\n", c->name);
+ return -1;
+ }
+ oh323_update_info(c);
+ return 0;
+}
+
+static int oh323_answer(struct ast_channel *c)
+{
+ int res;
+ struct oh323_pvt *pvt = (struct oh323_pvt *) c->tech_pvt;
+ char *token;
+
+ if (h323debug)
+ ast_debug(1, "Answering on %s\n", c->name);
+
+ ast_mutex_lock(&pvt->lock);
+ token = pvt->cd.call_token ? ast_strdup(pvt->cd.call_token) : NULL;
+ ast_mutex_unlock(&pvt->lock);
+ res = h323_answering_call(token, 0);
+ if (token)
+ ast_free(token);
+
+ oh323_update_info(c);
+ if (c->_state != AST_STATE_UP) {
+ ast_setstate(c, AST_STATE_UP);
+ }
+ return res;
+}
+
+static int oh323_hangup(struct ast_channel *c)
+{
+ struct oh323_pvt *pvt = (struct oh323_pvt *) c->tech_pvt;
+ int q931cause = AST_CAUSE_NORMAL_CLEARING;
+ char *call_token;
+
+
+ if (h323debug)
+ ast_debug(1, "Hanging up and scheduling destroy of call %s\n", c->name);
+
+ if (!c->tech_pvt) {
+ ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
+ return 0;
+ }
+ ast_mutex_lock(&pvt->lock);
+ /* Determine how to disconnect */
+ if (pvt->owner != c) {
+ ast_log(LOG_WARNING, "Huh? We aren't the owner?\n");
+ ast_mutex_unlock(&pvt->lock);
+ return 0;
+ }
+
+ pvt->owner = NULL;
+ c->tech_pvt = NULL;
+
+ if (c->hangupcause) {
+ q931cause = c->hangupcause;
+ } else {
+ const char *cause = pbx_builtin_getvar_helper(c, "DIALSTATUS");
+ if (cause) {
+ if (!strcmp(cause, "CONGESTION")) {
+ q931cause = AST_CAUSE_NORMAL_CIRCUIT_CONGESTION;
+ } else if (!strcmp(cause, "BUSY")) {
+ q931cause = AST_CAUSE_USER_BUSY;
+ } else if (!strcmp(cause, "CHANISUNVAIL")) {
+ q931cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
+ } else if (!strcmp(cause, "NOANSWER")) {
+ q931cause = AST_CAUSE_NO_ANSWER;
+ } else if (!strcmp(cause, "CANCEL")) {
+ q931cause = AST_CAUSE_CALL_REJECTED;
+ }
+ }
+ }
+
+ /* Start the process if it's not already started */
+ if (!pvt->alreadygone && !pvt->hangupcause) {
+ call_token = pvt->cd.call_token ? ast_strdup(pvt->cd.call_token) : NULL;
+ if (call_token) {
+ /* Release lock to eliminate deadlock */
+ ast_mutex_unlock(&pvt->lock);
+ if (h323_clear_call(call_token, q931cause)) {
+ ast_log(LOG_WARNING, "ClearCall failed.\n");
+ }
+ ast_free(call_token);
+ ast_mutex_lock(&pvt->lock);
+ }
+ }
+ pvt->needdestroy = 1;
+ ast_mutex_unlock(&pvt->lock);
+
+ /* Update usage counter */
+ ast_module_unref(ast_module_info->self);
+
+ return 0;
+}
+
+/*! \brief Retrieve audio/etc from channel. Assumes pvt->lock is already held. */
+static struct ast_frame *oh323_rtp_read(struct oh323_pvt *pvt)
+{
+ struct ast_frame *f;
+
+ /* Only apply it for the first packet, we just need the correct ip/port */
+ if (pvt->options.nat) {
+ ast_rtp_setnat(pvt->rtp, pvt->options.nat);
+ pvt->options.nat = 0;
+ }
+
+ f = ast_rtp_read(pvt->rtp);
+ /* Don't send RFC2833 if we're not supposed to */
+ if (f && (f->frametype == AST_FRAME_DTMF) && !(pvt->options.dtmfmode & (H323_DTMF_RFC2833 | H323_DTMF_CISCO))) {
+ return &ast_null_frame;
+ }
+ if (pvt->owner) {
+ /* We already hold the channel lock */
+ if (f->frametype == AST_FRAME_VOICE) {
+ if (f->subclass != pvt->owner->nativeformats) {
+ /* Try to avoid deadlock */
+ if (ast_channel_trylock(pvt->owner)) {
+ ast_log(LOG_NOTICE, "Format changed but channel is locked. Ignoring frame...\n");
+ return &ast_null_frame;
+ }
+ if (h323debug)
+ ast_debug(1, "Oooh, format changed to %d\n", f->subclass);
+ pvt->owner->nativeformats = f->subclass;
+ pvt->nativeformats = f->subclass;
+ ast_set_read_format(pvt->owner, pvt->owner->readformat);
+ ast_set_write_format(pvt->owner, pvt->owner->writeformat);
+ ast_channel_unlock(pvt->owner);
+ }
+ /* Do in-band DTMF detection */
+ if ((pvt->options.dtmfmode & H323_DTMF_INBAND) && pvt->vad) {
+ if ((pvt->nativeformats & (AST_FORMAT_SLINEAR | AST_FORMAT_ALAW | AST_FORMAT_ULAW))) {
+ if (!ast_channel_trylock(pvt->owner)) {
+ f = ast_dsp_process(pvt->owner, pvt->vad, f);
+ ast_channel_unlock(pvt->owner);
+ }
+ else
+ ast_log(LOG_NOTICE, "Unable to process inband DTMF while channel is locked\n");
+ } else if (pvt->nativeformats && !pvt->noInbandDtmf) {
+ ast_log(LOG_NOTICE, "Inband DTMF is not supported on codec %s. Use RFC2833\n", ast_getformatname(f->subclass));
+ pvt->noInbandDtmf = 1;
+ }
+ if (f &&(f->frametype == AST_FRAME_DTMF)) {
+ if (h323debug)
+ ast_log(LOG_DTMF, "Received in-band digit %c.\n", f->subclass);
+ }
+ }
+ }
+ }
+ return f;
+}
+
+static struct ast_frame *oh323_read(struct ast_channel *c)
+{
+ struct ast_frame *fr;
+ struct oh323_pvt *pvt = (struct oh323_pvt *)c->tech_pvt;
+ ast_mutex_lock(&pvt->lock);
+ __oh323_update_info(c, pvt);
+ switch(c->fdno) {
+ case 0:
+ fr = oh323_rtp_read(pvt);
+ break;
+ case 1:
+ if (pvt->rtp)
+ fr = ast_rtcp_read(pvt->rtp);
+ else
+ fr = &ast_null_frame;
+ break;
+ default:
+ ast_log(LOG_ERROR, "Unable to handle fd %d on channel %s\n", c->fdno, c->name);
+ fr = &ast_null_frame;
+ break;
+ }
+ ast_mutex_unlock(&pvt->lock);
+ return fr;
+}
+
+static int oh323_write(struct ast_channel *c, struct ast_frame *frame)
+{
+ struct oh323_pvt *pvt = (struct oh323_pvt *) c->tech_pvt;
+ int res = 0;
+ if (frame->frametype != AST_FRAME_VOICE) {
+ if (frame->frametype == AST_FRAME_IMAGE) {
+ return 0;
+ } else {
+ ast_log(LOG_WARNING, "Can't send %d type frames with H323 write\n", frame->frametype);
+ return 0;
+ }
+ } else {
+ if (!(frame->subclass & c->nativeformats)) {
+ ast_log(LOG_WARNING, "Asked to transmit frame type %d, while native formats is %d (read/write = %d/%d)\n",
+ frame->subclass, c->nativeformats, c->readformat, c->writeformat);
+ return 0;
+ }
+ }
+ if (pvt) {
+ ast_mutex_lock(&pvt->lock);
+ if (pvt->rtp && !pvt->recvonly)
+ res = ast_rtp_write(pvt->rtp, frame);
+ __oh323_update_info(c, pvt);
+ ast_mutex_unlock(&pvt->lock);
+ }
+ return res;
+}
+
+static int oh323_indicate(struct ast_channel *c, int condition, const void *data, size_t datalen)
+{
+
+ struct oh323_pvt *pvt = (struct oh323_pvt *) c->tech_pvt;
+ char *token = (char *)NULL;
+ int res = -1;
+ int got_progress;
+
+ ast_mutex_lock(&pvt->lock);
+ token = (pvt->cd.call_token ? ast_strdup(pvt->cd.call_token) : NULL);
+ got_progress = pvt->got_progress;
+ if (condition == AST_CONTROL_PROGRESS)
+ pvt->got_progress = 1;
+ else if ((condition == AST_CONTROL_BUSY) || (condition == AST_CONTROL_CONGESTION))
+ pvt->alreadygone = 1;
+ ast_mutex_unlock(&pvt->lock);
+
+ if (h323debug)
+ ast_debug(1, "OH323: Indicating %d on %s (%s)\n", condition, token, c->name);
+
+ switch(condition) {
+ case AST_CONTROL_RINGING:
+ if (c->_state == AST_STATE_RING || c->_state == AST_STATE_RINGING) {
+ h323_send_alerting(token);
+ res = (got_progress ? 0 : -1); /* Do not simulate any audio tones if we got PROGRESS message */
+ }
+ break;
+ case AST_CONTROL_PROGRESS:
+ if (c->_state != AST_STATE_UP) {
+ /* Do not send PROGRESS message more than once */
+ if (!got_progress)
+ h323_send_progress(token);
+ res = 0;
+ }
+ break;
+ case AST_CONTROL_BUSY:
+ if (c->_state != AST_STATE_UP) {
+ h323_answering_call(token, 1);
+ ast_softhangup_nolock(c, AST_SOFTHANGUP_DEV);
+ res = 0;
+ }
+ break;
+ case AST_CONTROL_CONGESTION:
+ if (c->_state != AST_STATE_UP) {
+ h323_answering_call(token, 1);
+ ast_softhangup_nolock(c, AST_SOFTHANGUP_DEV);
+ res = 0;
+ }
+ break;
+ case AST_CONTROL_HOLD:
+ h323_hold_call(token, 1);
+ /* We should start MOH only if remote party isn't provide audio for us */
+ ast_moh_start(c, data, NULL);
+ res = 0;
+ break;
+ case AST_CONTROL_UNHOLD:
+ h323_hold_call(token, 0);
+ ast_moh_stop(c);
+ res = 0;
+ break;
+ case AST_CONTROL_PROCEEDING:
+ case -1:
+ break;
+ default:
+ ast_log(LOG_WARNING, "OH323: Don't know how to indicate condition %d on %s\n", condition, token);
+ break;
+ }
+
+ if (h323debug)
+ ast_debug(1, "OH323: Indicated %d on %s, res=%d\n", condition, token, res);
+ if (token)
+ ast_free(token);
+ oh323_update_info(c);
+
+ return res;
+}
+
+static int oh323_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct oh323_pvt *pvt = (struct oh323_pvt *) newchan->tech_pvt;
+
+ ast_mutex_lock(&pvt->lock);
+ if (pvt->owner != oldchan) {
+ ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, pvt->owner);
+ return -1;
+ }
+ pvt->owner = newchan;
+ ast_mutex_unlock(&pvt->lock);
+ return 0;
+}
+
+static int __oh323_rtp_create(struct oh323_pvt *pvt)
+{
+ struct in_addr our_addr;
+
+ if (pvt->rtp)
+ return 0;
+
+ if (ast_find_ourip(&our_addr, bindaddr)) {
+ ast_mutex_unlock(&pvt->lock);
+ ast_log(LOG_ERROR, "Unable to locate local IP address for RTP stream\n");
+ return -1;
+ }
+ pvt->rtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, our_addr);
+ if (!pvt->rtp) {
+ ast_mutex_unlock(&pvt->lock);
+ ast_log(LOG_WARNING, "Unable to create RTP session: %s\n", strerror(errno));
+ return -1;
+ }
+ if (h323debug)
+ ast_debug(1, "Created RTP channel\n");
+
+ ast_rtp_setqos(pvt->rtp, tos, cos, "H323 RTP");
+
+ if (h323debug)
+ ast_debug(1, "Setting NAT on RTP to %d\n", pvt->options.nat);
+ ast_rtp_setnat(pvt->rtp, pvt->options.nat);
+
+ if (pvt->dtmf_pt[0] > 0)
+ ast_rtp_set_rtpmap_type(pvt->rtp, pvt->dtmf_pt[0], "audio", "telephone-event", 0);
+ if (pvt->dtmf_pt[1] > 0)
+ ast_rtp_set_rtpmap_type(pvt->rtp, pvt->dtmf_pt[1], "audio", "cisco-telephone-event", 0);
+
+ if (pvt->peercapability)
+ ast_rtp_codec_setpref(pvt->rtp, &pvt->peer_prefs);
+
+ if (pvt->owner && !ast_channel_trylock(pvt->owner)) {
+ ast_jb_configure(pvt->owner, &global_jbconf);
+ ast_channel_set_fd(pvt->owner, 0, ast_rtp_fd(pvt->rtp));
+ ast_channel_set_fd(pvt->owner, 1, ast_rtcp_fd(pvt->rtp));
+ ast_queue_frame(pvt->owner, &ast_null_frame); /* Tell Asterisk to apply changes */
+ ast_channel_unlock(pvt->owner);
+ } else
+ pvt->update_rtp_info = 1;
+
+ return 0;
+}
+
+/*! \brief Private structure should be locked on a call */
+static struct ast_channel *__oh323_new(struct oh323_pvt *pvt, int state, const char *host)
+{
+ struct ast_channel *ch;
+ char *cid_num, *cid_name;
+ int fmt;
+
+ if (!ast_strlen_zero(pvt->options.cid_num))
+ cid_num = pvt->options.cid_num;
+ else
+ cid_num = pvt->cd.call_source_e164;
+
+ if (!ast_strlen_zero(pvt->options.cid_name))
+ cid_name = pvt->options.cid_name;
+ else
+ cid_name = pvt->cd.call_source_name;
+
+ /* Don't hold a oh323_pvt lock while we allocate a chanel */
+ ast_mutex_unlock(&pvt->lock);
+ ch = ast_channel_alloc(1, state, cid_num, cid_name, pvt->accountcode, pvt->exten, pvt->context, pvt->amaflags, "H323/%s", host);
+ /* Update usage counter */
+ ast_module_ref(ast_module_info->self);
+ ast_mutex_lock(&pvt->lock);
+ if (ch) {
+ ch->tech = &oh323_tech;
+ if (!(fmt = pvt->jointcapability) && !(fmt = pvt->options.capability))
+ fmt = global_options.capability;
+ ch->nativeformats = ast_codec_choose(&pvt->options.prefs, fmt, 1)/* | (pvt->jointcapability & AST_FORMAT_VIDEO_MASK)*/;
+ pvt->nativeformats = ch->nativeformats;
+ fmt = ast_best_codec(ch->nativeformats);
+ ch->writeformat = fmt;
+ ch->rawwriteformat = fmt;
+ ch->readformat = fmt;
+ ch->rawreadformat = fmt;
+#if 0
+ ast_channel_set_fd(ch, 0, ast_rtp_fd(pvt->rtp));
+ ast_channel_set_fd(ch, 1, ast_rtcp_fd(pvt->rtp));
+#endif
+#ifdef VIDEO_SUPPORT
+ if (pvt->vrtp) {
+ ast_channel_set_fd(ch, 2, ast_rtp_fd(pvt->vrtp));
+ ast_channel_set_fd(ch, 3, ast_rtcp_fd(pvt->vrtp));
+ }
+#endif
+#ifdef T38_SUPPORT
+ if (pvt->udptl) {
+ ast_channel_set_fd(ch, 4, ast_udptl_fd(pvt->udptl));
+ }
+#endif
+ if (state == AST_STATE_RING) {
+ ch->rings = 1;
+ }
+ /* Allocate dsp for in-band DTMF support */
+ if (pvt->options.dtmfmode & H323_DTMF_INBAND) {
+ pvt->vad = ast_dsp_new();
+ ast_dsp_set_features(pvt->vad, DSP_FEATURE_DTMF_DETECT);
+ }
+ /* Register channel functions. */
+ ch->tech_pvt = pvt;
+ /* Set the owner of this channel */
+ pvt->owner = ch;
+
+ ast_copy_string(ch->context, pvt->context, sizeof(ch->context));
+ ast_copy_string(ch->exten, pvt->exten, sizeof(ch->exten));
+ ch->priority = 1;
+ if (!ast_strlen_zero(pvt->accountcode)) {
+ ast_string_field_set(ch, accountcode, pvt->accountcode);
+ }
+ if (pvt->amaflags) {
+ ch->amaflags = pvt->amaflags;
+ }
+
+ /* Don't use ast_set_callerid() here because it will
+ * generate a needless NewCallerID event */
+ ch->cid.cid_ani = ast_strdup(cid_num);
+
+ if (pvt->cd.redirect_reason >= 0) {
+ ch->cid.cid_rdnis = ast_strdup(pvt->cd.redirect_number);
+ pbx_builtin_setvar_helper(ch, "PRIREDIRECTREASON", redirectingreason2str(pvt->cd.redirect_reason));
+ }
+ ch->cid.cid_pres = pvt->cd.presentation;
+ ch->cid.cid_ton = pvt->cd.type_of_number;
+
+ if (!ast_strlen_zero(pvt->exten) && strcmp(pvt->exten, "s")) {
+ ch->cid.cid_dnid = ast_strdup(pvt->exten);
+ }
+ if (pvt->cd.transfer_capability >= 0)
+ ch->transfercapability = pvt->cd.transfer_capability;
+ if (state != AST_STATE_DOWN) {
+ if (ast_pbx_start(ch)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ch->name);
+ ast_hangup(ch);
+ ch = NULL;
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+ }
+ return ch;
+}
+
+static struct oh323_pvt *oh323_alloc(int callid)
+{
+ struct oh323_pvt *pvt;
+
+ pvt = ast_calloc(1, sizeof(*pvt));
+ if (!pvt) {
+ ast_log(LOG_ERROR, "Couldn't allocate private structure. This is bad\n");
+ return NULL;
+ }
+ pvt->cd.redirect_reason = -1;
+ pvt->cd.transfer_capability = -1;
+ /* Ensure the call token is allocated for outgoing call */
+ if (!callid) {
+ if ((pvt->cd).call_token == NULL) {
+ (pvt->cd).call_token = ast_calloc(1, 128);
+ }
+ if (!pvt->cd.call_token) {
+ ast_log(LOG_ERROR, "Not enough memory to alocate call token\n");
+ ast_rtp_destroy(pvt->rtp);
+ ast_free(pvt);
+ return NULL;
+ }
+ memset((char *)(pvt->cd).call_token, 0, 128);
+ pvt->cd.call_reference = callid;
+ }
+ memcpy(&pvt->options, &global_options, sizeof(pvt->options));
+ pvt->jointcapability = pvt->options.capability;
+ if (pvt->options.dtmfmode & (H323_DTMF_RFC2833 | H323_DTMF_CISCO)) {
+ pvt->nonCodecCapability |= AST_RTP_DTMF;
+ } else {
+ pvt->nonCodecCapability &= ~AST_RTP_DTMF;
+ }
+ ast_copy_string(pvt->context, default_context, sizeof(pvt->context));
+ pvt->newstate = pvt->newcontrol = pvt->newdigit = pvt->update_rtp_info = pvt->DTMFsched = -1;
+ ast_mutex_init(&pvt->lock);
+ /* Add to interface list */
+ ast_mutex_lock(&iflock);
+ pvt->next = iflist;
+ iflist = pvt;
+ ast_mutex_unlock(&iflock);
+ return pvt;
+}
+
+static struct oh323_pvt *find_call_locked(int call_reference, const char *token)
+{
+ struct oh323_pvt *pvt;
+
+ ast_mutex_lock(&iflock);
+ pvt = iflist;
+ while(pvt) {
+ if (!pvt->needdestroy && ((signed int)pvt->cd.call_reference == call_reference)) {
+ /* Found the call */
+ if ((token != NULL) && (!strcmp(pvt->cd.call_token, token))) {
+ ast_mutex_lock(&pvt->lock);
+ ast_mutex_unlock(&iflock);
+ return pvt;
+ } else if (token == NULL) {
+ ast_log(LOG_WARNING, "Call Token is NULL\n");
+ ast_mutex_lock(&pvt->lock);
+ ast_mutex_unlock(&iflock);
+ return pvt;
+ }
+ }
+ pvt = pvt->next;
+ }
+ ast_mutex_unlock(&iflock);
+ return NULL;
+}
+
+static int update_state(struct oh323_pvt *pvt, int state, int signal)
+{
+ if (!pvt)
+ return 0;
+ if (pvt->owner && !ast_channel_trylock(pvt->owner)) {
+ if (state >= 0)
+ ast_setstate(pvt->owner, state);
+ if (signal >= 0)
+ ast_queue_control(pvt->owner, signal);
+ ast_channel_unlock(pvt->owner);
+ return 1;
+ }
+ else {
+ if (state >= 0)
+ pvt->newstate = state;
+ if (signal >= 0)
+ pvt->newcontrol = signal;
+ return 0;
+ }
+}
+
+static struct oh323_alias *build_alias(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime)
+{
+ struct oh323_alias *alias;
+ int found = 0;
+
+ alias = ASTOBJ_CONTAINER_FIND_UNLINK_FULL(&aliasl, name, name, 0, 0, strcasecmp);
+
+ if (alias)
+ found++;
+ else {
+ if (!(alias = ast_calloc(1, sizeof(*alias))))
+ return NULL;
+ ASTOBJ_INIT(alias);
+ }
+ if (!found && name)
+ ast_copy_string(alias->name, name, sizeof(alias->name));
+ for (; v || ((v = alt) && !(alt = NULL)); v = v->next) {
+ if (!strcasecmp(v->name, "e164")) {
+ ast_copy_string(alias->e164, v->value, sizeof(alias->e164));
+ } else if (!strcasecmp(v->name, "prefix")) {
+ ast_copy_string(alias->prefix, v->value, sizeof(alias->prefix));
+ } else if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(alias->context, v->value, sizeof(alias->context));
+ } else if (!strcasecmp(v->name, "secret")) {
+ ast_copy_string(alias->secret, v->value, sizeof(alias->secret));
+ } else {
+ if (strcasecmp(v->value, "h323")) {
+ ast_log(LOG_WARNING, "Keyword %s does not make sense in type=h323\n", v->name);
+ }
+ }
+ }
+ ASTOBJ_UNMARK(alias);
+ return alias;
+}
+
+static struct oh323_alias *realtime_alias(const char *alias)
+{
+ struct ast_variable *var, *tmp;
+ struct oh323_alias *a;
+
+ var = ast_load_realtime("h323", "name", alias, NULL);
+
+ if (!var)
+ return NULL;
+
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "type") &&
+ !(!strcasecmp(tmp->value, "alias") || !strcasecmp(tmp->value, "h323"))) {
+ ast_variables_destroy(var);
+ return NULL;
+ }
+ }
+
+ a = build_alias(alias, var, NULL, 1);
+
+ ast_variables_destroy(var);
+
+ return a;
+}
+
+static int update_common_options(struct ast_variable *v, struct call_options *options)
+{
+ int tmp;
+ char *val, *opt;
+
+ if (!strcasecmp(v->name, "allow")) {
+ ast_parse_allow_disallow(&options->prefs, &options->capability, v->value, 1);
+ } else if (!strcasecmp(v->name, "disallow")) {
+ ast_parse_allow_disallow(&options->prefs, &options->capability, v->value, 0);
+ } else if (!strcasecmp(v->name, "dtmfmode")) {
+ val = ast_strdupa(v->value);
+ if ((opt = strchr(val, ':')) != (char *)NULL) {
+ *opt++ = '\0';
+ tmp = atoi(opt);
+ }
+ if (!strcasecmp(v->value, "inband")) {
+ options->dtmfmode |= H323_DTMF_INBAND;
+ } else if (!strcasecmp(val, "rfc2833")) {
+ options->dtmfmode |= H323_DTMF_RFC2833;
+ if (!opt) {
+ options->dtmfcodec[0] = H323_DTMF_RFC2833_PT;
+ } else if ((tmp >= 96) && (tmp < 128)) {
+ options->dtmfcodec[0] = tmp;
+ } else {
+ options->dtmfcodec[0] = H323_DTMF_RFC2833_PT;
+ ast_log(LOG_WARNING, "Unknown rfc2833 payload %s specified at line %d, using default %d\n", opt, v->lineno, options->dtmfcodec[0]);
+ }
+ } else if (!strcasecmp(val, "cisco")) {
+ options->dtmfmode |= H323_DTMF_CISCO;
+ if (!opt) {
+ options->dtmfcodec[1] = H323_DTMF_CISCO_PT;
+ } else if ((tmp >= 96) && (tmp < 128)) {
+ options->dtmfcodec[1] = tmp;
+ } else {
+ options->dtmfcodec[1] = H323_DTMF_CISCO_PT;
+ ast_log(LOG_WARNING, "Unknown Cisco DTMF payload %s specified at line %d, using default %d\n", opt, v->lineno, options->dtmfcodec[1]);
+ }
+ } else if (!strcasecmp(v->value, "h245-signal")) {
+ options->dtmfmode |= H323_DTMF_SIGNAL;
+ } else {
+ ast_log(LOG_WARNING, "Unknown dtmf mode '%s' at line %d\n", v->value, v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "dtmfcodec")) {
+ ast_log(LOG_NOTICE, "Option %s at line %d is deprecated. Use dtmfmode=rfc2833[:<payload>] instead.\n", v->name, v->lineno);
+ tmp = atoi(v->value);
+ if (tmp < 96)
+ ast_log(LOG_WARNING, "Invalid %s value %s at line %d\n", v->name, v->value, v->lineno);
+ else
+ options->dtmfcodec[0] = tmp;
+ } else if (!strcasecmp(v->name, "bridge")) {
+ options->bridge = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "nat")) {
+ options->nat = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "fastStart")) {
+ options->fastStart = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "h245Tunneling")) {
+ options->h245Tunneling = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "silenceSuppression")) {
+ options->silenceSuppression = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "progress_setup")) {
+ tmp = atoi(v->value);
+ if ((tmp != 0) && (tmp != 1) && (tmp != 3) && (tmp != 8)) {
+ ast_log(LOG_WARNING, "Invalid value %s for %s at line %d, assuming 0\n", v->value, v->name, v->lineno);
+ tmp = 0;
+ }
+ options->progress_setup = tmp;
+ } else if (!strcasecmp(v->name, "progress_alert")) {
+ tmp = atoi(v->value);
+ if ((tmp != 0) && (tmp != 1) && (tmp != 8)) {
+ ast_log(LOG_WARNING, "Invalid value %s for %s at line %d, assuming 0\n", v->value, v->name, v->lineno);
+ tmp = 0;
+ }
+ options->progress_alert = tmp;
+ } else if (!strcasecmp(v->name, "progress_audio")) {
+ options->progress_audio = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callerid")) {
+ ast_callerid_split(v->value, options->cid_name, sizeof(options->cid_name), options->cid_num, sizeof(options->cid_num));
+ } else if (!strcasecmp(v->name, "fullname")) {
+ ast_copy_string(options->cid_name, v->value, sizeof(options->cid_name));
+ } else if (!strcasecmp(v->name, "cid_number")) {
+ ast_copy_string(options->cid_num, v->value, sizeof(options->cid_num));
+ } else if (!strcasecmp(v->name, "tunneling")) {
+ if (!strcasecmp(v->value, "none"))
+ options->tunnelOptions = 0;
+ else if (!strcasecmp(v->value, "cisco"))
+ options->tunnelOptions |= H323_TUNNEL_CISCO;
+ else if (!strcasecmp(v->value, "qsig"))
+ options->tunnelOptions |= H323_TUNNEL_QSIG;
+ else
+ ast_log(LOG_WARNING, "Invalid value %s for %s at line %d\n", v->value, v->name, v->lineno);
+ } else if (!strcasecmp(v->name, "hold")) {
+ if (!strcasecmp(v->value, "none"))
+ options->holdHandling = ~0;
+ else if (!strcasecmp(v->value, "notify"))
+ options->holdHandling |= H323_HOLD_NOTIFY;
+ else if (!strcasecmp(v->value, "q931only"))
+ options->holdHandling |= H323_HOLD_NOTIFY | H323_HOLD_Q931ONLY;
+ else if (!strcasecmp(v->value, "h450"))
+ options->holdHandling |= H323_HOLD_H450;
+ else
+ ast_log(LOG_WARNING, "Invalid value %s for %s at line %d\n", v->value, v->name, v->lineno);
+ } else
+ return 1;
+
+ return 0;
+}
+
+static struct oh323_user *build_user(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime)
+{
+ struct oh323_user *user;
+ struct ast_ha *oldha;
+ int found = 0;
+ int format;
+
+ user = ASTOBJ_CONTAINER_FIND_UNLINK_FULL(&userl, name, name, 0, 0, strcmp);
+
+ if (user)
+ found++;
+ else {
+ if (!(user = ast_calloc(1, sizeof(*user))))
+ return NULL;
+ ASTOBJ_INIT(user);
+ }
+ oldha = user->ha;
+ user->ha = (struct ast_ha *)NULL;
+ memcpy(&user->options, &global_options, sizeof(user->options));
+ user->options.dtmfmode = 0;
+ user->options.holdHandling = 0;
+ /* Set default context */
+ ast_copy_string(user->context, default_context, sizeof(user->context));
+ if (user && !found)
+ ast_copy_string(user->name, name, sizeof(user->name));
+
+#if 0 /* XXX Port channel variables functionality from chan_sip XXX */
+ if (user->chanvars) {
+ ast_variables_destroy(user->chanvars);
+ user->chanvars = NULL;
+ }
+#endif
+
+ for (; v || ((v = alt) && !(alt = NULL)); v = v->next) {
+ if (!update_common_options(v, &user->options))
+ continue;
+ if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(user->context, v->value, sizeof(user->context));
+ } else if (!strcasecmp(v->name, "secret")) {
+ ast_copy_string(user->secret, v->value, sizeof(user->secret));
+ } else if (!strcasecmp(v->name, "accountcode")) {
+ ast_copy_string(user->accountcode, v->value, sizeof(user->accountcode));
+ } else if (!strcasecmp(v->name, "host")) {
+ if (!strcasecmp(v->value, "dynamic")) {
+ ast_log(LOG_ERROR, "A dynamic host on a type=user does not make any sense\n");
+ ASTOBJ_UNREF(user, oh323_destroy_user);
+ return NULL;
+ } else if (ast_get_ip(&user->addr, v->value)) {
+ ASTOBJ_UNREF(user, oh323_destroy_user);
+ return NULL;
+ }
+ /* Let us know we need to use ip authentication */
+ user->host = 1;
+ } else if (!strcasecmp(v->name, "amaflags")) {
+ format = ast_cdr_amaflags2int(v->value);
+ if (format < 0) {
+ ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
+ } else {
+ user->amaflags = format;
+ }
+ } else if (!strcasecmp(v->name, "permit") ||
+ !strcasecmp(v->name, "deny")) {
+ int ha_error = 0;
+
+ user->ha = ast_append_ha(v->name, v->value, user->ha, &ha_error);
+ if (ha_error)
+ ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value);
+ }
+ }
+ if (!user->options.dtmfmode)
+ user->options.dtmfmode = global_options.dtmfmode;
+ if (user->options.holdHandling == ~0)
+ user->options.holdHandling = 0;
+ else if (!user->options.holdHandling)
+ user->options.holdHandling = global_options.holdHandling;
+ ASTOBJ_UNMARK(user);
+ ast_free_ha(oldha);
+ return user;
+}
+
+static struct oh323_user *realtime_user(const call_details_t *cd)
+{
+ struct ast_variable *var, *tmp;
+ struct oh323_user *user;
+ const char *username;
+
+ if (userbyalias)
+ var = ast_load_realtime("h323", "name", username = cd->call_source_aliases, NULL);
+ else {
+ username = (char *)NULL;
+ var = ast_load_realtime("h323", "host", cd->sourceIp, NULL);
+ }
+
+ if (!var)
+ return NULL;
+
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "type") &&
+ !(!strcasecmp(tmp->value, "user") || !strcasecmp(tmp->value, "friend"))) {
+ ast_variables_destroy(var);
+ return NULL;
+ } else if (!username && !strcasecmp(tmp->name, "name"))
+ username = tmp->value;
+ }
+
+ if (!username) {
+ ast_log(LOG_WARNING, "Cannot determine user name for IP address %s\n", cd->sourceIp);
+ ast_variables_destroy(var);
+ return NULL;
+ }
+
+ user = build_user(username, var, NULL, 1);
+
+ ast_variables_destroy(var);
+
+ return user;
+}
+
+static struct oh323_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime)
+{
+ struct oh323_peer *peer;
+ struct ast_ha *oldha;
+ int found = 0;
+
+ peer = ASTOBJ_CONTAINER_FIND_UNLINK_FULL(&peerl, name, name, 0, 0, strcmp);
+
+ if (peer)
+ found++;
+ else {
+ if (!(peer = ast_calloc(1, sizeof(*peer))))
+ return NULL;
+ ASTOBJ_INIT(peer);
+ }
+ oldha = peer->ha;
+ peer->ha = NULL;
+ memcpy(&peer->options, &global_options, sizeof(peer->options));
+ peer->options.dtmfmode = 0;
+ peer->options.holdHandling = 0;
+ peer->addr.sin_port = htons(h323_signalling_port);
+ peer->addr.sin_family = AF_INET;
+ if (!found && name)
+ ast_copy_string(peer->name, name, sizeof(peer->name));
+
+#if 0 /* XXX Port channel variables functionality from chan_sip XXX */
+ if (peer->chanvars) {
+ ast_variables_destroy(peer->chanvars);
+ peer->chanvars = NULL;
+ }
+#endif
+ /* Default settings for mailbox */
+ peer->mailbox[0] = '\0';
+
+ for (; v || ((v = alt) && !(alt = NULL)); v = v->next) {
+ if (!update_common_options(v, &peer->options))
+ continue;
+ if (!strcasecmp(v->name, "host")) {
+ if (!strcasecmp(v->value, "dynamic")) {
+ ast_log(LOG_ERROR, "Dynamic host configuration not implemented.\n");
+ ASTOBJ_UNREF(peer, oh323_destroy_peer);
+ return NULL;
+ }
+ if (ast_get_ip(&peer->addr, v->value)) {
+ ast_log(LOG_ERROR, "Could not determine IP for %s\n", v->value);
+ ASTOBJ_UNREF(peer, oh323_destroy_peer);
+ return NULL;
+ }
+ } else if (!strcasecmp(v->name, "port")) {
+ peer->addr.sin_port = htons(atoi(v->value));
+ } else if (!strcasecmp(v->name, "permit") ||
+ !strcasecmp(v->name, "deny")) {
+ int ha_error = 0;
+
+ peer->ha = ast_append_ha(v->name, v->value, peer->ha, &ha_error);
+ if (ha_error)
+ ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value);
+ } else if (!strcasecmp(v->name, "mailbox")) {
+ ast_copy_string(peer->mailbox, v->value, sizeof(peer->mailbox));
+ }
+ }
+ if (!peer->options.dtmfmode)
+ peer->options.dtmfmode = global_options.dtmfmode;
+ if (peer->options.holdHandling == ~0)
+ peer->options.holdHandling = 0;
+ else if (!peer->options.holdHandling)
+ peer->options.holdHandling = global_options.holdHandling;
+ ASTOBJ_UNMARK(peer);
+ ast_free_ha(oldha);
+ return peer;
+}
+
+static struct oh323_peer *realtime_peer(const char *peername, struct sockaddr_in *sin)
+{
+ struct oh323_peer *peer;
+ struct ast_variable *var;
+ struct ast_variable *tmp;
+ const char *addr;
+
+ /* First check on peer name */
+ if (peername)
+ var = ast_load_realtime("h323", "name", peername, addr = NULL);
+ else if (sin) /* Then check on IP address for dynamic peers */
+ var = ast_load_realtime("h323", "host", addr = ast_inet_ntoa(sin->sin_addr), NULL);
+ else
+ return NULL;
+
+ if (!var)
+ return NULL;
+
+ for (tmp = var; tmp; tmp = tmp->next) {
+ /* If this is type=user, then skip this object. */
+ if (!strcasecmp(tmp->name, "type") &&
+ !(!strcasecmp(tmp->value, "peer") || !strcasecmp(tmp->value, "friend"))) {
+ ast_variables_destroy(var);
+ return NULL;
+ } else if (!peername && !strcasecmp(tmp->name, "name")) {
+ peername = tmp->value;
+ }
+ }
+
+ if (!peername) { /* Did not find peer in realtime */
+ ast_log(LOG_WARNING, "Cannot determine peer name for IP address %s\n", addr);
+ ast_variables_destroy(var);
+ return NULL;
+ }
+
+ /* Peer found in realtime, now build it in memory */
+ peer = build_peer(peername, var, NULL, 1);
+
+ ast_variables_destroy(var);
+
+ return peer;
+}
+
+static int oh323_addrcmp_str(struct in_addr inaddr, char *addr)
+{
+ return strcmp(ast_inet_ntoa(inaddr), addr);
+}
+
+static struct oh323_user *find_user(const call_details_t *cd, int realtime)
+{
+ struct oh323_user *u;
+
+ if (userbyalias)
+ u = ASTOBJ_CONTAINER_FIND(&userl, cd->call_source_aliases);
+ else
+ u = ASTOBJ_CONTAINER_FIND_FULL(&userl, cd->sourceIp, addr.sin_addr, 0, 0, oh323_addrcmp_str);
+
+ if (!u && realtime)
+ u = realtime_user(cd);
+
+ if (!u && h323debug)
+ ast_debug(1, "Could not find user by name %s or address %s\n", cd->call_source_aliases, cd->sourceIp);
+
+ return u;
+}
+
+static int oh323_addrcmp(struct sockaddr_in addr, struct sockaddr_in *sin)
+{
+ int res;
+
+ if (!sin)
+ res = -1;
+ else
+ res = inaddrcmp(&addr , sin);
+
+ return res;
+}
+
+static struct oh323_peer *find_peer(const char *peer, struct sockaddr_in *sin, int realtime)
+{
+ struct oh323_peer *p;
+
+ if (peer)
+ p = ASTOBJ_CONTAINER_FIND(&peerl, peer);
+ else
+ p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, addr, 0, 0, oh323_addrcmp);
+
+ if (!p && realtime)
+ p = realtime_peer(peer, sin);
+
+ if (!p && h323debug)
+ ast_debug(1, "Could not find peer by name %s or address %s\n", (peer ? peer : "<NONE>"), (sin ? ast_inet_ntoa(sin->sin_addr) : "<NONE>"));
+
+ return p;
+}
+
+static int create_addr(struct oh323_pvt *pvt, char *opeer)
+{
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ struct oh323_peer *p;
+ int portno;
+ int found = 0;
+ char *port;
+ char *hostn;
+ char peer[256] = "";
+
+ ast_copy_string(peer, opeer, sizeof(peer));
+ port = strchr(peer, ':');
+ if (port) {
+ *port = '\0';
+ port++;
+ }
+ pvt->sa.sin_family = AF_INET;
+ p = find_peer(peer, NULL, 1);
+ if (p) {
+ found++;
+ memcpy(&pvt->options, &p->options, sizeof(pvt->options));
+ pvt->jointcapability = pvt->options.capability;
+ if (pvt->options.dtmfmode) {
+ if (pvt->options.dtmfmode & H323_DTMF_RFC2833) {
+ pvt->nonCodecCapability |= AST_RTP_DTMF;
+ } else {
+ pvt->nonCodecCapability &= ~AST_RTP_DTMF;
+ }
+ }
+ if (p->addr.sin_addr.s_addr) {
+ pvt->sa.sin_addr = p->addr.sin_addr;
+ pvt->sa.sin_port = p->addr.sin_port;
+ }
+ ASTOBJ_UNREF(p, oh323_destroy_peer);
+ }
+ if (!p && !found) {
+ hostn = peer;
+ if (port) {
+ portno = atoi(port);
+ } else {
+ portno = h323_signalling_port;
+ }
+ hp = ast_gethostbyname(hostn, &ahp);
+ if (hp) {
+ memcpy(&pvt->sa.sin_addr, hp->h_addr, sizeof(pvt->sa.sin_addr));
+ pvt->sa.sin_port = htons(portno);
+ /* Look peer by address */
+ p = find_peer(NULL, &pvt->sa, 1);
+ memcpy(&pvt->options, (p ? &p->options : &global_options), sizeof(pvt->options));
+ pvt->jointcapability = pvt->options.capability;
+ if (p) {
+ ASTOBJ_UNREF(p, oh323_destroy_peer);
+ }
+ if (pvt->options.dtmfmode) {
+ if (pvt->options.dtmfmode & H323_DTMF_RFC2833) {
+ pvt->nonCodecCapability |= AST_RTP_DTMF;
+ } else {
+ pvt->nonCodecCapability &= ~AST_RTP_DTMF;
+ }
+ }
+ return 0;
+ } else {
+ ast_log(LOG_WARNING, "No such host: %s\n", peer);
+ return -1;
+ }
+ } else if (!found) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+static struct ast_channel *oh323_request(const char *type, int format, void *data, int *cause)
+{
+ int oldformat;
+ struct oh323_pvt *pvt;
+ struct ast_channel *tmpc = NULL;
+ char *dest = (char *)data;
+ char *ext, *host;
+ char *h323id = NULL;
+ char tmp[256], tmp1[256];
+
+ if (h323debug)
+ ast_debug(1, "type=%s, format=%d, data=%s.\n", type, format, (char *)data);
+
+ pvt = oh323_alloc(0);
+ if (!pvt) {
+ ast_log(LOG_WARNING, "Unable to build pvt data for '%s'\n", (char *)data);
+ return NULL;
+ }
+ oldformat = format;
+ format &= AST_FORMAT_AUDIO_MASK;
+ if (!format) {
+ ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", format);
+ oh323_destroy(pvt);
+ if (cause)
+ *cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
+ return NULL;
+ }
+ ast_copy_string(tmp, dest, sizeof(tmp));
+ host = strchr(tmp, '@');
+ if (host) {
+ *host = '\0';
+ host++;
+ ext = tmp;
+ } else {
+ ext = strrchr(tmp, '/');
+ if (ext)
+ *ext++ = '\0';
+ host = tmp;
+ }
+ strtok_r(host, "/", &(h323id));
+ if (!ast_strlen_zero(h323id)) {
+ h323_set_id(h323id);
+ }
+ if (ext) {
+ ast_copy_string(pvt->exten, ext, sizeof(pvt->exten));
+ }
+ if (h323debug)
+ ast_debug(1, "Extension: %s Host: %s\n", pvt->exten, host);
+
+ if (gatekeeper_disable) {
+ if (create_addr(pvt, host)) {
+ oh323_destroy(pvt);
+ if (cause)
+ *cause = AST_CAUSE_DESTINATION_OUT_OF_ORDER;
+ return NULL;
+ }
+ }
+ else {
+ memcpy(&pvt->options, &global_options, sizeof(pvt->options));
+ pvt->jointcapability = pvt->options.capability;
+ if (pvt->options.dtmfmode) {
+ if (pvt->options.dtmfmode & H323_DTMF_RFC2833) {
+ pvt->nonCodecCapability |= AST_RTP_DTMF;
+ } else {
+ pvt->nonCodecCapability &= ~AST_RTP_DTMF;
+ }
+ }
+ }
+
+ ast_mutex_lock(&caplock);
+ /* Generate unique channel identifier */
+ snprintf(tmp1, sizeof(tmp1)-1, "%s-%u", host, ++unique);
+ tmp1[sizeof(tmp1)-1] = '\0';
+ ast_mutex_unlock(&caplock);
+
+ ast_mutex_lock(&pvt->lock);
+ tmpc = __oh323_new(pvt, AST_STATE_DOWN, tmp1);
+ ast_mutex_unlock(&pvt->lock);
+ if (!tmpc) {
+ oh323_destroy(pvt);
+ if (cause)
+ *cause = AST_CAUSE_NORMAL_TEMPORARY_FAILURE;
+ }
+ ast_update_use_count();
+ restart_monitor();
+ return tmpc;
+}
+
+/*! \brief Find a call by alias */
+static struct oh323_alias *find_alias(const char *source_aliases, int realtime)
+{
+ struct oh323_alias *a;
+
+ a = ASTOBJ_CONTAINER_FIND(&aliasl, source_aliases);
+
+ if (!a && realtime)
+ a = realtime_alias(source_aliases);
+
+ return a;
+}
+
+/*! \brief
+ * Callback for sending digits from H.323 up to asterisk
+ *
+ */
+static int receive_digit(unsigned call_reference, char digit, const char *token, int duration)
+{
+ struct oh323_pvt *pvt;
+ int res;
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ ast_log(LOG_ERROR, "Received digit '%c' (%u ms) for call %s without private structure\n", digit, duration, token);
+ return -1;
+ }
+ if (h323debug)
+ ast_log(LOG_DTMF, "Received %s digit '%c' (%u ms) for call %s\n", (digit == ' ' ? "update for" : "new"), (digit == ' ' ? pvt->curDTMF : digit), duration, token);
+
+ if (pvt->owner && !ast_channel_trylock(pvt->owner)) {
+ if (digit == '!')
+ res = ast_queue_control(pvt->owner, AST_CONTROL_FLASH);
+ else {
+ struct ast_frame f = {
+ .frametype = AST_FRAME_DTMF_END,
+ .subclass = digit,
+ .samples = duration * 8,
+ .len = duration,
+ .src = "SEND_DIGIT",
+ };
+ if (digit == ' ') { /* signalUpdate message */
+ f.subclass = pvt->curDTMF;
+ if (pvt->DTMFsched >= 0) {
+ ast_sched_del(sched, pvt->DTMFsched);
+ pvt->DTMFsched = -1;
+ }
+ } else { /* Regular input or signal message */
+ if (pvt->DTMFsched >= 0) {
+ /* We still don't send DTMF END from previous event, send it now */
+ ast_sched_del(sched, pvt->DTMFsched);
+ pvt->DTMFsched = -1;
+ f.subclass = pvt->curDTMF;
+ f.samples = f.len = 0;
+ ast_queue_frame(pvt->owner, &f);
+ /* Restore values */
+ f.subclass = digit;
+ f.samples = duration * 8;
+ f.len = duration;
+ }
+ if (duration) { /* This is a signal, signalUpdate follows */
+ f.frametype = AST_FRAME_DTMF_BEGIN;
+ pvt->DTMFsched = ast_sched_add(sched, duration, oh323_simulate_dtmf_end, pvt);
+ if (h323debug)
+ ast_log(LOG_DTMF, "Scheduled DTMF END simulation for %d ms, id=%d\n", duration, pvt->DTMFsched);
+ }
+ pvt->curDTMF = digit;
+ }
+ res = ast_queue_frame(pvt->owner, &f);
+ }
+ ast_channel_unlock(pvt->owner);
+ } else {
+ if (digit == '!')
+ pvt->newcontrol = AST_CONTROL_FLASH;
+ else {
+ pvt->newduration = duration;
+ pvt->newdigit = digit;
+ }
+ res = 0;
+ }
+ ast_mutex_unlock(&pvt->lock);
+ return res;
+}
+
+/*! \brief
+ * Callback function used to inform the H.323 stack of the local rtp ip/port details
+ *
+ * \return Returns the local RTP information
+ */
+static struct rtp_info *external_rtp_create(unsigned call_reference, const char * token)
+{
+ struct oh323_pvt *pvt;
+ struct sockaddr_in us;
+ struct rtp_info *info;
+
+ info = ast_calloc(1, sizeof(*info));
+ if (!info) {
+ ast_log(LOG_ERROR, "Unable to allocated info structure, this is very bad\n");
+ return NULL;
+ }
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ ast_free(info);
+ ast_log(LOG_ERROR, "Unable to find call %s(%d)\n", token, call_reference);
+ return NULL;
+ }
+ if (!pvt->rtp)
+ __oh323_rtp_create(pvt);
+ if (!pvt->rtp) {
+ ast_mutex_unlock(&pvt->lock);
+ ast_free(info);
+ ast_log(LOG_ERROR, "No RTP stream is available for call %s (%d)", token, call_reference);
+ return NULL;
+ }
+ /* figure out our local RTP port and tell the H.323 stack about it */
+ ast_rtp_get_us(pvt->rtp, &us);
+ ast_mutex_unlock(&pvt->lock);
+
+ ast_copy_string(info->addr, ast_inet_ntoa(us.sin_addr), sizeof(info->addr));
+ info->port = ntohs(us.sin_port);
+ if (h323debug)
+ ast_debug(1, "Sending RTP 'US' %s:%d\n", info->addr, info->port);
+ return info;
+}
+
+/*
+ * Definition taken from rtp.c for rtpPayloadType because we need it here.
+ */
+
+struct rtpPayloadType {
+ int isAstFormat; /* whether the following code is an AST_FORMAT */
+ int code;
+};
+
+/*! \brief
+ * Call-back function passing remote ip/port information from H.323 to asterisk
+ *
+ * Returns nothing
+ */
+static void setup_rtp_connection(unsigned call_reference, const char *remoteIp, int remotePort, const char *token, int pt)
+{
+ struct oh323_pvt *pvt;
+ struct sockaddr_in them;
+ struct rtpPayloadType rtptype;
+ int nativeformats_changed;
+ enum { NEED_NONE, NEED_HOLD, NEED_UNHOLD } rtp_change = NEED_NONE;
+
+ if (h323debug)
+ ast_debug(1, "Setting up RTP connection for %s\n", token);
+
+ /* Find the call or allocate a private structure if call not found */
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ ast_log(LOG_ERROR, "Something is wrong: rtp\n");
+ return;
+ }
+ if (pvt->alreadygone) {
+ ast_mutex_unlock(&pvt->lock);
+ return;
+ }
+
+ if (!pvt->rtp)
+ __oh323_rtp_create(pvt);
+
+ if ((pt == 2) && (pvt->jointcapability & AST_FORMAT_G726_AAL2)) {
+ ast_rtp_set_rtpmap_type(pvt->rtp, pt, "audio", "G726-32", AST_RTP_OPT_G726_NONSTANDARD);
+ }
+
+ them.sin_family = AF_INET;
+ /* only works for IPv4 */
+ them.sin_addr.s_addr = inet_addr(remoteIp);
+ them.sin_port = htons(remotePort);
+
+ if (them.sin_addr.s_addr) {
+ ast_rtp_set_peer(pvt->rtp, &them);
+ if (pvt->recvonly) {
+ pvt->recvonly = 0;
+ rtp_change = NEED_UNHOLD;
+ }
+ } else {
+ ast_rtp_stop(pvt->rtp);
+ if (!pvt->recvonly) {
+ pvt->recvonly = 1;
+ rtp_change = NEED_HOLD;
+ }
+ }
+
+ /* Change native format to reflect information taken from OLC/OLCAck */
+ nativeformats_changed = 0;
+ if (pt != 128 && pvt->rtp) { /* Payload type is invalid, so try to use previously decided */
+ rtptype = ast_rtp_lookup_pt(pvt->rtp, pt);
+ if (h323debug)
+ ast_debug(1, "Native format is set to %d from %d by RTP payload type %d\n", rtptype.code, pvt->nativeformats, pt);
+ if (pvt->nativeformats != rtptype.code) {
+ pvt->nativeformats = rtptype.code;
+ nativeformats_changed = 1;
+ }
+ } else if (h323debug)
+ ast_log(LOG_NOTICE, "Payload type is unknown, formats isn't changed\n");
+
+ /* Don't try to lock the channel if nothing changed */
+ if (nativeformats_changed || pvt->options.progress_audio || (rtp_change != NEED_NONE)) {
+ if (pvt->owner && !ast_channel_trylock(pvt->owner)) {
+ /* Re-build translation path only if native format(s) has been changed */
+ if (pvt->owner->nativeformats != pvt->nativeformats) {
+ if (h323debug)
+ ast_debug(1, "Native format changed to %d from %d, read format is %d, write format is %d\n", pvt->nativeformats, pvt->owner->nativeformats, pvt->owner->readformat, pvt->owner->writeformat);
+ pvt->owner->nativeformats = pvt->nativeformats;
+ ast_set_read_format(pvt->owner, pvt->owner->readformat);
+ ast_set_write_format(pvt->owner, pvt->owner->writeformat);
+ }
+ if (pvt->options.progress_audio)
+ ast_queue_control(pvt->owner, AST_CONTROL_PROGRESS);
+ switch (rtp_change) {
+ case NEED_HOLD:
+ ast_queue_control(pvt->owner, AST_CONTROL_HOLD);
+ break;
+ case NEED_UNHOLD:
+ ast_queue_control(pvt->owner, AST_CONTROL_UNHOLD);
+ break;
+ default:
+ break;
+ }
+ ast_channel_unlock(pvt->owner);
+ }
+ else {
+ if (pvt->options.progress_audio)
+ pvt->newcontrol = AST_CONTROL_PROGRESS;
+ else if (rtp_change == NEED_HOLD)
+ pvt->newcontrol = AST_CONTROL_HOLD;
+ else if (rtp_change == NEED_UNHOLD)
+ pvt->newcontrol = AST_CONTROL_UNHOLD;
+ if (h323debug)
+ ast_debug(1, "RTP connection preparation for %s is pending...\n", token);
+ }
+ }
+ ast_mutex_unlock(&pvt->lock);
+
+ if (h323debug)
+ ast_debug(1, "RTP connection prepared for %s\n", token);
+
+ return;
+}
+
+/*! \brief
+ * Call-back function to signal asterisk that the channel has been answered
+ * Returns nothing
+ */
+static void connection_made(unsigned call_reference, const char *token)
+{
+ struct oh323_pvt *pvt;
+
+ if (h323debug)
+ ast_debug(1, "Call %s answered\n", token);
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ ast_log(LOG_ERROR, "Something is wrong: connection\n");
+ return;
+ }
+
+ /* Inform asterisk about remote party connected only on outgoing calls */
+ if (!pvt->outgoing) {
+ ast_mutex_unlock(&pvt->lock);
+ return;
+ }
+ /* Do not send ANSWER message more than once */
+ if (!pvt->connection_established) {
+ pvt->connection_established = 1;
+ update_state(pvt, -1, AST_CONTROL_ANSWER);
+ }
+ ast_mutex_unlock(&pvt->lock);
+ return;
+}
+
+static int progress(unsigned call_reference, const char *token, int inband)
+{
+ struct oh323_pvt *pvt;
+
+ if (h323debug)
+ ast_debug(1, "Received ALERT/PROGRESS message for %s tones\n", (inband ? "inband" : "self-generated"));
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ ast_log(LOG_ERROR, "Private structure not found in progress.\n");
+ return -1;
+ }
+ if (!pvt->owner) {
+ ast_mutex_unlock(&pvt->lock);
+ ast_log(LOG_ERROR, "No Asterisk channel associated with private structure.\n");
+ return -1;
+ }
+ update_state(pvt, -1, (inband ? AST_CONTROL_PROGRESS : AST_CONTROL_RINGING));
+ ast_mutex_unlock(&pvt->lock);
+
+ return 0;
+}
+
+/*! \brief
+ * Call-back function for incoming calls
+ *
+ * Returns 1 on success
+ */
+static call_options_t *setup_incoming_call(call_details_t *cd)
+{
+ struct oh323_pvt *pvt;
+ struct oh323_user *user = NULL;
+ struct oh323_alias *alias = NULL;
+
+ if (h323debug)
+ ast_debug(1, "Setting up incoming call for %s\n", cd->call_token);
+
+ /* allocate the call*/
+ pvt = oh323_alloc(cd->call_reference);
+
+ if (!pvt) {
+ ast_log(LOG_ERROR, "Unable to allocate private structure, this is bad.\n");
+ cleanup_call_details(cd);
+ return NULL;
+ }
+
+ /* Populate the call details in the private structure */
+ memcpy(&pvt->cd, cd, sizeof(pvt->cd));
+ memcpy(&pvt->options, &global_options, sizeof(pvt->options));
+ pvt->jointcapability = pvt->options.capability;
+
+ if (h323debug) {
+ ast_verb(3, "Setting up Call\n");
+ ast_verb(3, " \tCall token: [%s]\n", pvt->cd.call_token);
+ ast_verb(3, " \tCalling party name: [%s]\n", pvt->cd.call_source_name);
+ ast_verb(3, " \tCalling party number: [%s]\n", pvt->cd.call_source_e164);
+ ast_verb(3, " \tCalled party name: [%s]\n", pvt->cd.call_dest_alias);
+ ast_verb(3, " \tCalled party number: [%s]\n", pvt->cd.call_dest_e164);
+ if (pvt->cd.redirect_reason >= 0)
+ ast_verb(3, " \tRedirecting party number: [%s] (reason %d)\n", pvt->cd.redirect_number, pvt->cd.redirect_reason);
+ ast_verb(3, " \tCalling party IP: [%s]\n", pvt->cd.sourceIp);
+ }
+
+ /* Decide if we are allowing Gatekeeper routed calls*/
+ if ((!strcasecmp(cd->sourceIp, gatekeeper)) && (gkroute == -1) && !gatekeeper_disable) {
+ if (!ast_strlen_zero(cd->call_dest_e164)) {
+ ast_copy_string(pvt->exten, cd->call_dest_e164, sizeof(pvt->exten));
+ ast_copy_string(pvt->context, default_context, sizeof(pvt->context));
+ } else {
+ alias = find_alias(cd->call_dest_alias, 1);
+ if (!alias) {
+ ast_log(LOG_ERROR, "Call for %s rejected, alias not found\n", cd->call_dest_alias);
+ oh323_destroy(pvt);
+ return NULL;
+ }
+ ast_copy_string(pvt->exten, alias->name, sizeof(pvt->exten));
+ ast_copy_string(pvt->context, alias->context, sizeof(pvt->context));
+ }
+ } else {
+ /* Either this call is not from the Gatekeeper
+ or we are not allowing gk routed calls */
+ user = find_user(cd, 1);
+ if (!user) {
+ if (!acceptAnonymous) {
+ ast_log(LOG_NOTICE, "Anonymous call from '%s@%s' rejected\n", pvt->cd.call_source_aliases, pvt->cd.sourceIp);
+ oh323_destroy(pvt);
+ return NULL;
+ }
+ if (ast_strlen_zero(default_context)) {
+ ast_log(LOG_ERROR, "Call from '%s@%s' rejected due to no default context\n", pvt->cd.call_source_aliases, pvt->cd.sourceIp);
+ oh323_destroy(pvt);
+ return NULL;
+ }
+ ast_copy_string(pvt->context, default_context, sizeof(pvt->context));
+ if (!ast_strlen_zero(pvt->cd.call_dest_e164)) {
+ ast_copy_string(pvt->exten, cd->call_dest_e164, sizeof(pvt->exten));
+ } else {
+ ast_copy_string(pvt->exten, cd->call_dest_alias, sizeof(pvt->exten));
+ }
+ if (h323debug)
+ ast_debug(1, "Sending %s@%s to context [%s] extension %s\n", cd->call_source_aliases, cd->sourceIp, pvt->context, pvt->exten);
+ } else {
+ if (user->host) {
+ if (strcasecmp(cd->sourceIp, ast_inet_ntoa(user->addr.sin_addr))) {
+ if (ast_strlen_zero(user->context)) {
+ if (ast_strlen_zero(default_context)) {
+ ast_log(LOG_ERROR, "Call from '%s' rejected due to non-matching IP address (%s) and no default context\n", user->name, cd->sourceIp);
+ oh323_destroy(pvt);
+ ASTOBJ_UNREF(user, oh323_destroy_user);
+ return NULL;
+ }
+ ast_copy_string(pvt->context, default_context, sizeof(pvt->context));
+ } else {
+ ast_copy_string(pvt->context, user->context, sizeof(pvt->context));
+ }
+ pvt->exten[0] = 'i';
+ pvt->exten[1] = '\0';
+ ast_log(LOG_ERROR, "Call from '%s' rejected due to non-matching IP address (%s)s\n", user->name, cd->sourceIp);
+ oh323_destroy(pvt);
+ ASTOBJ_UNREF(user, oh323_destroy_user);
+ return NULL; /* XXX: Hmmm... Why to setup context if we drop connection immediately??? */
+ }
+ }
+ ast_copy_string(pvt->context, user->context, sizeof(pvt->context));
+ memcpy(&pvt->options, &user->options, sizeof(pvt->options));
+ pvt->jointcapability = pvt->options.capability;
+ if (!ast_strlen_zero(pvt->cd.call_dest_e164)) {
+ ast_copy_string(pvt->exten, cd->call_dest_e164, sizeof(pvt->exten));
+ } else {
+ ast_copy_string(pvt->exten, cd->call_dest_alias, sizeof(pvt->exten));
+ }
+ if (!ast_strlen_zero(user->accountcode)) {
+ ast_copy_string(pvt->accountcode, user->accountcode, sizeof(pvt->accountcode));
+ }
+ if (user->amaflags) {
+ pvt->amaflags = user->amaflags;
+ }
+ ASTOBJ_UNREF(user, oh323_destroy_user);
+ }
+ }
+ return &pvt->options;
+}
+
+/*! \brief
+ * Call-back function to start PBX when OpenH323 ready to serve incoming call
+ *
+ * Returns 1 on success
+ */
+static int answer_call(unsigned call_reference, const char *token)
+{
+ struct oh323_pvt *pvt;
+ struct ast_channel *c = NULL;
+ enum {ext_original, ext_s, ext_i, ext_notexists} try_exten;
+ char tmp_exten[sizeof(pvt->exten)];
+
+ if (h323debug)
+ ast_debug(1, "Preparing Asterisk to answer for %s\n", token);
+
+ /* Find the call or allocate a private structure if call not found */
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ ast_log(LOG_ERROR, "Something is wrong: answer_call\n");
+ return 0;
+ }
+ /* Check if requested extension@context pair exists in the dialplan */
+ ast_copy_string(tmp_exten, pvt->exten, sizeof(tmp_exten));
+
+ /* Try to find best extension in specified context */
+ if ((tmp_exten[0] != '\0') && (tmp_exten[1] == '\0')) {
+ if (tmp_exten[0] == 's')
+ try_exten = ext_s;
+ else if (tmp_exten[0] == 'i')
+ try_exten = ext_i;
+ else
+ try_exten = ext_original;
+ } else
+ try_exten = ext_original;
+ do {
+ if (ast_exists_extension(NULL, pvt->context, tmp_exten, 1, NULL))
+ break;
+ switch (try_exten) {
+ case ext_original:
+ tmp_exten[0] = 's';
+ tmp_exten[1] = '\0';
+ try_exten = ext_s;
+ break;
+ case ext_s:
+ tmp_exten[0] = 'i';
+ try_exten = ext_i;
+ break;
+ case ext_i:
+ try_exten = ext_notexists;
+ break;
+ default:
+ break;
+ }
+ } while (try_exten != ext_notexists);
+
+ /* Drop the call if we don't have <exten>, s and i extensions */
+ if (try_exten == ext_notexists) {
+ ast_log(LOG_NOTICE, "Dropping call because extensions '%s', 's' and 'i' doesn't exists in context [%s]\n", pvt->exten, pvt->context);
+ ast_mutex_unlock(&pvt->lock);
+ h323_clear_call(token, AST_CAUSE_UNALLOCATED);
+ return 0;
+ } else if ((try_exten != ext_original) && (strcmp(pvt->exten, tmp_exten) != 0)) {
+ if (h323debug)
+ ast_debug(1, "Going to extension %s@%s because %s@%s isn't exists\n", tmp_exten, pvt->context, pvt->exten, pvt->context);
+ ast_copy_string(pvt->exten, tmp_exten, sizeof(pvt->exten));
+ }
+
+ /* allocate a channel and tell asterisk about it */
+ c = __oh323_new(pvt, AST_STATE_RINGING, pvt->cd.call_token);
+
+ /* And release when done */
+ ast_mutex_unlock(&pvt->lock);
+ if (!c) {
+ ast_log(LOG_ERROR, "Couldn't create channel. This is bad\n");
+ return 0;
+ }
+ return 1;
+}
+
+/*! \brief
+ * Call-back function to establish an outgoing H.323 call
+ *
+ * Returns 1 on success
+ */
+static int setup_outgoing_call(call_details_t *cd)
+{
+ /* Use argument here or free it immediately */
+ cleanup_call_details(cd);
+
+ return 1;
+}
+
+/*! \brief
+ * Call-back function to signal asterisk that the channel is ringing
+ * Returns nothing
+ */
+static void chan_ringing(unsigned call_reference, const char *token)
+{
+ struct oh323_pvt *pvt;
+
+ if (h323debug)
+ ast_debug(1, "Ringing on %s\n", token);
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ ast_log(LOG_ERROR, "Something is wrong: ringing\n");
+ return;
+ }
+ if (!pvt->owner) {
+ ast_mutex_unlock(&pvt->lock);
+ ast_log(LOG_ERROR, "Channel has no owner\n");
+ return;
+ }
+ update_state(pvt, AST_STATE_RINGING, AST_CONTROL_RINGING);
+ ast_mutex_unlock(&pvt->lock);
+ return;
+}
+
+/*! \brief
+ * Call-back function to cleanup communication
+ * Returns nothing,
+ */
+static void cleanup_connection(unsigned call_reference, const char *call_token)
+{
+ struct oh323_pvt *pvt;
+
+ if (h323debug)
+ ast_debug(1, "Cleaning connection to %s\n", call_token);
+
+ while (1) {
+ pvt = find_call_locked(call_reference, call_token);
+ if (!pvt) {
+ if (h323debug)
+ ast_debug(1, "No connection for %s\n", call_token);
+ return;
+ }
+ if (!pvt->owner || !ast_channel_trylock(pvt->owner))
+ break;
+#if 1
+ ast_log(LOG_NOTICE, "Avoiding H.323 destory deadlock on %s\n", call_token);
+#ifdef DEBUG_THREADS
+ /* XXX to be completed
+ * If we want to print more info on who is holding the lock,
+ * implement the relevant code in lock.h and use the routines
+ * supplied there.
+ */
+#endif
+#endif
+ ast_mutex_unlock(&pvt->lock);
+ usleep(1);
+ }
+ if (pvt->rtp) {
+ /* Immediately stop RTP */
+ ast_rtp_destroy(pvt->rtp);
+ pvt->rtp = NULL;
+ }
+ /* Free dsp used for in-band DTMF detection */
+ if (pvt->vad) {
+ ast_dsp_free(pvt->vad);
+ pvt->vad = NULL;
+ }
+ cleanup_call_details(&pvt->cd);
+ pvt->alreadygone = 1;
+ /* Send hangup */
+ if (pvt->owner) {
+ pvt->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ ast_queue_hangup(pvt->owner);
+ ast_channel_unlock(pvt->owner);
+ }
+ ast_mutex_unlock(&pvt->lock);
+ if (h323debug)
+ ast_debug(1, "Connection to %s cleaned\n", call_token);
+ return;
+}
+
+static void hangup_connection(unsigned int call_reference, const char *token, int cause)
+{
+ struct oh323_pvt *pvt;
+
+ if (h323debug)
+ ast_debug(1, "Hanging up connection to %s with cause %d\n", token, cause);
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ if (h323debug)
+ ast_debug(1, "Connection to %s already cleared\n", token);
+ return;
+ }
+ if (pvt->owner && !ast_channel_trylock(pvt->owner)) {
+ pvt->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ pvt->owner->hangupcause = pvt->hangupcause = cause;
+ ast_queue_hangup(pvt->owner);
+ ast_channel_unlock(pvt->owner);
+ }
+ else {
+ pvt->needhangup = 1;
+ pvt->hangupcause = cause;
+ if (h323debug)
+ ast_debug(1, "Hangup for %s is pending\n", token);
+ }
+ ast_mutex_unlock(&pvt->lock);
+}
+
+static void set_dtmf_payload(unsigned call_reference, const char *token, int payload, int is_cisco)
+{
+ struct oh323_pvt *pvt;
+
+ if (h323debug)
+ ast_debug(1, "Setting %s DTMF payload to %d on %s\n", (is_cisco ? "Cisco" : "RFC2833"), payload, token);
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ return;
+ }
+ if (pvt->rtp) {
+ ast_rtp_set_rtpmap_type(pvt->rtp, payload, "audio", (is_cisco ? "cisco-telephone-event" : "telephone-event"), 0);
+ }
+ pvt->dtmf_pt[is_cisco ? 1 : 0] = payload;
+ ast_mutex_unlock(&pvt->lock);
+ if (h323debug)
+ ast_debug(1, "DTMF payload on %s set to %d\n", token, payload);
+}
+
+static void set_peer_capabilities(unsigned call_reference, const char *token, int capabilities, struct ast_codec_pref *prefs)
+{
+ struct oh323_pvt *pvt;
+
+ if (h323debug)
+ ast_debug(1, "Got remote capabilities from connection %s\n", token);
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt)
+ return;
+ pvt->peercapability = capabilities;
+ pvt->jointcapability = pvt->options.capability & capabilities;
+ if (prefs) {
+ memcpy(&pvt->peer_prefs, prefs, sizeof(pvt->peer_prefs));
+ if (h323debug) {
+ int i;
+ for (i = 0; i < 32; ++i) {
+ if (!prefs->order[i])
+ break;
+ ast_debug(1, "prefs[%d]=%s:%d\n", i, (prefs->order[i] ? ast_getformatname(1 << (prefs->order[i]-1)) : "<none>"), prefs->framing[i]);
+ }
+ }
+ if (pvt->rtp)
+ ast_rtp_codec_setpref(pvt->rtp, &pvt->peer_prefs);
+ }
+ ast_mutex_unlock(&pvt->lock);
+}
+
+static void set_local_capabilities(unsigned call_reference, const char *token)
+{
+ struct oh323_pvt *pvt;
+ int capability, dtmfmode, pref_codec;
+ struct ast_codec_pref prefs;
+
+ if (h323debug)
+ ast_debug(1, "Setting capabilities for connection %s\n", token);
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt)
+ return;
+ capability = (pvt->jointcapability) ? pvt->jointcapability : pvt->options.capability;
+ dtmfmode = pvt->options.dtmfmode;
+ prefs = pvt->options.prefs;
+ pref_codec = pvt->pref_codec;
+ ast_mutex_unlock(&pvt->lock);
+ h323_set_capabilities(token, capability, dtmfmode, &prefs, pref_codec);
+
+ if (h323debug)
+ ast_debug(1, "Capabilities for connection %s is set\n", token);
+}
+
+static void remote_hold(unsigned call_reference, const char *token, int is_hold)
+{
+ struct oh323_pvt *pvt;
+
+ if (h323debug)
+ ast_debug(1, "Setting %shold status for connection %s\n", (is_hold ? "" : "un"), token);
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt)
+ return;
+ if (pvt->owner && !ast_channel_trylock(pvt->owner)) {
+ if (is_hold)
+ ast_queue_control(pvt->owner, AST_CONTROL_HOLD);
+ else
+ ast_queue_control(pvt->owner, AST_CONTROL_UNHOLD);
+ ast_channel_unlock(pvt->owner);
+ }
+ else {
+ if (is_hold)
+ pvt->newcontrol = AST_CONTROL_HOLD;
+ else
+ pvt->newcontrol = AST_CONTROL_UNHOLD;
+ }
+ ast_mutex_unlock(&pvt->lock);
+}
+
+static void *do_monitor(void *data)
+{
+ int res;
+ int reloading;
+ struct oh323_pvt *oh323 = NULL;
+
+ for(;;) {
+ /* Check for a reload request */
+ ast_mutex_lock(&h323_reload_lock);
+ reloading = h323_reloading;
+ h323_reloading = 0;
+ ast_mutex_unlock(&h323_reload_lock);
+ if (reloading) {
+ ast_verb(1, "Reloading H.323\n");
+ h323_do_reload();
+ }
+ /* Check for interfaces needing to be killed */
+ if (!ast_mutex_trylock(&iflock)) {
+#if 1
+ do {
+ for (oh323 = iflist; oh323; oh323 = oh323->next) {
+ if (!ast_mutex_trylock(&oh323->lock)) {
+ if (oh323->needdestroy) {
+ __oh323_destroy(oh323);
+ break;
+ }
+ ast_mutex_unlock(&oh323->lock);
+ }
+ }
+ } while (/*oh323*/ 0);
+#else
+restartsearch:
+ oh323 = iflist;
+ while(oh323) {
+ if (!ast_mutex_trylock(&oh323->lock)) {
+ if (oh323->needdestroy) {
+ __oh323_destroy(oh323);
+ goto restartsearch;
+ }
+ ast_mutex_unlock(&oh323->lock);
+ oh323 = oh323->next;
+ }
+ }
+#endif
+ ast_mutex_unlock(&iflock);
+ } else
+ oh323 = (struct oh323_pvt *)1; /* Force fast loop */
+ pthread_testcancel();
+ /* Wait for sched or io */
+ res = ast_sched_wait(sched);
+ if ((res < 0) || (res > 1000)) {
+ res = 1000;
+ }
+ /* Do not wait if some channel(s) is destroyed, probably, more available too */
+ if (oh323)
+ res = 1;
+ res = ast_io_wait(io, res);
+ pthread_testcancel();
+ ast_mutex_lock(&monlock);
+ if (res >= 0) {
+ ast_sched_runq(sched);
+ }
+ ast_mutex_unlock(&monlock);
+ }
+ /* Never reached */
+ return NULL;
+}
+
+static int restart_monitor(void)
+{
+ /* If we're supposed to be stopped -- stay stopped */
+ if (ast_mutex_lock(&monlock)) {
+ ast_log(LOG_WARNING, "Unable to lock monitor\n");
+ return -1;
+ }
+ if (monitor_thread == AST_PTHREADT_STOP) {
+ ast_mutex_unlock(&monlock);
+ return 0;
+ }
+ if (monitor_thread == pthread_self()) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_WARNING, "Cannot kill myself\n");
+ return -1;
+ }
+ if (monitor_thread && (monitor_thread != AST_PTHREADT_NULL)) {
+ /* Wake up the thread */
+ pthread_kill(monitor_thread, SIGURG);
+ } else {
+ /* Start a new monitor */
+ if (ast_pthread_create_detached_background(&monitor_thread, NULL, do_monitor, NULL) < 0) {
+ monitor_thread = AST_PTHREADT_NULL;
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
+ return -1;
+ }
+ }
+ ast_mutex_unlock(&monlock);
+ return 0;
+}
+
+static char *handle_cli_h323_set_trace(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "h323 set trace [off]";
+ e->usage =
+ "Usage: h323 set trace (off|<trace level>)\n"
+ " Enable/Disable H.323 stack tracing for debugging purposes\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+ if (!strcasecmp(a->argv[3], "off")) {
+ h323_debug(0, 0);
+ ast_cli(a->fd, "H.323 Trace Disabled\n");
+ } else {
+ int tracelevel = atoi(a->argv[3]);
+ h323_debug(1, tracelevel);
+ ast_cli(a->fd, "H.323 Trace Enabled (Trace Level: %d)\n", tracelevel);
+ }
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_h323_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "h323 set debug [off]";
+ e->usage =
+ "Usage: h323 set debug [off]\n"
+ " Enable/Disable H.323 debugging output\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc < 3 || a->argc > 4)
+ return CLI_SHOWUSAGE;
+ if (a->argc == 4 && strcasecmp(a->argv[3], "off"))
+ return CLI_SHOWUSAGE;
+
+ h323debug = (a->argc == 3) ? 1 : 0;
+ ast_cli(a->fd, "H.323 Debugging %s\n", h323debug ? "Enabled" : "Disabled");
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_h323_cycle_gk(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "h323 cycle gk";
+ e->usage =
+ "Usage: h323 cycle gk\n"
+ " Manually re-register with the Gatekeper (Currently Disabled)\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ h323_gk_urq();
+
+ /* Possibly register with a GK */
+ if (!gatekeeper_disable) {
+ if (h323_set_gk(gatekeeper_discover, gatekeeper, secret)) {
+ ast_log(LOG_ERROR, "Gatekeeper registration failed.\n");
+ }
+ }
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_h323_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "h323 hangup";
+ e->usage =
+ "Usage: h323 hangup <token>\n"
+ " Manually try to hang up the call identified by <token>\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ if (h323_soft_hangup(a->argv[2])) {
+ ast_verb(3, "Hangup succeeded on %s\n", a->argv[2]);
+ } else {
+ ast_verb(3, "Hangup failed for %s\n", a->argv[2]);
+ }
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_h323_show_tokens(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "h323 show tokens";
+ e->usage =
+ "Usage: h323 show tokens\n"
+ " Print out all active call tokens\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ h323_show_tokens();
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_h323[] = {
+ AST_CLI_DEFINE(handle_cli_h323_set_trace, "Enable/Disable H.323 Stack Tracing"),
+ AST_CLI_DEFINE(handle_cli_h323_set_debug, "Enable/Disable H.323 Debugging"),
+ AST_CLI_DEFINE(handle_cli_h323_cycle_gk, "Manually re-register with the Gatekeper"),
+ AST_CLI_DEFINE(handle_cli_h323_hangup, "Manually try to hang up a call"),
+ AST_CLI_DEFINE(handle_cli_h323_show_tokens, "Show all active call tokens"),
+};
+
+static void delete_users(void)
+{
+ int pruned = 0;
+
+ /* Delete all users */
+ ASTOBJ_CONTAINER_WRLOCK(&userl);
+ ASTOBJ_CONTAINER_TRAVERSE(&userl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ ASTOBJ_MARK(iterator);
+ ++pruned;
+ ASTOBJ_UNLOCK(iterator);
+ } while (0) );
+ if (pruned) {
+ ASTOBJ_CONTAINER_PRUNE_MARKED(&userl, oh323_destroy_user);
+ }
+ ASTOBJ_CONTAINER_UNLOCK(&userl);
+
+ ASTOBJ_CONTAINER_WRLOCK(&peerl);
+ ASTOBJ_CONTAINER_TRAVERSE(&peerl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ ASTOBJ_MARK(iterator);
+ ASTOBJ_UNLOCK(iterator);
+ } while (0) );
+ ASTOBJ_CONTAINER_UNLOCK(&peerl);
+}
+
+static void delete_aliases(void)
+{
+ int pruned = 0;
+
+ /* Delete all aliases */
+ ASTOBJ_CONTAINER_WRLOCK(&aliasl);
+ ASTOBJ_CONTAINER_TRAVERSE(&aliasl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ ASTOBJ_MARK(iterator);
+ ++pruned;
+ ASTOBJ_UNLOCK(iterator);
+ } while (0) );
+ if (pruned) {
+ ASTOBJ_CONTAINER_PRUNE_MARKED(&aliasl, oh323_destroy_alias);
+ }
+ ASTOBJ_CONTAINER_UNLOCK(&aliasl);
+}
+
+static void prune_peers(void)
+{
+ /* Prune peers who still are supposed to be deleted */
+ ASTOBJ_CONTAINER_PRUNE_MARKED(&peerl, oh323_destroy_peer);
+}
+
+static int reload_config(int is_reload)
+{
+ struct ast_config *cfg, *ucfg;
+ struct ast_variable *v;
+ struct oh323_peer *peer = NULL;
+ struct oh323_user *user = NULL;
+ struct oh323_alias *alias = NULL;
+ struct ast_hostent ahp; struct hostent *hp;
+ char *cat;
+ const char *utype;
+ int is_user, is_peer, is_alias;
+ char _gatekeeper[100];
+ int gk_discover, gk_disable, gk_changed;
+ struct ast_flags config_flags = { is_reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+
+ cfg = ast_config_load(config, config_flags);
+
+ /* We *must* have a config file otherwise stop immediately */
+ if (!cfg) {
+ ast_log(LOG_NOTICE, "Unable to load config %s, H.323 disabled\n", config);
+ return 1;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
+ ucfg = ast_config_load("users.conf", config_flags);
+ if (ucfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+ ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
+ cfg = ast_config_load(config, config_flags);
+ } else {
+ ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
+ ucfg = ast_config_load("users.conf", config_flags);
+ }
+
+ if (is_reload) {
+ delete_users();
+ delete_aliases();
+ prune_peers();
+ }
+
+ /* fire up the H.323 Endpoint */
+ if (!h323_end_point_exist()) {
+ h323_end_point_create();
+ }
+ ast_copy_string(_gatekeeper, gatekeeper, sizeof(_gatekeeper));
+ gk_discover = gatekeeper_discover;
+ gk_disable = gatekeeper_disable;
+ memset(&bindaddr, 0, sizeof(bindaddr));
+ memset(&global_options, 0, sizeof(global_options));
+ global_options.fastStart = 1;
+ global_options.h245Tunneling = 1;
+ global_options.dtmfcodec[0] = H323_DTMF_RFC2833_PT;
+ global_options.dtmfcodec[1] = H323_DTMF_CISCO_PT;
+ global_options.dtmfmode = 0;
+ global_options.holdHandling = 0;
+ global_options.capability = GLOBAL_CAPABILITY;
+ global_options.bridge = 1; /* Do native bridging by default */
+ strcpy(default_context, "default");
+ h323_signalling_port = 1720;
+ gatekeeper_disable = 1;
+ gatekeeper_discover = 0;
+ gkroute = 0;
+ userbyalias = 1;
+ acceptAnonymous = 1;
+ tos = 0;
+ cos = 0;
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ if (ucfg) {
+ struct ast_variable *gen;
+ int genhas_h323;
+ const char *has_h323;
+
+ genhas_h323 = ast_true(ast_variable_retrieve(ucfg, "general", "hash323"));
+ gen = ast_variable_browse(ucfg, "general");
+ for (cat = ast_category_browse(ucfg, NULL); cat; cat = ast_category_browse(ucfg, cat)) {
+ if (strcasecmp(cat, "general")) {
+ has_h323 = ast_variable_retrieve(ucfg, cat, "hash323");
+ if (ast_true(has_h323) || (!has_h323 && genhas_h323)) {
+ user = build_user(cat, gen, ast_variable_browse(ucfg, cat), 0);
+ if (user) {
+ ASTOBJ_CONTAINER_LINK(&userl, user);
+ ASTOBJ_UNREF(user, oh323_destroy_user);
+ }
+ peer = build_peer(cat, gen, ast_variable_browse(ucfg, cat), 0);
+ if (peer) {
+ ASTOBJ_CONTAINER_LINK(&peerl, peer);
+ ASTOBJ_UNREF(peer, oh323_destroy_peer);
+ }
+ }
+ }
+ }
+ ast_config_destroy(ucfg);
+ }
+
+ for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
+ continue;
+ /* Create the interface list */
+ if (!strcasecmp(v->name, "port")) {
+ h323_signalling_port = (int)strtol(v->value, NULL, 10);
+ } else if (!strcasecmp(v->name, "bindaddr")) {
+ if (!(hp = ast_gethostbyname(v->value, &ahp))) {
+ ast_log(LOG_WARNING, "Invalid address: %s\n", v->value);
+ } else {
+ memcpy(&bindaddr.sin_addr, hp->h_addr, sizeof(bindaddr.sin_addr));
+ }
+ } else if (!strcasecmp(v->name, "tos")) { /* Needs to be removed in next release */
+ ast_log(LOG_WARNING, "The \"tos\" setting is deprecated in this version of Asterisk. Please change to \"tos_audio\".\n");
+ if (ast_str2tos(v->value, &tos)) {
+ ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "tos_audio")) {
+ if (ast_str2tos(v->value, &tos)) {
+ ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "cos")) {
+ ast_log(LOG_WARNING, "The \"cos\" setting is deprecated in this version of Asterisk. Please change to \"cos_audio\".\n");
+ if (ast_str2cos(v->value, &cos)) {
+ ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "cos_audio")) {
+ if (ast_str2cos(v->value, &cos)) {
+ ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "gatekeeper")) {
+ if (!strcasecmp(v->value, "DISABLE")) {
+ gatekeeper_disable = 1;
+ } else if (!strcasecmp(v->value, "DISCOVER")) {
+ gatekeeper_disable = 0;
+ gatekeeper_discover = 1;
+ } else {
+ gatekeeper_disable = 0;
+ ast_copy_string(gatekeeper, v->value, sizeof(gatekeeper));
+ }
+ } else if (!strcasecmp(v->name, "secret")) {
+ ast_copy_string(secret, v->value, sizeof(secret));
+ } else if (!strcasecmp(v->name, "AllowGKRouted")) {
+ gkroute = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(default_context, v->value, sizeof(default_context));
+ ast_verb(2, "Setting default context to %s\n", default_context);
+ } else if (!strcasecmp(v->name, "UserByAlias")) {
+ userbyalias = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "AcceptAnonymous")) {
+ acceptAnonymous = ast_true(v->value);
+ } else if (!update_common_options(v, &global_options)) {
+ /* dummy */
+ }
+ }
+ if (!global_options.dtmfmode)
+ global_options.dtmfmode = H323_DTMF_RFC2833;
+ if (global_options.holdHandling == ~0)
+ global_options.holdHandling = 0;
+ else if (!global_options.holdHandling)
+ global_options.holdHandling = H323_HOLD_H450;
+
+ for (cat = ast_category_browse(cfg, NULL); cat; cat = ast_category_browse(cfg, cat)) {
+ if (strcasecmp(cat, "general")) {
+ utype = ast_variable_retrieve(cfg, cat, "type");
+ if (utype) {
+ is_user = is_peer = is_alias = 0;
+ if (!strcasecmp(utype, "user"))
+ is_user = 1;
+ else if (!strcasecmp(utype, "peer"))
+ is_peer = 1;
+ else if (!strcasecmp(utype, "friend"))
+ is_user = is_peer = 1;
+ else if (!strcasecmp(utype, "h323") || !strcasecmp(utype, "alias"))
+ is_alias = 1;
+ else {
+ ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", utype, cat, config);
+ continue;
+ }
+ if (is_user) {
+ user = build_user(cat, ast_variable_browse(cfg, cat), NULL, 0);
+ if (user) {
+ ASTOBJ_CONTAINER_LINK(&userl, user);
+ ASTOBJ_UNREF(user, oh323_destroy_user);
+ }
+ }
+ if (is_peer) {
+ peer = build_peer(cat, ast_variable_browse(cfg, cat), NULL, 0);
+ if (peer) {
+ ASTOBJ_CONTAINER_LINK(&peerl, peer);
+ ASTOBJ_UNREF(peer, oh323_destroy_peer);
+ }
+ }
+ if (is_alias) {
+ alias = build_alias(cat, ast_variable_browse(cfg, cat), NULL, 0);
+ if (alias) {
+ ASTOBJ_CONTAINER_LINK(&aliasl, alias);
+ ASTOBJ_UNREF(alias, oh323_destroy_alias);
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Section '%s' lacks type\n", cat);
+ }
+ }
+ }
+ ast_config_destroy(cfg);
+
+ /* Register our H.323 aliases if any*/
+ ASTOBJ_CONTAINER_WRLOCK(&aliasl);
+ ASTOBJ_CONTAINER_TRAVERSE(&aliasl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ if (h323_set_alias(iterator)) {
+ ast_log(LOG_ERROR, "Alias %s rejected by endpoint\n", alias->name);
+ ASTOBJ_UNLOCK(iterator);
+ continue;
+ }
+ ASTOBJ_UNLOCK(iterator);
+ } while (0) );
+ ASTOBJ_CONTAINER_UNLOCK(&aliasl);
+
+ /* Don't touch GK if nothing changed because URQ will drop all existing calls */
+ gk_changed = 0;
+ if (gatekeeper_disable != gk_disable)
+ gk_changed = is_reload;
+ else if(!gatekeeper_disable && (gatekeeper_discover != gk_discover))
+ gk_changed = is_reload;
+ else if(!gatekeeper_disable && (strncmp(_gatekeeper, gatekeeper, sizeof(_gatekeeper)) != 0))
+ gk_changed = is_reload;
+ if (gk_changed) {
+ if(!gk_disable)
+ h323_gk_urq();
+ if (!gatekeeper_disable) {
+ if (h323_set_gk(gatekeeper_discover, gatekeeper, secret)) {
+ ast_log(LOG_ERROR, "Gatekeeper registration failed.\n");
+ gatekeeper_disable = 1;
+ }
+ }
+ }
+ return 0;
+}
+
+static int h323_reload(void)
+{
+ ast_mutex_lock(&h323_reload_lock);
+ if (h323_reloading) {
+ ast_verbose("Previous H.323 reload not yet done\n");
+ } else {
+ h323_reloading = 1;
+ }
+ ast_mutex_unlock(&h323_reload_lock);
+ restart_monitor();
+ return 0;
+}
+
+static char *handle_cli_h323_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "h323 reload";
+ e->usage =
+ "Usage: h323 reload\n"
+ " Reloads H.323 configuration from h323.conf\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 2)
+ return CLI_SHOWUSAGE;
+
+ h323_reload();
+
+ return CLI_SUCCESS;
+}
+
+static int h323_do_reload(void)
+{
+ reload_config(1);
+ return 0;
+}
+
+static int reload(void)
+{
+ if (!sched || !io) {
+ ast_log(LOG_NOTICE, "Unload and load chan_h323.so again in order to receive configuration changes.\n");
+ return 0;
+ }
+ return h323_reload();
+}
+
+static struct ast_cli_entry cli_h323_reload =
+ AST_CLI_DEFINE(handle_cli_h323_reload, "Reload H.323 configuration");
+
+static enum ast_rtp_get_result oh323_get_rtp_peer(struct ast_channel *chan, struct ast_rtp **rtp)
+{
+ struct oh323_pvt *pvt;
+ enum ast_rtp_get_result res = AST_RTP_GET_FAILED;
+
+ if (!(pvt = (struct oh323_pvt *)chan->tech_pvt))
+ return res;
+
+ ast_mutex_lock(&pvt->lock);
+ if (pvt->rtp && pvt->options.bridge) {
+ *rtp = pvt->rtp;
+ res = AST_RTP_TRY_NATIVE;
+ }
+ ast_mutex_unlock(&pvt->lock);
+
+ return res;
+}
+
+static enum ast_rtp_get_result oh323_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp **rtp)
+{
+ return AST_RTP_GET_FAILED;
+}
+
+static char *convertcap(int cap)
+{
+ switch (cap) {
+ case AST_FORMAT_G723_1:
+ return "G.723";
+ case AST_FORMAT_GSM:
+ return "GSM";
+ case AST_FORMAT_ULAW:
+ return "ULAW";
+ case AST_FORMAT_ALAW:
+ return "ALAW";
+ case AST_FORMAT_G722:
+ return "G.722";
+ case AST_FORMAT_ADPCM:
+ return "G.728";
+ case AST_FORMAT_G729A:
+ return "G.729";
+ case AST_FORMAT_SPEEX:
+ return "SPEEX";
+ case AST_FORMAT_ILBC:
+ return "ILBC";
+ default:
+ ast_log(LOG_NOTICE, "Don't know how to deal with mode %d\n", cap);
+ return NULL;
+ }
+}
+
+static int oh323_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp, struct ast_rtp *vrtp, struct ast_rtp *trtp, int codecs, int nat_active)
+{
+ /* XXX Deal with Video */
+ struct oh323_pvt *pvt;
+ struct sockaddr_in them;
+ struct sockaddr_in us;
+ char *mode;
+
+ if (!rtp) {
+ return 0;
+ }
+
+ mode = convertcap(chan->writeformat);
+ pvt = (struct oh323_pvt *) chan->tech_pvt;
+ if (!pvt) {
+ ast_log(LOG_ERROR, "No Private Structure, this is bad\n");
+ return -1;
+ }
+ ast_rtp_get_peer(rtp, &them);
+ ast_rtp_get_us(rtp, &us);
+#if 0 /* Native bridge still isn't ready */
+ h323_native_bridge(pvt->cd.call_token, ast_inet_ntoa(them.sin_addr), mode);
+#endif
+ return 0;
+}
+
+static struct ast_rtp_protocol oh323_rtp = {
+ .type = "H323",
+ .get_rtp_info = oh323_get_rtp_peer,
+ .get_vrtp_info = oh323_get_vrtp_peer,
+ .set_rtp_peer = oh323_set_rtp_peer,
+};
+
+static enum ast_module_load_result load_module(void)
+{
+ int res;
+
+ h323debug = 0;
+ sched = sched_context_create();
+ if (!sched) {
+ ast_log(LOG_WARNING, "Unable to create schedule context\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ io = io_context_create();
+ if (!io) {
+ ast_log(LOG_WARNING, "Unable to create I/O context\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ ast_cli_register(&cli_h323_reload);
+ ASTOBJ_CONTAINER_INIT(&userl);
+ ASTOBJ_CONTAINER_INIT(&peerl);
+ ASTOBJ_CONTAINER_INIT(&aliasl);
+ res = reload_config(0);
+ if (res) {
+ /* No config entry */
+ ast_log(LOG_NOTICE, "Unload and load chan_h323.so again in order to receive configuration changes.\n");
+ ast_cli_unregister(&cli_h323_reload);
+ io_context_destroy(io);
+ io = NULL;
+ sched_context_destroy(sched);
+ sched = NULL;
+ ASTOBJ_CONTAINER_DESTROY(&userl);
+ ASTOBJ_CONTAINER_DESTROY(&peerl);
+ ASTOBJ_CONTAINER_DESTROY(&aliasl);
+ return AST_MODULE_LOAD_DECLINE;
+ } else {
+ /* Make sure we can register our channel type */
+ if (ast_channel_register(&oh323_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'H323'\n");
+ ast_cli_unregister(&cli_h323_reload);
+ h323_end_process();
+ io_context_destroy(io);
+ sched_context_destroy(sched);
+
+ ASTOBJ_CONTAINER_DESTROYALL(&userl, oh323_destroy_user);
+ ASTOBJ_CONTAINER_DESTROY(&userl);
+ ASTOBJ_CONTAINER_DESTROYALL(&peerl, oh323_destroy_peer);
+ ASTOBJ_CONTAINER_DESTROY(&peerl);
+ ASTOBJ_CONTAINER_DESTROYALL(&aliasl, oh323_destroy_alias);
+ ASTOBJ_CONTAINER_DESTROY(&aliasl);
+
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ ast_cli_register_multiple(cli_h323, sizeof(cli_h323) / sizeof(struct ast_cli_entry));
+
+ ast_rtp_proto_register(&oh323_rtp);
+
+ /* Register our callback functions */
+ h323_callback_register(setup_incoming_call,
+ setup_outgoing_call,
+ external_rtp_create,
+ setup_rtp_connection,
+ cleanup_connection,
+ chan_ringing,
+ connection_made,
+ receive_digit,
+ answer_call,
+ progress,
+ set_dtmf_payload,
+ hangup_connection,
+ set_local_capabilities,
+ set_peer_capabilities,
+ remote_hold);
+ /* start the h.323 listener */
+ if (h323_start_listener(h323_signalling_port, bindaddr)) {
+ ast_log(LOG_ERROR, "Unable to create H323 listener.\n");
+ ast_rtp_proto_unregister(&oh323_rtp);
+ ast_cli_unregister_multiple(cli_h323, sizeof(cli_h323) / sizeof(struct ast_cli_entry));
+ ast_cli_unregister(&cli_h323_reload);
+ h323_end_process();
+ io_context_destroy(io);
+ sched_context_destroy(sched);
+
+ ASTOBJ_CONTAINER_DESTROYALL(&userl, oh323_destroy_user);
+ ASTOBJ_CONTAINER_DESTROY(&userl);
+ ASTOBJ_CONTAINER_DESTROYALL(&peerl, oh323_destroy_peer);
+ ASTOBJ_CONTAINER_DESTROY(&peerl);
+ ASTOBJ_CONTAINER_DESTROYALL(&aliasl, oh323_destroy_alias);
+ ASTOBJ_CONTAINER_DESTROY(&aliasl);
+
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ /* Possibly register with a GK */
+ if (!gatekeeper_disable) {
+ if (h323_set_gk(gatekeeper_discover, gatekeeper, secret)) {
+ ast_log(LOG_ERROR, "Gatekeeper registration failed.\n");
+ gatekeeper_disable = 1;
+ res = AST_MODULE_LOAD_SUCCESS;
+ }
+ }
+ /* And start the monitor for the first time */
+ restart_monitor();
+ }
+ return res;
+}
+
+static int unload_module(void)
+{
+ struct oh323_pvt *p, *pl;
+
+ /* unregister commands */
+ ast_cli_unregister_multiple(cli_h323, sizeof(cli_h323) / sizeof(struct ast_cli_entry));
+ ast_cli_unregister(&cli_h323_reload);
+
+ ast_channel_unregister(&oh323_tech);
+ ast_rtp_proto_unregister(&oh323_rtp);
+
+ if (!ast_mutex_lock(&iflock)) {
+ /* hangup all interfaces if they have an owner */
+ p = iflist;
+ while(p) {
+ if (p->owner) {
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ }
+ p = p->next;
+ }
+ iflist = NULL;
+ ast_mutex_unlock(&iflock);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the interface list\n");
+ return -1;
+ }
+ if (!ast_mutex_lock(&monlock)) {
+ if ((monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) {
+ /* this causes a seg, anyone know why? */
+ if (monitor_thread != pthread_self())
+ pthread_cancel(monitor_thread);
+ pthread_kill(monitor_thread, SIGURG);
+ pthread_join(monitor_thread, NULL);
+ }
+ monitor_thread = AST_PTHREADT_STOP;
+ ast_mutex_unlock(&monlock);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the monitor\n");
+ return -1;
+ }
+ if (!ast_mutex_lock(&iflock)) {
+ /* destroy all the interfaces and free their memory */
+ p = iflist;
+ while(p) {
+ pl = p;
+ p = p->next;
+ /* free associated memory */
+ ast_mutex_destroy(&pl->lock);
+ ast_free(pl);
+ }
+ iflist = NULL;
+ ast_mutex_unlock(&iflock);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the interface list\n");
+ return -1;
+ }
+ if (!gatekeeper_disable)
+ h323_gk_urq();
+ h323_end_process();
+ if (io)
+ io_context_destroy(io);
+ if (sched)
+ sched_context_destroy(sched);
+
+ ASTOBJ_CONTAINER_DESTROYALL(&userl, oh323_destroy_user);
+ ASTOBJ_CONTAINER_DESTROY(&userl);
+ ASTOBJ_CONTAINER_DESTROYALL(&peerl, oh323_destroy_peer);
+ ASTOBJ_CONTAINER_DESTROY(&peerl);
+ ASTOBJ_CONTAINER_DESTROYALL(&aliasl, oh323_destroy_alias);
+ ASTOBJ_CONTAINER_DESTROY(&aliasl);
+
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "The NuFone Network's OpenH323 Channel Driver",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+);
diff --git a/trunk/channels/chan_iax2.c b/trunk/channels/chan_iax2.c
new file mode 100644
index 000000000..c5774669a
--- /dev/null
+++ b/trunk/channels/chan_iax2.c
@@ -0,0 +1,11726 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Implementation of Inter-Asterisk eXchange Version 2
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \par See also
+ * \arg \ref Config_iax
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <use>zaptel</use>
+ <use>crypto</use>
+ <depend>res_features</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/mman.h>
+#include <dirent.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/time.h>
+#include <sys/signal.h>
+#include <signal.h>
+#include <strings.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <regex.h>
+
+#include "asterisk/zapata.h"
+#include "asterisk/paths.h" /* need ast_config_AST_DATA_DIR for firmware */
+
+#include "asterisk/lock.h"
+#include "asterisk/frame.h"
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/translate.h"
+#include "asterisk/md5.h"
+#include "asterisk/cdr.h"
+#include "asterisk/crypto.h"
+#include "asterisk/acl.h"
+#include "asterisk/manager.h"
+#include "asterisk/callerid.h"
+#include "asterisk/app.h"
+#include "asterisk/astdb.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/features.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/localtime.h"
+#include "asterisk/aes.h"
+#include "asterisk/dnsmgr.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/netsock.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/event.h"
+#include "asterisk/astobj2.h"
+
+#include "iax2.h"
+#include "iax2-parser.h"
+#include "iax2-provision.h"
+#include "jitterbuf.h"
+
+/* Define SCHED_MULTITHREADED to run the scheduler in a special
+ multithreaded mode. */
+#define SCHED_MULTITHREADED
+
+/* Define DEBUG_SCHED_MULTITHREADED to keep track of where each
+ thread is actually doing. */
+#define DEBUG_SCHED_MULTITHREAD
+
+
+#ifdef SO_NO_CHECK
+static int nochecksums = 0;
+#endif
+
+
+#define PTR_TO_CALLNO(a) ((unsigned short)(unsigned long)(a))
+#define CALLNO_TO_PTR(a) ((void *)(unsigned long)(a))
+
+#define DEFAULT_THREAD_COUNT 10
+#define DEFAULT_MAX_THREAD_COUNT 100
+#define DEFAULT_RETRY_TIME 1000
+#define MEMORY_SIZE 100
+#define DEFAULT_DROP 3
+/* Flag to use with trunk calls, keeping these calls high up. It halves our effective use
+ but keeps the division between trunked and non-trunked better. */
+#define TRUNK_CALL_START 0x4000
+
+#define DEBUG_SUPPORT
+
+#define MIN_REUSE_TIME 60 /* Don't reuse a call number within 60 seconds */
+
+/* Sample over last 100 units to determine historic jitter */
+#define GAMMA (0.01)
+
+static struct ast_codec_pref prefs;
+
+static const char tdesc[] = "Inter Asterisk eXchange Driver (Ver 2)";
+
+
+/*! \brief Maximum transmission unit for the UDP packet in the trunk not to be
+ fragmented. This is based on 1516 - ethernet - ip - udp - iax minus one g711 frame = 1240 */
+#define MAX_TRUNK_MTU 1240
+
+static int global_max_trunk_mtu; /*!< Maximum MTU, 0 if not used */
+static int trunk_timed, trunk_untimed, trunk_maxmtu, trunk_nmaxmtu ; /*!< Trunk MTU statistics */
+
+
+static char context[80] = "default";
+
+static char language[MAX_LANGUAGE] = "";
+static char regcontext[AST_MAX_CONTEXT] = "";
+
+static int maxauthreq = 3;
+static int max_retries = 4;
+static int ping_time = 21;
+static int lagrq_time = 10;
+static int maxtrunkcall = TRUNK_CALL_START;
+static int maxnontrunkcall = 1;
+static int maxjitterbuffer=1000;
+static int resyncthreshold=1000;
+static int maxjitterinterps=10;
+static int jittertargetextra = 40; /* number of milliseconds the new jitter buffer adds on to its size */
+
+#define MAX_TRUNKDATA 640 * 200 /*!< 40ms, uncompressed linear * 200 channels */
+
+static int trunkfreq = 20;
+static int trunkmaxsize = MAX_TRUNKDATA;
+
+static int authdebug = 1;
+static int autokill = 0;
+static int iaxcompat = 0;
+
+static int iaxdefaultdpcache=10 * 60; /* Cache dialplan entries for 10 minutes by default */
+
+static int iaxdefaulttimeout = 5; /* Default to wait no more than 5 seconds for a reply to come back */
+
+static unsigned int tos = 0;
+
+static unsigned int cos = 0;
+
+static int min_reg_expire;
+static int max_reg_expire;
+
+static int srvlookup = 0;
+
+static int timingfd = -1; /* Timing file descriptor */
+
+static struct ast_netsock_list *netsock;
+static struct ast_netsock_list *outsock; /*!< used if sourceaddress specified and bindaddr == INADDR_ANY */
+static int defaultsockfd = -1;
+
+int (*iax2_regfunk)(const char *username, int onoff) = NULL;
+
+/* Ethernet, etc */
+#define IAX_CAPABILITY_FULLBANDWIDTH 0xFFFF
+/* T1, maybe ISDN */
+#define IAX_CAPABILITY_MEDBANDWIDTH (IAX_CAPABILITY_FULLBANDWIDTH & \
+ ~AST_FORMAT_SLINEAR & \
+ ~AST_FORMAT_ULAW & \
+ ~AST_FORMAT_ALAW & \
+ ~AST_FORMAT_G722)
+/* A modem */
+#define IAX_CAPABILITY_LOWBANDWIDTH (IAX_CAPABILITY_MEDBANDWIDTH & \
+ ~AST_FORMAT_G726 & \
+ ~AST_FORMAT_G726_AAL2 & \
+ ~AST_FORMAT_ADPCM)
+
+#define IAX_CAPABILITY_LOWFREE (IAX_CAPABILITY_LOWBANDWIDTH & \
+ ~AST_FORMAT_G723_1)
+
+
+#define DEFAULT_MAXMS 2000 /* Must be faster than 2 seconds by default */
+#define DEFAULT_FREQ_OK 60 * 1000 /* How often to check for the host to be up */
+#define DEFAULT_FREQ_NOTOK 10 * 1000 /* How often to check, if the host is down... */
+
+static struct io_context *io;
+static struct sched_context *sched;
+
+static int iax2_capability = IAX_CAPABILITY_FULLBANDWIDTH;
+
+static int iaxdebug = 0;
+
+static int iaxtrunkdebug = 0;
+
+static int test_losspct = 0;
+#ifdef IAXTESTS
+static int test_late = 0;
+static int test_resync = 0;
+static int test_jit = 0;
+static int test_jitpct = 0;
+#endif /* IAXTESTS */
+
+static char accountcode[AST_MAX_ACCOUNT_CODE];
+static char mohinterpret[MAX_MUSICCLASS];
+static char mohsuggest[MAX_MUSICCLASS];
+static int amaflags = 0;
+static int adsi = 0;
+static int delayreject = 0;
+static int iax2_encryption = 0;
+
+static struct ast_flags globalflags = { 0 };
+
+static pthread_t netthreadid = AST_PTHREADT_NULL;
+static pthread_t schedthreadid = AST_PTHREADT_NULL;
+AST_MUTEX_DEFINE_STATIC(sched_lock);
+static ast_cond_t sched_cond;
+
+enum iax2_state {
+ IAX_STATE_STARTED = (1 << 0),
+ IAX_STATE_AUTHENTICATED = (1 << 1),
+ IAX_STATE_TBD = (1 << 2),
+ IAX_STATE_UNCHANGED = (1 << 3),
+};
+
+struct iax2_context {
+ char context[AST_MAX_CONTEXT];
+ struct iax2_context *next;
+};
+
+enum iax2_flags {
+ IAX_HASCALLERID = (1 << 0), /*!< CallerID has been specified */
+ IAX_DELME = (1 << 1), /*!< Needs to be deleted */
+ IAX_TEMPONLY = (1 << 2), /*!< Temporary (realtime) */
+ IAX_TRUNK = (1 << 3), /*!< Treat as a trunk */
+ IAX_NOTRANSFER = (1 << 4), /*!< Don't native bridge */
+ IAX_USEJITTERBUF = (1 << 5), /*!< Use jitter buffer */
+ IAX_DYNAMIC = (1 << 6), /*!< dynamic peer */
+ IAX_SENDANI = (1 << 7), /*!< Send ANI along with CallerID */
+ /* (1 << 8) is currently unused due to the deprecation of an old option. Go ahead, take it! */
+ IAX_ALREADYGONE = (1 << 9), /*!< Already disconnected */
+ IAX_PROVISION = (1 << 10), /*!< This is a provisioning request */
+ IAX_QUELCH = (1 << 11), /*!< Whether or not we quelch audio */
+ IAX_ENCRYPTED = (1 << 12), /*!< Whether we should assume encrypted tx/rx */
+ IAX_KEYPOPULATED = (1 << 13), /*!< Whether we have a key populated */
+ IAX_CODEC_USER_FIRST = (1 << 14), /*!< are we willing to let the other guy choose the codec? */
+ IAX_CODEC_NOPREFS = (1 << 15), /*!< Force old behaviour by turning off prefs */
+ IAX_CODEC_NOCAP = (1 << 16), /*!< only consider requested format and ignore capabilities*/
+ IAX_RTCACHEFRIENDS = (1 << 17), /*!< let realtime stay till your reload */
+ IAX_RTUPDATE = (1 << 18), /*!< Send a realtime update */
+ IAX_RTAUTOCLEAR = (1 << 19), /*!< erase me on expire */
+ IAX_FORCEJITTERBUF = (1 << 20), /*!< Force jitterbuffer, even when bridged to a channel that can take jitter */
+ IAX_RTIGNOREREGEXPIRE = (1 << 21), /*!< When using realtime, ignore registration expiration */
+ IAX_TRUNKTIMESTAMPS = (1 << 22), /*!< Send trunk timestamps */
+ IAX_TRANSFERMEDIA = (1 << 23), /*!< When doing IAX2 transfers, transfer media only */
+ IAX_MAXAUTHREQ = (1 << 24), /*!< Maximum outstanding AUTHREQ restriction is in place */
+ IAX_DELAYPBXSTART = (1 << 25), /*!< Don't start a PBX on the channel until the peer sends us a
+ response, so that we've achieved a three-way handshake with
+ them before sending voice or anything else*/
+};
+
+static int global_rtautoclear = 120;
+
+static int reload_config(void);
+
+struct iax2_user {
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(name);
+ AST_STRING_FIELD(secret);
+ AST_STRING_FIELD(dbsecret);
+ AST_STRING_FIELD(accountcode);
+ AST_STRING_FIELD(mohinterpret);
+ AST_STRING_FIELD(mohsuggest);
+ AST_STRING_FIELD(inkeys); /*!< Key(s) this user can use to authenticate to us */
+ AST_STRING_FIELD(language);
+ AST_STRING_FIELD(cid_num);
+ AST_STRING_FIELD(cid_name);
+ );
+
+ int authmethods;
+ int encmethods;
+ int amaflags;
+ int adsi;
+ unsigned int flags;
+ int capability;
+ int maxauthreq; /*!< Maximum allowed outstanding AUTHREQs */
+ int curauthreq; /*!< Current number of outstanding AUTHREQs */
+ struct ast_codec_pref prefs;
+ struct ast_ha *ha;
+ struct iax2_context *contexts;
+ struct ast_variable *vars;
+};
+
+struct iax2_peer {
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(name);
+ AST_STRING_FIELD(username);
+ AST_STRING_FIELD(secret);
+ AST_STRING_FIELD(dbsecret);
+ AST_STRING_FIELD(outkey); /*!< What key we use to talk to this peer */
+
+ AST_STRING_FIELD(regexten); /*!< Extension to register (if regcontext is used) */
+ AST_STRING_FIELD(context); /*!< For transfers only */
+ AST_STRING_FIELD(peercontext); /*!< Context to pass to peer */
+ AST_STRING_FIELD(mailbox); /*!< Mailbox */
+ AST_STRING_FIELD(mohinterpret);
+ AST_STRING_FIELD(mohsuggest);
+ AST_STRING_FIELD(inkeys); /*!< Key(s) this peer can use to authenticate to us */
+ /* Suggested caller id if registering */
+ AST_STRING_FIELD(cid_num); /*!< Default context (for transfer really) */
+ AST_STRING_FIELD(cid_name); /*!< Default context (for transfer really) */
+ AST_STRING_FIELD(zonetag); /*!< Time Zone */
+ );
+ struct ast_codec_pref prefs;
+ struct ast_dnsmgr_entry *dnsmgr; /*!< DNS refresh manager */
+ struct sockaddr_in addr;
+ int formats;
+ int sockfd; /*!< Socket to use for transmission */
+ struct in_addr mask;
+ int adsi;
+ unsigned int flags;
+
+ /* Dynamic Registration fields */
+ struct sockaddr_in defaddr; /*!< Default address if there is one */
+ int authmethods; /*!< Authentication methods (IAX_AUTH_*) */
+ int encmethods; /*!< Encryption methods (IAX_ENCRYPT_*) */
+
+ int expire; /*!< Schedule entry for expiry */
+ int expiry; /*!< How soon to expire */
+ int capability; /*!< Capability */
+
+ /* Qualification */
+ int callno; /*!< Call number of POKE request */
+ int pokeexpire; /*!< Scheduled qualification-related task (ie iax2_poke_peer_s or iax2_poke_noanswer) */
+ int lastms; /*!< How long last response took (in ms), or -1 for no response */
+ int maxms; /*!< Max ms we will accept for the host to be up, 0 to not monitor */
+
+ int pokefreqok; /*!< How often to check if the host is up */
+ int pokefreqnotok; /*!< How often to check when the host has been determined to be down */
+ int historicms; /*!< How long recent average responses took */
+ int smoothing; /*!< Sample over how many units to determine historic ms */
+
+ struct ast_event_sub *mwi_event_sub;
+
+ struct ast_ha *ha;
+};
+
+#define IAX2_TRUNK_PREFACE (sizeof(struct iax_frame) + sizeof(struct ast_iax2_meta_hdr) + sizeof(struct ast_iax2_meta_trunk_hdr))
+
+struct iax2_trunk_peer {
+ ast_mutex_t lock;
+ int sockfd;
+ struct sockaddr_in addr;
+ struct timeval txtrunktime; /*!< Transmit trunktime */
+ struct timeval rxtrunktime; /*!< Receive trunktime */
+ struct timeval lasttxtime; /*!< Last transmitted trunktime */
+ struct timeval trunkact; /*!< Last trunk activity */
+ unsigned int lastsent; /*!< Last sent time */
+ /* Trunk data and length */
+ unsigned char *trunkdata;
+ unsigned int trunkdatalen;
+ unsigned int trunkdataalloc;
+ int trunkmaxmtu;
+ int trunkerror;
+ int calls;
+ AST_LIST_ENTRY(iax2_trunk_peer) list;
+};
+
+static AST_LIST_HEAD_STATIC(tpeers, iax2_trunk_peer);
+
+struct iax_firmware {
+ AST_LIST_ENTRY(iax_firmware) list;
+ int fd;
+ int mmaplen;
+ int dead;
+ struct ast_iax2_firmware_header *fwh;
+ unsigned char *buf;
+};
+
+enum iax_reg_state {
+ REG_STATE_UNREGISTERED = 0,
+ REG_STATE_REGSENT,
+ REG_STATE_AUTHSENT,
+ REG_STATE_REGISTERED,
+ REG_STATE_REJECTED,
+ REG_STATE_TIMEOUT,
+ REG_STATE_NOAUTH
+};
+
+enum iax_transfer_state {
+ TRANSFER_NONE = 0,
+ TRANSFER_BEGIN,
+ TRANSFER_READY,
+ TRANSFER_RELEASED,
+ TRANSFER_PASSTHROUGH,
+ TRANSFER_MBEGIN,
+ TRANSFER_MREADY,
+ TRANSFER_MRELEASED,
+ TRANSFER_MPASSTHROUGH,
+ TRANSFER_MEDIA,
+ TRANSFER_MEDIAPASS
+};
+
+struct iax2_registry {
+ struct sockaddr_in addr; /*!< Who we connect to for registration purposes */
+ char username[80];
+ char secret[80]; /*!< Password or key name in []'s */
+ int expire; /*!< Sched ID of expiration */
+ int refresh; /*!< How often to refresh */
+ enum iax_reg_state regstate;
+ int messages; /*!< Message count, low 8 bits = new, high 8 bits = old */
+ int callno; /*!< Associated call number if applicable */
+ struct sockaddr_in us; /*!< Who the server thinks we are */
+ struct ast_dnsmgr_entry *dnsmgr; /*!< DNS refresh manager */
+ AST_LIST_ENTRY(iax2_registry) entry;
+};
+
+static AST_LIST_HEAD_STATIC(registrations, iax2_registry);
+
+/* Don't retry more frequently than every 10 ms, or less frequently than every 5 seconds */
+#define MIN_RETRY_TIME 100
+#define MAX_RETRY_TIME 10000
+
+#define MAX_JITTER_BUFFER 50
+#define MIN_JITTER_BUFFER 10
+
+#define DEFAULT_TRUNKDATA 640 * 10 /*!< 40ms, uncompressed linear * 10 channels */
+
+#define MAX_TIMESTAMP_SKEW 160 /*!< maximum difference between actual and predicted ts for sending */
+
+/* If consecutive voice frame timestamps jump by more than this many milliseconds, then jitter buffer will resync */
+#define TS_GAP_FOR_JB_RESYNC 5000
+
+static int iaxthreadcount = DEFAULT_THREAD_COUNT;
+static int iaxmaxthreadcount = DEFAULT_MAX_THREAD_COUNT;
+static int iaxdynamicthreadcount = 0;
+static int iaxactivethreadcount = 0;
+
+struct iax_rr {
+ int jitter;
+ int losspct;
+ int losscnt;
+ int packets;
+ int delay;
+ int dropped;
+ int ooo;
+};
+
+struct iax2_pvt_ref;
+
+struct chan_iax2_pvt {
+ /*! Socket to send/receive on for this call */
+ int sockfd;
+ /*! Last received voice format */
+ int voiceformat;
+ /*! Last received video format */
+ int videoformat;
+ /*! Last sent voice format */
+ int svoiceformat;
+ /*! Last sent video format */
+ int svideoformat;
+ /*! What we are capable of sending */
+ int capability;
+ /*! Last received timestamp */
+ unsigned int last;
+ /*! Last sent timestamp - never send the same timestamp twice in a single call */
+ unsigned int lastsent;
+ /*! Next outgoing timestamp if everything is good */
+ unsigned int nextpred;
+ /*! True if the last voice we transmitted was not silence/CNG */
+ unsigned int notsilenttx:1;
+ /*! Ping time */
+ unsigned int pingtime;
+ /*! Max time for initial response */
+ int maxtime;
+ /*! Peer Address */
+ struct sockaddr_in addr;
+ /*! Actual used codec preferences */
+ struct ast_codec_pref prefs;
+ /*! Requested codec preferences */
+ struct ast_codec_pref rprefs;
+ /*! Our call number */
+ unsigned short callno;
+ /*! Peer callno */
+ unsigned short peercallno;
+ /*! Negotiated format, this is only used to remember what format was
+ chosen for an unauthenticated call so that the channel can get
+ created later using the right format */
+ int chosenformat;
+ /*! Peer selected format */
+ int peerformat;
+ /*! Peer capability */
+ int peercapability;
+ /*! timeval that we base our transmission on */
+ struct timeval offset;
+ /*! timeval that we base our delivery on */
+ struct timeval rxcore;
+ /*! The jitterbuffer */
+ jitterbuf *jb;
+ /*! active jb read scheduler id */
+ int jbid;
+ /*! LAG */
+ int lag;
+ /*! Error, as discovered by the manager */
+ int error;
+ /*! Owner if we have one */
+ struct ast_channel *owner;
+ /*! What's our state? */
+ struct ast_flags state;
+ /*! Expiry (optional) */
+ int expiry;
+ /*! Next outgoing sequence number */
+ unsigned char oseqno;
+ /*! Next sequence number they have not yet acknowledged */
+ unsigned char rseqno;
+ /*! Next incoming sequence number */
+ unsigned char iseqno;
+ /*! Last incoming sequence number we have acknowledged */
+ unsigned char aseqno;
+
+ AST_DECLARE_STRING_FIELDS(
+ /*! Peer name */
+ AST_STRING_FIELD(peer);
+ /*! Default Context */
+ AST_STRING_FIELD(context);
+ /*! Caller ID if available */
+ AST_STRING_FIELD(cid_num);
+ AST_STRING_FIELD(cid_name);
+ /*! Hidden Caller ID (i.e. ANI) if appropriate */
+ AST_STRING_FIELD(ani);
+ /*! DNID */
+ AST_STRING_FIELD(dnid);
+ /*! RDNIS */
+ AST_STRING_FIELD(rdnis);
+ /*! Requested Extension */
+ AST_STRING_FIELD(exten);
+ /*! Expected Username */
+ AST_STRING_FIELD(username);
+ /*! Expected Secret */
+ AST_STRING_FIELD(secret);
+ /*! MD5 challenge */
+ AST_STRING_FIELD(challenge);
+ /*! Public keys permitted keys for incoming authentication */
+ AST_STRING_FIELD(inkeys);
+ /*! Private key for outgoing authentication */
+ AST_STRING_FIELD(outkey);
+ /*! Preferred language */
+ AST_STRING_FIELD(language);
+ /*! Hostname/peername for naming purposes */
+ AST_STRING_FIELD(host);
+
+ AST_STRING_FIELD(dproot);
+ AST_STRING_FIELD(accountcode);
+ AST_STRING_FIELD(mohinterpret);
+ AST_STRING_FIELD(mohsuggest);
+ /*! received OSP token */
+ AST_STRING_FIELD(osptoken);
+ );
+
+ /*! permitted authentication methods */
+ int authmethods;
+ /*! permitted encryption methods */
+ int encmethods;
+ /*! Encryption AES-128 Key */
+ ast_aes_encrypt_key ecx;
+ /*! Decryption AES-128 Key */
+ ast_aes_decrypt_key dcx;
+ /*! 32 bytes of semi-random data */
+ unsigned char semirand[32];
+ /*! Associated registry */
+ struct iax2_registry *reg;
+ /*! Associated peer for poking */
+ struct iax2_peer *peerpoke;
+ /*! IAX_ flags */
+ unsigned int flags;
+ int adsi;
+
+ /*! Transferring status */
+ enum iax_transfer_state transferring;
+ /*! Transfer identifier */
+ int transferid;
+ /*! Who we are IAX transferring to */
+ struct sockaddr_in transfer;
+ /*! What's the new call number for the transfer */
+ unsigned short transfercallno;
+ /*! Transfer encrypt AES-128 Key */
+ ast_aes_encrypt_key tdcx;
+
+ /*! Status of knowledge of peer ADSI capability */
+ int peeradsicpe;
+
+ /*! Who we are bridged to */
+ unsigned short bridgecallno;
+
+ int pingid; /*!< Transmit PING request */
+ int lagid; /*!< Retransmit lag request */
+ int autoid; /*!< Auto hangup for Dialplan requestor */
+ int authid; /*!< Authentication rejection ID */
+ int authfail; /*!< Reason to report failure */
+ int initid; /*!< Initial peer auto-congest ID (based on qualified peers) */
+ int calling_ton;
+ int calling_tns;
+ int calling_pres;
+ int amaflags;
+ AST_LIST_HEAD_NOLOCK(, iax2_dpcache) dpentries;
+ struct ast_variable *vars;
+ /*! last received remote rr */
+ struct iax_rr remote_rr;
+ /*! Current base time: (just for stats) */
+ int min;
+ /*! Dropped frame count: (just for stats) */
+ int frames_dropped;
+ /*! received frame count: (just for stats) */
+ int frames_received;
+};
+
+/*!
+ * \brief a list of frames that may need to be retransmitted
+ *
+ * \note The contents of this list do not need to be explicitly destroyed
+ * on module unload. This is because all active calls are destroyed, and
+ * all frames in this queue will get destroyed as a part of that process.
+ */
+static AST_LIST_HEAD_STATIC(frame_queue, iax_frame);
+
+/*!
+ * This module will get much higher performance when doing a lot of
+ * user and peer lookups if the number of buckets is increased from 1.
+ * However, to maintain old behavior for Asterisk 1.4, these are set to
+ * 1 by default. When using multiple buckets, search order through these
+ * containers is considered random, so you will not be able to depend on
+ * the order the entires are specified in iax.conf for matching order. */
+#ifdef LOW_MEMORY
+#define MAX_PEER_BUCKETS 17
+#else
+#define MAX_PEER_BUCKETS 563
+#endif
+static struct ao2_container *peers;
+
+#define MAX_USER_BUCKETS MAX_PEER_BUCKETS
+static struct ao2_container *users;
+
+static AST_LIST_HEAD_STATIC(firmwares, iax_firmware);
+
+enum {
+ /*! Extension exists */
+ CACHE_FLAG_EXISTS = (1 << 0),
+ /*! Extension is nonexistent */
+ CACHE_FLAG_NONEXISTENT = (1 << 1),
+ /*! Extension can exist */
+ CACHE_FLAG_CANEXIST = (1 << 2),
+ /*! Waiting to hear back response */
+ CACHE_FLAG_PENDING = (1 << 3),
+ /*! Timed out */
+ CACHE_FLAG_TIMEOUT = (1 << 4),
+ /*! Request transmitted */
+ CACHE_FLAG_TRANSMITTED = (1 << 5),
+ /*! Timeout */
+ CACHE_FLAG_UNKNOWN = (1 << 6),
+ /*! Matchmore */
+ CACHE_FLAG_MATCHMORE = (1 << 7),
+};
+
+struct iax2_dpcache {
+ char peercontext[AST_MAX_CONTEXT];
+ char exten[AST_MAX_EXTENSION];
+ struct timeval orig;
+ struct timeval expiry;
+ int flags;
+ unsigned short callno;
+ int waiters[256];
+ AST_LIST_ENTRY(iax2_dpcache) cache_list;
+ AST_LIST_ENTRY(iax2_dpcache) peer_list;
+};
+
+static AST_LIST_HEAD_STATIC(dpcache, iax2_dpcache);
+
+static void reg_source_db(struct iax2_peer *p);
+static struct iax2_peer *realtime_peer(const char *peername, struct sockaddr_in *sin);
+
+static int ast_cli_netstats(struct mansession *s, int fd, int limit_fmt);
+static char *complete_iax2_show_peer(const char *line, const char *word, int pos, int state);
+static char *complete_iax2_unregister(const char *line, const char *word, int pos, int state);
+
+enum iax2_thread_iostate {
+ IAX_IOSTATE_IDLE,
+ IAX_IOSTATE_READY,
+ IAX_IOSTATE_PROCESSING,
+ IAX_IOSTATE_SCHEDREADY,
+};
+
+enum iax2_thread_type {
+ IAX_THREAD_TYPE_POOL,
+ IAX_THREAD_TYPE_DYNAMIC,
+};
+
+struct iax2_pkt_buf {
+ AST_LIST_ENTRY(iax2_pkt_buf) entry;
+ size_t len;
+ unsigned char buf[1];
+};
+
+struct iax2_thread {
+ AST_LIST_ENTRY(iax2_thread) list;
+ enum iax2_thread_type type;
+ enum iax2_thread_iostate iostate;
+#ifdef SCHED_MULTITHREADED
+ void (*schedfunc)(const void *);
+ const void *scheddata;
+#endif
+#ifdef DEBUG_SCHED_MULTITHREAD
+ char curfunc[80];
+#endif
+ int actions;
+ pthread_t threadid;
+ int threadnum;
+ struct sockaddr_in iosin;
+ unsigned char readbuf[4096];
+ unsigned char *buf;
+ ssize_t buf_len;
+ size_t buf_size;
+ int iofd;
+ time_t checktime;
+ ast_mutex_t lock;
+ ast_cond_t cond;
+ unsigned int ready_for_signal:1;
+ /*! if this thread is processing a full frame,
+ some information about that frame will be stored
+ here, so we can avoid dispatching any more full
+ frames for that callno to other threads */
+ struct {
+ unsigned short callno;
+ struct sockaddr_in sin;
+ unsigned char type;
+ unsigned char csub;
+ } ffinfo;
+ /*! Queued up full frames for processing. If more full frames arrive for
+ * a call which this thread is already processing a full frame for, they
+ * are queued up here. */
+ AST_LIST_HEAD_NOLOCK(, iax2_pkt_buf) full_frames;
+};
+
+/* Thread lists */
+static AST_LIST_HEAD_STATIC(idle_list, iax2_thread);
+static AST_LIST_HEAD_STATIC(active_list, iax2_thread);
+static AST_LIST_HEAD_STATIC(dynamic_list, iax2_thread);
+
+static void *iax2_process_thread(void *data);
+
+static void signal_condition(ast_mutex_t *lock, ast_cond_t *cond)
+{
+ ast_mutex_lock(lock);
+ ast_cond_signal(cond);
+ ast_mutex_unlock(lock);
+}
+
+static void iax_debug_output(const char *data)
+{
+ if (iaxdebug)
+ ast_verbose("%s", data);
+}
+
+static void iax_error_output(const char *data)
+{
+ ast_log(LOG_WARNING, "%s", data);
+}
+
+static void jb_error_output(const char *fmt, ...)
+{
+ va_list args;
+ char buf[1024];
+
+ va_start(args, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+
+ ast_log(LOG_ERROR, buf);
+}
+
+static void jb_warning_output(const char *fmt, ...)
+{
+ va_list args;
+ char buf[1024];
+
+ va_start(args, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+
+ ast_log(LOG_WARNING, buf);
+}
+
+static void jb_debug_output(const char *fmt, ...)
+{
+ va_list args;
+ char buf[1024];
+
+ va_start(args, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+
+ ast_verbose(buf);
+}
+
+/*!
+ * \brief an array of iax2 pvt structures
+ *
+ * The container for active chan_iax2_pvt structures is implemented as an
+ * array for extremely quick direct access to the correct pvt structure
+ * based on the local call number. The local call number is used as the
+ * index into the array where the associated pvt structure is stored.
+ */
+static struct chan_iax2_pvt *iaxs[IAX_MAX_CALLS];
+/*!
+ * \brief chan_iax2_pvt structure locks
+ *
+ * These locks are used when accessing a pvt structure in the iaxs array.
+ * The index used here is the same as used in the iaxs array. It is the
+ * local call number for the associated pvt struct.
+ */
+static ast_mutex_t iaxsl[IAX_MAX_CALLS];
+/*!
+ * \brief The last time a call number was used
+ *
+ * It is important to know the last time that a call number was used locally so
+ * that it is not used again too soon. The reason for this is the same as the
+ * reason that the TCP protocol state machine requires a "TIME WAIT" state.
+ *
+ * For example, say that a call is up. Then, the remote side sends a HANGUP,
+ * which we respond to with an ACK. However, there is no way to know whether
+ * the ACK made it there successfully. If it were to get lost, the remote
+ * side may retransmit the HANGUP. If in the meantime, this call number has
+ * been reused locally, given the right set of circumstances, this retransmitted
+ * HANGUP could potentially improperly hang up the new session. So, to avoid
+ * this potential issue, we must wait a specified timeout period before reusing
+ * a local call number.
+ *
+ * The specified time that we must wait before reusing a local call number is
+ * defined as MIN_REUSE_TIME, with a default of 60 seconds.
+ */
+static struct timeval lastused[IAX_MAX_CALLS];
+
+static enum ast_bridge_result iax2_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
+static int expire_registry(const void *data);
+static int iax2_answer(struct ast_channel *c);
+static int iax2_call(struct ast_channel *c, char *dest, int timeout);
+static int iax2_devicestate(void *data);
+static int iax2_digit_begin(struct ast_channel *c, char digit);
+static int iax2_digit_end(struct ast_channel *c, char digit, unsigned int duration);
+static int iax2_do_register(struct iax2_registry *reg);
+static int iax2_fixup(struct ast_channel *oldchannel, struct ast_channel *newchan);
+static int iax2_hangup(struct ast_channel *c);
+static int iax2_indicate(struct ast_channel *c, int condition, const void *data, size_t datalen);
+static int iax2_poke_peer(struct iax2_peer *peer, int heldcall);
+static int iax2_provision(struct sockaddr_in *end, int sockfd, char *dest, const char *template, int force);
+static int iax2_send(struct chan_iax2_pvt *pvt, struct ast_frame *f, unsigned int ts, int seqno, int now, int transfer, int final);
+static int iax2_sendhtml(struct ast_channel *c, int subclass, const char *data, int datalen);
+static int iax2_sendimage(struct ast_channel *c, struct ast_frame *img);
+static int iax2_sendtext(struct ast_channel *c, const char *text);
+static int iax2_setoption(struct ast_channel *c, int option, void *data, int datalen);
+static int iax2_transfer(struct ast_channel *c, const char *dest);
+static int iax2_write(struct ast_channel *c, struct ast_frame *f);
+static int send_trunk(struct iax2_trunk_peer *tpeer, struct timeval *now);
+static int send_command(struct chan_iax2_pvt *, char, int, unsigned int, const unsigned char *, int, int);
+static int send_command_final(struct chan_iax2_pvt *, char, int, unsigned int, const unsigned char *, int, int);
+static int send_command_immediate(struct chan_iax2_pvt *, char, int, unsigned int, const unsigned char *, int, int);
+static int send_command_locked(unsigned short callno, char, int, unsigned int, const unsigned char *, int, int);
+static int send_command_transfer(struct chan_iax2_pvt *, char, int, unsigned int, const unsigned char *, int);
+static struct ast_channel *iax2_request(const char *type, int format, void *data, int *cause);
+static struct ast_frame *iax2_read(struct ast_channel *c);
+static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly);
+static struct iax2_user *build_user(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly);
+static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, time_t regtime);
+static void prune_peers(void);
+static void *iax2_dup_variable_datastore(void *);
+static void iax2_free_variable_datastore(void *);
+
+static int acf_channel_read(struct ast_channel *chan, const char *funcname, char *preparse, char *buf, size_t buflen);
+static int acf_channel_write(struct ast_channel *chan, const char *function, char *data, const char *value);
+
+static const struct ast_channel_tech iax2_tech = {
+ .type = "IAX2",
+ .description = tdesc,
+ .capabilities = IAX_CAPABILITY_FULLBANDWIDTH,
+ .properties = AST_CHAN_TP_WANTSJITTER,
+ .requester = iax2_request,
+ .devicestate = iax2_devicestate,
+ .send_digit_begin = iax2_digit_begin,
+ .send_digit_end = iax2_digit_end,
+ .send_text = iax2_sendtext,
+ .send_image = iax2_sendimage,
+ .send_html = iax2_sendhtml,
+ .call = iax2_call,
+ .hangup = iax2_hangup,
+ .answer = iax2_answer,
+ .read = iax2_read,
+ .write = iax2_write,
+ .write_video = iax2_write,
+ .indicate = iax2_indicate,
+ .setoption = iax2_setoption,
+ .bridge = iax2_bridge,
+ .transfer = iax2_transfer,
+ .fixup = iax2_fixup,
+ .func_channel_read = acf_channel_read,
+ .func_channel_write = acf_channel_write,
+};
+
+static void mwi_event_cb(const struct ast_event *event, void *userdata)
+{
+ /* The MWI subscriptions exist just so the core knows we care about those
+ * mailboxes. However, we just grab the events out of the cache when it
+ * is time to send MWI, since it is only sent with a REGACK. */
+}
+
+/*! \brief Send manager event at call setup to link between Asterisk channel name
+ and IAX2 call identifiers */
+static void iax2_ami_channelupdate(struct chan_iax2_pvt *pvt)
+{
+ manager_event(EVENT_FLAG_SYSTEM, "ChannelUpdate",
+ "Channel: %s\r\nChanneltype: IAX2\r\nIAX2-callno-local: %d\r\nIAX2-callno-remote: %d\r\nIAX2-peer: %s\r\n",
+ pvt->owner ? pvt->owner->name : "",
+ pvt->callno, pvt->peercallno, pvt->peer ? pvt->peer : "");
+}
+
+
+static struct ast_datastore_info iax2_variable_datastore_info = {
+ .type = "IAX2_VARIABLE",
+ .duplicate = iax2_dup_variable_datastore,
+ .destroy = iax2_free_variable_datastore,
+};
+
+static void *iax2_dup_variable_datastore(void *old)
+{
+ AST_LIST_HEAD(, ast_var_t) *oldlist = old, *newlist;
+ struct ast_var_t *oldvar, *newvar;
+
+ newlist = ast_calloc(sizeof(*newlist), 1);
+ if (!newlist) {
+ ast_log(LOG_ERROR, "Unable to duplicate iax2 variables\n");
+ return NULL;
+ }
+
+ AST_LIST_HEAD_INIT(newlist);
+ AST_LIST_LOCK(oldlist);
+ AST_LIST_TRAVERSE(oldlist, oldvar, entries) {
+ newvar = ast_var_assign(ast_var_name(oldvar), ast_var_value(oldvar));
+ if (newvar)
+ AST_LIST_INSERT_TAIL(newlist, newvar, entries);
+ else
+ ast_log(LOG_ERROR, "Unable to duplicate iax2 variable '%s'\n", ast_var_name(oldvar));
+ }
+ AST_LIST_UNLOCK(oldlist);
+ return newlist;
+}
+
+static void iax2_free_variable_datastore(void *old)
+{
+ AST_LIST_HEAD(, ast_var_t) *oldlist = old;
+ struct ast_var_t *oldvar;
+
+ AST_LIST_LOCK(oldlist);
+ while ((oldvar = AST_LIST_REMOVE_HEAD(oldlist, entries))) {
+ ast_free(oldvar);
+ }
+ AST_LIST_UNLOCK(oldlist);
+ AST_LIST_HEAD_DESTROY(oldlist);
+ ast_free(oldlist);
+}
+
+
+/* WARNING: insert_idle_thread should only ever be called within the
+ * context of an iax2_process_thread() thread.
+ */
+static void insert_idle_thread(struct iax2_thread *thread)
+{
+ if (thread->type == IAX_THREAD_TYPE_DYNAMIC) {
+ AST_LIST_LOCK(&dynamic_list);
+ AST_LIST_INSERT_TAIL(&dynamic_list, thread, list);
+ AST_LIST_UNLOCK(&dynamic_list);
+ } else {
+ AST_LIST_LOCK(&idle_list);
+ AST_LIST_INSERT_TAIL(&idle_list, thread, list);
+ AST_LIST_UNLOCK(&idle_list);
+ }
+
+ return;
+}
+
+static struct iax2_thread *find_idle_thread(void)
+{
+ struct iax2_thread *thread = NULL;
+
+ /* Pop the head of the idle list off */
+ AST_LIST_LOCK(&idle_list);
+ thread = AST_LIST_REMOVE_HEAD(&idle_list, list);
+ AST_LIST_UNLOCK(&idle_list);
+
+ /* If we popped a thread off the idle list, just return it */
+ if (thread) {
+ memset(&thread->ffinfo, 0, sizeof(thread->ffinfo));
+ return thread;
+ }
+
+ /* Pop the head of the dynamic list off */
+ AST_LIST_LOCK(&dynamic_list);
+ thread = AST_LIST_REMOVE_HEAD(&dynamic_list, list);
+ AST_LIST_UNLOCK(&dynamic_list);
+
+ /* If we popped a thread off the dynamic list, just return it */
+ if (thread) {
+ memset(&thread->ffinfo, 0, sizeof(thread->ffinfo));
+ return thread;
+ }
+
+ /* If we can't create a new dynamic thread for any reason, return no thread at all */
+ if (iaxdynamicthreadcount >= iaxmaxthreadcount || !(thread = ast_calloc(1, sizeof(*thread))))
+ return NULL;
+
+ /* Set default values */
+ thread->threadnum = ast_atomic_fetchadd_int(&iaxdynamicthreadcount, 1);
+ thread->type = IAX_THREAD_TYPE_DYNAMIC;
+
+ /* Initialize lock and condition */
+ ast_mutex_init(&thread->lock);
+ ast_cond_init(&thread->cond, NULL);
+
+ /* Create thread and send it on it's way */
+ if (ast_pthread_create_detached_background(&thread->threadid, NULL, iax2_process_thread, thread)) {
+ ast_cond_destroy(&thread->cond);
+ ast_mutex_destroy(&thread->lock);
+ ast_free(thread);
+ return NULL;
+ }
+
+ /* this thread is not processing a full frame (since it is idle),
+ so ensure that the field for the full frame call number is empty */
+ memset(&thread->ffinfo, 0, sizeof(thread->ffinfo));
+
+ /* Wait for the thread to be ready before returning it to the caller */
+ while (!thread->ready_for_signal)
+ usleep(1);
+
+ return thread;
+}
+
+#ifdef SCHED_MULTITHREADED
+static int __schedule_action(void (*func)(const void *data), const void *data, const char *funcname)
+{
+ struct iax2_thread *thread = NULL;
+ static time_t lasterror;
+ static time_t t;
+
+ thread = find_idle_thread();
+
+ if (thread != NULL) {
+ thread->schedfunc = func;
+ thread->scheddata = data;
+ thread->iostate = IAX_IOSTATE_SCHEDREADY;
+#ifdef DEBUG_SCHED_MULTITHREAD
+ ast_copy_string(thread->curfunc, funcname, sizeof(thread->curfunc));
+#endif
+ signal_condition(&thread->lock, &thread->cond);
+ return 0;
+ }
+ time(&t);
+ if (t != lasterror)
+ ast_log(LOG_NOTICE, "Out of idle IAX2 threads for scheduling!\n");
+ lasterror = t;
+
+ return -1;
+}
+#define schedule_action(func, data) __schedule_action(func, data, __PRETTY_FUNCTION__)
+#endif
+
+static int iax2_sched_replace(int old_id, struct sched_context *con, int when, ast_sched_cb callback, const void *data)
+{
+ int res;
+
+ res = ast_sched_replace(old_id, con, when, callback, data);
+ signal_condition(&sched_lock, &sched_cond);
+
+ return res;
+}
+
+static int iax2_sched_add(struct sched_context *con, int when, ast_sched_cb callback, const void *data)
+{
+ int res;
+
+ res = ast_sched_add(con, when, callback, data);
+ signal_condition(&sched_lock, &sched_cond);
+
+ return res;
+}
+
+static int send_ping(const void *data);
+
+static void __send_ping(const void *data)
+{
+ int callno = (long)data;
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno] && iaxs[callno]->pingid != -1) {
+ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_PING, 0, NULL, 0, -1);
+ iaxs[callno]->pingid = iax2_sched_add(sched, ping_time * 1000, send_ping, data);
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+}
+
+static int send_ping(const void *data)
+{
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__send_ping, data))
+#endif
+ __send_ping(data);
+ return 0;
+}
+
+static int get_encrypt_methods(const char *s)
+{
+ int e;
+ if (!strcasecmp(s, "aes128"))
+ e = IAX_ENCRYPT_AES128;
+ else if (ast_true(s))
+ e = IAX_ENCRYPT_AES128;
+ else
+ e = 0;
+ return e;
+}
+
+static int send_lagrq(const void *data);
+
+static void __send_lagrq(const void *data)
+{
+ int callno = (long)data;
+ /* Ping only if it's real not if it's bridged */
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno] && iaxs[callno]->lagid != -1) {
+ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_LAGRQ, 0, NULL, 0, -1);
+ iaxs[callno]->lagid = iax2_sched_add(sched, lagrq_time * 1000, send_lagrq, data);
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+}
+
+static int send_lagrq(const void *data)
+{
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__send_lagrq, data))
+#endif
+ __send_lagrq(data);
+ return 0;
+}
+
+static unsigned char compress_subclass(int subclass)
+{
+ int x;
+ int power=-1;
+ /* If it's 128 or smaller, just return it */
+ if (subclass < IAX_FLAG_SC_LOG)
+ return subclass;
+ /* Otherwise find its power */
+ for (x = 0; x < IAX_MAX_SHIFT; x++) {
+ if (subclass & (1 << x)) {
+ if (power > -1) {
+ ast_log(LOG_WARNING, "Can't compress subclass %d\n", subclass);
+ return 0;
+ } else
+ power = x;
+ }
+ }
+ return power | IAX_FLAG_SC_LOG;
+}
+
+static int uncompress_subclass(unsigned char csub)
+{
+ /* If the SC_LOG flag is set, return 2^csub otherwise csub */
+ if (csub & IAX_FLAG_SC_LOG) {
+ /* special case for 'compressed' -1 */
+ if (csub == 0xff)
+ return -1;
+ else
+ return 1 << (csub & ~IAX_FLAG_SC_LOG & IAX_MAX_SHIFT);
+ }
+ else
+ return csub;
+}
+
+/*!
+ * \note The only member of the peer passed here guaranteed to be set is the name field
+ */
+static int peer_hash_cb(const void *obj, const int flags)
+{
+ const struct iax2_peer *peer = obj;
+
+ return ast_str_hash(peer->name);
+}
+
+/*!
+ * \note The only member of the peer passed here guaranteed to be set is the name field
+ */
+static int peer_cmp_cb(void *obj, void *arg, int flags)
+{
+ struct iax2_peer *peer = obj, *peer2 = arg;
+
+ return !strcasecmp(peer->name, peer2->name) ? CMP_MATCH : 0;
+}
+
+/*!
+ * \note The only member of the user passed here guaranteed to be set is the name field
+ */
+static int user_hash_cb(const void *obj, const int flags)
+{
+ const struct iax2_user *user = obj;
+
+ return ast_str_hash(user->name);
+}
+
+/*!
+ * \note The only member of the user passed here guaranteed to be set is the name field
+ */
+static int user_cmp_cb(void *obj, void *arg, int flags)
+{
+ struct iax2_user *user = obj, *user2 = arg;
+
+ return !strcasecmp(user->name, user2->name) ? CMP_MATCH : 0;
+}
+
+/*!
+ * \note This funtion calls realtime_peer -> reg_source_db -> iax2_poke_peer -> find_callno,
+ * so do not call it with a pvt lock held.
+ */
+static struct iax2_peer *find_peer(const char *name, int realtime)
+{
+ struct iax2_peer *peer = NULL;
+ struct iax2_peer tmp_peer = {
+ .name = name,
+ };
+
+ peer = ao2_find(peers, &tmp_peer, OBJ_POINTER);
+
+ /* Now go for realtime if applicable */
+ if(!peer && realtime)
+ peer = realtime_peer(name, NULL);
+
+ return peer;
+}
+
+static struct iax2_peer *peer_ref(struct iax2_peer *peer)
+{
+ ao2_ref(peer, +1);
+ return peer;
+}
+
+static inline struct iax2_peer *peer_unref(struct iax2_peer *peer)
+{
+ ao2_ref(peer, -1);
+ return NULL;
+}
+
+static inline struct iax2_user *user_ref(struct iax2_user *user)
+{
+ ao2_ref(user, +1);
+ return user;
+}
+
+static inline struct iax2_user *user_unref(struct iax2_user *user)
+{
+ ao2_ref(user, -1);
+ return NULL;
+}
+
+static int iax2_getpeername(struct sockaddr_in sin, char *host, int len)
+{
+ struct iax2_peer *peer = NULL;
+ int res = 0;
+ struct ao2_iterator i;
+
+ i = ao2_iterator_init(peers, 0);
+ while ((peer = ao2_iterator_next(&i))) {
+ if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) &&
+ (peer->addr.sin_port == sin.sin_port)) {
+ ast_copy_string(host, peer->name, len);
+ peer_unref(peer);
+ res = 1;
+ break;
+ }
+ peer_unref(peer);
+ }
+
+ if (!peer) {
+ peer = realtime_peer(NULL, &sin);
+ if (peer) {
+ ast_copy_string(host, peer->name, len);
+ peer_unref(peer);
+ res = 1;
+ }
+ }
+
+ return res;
+}
+
+static struct chan_iax2_pvt *new_iax(struct sockaddr_in *sin, const char *host)
+{
+ struct chan_iax2_pvt *tmp;
+ jb_conf jbconf;
+
+ if (!(tmp = ast_calloc(1, sizeof(*tmp))))
+ return NULL;
+
+ if (ast_string_field_init(tmp, 32)) {
+ ast_free(tmp);
+ tmp = NULL;
+ return NULL;
+ }
+
+ tmp->prefs = prefs;
+ tmp->pingid = -1;
+ tmp->lagid = -1;
+ tmp->autoid = -1;
+ tmp->authid = -1;
+ tmp->initid = -1;
+
+ ast_string_field_set(tmp,exten, "s");
+ ast_string_field_set(tmp,host, host);
+
+ tmp->jb = jb_new();
+ tmp->jbid = -1;
+ jbconf.max_jitterbuf = maxjitterbuffer;
+ jbconf.resync_threshold = resyncthreshold;
+ jbconf.max_contig_interp = maxjitterinterps;
+ jbconf.target_extra = jittertargetextra;
+ jb_setconf(tmp->jb,&jbconf);
+
+ AST_LIST_HEAD_INIT_NOLOCK(&tmp->dpentries);
+
+ return tmp;
+}
+
+static struct iax_frame *iaxfrdup2(struct iax_frame *fr)
+{
+ struct iax_frame *new = iax_frame_new(DIRECTION_INGRESS, fr->af.datalen, fr->cacheable);
+ if (new) {
+ size_t afdatalen = new->afdatalen;
+ memcpy(new, fr, sizeof(*new));
+ iax_frame_wrap(new, &fr->af);
+ new->afdatalen = afdatalen;
+ new->data = NULL;
+ new->datalen = 0;
+ new->direction = DIRECTION_INGRESS;
+ new->retrans = -1;
+ }
+ return new;
+}
+
+#define NEW_PREVENT 0
+#define NEW_ALLOW 1
+#define NEW_FORCE 2
+
+static int match(struct sockaddr_in *sin, unsigned short callno, unsigned short dcallno, const struct chan_iax2_pvt *cur)
+{
+ if ((cur->addr.sin_addr.s_addr == sin->sin_addr.s_addr) &&
+ (cur->addr.sin_port == sin->sin_port)) {
+ /* This is the main host */
+ if ((cur->peercallno == callno) ||
+ ((dcallno == cur->callno) && !cur->peercallno)) {
+ /* That's us. Be sure we keep track of the peer call number */
+ return 1;
+ }
+ }
+ if ((cur->transfer.sin_addr.s_addr == sin->sin_addr.s_addr) &&
+ (cur->transfer.sin_port == sin->sin_port) && (cur->transferring)) {
+ /* We're transferring */
+ if ((dcallno == cur->callno) || (cur->transferring == TRANSFER_MEDIAPASS && cur->transfercallno == callno))
+ return 1;
+ }
+ return 0;
+}
+
+static void update_max_trunk(void)
+{
+ int max = TRUNK_CALL_START;
+ int x;
+ /* XXX Prolly don't need locks here XXX */
+ for (x=TRUNK_CALL_START;x<IAX_MAX_CALLS - 1; x++) {
+ if (iaxs[x])
+ max = x + 1;
+ }
+ maxtrunkcall = max;
+ if (iaxdebug)
+ ast_debug(1, "New max trunk callno is %d\n", max);
+}
+
+static void update_max_nontrunk(void)
+{
+ int max = 1;
+ int x;
+ /* XXX Prolly don't need locks here XXX */
+ for (x=1;x<TRUNK_CALL_START - 1; x++) {
+ if (iaxs[x])
+ max = x + 1;
+ }
+ maxnontrunkcall = max;
+ if (iaxdebug)
+ ast_debug(1, "New max nontrunk callno is %d\n", max);
+}
+
+static int make_trunk(unsigned short callno, int locked)
+{
+ int x;
+ int res= 0;
+ struct timeval now = ast_tvnow();
+ if (iaxs[callno]->oseqno) {
+ ast_log(LOG_WARNING, "Can't make trunk once a call has started!\n");
+ return -1;
+ }
+ if (callno & TRUNK_CALL_START) {
+ ast_log(LOG_WARNING, "Call %d is already a trunk\n", callno);
+ return -1;
+ }
+ for (x=TRUNK_CALL_START;x<IAX_MAX_CALLS - 1; x++) {
+ ast_mutex_lock(&iaxsl[x]);
+ if (!iaxs[x] && ((now.tv_sec - lastused[x].tv_sec) > MIN_REUSE_TIME)) {
+ iaxs[x] = iaxs[callno];
+ iaxs[x]->callno = x;
+ iaxs[callno] = NULL;
+ /* Update the two timers that should have been started */
+ iaxs[x]->pingid = iax2_sched_replace(iaxs[x]->pingid, sched,
+ ping_time * 1000, send_ping, (void *)(long)x);
+ iaxs[x]->lagid = iax2_sched_replace(iaxs[x]->lagid, sched,
+ lagrq_time * 1000, send_lagrq, (void *)(long)x);
+ if (locked)
+ ast_mutex_unlock(&iaxsl[callno]);
+ res = x;
+ if (!locked)
+ ast_mutex_unlock(&iaxsl[x]);
+ break;
+ }
+ ast_mutex_unlock(&iaxsl[x]);
+ }
+ if (x >= IAX_MAX_CALLS - 1) {
+ ast_log(LOG_WARNING, "Unable to trunk call: Insufficient space\n");
+ return -1;
+ }
+ ast_debug(1, "Made call %d into trunk call %d\n", callno, x);
+ /* We move this call from a non-trunked to a trunked call */
+ update_max_trunk();
+ update_max_nontrunk();
+ return res;
+}
+
+/*!
+ * \todo XXX Note that this function contains a very expensive operation that
+ * happens for *every* incoming media frame. It iterates through every
+ * possible call number, locking and unlocking each one, to try to match the
+ * incoming frame to an active call. Call numbers can be up to 2^15, 32768.
+ * So, for a call with a local call number of 20000, every incoming audio
+ * frame would require 20000 mutex lock and unlock operations. Ouch.
+ *
+ * It's a shame that IAX2 media frames carry the source call number instead of
+ * the destination call number. If they did, this lookup wouldn't be needed.
+ * However, it's too late to change that now. Instead, we need to come up with
+ * a better way of indexing active calls so that these frequent lookups are not
+ * so expensive.
+ *
+ * \note Calling this function while holding another pvt lock can cause a deadlock.
+ */
+static int find_callno(unsigned short callno, unsigned short dcallno, struct sockaddr_in *sin, int new, int sockfd)
+{
+ int res = 0;
+ int x;
+ struct timeval now;
+ char host[80];
+
+ if (new <= NEW_ALLOW) {
+ for (x=1;(res < 1) && (x<maxnontrunkcall);x++) {
+ ast_mutex_lock(&iaxsl[x]);
+ if (iaxs[x]) {
+ /* Look for an exact match */
+ if (match(sin, callno, dcallno, iaxs[x])) {
+ res = x;
+ }
+ }
+ ast_mutex_unlock(&iaxsl[x]);
+ }
+ for (x=TRUNK_CALL_START;(res < 1) && (x<maxtrunkcall);x++) {
+ ast_mutex_lock(&iaxsl[x]);
+ if (iaxs[x]) {
+ /* Look for an exact match */
+ if (match(sin, callno, dcallno, iaxs[x])) {
+ res = x;
+ }
+ }
+ ast_mutex_unlock(&iaxsl[x]);
+ }
+ }
+ if ((res < 1) && (new >= NEW_ALLOW)) {
+ /* It may seem odd that we look through the peer list for a name for
+ * this *incoming* call. Well, it is weird. However, users don't
+ * have an IP address/port number that we can match against. So,
+ * this is just checking for a peer that has that IP/port and
+ * assuming that we have a user of the same name. This isn't always
+ * correct, but it will be changed if needed after authentication. */
+ if (!iax2_getpeername(*sin, host, sizeof(host)))
+ snprintf(host, sizeof(host), "%s:%d", ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ now = ast_tvnow();
+ for (x=1;x<TRUNK_CALL_START;x++) {
+ /* Find first unused call number that hasn't been used in a while */
+ ast_mutex_lock(&iaxsl[x]);
+ if (!iaxs[x] && ((now.tv_sec - lastused[x].tv_sec) > MIN_REUSE_TIME)) break;
+ ast_mutex_unlock(&iaxsl[x]);
+ }
+ /* We've still got lock held if we found a spot */
+ if (x >= TRUNK_CALL_START) {
+ ast_log(LOG_WARNING, "No more space\n");
+ return 0;
+ }
+ iaxs[x] = new_iax(sin, host);
+ update_max_nontrunk();
+ if (iaxs[x]) {
+ if (iaxdebug)
+ ast_debug(1, "Creating new call structure %d\n", x);
+ iaxs[x]->sockfd = sockfd;
+ iaxs[x]->addr.sin_port = sin->sin_port;
+ iaxs[x]->addr.sin_family = sin->sin_family;
+ iaxs[x]->addr.sin_addr.s_addr = sin->sin_addr.s_addr;
+ iaxs[x]->peercallno = callno;
+ iaxs[x]->callno = x;
+ iaxs[x]->pingtime = DEFAULT_RETRY_TIME;
+ iaxs[x]->expiry = min_reg_expire;
+ iaxs[x]->pingid = iax2_sched_add(sched, ping_time * 1000, send_ping, (void *)(long)x);
+ iaxs[x]->lagid = iax2_sched_add(sched, lagrq_time * 1000, send_lagrq, (void *)(long)x);
+ iaxs[x]->amaflags = amaflags;
+ ast_copy_flags(iaxs[x], (&globalflags), IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF);
+
+ ast_string_field_set(iaxs[x], accountcode, accountcode);
+ ast_string_field_set(iaxs[x], mohinterpret, mohinterpret);
+ ast_string_field_set(iaxs[x], mohsuggest, mohsuggest);
+ } else {
+ ast_log(LOG_WARNING, "Out of resources\n");
+ ast_mutex_unlock(&iaxsl[x]);
+ return 0;
+ }
+ ast_mutex_unlock(&iaxsl[x]);
+ res = x;
+ }
+ return res;
+}
+
+static void iax2_frame_free(struct iax_frame *fr)
+{
+ if (fr->retrans > -1)
+ ast_sched_del(sched, fr->retrans);
+ iax_frame_free(fr);
+}
+
+/*!
+ * \brief Queue a frame to a call's owning asterisk channel
+ *
+ * \pre This function assumes that iaxsl[callno] is locked when called.
+ *
+ * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno]
+ * was valid before calling it, it may no longer be valid after calling it.
+ * This function may unlock and lock the mutex associated with this callno,
+ * meaning that another thread may grab it and destroy the call.
+ */
+static int iax2_queue_frame(int callno, struct ast_frame *f)
+{
+ for (;;) {
+ if (iaxs[callno] && iaxs[callno]->owner) {
+ if (ast_channel_trylock(iaxs[callno]->owner)) {
+ /* Avoid deadlock by pausing and trying again */
+ ast_mutex_unlock(&iaxsl[callno]);
+ usleep(1);
+ ast_mutex_lock(&iaxsl[callno]);
+ } else {
+ ast_queue_frame(iaxs[callno]->owner, f);
+ ast_channel_unlock(iaxs[callno]->owner);
+ break;
+ }
+ } else
+ break;
+ }
+ return 0;
+}
+
+/*!
+ * \brief Queue a hangup frame on the ast_channel owner
+ *
+ * This function queues a hangup frame on the owner of the IAX2 pvt struct that
+ * is active for the given call number.
+ *
+ * \pre Assumes lock for callno is already held.
+ *
+ * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno]
+ * was valid before calling it, it may no longer be valid after calling it.
+ * This function may unlock and lock the mutex associated with this callno,
+ * meaning that another thread may grab it and destroy the call.
+ */
+static int iax2_queue_hangup(int callno)
+{
+ for (;;) {
+ if (iaxs[callno] && iaxs[callno]->owner) {
+ if (ast_channel_trylock(iaxs[callno]->owner)) {
+ /* Avoid deadlock by pausing and trying again */
+ ast_mutex_unlock(&iaxsl[callno]);
+ usleep(1);
+ ast_mutex_lock(&iaxsl[callno]);
+ } else {
+ ast_queue_hangup(iaxs[callno]->owner);
+ ast_channel_unlock(iaxs[callno]->owner);
+ break;
+ }
+ } else
+ break;
+ }
+ return 0;
+}
+
+/*!
+ * \brief Queue a control frame on the ast_channel owner
+ *
+ * This function queues a control frame on the owner of the IAX2 pvt struct that
+ * is active for the given call number.
+ *
+ * \pre Assumes lock for callno is already held.
+ *
+ * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno]
+ * was valid before calling it, it may no longer be valid after calling it.
+ * This function may unlock and lock the mutex associated with this callno,
+ * meaning that another thread may grab it and destroy the call.
+ */
+static int iax2_queue_control_data(int callno,
+ enum ast_control_frame_type control, const void *data, size_t datalen)
+{
+ for (;;) {
+ if (iaxs[callno] && iaxs[callno]->owner) {
+ if (ast_channel_trylock(iaxs[callno]->owner)) {
+ /* Avoid deadlock by pausing and trying again */
+ ast_mutex_unlock(&iaxsl[callno]);
+ usleep(1);
+ ast_mutex_lock(&iaxsl[callno]);
+ } else {
+ ast_queue_control_data(iaxs[callno]->owner, control, data, datalen);
+ ast_channel_unlock(iaxs[callno]->owner);
+ break;
+ }
+ } else
+ break;
+ }
+ return 0;
+}
+static void destroy_firmware(struct iax_firmware *cur)
+{
+ /* Close firmware */
+ if (cur->fwh) {
+ munmap((void*)cur->fwh, ntohl(cur->fwh->datalen) + sizeof(*(cur->fwh)));
+ }
+ close(cur->fd);
+ ast_free(cur);
+}
+
+static int try_firmware(char *s)
+{
+ struct stat stbuf;
+ struct iax_firmware *cur = NULL;
+ int ifd, fd, res, len, chunk;
+ struct ast_iax2_firmware_header *fwh, fwh2;
+ struct MD5Context md5;
+ unsigned char sum[16], buf[1024];
+ char *s2, *last;
+
+ if (!(s2 = alloca(strlen(s) + 100))) {
+ ast_log(LOG_WARNING, "Alloca failed!\n");
+ return -1;
+ }
+
+ last = strrchr(s, '/');
+ if (last)
+ last++;
+ else
+ last = s;
+
+ snprintf(s2, strlen(s) + 100, "/var/tmp/%s-%ld", last, (unsigned long)ast_random());
+
+ if ((res = stat(s, &stbuf) < 0)) {
+ ast_log(LOG_WARNING, "Failed to stat '%s': %s\n", s, strerror(errno));
+ return -1;
+ }
+
+ /* Make sure it's not a directory */
+ if (S_ISDIR(stbuf.st_mode))
+ return -1;
+ ifd = open(s, O_RDONLY);
+ if (ifd < 0) {
+ ast_log(LOG_WARNING, "Cannot open '%s': %s\n", s, strerror(errno));
+ return -1;
+ }
+ fd = open(s2, O_RDWR | O_CREAT | O_EXCL, AST_FILE_MODE);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Cannot open '%s' for writing: %s\n", s2, strerror(errno));
+ close(ifd);
+ return -1;
+ }
+ /* Unlink our newly created file */
+ unlink(s2);
+
+ /* Now copy the firmware into it */
+ len = stbuf.st_size;
+ while(len) {
+ chunk = len;
+ if (chunk > sizeof(buf))
+ chunk = sizeof(buf);
+ res = read(ifd, buf, chunk);
+ if (res != chunk) {
+ ast_log(LOG_WARNING, "Only read %d of %d bytes of data :(: %s\n", res, chunk, strerror(errno));
+ close(ifd);
+ close(fd);
+ return -1;
+ }
+ res = write(fd, buf, chunk);
+ if (res != chunk) {
+ ast_log(LOG_WARNING, "Only write %d of %d bytes of data :(: %s\n", res, chunk, strerror(errno));
+ close(ifd);
+ close(fd);
+ return -1;
+ }
+ len -= chunk;
+ }
+ close(ifd);
+ /* Return to the beginning */
+ lseek(fd, 0, SEEK_SET);
+ if ((res = read(fd, &fwh2, sizeof(fwh2))) != sizeof(fwh2)) {
+ ast_log(LOG_WARNING, "Unable to read firmware header in '%s'\n", s);
+ close(fd);
+ return -1;
+ }
+ if (ntohl(fwh2.magic) != IAX_FIRMWARE_MAGIC) {
+ ast_log(LOG_WARNING, "'%s' is not a valid firmware file\n", s);
+ close(fd);
+ return -1;
+ }
+ if (ntohl(fwh2.datalen) != (stbuf.st_size - sizeof(fwh2))) {
+ ast_log(LOG_WARNING, "Invalid data length in firmware '%s'\n", s);
+ close(fd);
+ return -1;
+ }
+ if (fwh2.devname[sizeof(fwh2.devname) - 1] || ast_strlen_zero((char *)fwh2.devname)) {
+ ast_log(LOG_WARNING, "No or invalid device type specified for '%s'\n", s);
+ close(fd);
+ return -1;
+ }
+ fwh = (struct ast_iax2_firmware_header*)mmap(NULL, stbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (fwh == (void *) -1) {
+ ast_log(LOG_WARNING, "mmap failed: %s\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ MD5Init(&md5);
+ MD5Update(&md5, fwh->data, ntohl(fwh->datalen));
+ MD5Final(sum, &md5);
+ if (memcmp(sum, fwh->chksum, sizeof(sum))) {
+ ast_log(LOG_WARNING, "Firmware file '%s' fails checksum\n", s);
+ munmap((void*)fwh, stbuf.st_size);
+ close(fd);
+ return -1;
+ }
+
+ AST_LIST_TRAVERSE(&firmwares, cur, list) {
+ if (!strcmp((char *)cur->fwh->devname, (char *)fwh->devname)) {
+ /* Found a candidate */
+ if (cur->dead || (ntohs(cur->fwh->version) < ntohs(fwh->version)))
+ /* The version we have on loaded is older, load this one instead */
+ break;
+ /* This version is no newer than what we have. Don't worry about it.
+ We'll consider it a proper load anyhow though */
+ munmap((void*)fwh, stbuf.st_size);
+ close(fd);
+ return 0;
+ }
+ }
+
+ if (!cur && ((cur = ast_calloc(1, sizeof(*cur))))) {
+ cur->fd = -1;
+ AST_LIST_INSERT_TAIL(&firmwares, cur, list);
+ }
+
+ if (cur) {
+ if (cur->fwh)
+ munmap((void*)cur->fwh, cur->mmaplen);
+ if (cur->fd > -1)
+ close(cur->fd);
+ cur->fwh = fwh;
+ cur->fd = fd;
+ cur->mmaplen = stbuf.st_size;
+ cur->dead = 0;
+ }
+
+ return 0;
+}
+
+static int iax_check_version(char *dev)
+{
+ int res = 0;
+ struct iax_firmware *cur = NULL;
+
+ if (ast_strlen_zero(dev))
+ return 0;
+
+ AST_LIST_LOCK(&firmwares);
+ AST_LIST_TRAVERSE(&firmwares, cur, list) {
+ if (!strcmp(dev, (char *)cur->fwh->devname)) {
+ res = ntohs(cur->fwh->version);
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&firmwares);
+
+ return res;
+}
+
+static int iax_firmware_append(struct iax_ie_data *ied, const unsigned char *dev, unsigned int desc)
+{
+ int res = -1;
+ unsigned int bs = desc & 0xff;
+ unsigned int start = (desc >> 8) & 0xffffff;
+ unsigned int bytes;
+ struct iax_firmware *cur;
+
+ if (ast_strlen_zero((char *)dev) || !bs)
+ return -1;
+
+ start *= bs;
+
+ AST_LIST_LOCK(&firmwares);
+ AST_LIST_TRAVERSE(&firmwares, cur, list) {
+ if (strcmp((char *)dev, (char *)cur->fwh->devname))
+ continue;
+ iax_ie_append_int(ied, IAX_IE_FWBLOCKDESC, desc);
+ if (start < ntohl(cur->fwh->datalen)) {
+ bytes = ntohl(cur->fwh->datalen) - start;
+ if (bytes > bs)
+ bytes = bs;
+ iax_ie_append_raw(ied, IAX_IE_FWBLOCKDATA, cur->fwh->data + start, bytes);
+ } else {
+ bytes = 0;
+ iax_ie_append(ied, IAX_IE_FWBLOCKDATA);
+ }
+ if (bytes == bs)
+ res = 0;
+ else
+ res = 1;
+ break;
+ }
+ AST_LIST_UNLOCK(&firmwares);
+
+ return res;
+}
+
+
+static void reload_firmware(int unload)
+{
+ struct iax_firmware *cur = NULL;
+ DIR *fwd;
+ struct dirent *de;
+ char dir[256], fn[256];
+
+ AST_LIST_LOCK(&firmwares);
+
+ /* Mark all as dead */
+ AST_LIST_TRAVERSE(&firmwares, cur, list)
+ cur->dead = 1;
+
+ /* Now that we have marked them dead... load new ones */
+ if (!unload) {
+ snprintf(dir, sizeof(dir), "%s/firmware/iax", ast_config_AST_DATA_DIR);
+ fwd = opendir(dir);
+ if (fwd) {
+ while((de = readdir(fwd))) {
+ if (de->d_name[0] != '.') {
+ snprintf(fn, sizeof(fn), "%s/%s", dir, de->d_name);
+ if (!try_firmware(fn)) {
+ ast_verb(2, "Loaded firmware '%s'\n", de->d_name);
+ }
+ }
+ }
+ closedir(fwd);
+ } else
+ ast_log(LOG_WARNING, "Error opening firmware directory '%s': %s\n", dir, strerror(errno));
+ }
+
+ /* Clean up leftovers */
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&firmwares, cur, list) {
+ if (!cur->dead)
+ continue;
+ AST_LIST_REMOVE_CURRENT(list);
+ destroy_firmware(cur);
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ AST_LIST_UNLOCK(&firmwares);
+}
+
+/*!
+ * \note This function assumes that iaxsl[callno] is locked when called.
+ *
+ * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno]
+ * was valid before calling it, it may no longer be valid after calling it.
+ * This function calls iax2_queue_frame(), which may unlock and lock the mutex
+ * associated with this callno, meaning that another thread may grab it and destroy the call.
+ */
+static int __do_deliver(void *data)
+{
+ /* Just deliver the packet by using queueing. This is called by
+ the IAX thread with the iaxsl lock held. */
+ struct iax_frame *fr = data;
+ fr->retrans = -1;
+ ast_clear_flag(&fr->af, AST_FRFLAG_HAS_TIMING_INFO);
+ if (iaxs[fr->callno] && !ast_test_flag(iaxs[fr->callno], IAX_ALREADYGONE))
+ iax2_queue_frame(fr->callno, &fr->af);
+ /* Free our iax frame */
+ iax2_frame_free(fr);
+ /* And don't run again */
+ return 0;
+}
+
+static int handle_error(void)
+{
+ /* XXX Ideally we should figure out why an error occurred and then abort those
+ rather than continuing to try. Unfortunately, the published interface does
+ not seem to work XXX */
+#if 0
+ struct sockaddr_in *sin;
+ int res;
+ struct msghdr m;
+ struct sock_extended_err e;
+ m.msg_name = NULL;
+ m.msg_namelen = 0;
+ m.msg_iov = NULL;
+ m.msg_control = &e;
+ m.msg_controllen = sizeof(e);
+ m.msg_flags = 0;
+ res = recvmsg(netsocket, &m, MSG_ERRQUEUE);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Error detected, but unable to read error: %s\n", strerror(errno));
+ else {
+ if (m.msg_controllen) {
+ sin = (struct sockaddr_in *)SO_EE_OFFENDER(&e);
+ if (sin)
+ ast_log(LOG_WARNING, "Receive error from %s\n", ast_inet_ntoa(sin->sin_addr));
+ else
+ ast_log(LOG_WARNING, "No address detected??\n");
+ } else {
+ ast_log(LOG_WARNING, "Local error: %s\n", strerror(e.ee_errno));
+ }
+ }
+#endif
+ return 0;
+}
+
+static int transmit_trunk(struct iax_frame *f, struct sockaddr_in *sin, int sockfd)
+{
+ int res;
+ res = sendto(sockfd, f->data, f->datalen, 0,(struct sockaddr *)sin,
+ sizeof(*sin));
+ if (res < 0) {
+ ast_debug(1, "Received error: %s\n", strerror(errno));
+ handle_error();
+ } else
+ res = 0;
+ return res;
+}
+
+static int send_packet(struct iax_frame *f)
+{
+ int res;
+ int callno = f->callno;
+
+ /* Don't send if there was an error, but return error instead */
+ if (!callno || !iaxs[callno] || iaxs[callno]->error)
+ return -1;
+
+ /* Called with iaxsl held */
+ if (iaxdebug)
+ ast_debug(3, "Sending %d on %d/%d to %s:%d\n", f->ts, callno, iaxs[callno]->peercallno, ast_inet_ntoa(iaxs[callno]->addr.sin_addr), ntohs(iaxs[callno]->addr.sin_port));
+ if (f->transfer) {
+ if (iaxdebug)
+ iax_showframe(f, NULL, 0, &iaxs[callno]->transfer, f->datalen - sizeof(struct ast_iax2_full_hdr));
+ res = sendto(iaxs[callno]->sockfd, f->data, f->datalen, 0,(struct sockaddr *)&iaxs[callno]->transfer,
+ sizeof(iaxs[callno]->transfer));
+ } else {
+ if (iaxdebug)
+ iax_showframe(f, NULL, 0, &iaxs[callno]->addr, f->datalen - sizeof(struct ast_iax2_full_hdr));
+ res = sendto(iaxs[callno]->sockfd, f->data, f->datalen, 0,(struct sockaddr *)&iaxs[callno]->addr,
+ sizeof(iaxs[callno]->addr));
+ }
+ if (res < 0) {
+ if (iaxdebug)
+ ast_debug(1, "Received error: %s\n", strerror(errno));
+ handle_error();
+ } else
+ res = 0;
+ return res;
+}
+
+static void iax2_destroy_helper(struct chan_iax2_pvt *pvt)
+{
+ /* Decrement AUTHREQ count if needed */
+ if (ast_test_flag(pvt, IAX_MAXAUTHREQ)) {
+ struct iax2_user *user;
+ struct iax2_user tmp_user = {
+ .name = pvt->username,
+ };
+
+ user = ao2_find(users, &tmp_user, OBJ_POINTER);
+ if (user) {
+ ast_atomic_fetchadd_int(&user->curauthreq, -1);
+ user_unref(user);
+ }
+
+ ast_clear_flag(pvt, IAX_MAXAUTHREQ);
+ }
+ /* No more pings or lagrq's */
+ if (pvt->pingid > -1)
+ ast_sched_del(sched, pvt->pingid);
+ pvt->pingid = -1;
+ if (pvt->lagid > -1)
+ ast_sched_del(sched, pvt->lagid);
+ pvt->lagid = -1;
+ if (pvt->autoid > -1)
+ ast_sched_del(sched, pvt->autoid);
+ pvt->autoid = -1;
+ if (pvt->authid > -1)
+ ast_sched_del(sched, pvt->authid);
+ pvt->authid = -1;
+ if (pvt->initid > -1)
+ ast_sched_del(sched, pvt->initid);
+ pvt->initid = -1;
+ if (pvt->jbid > -1)
+ ast_sched_del(sched, pvt->jbid);
+ pvt->jbid = -1;
+}
+
+/*!
+ * \note Since this function calls iax2_queue_hangup(), the pvt struct
+ * for the given call number may disappear during its execution.
+ */
+static int iax2_predestroy(int callno)
+{
+ struct ast_channel *c = NULL;
+ struct chan_iax2_pvt *pvt = iaxs[callno];
+
+ if (!pvt)
+ return -1;
+
+ if (!ast_test_flag(pvt, IAX_ALREADYGONE)) {
+ iax2_destroy_helper(pvt);
+ ast_set_flag(pvt, IAX_ALREADYGONE);
+ }
+
+ if ((c = pvt->owner)) {
+ c->tech_pvt = NULL;
+ iax2_queue_hangup(callno);
+ pvt->owner = NULL;
+ ast_module_unref(ast_module_info->self);
+ }
+
+ return 0;
+}
+
+static void iax2_destroy(int callno)
+{
+ struct chan_iax2_pvt *pvt = NULL;
+ struct iax_frame *cur = NULL;
+ struct ast_channel *owner = NULL;
+
+retry:
+ pvt = iaxs[callno];
+ lastused[callno] = ast_tvnow();
+
+ owner = pvt ? pvt->owner : NULL;
+
+ if (owner) {
+ if (ast_channel_trylock(owner)) {
+ ast_log(LOG_NOTICE, "Avoiding IAX destroy deadlock\n");
+ ast_mutex_unlock(&iaxsl[callno]);
+ usleep(1);
+ ast_mutex_lock(&iaxsl[callno]);
+ goto retry;
+ }
+ }
+ if (!owner)
+ iaxs[callno] = NULL;
+ if (pvt) {
+ if (!owner)
+ pvt->owner = NULL;
+ iax2_destroy_helper(pvt);
+
+ /* Already gone */
+ ast_set_flag(pvt, IAX_ALREADYGONE);
+
+ if (owner) {
+ /* If there's an owner, prod it to give up */
+ /* It is ok to use ast_queue_hangup() here instead of iax2_queue_hangup()
+ * because we already hold the owner channel lock. */
+ ast_queue_hangup(owner);
+ }
+
+ AST_LIST_LOCK(&frame_queue);
+ AST_LIST_TRAVERSE(&frame_queue, cur, list) {
+ /* Cancel any pending transmissions */
+ if (cur->callno == pvt->callno)
+ cur->retries = -1;
+ }
+ AST_LIST_UNLOCK(&frame_queue);
+
+ if (pvt->reg)
+ pvt->reg->callno = 0;
+ if (!owner) {
+ jb_frame frame;
+ if (pvt->vars) {
+ ast_variables_destroy(pvt->vars);
+ pvt->vars = NULL;
+ }
+
+ while (jb_getall(pvt->jb, &frame) == JB_OK)
+ iax2_frame_free(frame.data);
+ jb_destroy(pvt->jb);
+ /* gotta free up the stringfields */
+ ast_string_field_free_memory(pvt);
+ ast_free(pvt);
+ }
+ }
+ if (owner) {
+ ast_channel_unlock(owner);
+ }
+ if (callno & 0x4000)
+ update_max_trunk();
+}
+
+static int update_packet(struct iax_frame *f)
+{
+ /* Called with iaxsl lock held, and iaxs[callno] non-NULL */
+ struct ast_iax2_full_hdr *fh = f->data;
+ /* Mark this as a retransmission */
+ fh->dcallno = ntohs(IAX_FLAG_RETRANS | f->dcallno);
+ /* Update iseqno */
+ f->iseqno = iaxs[f->callno]->iseqno;
+ fh->iseqno = f->iseqno;
+ return 0;
+}
+
+static int attempt_transmit(const void *data);
+static void __attempt_transmit(const void *data)
+{
+ /* Attempt to transmit the frame to the remote peer...
+ Called without iaxsl held. */
+ struct iax_frame *f = (struct iax_frame *)data;
+ int freeme = 0;
+ int callno = f->callno;
+ /* Make sure this call is still active */
+ if (callno)
+ ast_mutex_lock(&iaxsl[callno]);
+ if (callno && iaxs[callno]) {
+ if ((f->retries < 0) /* Already ACK'd */ ||
+ (f->retries >= max_retries) /* Too many attempts */) {
+ /* Record an error if we've transmitted too many times */
+ if (f->retries >= max_retries) {
+ if (f->transfer) {
+ /* Transfer timeout */
+ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_TXREJ, 0, NULL, 0, -1);
+ } else if (f->final) {
+ if (f->final)
+ iax2_destroy(callno);
+ } else {
+ if (iaxs[callno]->owner)
+ ast_log(LOG_WARNING, "Max retries exceeded to host %s on %s (type = %d, subclass = %d, ts=%d, seqno=%d)\n", ast_inet_ntoa(iaxs[f->callno]->addr.sin_addr),iaxs[f->callno]->owner->name , f->af.frametype, f->af.subclass, f->ts, f->oseqno);
+ iaxs[callno]->error = ETIMEDOUT;
+ if (iaxs[callno]->owner) {
+ struct ast_frame fr = { 0, };
+ /* Hangup the fd */
+ fr.frametype = AST_FRAME_CONTROL;
+ fr.subclass = AST_CONTROL_HANGUP;
+ iax2_queue_frame(callno, &fr); /* XXX */
+ /* Remember, owner could disappear */
+ if (iaxs[callno] && iaxs[callno]->owner)
+ iaxs[callno]->owner->hangupcause = AST_CAUSE_DESTINATION_OUT_OF_ORDER;
+ } else {
+ if (iaxs[callno]->reg) {
+ memset(&iaxs[callno]->reg->us, 0, sizeof(iaxs[callno]->reg->us));
+ iaxs[callno]->reg->regstate = REG_STATE_TIMEOUT;
+ iaxs[callno]->reg->refresh = IAX_DEFAULT_REG_EXPIRE;
+ }
+ iax2_destroy(callno);
+ }
+ }
+
+ }
+ freeme = 1;
+ } else {
+ /* Update it if it needs it */
+ update_packet(f);
+ /* Attempt transmission */
+ send_packet(f);
+ f->retries++;
+ /* Try again later after 10 times as long */
+ f->retrytime *= 10;
+ if (f->retrytime > MAX_RETRY_TIME)
+ f->retrytime = MAX_RETRY_TIME;
+ /* Transfer messages max out at one second */
+ if (f->transfer && (f->retrytime > 1000))
+ f->retrytime = 1000;
+ f->retrans = iax2_sched_add(sched, f->retrytime, attempt_transmit, f);
+ }
+ } else {
+ /* Make sure it gets freed */
+ f->retries = -1;
+ freeme = 1;
+ }
+ if (callno)
+ ast_mutex_unlock(&iaxsl[callno]);
+ /* Do not try again */
+ if (freeme) {
+ /* Don't attempt delivery, just remove it from the queue */
+ AST_LIST_LOCK(&frame_queue);
+ AST_LIST_REMOVE(&frame_queue, f, list);
+ AST_LIST_UNLOCK(&frame_queue);
+ f->retrans = -1;
+ /* Free the IAX frame */
+ iax2_frame_free(f);
+ }
+}
+
+static int attempt_transmit(const void *data)
+{
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__attempt_transmit, data))
+#endif
+ __attempt_transmit(data);
+ return 0;
+}
+
+static char *handle_cli_iax2_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct iax2_peer *peer;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 prune realtime";
+ e->usage =
+ "Usage: iax2 prune realtime [<peername>|all]\n"
+ " Prunes object(s) from the cache\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_iax2_show_peer(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+ if (!strcmp(a->argv[3], "all")) {
+ reload_config();
+ ast_cli(a->fd, "Cache flushed successfully.\n");
+ } else if ((peer = find_peer(a->argv[3], 0))) {
+ if(ast_test_flag(peer, IAX_RTCACHEFRIENDS)) {
+ ast_set_flag(peer, IAX_RTAUTOCLEAR);
+ expire_registry(peer_ref(peer));
+ ast_cli(a->fd, "Peer %s was removed from the cache.\n", a->argv[3]);
+ } else {
+ ast_cli(a->fd, "Peer %s is not eligible for this operation.\n", a->argv[3]);
+ }
+ peer_unref(peer);
+ } else {
+ ast_cli(a->fd, "Peer %s was not found in the cache.\n", a->argv[3]);
+ }
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_test_losspct(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 test losspct";
+ e->usage =
+ "Usage: iax2 test losspct <percentage>\n"
+ " For testing, throws away <percentage> percent of incoming packets\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ test_losspct = atoi(a->argv[3]);
+
+ return CLI_SUCCESS;
+}
+
+#ifdef IAXTESTS
+static char *handle_cli_iax2_test_late(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 test late";
+ e->usage =
+ "Usage: iax2 test late <ms>\n"
+ " For testing, count the next frame as <ms> ms late\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ test_late = atoi(a->argv[3]);
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_test_resync(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 test resync";
+ e->usage =
+ "Usage: iax2 test resync <ms>\n"
+ " For testing, adjust all future frames by <ms> ms\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ test_resync = atoi(a->argv[3]);
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_test_jitter(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 test jitter";
+ e->usage =
+ "Usage: iax2 test jitter <ms> <pct>\n"
+ " For testing, simulate maximum jitter of +/- <ms> on <pct>\n"
+ " percentage of packets. If <pct> is not specified, adds\n"
+ " jitter to all packets.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc < 4 || a->argc > 5)
+ return CLI_SHOWUSAGE;
+
+ test_jit = atoi(a->argv[3]);
+ if (a->argc == 5)
+ test_jitpct = atoi(a->argv[4]);
+
+ return CLI_SUCCESS;
+}
+#endif /* IAXTESTS */
+
+/*! \brief peer_status: Report Peer status in character string */
+/* returns 1 if peer is online, -1 if unmonitored */
+static int peer_status(struct iax2_peer *peer, char *status, int statuslen)
+{
+ int res = 0;
+ if (peer->maxms) {
+ if (peer->lastms < 0) {
+ ast_copy_string(status, "UNREACHABLE", statuslen);
+ } else if (peer->lastms > peer->maxms) {
+ snprintf(status, statuslen, "LAGGED (%d ms)", peer->lastms);
+ res = 1;
+ } else if (peer->lastms) {
+ snprintf(status, statuslen, "OK (%d ms)", peer->lastms);
+ res = 1;
+ } else {
+ ast_copy_string(status, "UNKNOWN", statuslen);
+ }
+ } else {
+ ast_copy_string(status, "Unmonitored", statuslen);
+ res = -1;
+ }
+ return res;
+}
+
+/*! \brief Show one peer in detail */
+static char *handle_cli_iax2_show_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char status[30];
+ char cbuf[256];
+ struct iax2_peer *peer;
+ char codec_buf[512];
+ int x = 0, codec = 0, load_realtime = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show peer";
+ e->usage =
+ "Usage: iax2 show peer <name>\n"
+ " Display details on specific IAX peer\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_iax2_show_peer(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc < 4)
+ return CLI_SHOWUSAGE;
+
+ load_realtime = (a->argc == 5 && !strcmp(a->argv[4], "load")) ? 1 : 0;
+
+ peer = find_peer(a->argv[3], load_realtime);
+ if (peer) {
+ ast_cli(a->fd, "\n\n");
+ ast_cli(a->fd, " * Name : %s\n", peer->name);
+ ast_cli(a->fd, " Secret : %s\n", ast_strlen_zero(peer->secret) ? "<Not set>" : "<Set>");
+ ast_cli(a->fd, " Context : %s\n", peer->context);
+ ast_cli(a->fd, " Mailbox : %s\n", peer->mailbox);
+ ast_cli(a->fd, " Dynamic : %s\n", ast_test_flag(peer, IAX_DYNAMIC) ? "Yes" : "No");
+ ast_cli(a->fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, "<unspecified>"));
+ ast_cli(a->fd, " Expire : %d\n", peer->expire);
+ ast_cli(a->fd, " ACL : %s\n", (peer->ha ? "Yes" : "No"));
+ ast_cli(a->fd, " Addr->IP : %s Port %d\n", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "(Unspecified)", ntohs(peer->addr.sin_port));
+ ast_cli(a->fd, " Defaddr->IP : %s Port %d\n", ast_inet_ntoa(peer->defaddr.sin_addr), ntohs(peer->defaddr.sin_port));
+ ast_cli(a->fd, " Username : %s\n", peer->username);
+ ast_cli(a->fd, " Codecs : ");
+ ast_getformatname_multiple(codec_buf, sizeof(codec_buf) -1, peer->capability);
+ ast_cli(a->fd, "%s\n", codec_buf);
+
+ ast_cli(a->fd, " Codec Order : (");
+ for(x = 0; x < 32 ; x++) {
+ codec = ast_codec_pref_index(&peer->prefs,x);
+ if(!codec)
+ break;
+ ast_cli(a->fd, "%s", ast_getformatname(codec));
+ if(x < 31 && ast_codec_pref_index(&peer->prefs,x+1))
+ ast_cli(a->fd, "|");
+ }
+
+ if (!x)
+ ast_cli(a->fd, "none");
+ ast_cli(a->fd, ")\n");
+
+ ast_cli(a->fd, " Status : ");
+ peer_status(peer, status, sizeof(status));
+ ast_cli(a->fd, "%s\n",status);
+ ast_cli(a->fd, " Qualify : every %dms when OK, every %dms when UNREACHABLE (sample smoothing %s)\n", peer->pokefreqok, peer->pokefreqnotok, peer->smoothing ? "On" : "Off");
+ ast_cli(a->fd, "\n");
+ peer_unref(peer);
+ } else {
+ ast_cli(a->fd, "Peer %s not found.\n", a->argv[3]);
+ ast_cli(a->fd, "\n");
+ }
+
+ return CLI_SUCCESS;
+}
+
+static char *complete_iax2_show_peer(const char *line, const char *word, int pos, int state)
+{
+ int which = 0;
+ struct iax2_peer *peer;
+ char *res = NULL;
+ int wordlen = strlen(word);
+ struct ao2_iterator i;
+
+ /* 0 - iax2; 1 - show; 2 - peer; 3 - <peername> */
+ if (pos != 3)
+ return NULL;
+
+ i = ao2_iterator_init(peers, 0);
+ while ((peer = ao2_iterator_next(&i))) {
+ if (!strncasecmp(peer->name, word, wordlen) && ++which > state) {
+ res = ast_strdup(peer->name);
+ peer_unref(peer);
+ break;
+ }
+ peer_unref(peer);
+ }
+
+ return res;
+}
+
+static char *handle_cli_iax2_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct iax_frame *cur;
+ int cnt = 0, dead = 0, final = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show stats";
+ e->usage =
+ "Usage: iax2 show stats\n"
+ " Display statistics on IAX channel driver.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ AST_LIST_LOCK(&frame_queue);
+ AST_LIST_TRAVERSE(&frame_queue, cur, list) {
+ if (cur->retries < 0)
+ dead++;
+ if (cur->final)
+ final++;
+ cnt++;
+ }
+ AST_LIST_UNLOCK(&frame_queue);
+
+ ast_cli(a->fd, " IAX Statistics\n");
+ ast_cli(a->fd, "---------------------\n");
+ ast_cli(a->fd, "Outstanding frames: %d (%d ingress, %d egress)\n", iax_get_frames(), iax_get_iframes(), iax_get_oframes());
+ ast_cli(a->fd, "%d timed and %d untimed transmits; MTU %d/%d/%d\n", trunk_timed, trunk_untimed,
+ trunk_maxmtu, trunk_nmaxmtu, global_max_trunk_mtu);
+ ast_cli(a->fd, "Packets in transmit queue: %d dead, %d final, %d total\n\n", dead, final, cnt);
+
+ trunk_timed = trunk_untimed = 0;
+ if (trunk_maxmtu > trunk_nmaxmtu)
+ trunk_nmaxmtu = trunk_maxmtu;
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief Set trunk MTU from CLI */
+static char *handle_cli_iax2_set_mtu(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int mtuv;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 set mtu";
+ e->usage =
+ "Usage: iax2 set mtu <value>\n"
+ " Set the system-wide IAX IP mtu to <value> bytes net or\n"
+ " zero to disable. Disabling means that the operating system\n"
+ " must handle fragmentation of UDP packets when the IAX2 trunk\n"
+ " packet exceeds the UDP payload size. This is substantially\n"
+ " below the IP mtu. Try 1240 on ethernets. Must be 172 or\n"
+ " greater for G.711 samples.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+ if (strncasecmp(a->argv[3], "default", strlen(a->argv[3])) == 0)
+ mtuv = MAX_TRUNK_MTU;
+ else
+ mtuv = atoi(a->argv[3]);
+
+ if (mtuv == 0) {
+ ast_cli(a->fd, "Trunk MTU control disabled (mtu was %d)\n", global_max_trunk_mtu);
+ global_max_trunk_mtu = 0;
+ return CLI_SUCCESS;
+ }
+ if (mtuv < 172 || mtuv > 4000) {
+ ast_cli(a->fd, "Trunk MTU must be between 172 and 4000\n");
+ return CLI_SHOWUSAGE;
+ }
+ ast_cli(a->fd, "Trunk MTU changed from %d to %d\n", global_max_trunk_mtu, mtuv);
+ global_max_trunk_mtu = mtuv;
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_show_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct iax2_dpcache *dp = NULL;
+ char tmp[1024], *pc = NULL;
+ int s, x, y;
+ struct timeval tv = ast_tvnow();
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show cache";
+ e->usage =
+ "Usage: iax2 show cache\n"
+ " Display currently cached IAX Dialplan results.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ AST_LIST_LOCK(&dpcache);
+
+ ast_cli(a->fd, "%-20.20s %-12.12s %-9.9s %-8.8s %s\n", "Peer/Context", "Exten", "Exp.", "Wait.", "Flags");
+
+ AST_LIST_TRAVERSE(&dpcache, dp, cache_list) {
+ s = dp->expiry.tv_sec - tv.tv_sec;
+ tmp[0] = '\0';
+ if (dp->flags & CACHE_FLAG_EXISTS)
+ strncat(tmp, "EXISTS|", sizeof(tmp) - strlen(tmp) - 1);
+ if (dp->flags & CACHE_FLAG_NONEXISTENT)
+ strncat(tmp, "NONEXISTENT|", sizeof(tmp) - strlen(tmp) - 1);
+ if (dp->flags & CACHE_FLAG_CANEXIST)
+ strncat(tmp, "CANEXIST|", sizeof(tmp) - strlen(tmp) - 1);
+ if (dp->flags & CACHE_FLAG_PENDING)
+ strncat(tmp, "PENDING|", sizeof(tmp) - strlen(tmp) - 1);
+ if (dp->flags & CACHE_FLAG_TIMEOUT)
+ strncat(tmp, "TIMEOUT|", sizeof(tmp) - strlen(tmp) - 1);
+ if (dp->flags & CACHE_FLAG_TRANSMITTED)
+ strncat(tmp, "TRANSMITTED|", sizeof(tmp) - strlen(tmp) - 1);
+ if (dp->flags & CACHE_FLAG_MATCHMORE)
+ strncat(tmp, "MATCHMORE|", sizeof(tmp) - strlen(tmp) - 1);
+ if (dp->flags & CACHE_FLAG_UNKNOWN)
+ strncat(tmp, "UNKNOWN|", sizeof(tmp) - strlen(tmp) - 1);
+ /* Trim trailing pipe */
+ if (!ast_strlen_zero(tmp))
+ tmp[strlen(tmp) - 1] = '\0';
+ else
+ ast_copy_string(tmp, "(none)", sizeof(tmp));
+ y = 0;
+ pc = strchr(dp->peercontext, '@');
+ if (!pc)
+ pc = dp->peercontext;
+ else
+ pc++;
+ for (x = 0; x < sizeof(dp->waiters) / sizeof(dp->waiters[0]); x++)
+ if (dp->waiters[x] > -1)
+ y++;
+ if (s > 0)
+ ast_cli(a->fd, "%-20.20s %-12.12s %-9d %-8d %s\n", pc, dp->exten, s, y, tmp);
+ else
+ ast_cli(a->fd, "%-20.20s %-12.12s %-9.9s %-8d %s\n", pc, dp->exten, "(expired)", y, tmp);
+ }
+
+ AST_LIST_LOCK(&dpcache);
+
+ return CLI_SUCCESS;
+}
+
+static unsigned int calc_rxstamp(struct chan_iax2_pvt *p, unsigned int offset);
+
+static void unwrap_timestamp(struct iax_frame *fr)
+{
+ int x;
+
+ if ( (fr->ts & 0xFFFF0000) == (iaxs[fr->callno]->last & 0xFFFF0000) ) {
+ x = fr->ts - iaxs[fr->callno]->last;
+ if (x < -50000) {
+ /* Sudden big jump backwards in timestamp:
+ What likely happened here is that miniframe timestamp has circled but we haven't
+ gotten the update from the main packet. We'll just pretend that we did, and
+ update the timestamp appropriately. */
+ fr->ts = ( (iaxs[fr->callno]->last & 0xFFFF0000) + 0x10000) | (fr->ts & 0xFFFF);
+ if (iaxdebug)
+ ast_debug(1, "schedule_delivery: pushed forward timestamp\n");
+ }
+ if (x > 50000) {
+ /* Sudden apparent big jump forwards in timestamp:
+ What's likely happened is this is an old miniframe belonging to the previous
+ top-16-bit timestamp that has turned up out of order.
+ Adjust the timestamp appropriately. */
+ fr->ts = ( (iaxs[fr->callno]->last & 0xFFFF0000) - 0x10000) | (fr->ts & 0xFFFF);
+ if (iaxdebug)
+ ast_debug(1, "schedule_delivery: pushed back timestamp\n");
+ }
+ }
+}
+
+static int get_from_jb(const void *p);
+
+static void update_jbsched(struct chan_iax2_pvt *pvt)
+{
+ int when;
+
+ when = ast_tvdiff_ms(ast_tvnow(), pvt->rxcore);
+
+ when = jb_next(pvt->jb) - when;
+
+ if(when <= 0) {
+ /* XXX should really just empty until when > 0.. */
+ when = 1;
+ }
+
+ pvt->jbid = iax2_sched_replace(pvt->jbid, sched, when, get_from_jb,
+ CALLNO_TO_PTR(pvt->callno));
+}
+
+static void __get_from_jb(const void *p)
+{
+ int callno = PTR_TO_CALLNO(p);
+ struct chan_iax2_pvt *pvt = NULL;
+ struct iax_frame *fr;
+ jb_frame frame;
+ int ret;
+ long now;
+ long next;
+ struct timeval tv = ast_tvnow();
+
+ /* Make sure we have a valid private structure before going on */
+ ast_mutex_lock(&iaxsl[callno]);
+ pvt = iaxs[callno];
+ if (!pvt) {
+ /* No go! */
+ ast_mutex_unlock(&iaxsl[callno]);
+ return;
+ }
+
+ pvt->jbid = -1;
+
+ /* round up a millisecond since ast_sched_runq does; */
+ /* prevents us from spinning while waiting for our now */
+ /* to catch up with runq's now */
+ tv.tv_usec += 1000;
+
+ now = ast_tvdiff_ms(tv, pvt->rxcore);
+
+ if(now >= (next = jb_next(pvt->jb))) {
+ ret = jb_get(pvt->jb,&frame,now,ast_codec_interp_len(pvt->voiceformat));
+ switch(ret) {
+ case JB_OK:
+ fr = frame.data;
+ __do_deliver(fr);
+ /* __do_deliver() can cause the call to disappear */
+ pvt = iaxs[callno];
+ break;
+ case JB_INTERP:
+ {
+ struct ast_frame af = { 0, };
+
+ /* create an interpolation frame */
+ af.frametype = AST_FRAME_VOICE;
+ af.subclass = pvt->voiceformat;
+ af.samples = frame.ms * 8;
+ af.src = "IAX2 JB interpolation";
+ af.delivery = ast_tvadd(pvt->rxcore, ast_samp2tv(next, 1000));
+ af.offset = AST_FRIENDLY_OFFSET;
+
+ /* queue the frame: For consistency, we would call __do_deliver here, but __do_deliver wants an iax_frame,
+ * which we'd need to malloc, and then it would free it. That seems like a drag */
+ if (!ast_test_flag(iaxs[callno], IAX_ALREADYGONE)) {
+ iax2_queue_frame(callno, &af);
+ /* iax2_queue_frame() could cause the call to disappear */
+ pvt = iaxs[callno];
+ }
+ }
+ break;
+ case JB_DROP:
+ iax2_frame_free(frame.data);
+ break;
+ case JB_NOFRAME:
+ case JB_EMPTY:
+ /* do nothing */
+ break;
+ default:
+ /* shouldn't happen */
+ break;
+ }
+ }
+ if (pvt)
+ update_jbsched(pvt);
+ ast_mutex_unlock(&iaxsl[callno]);
+}
+
+static int get_from_jb(const void *data)
+{
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__get_from_jb, data))
+#endif
+ __get_from_jb(data);
+ return 0;
+}
+
+/*!
+ * \note This function assumes fr->callno is locked
+ *
+ * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno]
+ * was valid before calling it, it may no longer be valid after calling it.
+ */
+static int schedule_delivery(struct iax_frame *fr, int updatehistory, int fromtrunk, unsigned int *tsout)
+{
+ int type, len;
+ int ret;
+ int needfree = 0;
+
+ /* Attempt to recover wrapped timestamps */
+ unwrap_timestamp(fr);
+
+ /* delivery time is sender's sent timestamp converted back into absolute time according to our clock */
+ if ( !fromtrunk && !ast_tvzero(iaxs[fr->callno]->rxcore))
+ fr->af.delivery = ast_tvadd(iaxs[fr->callno]->rxcore, ast_samp2tv(fr->ts, 1000));
+ else {
+#if 0
+ ast_debug(1, "schedule_delivery: set delivery to 0 as we don't have an rxcore yet, or frame is from trunk.\n");
+#endif
+ fr->af.delivery = ast_tv(0,0);
+ }
+
+ type = JB_TYPE_CONTROL;
+ len = 0;
+
+ if(fr->af.frametype == AST_FRAME_VOICE) {
+ type = JB_TYPE_VOICE;
+ len = ast_codec_get_samples(&fr->af) / 8;
+ } else if(fr->af.frametype == AST_FRAME_CNG) {
+ type = JB_TYPE_SILENCE;
+ }
+
+ if ( (!ast_test_flag(iaxs[fr->callno], IAX_USEJITTERBUF)) ) {
+ if (tsout)
+ *tsout = fr->ts;
+ __do_deliver(fr);
+ return -1;
+ }
+
+ /* if the user hasn't requested we force the use of the jitterbuffer, and we're bridged to
+ * a channel that can accept jitter, then flush and suspend the jb, and send this frame straight through */
+ if( (!ast_test_flag(iaxs[fr->callno], IAX_FORCEJITTERBUF)) &&
+ iaxs[fr->callno]->owner && ast_bridged_channel(iaxs[fr->callno]->owner) &&
+ (ast_bridged_channel(iaxs[fr->callno]->owner)->tech->properties & AST_CHAN_TP_WANTSJITTER)) {
+ jb_frame frame;
+
+ /* deliver any frames in the jb */
+ while (jb_getall(iaxs[fr->callno]->jb, &frame) == JB_OK) {
+ __do_deliver(frame.data);
+ /* __do_deliver() can make the call disappear */
+ if (!iaxs[fr->callno])
+ return -1;
+ }
+
+ jb_reset(iaxs[fr->callno]->jb);
+
+ if (iaxs[fr->callno]->jbid > -1)
+ ast_sched_del(sched, iaxs[fr->callno]->jbid);
+
+ iaxs[fr->callno]->jbid = -1;
+
+ /* deliver this frame now */
+ if (tsout)
+ *tsout = fr->ts;
+ __do_deliver(fr);
+ return -1;
+ }
+
+ /* insert into jitterbuffer */
+ /* TODO: Perhaps we could act immediately if it's not droppable and late */
+ ret = jb_put(iaxs[fr->callno]->jb, fr, type, len, fr->ts,
+ calc_rxstamp(iaxs[fr->callno],fr->ts));
+ if (ret == JB_DROP) {
+ needfree++;
+ } else if (ret == JB_SCHED) {
+ update_jbsched(iaxs[fr->callno]);
+ }
+ if (tsout)
+ *tsout = fr->ts;
+ if (needfree) {
+ /* Free our iax frame */
+ iax2_frame_free(fr);
+ return -1;
+ }
+ return 0;
+}
+
+static int iax2_transmit(struct iax_frame *fr)
+{
+ /* Lock the queue and place this packet at the end */
+ /* By setting this to 0, the network thread will send it for us, and
+ queue retransmission if necessary */
+ fr->sentyet = 0;
+ AST_LIST_LOCK(&frame_queue);
+ AST_LIST_INSERT_TAIL(&frame_queue, fr, list);
+ AST_LIST_UNLOCK(&frame_queue);
+ /* Wake up the network and scheduler thread */
+ if (netthreadid != AST_PTHREADT_NULL)
+ pthread_kill(netthreadid, SIGURG);
+ signal_condition(&sched_lock, &sched_cond);
+ return 0;
+}
+
+
+
+static int iax2_digit_begin(struct ast_channel *c, char digit)
+{
+ return send_command_locked(PTR_TO_CALLNO(c->tech_pvt), AST_FRAME_DTMF_BEGIN, digit, 0, NULL, 0, -1);
+}
+
+static int iax2_digit_end(struct ast_channel *c, char digit, unsigned int duration)
+{
+ return send_command_locked(PTR_TO_CALLNO(c->tech_pvt), AST_FRAME_DTMF_END, digit, 0, NULL, 0, -1);
+}
+
+static int iax2_sendtext(struct ast_channel *c, const char *text)
+{
+
+ return send_command_locked(PTR_TO_CALLNO(c->tech_pvt), AST_FRAME_TEXT,
+ 0, 0, (unsigned char *)text, strlen(text) + 1, -1);
+}
+
+static int iax2_sendimage(struct ast_channel *c, struct ast_frame *img)
+{
+ return send_command_locked(PTR_TO_CALLNO(c->tech_pvt), AST_FRAME_IMAGE, img->subclass, 0, img->data, img->datalen, -1);
+}
+
+static int iax2_sendhtml(struct ast_channel *c, int subclass, const char *data, int datalen)
+{
+ return send_command_locked(PTR_TO_CALLNO(c->tech_pvt), AST_FRAME_HTML, subclass, 0, (unsigned char *)data, datalen, -1);
+}
+
+static int iax2_fixup(struct ast_channel *oldchannel, struct ast_channel *newchan)
+{
+ unsigned short callno = PTR_TO_CALLNO(newchan->tech_pvt);
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno])
+ iaxs[callno]->owner = newchan;
+ else
+ ast_log(LOG_WARNING, "Uh, this isn't a good sign...\n");
+ ast_mutex_unlock(&iaxsl[callno]);
+ return 0;
+}
+
+/*!
+ * \note This function calls reg_source_db -> iax2_poke_peer -> find_callno,
+ * so do not call this with a pvt lock held.
+ */
+static struct iax2_peer *realtime_peer(const char *peername, struct sockaddr_in *sin)
+{
+ struct ast_variable *var = NULL;
+ struct ast_variable *tmp;
+ struct iax2_peer *peer=NULL;
+ time_t regseconds = 0, nowtime;
+ int dynamic=0;
+
+ if (peername) {
+ var = ast_load_realtime("iaxpeers", "name", peername, "host", "dynamic", NULL);
+ if (!var && sin)
+ var = ast_load_realtime("iaxpeers", "name", peername, "host", ast_inet_ntoa(sin->sin_addr), NULL);
+ } else if (sin) {
+ char porta[25];
+ sprintf(porta, "%d", ntohs(sin->sin_port));
+ var = ast_load_realtime("iaxpeers", "ipaddr", ast_inet_ntoa(sin->sin_addr), "port", porta, NULL);
+ if (var) {
+ /* We'll need the peer name in order to build the structure! */
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "name"))
+ peername = tmp->value;
+ }
+ }
+ }
+ if (!var && peername) { /* Last ditch effort */
+ var = ast_load_realtime("iaxpeers", "name", peername, NULL);
+ /*!\note
+ * If this one loaded something, then we need to ensure that the host
+ * field matched. The only reason why we can't have this as a criteria
+ * is because we only have the IP address and the host field might be
+ * set as a name (and the reverse PTR might not match).
+ */
+ if (var && sin) {
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "host")) {
+ struct in_addr sin2 = { 0, };
+ struct ast_dnsmgr_entry *dnsmgr = NULL;
+ if ((ast_dnsmgr_lookup(tmp->value, &sin2, &dnsmgr) < 0) || (memcmp(&sin2, &sin->sin_addr, sizeof(sin2)) != 0)) {
+ /* No match */
+ ast_variables_destroy(var);
+ var = NULL;
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (!var)
+ return NULL;
+
+ peer = build_peer(peername, var, NULL, ast_test_flag((&globalflags), IAX_RTCACHEFRIENDS) ? 0 : 1);
+
+ if (!peer) {
+ ast_variables_destroy(var);
+ return NULL;
+ }
+
+ for (tmp = var; tmp; tmp = tmp->next) {
+ /* Make sure it's not a user only... */
+ if (!strcasecmp(tmp->name, "type")) {
+ if (strcasecmp(tmp->value, "friend") &&
+ strcasecmp(tmp->value, "peer")) {
+ /* Whoops, we weren't supposed to exist! */
+ peer = peer_unref(peer);
+ break;
+ }
+ } else if (!strcasecmp(tmp->name, "regseconds")) {
+ ast_get_time_t(tmp->value, &regseconds, 0, NULL);
+ } else if (!strcasecmp(tmp->name, "ipaddr")) {
+ inet_aton(tmp->value, &(peer->addr.sin_addr));
+ } else if (!strcasecmp(tmp->name, "port")) {
+ peer->addr.sin_port = htons(atoi(tmp->value));
+ } else if (!strcasecmp(tmp->name, "host")) {
+ if (!strcasecmp(tmp->value, "dynamic"))
+ dynamic = 1;
+ }
+ }
+
+ ast_variables_destroy(var);
+
+ if (!peer)
+ return NULL;
+
+ if (ast_test_flag((&globalflags), IAX_RTCACHEFRIENDS)) {
+ ast_copy_flags(peer, &globalflags, IAX_RTAUTOCLEAR|IAX_RTCACHEFRIENDS);
+ if (ast_test_flag(peer, IAX_RTAUTOCLEAR)) {
+ if (peer->expire > -1) {
+ if (!ast_sched_del(sched, peer->expire)) {
+ peer->expire = -1;
+ peer_unref(peer);
+ }
+ }
+ peer->expire = iax2_sched_add(sched, (global_rtautoclear) * 1000, expire_registry, peer_ref(peer));
+ if (peer->expire == -1)
+ peer_unref(peer);
+ }
+ ao2_link(peers, peer);
+ if (ast_test_flag(peer, IAX_DYNAMIC))
+ reg_source_db(peer);
+ } else {
+ ast_set_flag(peer, IAX_TEMPONLY);
+ }
+
+ if (!ast_test_flag(&globalflags, IAX_RTIGNOREREGEXPIRE) && dynamic) {
+ time(&nowtime);
+ if ((nowtime - regseconds) > IAX_DEFAULT_REG_EXPIRE) {
+ memset(&peer->addr, 0, sizeof(peer->addr));
+ realtime_update_peer(peer->name, &peer->addr, 0);
+ ast_debug(1, "realtime_peer: Bah, '%s' is expired (%d/%d/%d)!\n",
+ peername, (int)(nowtime - regseconds), (int)regseconds, (int)nowtime);
+ }
+ else {
+ ast_debug(1, "realtime_peer: Registration for '%s' still active (%d/%d/%d)!\n",
+ peername, (int)(nowtime - regseconds), (int)regseconds, (int)nowtime);
+ }
+ }
+
+ return peer;
+}
+
+static struct iax2_user *realtime_user(const char *username, struct sockaddr_in *sin)
+{
+ struct ast_variable *var;
+ struct ast_variable *tmp;
+ struct iax2_user *user=NULL;
+
+ var = ast_load_realtime("iaxusers", "name", username, "host", "dynamic", NULL);
+ if (!var)
+ var = ast_load_realtime("iaxusers", "name", username, "host", ast_inet_ntoa(sin->sin_addr), NULL);
+ if (!var && sin) {
+ char porta[6];
+ snprintf(porta, sizeof(porta), "%d", ntohs(sin->sin_port));
+ var = ast_load_realtime("iaxusers", "name", username, "ipaddr", ast_inet_ntoa(sin->sin_addr), "port", porta, NULL);
+ if (!var)
+ var = ast_load_realtime("iaxusers", "ipaddr", ast_inet_ntoa(sin->sin_addr), "port", porta, NULL);
+ }
+ if (!var) { /* Last ditch effort */
+ var = ast_load_realtime("iaxusers", "name", username, NULL);
+ /*!\note
+ * If this one loaded something, then we need to ensure that the host
+ * field matched. The only reason why we can't have this as a criteria
+ * is because we only have the IP address and the host field might be
+ * set as a name (and the reverse PTR might not match).
+ */
+ if (var) {
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "host")) {
+ struct in_addr sin2 = { 0, };
+ struct ast_dnsmgr_entry *dnsmgr = NULL;
+ if ((ast_dnsmgr_lookup(tmp->value, &sin2, &dnsmgr) < 0) || (memcmp(&sin2, &sin->sin_addr, sizeof(sin2)) != 0)) {
+ /* No match */
+ ast_variables_destroy(var);
+ var = NULL;
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (!var)
+ return NULL;
+
+ tmp = var;
+ while(tmp) {
+ /* Make sure it's not a peer only... */
+ if (!strcasecmp(tmp->name, "type")) {
+ if (strcasecmp(tmp->value, "friend") &&
+ strcasecmp(tmp->value, "user")) {
+ return NULL;
+ }
+ }
+ tmp = tmp->next;
+ }
+
+ user = build_user(username, var, NULL, !ast_test_flag((&globalflags), IAX_RTCACHEFRIENDS));
+
+ ast_variables_destroy(var);
+
+ if (!user)
+ return NULL;
+
+ if (ast_test_flag((&globalflags), IAX_RTCACHEFRIENDS)) {
+ ast_set_flag(user, IAX_RTCACHEFRIENDS);
+ ao2_link(users, user);
+ } else {
+ ast_set_flag(user, IAX_TEMPONLY);
+ }
+
+ return user;
+}
+
+static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, time_t regtime)
+{
+ char port[10];
+ char regseconds[20];
+
+ snprintf(regseconds, sizeof(regseconds), "%d", (int)regtime);
+ snprintf(port, sizeof(port), "%d", ntohs(sin->sin_port));
+ ast_update_realtime("iaxpeers", "name", peername,
+ "ipaddr", ast_inet_ntoa(sin->sin_addr), "port", port,
+ "regseconds", regseconds, NULL);
+}
+
+struct create_addr_info {
+ int capability;
+ unsigned int flags;
+ int maxtime;
+ int encmethods;
+ int found;
+ int sockfd;
+ int adsi;
+ char username[80];
+ char secret[80];
+ char outkey[80];
+ char timezone[80];
+ char prefs[32];
+ char context[AST_MAX_CONTEXT];
+ char peercontext[AST_MAX_CONTEXT];
+ char mohinterpret[MAX_MUSICCLASS];
+ char mohsuggest[MAX_MUSICCLASS];
+};
+
+static int create_addr(const char *peername, struct ast_channel *c, struct sockaddr_in *sin, struct create_addr_info *cai)
+{
+ struct iax2_peer *peer;
+ int res = -1;
+ struct ast_codec_pref ourprefs;
+
+ ast_clear_flag(cai, IAX_SENDANI | IAX_TRUNK);
+ cai->sockfd = defaultsockfd;
+ cai->maxtime = 0;
+ sin->sin_family = AF_INET;
+
+ if (!(peer = find_peer(peername, 1))) {
+ cai->found = 0;
+ if (ast_get_ip_or_srv(sin, peername, srvlookup ? "_iax._udp" : NULL)) {
+ ast_log(LOG_WARNING, "No such host: %s\n", peername);
+ return -1;
+ }
+ sin->sin_port = htons(IAX_DEFAULT_PORTNO);
+ /* use global iax prefs for unknown peer/user */
+ /* But move the calling channel's native codec to the top of the preference list */
+ memcpy(&ourprefs, &prefs, sizeof(ourprefs));
+ if (c)
+ ast_codec_pref_prepend(&ourprefs, c->nativeformats, 1);
+ ast_codec_pref_convert(&ourprefs, cai->prefs, sizeof(cai->prefs), 1);
+ return 0;
+ }
+
+ cai->found = 1;
+
+ /* if the peer has no address (current or default), return failure */
+ if (!(peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr))
+ goto return_unref;
+
+ /* if the peer is being monitored and is currently unreachable, return failure */
+ if (peer->maxms && ((peer->lastms > peer->maxms) || (peer->lastms < 0)))
+ goto return_unref;
+
+ ast_copy_flags(cai, peer, IAX_SENDANI | IAX_TRUNK | IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF);
+ cai->maxtime = peer->maxms;
+ cai->capability = peer->capability;
+ cai->encmethods = peer->encmethods;
+ cai->sockfd = peer->sockfd;
+ cai->adsi = peer->adsi;
+ memcpy(&ourprefs, &peer->prefs, sizeof(ourprefs));
+ /* Move the calling channel's native codec to the top of the preference list */
+ if (c) {
+ ast_log(LOG_DEBUG, "prepending %x to prefs\n", c->nativeformats);
+ ast_codec_pref_prepend(&ourprefs, c->nativeformats, 1);
+ }
+ ast_codec_pref_convert(&ourprefs, cai->prefs, sizeof(cai->prefs), 1);
+ ast_copy_string(cai->context, peer->context, sizeof(cai->context));
+ ast_copy_string(cai->peercontext, peer->peercontext, sizeof(cai->peercontext));
+ ast_copy_string(cai->username, peer->username, sizeof(cai->username));
+ ast_copy_string(cai->timezone, peer->zonetag, sizeof(cai->timezone));
+ ast_copy_string(cai->outkey, peer->outkey, sizeof(cai->outkey));
+ ast_copy_string(cai->mohinterpret, peer->mohinterpret, sizeof(cai->mohinterpret));
+ ast_copy_string(cai->mohsuggest, peer->mohsuggest, sizeof(cai->mohsuggest));
+ if (ast_strlen_zero(peer->dbsecret)) {
+ ast_copy_string(cai->secret, peer->secret, sizeof(cai->secret));
+ } else {
+ char *family;
+ char *key = NULL;
+
+ family = ast_strdupa(peer->dbsecret);
+ key = strchr(family, '/');
+ if (key)
+ *key++ = '\0';
+ if (!key || ast_db_get(family, key, cai->secret, sizeof(cai->secret))) {
+ ast_log(LOG_WARNING, "Unable to retrieve database password for family/key '%s'!\n", peer->dbsecret);
+ goto return_unref;
+ }
+ }
+
+ if (peer->addr.sin_addr.s_addr) {
+ sin->sin_addr = peer->addr.sin_addr;
+ sin->sin_port = peer->addr.sin_port;
+ } else {
+ sin->sin_addr = peer->defaddr.sin_addr;
+ sin->sin_port = peer->defaddr.sin_port;
+ }
+
+ res = 0;
+
+return_unref:
+ peer_unref(peer);
+
+ return res;
+}
+
+static void __auto_congest(const void *nothing)
+{
+ int callno = PTR_TO_CALLNO(nothing);
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_CONGESTION };
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno]) {
+ iaxs[callno]->initid = -1;
+ iax2_queue_frame(callno, &f);
+ ast_log(LOG_NOTICE, "Auto-congesting call due to slow response\n");
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+}
+
+static int auto_congest(const void *data)
+{
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__auto_congest, data))
+#endif
+ __auto_congest(data);
+ return 0;
+}
+
+static unsigned int iax2_datetime(const char *tz)
+{
+ struct timeval t = ast_tvnow();
+ struct ast_tm tm;
+ unsigned int tmp;
+ ast_localtime(&t, &tm, ast_strlen_zero(tz) ? NULL : tz);
+ tmp = (tm.tm_sec >> 1) & 0x1f; /* 5 bits of seconds */
+ tmp |= (tm.tm_min & 0x3f) << 5; /* 6 bits of minutes */
+ tmp |= (tm.tm_hour & 0x1f) << 11; /* 5 bits of hours */
+ tmp |= (tm.tm_mday & 0x1f) << 16; /* 5 bits of day of month */
+ tmp |= ((tm.tm_mon + 1) & 0xf) << 21; /* 4 bits of month */
+ tmp |= ((tm.tm_year - 100) & 0x7f) << 25; /* 7 bits of year */
+ return tmp;
+}
+
+struct parsed_dial_string {
+ char *username;
+ char *password;
+ char *key;
+ char *peer;
+ char *port;
+ char *exten;
+ char *context;
+ char *options;
+};
+
+/*!
+ * \brief Parses an IAX dial string into its component parts.
+ * \param data the string to be parsed
+ * \param pds pointer to a \c struct \c parsed_dial_string to be filled in
+ * \return nothing
+ *
+ * This function parses the string and fills the structure
+ * with pointers to its component parts. The input string
+ * will be modified.
+ *
+ * \note This function supports both plaintext passwords and RSA
+ * key names; if the password string is formatted as '[keyname]',
+ * then the keyname will be placed into the key field, and the
+ * password field will be set to NULL.
+ *
+ * \note The dial string format is:
+ * [username[:password]@]peer[:port][/exten[@@context]][/options]
+ */
+static void parse_dial_string(char *data, struct parsed_dial_string *pds)
+{
+ if (ast_strlen_zero(data))
+ return;
+
+ pds->peer = strsep(&data, "/");
+ pds->exten = strsep(&data, "/");
+ pds->options = data;
+
+ if (pds->exten) {
+ data = pds->exten;
+ pds->exten = strsep(&data, "@");
+ pds->context = data;
+ }
+
+ if (strchr(pds->peer, '@')) {
+ data = pds->peer;
+ pds->username = strsep(&data, "@");
+ pds->peer = data;
+ }
+
+ if (pds->username) {
+ data = pds->username;
+ pds->username = strsep(&data, ":");
+ pds->password = data;
+ }
+
+ data = pds->peer;
+ pds->peer = strsep(&data, ":");
+ pds->port = data;
+
+ /* check for a key name wrapped in [] in the secret position, if found,
+ move it to the key field instead
+ */
+ if (pds->password && (pds->password[0] == '[')) {
+ pds->key = ast_strip_quoted(pds->password, "[", "]");
+ pds->password = NULL;
+ }
+}
+
+static int iax2_call(struct ast_channel *c, char *dest, int timeout)
+{
+ struct sockaddr_in sin;
+ char *l=NULL, *n=NULL, *tmpstr;
+ struct iax_ie_data ied;
+ char *defaultrdest = "s";
+ unsigned short callno = PTR_TO_CALLNO(c->tech_pvt);
+ struct parsed_dial_string pds;
+ struct create_addr_info cai;
+ struct ast_var_t *var;
+ struct ast_datastore *variablestore = ast_channel_datastore_find(c, &iax2_variable_datastore_info, NULL);
+ const char* osp_token_ptr;
+ unsigned int osp_token_length;
+ unsigned char osp_block_index;
+ unsigned int osp_block_length;
+ unsigned char osp_buffer[256];
+
+ if ((c->_state != AST_STATE_DOWN) && (c->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "Channel is already in use (%s)?\n", c->name);
+ return -1;
+ }
+
+ memset(&cai, 0, sizeof(cai));
+ cai.encmethods = iax2_encryption;
+
+ memset(&pds, 0, sizeof(pds));
+ tmpstr = ast_strdupa(dest);
+ parse_dial_string(tmpstr, &pds);
+
+ if (!pds.exten)
+ pds.exten = defaultrdest;
+
+ if (create_addr(pds.peer, c, &sin, &cai)) {
+ ast_log(LOG_WARNING, "No address associated with '%s'\n", pds.peer);
+ return -1;
+ }
+
+ if (!pds.username && !ast_strlen_zero(cai.username))
+ pds.username = cai.username;
+ if (!pds.password && !ast_strlen_zero(cai.secret))
+ pds.password = cai.secret;
+ if (!pds.key && !ast_strlen_zero(cai.outkey))
+ pds.key = cai.outkey;
+ if (!pds.context && !ast_strlen_zero(cai.peercontext))
+ pds.context = cai.peercontext;
+
+ /* Keep track of the context for outgoing calls too */
+ ast_copy_string(c->context, cai.context, sizeof(c->context));
+
+ if (pds.port)
+ sin.sin_port = htons(atoi(pds.port));
+
+ l = c->cid.cid_num;
+ n = c->cid.cid_name;
+
+ /* Now build request */
+ memset(&ied, 0, sizeof(ied));
+
+ /* On new call, first IE MUST be IAX version of caller */
+ iax_ie_append_short(&ied, IAX_IE_VERSION, IAX_PROTO_VERSION);
+ iax_ie_append_str(&ied, IAX_IE_CALLED_NUMBER, pds.exten);
+ if (pds.options && strchr(pds.options, 'a')) {
+ /* Request auto answer */
+ iax_ie_append(&ied, IAX_IE_AUTOANSWER);
+ }
+
+ iax_ie_append_str(&ied, IAX_IE_CODEC_PREFS, cai.prefs);
+
+ if (l) {
+ iax_ie_append_str(&ied, IAX_IE_CALLING_NUMBER, l);
+ iax_ie_append_byte(&ied, IAX_IE_CALLINGPRES, c->cid.cid_pres);
+ } else {
+ if (n)
+ iax_ie_append_byte(&ied, IAX_IE_CALLINGPRES, c->cid.cid_pres);
+ else
+ iax_ie_append_byte(&ied, IAX_IE_CALLINGPRES, AST_PRES_NUMBER_NOT_AVAILABLE);
+ }
+
+ iax_ie_append_byte(&ied, IAX_IE_CALLINGTON, c->cid.cid_ton);
+ iax_ie_append_short(&ied, IAX_IE_CALLINGTNS, c->cid.cid_tns);
+
+ if (n)
+ iax_ie_append_str(&ied, IAX_IE_CALLING_NAME, n);
+ if (ast_test_flag(iaxs[callno], IAX_SENDANI) && c->cid.cid_ani)
+ iax_ie_append_str(&ied, IAX_IE_CALLING_ANI, c->cid.cid_ani);
+
+ if (!ast_strlen_zero(c->language))
+ iax_ie_append_str(&ied, IAX_IE_LANGUAGE, c->language);
+ if (!ast_strlen_zero(c->cid.cid_dnid))
+ iax_ie_append_str(&ied, IAX_IE_DNID, c->cid.cid_dnid);
+ if (!ast_strlen_zero(c->cid.cid_rdnis))
+ iax_ie_append_str(&ied, IAX_IE_RDNIS, c->cid.cid_rdnis);
+
+ if (pds.context)
+ iax_ie_append_str(&ied, IAX_IE_CALLED_CONTEXT, pds.context);
+
+ if (pds.username)
+ iax_ie_append_str(&ied, IAX_IE_USERNAME, pds.username);
+
+ if (cai.encmethods)
+ iax_ie_append_short(&ied, IAX_IE_ENCRYPTION, cai.encmethods);
+
+ ast_mutex_lock(&iaxsl[callno]);
+
+ if (!ast_strlen_zero(c->context))
+ ast_string_field_set(iaxs[callno], context, c->context);
+
+ if (pds.username)
+ ast_string_field_set(iaxs[callno], username, pds.username);
+
+ iaxs[callno]->encmethods = cai.encmethods;
+
+ iaxs[callno]->adsi = cai.adsi;
+
+ ast_string_field_set(iaxs[callno], mohinterpret, cai.mohinterpret);
+ ast_string_field_set(iaxs[callno], mohsuggest, cai.mohsuggest);
+
+ if (pds.key)
+ ast_string_field_set(iaxs[callno], outkey, pds.key);
+ if (pds.password)
+ ast_string_field_set(iaxs[callno], secret, pds.password);
+
+ iax_ie_append_int(&ied, IAX_IE_FORMAT, c->nativeformats);
+ iax_ie_append_int(&ied, IAX_IE_CAPABILITY, iaxs[callno]->capability);
+ iax_ie_append_short(&ied, IAX_IE_ADSICPE, c->adsicpe);
+ iax_ie_append_int(&ied, IAX_IE_DATETIME, iax2_datetime(cai.timezone));
+
+ if (iaxs[callno]->maxtime) {
+ /* Initialize pingtime and auto-congest time */
+ iaxs[callno]->pingtime = iaxs[callno]->maxtime / 2;
+ iaxs[callno]->initid = iax2_sched_add(sched, iaxs[callno]->maxtime * 2, auto_congest, CALLNO_TO_PTR(callno));
+ } else if (autokill) {
+ iaxs[callno]->pingtime = autokill / 2;
+ iaxs[callno]->initid = iax2_sched_add(sched, autokill * 2, auto_congest, CALLNO_TO_PTR(callno));
+ }
+
+ /* Check if there is an OSP token set by IAXCHANINFO function */
+ osp_token_ptr = iaxs[callno]->osptoken;
+ if (!ast_strlen_zero(osp_token_ptr)) {
+ if ((osp_token_length = strlen(osp_token_ptr)) <= IAX_MAX_OSPTOKEN_SIZE) {
+ osp_block_index = 0;
+ while (osp_token_length > 0) {
+ osp_block_length = IAX_MAX_OSPBLOCK_SIZE < osp_token_length ? IAX_MAX_OSPBLOCK_SIZE : osp_token_length;
+ osp_buffer[0] = osp_block_index;
+ memcpy(osp_buffer + 1, osp_token_ptr, osp_block_length);
+ iax_ie_append_raw(&ied, IAX_IE_OSPTOKEN, osp_buffer, osp_block_length + 1);
+ osp_block_index++;
+ osp_token_ptr += osp_block_length;
+ osp_token_length -= osp_block_length;
+ }
+ } else
+ ast_log(LOG_WARNING, "OSP token is too long\n");
+ } else if (iaxdebug)
+ ast_debug(1, "OSP token is undefined\n");
+
+ /* send the command using the appropriate socket for this peer */
+ iaxs[callno]->sockfd = cai.sockfd;
+
+ /* Add remote vars */
+ if (variablestore) {
+ AST_LIST_HEAD(, ast_var_t) *variablelist = variablestore->data;
+ AST_LIST_LOCK(variablelist);
+ AST_LIST_TRAVERSE(variablelist, var, entries) {
+ char tmp[256];
+ int i;
+ /* Automatically divide the value up into sized chunks */
+ for (i = 0; i < strlen(ast_var_value(var)); i += 255 - (strlen(ast_var_name(var)) + 1)) {
+ snprintf(tmp, sizeof(tmp), "%s=%s", ast_var_name(var), ast_var_value(var) + i);
+ iax_ie_append_str(&ied, IAX_IE_VARIABLE, tmp);
+ }
+ }
+ AST_LIST_UNLOCK(variablelist);
+ }
+
+ /* Transmit the string in a "NEW" request */
+ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_NEW, 0, ied.buf, ied.pos, -1);
+
+ ast_mutex_unlock(&iaxsl[callno]);
+ ast_setstate(c, AST_STATE_RINGING);
+
+ return 0;
+}
+
+static int iax2_hangup(struct ast_channel *c)
+{
+ unsigned short callno = PTR_TO_CALLNO(c->tech_pvt);
+ int alreadygone;
+ struct iax_ie_data ied;
+ memset(&ied, 0, sizeof(ied));
+ ast_mutex_lock(&iaxsl[callno]);
+ if (callno && iaxs[callno]) {
+ ast_debug(1, "We're hanging up %s now...\n", c->name);
+ alreadygone = ast_test_flag(iaxs[callno], IAX_ALREADYGONE);
+ /* Send the hangup unless we have had a transmission error or are already gone */
+ iax_ie_append_byte(&ied, IAX_IE_CAUSECODE, (unsigned char)c->hangupcause);
+ if (!iaxs[callno]->error && !alreadygone) {
+ send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_HANGUP, 0, ied.buf, ied.pos, -1);
+ if (!iaxs[callno]) {
+ ast_mutex_unlock(&iaxsl[callno]);
+ return 0;
+ }
+ }
+ /* Explicitly predestroy it */
+ iax2_predestroy(callno);
+ /* If we were already gone to begin with, destroy us now */
+ if (alreadygone && iaxs[callno]) {
+ ast_debug(1, "Really destroying %s now...\n", c->name);
+ iax2_destroy(callno);
+ }
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+ ast_verb(3, "Hungup '%s'\n", c->name);
+ return 0;
+}
+
+static int iax2_setoption(struct ast_channel *c, int option, void *data, int datalen)
+{
+ struct ast_option_header *h;
+ int res;
+
+ switch (option) {
+ case AST_OPTION_TXGAIN:
+ case AST_OPTION_RXGAIN:
+ /* these two cannot be sent, because they require a result */
+ errno = ENOSYS;
+ return -1;
+ default:
+ if (!(h = ast_malloc(datalen + sizeof(*h))))
+ return -1;
+
+ h->flag = AST_OPTION_FLAG_REQUEST;
+ h->option = htons(option);
+ memcpy(h->data, data, datalen);
+ res = send_command_locked(PTR_TO_CALLNO(c->tech_pvt), AST_FRAME_CONTROL,
+ AST_CONTROL_OPTION, 0, (unsigned char *) h,
+ datalen + sizeof(*h), -1);
+ ast_free(h);
+ return res;
+ }
+}
+
+static struct ast_frame *iax2_read(struct ast_channel *c)
+{
+ ast_log(LOG_NOTICE, "I should never be called!\n");
+ return &ast_null_frame;
+}
+
+static int iax2_start_transfer(unsigned short callno0, unsigned short callno1, int mediaonly)
+{
+ int res;
+ struct iax_ie_data ied0;
+ struct iax_ie_data ied1;
+ unsigned int transferid = (unsigned int)ast_random();
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_addr(&ied0, IAX_IE_APPARENT_ADDR, &iaxs[callno1]->addr);
+ iax_ie_append_short(&ied0, IAX_IE_CALLNO, iaxs[callno1]->peercallno);
+ iax_ie_append_int(&ied0, IAX_IE_TRANSFERID, transferid);
+
+ memset(&ied1, 0, sizeof(ied1));
+ iax_ie_append_addr(&ied1, IAX_IE_APPARENT_ADDR, &iaxs[callno0]->addr);
+ iax_ie_append_short(&ied1, IAX_IE_CALLNO, iaxs[callno0]->peercallno);
+ iax_ie_append_int(&ied1, IAX_IE_TRANSFERID, transferid);
+
+ res = send_command(iaxs[callno0], AST_FRAME_IAX, IAX_COMMAND_TXREQ, 0, ied0.buf, ied0.pos, -1);
+ if (res)
+ return -1;
+ res = send_command(iaxs[callno1], AST_FRAME_IAX, IAX_COMMAND_TXREQ, 0, ied1.buf, ied1.pos, -1);
+ if (res)
+ return -1;
+ iaxs[callno0]->transferring = mediaonly ? TRANSFER_MBEGIN : TRANSFER_BEGIN;
+ iaxs[callno1]->transferring = mediaonly ? TRANSFER_MBEGIN : TRANSFER_BEGIN;
+ return 0;
+}
+
+static void lock_both(unsigned short callno0, unsigned short callno1)
+{
+ ast_mutex_lock(&iaxsl[callno0]);
+ while (ast_mutex_trylock(&iaxsl[callno1])) {
+ ast_mutex_unlock(&iaxsl[callno0]);
+ usleep(10);
+ ast_mutex_lock(&iaxsl[callno0]);
+ }
+}
+
+static void unlock_both(unsigned short callno0, unsigned short callno1)
+{
+ ast_mutex_unlock(&iaxsl[callno1]);
+ ast_mutex_unlock(&iaxsl[callno0]);
+}
+
+static enum ast_bridge_result iax2_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms)
+{
+ struct ast_channel *cs[3];
+ struct ast_channel *who, *other;
+ int to = -1;
+ int res = -1;
+ int transferstarted=0;
+ struct ast_frame *f;
+ unsigned short callno0 = PTR_TO_CALLNO(c0->tech_pvt);
+ unsigned short callno1 = PTR_TO_CALLNO(c1->tech_pvt);
+ struct timeval waittimer = {0, 0}, tv;
+
+ lock_both(callno0, callno1);
+ if (!iaxs[callno0] || !iaxs[callno1]) {
+ unlock_both(callno0, callno1);
+ return AST_BRIDGE_FAILED;
+ }
+ /* Put them in native bridge mode */
+ if (!flags & (AST_BRIDGE_DTMF_CHANNEL_0 | AST_BRIDGE_DTMF_CHANNEL_1)) {
+ iaxs[callno0]->bridgecallno = callno1;
+ iaxs[callno1]->bridgecallno = callno0;
+ }
+ unlock_both(callno0, callno1);
+
+ /* If not, try to bridge until we can execute a transfer, if we can */
+ cs[0] = c0;
+ cs[1] = c1;
+ for (/* ever */;;) {
+ /* Check in case we got masqueraded into */
+ if ((c0->tech != &iax2_tech) || (c1->tech != &iax2_tech)) {
+ ast_verb(3, "Can't masquerade, we're different...\n");
+ /* Remove from native mode */
+ if (c0->tech == &iax2_tech) {
+ ast_mutex_lock(&iaxsl[callno0]);
+ iaxs[callno0]->bridgecallno = 0;
+ ast_mutex_unlock(&iaxsl[callno0]);
+ }
+ if (c1->tech == &iax2_tech) {
+ ast_mutex_lock(&iaxsl[callno1]);
+ iaxs[callno1]->bridgecallno = 0;
+ ast_mutex_unlock(&iaxsl[callno1]);
+ }
+ return AST_BRIDGE_FAILED_NOWARN;
+ }
+ if (c0->nativeformats != c1->nativeformats) {
+ char buf0[255];
+ char buf1[255];
+ ast_getformatname_multiple(buf0, sizeof(buf0) -1, c0->nativeformats);
+ ast_getformatname_multiple(buf1, sizeof(buf1) -1, c1->nativeformats);
+ ast_verb(3, "Operating with different codecs %d[%s] %d[%s] , can't native bridge...\n", c0->nativeformats, buf0, c1->nativeformats, buf1);
+ /* Remove from native mode */
+ lock_both(callno0, callno1);
+ if (iaxs[callno0])
+ iaxs[callno0]->bridgecallno = 0;
+ if (iaxs[callno1])
+ iaxs[callno1]->bridgecallno = 0;
+ unlock_both(callno0, callno1);
+ return AST_BRIDGE_FAILED_NOWARN;
+ }
+ /* check if transfered and if we really want native bridging */
+ if (!transferstarted && !ast_test_flag(iaxs[callno0], IAX_NOTRANSFER) && !ast_test_flag(iaxs[callno1], IAX_NOTRANSFER)) {
+ /* Try the transfer */
+ if (iax2_start_transfer(callno0, callno1, (flags & (AST_BRIDGE_DTMF_CHANNEL_0 | AST_BRIDGE_DTMF_CHANNEL_1)) ||
+ ast_test_flag(iaxs[callno0], IAX_TRANSFERMEDIA) | ast_test_flag(iaxs[callno1], IAX_TRANSFERMEDIA)))
+ ast_log(LOG_WARNING, "Unable to start the transfer\n");
+ transferstarted = 1;
+ }
+ if ((iaxs[callno0]->transferring == TRANSFER_RELEASED) && (iaxs[callno1]->transferring == TRANSFER_RELEASED)) {
+ /* Call has been transferred. We're no longer involved */
+ tv = ast_tvnow();
+ if (ast_tvzero(waittimer)) {
+ waittimer = tv;
+ } else if (tv.tv_sec - waittimer.tv_sec > IAX_LINGER_TIMEOUT) {
+ c0->_softhangup |= AST_SOFTHANGUP_DEV;
+ c1->_softhangup |= AST_SOFTHANGUP_DEV;
+ *fo = NULL;
+ *rc = c0;
+ res = AST_BRIDGE_COMPLETE;
+ break;
+ }
+ }
+ to = 1000;
+ who = ast_waitfor_n(cs, 2, &to);
+ if (timeoutms > -1) {
+ timeoutms -= (1000 - to);
+ if (timeoutms < 0)
+ timeoutms = 0;
+ }
+ if (!who) {
+ if (!timeoutms) {
+ res = AST_BRIDGE_RETRY;
+ break;
+ }
+ if (ast_check_hangup(c0) || ast_check_hangup(c1)) {
+ res = AST_BRIDGE_FAILED;
+ break;
+ }
+ continue;
+ }
+ f = ast_read(who);
+ if (!f) {
+ *fo = NULL;
+ *rc = who;
+ res = AST_BRIDGE_COMPLETE;
+ break;
+ }
+ if ((f->frametype == AST_FRAME_CONTROL) && !(flags & AST_BRIDGE_IGNORE_SIGS)) {
+ *fo = f;
+ *rc = who;
+ res = AST_BRIDGE_COMPLETE;
+ break;
+ }
+ other = (who == c0) ? c1 : c0; /* the 'other' channel */
+ if ((f->frametype == AST_FRAME_VOICE) ||
+ (f->frametype == AST_FRAME_TEXT) ||
+ (f->frametype == AST_FRAME_VIDEO) ||
+ (f->frametype == AST_FRAME_IMAGE) ||
+ (f->frametype == AST_FRAME_DTMF)) {
+ /* monitored dtmf take out of the bridge.
+ * check if we monitor the specific source.
+ */
+ int monitored_source = (who == c0) ? AST_BRIDGE_DTMF_CHANNEL_0 : AST_BRIDGE_DTMF_CHANNEL_1;
+ if (f->frametype == AST_FRAME_DTMF && (flags & monitored_source)) {
+ *rc = who;
+ *fo = f;
+ res = AST_BRIDGE_COMPLETE;
+ /* Remove from native mode */
+ break;
+ }
+ /* everything else goes to the other side */
+ ast_write(other, f);
+ }
+ ast_frfree(f);
+ /* Swap who gets priority */
+ cs[2] = cs[0];
+ cs[0] = cs[1];
+ cs[1] = cs[2];
+ }
+ lock_both(callno0, callno1);
+ if(iaxs[callno0])
+ iaxs[callno0]->bridgecallno = 0;
+ if(iaxs[callno1])
+ iaxs[callno1]->bridgecallno = 0;
+ unlock_both(callno0, callno1);
+ return res;
+}
+
+static int iax2_answer(struct ast_channel *c)
+{
+ unsigned short callno = PTR_TO_CALLNO(c->tech_pvt);
+ ast_debug(1, "Answering IAX2 call\n");
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno])
+ iax2_ami_channelupdate(iaxs[callno]);
+ ast_mutex_unlock(&iaxsl[callno]);
+ return send_command_locked(callno, AST_FRAME_CONTROL, AST_CONTROL_ANSWER, 0, NULL, 0, -1);
+}
+
+static int iax2_indicate(struct ast_channel *c, int condition, const void *data, size_t datalen)
+{
+ unsigned short callno = PTR_TO_CALLNO(c->tech_pvt);
+ struct chan_iax2_pvt *pvt;
+ int res = 0;
+
+ if (iaxdebug)
+ ast_debug(1, "Indicating condition %d\n", condition);
+
+ ast_mutex_lock(&iaxsl[callno]);
+ pvt = iaxs[callno];
+
+ switch (condition) {
+ case AST_CONTROL_HOLD:
+ if (strcasecmp(pvt->mohinterpret, "passthrough")) {
+ ast_moh_start(c, data, pvt->mohinterpret);
+ goto done;
+ }
+ break;
+ case AST_CONTROL_UNHOLD:
+ if (strcasecmp(pvt->mohinterpret, "passthrough")) {
+ ast_moh_stop(c);
+ goto done;
+ }
+ }
+
+ res = send_command(pvt, AST_FRAME_CONTROL, condition, 0, data, datalen, -1);
+
+done:
+ ast_mutex_unlock(&iaxsl[callno]);
+
+ return res;
+}
+
+static int iax2_transfer(struct ast_channel *c, const char *dest)
+{
+ unsigned short callno = PTR_TO_CALLNO(c->tech_pvt);
+ struct iax_ie_data ied = { "", };
+ char tmp[256], *context;
+ ast_copy_string(tmp, dest, sizeof(tmp));
+ context = strchr(tmp, '@');
+ if (context) {
+ *context = '\0';
+ context++;
+ }
+ iax_ie_append_str(&ied, IAX_IE_CALLED_NUMBER, tmp);
+ if (context)
+ iax_ie_append_str(&ied, IAX_IE_CALLED_CONTEXT, context);
+ ast_debug(1, "Transferring '%s' to '%s'\n", c->name, dest);
+ return send_command_locked(callno, AST_FRAME_IAX, IAX_COMMAND_TRANSFER, 0, ied.buf, ied.pos, -1);
+}
+
+static int iax2_getpeertrunk(struct sockaddr_in sin)
+{
+ struct iax2_peer *peer;
+ int res = 0;
+ struct ao2_iterator i;
+
+ i = ao2_iterator_init(peers, 0);
+ while ((peer = ao2_iterator_next(&i))) {
+ if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) &&
+ (peer->addr.sin_port == sin.sin_port)) {
+ res = ast_test_flag(peer, IAX_TRUNK);
+ peer_unref(peer);
+ break;
+ }
+ peer_unref(peer);
+ }
+
+ return res;
+}
+
+/*! \brief Create new call, interface with the PBX core */
+static struct ast_channel *ast_iax2_new(int callno, int state, int capability)
+{
+ struct ast_channel *tmp;
+ struct chan_iax2_pvt *i;
+ struct ast_variable *v = NULL;
+
+ if (!(i = iaxs[callno])) {
+ ast_log(LOG_WARNING, "No IAX2 pvt found for callno '%d' !\n", callno);
+ return NULL;
+ }
+
+ /* Don't hold call lock */
+ ast_mutex_unlock(&iaxsl[callno]);
+ tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, i->amaflags, "IAX2/%s-%d", i->host, i->callno);
+ ast_mutex_lock(&iaxsl[callno]);
+ iax2_ami_channelupdate(i);
+ if (!tmp)
+ return NULL;
+ tmp->tech = &iax2_tech;
+ /* We can support any format by default, until we get restricted */
+ tmp->nativeformats = capability;
+ tmp->readformat = ast_best_codec(capability);
+ tmp->writeformat = ast_best_codec(capability);
+ tmp->tech_pvt = CALLNO_TO_PTR(i->callno);
+
+ /* Don't use ast_set_callerid() here because it will
+ * generate a NewCallerID event before the NewChannel event */
+ if (!ast_strlen_zero(i->ani))
+ tmp->cid.cid_ani = ast_strdup(i->ani);
+ else
+ tmp->cid.cid_ani = ast_strdup(i->cid_num);
+ tmp->cid.cid_dnid = ast_strdup(i->dnid);
+ tmp->cid.cid_rdnis = ast_strdup(i->rdnis);
+ tmp->cid.cid_pres = i->calling_pres;
+ tmp->cid.cid_ton = i->calling_ton;
+ tmp->cid.cid_tns = i->calling_tns;
+ if (!ast_strlen_zero(i->language))
+ ast_string_field_set(tmp, language, i->language);
+ if (!ast_strlen_zero(i->accountcode))
+ ast_string_field_set(tmp, accountcode, i->accountcode);
+ if (i->amaflags)
+ tmp->amaflags = i->amaflags;
+ ast_copy_string(tmp->context, i->context, sizeof(tmp->context));
+ ast_copy_string(tmp->exten, i->exten, sizeof(tmp->exten));
+ if (i->adsi)
+ tmp->adsicpe = i->peeradsicpe;
+ else
+ tmp->adsicpe = AST_ADSI_UNAVAILABLE;
+ i->owner = tmp;
+ i->capability = capability;
+
+ /* Set inherited variables */
+ if (i->vars) {
+ for (v = i->vars ; v ; v = v->next)
+ pbx_builtin_setvar_helper(tmp, v->name, v->value);
+ }
+
+ if (state != AST_STATE_DOWN) {
+ if (ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ ast_hangup(tmp);
+ i->owner = NULL;
+ return NULL;
+ }
+ }
+
+ ast_module_ref(ast_module_info->self);
+ return tmp;
+}
+
+static unsigned int calc_txpeerstamp(struct iax2_trunk_peer *tpeer, int sampms, struct timeval *tv)
+{
+ unsigned long int mssincetx; /* unsigned to handle overflows */
+ long int ms, pred;
+
+ tpeer->trunkact = *tv;
+ mssincetx = ast_tvdiff_ms(*tv, tpeer->lasttxtime);
+ if (mssincetx > 5000 || ast_tvzero(tpeer->txtrunktime)) {
+ /* If it's been at least 5 seconds since the last time we transmitted on this trunk, reset our timers */
+ tpeer->txtrunktime = *tv;
+ tpeer->lastsent = 999999;
+ }
+ /* Update last transmit time now */
+ tpeer->lasttxtime = *tv;
+
+ /* Calculate ms offset */
+ ms = ast_tvdiff_ms(*tv, tpeer->txtrunktime);
+ /* Predict from last value */
+ pred = tpeer->lastsent + sampms;
+ if (abs(ms - pred) < MAX_TIMESTAMP_SKEW)
+ ms = pred;
+
+ /* We never send the same timestamp twice, so fudge a little if we must */
+ if (ms == tpeer->lastsent)
+ ms = tpeer->lastsent + 1;
+ tpeer->lastsent = ms;
+ return ms;
+}
+
+static unsigned int fix_peerts(struct timeval *tv, int callno, unsigned int ts)
+{
+ long ms; /* NOT unsigned */
+ if (ast_tvzero(iaxs[callno]->rxcore)) {
+ /* Initialize rxcore time if appropriate */
+ iaxs[callno]->rxcore = ast_tvnow();
+ /* Round to nearest 20ms so traces look pretty */
+ iaxs[callno]->rxcore.tv_usec -= iaxs[callno]->rxcore.tv_usec % 20000;
+ }
+ /* Calculate difference between trunk and channel */
+ ms = ast_tvdiff_ms(*tv, iaxs[callno]->rxcore);
+ /* Return as the sum of trunk time and the difference between trunk and real time */
+ return ms + ts;
+}
+
+static unsigned int calc_timestamp(struct chan_iax2_pvt *p, unsigned int ts, struct ast_frame *f)
+{
+ int ms;
+ int voice = 0;
+ int genuine = 0;
+ int adjust;
+ struct timeval *delivery = NULL;
+
+
+ /* What sort of frame do we have?: voice is self-explanatory
+ "genuine" means an IAX frame - things like LAGRQ/RP, PING/PONG, ACK
+ non-genuine frames are CONTROL frames [ringing etc], DTMF
+ The "genuine" distinction is needed because genuine frames must get a clock-based timestamp,
+ the others need a timestamp slaved to the voice frames so that they go in sequence
+ */
+ if (f) {
+ if (f->frametype == AST_FRAME_VOICE) {
+ voice = 1;
+ delivery = &f->delivery;
+ } else if (f->frametype == AST_FRAME_IAX) {
+ genuine = 1;
+ } else if (f->frametype == AST_FRAME_CNG) {
+ p->notsilenttx = 0;
+ }
+ }
+ if (ast_tvzero(p->offset)) {
+ p->offset = ast_tvnow();
+ /* Round to nearest 20ms for nice looking traces */
+ p->offset.tv_usec -= p->offset.tv_usec % 20000;
+ }
+ /* If the timestamp is specified, just send it as is */
+ if (ts)
+ return ts;
+ /* If we have a time that the frame arrived, always use it to make our timestamp */
+ if (delivery && !ast_tvzero(*delivery)) {
+ ms = ast_tvdiff_ms(*delivery, p->offset);
+ if (iaxdebug)
+ ast_debug(3, "calc_timestamp: call %d/%d: Timestamp slaved to delivery time\n", p->callno, iaxs[p->callno]->peercallno);
+ } else {
+ ms = ast_tvdiff_ms(ast_tvnow(), p->offset);
+ if (ms < 0)
+ ms = 0;
+ if (voice) {
+ /* On a voice frame, use predicted values if appropriate */
+ if (p->notsilenttx && abs(ms - p->nextpred) <= MAX_TIMESTAMP_SKEW) {
+ /* Adjust our txcore, keeping voice and non-voice synchronized */
+ /* AN EXPLANATION:
+ When we send voice, we usually send "calculated" timestamps worked out
+ on the basis of the number of samples sent. When we send other frames,
+ we usually send timestamps worked out from the real clock.
+ The problem is that they can tend to drift out of step because the
+ source channel's clock and our clock may not be exactly at the same rate.
+ We fix this by continuously "tweaking" p->offset. p->offset is "time zero"
+ for this call. Moving it adjusts timestamps for non-voice frames.
+ We make the adjustment in the style of a moving average. Each time we
+ adjust p->offset by 10% of the difference between our clock-derived
+ timestamp and the predicted timestamp. That's why you see "10000"
+ below even though IAX2 timestamps are in milliseconds.
+ The use of a moving average avoids offset moving too radically.
+ Generally, "adjust" roams back and forth around 0, with offset hardly
+ changing at all. But if a consistent different starts to develop it
+ will be eliminated over the course of 10 frames (200-300msecs)
+ */
+ adjust = (ms - p->nextpred);
+ if (adjust < 0)
+ p->offset = ast_tvsub(p->offset, ast_samp2tv(abs(adjust), 10000));
+ else if (adjust > 0)
+ p->offset = ast_tvadd(p->offset, ast_samp2tv(adjust, 10000));
+
+ if (!p->nextpred) {
+ p->nextpred = ms; /*f->samples / 8;*/
+ if (p->nextpred <= p->lastsent)
+ p->nextpred = p->lastsent + 3;
+ }
+ ms = p->nextpred;
+ } else {
+ /* in this case, just use the actual
+ * time, since we're either way off
+ * (shouldn't happen), or we're ending a
+ * silent period -- and seed the next
+ * predicted time. Also, round ms to the
+ * next multiple of frame size (so our
+ * silent periods are multiples of
+ * frame size too) */
+
+ if (iaxdebug && abs(ms - p->nextpred) > MAX_TIMESTAMP_SKEW )
+ ast_debug(1, "predicted timestamp skew (%u) > max (%u), using real ts instead.\n",
+ abs(ms - p->nextpred), MAX_TIMESTAMP_SKEW);
+
+ if (f->samples >= 8) /* check to make sure we dont core dump */
+ {
+ int diff = ms % (f->samples / 8);
+ if (diff)
+ ms += f->samples/8 - diff;
+ }
+
+ p->nextpred = ms;
+ p->notsilenttx = 1;
+ }
+ } else {
+ /* On a dataframe, use last value + 3 (to accomodate jitter buffer shrinking) if appropriate unless
+ it's a genuine frame */
+ if (genuine) {
+ /* genuine (IAX LAGRQ etc) must keep their clock-based stamps */
+ if (ms <= p->lastsent)
+ ms = p->lastsent + 3;
+ } else if (abs(ms - p->lastsent) <= MAX_TIMESTAMP_SKEW) {
+ /* non-genuine frames (!?) (DTMF, CONTROL) should be pulled into the predicted stream stamps */
+ ms = p->lastsent + 3;
+ }
+ }
+ }
+ p->lastsent = ms;
+ if (voice)
+ p->nextpred = p->nextpred + f->samples / 8;
+ return ms;
+}
+
+static unsigned int calc_rxstamp(struct chan_iax2_pvt *p, unsigned int offset)
+{
+ /* Returns where in "receive time" we are. That is, how many ms
+ since we received (or would have received) the frame with timestamp 0 */
+ int ms;
+#ifdef IAXTESTS
+ int jit;
+#endif /* IAXTESTS */
+ /* Setup rxcore if necessary */
+ if (ast_tvzero(p->rxcore)) {
+ p->rxcore = ast_tvnow();
+ if (iaxdebug)
+ ast_debug(1, "calc_rxstamp: call=%d: rxcore set to %d.%6.6d - %dms\n",
+ p->callno, (int)(p->rxcore.tv_sec), (int)(p->rxcore.tv_usec), offset);
+ p->rxcore = ast_tvsub(p->rxcore, ast_samp2tv(offset, 1000));
+#if 1
+ if (iaxdebug)
+ ast_debug(1, "calc_rxstamp: call=%d: works out as %d.%6.6d\n",
+ p->callno, (int)(p->rxcore.tv_sec),(int)( p->rxcore.tv_usec));
+#endif
+ }
+
+ ms = ast_tvdiff_ms(ast_tvnow(), p->rxcore);
+#ifdef IAXTESTS
+ if (test_jit) {
+ if (!test_jitpct || ((100.0 * ast_random() / (RAND_MAX + 1.0)) < test_jitpct)) {
+ jit = (int)((float)test_jit * ast_random() / (RAND_MAX + 1.0));
+ if ((int)(2.0 * ast_random() / (RAND_MAX + 1.0)))
+ jit = -jit;
+ ms += jit;
+ }
+ }
+ if (test_late) {
+ ms += test_late;
+ test_late = 0;
+ }
+#endif /* IAXTESTS */
+ return ms;
+}
+
+static struct iax2_trunk_peer *find_tpeer(struct sockaddr_in *sin, int fd)
+{
+ struct iax2_trunk_peer *tpeer = NULL;
+
+ /* Finds and locks trunk peer */
+ AST_LIST_LOCK(&tpeers);
+
+ AST_LIST_TRAVERSE(&tpeers, tpeer, list) {
+ if (!inaddrcmp(&tpeer->addr, sin)) {
+ ast_mutex_lock(&tpeer->lock);
+ break;
+ }
+ }
+
+ if (!tpeer) {
+ if ((tpeer = ast_calloc(1, sizeof(*tpeer)))) {
+ ast_mutex_init(&tpeer->lock);
+ tpeer->lastsent = 9999;
+ memcpy(&tpeer->addr, sin, sizeof(tpeer->addr));
+ tpeer->trunkact = ast_tvnow();
+ ast_mutex_lock(&tpeer->lock);
+ tpeer->sockfd = fd;
+#ifdef SO_NO_CHECK
+ setsockopt(tpeer->sockfd, SOL_SOCKET, SO_NO_CHECK, &nochecksums, sizeof(nochecksums));
+#endif
+ ast_debug(1, "Created trunk peer for '%s:%d'\n", ast_inet_ntoa(tpeer->addr.sin_addr), ntohs(tpeer->addr.sin_port));
+ AST_LIST_INSERT_TAIL(&tpeers, tpeer, list);
+ }
+ }
+
+ AST_LIST_UNLOCK(&tpeers);
+
+ return tpeer;
+}
+
+static int iax2_trunk_queue(struct chan_iax2_pvt *pvt, struct iax_frame *fr)
+{
+ struct ast_frame *f;
+ struct iax2_trunk_peer *tpeer;
+ void *tmp, *ptr;
+ struct timeval now;
+ int res;
+ struct ast_iax2_meta_trunk_entry *met;
+ struct ast_iax2_meta_trunk_mini *mtm;
+
+ f = &fr->af;
+ tpeer = find_tpeer(&pvt->addr, pvt->sockfd);
+ if (tpeer) {
+ if (tpeer->trunkdatalen + f->datalen + 4 >= tpeer->trunkdataalloc) {
+ /* Need to reallocate space */
+ if (tpeer->trunkdataalloc < trunkmaxsize) {
+ if (!(tmp = ast_realloc(tpeer->trunkdata, tpeer->trunkdataalloc + DEFAULT_TRUNKDATA + IAX2_TRUNK_PREFACE))) {
+ ast_mutex_unlock(&tpeer->lock);
+ return -1;
+ }
+
+ tpeer->trunkdataalloc += DEFAULT_TRUNKDATA;
+ tpeer->trunkdata = tmp;
+ ast_debug(1, "Expanded trunk '%s:%d' to %d bytes\n", ast_inet_ntoa(tpeer->addr.sin_addr), ntohs(tpeer->addr.sin_port), tpeer->trunkdataalloc);
+ } else {
+ ast_log(LOG_WARNING, "Maximum trunk data space exceeded to %s:%d\n", ast_inet_ntoa(tpeer->addr.sin_addr), ntohs(tpeer->addr.sin_port));
+ ast_mutex_unlock(&tpeer->lock);
+ return -1;
+ }
+ }
+
+ /* Append to meta frame */
+ ptr = tpeer->trunkdata + IAX2_TRUNK_PREFACE + tpeer->trunkdatalen;
+ if (ast_test_flag(&globalflags, IAX_TRUNKTIMESTAMPS)) {
+ mtm = (struct ast_iax2_meta_trunk_mini *)ptr;
+ mtm->len = htons(f->datalen);
+ mtm->mini.callno = htons(pvt->callno);
+ mtm->mini.ts = htons(0xffff & fr->ts);
+ ptr += sizeof(struct ast_iax2_meta_trunk_mini);
+ tpeer->trunkdatalen += sizeof(struct ast_iax2_meta_trunk_mini);
+ } else {
+ met = (struct ast_iax2_meta_trunk_entry *)ptr;
+ /* Store call number and length in meta header */
+ met->callno = htons(pvt->callno);
+ met->len = htons(f->datalen);
+ /* Advance pointers/decrease length past trunk entry header */
+ ptr += sizeof(struct ast_iax2_meta_trunk_entry);
+ tpeer->trunkdatalen += sizeof(struct ast_iax2_meta_trunk_entry);
+ }
+ /* Copy actual trunk data */
+ memcpy(ptr, f->data, f->datalen);
+ tpeer->trunkdatalen += f->datalen;
+
+ tpeer->calls++;
+
+ /* track the largest mtu we actually have sent */
+ if (tpeer->trunkdatalen + f->datalen + 4 > trunk_maxmtu)
+ trunk_maxmtu = tpeer->trunkdatalen + f->datalen + 4 ;
+
+ /* if we have enough for a full MTU, ship it now without waiting */
+ if (global_max_trunk_mtu > 0 && tpeer->trunkdatalen + f->datalen + 4 >= global_max_trunk_mtu) {
+ now = ast_tvnow();
+ res = send_trunk(tpeer, &now);
+ trunk_untimed ++;
+ }
+
+ ast_mutex_unlock(&tpeer->lock);
+ }
+ return 0;
+}
+
+static void build_enc_keys(const unsigned char *digest, ast_aes_encrypt_key *ecx, ast_aes_decrypt_key *dcx)
+{
+ ast_aes_encrypt_key(digest, ecx);
+ ast_aes_decrypt_key(digest, dcx);
+}
+
+static void memcpy_decrypt(unsigned char *dst, const unsigned char *src, int len, ast_aes_decrypt_key *dcx)
+{
+#if 0
+ /* Debug with "fake encryption" */
+ int x;
+ if (len % 16)
+ ast_log(LOG_WARNING, "len should be multiple of 16, not %d!\n", len);
+ for (x=0;x<len;x++)
+ dst[x] = src[x] ^ 0xff;
+#else
+ unsigned char lastblock[16] = { 0 };
+ int x;
+ while(len > 0) {
+ ast_aes_decrypt(src, dst, dcx);
+ for (x=0;x<16;x++)
+ dst[x] ^= lastblock[x];
+ memcpy(lastblock, src, sizeof(lastblock));
+ dst += 16;
+ src += 16;
+ len -= 16;
+ }
+#endif
+}
+
+static void memcpy_encrypt(unsigned char *dst, const unsigned char *src, int len, ast_aes_encrypt_key *ecx)
+{
+#if 0
+ /* Debug with "fake encryption" */
+ int x;
+ if (len % 16)
+ ast_log(LOG_WARNING, "len should be multiple of 16, not %d!\n", len);
+ for (x=0;x<len;x++)
+ dst[x] = src[x] ^ 0xff;
+#else
+ unsigned char curblock[16] = { 0 };
+ int x;
+ while(len > 0) {
+ for (x=0;x<16;x++)
+ curblock[x] ^= src[x];
+ ast_aes_encrypt(curblock, dst, ecx);
+ memcpy(curblock, dst, sizeof(curblock));
+ dst += 16;
+ src += 16;
+ len -= 16;
+ }
+#endif
+}
+
+static int decode_frame(ast_aes_decrypt_key *dcx, struct ast_iax2_full_hdr *fh, struct ast_frame *f, int *datalen)
+{
+ int padding;
+ unsigned char *workspace;
+
+ workspace = alloca(*datalen);
+ memset(f, 0, sizeof(*f));
+ if (ntohs(fh->scallno) & IAX_FLAG_FULL) {
+ struct ast_iax2_full_enc_hdr *efh = (struct ast_iax2_full_enc_hdr *)fh;
+ if (*datalen < 16 + sizeof(struct ast_iax2_full_hdr))
+ return -1;
+ /* Decrypt */
+ memcpy_decrypt(workspace, efh->encdata, *datalen - sizeof(struct ast_iax2_full_enc_hdr), dcx);
+
+ padding = 16 + (workspace[15] & 0xf);
+ if (iaxdebug)
+ ast_debug(1, "Decoding full frame with length %d (padding = %d) (15=%02x)\n", *datalen, padding, workspace[15]);
+ if (*datalen < padding + sizeof(struct ast_iax2_full_hdr))
+ return -1;
+
+ *datalen -= padding;
+ memcpy(efh->encdata, workspace + padding, *datalen - sizeof(struct ast_iax2_full_enc_hdr));
+ f->frametype = fh->type;
+ if (f->frametype == AST_FRAME_VIDEO) {
+ f->subclass = uncompress_subclass(fh->csub & ~0x40) | ((fh->csub >> 6) & 0x1);
+ } else {
+ f->subclass = uncompress_subclass(fh->csub);
+ }
+ } else {
+ struct ast_iax2_mini_enc_hdr *efh = (struct ast_iax2_mini_enc_hdr *)fh;
+ if (iaxdebug)
+ ast_debug(1, "Decoding mini with length %d\n", *datalen);
+ if (*datalen < 16 + sizeof(struct ast_iax2_mini_hdr))
+ return -1;
+ /* Decrypt */
+ memcpy_decrypt(workspace, efh->encdata, *datalen - sizeof(struct ast_iax2_mini_enc_hdr), dcx);
+ padding = 16 + (workspace[15] & 0x0f);
+ if (*datalen < padding + sizeof(struct ast_iax2_mini_hdr))
+ return -1;
+ *datalen -= padding;
+ memcpy(efh->encdata, workspace + padding, *datalen - sizeof(struct ast_iax2_mini_enc_hdr));
+ }
+ return 0;
+}
+
+static int encrypt_frame(ast_aes_encrypt_key *ecx, struct ast_iax2_full_hdr *fh, unsigned char *poo, int *datalen)
+{
+ int padding;
+ unsigned char *workspace;
+ workspace = alloca(*datalen + 32);
+ if (!workspace)
+ return -1;
+ if (ntohs(fh->scallno) & IAX_FLAG_FULL) {
+ struct ast_iax2_full_enc_hdr *efh = (struct ast_iax2_full_enc_hdr *)fh;
+ if (iaxdebug)
+ ast_debug(1, "Encoding full frame %d/%d with length %d\n", fh->type, fh->csub, *datalen);
+ padding = 16 - ((*datalen - sizeof(struct ast_iax2_full_enc_hdr)) % 16);
+ padding = 16 + (padding & 0xf);
+ memcpy(workspace, poo, padding);
+ memcpy(workspace + padding, efh->encdata, *datalen - sizeof(struct ast_iax2_full_enc_hdr));
+ workspace[15] &= 0xf0;
+ workspace[15] |= (padding & 0xf);
+ if (iaxdebug)
+ ast_debug(1, "Encoding full frame %d/%d with length %d + %d padding (15=%02x)\n", fh->type, fh->csub, *datalen, padding, workspace[15]);
+ *datalen += padding;
+ memcpy_encrypt(efh->encdata, workspace, *datalen - sizeof(struct ast_iax2_full_enc_hdr), ecx);
+ if (*datalen >= 32 + sizeof(struct ast_iax2_full_enc_hdr))
+ memcpy(poo, workspace + *datalen - 32, 32);
+ } else {
+ struct ast_iax2_mini_enc_hdr *efh = (struct ast_iax2_mini_enc_hdr *)fh;
+ if (iaxdebug)
+ ast_debug(1, "Encoding mini frame with length %d\n", *datalen);
+ padding = 16 - ((*datalen - sizeof(struct ast_iax2_mini_enc_hdr)) % 16);
+ padding = 16 + (padding & 0xf);
+ memcpy(workspace, poo, padding);
+ memcpy(workspace + padding, efh->encdata, *datalen - sizeof(struct ast_iax2_mini_enc_hdr));
+ workspace[15] &= 0xf0;
+ workspace[15] |= (padding & 0x0f);
+ *datalen += padding;
+ memcpy_encrypt(efh->encdata, workspace, *datalen - sizeof(struct ast_iax2_mini_enc_hdr), ecx);
+ if (*datalen >= 32 + sizeof(struct ast_iax2_mini_enc_hdr))
+ memcpy(poo, workspace + *datalen - 32, 32);
+ }
+ return 0;
+}
+
+static int decrypt_frame(int callno, struct ast_iax2_full_hdr *fh, struct ast_frame *f, int *datalen)
+{
+ int res=-1;
+ if (!ast_test_flag(iaxs[callno], IAX_KEYPOPULATED)) {
+ /* Search for possible keys, given secrets */
+ struct MD5Context md5;
+ unsigned char digest[16];
+ char *tmppw, *stringp;
+
+ tmppw = ast_strdupa(iaxs[callno]->secret);
+ stringp = tmppw;
+ while ((tmppw = strsep(&stringp, ";"))) {
+ MD5Init(&md5);
+ MD5Update(&md5, (unsigned char *)iaxs[callno]->challenge, strlen(iaxs[callno]->challenge));
+ MD5Update(&md5, (unsigned char *)tmppw, strlen(tmppw));
+ MD5Final(digest, &md5);
+ build_enc_keys(digest, &iaxs[callno]->ecx, &iaxs[callno]->dcx);
+ res = decode_frame(&iaxs[callno]->dcx, fh, f, datalen);
+ if (!res) {
+ ast_set_flag(iaxs[callno], IAX_KEYPOPULATED);
+ break;
+ }
+ }
+ } else
+ res = decode_frame(&iaxs[callno]->dcx, fh, f, datalen);
+ return res;
+}
+
+static int iax2_send(struct chan_iax2_pvt *pvt, struct ast_frame *f, unsigned int ts, int seqno, int now, int transfer, int final)
+{
+ /* Queue a packet for delivery on a given private structure. Use "ts" for
+ timestamp, or calculate if ts is 0. Send immediately without retransmission
+ or delayed, with retransmission */
+ struct ast_iax2_full_hdr *fh;
+ struct ast_iax2_mini_hdr *mh;
+ struct ast_iax2_video_hdr *vh;
+ struct {
+ struct iax_frame fr2;
+ unsigned char buffer[4096];
+ } frb;
+ struct iax_frame *fr;
+ int res;
+ int sendmini=0;
+ unsigned int lastsent;
+ unsigned int fts;
+
+ frb.fr2.afdatalen = sizeof(frb.buffer);
+
+ if (!pvt) {
+ ast_log(LOG_WARNING, "No private structure for packet?\n");
+ return -1;
+ }
+
+ lastsent = pvt->lastsent;
+
+ /* Calculate actual timestamp */
+ fts = calc_timestamp(pvt, ts, f);
+
+ /* Bail here if this is an "interp" frame; we don't want or need to send these placeholders out
+ * (the endpoint should detect the lost packet itself). But, we want to do this here, so that we
+ * increment the "predicted timestamps" for voice, if we're predicting */
+ if(f->frametype == AST_FRAME_VOICE && f->datalen == 0)
+ return 0;
+
+
+ if ((ast_test_flag(pvt, IAX_TRUNK) ||
+ (((fts & 0xFFFF0000L) == (lastsent & 0xFFFF0000L)) ||
+ ((fts & 0xFFFF0000L) == ((lastsent + 0x10000) & 0xFFFF0000L))))
+ /* High two bytes are the same on timestamp, or sending on a trunk */ &&
+ (f->frametype == AST_FRAME_VOICE)
+ /* is a voice frame */ &&
+ (f->subclass == pvt->svoiceformat)
+ /* is the same type */ ) {
+ /* Force immediate rather than delayed transmission */
+ now = 1;
+ /* Mark that mini-style frame is appropriate */
+ sendmini = 1;
+ }
+ if (((fts & 0xFFFF8000L) == (lastsent & 0xFFFF8000L)) &&
+ (f->frametype == AST_FRAME_VIDEO) &&
+ ((f->subclass & ~0x1) == pvt->svideoformat)) {
+ now = 1;
+ sendmini = 1;
+ }
+ /* Allocate an iax_frame */
+ if (now) {
+ fr = &frb.fr2;
+ } else
+ fr = iax_frame_new(DIRECTION_OUTGRESS, ast_test_flag(pvt, IAX_ENCRYPTED) ? f->datalen + 32 : f->datalen, (f->frametype == AST_FRAME_VOICE) || (f->frametype == AST_FRAME_VIDEO));
+ if (!fr) {
+ ast_log(LOG_WARNING, "Out of memory\n");
+ return -1;
+ }
+ /* Copy our prospective frame into our immediate or retransmitted wrapper */
+ iax_frame_wrap(fr, f);
+
+ fr->ts = fts;
+ fr->callno = pvt->callno;
+ fr->transfer = transfer;
+ fr->final = final;
+ if (!sendmini) {
+ /* We need a full frame */
+ if (seqno > -1)
+ fr->oseqno = seqno;
+ else
+ fr->oseqno = pvt->oseqno++;
+ fr->iseqno = pvt->iseqno;
+ fh = (struct ast_iax2_full_hdr *)(fr->af.data - sizeof(struct ast_iax2_full_hdr));
+ fh->scallno = htons(fr->callno | IAX_FLAG_FULL);
+ fh->ts = htonl(fr->ts);
+ fh->oseqno = fr->oseqno;
+ if (transfer) {
+ fh->iseqno = 0;
+ } else
+ fh->iseqno = fr->iseqno;
+ /* Keep track of the last thing we've acknowledged */
+ if (!transfer)
+ pvt->aseqno = fr->iseqno;
+ fh->type = fr->af.frametype & 0xFF;
+ if (fr->af.frametype == AST_FRAME_VIDEO)
+ fh->csub = compress_subclass(fr->af.subclass & ~0x1) | ((fr->af.subclass & 0x1) << 6);
+ else
+ fh->csub = compress_subclass(fr->af.subclass);
+ if (transfer) {
+ fr->dcallno = pvt->transfercallno;
+ } else
+ fr->dcallno = pvt->peercallno;
+ fh->dcallno = htons(fr->dcallno);
+ fr->datalen = fr->af.datalen + sizeof(struct ast_iax2_full_hdr);
+ fr->data = fh;
+ fr->retries = 0;
+ /* Retry after 2x the ping time has passed */
+ fr->retrytime = pvt->pingtime * 2;
+ if (fr->retrytime < MIN_RETRY_TIME)
+ fr->retrytime = MIN_RETRY_TIME;
+ if (fr->retrytime > MAX_RETRY_TIME)
+ fr->retrytime = MAX_RETRY_TIME;
+ /* Acks' don't get retried */
+ if ((f->frametype == AST_FRAME_IAX) && (f->subclass == IAX_COMMAND_ACK))
+ fr->retries = -1;
+ else if (f->frametype == AST_FRAME_VOICE)
+ pvt->svoiceformat = f->subclass;
+ else if (f->frametype == AST_FRAME_VIDEO)
+ pvt->svideoformat = f->subclass & ~0x1;
+ if (ast_test_flag(pvt, IAX_ENCRYPTED)) {
+ if (ast_test_flag(pvt, IAX_KEYPOPULATED)) {
+ if (iaxdebug) {
+ if (fr->transfer)
+ iax_showframe(fr, NULL, 2, &pvt->transfer, fr->datalen - sizeof(struct ast_iax2_full_hdr));
+ else
+ iax_showframe(fr, NULL, 2, &pvt->addr, fr->datalen - sizeof(struct ast_iax2_full_hdr));
+ }
+ encrypt_frame(&pvt->ecx, fh, pvt->semirand, &fr->datalen);
+ } else
+ ast_log(LOG_WARNING, "Supposed to send packet encrypted, but no key?\n");
+ }
+
+ if (now) {
+ res = send_packet(fr);
+ } else
+ res = iax2_transmit(fr);
+ } else {
+ if (ast_test_flag(pvt, IAX_TRUNK)) {
+ iax2_trunk_queue(pvt, fr);
+ res = 0;
+ } else if (fr->af.frametype == AST_FRAME_VIDEO) {
+ /* Video frame have no sequence number */
+ fr->oseqno = -1;
+ fr->iseqno = -1;
+ vh = (struct ast_iax2_video_hdr *)(fr->af.data - sizeof(struct ast_iax2_video_hdr));
+ vh->zeros = 0;
+ vh->callno = htons(0x8000 | fr->callno);
+ vh->ts = htons((fr->ts & 0x7FFF) | (fr->af.subclass & 0x1 ? 0x8000 : 0));
+ fr->datalen = fr->af.datalen + sizeof(struct ast_iax2_video_hdr);
+ fr->data = vh;
+ fr->retries = -1;
+ res = send_packet(fr);
+ } else {
+ /* Mini-frames have no sequence number */
+ fr->oseqno = -1;
+ fr->iseqno = -1;
+ /* Mini frame will do */
+ mh = (struct ast_iax2_mini_hdr *)(fr->af.data - sizeof(struct ast_iax2_mini_hdr));
+ mh->callno = htons(fr->callno);
+ mh->ts = htons(fr->ts & 0xFFFF);
+ fr->datalen = fr->af.datalen + sizeof(struct ast_iax2_mini_hdr);
+ fr->data = mh;
+ fr->retries = -1;
+ if (pvt->transferring == TRANSFER_MEDIAPASS)
+ fr->transfer = 1;
+ if (ast_test_flag(pvt, IAX_ENCRYPTED)) {
+ if (ast_test_flag(pvt, IAX_KEYPOPULATED)) {
+ encrypt_frame(&pvt->ecx, (struct ast_iax2_full_hdr *)mh, pvt->semirand, &fr->datalen);
+ } else
+ ast_log(LOG_WARNING, "Supposed to send packet encrypted, but no key?\n");
+ }
+ res = send_packet(fr);
+ }
+ }
+ return res;
+}
+
+static char *handle_cli_iax2_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ regex_t regexbuf;
+ int havepattern = 0;
+
+#define FORMAT "%-15.15s %-20.20s %-15.15s %-15.15s %-5.5s %-5.10s\n"
+#define FORMAT2 "%-15.15s %-20.20s %-15.15d %-15.15s %-5.5s %-5.10s\n"
+
+ struct iax2_user *user = NULL;
+ char auth[90];
+ char *pstr = "";
+ struct ao2_iterator i;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show users [like]";
+ e->usage =
+ "Usage: iax2 show users [like <pattern>]\n"
+ " Lists all known IAX2 users.\n"
+ " Optional regular expression pattern is used to filter the user list.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ switch (a->argc) {
+ case 5:
+ if (!strcasecmp(a->argv[3], "like")) {
+ if (regcomp(&regexbuf, a->argv[4], REG_EXTENDED | REG_NOSUB))
+ return CLI_SHOWUSAGE;
+ havepattern = 1;
+ } else
+ return CLI_SHOWUSAGE;
+ case 3:
+ break;
+ default:
+ return CLI_SHOWUSAGE;
+ }
+
+ ast_cli(a->fd, FORMAT, "Username", "Secret", "Authen", "Def.Context", "A/C","Codec Pref");
+ i = ao2_iterator_init(users, 0);
+ for (user = ao2_iterator_next(&i); user;
+ user_unref(user), user = ao2_iterator_next(&i)) {
+ if (havepattern && regexec(&regexbuf, user->name, 0, NULL, 0))
+ continue;
+
+ if (!ast_strlen_zero(user->secret)) {
+ ast_copy_string(auth,user->secret, sizeof(auth));
+ } else if (!ast_strlen_zero(user->inkeys)) {
+ snprintf(auth, sizeof(auth), "Key: %-15.15s ", user->inkeys);
+ } else
+ ast_copy_string(auth, "-no secret-", sizeof(auth));
+
+ if(ast_test_flag(user,IAX_CODEC_NOCAP))
+ pstr = "REQ Only";
+ else if(ast_test_flag(user,IAX_CODEC_NOPREFS))
+ pstr = "Disabled";
+ else
+ pstr = ast_test_flag(user,IAX_CODEC_USER_FIRST) ? "Caller" : "Host";
+
+ ast_cli(a->fd, FORMAT2, user->name, auth, user->authmethods,
+ user->contexts ? user->contexts->context : context,
+ user->ha ? "Yes" : "No", pstr);
+ }
+
+ if (havepattern)
+ regfree(&regexbuf);
+
+ return CLI_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static int __iax2_show_peers(int manager, int fd, struct mansession *s, int argc, char *argv[])
+{
+ regex_t regexbuf;
+ int havepattern = 0;
+ int total_peers = 0;
+ int online_peers = 0;
+ int offline_peers = 0;
+ int unmonitored_peers = 0;
+ struct ao2_iterator i;
+
+#define FORMAT2 "%-15.15s %-15.15s %s %-15.15s %-8s %s %-10s%s"
+#define FORMAT "%-15.15s %-15.15s %s %-15.15s %-5d%s %s %-10s%s"
+
+ struct iax2_peer *peer = NULL;
+ char name[256];
+ int registeredonly=0;
+ char *term = manager ? "\r\n" : "\n";
+ char idtext[256] = "";
+ switch (argc) {
+ case 6:
+ if (!strcasecmp(argv[3], "registered"))
+ registeredonly = 1;
+ else
+ return RESULT_SHOWUSAGE;
+ if (!strcasecmp(argv[4], "like")) {
+ if (regcomp(&regexbuf, argv[5], REG_EXTENDED | REG_NOSUB))
+ return RESULT_SHOWUSAGE;
+ havepattern = 1;
+ } else
+ return RESULT_SHOWUSAGE;
+ break;
+ case 5:
+ if (!strcasecmp(argv[3], "like")) {
+ if (regcomp(&regexbuf, argv[4], REG_EXTENDED | REG_NOSUB))
+ return RESULT_SHOWUSAGE;
+ havepattern = 1;
+ } else
+ return RESULT_SHOWUSAGE;
+ break;
+ case 4:
+ if (!strcasecmp(argv[3], "registered"))
+ registeredonly = 1;
+ else
+ return RESULT_SHOWUSAGE;
+ break;
+ case 3:
+ break;
+ default:
+ return RESULT_SHOWUSAGE;
+ }
+
+
+ if (!s)
+ ast_cli(fd, FORMAT2, "Name/Username", "Host", " ", "Mask", "Port", " ", "Status", term);
+
+ i = ao2_iterator_init(peers, 0);
+ for (peer = ao2_iterator_next(&i); peer;
+ peer_unref(peer), peer = ao2_iterator_next(&i)) {
+ char nm[20];
+ char status[20];
+ char srch[2000];
+ int retstatus;
+
+ if (registeredonly && !peer->addr.sin_addr.s_addr)
+ continue;
+ if (havepattern && regexec(&regexbuf, peer->name, 0, NULL, 0))
+ continue;
+
+ if (!ast_strlen_zero(peer->username))
+ snprintf(name, sizeof(name), "%s/%s", peer->name, peer->username);
+ else
+ ast_copy_string(name, peer->name, sizeof(name));
+
+ retstatus = peer_status(peer, status, sizeof(status));
+ if (retstatus > 0)
+ online_peers++;
+ else if (!retstatus)
+ offline_peers++;
+ else
+ unmonitored_peers++;
+
+ ast_copy_string(nm, ast_inet_ntoa(peer->mask), sizeof(nm));
+
+ snprintf(srch, sizeof(srch), FORMAT, name,
+ peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "(Unspecified)",
+ ast_test_flag(peer, IAX_DYNAMIC) ? "(D)" : "(S)",
+ nm,
+ ntohs(peer->addr.sin_port), ast_test_flag(peer, IAX_TRUNK) ? "(T)" : " ",
+ peer->encmethods ? "(E)" : " ", status, term);
+
+ if (s)
+ astman_append(s,
+ "Event: PeerEntry\r\n%s"
+ "Channeltype: IAX2\r\n"
+ "ChanObjectType: peer\r\n"
+ "ObjectName: %s\r\n"
+ "IPaddress: %s\r\n"
+ "IPport: %d\r\n"
+ "Dynamic: %s\r\n"
+ "Status: %s\r\n\r\n",
+ idtext,
+ name,
+ peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "-none-",
+ ntohs(peer->addr.sin_port),
+ ast_test_flag(peer, IAX_DYNAMIC) ? "yes" : "no",
+ status);
+
+ else
+ ast_cli(fd, FORMAT, name,
+ peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "(Unspecified)",
+ ast_test_flag(peer, IAX_DYNAMIC) ? "(D)" : "(S)",
+ nm,
+ ntohs(peer->addr.sin_port), ast_test_flag(peer, IAX_TRUNK) ? "(T)" : " ",
+ peer->encmethods ? "(E)" : " ", status, term);
+ total_peers++;
+ }
+
+ if (!s)
+ ast_cli(fd,"%d iax2 peers [%d online, %d offline, %d unmonitored]%s", total_peers, online_peers, offline_peers, unmonitored_peers, term);
+
+ if (havepattern)
+ regfree(&regexbuf);
+
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static char *handle_cli_iax2_show_threads(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct iax2_thread *thread = NULL;
+ time_t t;
+ int threadcount = 0, dynamiccount = 0;
+ char type;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show threads";
+ e->usage =
+ "Usage: iax2 show threads\n"
+ " Lists status of IAX helper threads\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ ast_cli(a->fd, "IAX2 Thread Information\n");
+ time(&t);
+ ast_cli(a->fd, "Idle Threads:\n");
+ AST_LIST_LOCK(&idle_list);
+ AST_LIST_TRAVERSE(&idle_list, thread, list) {
+#ifdef DEBUG_SCHED_MULTITHREAD
+ ast_cli(a->fd, "Thread %d: state=%d, update=%d, actions=%d, func='%s'\n",
+ thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions, thread->curfunc);
+#else
+ ast_cli(a->fd, "Thread %d: state=%d, update=%d, actions=%d\n",
+ thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions);
+#endif
+ threadcount++;
+ }
+ AST_LIST_UNLOCK(&idle_list);
+ ast_cli(a->fd, "Active Threads:\n");
+ AST_LIST_LOCK(&active_list);
+ AST_LIST_TRAVERSE(&active_list, thread, list) {
+ if (thread->type == IAX_THREAD_TYPE_DYNAMIC)
+ type = 'D';
+ else
+ type = 'P';
+#ifdef DEBUG_SCHED_MULTITHREAD
+ ast_cli(a->fd, "Thread %c%d: state=%d, update=%d, actions=%d, func='%s'\n",
+ type, thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions, thread->curfunc);
+#else
+ ast_cli(a->fd, "Thread %c%d: state=%d, update=%d, actions=%d\n",
+ type, thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions);
+#endif
+ threadcount++;
+ }
+ AST_LIST_UNLOCK(&active_list);
+ ast_cli(a->fd, "Dynamic Threads:\n");
+ AST_LIST_LOCK(&dynamic_list);
+ AST_LIST_TRAVERSE(&dynamic_list, thread, list) {
+#ifdef DEBUG_SCHED_MULTITHREAD
+ ast_cli(a->fd, "Thread %d: state=%d, update=%d, actions=%d, func='%s'\n",
+ thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions, thread->curfunc);
+#else
+ ast_cli(a->fd, "Thread %d: state=%d, update=%d, actions=%d\n",
+ thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions);
+#endif
+ dynamiccount++;
+ }
+ AST_LIST_UNLOCK(&dynamic_list);
+ ast_cli(a->fd, "%d of %d threads accounted for with %d dynamic threads\n", threadcount, iaxthreadcount, dynamiccount);
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct iax2_peer *p;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 unregister";
+ e->usage =
+ "Usage: iax2 unregister <peername>\n"
+ " Unregister (force expiration) an IAX2 peer from the registry.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_iax2_unregister(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ p = find_peer(a->argv[2], 1);
+ if (p) {
+ if (p->expire > 0) {
+ struct iax2_peer tmp_peer = {
+ .name = a->argv[2],
+ };
+ struct iax2_peer *peer;
+
+ peer = ao2_find(peers, &tmp_peer, OBJ_POINTER);
+ if (peer) {
+ expire_registry(peer_ref(peer)); /* will release its own reference when done */
+ peer_unref(peer); /* ref from ao2_find() */
+ ast_cli(a->fd, "Peer %s unregistered\n", a->argv[2]);
+ } else {
+ ast_cli(a->fd, "Peer %s not found\n", a->argv[2]);
+ }
+ } else {
+ ast_cli(a->fd, "Peer %s not registered\n", a->argv[2]);
+ }
+ } else {
+ ast_cli(a->fd, "Peer unknown: %s. Not unregistered\n", a->argv[2]);
+ }
+ return CLI_SUCCESS;
+}
+
+static char *complete_iax2_unregister(const char *line, const char *word, int pos, int state)
+{
+ int which = 0;
+ struct iax2_peer *p = NULL;
+ char *res = NULL;
+ int wordlen = strlen(word);
+
+ /* 0 - iax2; 1 - unregister; 2 - <peername> */
+ if (pos == 2) {
+ struct ao2_iterator i = ao2_iterator_init(peers, 0);
+ while ((p = ao2_iterator_next(&i))) {
+ if (!strncasecmp(p->name, word, wordlen) &&
+ ++which > state && p->expire > 0) {
+ res = ast_strdup(p->name);
+ peer_unref(p);
+ break;
+ }
+ peer_unref(p);
+ }
+ }
+
+ return res;
+}
+
+static char *handle_cli_iax2_show_peers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show peers";
+ e->usage =
+ "Usage: iax2 show peers [registered] [like <pattern>]\n"
+ " Lists all known IAX2 peers.\n"
+ " Optional 'registered' argument lists only peers with known addresses.\n"
+ " Optional regular expression pattern is used to filter the peer list.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ switch (__iax2_show_peers(0, a->fd, NULL, a->argc, a->argv)) {
+ case RESULT_SHOWUSAGE:
+ return CLI_SHOWUSAGE;
+ case RESULT_FAILURE:
+ return CLI_FAILURE;
+ default:
+ return CLI_SUCCESS;
+ }
+}
+
+static int manager_iax2_show_netstats(struct mansession *s, const struct message *m)
+{
+ ast_cli_netstats(s, -1, 0);
+ astman_append(s, "\r\n");
+ return RESULT_SUCCESS;
+}
+
+static char *handle_cli_iax2_show_firmware(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct iax_firmware *cur = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show firmware";
+ e->usage =
+ "Usage: iax2 show firmware\n"
+ " Lists all known IAX firmware images.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3 && a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ ast_cli(a->fd, "%-15.15s %-15.15s %-15.15s\n", "Device", "Version", "Size");
+ AST_LIST_LOCK(&firmwares);
+ AST_LIST_TRAVERSE(&firmwares, cur, list) {
+ if ((a->argc == 3) || (!strcasecmp(a->argv[3], (char *) cur->fwh->devname))) {
+ ast_cli(a->fd, "%-15.15s %-15d %-15d\n", cur->fwh->devname,
+ ntohs(cur->fwh->version), (int)ntohl(cur->fwh->datalen));
+ }
+ }
+ AST_LIST_UNLOCK(&firmwares);
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief callback to display iax peers in manager */
+static int manager_iax2_show_peers(struct mansession *s, const struct message *m)
+{
+ char *a[] = { "iax2", "show", "users" };
+ const char *id = astman_get_header(m,"ActionID");
+ char idtext[256] = "";
+
+ if (!ast_strlen_zero(id))
+ snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id);
+ astman_send_ack(s, m, "Peer status list will follow");
+ return __iax2_show_peers(1, -1, s, 3, a );
+}
+
+/*! \brief callback to display iax peers in manager format */
+static int manager_iax2_show_peer_list(struct mansession *s, const struct message *m)
+{
+ struct iax2_peer *peer = NULL;
+ int peer_count = 0;
+ char nm[20];
+ char status[20];
+ const char *id = astman_get_header(m,"ActionID");
+ char idtext[256] = "";
+ struct ao2_iterator i;
+
+ if (!ast_strlen_zero(id))
+ snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id);
+
+ astman_append(s, "Response: Success\r\n%sMessage: IAX Peer status list will follow\r\n\r\n", idtext);
+
+
+ i = ao2_iterator_init(peers, 0);
+ for (peer = ao2_iterator_next(&i); peer; peer_unref(peer), peer = ao2_iterator_next(&i)) {
+
+ astman_append(s, "Event: PeerEntry\r\n%sChanneltype: IAX\r\n", idtext);
+ if (!ast_strlen_zero(peer->username)) {
+ astman_append(s, "ObjectName: %s\r\nObjectUsername: %s\r\n", peer->name, peer->username);
+ } else {
+ astman_append(s, "ObjectName: %s\r\n", peer->name);
+ }
+ astman_append(s, "ChanObjectType: peer\r\n");
+ astman_append(s, "IPaddress: %s\r\n", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "-none-");
+ ast_copy_string(nm, ast_inet_ntoa(peer->mask), sizeof(nm));
+ astman_append(s, "Mask: %s\r\n", nm);
+ astman_append(s, "Port: %d\r\n", ntohs(peer->addr.sin_port));
+ astman_append(s, "Dynamic: %s\r\n", ast_test_flag(peer, IAX_DYNAMIC) ? "Yes" : "No");
+ peer_status(peer, status, sizeof(status));
+ astman_append(s, "Status: %s\r\n\r\n", status);
+ peer_count++;
+ }
+
+ astman_append(s, "Event: PeerlistComplete\r\n%sListItems: %d\r\n\r\n", idtext, peer_count);
+ return RESULT_SUCCESS;
+}
+
+
+static char *regstate2str(int regstate)
+{
+ switch(regstate) {
+ case REG_STATE_UNREGISTERED:
+ return "Unregistered";
+ case REG_STATE_REGSENT:
+ return "Request Sent";
+ case REG_STATE_AUTHSENT:
+ return "Auth. Sent";
+ case REG_STATE_REGISTERED:
+ return "Registered";
+ case REG_STATE_REJECTED:
+ return "Rejected";
+ case REG_STATE_TIMEOUT:
+ return "Timeout";
+ case REG_STATE_NOAUTH:
+ return "No Authentication";
+ default:
+ return "Unknown";
+ }
+}
+
+static char *handle_cli_iax2_show_registry(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT2 "%-20.20s %-6.6s %-10.10s %-20.20s %8.8s %s\n"
+#define FORMAT "%-20.20s %-6.6s %-10.10s %-20.20s %8d %s\n"
+ struct iax2_registry *reg = NULL;
+ char host[80];
+ char perceived[80];
+ int counter = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show registry";
+ e->usage =
+ "Usage: iax2 show registry\n"
+ " Lists all registration requests and status.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ ast_cli(a->fd, FORMAT2, "Host", "dnsmgr", "Username", "Perceived", "Refresh", "State");
+ AST_LIST_LOCK(&registrations);
+ AST_LIST_TRAVERSE(&registrations, reg, entry) {
+ snprintf(host, sizeof(host), "%s:%d", ast_inet_ntoa(reg->addr.sin_addr), ntohs(reg->addr.sin_port));
+ if (reg->us.sin_addr.s_addr)
+ snprintf(perceived, sizeof(perceived), "%s:%d", ast_inet_ntoa(reg->us.sin_addr), ntohs(reg->us.sin_port));
+ else
+ ast_copy_string(perceived, "<Unregistered>", sizeof(perceived));
+ ast_cli(a->fd, FORMAT, host,
+ (reg->dnsmgr) ? "Y" : "N",
+ reg->username, perceived, reg->refresh, regstate2str(reg->regstate));
+ counter++;
+ }
+ AST_LIST_UNLOCK(&registrations);
+ ast_cli(a->fd, "%d IAX2 registrations.\n", counter);
+ return CLI_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static char *handle_cli_iax2_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT2 "%-20.20s %-15.15s %-10.10s %-11.11s %-11.11s %-7.7s %-6.6s %-6.6s %s\n"
+#define FORMAT "%-20.20s %-15.15s %-10.10s %5.5d/%5.5d %5.5d/%5.5d %-5.5dms %-4.4dms %-4.4dms %-6.6s\n"
+#define FORMATB "%-20.20s %-15.15s %-10.10s %5.5d/%5.5d %5.5d/%5.5d [Native Bridged to ID=%5.5d]\n"
+ int x;
+ int numchans = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show channels";
+ e->usage =
+ "Usage: iax2 show channels\n"
+ " Lists all currently active IAX channels.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ ast_cli(a->fd, FORMAT2, "Channel", "Peer", "Username", "ID (Lo/Rem)", "Seq (Tx/Rx)", "Lag", "Jitter", "JitBuf", "Format");
+ for (x = 0; x < IAX_MAX_CALLS; x++) {
+ ast_mutex_lock(&iaxsl[x]);
+ if (iaxs[x]) {
+ int lag, jitter, localdelay;
+ jb_info jbinfo;
+
+ if (ast_test_flag(iaxs[x], IAX_USEJITTERBUF)) {
+ jb_getinfo(iaxs[x]->jb, &jbinfo);
+ jitter = jbinfo.jitter;
+ localdelay = jbinfo.current - jbinfo.min;
+ } else {
+ jitter = -1;
+ localdelay = 0;
+ }
+ lag = iaxs[x]->remote_rr.delay;
+ ast_cli(a->fd, FORMAT,
+ iaxs[x]->owner ? iaxs[x]->owner->name : "(None)",
+ ast_inet_ntoa(iaxs[x]->addr.sin_addr),
+ S_OR(iaxs[x]->username, "(None)"),
+ iaxs[x]->callno, iaxs[x]->peercallno,
+ iaxs[x]->oseqno, iaxs[x]->iseqno,
+ lag,
+ jitter,
+ localdelay,
+ ast_getformatname(iaxs[x]->voiceformat) );
+ numchans++;
+ }
+ ast_mutex_unlock(&iaxsl[x]);
+ }
+ ast_cli(a->fd, "%d active IAX channel%s\n", numchans, (numchans != 1) ? "s" : "");
+ return CLI_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+#undef FORMATB
+}
+
+static int ast_cli_netstats(struct mansession *s, int fd, int limit_fmt)
+{
+ int x;
+ int numchans = 0;
+ for (x=0;x<IAX_MAX_CALLS;x++) {
+ ast_mutex_lock(&iaxsl[x]);
+ if (iaxs[x]) {
+ int localjitter, localdelay, locallost, locallosspct, localdropped, localooo;
+ char *fmt;
+ jb_info jbinfo;
+
+ if(ast_test_flag(iaxs[x], IAX_USEJITTERBUF)) {
+ jb_getinfo(iaxs[x]->jb, &jbinfo);
+ localjitter = jbinfo.jitter;
+ localdelay = jbinfo.current - jbinfo.min;
+ locallost = jbinfo.frames_lost;
+ locallosspct = jbinfo.losspct/1000;
+ localdropped = jbinfo.frames_dropped;
+ localooo = jbinfo.frames_ooo;
+ } else {
+ localjitter = -1;
+ localdelay = 0;
+ locallost = -1;
+ locallosspct = -1;
+ localdropped = 0;
+ localooo = -1;
+ }
+ if (limit_fmt)
+ fmt = "%-25.25s %4d %4d %4d %5d %3d %5d %4d %6d %4d %4d %5d %3d %5d %4d %6d\n";
+ else
+ fmt = "%s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n";
+ if (s)
+
+ astman_append(s, fmt,
+ iaxs[x]->owner ? iaxs[x]->owner->name : "(None)",
+ iaxs[x]->pingtime,
+ localjitter,
+ localdelay,
+ locallost,
+ locallosspct,
+ localdropped,
+ localooo,
+ iaxs[x]->frames_received/1000,
+ iaxs[x]->remote_rr.jitter,
+ iaxs[x]->remote_rr.delay,
+ iaxs[x]->remote_rr.losscnt,
+ iaxs[x]->remote_rr.losspct,
+ iaxs[x]->remote_rr.dropped,
+ iaxs[x]->remote_rr.ooo,
+ iaxs[x]->remote_rr.packets/1000);
+ else
+ ast_cli(fd, fmt,
+ iaxs[x]->owner ? iaxs[x]->owner->name : "(None)",
+ iaxs[x]->pingtime,
+ localjitter,
+ localdelay,
+ locallost,
+ locallosspct,
+ localdropped,
+ localooo,
+ iaxs[x]->frames_received/1000,
+ iaxs[x]->remote_rr.jitter,
+ iaxs[x]->remote_rr.delay,
+ iaxs[x]->remote_rr.losscnt,
+ iaxs[x]->remote_rr.losspct,
+ iaxs[x]->remote_rr.dropped,
+ iaxs[x]->remote_rr.ooo,
+ iaxs[x]->remote_rr.packets/1000
+ );
+ numchans++;
+ }
+ ast_mutex_unlock(&iaxsl[x]);
+ }
+
+ return numchans;
+}
+
+static char *handle_cli_iax2_show_netstats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int numchans = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show netstats";
+ e->usage =
+ "Usage: iax2 show netstats\n"
+ " Lists network status for all currently active IAX channels.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ ast_cli(a->fd, " -------- LOCAL --------------------- -------- REMOTE --------------------\n");
+ ast_cli(a->fd, "Channel RTT Jit Del Lost %% Drop OOO Kpkts Jit Del Lost %% Drop OOO Kpkts\n");
+ numchans = ast_cli_netstats(NULL, a->fd, 1);
+ ast_cli(a->fd, "%d active IAX channel%s\n", numchans, (numchans != 1) ? "s" : "");
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 set debug";
+ e->usage =
+ "Usage: iax2 set debug\n"
+ " Enables dumping of IAX packets for debugging purposes.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc < 2 || a->argc > 3)
+ return CLI_SHOWUSAGE;
+ iaxdebug = 1;
+ ast_cli(a->fd, "IAX2 Debugging Enabled\n");
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_set_debug_off(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 set debug off";
+ e->usage =
+ "Usage: iax2 set debug off\n"
+ " Disables dumping of IAX packets for debugging purposes.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc < 3 || a->argc > 4)
+ return CLI_SHOWUSAGE;
+ iaxdebug = 0;
+ ast_cli(a->fd, "IAX2 Debugging Disabled\n");
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_set_debug_trunk(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 set debug trunk";
+ e->usage =
+ "Usage: iax2 set debug trunk\n"
+ " Requests current status of IAX trunking\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc < 3 || a->argc > 4)
+ return CLI_SHOWUSAGE;
+ iaxtrunkdebug = 1;
+ ast_cli(a->fd, "IAX2 Trunk Debugging Requested\n");
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_set_debug_trunk_off(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 set debug trunk off";
+ e->usage =
+ "Usage: iax2 set debug trunk off\n"
+ " Disables debugging of IAX trunking\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc < 4 || a->argc > 5)
+ return CLI_SHOWUSAGE;
+ iaxtrunkdebug = 0;
+ ast_cli(a->fd, "IAX2 Trunk Debugging Disabled\n");
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_set_debug_jb(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 set debug jb";
+ e->usage =
+ "Usage: iax2 set debug jb\n"
+ " Enables jitterbuffer debugging information\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc < 3 || a->argc > 4)
+ return CLI_SHOWUSAGE;
+ jb_setoutput(jb_error_output, jb_warning_output, jb_debug_output);
+ ast_cli(a->fd, "IAX2 Jitterbuffer Debugging Enabled\n");
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_iax2_set_debug_jb_off(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 set debug jb off";
+ e->usage =
+ "Usage: iax2 set debug jb off\n"
+ " Disables jitterbuffer debugging information\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc < 4 || a->argc > 5)
+ return CLI_SHOWUSAGE;
+ jb_setoutput(jb_error_output, jb_warning_output, NULL);
+ ast_cli(a->fd, "IAX2 Jitterbuffer Debugging Disabled\n");
+ return CLI_SUCCESS;
+}
+
+static int iax2_write(struct ast_channel *c, struct ast_frame *f)
+{
+ unsigned short callno = PTR_TO_CALLNO(c->tech_pvt);
+ int res = -1;
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno]) {
+ /* If there's an outstanding error, return failure now */
+ if (!iaxs[callno]->error) {
+ if (ast_test_flag(iaxs[callno], IAX_ALREADYGONE))
+ res = 0;
+ /* Don't waste bandwidth sending null frames */
+ else if (f->frametype == AST_FRAME_NULL)
+ res = 0;
+ else if ((f->frametype == AST_FRAME_VOICE) && ast_test_flag(iaxs[callno], IAX_QUELCH))
+ res = 0;
+ else if (!ast_test_flag(&iaxs[callno]->state, IAX_STATE_STARTED))
+ res = 0;
+ else
+ /* Simple, just queue for transmission */
+ res = iax2_send(iaxs[callno], f, 0, -1, 0, 0, 0);
+ } else {
+ ast_debug(1, "Write error: %s\n", strerror(errno));
+ }
+ }
+ /* If it's already gone, just return */
+ ast_mutex_unlock(&iaxsl[callno]);
+ return res;
+}
+
+static int __send_command(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno,
+ int now, int transfer, int final)
+{
+ struct ast_frame f = { 0, };
+
+ f.frametype = type;
+ f.subclass = command;
+ f.datalen = datalen;
+ f.src = __FUNCTION__;
+ f.data = (void *) data;
+
+ return iax2_send(i, &f, ts, seqno, now, transfer, final);
+}
+
+static int send_command(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno)
+{
+ return __send_command(i, type, command, ts, data, datalen, seqno, 0, 0, 0);
+}
+
+static int send_command_locked(unsigned short callno, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno)
+{
+ int res;
+ ast_mutex_lock(&iaxsl[callno]);
+ res = send_command(iaxs[callno], type, command, ts, data, datalen, seqno);
+ ast_mutex_unlock(&iaxsl[callno]);
+ return res;
+}
+
+/*!
+ * \note Since this function calls iax2_predestroy() -> iax2_queue_hangup(),
+ * the pvt struct for the given call number may disappear during its
+ * execution.
+ */
+static int send_command_final(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno)
+{
+ int call_num = i->callno;
+ /* It is assumed that the callno has already been locked */
+ iax2_predestroy(i->callno);
+ if (!iaxs[call_num])
+ return -1;
+ return __send_command(i, type, command, ts, data, datalen, seqno, 0, 0, 1);
+}
+
+static int send_command_immediate(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno)
+{
+ return __send_command(i, type, command, ts, data, datalen, seqno, 1, 0, 0);
+}
+
+static int send_command_transfer(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen)
+{
+ return __send_command(i, type, command, ts, data, datalen, 0, 0, 1, 0);
+}
+
+static int apply_context(struct iax2_context *con, const char *context)
+{
+ while(con) {
+ if (!strcmp(con->context, context) || !strcmp(con->context, "*"))
+ return -1;
+ con = con->next;
+ }
+ return 0;
+}
+
+
+static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies)
+{
+ /* Start pessimistic */
+ int res = -1;
+ int version = 2;
+ struct iax2_user *user = NULL, *best = NULL;
+ int bestscore = 0;
+ int gotcapability = 0;
+ struct ast_variable *v = NULL, *tmpvar = NULL;
+ struct ao2_iterator i;
+
+ if (!iaxs[callno])
+ return res;
+ if (ies->called_number)
+ ast_string_field_set(iaxs[callno], exten, ies->called_number);
+ if (ies->calling_number) {
+ ast_shrink_phone_number(ies->calling_number);
+ ast_string_field_set(iaxs[callno], cid_num, ies->calling_number);
+ }
+ if (ies->calling_name)
+ ast_string_field_set(iaxs[callno], cid_name, ies->calling_name);
+ if (ies->calling_ani)
+ ast_string_field_set(iaxs[callno], ani, ies->calling_ani);
+ if (ies->dnid)
+ ast_string_field_set(iaxs[callno], dnid, ies->dnid);
+ if (ies->rdnis)
+ ast_string_field_set(iaxs[callno], rdnis, ies->rdnis);
+ if (ies->called_context)
+ ast_string_field_set(iaxs[callno], context, ies->called_context);
+ if (ies->language)
+ ast_string_field_set(iaxs[callno], language, ies->language);
+ if (ies->username)
+ ast_string_field_set(iaxs[callno], username, ies->username);
+ if (ies->calling_ton > -1)
+ iaxs[callno]->calling_ton = ies->calling_ton;
+ if (ies->calling_tns > -1)
+ iaxs[callno]->calling_tns = ies->calling_tns;
+ if (ies->calling_pres > -1)
+ iaxs[callno]->calling_pres = ies->calling_pres;
+ if (ies->format)
+ iaxs[callno]->peerformat = ies->format;
+ if (ies->adsicpe)
+ iaxs[callno]->peeradsicpe = ies->adsicpe;
+ if (ies->capability) {
+ gotcapability = 1;
+ iaxs[callno]->peercapability = ies->capability;
+ }
+ if (ies->version)
+ version = ies->version;
+
+ /* Use provided preferences until told otherwise for actual preferences */
+ if(ies->codec_prefs) {
+ ast_codec_pref_convert(&iaxs[callno]->rprefs, ies->codec_prefs, 32, 0);
+ ast_codec_pref_convert(&iaxs[callno]->prefs, ies->codec_prefs, 32, 0);
+ }
+
+ if (!gotcapability)
+ iaxs[callno]->peercapability = iaxs[callno]->peerformat;
+ if (version > IAX_PROTO_VERSION) {
+ ast_log(LOG_WARNING, "Peer '%s' has too new a protocol version (%d) for me\n",
+ ast_inet_ntoa(sin->sin_addr), version);
+ return res;
+ }
+ /* Search the userlist for a compatible entry, and fill in the rest */
+ i = ao2_iterator_init(users, 0);
+ while ((user = ao2_iterator_next(&i))) {
+ if ((ast_strlen_zero(iaxs[callno]->username) || /* No username specified */
+ !strcmp(iaxs[callno]->username, user->name)) /* Or this username specified */
+ && ast_apply_ha(user->ha, sin) /* Access is permitted from this IP */
+ && (ast_strlen_zero(iaxs[callno]->context) || /* No context specified */
+ apply_context(user->contexts, iaxs[callno]->context))) { /* Context is permitted */
+ if (!ast_strlen_zero(iaxs[callno]->username)) {
+ /* Exact match, stop right now. */
+ if (best)
+ user_unref(best);
+ best = user;
+ break;
+ } else if (ast_strlen_zero(user->secret) && ast_strlen_zero(user->inkeys)) {
+ /* No required authentication */
+ if (user->ha) {
+ /* There was host authentication and we passed, bonus! */
+ if (bestscore < 4) {
+ bestscore = 4;
+ if (best)
+ user_unref(best);
+ best = user;
+ continue;
+ }
+ } else {
+ /* No host access, but no secret, either, not bad */
+ if (bestscore < 3) {
+ bestscore = 3;
+ if (best)
+ user_unref(best);
+ best = user;
+ continue;
+ }
+ }
+ } else {
+ if (user->ha) {
+ /* Authentication, but host access too, eh, it's something.. */
+ if (bestscore < 2) {
+ bestscore = 2;
+ if (best)
+ user_unref(best);
+ best = user;
+ continue;
+ }
+ } else {
+ /* Authentication and no host access... This is our baseline */
+ if (bestscore < 1) {
+ bestscore = 1;
+ if (best)
+ user_unref(best);
+ best = user;
+ continue;
+ }
+ }
+ }
+ }
+ user_unref(user);
+ }
+ user = best;
+ if (!user && !ast_strlen_zero(iaxs[callno]->username)) {
+ user = realtime_user(iaxs[callno]->username, sin);
+ if (user && !ast_strlen_zero(iaxs[callno]->context) && /* No context specified */
+ !apply_context(user->contexts, iaxs[callno]->context)) { /* Context is permitted */
+ user = user_unref(user);
+ }
+ }
+ if (user) {
+ /* We found our match (use the first) */
+ /* copy vars */
+ for (v = user->vars ; v ; v = v->next) {
+ if((tmpvar = ast_variable_new(v->name, v->value, v->file))) {
+ tmpvar->next = iaxs[callno]->vars;
+ iaxs[callno]->vars = tmpvar;
+ }
+ }
+ /* If a max AUTHREQ restriction is in place, activate it */
+ if (user->maxauthreq > 0)
+ ast_set_flag(iaxs[callno], IAX_MAXAUTHREQ);
+ iaxs[callno]->prefs = user->prefs;
+ ast_copy_flags(iaxs[callno], user, IAX_CODEC_USER_FIRST);
+ ast_copy_flags(iaxs[callno], user, IAX_CODEC_NOPREFS);
+ ast_copy_flags(iaxs[callno], user, IAX_CODEC_NOCAP);
+ iaxs[callno]->encmethods = user->encmethods;
+ /* Store the requested username if not specified */
+ if (ast_strlen_zero(iaxs[callno]->username))
+ ast_string_field_set(iaxs[callno], username, user->name);
+ /* Store whether this is a trunked call, too, of course, and move if appropriate */
+ ast_copy_flags(iaxs[callno], user, IAX_TRUNK);
+ iaxs[callno]->capability = user->capability;
+ /* And use the default context */
+ if (ast_strlen_zero(iaxs[callno]->context)) {
+ if (user->contexts)
+ ast_string_field_set(iaxs[callno], context, user->contexts->context);
+ else
+ ast_string_field_set(iaxs[callno], context, context);
+ }
+ /* And any input keys */
+ ast_string_field_set(iaxs[callno], inkeys, user->inkeys);
+ /* And the permitted authentication methods */
+ iaxs[callno]->authmethods = user->authmethods;
+ iaxs[callno]->adsi = user->adsi;
+ /* If they have callerid, override the given caller id. Always store the ANI */
+ if (!ast_strlen_zero(iaxs[callno]->cid_num) || !ast_strlen_zero(iaxs[callno]->cid_name)) {
+ if (ast_test_flag(user, IAX_HASCALLERID)) {
+ iaxs[callno]->calling_tns = 0;
+ iaxs[callno]->calling_ton = 0;
+ ast_string_field_set(iaxs[callno], cid_num, user->cid_num);
+ ast_string_field_set(iaxs[callno], cid_name, user->cid_name);
+ iaxs[callno]->calling_pres = AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN;
+ }
+ if (ast_strlen_zero(iaxs[callno]->ani))
+ ast_string_field_set(iaxs[callno], ani, user->cid_num);
+ } else {
+ iaxs[callno]->calling_pres = AST_PRES_NUMBER_NOT_AVAILABLE;
+ }
+ if (!ast_strlen_zero(user->accountcode))
+ ast_string_field_set(iaxs[callno], accountcode, user->accountcode);
+ if (!ast_strlen_zero(user->mohinterpret))
+ ast_string_field_set(iaxs[callno], mohinterpret, user->mohinterpret);
+ if (!ast_strlen_zero(user->mohsuggest))
+ ast_string_field_set(iaxs[callno], mohsuggest, user->mohsuggest);
+ if (user->amaflags)
+ iaxs[callno]->amaflags = user->amaflags;
+ if (!ast_strlen_zero(user->language))
+ ast_string_field_set(iaxs[callno], language, user->language);
+ ast_copy_flags(iaxs[callno], user, IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF);
+ /* Keep this check last */
+ if (!ast_strlen_zero(user->dbsecret)) {
+ char *family, *key=NULL;
+ char buf[80];
+ family = ast_strdupa(user->dbsecret);
+ key = strchr(family, '/');
+ if (key) {
+ *key = '\0';
+ key++;
+ }
+ if (!key || ast_db_get(family, key, buf, sizeof(buf)))
+ ast_log(LOG_WARNING, "Unable to retrieve database password for family/key '%s'!\n", user->dbsecret);
+ else
+ ast_string_field_set(iaxs[callno], secret, buf);
+ } else
+ ast_string_field_set(iaxs[callno], secret, user->secret);
+ res = 0;
+ user = user_unref(user);
+ }
+ ast_set2_flag(iaxs[callno], iax2_getpeertrunk(*sin), IAX_TRUNK);
+ return res;
+}
+
+static int raw_hangup(struct sockaddr_in *sin, unsigned short src, unsigned short dst, int sockfd)
+{
+ struct ast_iax2_full_hdr fh;
+ fh.scallno = htons(src | IAX_FLAG_FULL);
+ fh.dcallno = htons(dst);
+ fh.ts = 0;
+ fh.oseqno = 0;
+ fh.iseqno = 0;
+ fh.type = AST_FRAME_IAX;
+ fh.csub = compress_subclass(IAX_COMMAND_INVAL);
+ if (iaxdebug)
+ iax_showframe(NULL, &fh, 0, sin, 0);
+#if 0
+ if (option_debug)
+#endif
+ ast_debug(1, "Raw Hangup %s:%d, src=%d, dst=%d\n",
+ ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), src, dst);
+ return sendto(sockfd, &fh, sizeof(fh), 0, (struct sockaddr *)sin, sizeof(*sin));
+}
+
+static void merge_encryption(struct chan_iax2_pvt *p, unsigned int enc)
+{
+ /* Select exactly one common encryption if there are any */
+ p->encmethods &= enc;
+ if (p->encmethods) {
+ if (p->encmethods & IAX_ENCRYPT_AES128)
+ p->encmethods = IAX_ENCRYPT_AES128;
+ else
+ p->encmethods = 0;
+ }
+}
+
+/*!
+ * \pre iaxsl[call_num] is locked
+ *
+ * \note Since this function calls send_command_final(), the pvt struct for the given
+ * call number may disappear while executing this function.
+ */
+static int authenticate_request(int call_num)
+{
+ struct iax_ie_data ied;
+ int res = -1, authreq_restrict = 0;
+ char challenge[10];
+ struct chan_iax2_pvt *p = iaxs[call_num];
+
+ memset(&ied, 0, sizeof(ied));
+
+ /* If an AUTHREQ restriction is in place, make sure we can send an AUTHREQ back */
+ if (ast_test_flag(p, IAX_MAXAUTHREQ)) {
+ struct iax2_user *user, tmp_user = {
+ .name = p->username,
+ };
+
+ user = ao2_find(users, &tmp_user, OBJ_POINTER);
+ if (user) {
+ if (user->curauthreq == user->maxauthreq)
+ authreq_restrict = 1;
+ else
+ user->curauthreq++;
+ user = user_unref(user);
+ }
+ }
+
+ /* If the AUTHREQ limit test failed, send back an error */
+ if (authreq_restrict) {
+ iax_ie_append_str(&ied, IAX_IE_CAUSE, "Unauthenticated call limit reached");
+ iax_ie_append_byte(&ied, IAX_IE_CAUSECODE, AST_CAUSE_CALL_REJECTED);
+ send_command_final(p, AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied.buf, ied.pos, -1);
+ return 0;
+ }
+
+ iax_ie_append_short(&ied, IAX_IE_AUTHMETHODS, p->authmethods);
+ if (p->authmethods & (IAX_AUTH_MD5 | IAX_AUTH_RSA)) {
+ snprintf(challenge, sizeof(challenge), "%d", (int)ast_random());
+ ast_string_field_set(p, challenge, challenge);
+ /* snprintf(p->challenge, sizeof(p->challenge), "%d", (int)ast_random()); */
+ iax_ie_append_str(&ied, IAX_IE_CHALLENGE, p->challenge);
+ }
+ if (p->encmethods)
+ iax_ie_append_short(&ied, IAX_IE_ENCRYPTION, p->encmethods);
+
+ iax_ie_append_str(&ied,IAX_IE_USERNAME, p->username);
+
+ res = send_command(p, AST_FRAME_IAX, IAX_COMMAND_AUTHREQ, 0, ied.buf, ied.pos, -1);
+
+ if (p->encmethods)
+ ast_set_flag(p, IAX_ENCRYPTED);
+
+ return res;
+}
+
+static int authenticate_verify(struct chan_iax2_pvt *p, struct iax_ies *ies)
+{
+ char requeststr[256];
+ char md5secret[256] = "";
+ char secret[256] = "";
+ char rsasecret[256] = "";
+ int res = -1;
+ int x;
+ struct iax2_user *user, tmp_user = {
+ .name = p->username,
+ };
+
+ user = ao2_find(users, &tmp_user, OBJ_POINTER);
+ if (user) {
+ if (ast_test_flag(p, IAX_MAXAUTHREQ)) {
+ ast_atomic_fetchadd_int(&user->curauthreq, -1);
+ ast_clear_flag(p, IAX_MAXAUTHREQ);
+ }
+ ast_string_field_set(p, host, user->name);
+ user = user_unref(user);
+ }
+
+ if (!ast_test_flag(&p->state, IAX_STATE_AUTHENTICATED))
+ return res;
+ if (ies->password)
+ ast_copy_string(secret, ies->password, sizeof(secret));
+ if (ies->md5_result)
+ ast_copy_string(md5secret, ies->md5_result, sizeof(md5secret));
+ if (ies->rsa_result)
+ ast_copy_string(rsasecret, ies->rsa_result, sizeof(rsasecret));
+ if ((p->authmethods & IAX_AUTH_RSA) && !ast_strlen_zero(rsasecret) && !ast_strlen_zero(p->inkeys)) {
+ struct ast_key *key;
+ char *keyn;
+ char tmpkey[256];
+ char *stringp=NULL;
+ ast_copy_string(tmpkey, p->inkeys, sizeof(tmpkey));
+ stringp=tmpkey;
+ keyn = strsep(&stringp, ":");
+ while(keyn) {
+ key = ast_key_get(keyn, AST_KEY_PUBLIC);
+ if (key && !ast_check_signature(key, p->challenge, rsasecret)) {
+ res = 0;
+ break;
+ } else if (!key)
+ ast_log(LOG_WARNING, "requested inkey '%s' for RSA authentication does not exist\n", keyn);
+ keyn = strsep(&stringp, ":");
+ }
+ } else if (p->authmethods & IAX_AUTH_MD5) {
+ struct MD5Context md5;
+ unsigned char digest[16];
+ char *tmppw, *stringp;
+
+ tmppw = ast_strdupa(p->secret);
+ stringp = tmppw;
+ while((tmppw = strsep(&stringp, ";"))) {
+ MD5Init(&md5);
+ MD5Update(&md5, (unsigned char *)p->challenge, strlen(p->challenge));
+ MD5Update(&md5, (unsigned char *)tmppw, strlen(tmppw));
+ MD5Final(digest, &md5);
+ /* If they support md5, authenticate with it. */
+ for (x=0;x<16;x++)
+ sprintf(requeststr + (x << 1), "%2.2x", digest[x]); /* safe */
+ if (!strcasecmp(requeststr, md5secret)) {
+ res = 0;
+ break;
+ }
+ }
+ } else if (p->authmethods & IAX_AUTH_PLAINTEXT) {
+ if (!strcmp(secret, p->secret))
+ res = 0;
+ }
+ return res;
+}
+
+/*! \brief Verify inbound registration */
+static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies *ies)
+{
+ char requeststr[256] = "";
+ char peer[256] = "";
+ char md5secret[256] = "";
+ char rsasecret[256] = "";
+ char secret[256] = "";
+ struct iax2_peer *p = NULL;
+ struct ast_key *key;
+ char *keyn;
+ int x;
+ int expire = 0;
+ int res = -1;
+
+ ast_clear_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED | IAX_STATE_UNCHANGED);
+ /* iaxs[callno]->peer[0] = '\0'; not necc. any more-- stringfield is pre-inited to null string */
+ if (ies->username)
+ ast_copy_string(peer, ies->username, sizeof(peer));
+ if (ies->password)
+ ast_copy_string(secret, ies->password, sizeof(secret));
+ if (ies->md5_result)
+ ast_copy_string(md5secret, ies->md5_result, sizeof(md5secret));
+ if (ies->rsa_result)
+ ast_copy_string(rsasecret, ies->rsa_result, sizeof(rsasecret));
+ if (ies->refresh)
+ expire = ies->refresh;
+
+ if (ast_strlen_zero(peer)) {
+ ast_log(LOG_NOTICE, "Empty registration from %s\n", ast_inet_ntoa(sin->sin_addr));
+ return -1;
+ }
+
+ /* SLD: first call to lookup peer during registration */
+ ast_mutex_unlock(&iaxsl[callno]);
+ p = find_peer(peer, 1);
+ ast_mutex_lock(&iaxsl[callno]);
+ if (!p || !iaxs[callno]) {
+ if (authdebug && !p)
+ ast_log(LOG_NOTICE, "No registration for peer '%s' (from %s)\n", peer, ast_inet_ntoa(sin->sin_addr));
+ goto return_unref;
+ }
+
+ if (!ast_test_flag(p, IAX_DYNAMIC)) {
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Peer '%s' is not dynamic (from %s)\n", peer, ast_inet_ntoa(sin->sin_addr));
+ goto return_unref;
+ }
+
+ if (!ast_apply_ha(p->ha, sin)) {
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Host %s denied access to register peer '%s'\n", ast_inet_ntoa(sin->sin_addr), p->name);
+ goto return_unref;
+ }
+ if (!inaddrcmp(&p->addr, sin))
+ ast_set_flag(&iaxs[callno]->state, IAX_STATE_UNCHANGED);
+ ast_string_field_set(iaxs[callno], secret, p->secret);
+ ast_string_field_set(iaxs[callno], inkeys, p->inkeys);
+ /* Check secret against what we have on file */
+ if (!ast_strlen_zero(rsasecret) && (p->authmethods & IAX_AUTH_RSA) && !ast_strlen_zero(iaxs[callno]->challenge)) {
+ if (!ast_strlen_zero(p->inkeys)) {
+ char tmpkeys[256];
+ char *stringp=NULL;
+ ast_copy_string(tmpkeys, p->inkeys, sizeof(tmpkeys));
+ stringp=tmpkeys;
+ keyn = strsep(&stringp, ":");
+ while(keyn) {
+ key = ast_key_get(keyn, AST_KEY_PUBLIC);
+ if (key && !ast_check_signature(key, iaxs[callno]->challenge, rsasecret)) {
+ ast_set_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED);
+ break;
+ } else if (!key)
+ ast_log(LOG_WARNING, "requested inkey '%s' does not exist\n", keyn);
+ keyn = strsep(&stringp, ":");
+ }
+ if (!keyn) {
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Host %s failed RSA authentication with inkeys '%s'\n", peer, p->inkeys);
+ goto return_unref;
+ }
+ } else {
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Host '%s' trying to do RSA authentication, but we have no inkeys\n", peer);
+ goto return_unref;
+ }
+ } else if (!ast_strlen_zero(md5secret) && (p->authmethods & IAX_AUTH_MD5) && !ast_strlen_zero(iaxs[callno]->challenge)) {
+ struct MD5Context md5;
+ unsigned char digest[16];
+ char *tmppw, *stringp;
+
+ tmppw = ast_strdupa(p->secret);
+ stringp = tmppw;
+ while((tmppw = strsep(&stringp, ";"))) {
+ MD5Init(&md5);
+ MD5Update(&md5, (unsigned char *)iaxs[callno]->challenge, strlen(iaxs[callno]->challenge));
+ MD5Update(&md5, (unsigned char *)tmppw, strlen(tmppw));
+ MD5Final(digest, &md5);
+ for (x=0;x<16;x++)
+ sprintf(requeststr + (x << 1), "%2.2x", digest[x]); /* safe */
+ if (!strcasecmp(requeststr, md5secret))
+ break;
+ }
+ if (tmppw) {
+ ast_set_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED);
+ } else {
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Host %s failed MD5 authentication for '%s' (%s != %s)\n", ast_inet_ntoa(sin->sin_addr), p->name, requeststr, md5secret);
+ goto return_unref;
+ }
+ } else if (!ast_strlen_zero(secret) && (p->authmethods & IAX_AUTH_PLAINTEXT)) {
+ /* They've provided a plain text password and we support that */
+ if (strcmp(secret, p->secret)) {
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Host %s did not provide proper plaintext password for '%s'\n", ast_inet_ntoa(sin->sin_addr), p->name);
+ goto return_unref;
+ } else
+ ast_set_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED);
+ } else if (!ast_strlen_zero(md5secret) || !ast_strlen_zero(secret)) {
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Inappropriate authentication received\n");
+ goto return_unref;
+ }
+ ast_string_field_set(iaxs[callno], peer, peer);
+ /* Choose lowest expiry number */
+ if (expire && (expire < iaxs[callno]->expiry))
+ iaxs[callno]->expiry = expire;
+
+ ast_device_state_changed("IAX2/%s", p->name); /* Activate notification */
+
+ res = 0;
+
+return_unref:
+ if (p)
+ peer_unref(p);
+
+ return res;
+}
+
+static int authenticate(const char *challenge, const char *secret, const char *keyn, int authmethods, struct iax_ie_data *ied, struct sockaddr_in *sin, ast_aes_encrypt_key *ecx, ast_aes_decrypt_key *dcx)
+{
+ int res = -1;
+ int x;
+ if (!ast_strlen_zero(keyn)) {
+ if (!(authmethods & IAX_AUTH_RSA)) {
+ if (ast_strlen_zero(secret))
+ ast_log(LOG_NOTICE, "Asked to authenticate to %s with an RSA key, but they don't allow RSA authentication\n", ast_inet_ntoa(sin->sin_addr));
+ } else if (ast_strlen_zero(challenge)) {
+ ast_log(LOG_NOTICE, "No challenge provided for RSA authentication to %s\n", ast_inet_ntoa(sin->sin_addr));
+ } else {
+ char sig[256];
+ struct ast_key *key;
+ key = ast_key_get(keyn, AST_KEY_PRIVATE);
+ if (!key) {
+ ast_log(LOG_NOTICE, "Unable to find private key '%s'\n", keyn);
+ } else {
+ if (ast_sign(key, (char*)challenge, sig)) {
+ ast_log(LOG_NOTICE, "Unable to sign challenge with key\n");
+ res = -1;
+ } else {
+ iax_ie_append_str(ied, IAX_IE_RSA_RESULT, sig);
+ res = 0;
+ }
+ }
+ }
+ }
+ /* Fall back */
+ if (res && !ast_strlen_zero(secret)) {
+ if ((authmethods & IAX_AUTH_MD5) && !ast_strlen_zero(challenge)) {
+ struct MD5Context md5;
+ unsigned char digest[16];
+ char digres[128];
+ MD5Init(&md5);
+ MD5Update(&md5, (unsigned char *)challenge, strlen(challenge));
+ MD5Update(&md5, (unsigned char *)secret, strlen(secret));
+ MD5Final(digest, &md5);
+ /* If they support md5, authenticate with it. */
+ for (x=0;x<16;x++)
+ sprintf(digres + (x << 1), "%2.2x", digest[x]); /* safe */
+ if (ecx && dcx)
+ build_enc_keys(digest, ecx, dcx);
+ iax_ie_append_str(ied, IAX_IE_MD5_RESULT, digres);
+ res = 0;
+ } else if (authmethods & IAX_AUTH_PLAINTEXT) {
+ iax_ie_append_str(ied, IAX_IE_PASSWORD, secret);
+ res = 0;
+ } else
+ ast_log(LOG_NOTICE, "No way to send secret to peer '%s' (their methods: %d)\n", ast_inet_ntoa(sin->sin_addr), authmethods);
+ }
+ return res;
+}
+
+/*!
+ * \note This function calls realtime_peer -> reg_source_db -> iax2_poke_peer -> find_callno,
+ * so do not call this function with a pvt lock held.
+ */
+static int authenticate_reply(struct chan_iax2_pvt *p, struct sockaddr_in *sin, struct iax_ies *ies, const char *override, const char *okey)
+{
+ struct iax2_peer *peer = NULL;
+ /* Start pessimistic */
+ int res = -1;
+ int authmethods = 0;
+ struct iax_ie_data ied;
+ uint16_t callno = p->callno;
+
+ memset(&ied, 0, sizeof(ied));
+
+ if (ies->username)
+ ast_string_field_set(p, username, ies->username);
+ if (ies->challenge)
+ ast_string_field_set(p, challenge, ies->challenge);
+ if (ies->authmethods)
+ authmethods = ies->authmethods;
+ if (authmethods & IAX_AUTH_MD5)
+ merge_encryption(p, ies->encmethods);
+ else
+ p->encmethods = 0;
+
+ /* Check for override RSA authentication first */
+ if (!ast_strlen_zero(override) || !ast_strlen_zero(okey)) {
+ /* Normal password authentication */
+ res = authenticate(p->challenge, override, okey, authmethods, &ied, sin, &p->ecx, &p->dcx);
+ } else {
+ struct ao2_iterator i = ao2_iterator_init(peers, 0);
+ while ((peer = ao2_iterator_next(&i))) {
+ if ((ast_strlen_zero(p->peer) || !strcmp(p->peer, peer->name))
+ /* No peer specified at our end, or this is the peer */
+ && (ast_strlen_zero(peer->username) || (!strcmp(peer->username, p->username)))
+ /* No username specified in peer rule, or this is the right username */
+ && (!peer->addr.sin_addr.s_addr || ((sin->sin_addr.s_addr & peer->mask.s_addr) == (peer->addr.sin_addr.s_addr & peer->mask.s_addr)))
+ /* No specified host, or this is our host */
+ ) {
+ res = authenticate(p->challenge, peer->secret, peer->outkey, authmethods, &ied, sin, &p->ecx, &p->dcx);
+ if (!res) {
+ peer_unref(peer);
+ break;
+ }
+ }
+ peer_unref(peer);
+ }
+ if (!peer) {
+ /* We checked our list and didn't find one. It's unlikely, but possible,
+ that we're trying to authenticate *to* a realtime peer */
+ const char *peer_name = ast_strdupa(p->peer);
+ ast_mutex_unlock(&iaxsl[callno]);
+ if ((peer = realtime_peer(peer_name, NULL))) {
+ ast_mutex_lock(&iaxsl[callno]);
+ if (!(p = iaxs[callno])) {
+ peer_unref(peer);
+ return -1;
+ }
+ res = authenticate(p->challenge, peer->secret,peer->outkey, authmethods, &ied, sin, &p->ecx, &p->dcx);
+ peer_unref(peer);
+ }
+ if (!peer) {
+ ast_mutex_lock(&iaxsl[callno]);
+ if (!(p = iaxs[callno]))
+ return -1;
+ }
+ }
+ }
+ if (ies->encmethods)
+ ast_set_flag(p, IAX_ENCRYPTED | IAX_KEYPOPULATED);
+ if (!res) {
+ struct ast_datastore *variablestore;
+ struct ast_variable *var, *prev = NULL;
+ AST_LIST_HEAD(, ast_var_t) *varlist;
+ varlist = ast_calloc(1, sizeof(*varlist));
+ variablestore = ast_channel_datastore_alloc(&iax2_variable_datastore_info, NULL);
+ if (variablestore && varlist && p->owner) {
+ variablestore->data = varlist;
+ variablestore->inheritance = DATASTORE_INHERIT_FOREVER;
+ AST_LIST_HEAD_INIT(varlist);
+ for (var = ies->vars; var; var = var->next) {
+ struct ast_var_t *newvar = ast_var_assign(var->name, var->value);
+ if (prev)
+ ast_free(prev);
+ prev = var;
+ if (!newvar) {
+ /* Don't abort list traversal, as this would leave ies->vars in an inconsistent state. */
+ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n");
+ } else {
+ AST_LIST_INSERT_TAIL(varlist, newvar, entries);
+ }
+ }
+ if (prev)
+ ast_free(prev);
+ ies->vars = NULL;
+ ast_channel_datastore_add(p->owner, variablestore);
+ } else {
+ if (p->owner)
+ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n");
+ if (variablestore)
+ ast_channel_datastore_free(variablestore);
+ if (varlist)
+ ast_free(varlist);
+ }
+ }
+
+ if (!res)
+ res = send_command(p, AST_FRAME_IAX, IAX_COMMAND_AUTHREP, 0, ied.buf, ied.pos, -1);
+ return res;
+}
+
+static int iax2_do_register(struct iax2_registry *reg);
+
+static void __iax2_do_register_s(const void *data)
+{
+ struct iax2_registry *reg = (struct iax2_registry *)data;
+ reg->expire = -1;
+ iax2_do_register(reg);
+}
+
+static int iax2_do_register_s(const void *data)
+{
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__iax2_do_register_s, data))
+#endif
+ __iax2_do_register_s(data);
+ return 0;
+}
+
+static int try_transfer(struct chan_iax2_pvt *pvt, struct iax_ies *ies)
+{
+ int newcall = 0;
+ char newip[256];
+ struct iax_ie_data ied;
+ struct sockaddr_in new;
+
+
+ memset(&ied, 0, sizeof(ied));
+ if (ies->apparent_addr)
+ bcopy(ies->apparent_addr, &new, sizeof(new));
+ if (ies->callno)
+ newcall = ies->callno;
+ if (!newcall || !new.sin_addr.s_addr || !new.sin_port) {
+ ast_log(LOG_WARNING, "Invalid transfer request\n");
+ return -1;
+ }
+ pvt->transfercallno = newcall;
+ memcpy(&pvt->transfer, &new, sizeof(pvt->transfer));
+ inet_aton(newip, &pvt->transfer.sin_addr);
+ pvt->transfer.sin_family = AF_INET;
+ pvt->transferring = TRANSFER_BEGIN;
+ pvt->transferid = ies->transferid;
+ if (ies->transferid)
+ iax_ie_append_int(&ied, IAX_IE_TRANSFERID, ies->transferid);
+ send_command_transfer(pvt, AST_FRAME_IAX, IAX_COMMAND_TXCNT, 0, ied.buf, ied.pos);
+ return 0;
+}
+
+static int complete_dpreply(struct chan_iax2_pvt *pvt, struct iax_ies *ies)
+{
+ char exten[256] = "";
+ int status = CACHE_FLAG_UNKNOWN, expiry = iaxdefaultdpcache, x, matchmore = 0;
+ struct iax2_dpcache *dp = NULL;
+
+ if (ies->called_number)
+ ast_copy_string(exten, ies->called_number, sizeof(exten));
+
+ if (ies->dpstatus & IAX_DPSTATUS_EXISTS)
+ status = CACHE_FLAG_EXISTS;
+ else if (ies->dpstatus & IAX_DPSTATUS_CANEXIST)
+ status = CACHE_FLAG_CANEXIST;
+ else if (ies->dpstatus & IAX_DPSTATUS_NONEXISTENT)
+ status = CACHE_FLAG_NONEXISTENT;
+
+ if (ies->refresh)
+ expiry = ies->refresh;
+ if (ies->dpstatus & IAX_DPSTATUS_MATCHMORE)
+ matchmore = CACHE_FLAG_MATCHMORE;
+
+ AST_LIST_LOCK(&dpcache);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&dpcache, dp, peer_list) {
+ if (strcmp(dp->exten, exten))
+ continue;
+ AST_LIST_REMOVE_CURRENT(peer_list);
+ dp->callno = 0;
+ dp->expiry.tv_sec = dp->orig.tv_sec + expiry;
+ if (dp->flags & CACHE_FLAG_PENDING) {
+ dp->flags &= ~CACHE_FLAG_PENDING;
+ dp->flags |= status;
+ dp->flags |= matchmore;
+ }
+ /* Wake up waiters */
+ for (x=0;x<sizeof(dp->waiters) / sizeof(dp->waiters[0]); x++)
+ if (dp->waiters[x] > -1)
+ write(dp->waiters[x], "asdf", 4);
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&dpcache);
+
+ return 0;
+}
+
+static int complete_transfer(int callno, struct iax_ies *ies)
+{
+ int peercallno = 0;
+ struct chan_iax2_pvt *pvt = iaxs[callno];
+ struct iax_frame *cur;
+ jb_frame frame;
+
+ if (ies->callno)
+ peercallno = ies->callno;
+
+ if (peercallno < 1) {
+ ast_log(LOG_WARNING, "Invalid transfer request\n");
+ return -1;
+ }
+ memcpy(&pvt->addr, &pvt->transfer, sizeof(pvt->addr));
+ memset(&pvt->transfer, 0, sizeof(pvt->transfer));
+ /* Reset sequence numbers */
+ pvt->oseqno = 0;
+ pvt->rseqno = 0;
+ pvt->iseqno = 0;
+ pvt->aseqno = 0;
+ pvt->peercallno = peercallno;
+ pvt->transferring = TRANSFER_NONE;
+ pvt->svoiceformat = -1;
+ pvt->voiceformat = 0;
+ pvt->svideoformat = -1;
+ pvt->videoformat = 0;
+ pvt->transfercallno = -1;
+ memset(&pvt->rxcore, 0, sizeof(pvt->rxcore));
+ memset(&pvt->offset, 0, sizeof(pvt->offset));
+ /* reset jitterbuffer */
+ while(jb_getall(pvt->jb,&frame) == JB_OK)
+ iax2_frame_free(frame.data);
+ jb_reset(pvt->jb);
+ pvt->lag = 0;
+ pvt->last = 0;
+ pvt->lastsent = 0;
+ pvt->nextpred = 0;
+ pvt->pingtime = DEFAULT_RETRY_TIME;
+ AST_LIST_LOCK(&frame_queue);
+ AST_LIST_TRAVERSE(&frame_queue, cur, list) {
+ /* We must cancel any packets that would have been transmitted
+ because now we're talking to someone new. It's okay, they
+ were transmitted to someone that didn't care anyway. */
+ if (callno == cur->callno)
+ cur->retries = -1;
+ }
+ AST_LIST_UNLOCK(&frame_queue);
+ return 0;
+}
+
+/*! \brief Acknowledgment received for OUR registration */
+static int iax2_ack_registry(struct iax_ies *ies, struct sockaddr_in *sin, int callno)
+{
+ struct iax2_registry *reg;
+ /* Start pessimistic */
+ char peer[256] = "";
+ char msgstatus[60];
+ int refresh = 60;
+ char ourip[256] = "<Unspecified>";
+ struct sockaddr_in oldus;
+ struct sockaddr_in us;
+ int oldmsgs;
+
+ memset(&us, 0, sizeof(us));
+ if (ies->apparent_addr)
+ bcopy(ies->apparent_addr, &us, sizeof(us));
+ if (ies->username)
+ ast_copy_string(peer, ies->username, sizeof(peer));
+ if (ies->refresh)
+ refresh = ies->refresh;
+ if (ies->calling_number) {
+ /* We don't do anything with it really, but maybe we should */
+ }
+ reg = iaxs[callno]->reg;
+ if (!reg) {
+ ast_log(LOG_WARNING, "Registry acknowledge on unknown registry '%s'\n", peer);
+ return -1;
+ }
+ memcpy(&oldus, &reg->us, sizeof(oldus));
+ oldmsgs = reg->messages;
+ if (inaddrcmp(&reg->addr, sin)) {
+ ast_log(LOG_WARNING, "Received unsolicited registry ack from '%s'\n", ast_inet_ntoa(sin->sin_addr));
+ return -1;
+ }
+ memcpy(&reg->us, &us, sizeof(reg->us));
+ if (ies->msgcount >= 0)
+ reg->messages = ies->msgcount & 0xffff; /* only low 16 bits are used in the transmission of the IE */
+ /* always refresh the registration at the interval requested by the server
+ we are registering to
+ */
+ reg->refresh = refresh;
+ reg->expire = iax2_sched_replace(reg->expire, sched,
+ (5 * reg->refresh / 6) * 1000, iax2_do_register_s, reg);
+ if (inaddrcmp(&oldus, &reg->us) || (reg->messages != oldmsgs)) {
+ if (reg->messages > 255)
+ snprintf(msgstatus, sizeof(msgstatus), " with %d new and %d old messages waiting", reg->messages & 0xff, reg->messages >> 8);
+ else if (reg->messages > 1)
+ snprintf(msgstatus, sizeof(msgstatus), " with %d new messages waiting\n", reg->messages);
+ else if (reg->messages > 0)
+ ast_copy_string(msgstatus, " with 1 new message waiting\n", sizeof(msgstatus));
+ else
+ ast_copy_string(msgstatus, " with no messages waiting\n", sizeof(msgstatus));
+ snprintf(ourip, sizeof(ourip), "%s:%d", ast_inet_ntoa(reg->us.sin_addr), ntohs(reg->us.sin_port));
+ ast_verb(3, "Registered IAX2 to '%s', who sees us as %s%s\n", ast_inet_ntoa(sin->sin_addr), ourip, msgstatus);
+ manager_event(EVENT_FLAG_SYSTEM, "Registry", "ChannelType: IAX2\r\nDomain: %s\r\nStatus: Registered\r\n", ast_inet_ntoa(sin->sin_addr));
+ }
+ reg->regstate = REG_STATE_REGISTERED;
+ return 0;
+}
+
+static int iax2_append_register(const char *hostname, const char *username,
+ const char *secret, const char *porta)
+{
+ struct iax2_registry *reg;
+
+ if (!(reg = ast_calloc(1, sizeof(*reg))))
+ return -1;
+
+ if (ast_dnsmgr_lookup(hostname, &reg->addr.sin_addr, &reg->dnsmgr) < 0) {
+ ast_free(reg);
+ return -1;
+ }
+
+ ast_copy_string(reg->username, username, sizeof(reg->username));
+
+ if (secret)
+ ast_copy_string(reg->secret, secret, sizeof(reg->secret));
+
+ reg->expire = -1;
+ reg->refresh = IAX_DEFAULT_REG_EXPIRE;
+ reg->addr.sin_family = AF_INET;
+ reg->addr.sin_port = porta ? htons(atoi(porta)) : htons(IAX_DEFAULT_PORTNO);
+
+ AST_LIST_LOCK(&registrations);
+ AST_LIST_INSERT_HEAD(&registrations, reg, entry);
+ AST_LIST_UNLOCK(&registrations);
+
+ return 0;
+}
+
+static int iax2_register(const char *value, int lineno)
+{
+ char copy[256];
+ char *username, *hostname, *secret;
+ char *porta;
+ char *stringp=NULL;
+
+ if (!value)
+ return -1;
+
+ ast_copy_string(copy, value, sizeof(copy));
+ stringp = copy;
+ username = strsep(&stringp, "@");
+ hostname = strsep(&stringp, "@");
+
+ if (!hostname) {
+ ast_log(LOG_WARNING, "Format for registration is user[:secret]@host[:port] at line %d\n", lineno);
+ return -1;
+ }
+
+ stringp = username;
+ username = strsep(&stringp, ":");
+ secret = strsep(&stringp, ":");
+ stringp = hostname;
+ hostname = strsep(&stringp, ":");
+ porta = strsep(&stringp, ":");
+
+ if (porta && !atoi(porta)) {
+ ast_log(LOG_WARNING, "%s is not a valid port number at line %d\n", porta, lineno);
+ return -1;
+ }
+
+ return iax2_append_register(hostname, username, secret, porta);
+}
+
+
+static void register_peer_exten(struct iax2_peer *peer, int onoff)
+{
+ char multi[256];
+ char *stringp, *ext;
+ if (!ast_strlen_zero(regcontext)) {
+ ast_copy_string(multi, S_OR(peer->regexten, peer->name), sizeof(multi));
+ stringp = multi;
+ while((ext = strsep(&stringp, "&"))) {
+ if (onoff) {
+ if (!ast_exists_extension(NULL, regcontext, ext, 1, NULL))
+ ast_add_extension(regcontext, 1, ext, 1, NULL, NULL,
+ "Noop", ast_strdup(peer->name), ast_free_ptr, "IAX2");
+ } else
+ ast_context_remove_extension(regcontext, ext, 1, NULL);
+ }
+ }
+}
+static void prune_peers(void);
+
+static void unlink_peer(struct iax2_peer *peer)
+{
+ if (peer->expire > -1) {
+ if (!ast_sched_del(sched, peer->expire)) {
+ peer->expire = -1;
+ peer_unref(peer);
+ }
+ }
+
+ if (peer->pokeexpire > -1) {
+ if (!ast_sched_del(sched, peer->pokeexpire)) {
+ peer->pokeexpire = -1;
+ peer_unref(peer);
+ }
+ }
+
+ ao2_unlink(peers, peer);
+}
+
+static void __expire_registry(const void *data)
+{
+ struct iax2_peer *peer = (struct iax2_peer *) data;
+
+ if (!peer)
+ return;
+
+ peer->expire = -1;
+
+ ast_debug(1, "Expiring registration for peer '%s'\n", peer->name);
+ if (ast_test_flag((&globalflags), IAX_RTUPDATE) && (ast_test_flag(peer, IAX_TEMPONLY|IAX_RTCACHEFRIENDS)))
+ realtime_update_peer(peer->name, &peer->addr, 0);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name);
+ /* Reset the address */
+ memset(&peer->addr, 0, sizeof(peer->addr));
+ /* Reset expiry value */
+ peer->expiry = min_reg_expire;
+ if (!ast_test_flag(peer, IAX_TEMPONLY))
+ ast_db_del("IAX/Registry", peer->name);
+ register_peer_exten(peer, 0);
+ ast_device_state_changed("IAX2/%s", peer->name); /* Activate notification */
+ if (iax2_regfunk)
+ iax2_regfunk(peer->name, 0);
+
+ if (ast_test_flag(peer, IAX_RTAUTOCLEAR))
+ unlink_peer(peer);
+
+ peer_unref(peer);
+}
+
+static int expire_registry(const void *data)
+{
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__expire_registry, data))
+#endif
+ __expire_registry(data);
+ return 0;
+}
+
+static int iax2_poke_peer(struct iax2_peer *peer, int heldcall);
+
+static void reg_source_db(struct iax2_peer *p)
+{
+ char data[80];
+ struct in_addr in;
+ char *c, *d;
+ if (!ast_test_flag(p, IAX_TEMPONLY) && (!ast_db_get("IAX/Registry", p->name, data, sizeof(data)))) {
+ c = strchr(data, ':');
+ if (c) {
+ *c = '\0';
+ c++;
+ if (inet_aton(data, &in)) {
+ d = strchr(c, ':');
+ if (d) {
+ *d = '\0';
+ d++;
+ ast_verb(3, "Seeding '%s' at %s:%d for %d\n", p->name,
+ ast_inet_ntoa(in), atoi(c), atoi(d));
+ iax2_poke_peer(p, 0);
+ p->expiry = atoi(d);
+ memset(&p->addr, 0, sizeof(p->addr));
+ p->addr.sin_family = AF_INET;
+ p->addr.sin_addr = in;
+ p->addr.sin_port = htons(atoi(c));
+ if (p->expire > -1) {
+ if (!ast_sched_del(sched, p->expire)) {
+ p->expire = -1;
+ peer_unref(p);
+ }
+ }
+ ast_device_state_changed("IAX2/%s", p->name); /* Activate notification */
+ p->expire = iax2_sched_add(sched, (p->expiry + 10) * 1000, expire_registry, peer_ref(p));
+ if (p->expire == -1)
+ peer_unref(p);
+ if (iax2_regfunk)
+ iax2_regfunk(p->name, 1);
+ register_peer_exten(p, 1);
+ }
+
+ }
+ }
+ }
+}
+
+/*!
+ * \pre iaxsl[callno] is locked
+ *
+ * \note Since this function calls send_command_final(), the pvt struct for
+ * the given call number may disappear while executing this function.
+ */
+static int update_registry(struct sockaddr_in *sin, int callno, char *devtype, int fd, unsigned short refresh)
+{
+ /* Called from IAX thread only, with proper iaxsl lock */
+ struct iax_ie_data ied;
+ struct iax2_peer *p;
+ int msgcount;
+ char data[80];
+ int version;
+ const char *peer_name;
+ int res = -1;
+
+ memset(&ied, 0, sizeof(ied));
+
+ peer_name = ast_strdupa(iaxs[callno]->peer);
+
+ /* SLD: Another find_peer call during registration - this time when we are really updating our registration */
+ ast_mutex_unlock(&iaxsl[callno]);
+ if (!(p = find_peer(peer_name, 1))) {
+ ast_mutex_lock(&iaxsl[callno]);
+ ast_log(LOG_WARNING, "No such peer '%s'\n", peer_name);
+ return -1;
+ }
+ ast_mutex_lock(&iaxsl[callno]);
+ if (!iaxs[callno])
+ goto return_unref;
+
+ if (ast_test_flag((&globalflags), IAX_RTUPDATE) && (ast_test_flag(p, IAX_TEMPONLY|IAX_RTCACHEFRIENDS))) {
+ if (sin->sin_addr.s_addr) {
+ time_t nowtime;
+ time(&nowtime);
+ realtime_update_peer(peer_name, sin, nowtime);
+ } else {
+ realtime_update_peer(peer_name, sin, 0);
+ }
+ }
+ if (inaddrcmp(&p->addr, sin)) {
+ if (iax2_regfunk)
+ iax2_regfunk(p->name, 1);
+ /* Stash the IP address from which they registered */
+ memcpy(&p->addr, sin, sizeof(p->addr));
+ snprintf(data, sizeof(data), "%s:%d:%d", ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), p->expiry);
+ if (!ast_test_flag(p, IAX_TEMPONLY) && sin->sin_addr.s_addr) {
+ ast_db_put("IAX/Registry", p->name, data);
+ ast_verb(3, "Registered IAX2 '%s' (%s) at %s:%d\n", p->name,
+ ast_test_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED) ? "AUTHENTICATED" : "UNAUTHENTICATED", ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Registered\r\n", p->name);
+ register_peer_exten(p, 1);
+ ast_device_state_changed("IAX2/%s", p->name); /* Activate notification */
+ } else if (!ast_test_flag(p, IAX_TEMPONLY)) {
+ ast_verb(3, "Unregistered IAX2 '%s' (%s)\n", p->name,
+ ast_test_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED) ? "AUTHENTICATED" : "UNAUTHENTICATED");
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Unregistered\r\n", p->name);
+ register_peer_exten(p, 0);
+ ast_db_del("IAX/Registry", p->name);
+ ast_device_state_changed("IAX2/%s", p->name); /* Activate notification */
+ }
+ /* Update the host */
+ /* Verify that the host is really there */
+ iax2_poke_peer(p, callno);
+ }
+
+ /* Make sure our call still exists, an INVAL at the right point may make it go away */
+ if (!iaxs[callno]) {
+ res = 0;
+ goto return_unref;
+ }
+
+ /* Store socket fd */
+ p->sockfd = fd;
+ /* Setup the expiry */
+ if (p->expire > -1) {
+ if (!ast_sched_del(sched, p->expire)) {
+ p->expire = -1;
+ peer_unref(p);
+ }
+ }
+ /* treat an unspecified refresh interval as the minimum */
+ if (!refresh)
+ refresh = min_reg_expire;
+ if (refresh > max_reg_expire) {
+ ast_log(LOG_NOTICE, "Restricting registration for peer '%s' to %d seconds (requested %d)\n",
+ p->name, max_reg_expire, refresh);
+ p->expiry = max_reg_expire;
+ } else if (refresh < min_reg_expire) {
+ ast_log(LOG_NOTICE, "Restricting registration for peer '%s' to %d seconds (requested %d)\n",
+ p->name, min_reg_expire, refresh);
+ p->expiry = min_reg_expire;
+ } else {
+ p->expiry = refresh;
+ }
+ if (p->expiry && sin->sin_addr.s_addr) {
+ p->expire = iax2_sched_add(sched, (p->expiry + 10) * 1000, expire_registry, peer_ref(p));
+ if (p->expire == -1)
+ peer_unref(p);
+ }
+ iax_ie_append_str(&ied, IAX_IE_USERNAME, p->name);
+ iax_ie_append_int(&ied, IAX_IE_DATETIME, iax2_datetime(p->zonetag));
+ if (sin->sin_addr.s_addr) {
+ iax_ie_append_short(&ied, IAX_IE_REFRESH, p->expiry);
+ iax_ie_append_addr(&ied, IAX_IE_APPARENT_ADDR, &p->addr);
+ if (!ast_strlen_zero(p->mailbox)) {
+ struct ast_event *event;
+ int new, old;
+ char *mailbox, *context;
+
+ context = mailbox = ast_strdupa(p->mailbox);
+ strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+
+ event = ast_event_get_cached(AST_EVENT_MWI,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
+ AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_EXISTS,
+ AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_EXISTS,
+ AST_EVENT_IE_END);
+ if (event) {
+ new = ast_event_get_ie_uint(event, AST_EVENT_IE_NEWMSGS);
+ old = ast_event_get_ie_uint(event, AST_EVENT_IE_OLDMSGS);
+ ast_event_destroy(event);
+ } else /* Fall back on checking the mailbox directly */
+ ast_app_inboxcount(p->mailbox, &new, &old);
+
+ if (new > 255)
+ new = 255;
+ if (old > 255)
+ old = 255;
+ msgcount = (old << 8) | new;
+
+ iax_ie_append_short(&ied, IAX_IE_MSGCOUNT, msgcount);
+ }
+ if (ast_test_flag(p, IAX_HASCALLERID)) {
+ iax_ie_append_str(&ied, IAX_IE_CALLING_NUMBER, p->cid_num);
+ iax_ie_append_str(&ied, IAX_IE_CALLING_NAME, p->cid_name);
+ }
+ }
+ version = iax_check_version(devtype);
+ if (version)
+ iax_ie_append_short(&ied, IAX_IE_FIRMWAREVER, version);
+
+ res = 0;
+
+return_unref:
+ peer_unref(p);
+
+ return res ? res : send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGACK, 0, ied.buf, ied.pos, -1);
+}
+
+static int registry_authrequest(int callno)
+{
+ struct iax_ie_data ied;
+ struct iax2_peer *p;
+ char challenge[10];
+ const char *peer_name;
+ int res = -1;
+
+ peer_name = ast_strdupa(iaxs[callno]->peer);
+
+ /* SLD: third call to find_peer in registration */
+ ast_mutex_unlock(&iaxsl[callno]);
+ p = find_peer(peer_name, 1);
+ ast_mutex_lock(&iaxsl[callno]);
+ if (!iaxs[callno])
+ goto return_unref;
+ if (!p) {
+ ast_log(LOG_WARNING, "No such peer '%s'\n", peer_name);
+ goto return_unref;
+ }
+
+ memset(&ied, 0, sizeof(ied));
+ iax_ie_append_short(&ied, IAX_IE_AUTHMETHODS, p->authmethods);
+ if (p->authmethods & (IAX_AUTH_RSA | IAX_AUTH_MD5)) {
+ /* Build the challenge */
+ snprintf(challenge, sizeof(challenge), "%d", (int)ast_random());
+ ast_string_field_set(iaxs[callno], challenge, challenge);
+ iax_ie_append_str(&ied, IAX_IE_CHALLENGE, iaxs[callno]->challenge);
+ }
+ iax_ie_append_str(&ied, IAX_IE_USERNAME, peer_name);
+
+ res = 0;
+
+return_unref:
+ peer_unref(p);
+
+ return res ? res : send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGAUTH, 0, ied.buf, ied.pos, -1);;
+}
+
+static int registry_rerequest(struct iax_ies *ies, int callno, struct sockaddr_in *sin)
+{
+ struct iax2_registry *reg;
+ /* Start pessimistic */
+ struct iax_ie_data ied;
+ char peer[256] = "";
+ char challenge[256] = "";
+ int res;
+ int authmethods = 0;
+ if (ies->authmethods)
+ authmethods = ies->authmethods;
+ if (ies->username)
+ ast_copy_string(peer, ies->username, sizeof(peer));
+ if (ies->challenge)
+ ast_copy_string(challenge, ies->challenge, sizeof(challenge));
+ memset(&ied, 0, sizeof(ied));
+ reg = iaxs[callno]->reg;
+ if (reg) {
+ if (inaddrcmp(&reg->addr, sin)) {
+ ast_log(LOG_WARNING, "Received unsolicited registry authenticate request from '%s'\n", ast_inet_ntoa(sin->sin_addr));
+ return -1;
+ }
+ if (ast_strlen_zero(reg->secret)) {
+ ast_log(LOG_NOTICE, "No secret associated with peer '%s'\n", reg->username);
+ reg->regstate = REG_STATE_NOAUTH;
+ return -1;
+ }
+ iax_ie_append_str(&ied, IAX_IE_USERNAME, reg->username);
+ iax_ie_append_short(&ied, IAX_IE_REFRESH, reg->refresh);
+ if (reg->secret[0] == '[') {
+ char tmpkey[256];
+ ast_copy_string(tmpkey, reg->secret + 1, sizeof(tmpkey));
+ tmpkey[strlen(tmpkey) - 1] = '\0';
+ res = authenticate(challenge, NULL, tmpkey, authmethods, &ied, sin, NULL, NULL);
+ } else
+ res = authenticate(challenge, reg->secret, NULL, authmethods, &ied, sin, NULL, NULL);
+ if (!res) {
+ reg->regstate = REG_STATE_AUTHSENT;
+ return send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGREQ, 0, ied.buf, ied.pos, -1);
+ } else
+ return -1;
+ ast_log(LOG_WARNING, "Registry acknowledge on unknown registery '%s'\n", peer);
+ } else
+ ast_log(LOG_NOTICE, "Can't reregister without a reg\n");
+ return -1;
+}
+
+static void stop_stuff(int callno)
+{
+ iax2_destroy_helper(iaxs[callno]);
+}
+
+static void __auth_reject(const void *nothing)
+{
+ /* Called from IAX thread only, without iaxs lock */
+ int callno = (int)(long)(nothing);
+ struct iax_ie_data ied;
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno]) {
+ memset(&ied, 0, sizeof(ied));
+ if (iaxs[callno]->authfail == IAX_COMMAND_REGREJ) {
+ iax_ie_append_str(&ied, IAX_IE_CAUSE, "Registration Refused");
+ iax_ie_append_byte(&ied, IAX_IE_CAUSECODE, AST_CAUSE_FACILITY_REJECTED);
+ } else if (iaxs[callno]->authfail == IAX_COMMAND_REJECT) {
+ iax_ie_append_str(&ied, IAX_IE_CAUSE, "No authority found");
+ iax_ie_append_byte(&ied, IAX_IE_CAUSECODE, AST_CAUSE_FACILITY_NOT_SUBSCRIBED);
+ }
+ send_command_final(iaxs[callno], AST_FRAME_IAX, iaxs[callno]->authfail, 0, ied.buf, ied.pos, -1);
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+}
+
+static int auth_reject(const void *data)
+{
+ int callno = (int)(long)(data);
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno])
+ iaxs[callno]->authid = -1;
+ ast_mutex_unlock(&iaxsl[callno]);
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__auth_reject, data))
+#endif
+ __auth_reject(data);
+ return 0;
+}
+
+static int auth_fail(int callno, int failcode)
+{
+ /* Schedule sending the authentication failure in one second, to prevent
+ guessing */
+ if (iaxs[callno]) {
+ iaxs[callno]->authfail = failcode;
+ if (delayreject) {
+ iaxs[callno]->authid = iax2_sched_replace(iaxs[callno]->authid,
+ sched, 1000, auth_reject, (void *)(long)callno);
+ } else
+ auth_reject((void *)(long)callno);
+ }
+ return 0;
+}
+
+static void __auto_hangup(const void *nothing)
+{
+ /* Called from IAX thread only, without iaxs lock */
+ int callno = (int)(long)(nothing);
+ struct iax_ie_data ied;
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno]) {
+ memset(&ied, 0, sizeof(ied));
+ iax_ie_append_str(&ied, IAX_IE_CAUSE, "Timeout");
+ iax_ie_append_byte(&ied, IAX_IE_CAUSECODE, AST_CAUSE_NO_USER_RESPONSE);
+ send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_HANGUP, 0, ied.buf, ied.pos, -1);
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+}
+
+static int auto_hangup(const void *data)
+{
+ int callno = (int)(long)(data);
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno]) {
+ iaxs[callno]->autoid = -1;
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__auto_hangup, data))
+#endif
+ __auto_hangup(data);
+ return 0;
+}
+
+static void iax2_dprequest(struct iax2_dpcache *dp, int callno)
+{
+ struct iax_ie_data ied;
+ /* Auto-hangup with 30 seconds of inactivity */
+ iaxs[callno]->autoid = iax2_sched_replace(iaxs[callno]->autoid,
+ sched, 30000, auto_hangup, (void *)(long)callno);
+ memset(&ied, 0, sizeof(ied));
+ iax_ie_append_str(&ied, IAX_IE_CALLED_NUMBER, dp->exten);
+ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_DPREQ, 0, ied.buf, ied.pos, -1);
+ dp->flags |= CACHE_FLAG_TRANSMITTED;
+}
+
+static int iax2_vnak(int callno)
+{
+ return send_command_immediate(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_VNAK, 0, NULL, 0, iaxs[callno]->iseqno);
+}
+
+static void vnak_retransmit(int callno, int last)
+{
+ struct iax_frame *f;
+
+ AST_LIST_LOCK(&frame_queue);
+ AST_LIST_TRAVERSE(&frame_queue, f, list) {
+ /* Send a copy immediately */
+ if ((f->callno == callno) && iaxs[f->callno] &&
+ ((unsigned char ) (f->oseqno - last) < 128) &&
+ (f->retries >= 0)) {
+ send_packet(f);
+ }
+ }
+ AST_LIST_UNLOCK(&frame_queue);
+}
+
+static void __iax2_poke_peer_s(const void *data)
+{
+ struct iax2_peer *peer = (struct iax2_peer *)data;
+ iax2_poke_peer(peer, 0);
+ peer_unref(peer);
+}
+
+static int iax2_poke_peer_s(const void *data)
+{
+ struct iax2_peer *peer = (struct iax2_peer *)data;
+ peer->pokeexpire = -1;
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__iax2_poke_peer_s, data))
+#endif
+ __iax2_poke_peer_s(data);
+ return 0;
+}
+
+static int send_trunk(struct iax2_trunk_peer *tpeer, struct timeval *now)
+{
+ int res = 0;
+ struct iax_frame *fr;
+ struct ast_iax2_meta_hdr *meta;
+ struct ast_iax2_meta_trunk_hdr *mth;
+ int calls = 0;
+
+ /* Point to frame */
+ fr = (struct iax_frame *)tpeer->trunkdata;
+ /* Point to meta data */
+ meta = (struct ast_iax2_meta_hdr *)fr->afdata;
+ mth = (struct ast_iax2_meta_trunk_hdr *)meta->data;
+ if (tpeer->trunkdatalen) {
+ /* We're actually sending a frame, so fill the meta trunk header and meta header */
+ meta->zeros = 0;
+ meta->metacmd = IAX_META_TRUNK;
+ if (ast_test_flag(&globalflags, IAX_TRUNKTIMESTAMPS))
+ meta->cmddata = IAX_META_TRUNK_MINI;
+ else
+ meta->cmddata = IAX_META_TRUNK_SUPERMINI;
+ mth->ts = htonl(calc_txpeerstamp(tpeer, trunkfreq, now));
+ /* And the rest of the ast_iax2 header */
+ fr->direction = DIRECTION_OUTGRESS;
+ fr->retrans = -1;
+ fr->transfer = 0;
+ /* Any appropriate call will do */
+ fr->data = fr->afdata;
+ fr->datalen = tpeer->trunkdatalen + sizeof(struct ast_iax2_meta_hdr) + sizeof(struct ast_iax2_meta_trunk_hdr);
+ res = transmit_trunk(fr, &tpeer->addr, tpeer->sockfd);
+ calls = tpeer->calls;
+#if 0
+ ast_debug(1, "Trunking %d call chunks in %d bytes to %s:%d, ts=%d\n", calls, fr->datalen, ast_inet_ntoa(tpeer->addr.sin_addr), ntohs(tpeer->addr.sin_port), ntohl(mth->ts));
+#endif
+ /* Reset transmit trunk side data */
+ tpeer->trunkdatalen = 0;
+ tpeer->calls = 0;
+ }
+ if (res < 0)
+ return res;
+ return calls;
+}
+
+static inline int iax2_trunk_expired(struct iax2_trunk_peer *tpeer, struct timeval *now)
+{
+ /* Drop when trunk is about 5 seconds idle */
+ if (now->tv_sec > tpeer->trunkact.tv_sec + 5)
+ return 1;
+ return 0;
+}
+
+static int timing_read(int *id, int fd, short events, void *cbdata)
+{
+ char buf[1024];
+ int res, processed = 0, totalcalls = 0;
+ struct iax2_trunk_peer *tpeer = NULL, *drop = NULL;
+#ifdef ZT_TIMERACK
+ int x = 1;
+#endif
+ struct timeval now = ast_tvnow();
+ if (iaxtrunkdebug)
+ ast_verbose("Beginning trunk processing. Trunk queue ceiling is %d bytes per host\n", trunkmaxsize);
+ if (events & AST_IO_PRI) {
+#ifdef ZT_TIMERACK
+ /* Great, this is a timing interface, just call the ioctl */
+ if (ioctl(fd, ZT_TIMERACK, &x))
+ ast_log(LOG_WARNING, "Unable to acknowledge zap timer\n");
+ res = 0;
+#endif
+ } else {
+ /* Read and ignore from the pseudo channel for timing */
+ res = read(fd, buf, sizeof(buf));
+ if (res < 1) {
+ ast_log(LOG_WARNING, "Unable to read from timing fd\n");
+ return 1;
+ }
+ }
+ /* For each peer that supports trunking... */
+ AST_LIST_LOCK(&tpeers);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&tpeers, tpeer, list) {
+ processed++;
+ res = 0;
+ ast_mutex_lock(&tpeer->lock);
+ /* We can drop a single tpeer per pass. That makes all this logic
+ substantially easier */
+ if (!drop && iax2_trunk_expired(tpeer, &now)) {
+ /* Take it out of the list, but don't free it yet, because it
+ could be in use */
+ AST_LIST_REMOVE_CURRENT(list);
+ drop = tpeer;
+ } else {
+ res = send_trunk(tpeer, &now);
+ trunk_timed++;
+ if (iaxtrunkdebug)
+ ast_verbose(" - Trunk peer (%s:%d) has %d call chunk%s in transit, %d bytes backloged and has hit a high water mark of %d bytes\n", ast_inet_ntoa(tpeer->addr.sin_addr), ntohs(tpeer->addr.sin_port), res, (res != 1) ? "s" : "", tpeer->trunkdatalen, tpeer->trunkdataalloc);
+ }
+ totalcalls += res;
+ res = 0;
+ ast_mutex_unlock(&tpeer->lock);
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&tpeers);
+
+ if (drop) {
+ ast_mutex_lock(&drop->lock);
+ /* Once we have this lock, we're sure nobody else is using it or could use it once we release it,
+ because by the time they could get tpeerlock, we've already grabbed it */
+ ast_debug(1, "Dropping unused iax2 trunk peer '%s:%d'\n", ast_inet_ntoa(drop->addr.sin_addr), ntohs(drop->addr.sin_port));
+ ast_free(drop->trunkdata);
+ ast_mutex_unlock(&drop->lock);
+ ast_mutex_destroy(&drop->lock);
+ ast_free(drop);
+
+ }
+
+ if (iaxtrunkdebug)
+ ast_verbose("Ending trunk processing with %d peers and %d call chunks processed\n", processed, totalcalls);
+ iaxtrunkdebug = 0;
+
+ return 1;
+}
+
+struct dpreq_data {
+ int callno;
+ char context[AST_MAX_EXTENSION];
+ char callednum[AST_MAX_EXTENSION];
+ char *callerid;
+};
+
+static void dp_lookup(int callno, const char *context, const char *callednum, const char *callerid, int skiplock)
+{
+ unsigned short dpstatus = 0;
+ struct iax_ie_data ied1;
+ int mm;
+
+ memset(&ied1, 0, sizeof(ied1));
+ mm = ast_matchmore_extension(NULL, context, callednum, 1, callerid);
+ /* Must be started */
+ if (!strcmp(callednum, ast_parking_ext()) || ast_exists_extension(NULL, context, callednum, 1, callerid)) {
+ dpstatus = IAX_DPSTATUS_EXISTS;
+ } else if (ast_canmatch_extension(NULL, context, callednum, 1, callerid)) {
+ dpstatus = IAX_DPSTATUS_CANEXIST;
+ } else {
+ dpstatus = IAX_DPSTATUS_NONEXISTENT;
+ }
+ if (ast_ignore_pattern(context, callednum))
+ dpstatus |= IAX_DPSTATUS_IGNOREPAT;
+ if (mm)
+ dpstatus |= IAX_DPSTATUS_MATCHMORE;
+ if (!skiplock)
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno]) {
+ iax_ie_append_str(&ied1, IAX_IE_CALLED_NUMBER, callednum);
+ iax_ie_append_short(&ied1, IAX_IE_DPSTATUS, dpstatus);
+ iax_ie_append_short(&ied1, IAX_IE_REFRESH, iaxdefaultdpcache);
+ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_DPREP, 0, ied1.buf, ied1.pos, -1);
+ }
+ if (!skiplock)
+ ast_mutex_unlock(&iaxsl[callno]);
+}
+
+static void *dp_lookup_thread(void *data)
+{
+ /* Look up for dpreq */
+ struct dpreq_data *dpr = data;
+ dp_lookup(dpr->callno, dpr->context, dpr->callednum, dpr->callerid, 0);
+ if (dpr->callerid)
+ ast_free(dpr->callerid);
+ ast_free(dpr);
+ return NULL;
+}
+
+static void spawn_dp_lookup(int callno, const char *context, const char *callednum, const char *callerid)
+{
+ pthread_t newthread;
+ struct dpreq_data *dpr;
+
+ if (!(dpr = ast_calloc(1, sizeof(*dpr))))
+ return;
+
+ dpr->callno = callno;
+ ast_copy_string(dpr->context, context, sizeof(dpr->context));
+ ast_copy_string(dpr->callednum, callednum, sizeof(dpr->callednum));
+ if (callerid)
+ dpr->callerid = ast_strdup(callerid);
+ if (ast_pthread_create_detached(&newthread, NULL, dp_lookup_thread, dpr)) {
+ ast_log(LOG_WARNING, "Unable to start lookup thread!\n");
+ }
+}
+
+struct iax_dual {
+ struct ast_channel *chan1;
+ struct ast_channel *chan2;
+};
+
+static void *iax_park_thread(void *stuff)
+{
+ struct ast_channel *chan1, *chan2;
+ struct iax_dual *d;
+ struct ast_frame *f;
+ int ext;
+ int res;
+ d = stuff;
+ chan1 = d->chan1;
+ chan2 = d->chan2;
+ ast_free(d);
+ f = ast_read(chan1);
+ if (f)
+ ast_frfree(f);
+ res = ast_park_call(chan1, chan2, 0, &ext);
+ ast_hangup(chan2);
+ ast_log(LOG_NOTICE, "Parked on extension '%d'\n", ext);
+ return NULL;
+}
+
+static int iax_park(struct ast_channel *chan1, struct ast_channel *chan2)
+{
+ struct iax_dual *d;
+ struct ast_channel *chan1m, *chan2m;
+ pthread_t th;
+ chan1m = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, chan2->accountcode, chan1->exten, chan1->context, chan1->amaflags, "Parking/%s", chan1->name);
+ chan2m = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, chan2->accountcode, chan2->exten, chan2->context, chan2->amaflags, "IAXPeer/%s",chan2->name);
+ if (chan2m && chan1m) {
+ /* Make formats okay */
+ chan1m->readformat = chan1->readformat;
+ chan1m->writeformat = chan1->writeformat;
+ ast_channel_masquerade(chan1m, chan1);
+ /* Setup the extensions and such */
+ ast_copy_string(chan1m->context, chan1->context, sizeof(chan1m->context));
+ ast_copy_string(chan1m->exten, chan1->exten, sizeof(chan1m->exten));
+ chan1m->priority = chan1->priority;
+
+ /* We make a clone of the peer channel too, so we can play
+ back the announcement */
+ /* Make formats okay */
+ chan2m->readformat = chan2->readformat;
+ chan2m->writeformat = chan2->writeformat;
+ ast_channel_masquerade(chan2m, chan2);
+ /* Setup the extensions and such */
+ ast_copy_string(chan2m->context, chan2->context, sizeof(chan2m->context));
+ ast_copy_string(chan2m->exten, chan2->exten, sizeof(chan2m->exten));
+ chan2m->priority = chan2->priority;
+ if (ast_do_masquerade(chan2m)) {
+ ast_log(LOG_WARNING, "Masquerade failed :(\n");
+ ast_hangup(chan2m);
+ return -1;
+ }
+ } else {
+ if (chan1m)
+ ast_hangup(chan1m);
+ if (chan2m)
+ ast_hangup(chan2m);
+ return -1;
+ }
+ if ((d = ast_calloc(1, sizeof(*d)))) {
+ d->chan1 = chan1m;
+ d->chan2 = chan2m;
+ if (!ast_pthread_create_detached_background(&th, NULL, iax_park_thread, d)) {
+ return 0;
+ }
+ ast_free(d);
+ }
+ return -1;
+}
+
+
+static int iax2_provision(struct sockaddr_in *end, int sockfd, char *dest, const char *template, int force);
+
+static int check_provisioning(struct sockaddr_in *sin, int sockfd, char *si, unsigned int ver)
+{
+ unsigned int ourver;
+ char rsi[80];
+ snprintf(rsi, sizeof(rsi), "si-%s", si);
+ if (iax_provision_version(&ourver, rsi, 1))
+ return 0;
+ ast_debug(1, "Service identifier '%s', we think '%08x', they think '%08x'\n", si, ourver, ver);
+ if (ourver != ver)
+ iax2_provision(sin, sockfd, NULL, rsi, 1);
+ return 0;
+}
+
+static void construct_rr(struct chan_iax2_pvt *pvt, struct iax_ie_data *iep)
+{
+ jb_info stats;
+ jb_getinfo(pvt->jb, &stats);
+
+ memset(iep, 0, sizeof(*iep));
+
+ iax_ie_append_int(iep,IAX_IE_RR_JITTER, stats.jitter);
+ if(stats.frames_in == 0) stats.frames_in = 1;
+ iax_ie_append_int(iep,IAX_IE_RR_LOSS, ((0xff & (stats.losspct/1000)) << 24 | (stats.frames_lost & 0x00ffffff)));
+ iax_ie_append_int(iep,IAX_IE_RR_PKTS, stats.frames_in);
+ iax_ie_append_short(iep,IAX_IE_RR_DELAY, stats.current - stats.min);
+ iax_ie_append_int(iep,IAX_IE_RR_DROPPED, stats.frames_dropped);
+ iax_ie_append_int(iep,IAX_IE_RR_OOO, stats.frames_ooo);
+}
+
+static void save_rr(struct iax_frame *fr, struct iax_ies *ies)
+{
+ iaxs[fr->callno]->remote_rr.jitter = ies->rr_jitter;
+ iaxs[fr->callno]->remote_rr.losspct = ies->rr_loss >> 24;
+ iaxs[fr->callno]->remote_rr.losscnt = ies->rr_loss & 0xffffff;
+ iaxs[fr->callno]->remote_rr.packets = ies->rr_pkts;
+ iaxs[fr->callno]->remote_rr.delay = ies->rr_delay;
+ iaxs[fr->callno]->remote_rr.dropped = ies->rr_dropped;
+ iaxs[fr->callno]->remote_rr.ooo = ies->rr_ooo;
+}
+
+static void save_osptoken(struct iax_frame *fr, struct iax_ies *ies)
+{
+ int i;
+ unsigned int length, offset = 0;
+ char full_osptoken[IAX_MAX_OSPBUFF_SIZE];
+
+ for (i = 0; i < IAX_MAX_OSPBLOCK_NUM; i++) {
+ length = ies->ospblocklength[i];
+ if (length != 0) {
+ if (length > IAX_MAX_OSPBLOCK_SIZE) {
+ /* OSP token block length wrong, clear buffer */
+ offset = 0;
+ break;
+ } else {
+ memcpy(full_osptoken + offset, ies->osptokenblock[i], length);
+ offset += length;
+ }
+ } else {
+ break;
+ }
+ }
+ *(full_osptoken + offset) = '\0';
+ if (strlen(full_osptoken) != offset) {
+ /* OSP token length wrong, clear buffer */
+ *full_osptoken = '\0';
+ }
+
+ ast_string_field_set(iaxs[fr->callno], osptoken, full_osptoken);
+}
+
+static int socket_process(struct iax2_thread *thread);
+
+/*!
+ * \brief Handle any deferred full frames for this thread
+ */
+static void handle_deferred_full_frames(struct iax2_thread *thread)
+{
+ struct iax2_pkt_buf *pkt_buf;
+
+ ast_mutex_lock(&thread->lock);
+
+ while ((pkt_buf = AST_LIST_REMOVE_HEAD(&thread->full_frames, entry))) {
+ ast_mutex_unlock(&thread->lock);
+
+ thread->buf = pkt_buf->buf;
+ thread->buf_len = pkt_buf->len;
+ thread->buf_size = pkt_buf->len + 1;
+
+ socket_process(thread);
+
+ thread->buf = NULL;
+ ast_free(pkt_buf);
+
+ ast_mutex_lock(&thread->lock);
+ }
+
+ ast_mutex_unlock(&thread->lock);
+}
+
+/*!
+ * \brief Queue the last read full frame for processing by a certain thread
+ *
+ * If there are already any full frames queued, they are sorted
+ * by sequence number.
+ */
+static void defer_full_frame(struct iax2_thread *from_here, struct iax2_thread *to_here)
+{
+ struct iax2_pkt_buf *pkt_buf, *cur_pkt_buf;
+ struct ast_iax2_full_hdr *fh, *cur_fh;
+
+ if (!(pkt_buf = ast_calloc(1, sizeof(*pkt_buf) + from_here->buf_len)))
+ return;
+
+ pkt_buf->len = from_here->buf_len;
+ memcpy(pkt_buf->buf, from_here->buf, pkt_buf->len);
+
+ fh = (struct ast_iax2_full_hdr *) pkt_buf->buf;
+ ast_mutex_lock(&to_here->lock);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&to_here->full_frames, cur_pkt_buf, entry) {
+ cur_fh = (struct ast_iax2_full_hdr *) cur_pkt_buf->buf;
+ if (fh->oseqno < cur_fh->oseqno) {
+ AST_LIST_INSERT_BEFORE_CURRENT(pkt_buf, entry);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+
+ if (!cur_pkt_buf)
+ AST_LIST_INSERT_TAIL(&to_here->full_frames, pkt_buf, entry);
+
+ ast_mutex_unlock(&to_here->lock);
+}
+
+static int socket_read(int *id, int fd, short events, void *cbdata)
+{
+ struct iax2_thread *thread;
+ socklen_t len;
+ time_t t;
+ static time_t last_errtime = 0;
+ struct ast_iax2_full_hdr *fh;
+
+ if (!(thread = find_idle_thread())) {
+ time(&t);
+ if (t != last_errtime)
+ ast_log(LOG_NOTICE, "Out of idle IAX2 threads for I/O, pausing!\n");
+ last_errtime = t;
+ usleep(1);
+ return 1;
+ }
+
+ len = sizeof(thread->iosin);
+ thread->iofd = fd;
+ thread->buf_len = recvfrom(fd, thread->readbuf, sizeof(thread->readbuf), 0, (struct sockaddr *) &thread->iosin, &len);
+ thread->buf_size = sizeof(thread->readbuf);
+ thread->buf = thread->readbuf;
+ if (thread->buf_len < 0) {
+ if (errno != ECONNREFUSED && errno != EAGAIN)
+ ast_log(LOG_WARNING, "Error: %s\n", strerror(errno));
+ handle_error();
+ thread->iostate = IAX_IOSTATE_IDLE;
+ signal_condition(&thread->lock, &thread->cond);
+ return 1;
+ }
+ if (test_losspct && ((100.0 * ast_random() / (RAND_MAX + 1.0)) < test_losspct)) { /* simulate random loss condition */
+ thread->iostate = IAX_IOSTATE_IDLE;
+ signal_condition(&thread->lock, &thread->cond);
+ return 1;
+ }
+
+ /* Determine if this frame is a full frame; if so, and any thread is currently
+ processing a full frame for the same callno from this peer, then drop this
+ frame (and the peer will retransmit it) */
+ fh = (struct ast_iax2_full_hdr *) thread->buf;
+ if (ntohs(fh->scallno) & IAX_FLAG_FULL) {
+ struct iax2_thread *cur = NULL;
+ uint16_t callno = ntohs(fh->scallno) & ~IAX_FLAG_FULL;
+
+ AST_LIST_LOCK(&active_list);
+ AST_LIST_TRAVERSE(&active_list, cur, list) {
+ if ((cur->ffinfo.callno == callno) &&
+ !inaddrcmp(&cur->ffinfo.sin, &thread->iosin))
+ break;
+ }
+ if (cur) {
+ /* we found another thread processing a full frame for this call,
+ so queue it up for processing later. */
+ defer_full_frame(thread, cur);
+ AST_LIST_UNLOCK(&active_list);
+ thread->iostate = IAX_IOSTATE_IDLE;
+ signal_condition(&thread->lock, &thread->cond);
+ return 1;
+ } else {
+ /* this thread is going to process this frame, so mark it */
+ thread->ffinfo.callno = callno;
+ memcpy(&thread->ffinfo.sin, &thread->iosin, sizeof(thread->ffinfo.sin));
+ thread->ffinfo.type = fh->type;
+ thread->ffinfo.csub = fh->csub;
+ }
+ AST_LIST_UNLOCK(&active_list);
+ }
+
+ /* Mark as ready and send on its way */
+ thread->iostate = IAX_IOSTATE_READY;
+#ifdef DEBUG_SCHED_MULTITHREAD
+ ast_copy_string(thread->curfunc, "socket_process", sizeof(thread->curfunc));
+#endif
+ signal_condition(&thread->lock, &thread->cond);
+
+ return 1;
+}
+
+static int socket_process_meta(int packet_len, struct ast_iax2_meta_hdr *meta, struct sockaddr_in *sin, int sockfd,
+ struct iax_frame *fr)
+{
+ unsigned char metatype;
+ struct ast_iax2_meta_trunk_mini *mtm;
+ struct ast_iax2_meta_trunk_hdr *mth;
+ struct ast_iax2_meta_trunk_entry *mte;
+ struct iax2_trunk_peer *tpeer;
+ unsigned int ts;
+ void *ptr;
+ struct timeval rxtrunktime;
+ struct ast_frame f = { 0, };
+
+ if (packet_len < sizeof(*meta)) {
+ ast_log(LOG_WARNING, "Rejecting packet from '%s.%d' that is flagged as a meta frame but is too short\n",
+ ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ return 1;
+ }
+
+ if (meta->metacmd != IAX_META_TRUNK)
+ return 1;
+
+ if (packet_len < (sizeof(*meta) + sizeof(*mth))) {
+ ast_log(LOG_WARNING, "midget meta trunk packet received (%d of %d min)\n", packet_len,
+ (int) (sizeof(*meta) + sizeof(*mth)));
+ return 1;
+ }
+ mth = (struct ast_iax2_meta_trunk_hdr *)(meta->data);
+ ts = ntohl(mth->ts);
+ metatype = meta->cmddata;
+ packet_len -= (sizeof(*meta) + sizeof(*mth));
+ ptr = mth->data;
+ tpeer = find_tpeer(sin, sockfd);
+ if (!tpeer) {
+ ast_log(LOG_WARNING, "Unable to accept trunked packet from '%s:%d': No matching peer\n",
+ ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ return 1;
+ }
+ tpeer->trunkact = ast_tvnow();
+ if (!ts || ast_tvzero(tpeer->rxtrunktime))
+ tpeer->rxtrunktime = tpeer->trunkact;
+ rxtrunktime = tpeer->rxtrunktime;
+ ast_mutex_unlock(&tpeer->lock);
+ while (packet_len >= sizeof(*mte)) {
+ /* Process channels */
+ unsigned short callno, trunked_ts, len;
+
+ if (metatype == IAX_META_TRUNK_MINI) {
+ mtm = (struct ast_iax2_meta_trunk_mini *) ptr;
+ ptr += sizeof(*mtm);
+ packet_len -= sizeof(*mtm);
+ len = ntohs(mtm->len);
+ callno = ntohs(mtm->mini.callno);
+ trunked_ts = ntohs(mtm->mini.ts);
+ } else if (metatype == IAX_META_TRUNK_SUPERMINI) {
+ mte = (struct ast_iax2_meta_trunk_entry *)ptr;
+ ptr += sizeof(*mte);
+ packet_len -= sizeof(*mte);
+ len = ntohs(mte->len);
+ callno = ntohs(mte->callno);
+ trunked_ts = 0;
+ } else {
+ ast_log(LOG_WARNING, "Unknown meta trunk cmd from '%s:%d': dropping\n", ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ break;
+ }
+ /* Stop if we don't have enough data */
+ if (len > packet_len)
+ break;
+ fr->callno = find_callno(callno & ~IAX_FLAG_FULL, 0, sin, NEW_PREVENT, sockfd);
+ if (!fr->callno)
+ continue;
+
+ ast_mutex_lock(&iaxsl[fr->callno]);
+
+ /* If it's a valid call, deliver the contents. If not, we
+ drop it, since we don't have a scallno to use for an INVAL */
+ /* Process as a mini frame */
+ memset(&f, 0, sizeof(f));
+ f.frametype = AST_FRAME_VOICE;
+ if (!iaxs[fr->callno]) {
+ /* drop it */
+ } else if (iaxs[fr->callno]->voiceformat == 0) {
+ ast_log(LOG_WARNING, "Received trunked frame before first full voice frame\n ");
+ iax2_vnak(fr->callno);
+ } else {
+ f.subclass = iaxs[fr->callno]->voiceformat;
+ f.datalen = len;
+ if (f.datalen >= 0) {
+ if (f.datalen)
+ f.data = ptr;
+ else
+ f.data = NULL;
+ if (trunked_ts)
+ fr->ts = (iaxs[fr->callno]->last & 0xFFFF0000L) | (trunked_ts & 0xffff);
+ else
+ fr->ts = fix_peerts(&rxtrunktime, fr->callno, ts);
+ /* Don't pass any packets until we're started */
+ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED)) {
+ struct iax_frame *duped_fr;
+
+ /* Common things */
+ f.src = "IAX2";
+ f.mallocd = 0;
+ f.offset = 0;
+ if (f.datalen && (f.frametype == AST_FRAME_VOICE))
+ f.samples = ast_codec_get_samples(&f);
+ else
+ f.samples = 0;
+ fr->outoforder = 0;
+ iax_frame_wrap(fr, &f);
+ duped_fr = iaxfrdup2(fr);
+ if (duped_fr)
+ schedule_delivery(duped_fr, 1, 1, &fr->ts);
+ if (iaxs[fr->callno] && iaxs[fr->callno]->last < fr->ts)
+ iaxs[fr->callno]->last = fr->ts;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Datalen < 0?\n");
+ }
+ }
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ ptr += len;
+ packet_len -= len;
+ }
+
+ return 1;
+}
+
+static int acf_iaxvar_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ struct ast_datastore *variablestore = ast_channel_datastore_find(chan, &iax2_variable_datastore_info, NULL);
+ AST_LIST_HEAD(, ast_var_t) *varlist;
+ struct ast_var_t *var;
+
+ if (!variablestore) {
+ *buf = '\0';
+ return 0;
+ }
+ varlist = variablestore->data;
+
+ AST_LIST_LOCK(varlist);
+ AST_LIST_TRAVERSE(varlist, var, entries) {
+ if (strcmp(var->name, data) == 0) {
+ ast_copy_string(buf, var->value, len);
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(varlist);
+ return 0;
+}
+
+static int acf_iaxvar_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
+{
+ struct ast_datastore *variablestore = ast_channel_datastore_find(chan, &iax2_variable_datastore_info, NULL);
+ AST_LIST_HEAD(, ast_var_t) *varlist;
+ struct ast_var_t *var;
+
+ if (!variablestore) {
+ variablestore = ast_channel_datastore_alloc(&iax2_variable_datastore_info, NULL);
+ if (!variablestore) {
+ ast_log(LOG_ERROR, "Memory allocation error\n");
+ return -1;
+ }
+ varlist = ast_calloc(1, sizeof(*varlist));
+ if (!varlist) {
+ ast_log(LOG_ERROR, "Unable to assign new variable '%s'\n", data);
+ return -1;
+ }
+
+ AST_LIST_HEAD_INIT(varlist);
+ variablestore->data = varlist;
+ variablestore->inheritance = DATASTORE_INHERIT_FOREVER;
+ ast_channel_datastore_add(chan, variablestore);
+ } else
+ varlist = variablestore->data;
+
+ AST_LIST_LOCK(varlist);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(varlist, var, entries) {
+ if (strcmp(var->name, data) == 0) {
+ AST_LIST_REMOVE_CURRENT(entries);
+ ast_var_delete(var);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ var = ast_var_assign(data, value);
+ if (var)
+ AST_LIST_INSERT_TAIL(varlist, var, entries);
+ else
+ ast_log(LOG_ERROR, "Unable to assign new variable '%s'\n", data);
+ AST_LIST_UNLOCK(varlist);
+ return 0;
+}
+
+static struct ast_custom_function iaxvar_function = {
+ .name = "IAXVAR",
+ .synopsis = "Sets or retrieves a remote variable",
+ .syntax = "IAXVAR(<varname>)",
+ .read = acf_iaxvar_read,
+ .write = acf_iaxvar_write,
+};
+
+static int socket_process(struct iax2_thread *thread)
+{
+ struct sockaddr_in sin;
+ int res;
+ int updatehistory=1;
+ int new = NEW_PREVENT;
+ int dcallno = 0;
+ struct ast_iax2_full_hdr *fh = (struct ast_iax2_full_hdr *)thread->buf;
+ struct ast_iax2_mini_hdr *mh = (struct ast_iax2_mini_hdr *)thread->buf;
+ struct ast_iax2_meta_hdr *meta = (struct ast_iax2_meta_hdr *)thread->buf;
+ struct ast_iax2_video_hdr *vh = (struct ast_iax2_video_hdr *)thread->buf;
+ struct iax_frame *fr;
+ struct iax_frame *cur;
+ struct ast_frame f = { 0, };
+ struct ast_channel *c = NULL;
+ struct iax2_dpcache *dp;
+ struct iax2_peer *peer;
+ struct iax_ies ies;
+ struct iax_ie_data ied0, ied1;
+ int format;
+ int fd;
+ int exists;
+ int minivid = 0;
+ char empty[32]=""; /* Safety measure */
+ struct iax_frame *duped_fr;
+ char host_pref_buf[128];
+ char caller_pref_buf[128];
+ struct ast_codec_pref pref;
+ char *using_prefs = "mine";
+
+ /* allocate an iax_frame with 4096 bytes of data buffer */
+ fr = alloca(sizeof(*fr) + 4096);
+ fr->callno = 0;
+ fr->afdatalen = 4096; /* From alloca() above */
+
+ /* Copy frequently used parameters to the stack */
+ res = thread->buf_len;
+ fd = thread->iofd;
+ memcpy(&sin, &thread->iosin, sizeof(sin));
+
+ if (res < sizeof(*mh)) {
+ ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, (int) sizeof(*mh));
+ return 1;
+ }
+ if ((vh->zeros == 0) && (ntohs(vh->callno) & 0x8000)) {
+ if (res < sizeof(*vh)) {
+ ast_log(LOG_WARNING, "Rejecting packet from '%s.%d' that is flagged as a video frame but is too short\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+ return 1;
+ }
+
+ /* This is a video frame, get call number */
+ fr->callno = find_callno(ntohs(vh->callno) & ~0x8000, dcallno, &sin, new, fd);
+ minivid = 1;
+ } else if ((meta->zeros == 0) && !(ntohs(meta->metacmd) & 0x8000))
+ return socket_process_meta(res, meta, &sin, fd, fr);
+
+#ifdef DEBUG_SUPPORT
+ if (iaxdebug && (res >= sizeof(*fh)))
+ iax_showframe(NULL, fh, 1, &sin, res - sizeof(*fh));
+#endif
+ if (ntohs(mh->callno) & IAX_FLAG_FULL) {
+ if (res < sizeof(*fh)) {
+ ast_log(LOG_WARNING, "Rejecting packet from '%s.%d' that is flagged as a full frame but is too short\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+ return 1;
+ }
+
+ /* Get the destination call number */
+ dcallno = ntohs(fh->dcallno) & ~IAX_FLAG_RETRANS;
+ /* Retrieve the type and subclass */
+ f.frametype = fh->type;
+ if (f.frametype == AST_FRAME_VIDEO) {
+ f.subclass = uncompress_subclass(fh->csub & ~0x40) | ((fh->csub >> 6) & 0x1);
+ } else {
+ f.subclass = uncompress_subclass(fh->csub);
+ }
+ if ((f.frametype == AST_FRAME_IAX) && ((f.subclass == IAX_COMMAND_NEW) || (f.subclass == IAX_COMMAND_REGREQ) ||
+ (f.subclass == IAX_COMMAND_POKE) || (f.subclass == IAX_COMMAND_FWDOWNL) ||
+ (f.subclass == IAX_COMMAND_REGREL)))
+ new = NEW_ALLOW;
+ } else {
+ /* Don't know anything about it yet */
+ f.frametype = AST_FRAME_NULL;
+ f.subclass = 0;
+ }
+
+ if (!fr->callno)
+ fr->callno = find_callno(ntohs(mh->callno) & ~IAX_FLAG_FULL, dcallno, &sin, new, fd);
+
+ if (fr->callno > 0)
+ ast_mutex_lock(&iaxsl[fr->callno]);
+
+ if (!fr->callno || !iaxs[fr->callno]) {
+ /* A call arrived for a nonexistent destination. Unless it's an "inval"
+ frame, reply with an inval */
+ if (ntohs(mh->callno) & IAX_FLAG_FULL) {
+ /* We can only raw hangup control frames */
+ if (((f.subclass != IAX_COMMAND_INVAL) &&
+ (f.subclass != IAX_COMMAND_TXCNT) &&
+ (f.subclass != IAX_COMMAND_TXACC) &&
+ (f.subclass != IAX_COMMAND_FWDOWNL))||
+ (f.frametype != AST_FRAME_IAX))
+ raw_hangup(&sin, ntohs(fh->dcallno) & ~IAX_FLAG_RETRANS, ntohs(mh->callno) & ~IAX_FLAG_FULL,
+ fd);
+ }
+ if (fr->callno > 0)
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ if (ast_test_flag(iaxs[fr->callno], IAX_ENCRYPTED)) {
+ if (decrypt_frame(fr->callno, fh, &f, &res)) {
+ ast_log(LOG_NOTICE, "Packet Decrypt Failed!\n");
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+#ifdef DEBUG_SUPPORT
+ else if (iaxdebug)
+ iax_showframe(NULL, fh, 3, &sin, res - sizeof(*fh));
+#endif
+ }
+
+ /* count this frame */
+ iaxs[fr->callno]->frames_received++;
+
+ if (!inaddrcmp(&sin, &iaxs[fr->callno]->addr) && !minivid &&
+ f.subclass != IAX_COMMAND_TXCNT && /* for attended transfer */
+ f.subclass != IAX_COMMAND_TXACC) { /* for attended transfer */
+ iaxs[fr->callno]->peercallno = (unsigned short)(ntohs(mh->callno) & ~IAX_FLAG_FULL);
+ }
+ if (ntohs(mh->callno) & IAX_FLAG_FULL) {
+ if (iaxdebug)
+ ast_debug(1, "Received packet %d, (%d, %d)\n", fh->oseqno, f.frametype, f.subclass);
+ /* Check if it's out of order (and not an ACK or INVAL) */
+ fr->oseqno = fh->oseqno;
+ fr->iseqno = fh->iseqno;
+ fr->ts = ntohl(fh->ts);
+#ifdef IAXTESTS
+ if (test_resync) {
+ ast_debug(1, "Simulating frame ts resync, was %u now %u\n", fr->ts, fr->ts + test_resync);
+ fr->ts += test_resync;
+ }
+#endif /* IAXTESTS */
+#if 0
+ if ( (ntohs(fh->dcallno) & IAX_FLAG_RETRANS) ||
+ ( (f.frametype != AST_FRAME_VOICE) && ! (f.frametype == AST_FRAME_IAX &&
+ (f.subclass == IAX_COMMAND_NEW ||
+ f.subclass == IAX_COMMAND_AUTHREQ ||
+ f.subclass == IAX_COMMAND_ACCEPT ||
+ f.subclass == IAX_COMMAND_REJECT)) ) )
+#endif
+ if ((ntohs(fh->dcallno) & IAX_FLAG_RETRANS) || (f.frametype != AST_FRAME_VOICE))
+ updatehistory = 0;
+ if ((iaxs[fr->callno]->iseqno != fr->oseqno) &&
+ (iaxs[fr->callno]->iseqno ||
+ ((f.subclass != IAX_COMMAND_TXCNT) &&
+ (f.subclass != IAX_COMMAND_TXREADY) && /* for attended transfer */
+ (f.subclass != IAX_COMMAND_TXREL) && /* for attended transfer */
+ (f.subclass != IAX_COMMAND_UNQUELCH ) && /* for attended transfer */
+ (f.subclass != IAX_COMMAND_TXACC)) ||
+ (f.frametype != AST_FRAME_IAX))) {
+ if (
+ ((f.subclass != IAX_COMMAND_ACK) &&
+ (f.subclass != IAX_COMMAND_INVAL) &&
+ (f.subclass != IAX_COMMAND_TXCNT) &&
+ (f.subclass != IAX_COMMAND_TXREADY) && /* for attended transfer */
+ (f.subclass != IAX_COMMAND_TXREL) && /* for attended transfer */
+ (f.subclass != IAX_COMMAND_UNQUELCH ) && /* for attended transfer */
+ (f.subclass != IAX_COMMAND_TXACC) &&
+ (f.subclass != IAX_COMMAND_VNAK)) ||
+ (f.frametype != AST_FRAME_IAX)) {
+ /* If it's not an ACK packet, it's out of order. */
+ ast_debug(1, "Packet arrived out of order (expecting %d, got %d) (frametype = %d, subclass = %d)\n",
+ iaxs[fr->callno]->iseqno, fr->oseqno, f.frametype, f.subclass);
+ /* Check to see if we need to request retransmission,
+ * and take sequence number wraparound into account */
+ if ((unsigned char) (iaxs[fr->callno]->iseqno - fr->oseqno) < 128) {
+ /* If we've already seen it, ack it XXX There's a border condition here XXX */
+ if ((f.frametype != AST_FRAME_IAX) ||
+ ((f.subclass != IAX_COMMAND_ACK) && (f.subclass != IAX_COMMAND_INVAL))) {
+ ast_debug(1, "Acking anyway\n");
+ /* XXX Maybe we should handle its ack to us, but then again, it's probably outdated anyway, and if
+ we have anything to send, we'll retransmit and get an ACK back anyway XXX */
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ }
+ } else {
+ /* Send a VNAK requesting retransmission */
+ iax2_vnak(fr->callno);
+ }
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ } else {
+ /* Increment unless it's an ACK or VNAK */
+ if (((f.subclass != IAX_COMMAND_ACK) &&
+ (f.subclass != IAX_COMMAND_INVAL) &&
+ (f.subclass != IAX_COMMAND_TXCNT) &&
+ (f.subclass != IAX_COMMAND_TXACC) &&
+ (f.subclass != IAX_COMMAND_VNAK)) ||
+ (f.frametype != AST_FRAME_IAX))
+ iaxs[fr->callno]->iseqno++;
+ }
+ /* A full frame */
+ if (res < sizeof(*fh)) {
+ ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, (int) sizeof(*fh));
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ /* Ensure text frames are NULL-terminated */
+ if (f.frametype == AST_FRAME_TEXT && thread->buf[res - 1] != '\0') {
+ if (res < thread->buf_size)
+ thread->buf[res++] = '\0';
+ else /* Trims one character from the text message, but that's better than overwriting the end of the buffer. */
+ thread->buf[res - 1] = '\0';
+ }
+ f.datalen = res - sizeof(*fh);
+
+ /* Handle implicit ACKing unless this is an INVAL, and only if this is
+ from the real peer, not the transfer peer */
+ if (!inaddrcmp(&sin, &iaxs[fr->callno]->addr) &&
+ ((f.subclass != IAX_COMMAND_INVAL) ||
+ (f.frametype != AST_FRAME_IAX))) {
+ unsigned char x;
+ int call_to_destroy;
+ /* XXX This code is not very efficient. Surely there is a better way which still
+ properly handles boundary conditions? XXX */
+ /* First we have to qualify that the ACKed value is within our window */
+ for (x=iaxs[fr->callno]->rseqno; x != iaxs[fr->callno]->oseqno; x++)
+ if (fr->iseqno == x)
+ break;
+ if ((x != iaxs[fr->callno]->oseqno) || (iaxs[fr->callno]->oseqno == fr->iseqno)) {
+ /* The acknowledgement is within our window. Time to acknowledge everything
+ that it says to */
+ for (x=iaxs[fr->callno]->rseqno; x != fr->iseqno; x++) {
+ /* Ack the packet with the given timestamp */
+ if (iaxdebug)
+ ast_debug(1, "Cancelling transmission of packet %d\n", x);
+ call_to_destroy = 0;
+ AST_LIST_LOCK(&frame_queue);
+ AST_LIST_TRAVERSE(&frame_queue, cur, list) {
+ /* If it's our call, and our timestamp, mark -1 retries */
+ if ((fr->callno == cur->callno) && (x == cur->oseqno)) {
+ cur->retries = -1;
+ /* Destroy call if this is the end */
+ if (cur->final)
+ call_to_destroy = fr->callno;
+ }
+ }
+ AST_LIST_UNLOCK(&frame_queue);
+ if (call_to_destroy) {
+ if (iaxdebug)
+ ast_debug(1, "Really destroying %d, having been acked on final message\n", call_to_destroy);
+ iax2_destroy(call_to_destroy);
+ }
+ }
+ /* Note how much we've received acknowledgement for */
+ if (iaxs[fr->callno])
+ iaxs[fr->callno]->rseqno = fr->iseqno;
+ else {
+ /* Stop processing now */
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ } else {
+ ast_debug(1, "Received iseqno %d not within window %d->%d\n", fr->iseqno, iaxs[fr->callno]->rseqno, iaxs[fr->callno]->oseqno);
+ }
+ }
+ if (inaddrcmp(&sin, &iaxs[fr->callno]->addr) &&
+ ((f.frametype != AST_FRAME_IAX) ||
+ ((f.subclass != IAX_COMMAND_TXACC) &&
+ (f.subclass != IAX_COMMAND_TXCNT)))) {
+ /* Only messages we accept from a transfer host are TXACC and TXCNT */
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+
+ if (f.datalen) {
+ if (f.frametype == AST_FRAME_IAX) {
+ if (iax_parse_ies(&ies, thread->buf + sizeof(*fh), f.datalen)) {
+ ast_log(LOG_WARNING, "Undecodable frame received from '%s'\n", ast_inet_ntoa(sin.sin_addr));
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ f.data = NULL;
+ f.datalen = 0;
+ } else
+ f.data = thread->buf + sizeof(*fh);
+ } else {
+ if (f.frametype == AST_FRAME_IAX)
+ f.data = NULL;
+ else
+ f.data = empty;
+ memset(&ies, 0, sizeof(ies));
+ }
+
+ /* when we receive the first full frame for a new incoming channel,
+ it is safe to start the PBX on the channel because we have now
+ completed a 3-way handshake with the peer */
+ if ((f.frametype == AST_FRAME_VOICE) ||
+ (f.frametype == AST_FRAME_VIDEO) ||
+ (f.frametype == AST_FRAME_IAX)) {
+ if (ast_test_flag(iaxs[fr->callno], IAX_DELAYPBXSTART)) {
+ ast_clear_flag(iaxs[fr->callno], IAX_DELAYPBXSTART);
+ if (!ast_iax2_new(fr->callno, AST_STATE_RING, iaxs[fr->callno]->chosenformat)) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ } else if (ies.vars) {
+ struct ast_datastore *variablestore;
+ struct ast_variable *var, *prev = NULL;
+ AST_LIST_HEAD(, ast_var_t) *varlist;
+ varlist = ast_calloc(1, sizeof(*varlist));
+ variablestore = ast_channel_datastore_alloc(&iax2_variable_datastore_info, NULL);
+ if (variablestore && varlist) {
+ variablestore->data = varlist;
+ variablestore->inheritance = DATASTORE_INHERIT_FOREVER;
+ AST_LIST_HEAD_INIT(varlist);
+ for (var = ies.vars; var; var = var->next) {
+ struct ast_var_t *newvar = ast_var_assign(var->name, var->value);
+ if (prev)
+ ast_free(prev);
+ prev = var;
+ if (!newvar) {
+ /* Don't abort list traversal, as this would leave ies.vars in an inconsistent state. */
+ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n");
+ } else {
+ AST_LIST_INSERT_TAIL(varlist, newvar, entries);
+ }
+ }
+ if (prev)
+ ast_free(prev);
+ ies.vars = NULL;
+ ast_channel_datastore_add(c, variablestore);
+ } else {
+ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n");
+ if (variablestore)
+ ast_channel_datastore_free(variablestore);
+ if (varlist)
+ ast_free(varlist);
+ }
+ }
+ }
+ }
+
+ if (f.frametype == AST_FRAME_VOICE) {
+ if (f.subclass != iaxs[fr->callno]->voiceformat) {
+ iaxs[fr->callno]->voiceformat = f.subclass;
+ ast_debug(1, "Ooh, voice format changed to %d\n", f.subclass);
+ if (iaxs[fr->callno]->owner) {
+ int orignative;
+retryowner:
+ if (ast_channel_trylock(iaxs[fr->callno]->owner)) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ usleep(1);
+ ast_mutex_lock(&iaxsl[fr->callno]);
+ if (iaxs[fr->callno] && iaxs[fr->callno]->owner) goto retryowner;
+ }
+ if (iaxs[fr->callno]) {
+ if (iaxs[fr->callno]->owner) {
+ orignative = iaxs[fr->callno]->owner->nativeformats;
+ iaxs[fr->callno]->owner->nativeformats = f.subclass;
+ if (iaxs[fr->callno]->owner->readformat)
+ ast_set_read_format(iaxs[fr->callno]->owner, iaxs[fr->callno]->owner->readformat);
+ iaxs[fr->callno]->owner->nativeformats = orignative;
+ ast_channel_unlock(iaxs[fr->callno]->owner);
+ }
+ } else {
+ ast_debug(1, "Neat, somebody took away the channel at a magical time but i found it!\n");
+ /* Free remote variables (if any) */
+ if (ies.vars)
+ ast_variables_destroy(ies.vars);
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ }
+ }
+ }
+ if (f.frametype == AST_FRAME_VIDEO) {
+ if (f.subclass != iaxs[fr->callno]->videoformat) {
+ ast_debug(1, "Ooh, video format changed to %d\n", f.subclass & ~0x1);
+ iaxs[fr->callno]->videoformat = f.subclass & ~0x1;
+ }
+ }
+ if (f.frametype == AST_FRAME_IAX) {
+ if (iaxs[fr->callno]->initid > -1) {
+ /* Don't auto congest anymore since we've gotten something usefulb ack */
+ ast_sched_del(sched, iaxs[fr->callno]->initid);
+ iaxs[fr->callno]->initid = -1;
+ }
+ /* Handle the IAX pseudo frame itself */
+ if (iaxdebug)
+ ast_debug(1, "IAX subclass %d received\n", f.subclass);
+
+ /* Update last ts unless the frame's timestamp originated with us. */
+ if (iaxs[fr->callno]->last < fr->ts &&
+ f.subclass != IAX_COMMAND_ACK &&
+ f.subclass != IAX_COMMAND_PONG &&
+ f.subclass != IAX_COMMAND_LAGRP) {
+ iaxs[fr->callno]->last = fr->ts;
+ if (iaxdebug)
+ ast_debug(1, "For call=%d, set last=%d\n", fr->callno, fr->ts);
+ }
+
+ switch(f.subclass) {
+ case IAX_COMMAND_ACK:
+ /* Do nothing */
+ break;
+ case IAX_COMMAND_QUELCH:
+ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED)) {
+ /* Generate Manager Hold event, if necessary*/
+ if (iaxs[fr->callno]->owner) {
+ manager_event(EVENT_FLAG_CALL, "Hold",
+ "Status: On\r\n"
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n",
+ iaxs[fr->callno]->owner->name,
+ iaxs[fr->callno]->owner->uniqueid);
+ }
+
+ ast_set_flag(iaxs[fr->callno], IAX_QUELCH);
+ if (ies.musiconhold) {
+ if (iaxs[fr->callno]->owner && ast_bridged_channel(iaxs[fr->callno]->owner)) {
+ const char *mohsuggest = iaxs[fr->callno]->mohsuggest;
+ iax2_queue_control_data(fr->callno, AST_CONTROL_HOLD,
+ S_OR(mohsuggest, NULL),
+ !ast_strlen_zero(mohsuggest) ? strlen(mohsuggest) + 1 : 0);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ }
+ }
+ }
+ break;
+ case IAX_COMMAND_UNQUELCH:
+ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED)) {
+ /* Generate Manager Unhold event, if necessary*/
+ if (iaxs[fr->callno]->owner && ast_test_flag(iaxs[fr->callno], IAX_QUELCH)) {
+ manager_event(EVENT_FLAG_CALL, "Hold",
+ "Status: Off\r\n"
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n",
+ iaxs[fr->callno]->owner->name,
+ iaxs[fr->callno]->owner->uniqueid);
+ }
+
+ ast_clear_flag(iaxs[fr->callno], IAX_QUELCH);
+ if (iaxs[fr->callno]->owner && ast_bridged_channel(iaxs[fr->callno]->owner)) {
+ iax2_queue_control_data(fr->callno, AST_CONTROL_UNHOLD, NULL, 0);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ }
+ }
+ break;
+ case IAX_COMMAND_TXACC:
+ if (iaxs[fr->callno]->transferring == TRANSFER_BEGIN) {
+ /* Ack the packet with the given timestamp */
+ AST_LIST_LOCK(&frame_queue);
+ AST_LIST_TRAVERSE(&frame_queue, cur, list) {
+ /* Cancel any outstanding txcnt's */
+ if ((fr->callno == cur->callno) && (cur->transfer))
+ cur->retries = -1;
+ }
+ AST_LIST_UNLOCK(&frame_queue);
+ memset(&ied1, 0, sizeof(ied1));
+ iax_ie_append_short(&ied1, IAX_IE_CALLNO, iaxs[fr->callno]->callno);
+ send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_TXREADY, 0, ied1.buf, ied1.pos, -1);
+ iaxs[fr->callno]->transferring = TRANSFER_READY;
+ }
+ break;
+ case IAX_COMMAND_NEW:
+ /* Ignore if it's already up */
+ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED | IAX_STATE_TBD))
+ break;
+ if (ies.provverpres && ies.serviceident && sin.sin_addr.s_addr) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ check_provisioning(&sin, fd, ies.serviceident, ies.provver);
+ ast_mutex_lock(&iaxsl[fr->callno]);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ }
+ /* If we're in trunk mode, do it now, and update the trunk number in our frame before continuing */
+ if (ast_test_flag(iaxs[fr->callno], IAX_TRUNK)) {
+ int new_callno;
+ if ((new_callno = make_trunk(fr->callno, 1)) != -1)
+ fr->callno = new_callno;
+ }
+ /* For security, always ack immediately */
+ if (delayreject)
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ if (check_access(fr->callno, &sin, &ies)) {
+ /* They're not allowed on */
+ auth_fail(fr->callno, IAX_COMMAND_REJECT);
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Rejected connect attempt from %s, who was trying to reach '%s@%s'\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->exten, iaxs[fr->callno]->context);
+ break;
+ }
+ if (strcasecmp(iaxs[fr->callno]->exten, "TBD")) {
+ const char *context, *exten, *cid_num;
+
+ context = ast_strdupa(iaxs[fr->callno]->context);
+ exten = ast_strdupa(iaxs[fr->callno]->exten);
+ cid_num = ast_strdupa(iaxs[fr->callno]->cid_num);
+
+ /* This might re-enter the IAX code and need the lock */
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ exists = ast_exists_extension(NULL, context, exten, 1, cid_num);
+ ast_mutex_lock(&iaxsl[fr->callno]);
+
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ } else
+ exists = 0;
+ /* Get OSP token if it does exist */
+ save_osptoken(fr, &ies);
+ if (ast_strlen_zero(iaxs[fr->callno]->secret) && ast_strlen_zero(iaxs[fr->callno]->inkeys)) {
+ if (strcmp(iaxs[fr->callno]->exten, "TBD") && !exists) {
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_str(&ied0, IAX_IE_CAUSE, "No such context/extension");
+ iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_NO_ROUTE_DESTINATION);
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Rejected connect attempt from %s, request '%s@%s' does not exist\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->exten, iaxs[fr->callno]->context);
+ } else {
+ /* Select an appropriate format */
+
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOPREFS)) {
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP)) {
+ using_prefs = "reqonly";
+ } else {
+ using_prefs = "disabled";
+ }
+ format = iaxs[fr->callno]->peerformat & iaxs[fr->callno]->capability;
+ memset(&pref, 0, sizeof(pref));
+ strcpy(caller_pref_buf, "disabled");
+ strcpy(host_pref_buf, "disabled");
+ } else {
+ using_prefs = "mine";
+ /* If the information elements are in here... use them */
+ if (ies.codec_prefs)
+ ast_codec_pref_convert(&iaxs[fr->callno]->rprefs, ies.codec_prefs, 32, 0);
+ if (ast_codec_pref_index(&iaxs[fr->callno]->rprefs, 0)) {
+ /* If we are codec_first_choice we let the caller have the 1st shot at picking the codec.*/
+ if (ast_test_flag(iaxs[fr->callno], IAX_CODEC_USER_FIRST)) {
+ pref = iaxs[fr->callno]->rprefs;
+ using_prefs = "caller";
+ } else {
+ pref = iaxs[fr->callno]->prefs;
+ }
+ } else
+ pref = iaxs[fr->callno]->prefs;
+
+ format = ast_codec_choose(&pref, iaxs[fr->callno]->capability & iaxs[fr->callno]->peercapability, 0);
+ ast_codec_pref_string(&iaxs[fr->callno]->rprefs, caller_pref_buf, sizeof(caller_pref_buf) - 1);
+ ast_codec_pref_string(&iaxs[fr->callno]->prefs, host_pref_buf, sizeof(host_pref_buf) - 1);
+ }
+ if (!format) {
+ if(!ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP))
+ format = iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability;
+ if (!format) {
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_str(&ied0, IAX_IE_CAUSE, "Unable to negotiate codec");
+ iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL);
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ if (authdebug) {
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP))
+ ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested 0x%x incompatible with our capability 0x%x.\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->peerformat, iaxs[fr->callno]->capability);
+ else
+ ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability 0x%x/0x%x incompatible with our capability 0x%x.\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->peerformat, iaxs[fr->callno]->peercapability, iaxs[fr->callno]->capability);
+ }
+ } else {
+ /* Pick one... */
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP)) {
+ if(!(iaxs[fr->callno]->peerformat & iaxs[fr->callno]->capability))
+ format = 0;
+ } else {
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOPREFS)) {
+ using_prefs = ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP) ? "reqonly" : "disabled";
+ memset(&pref, 0, sizeof(pref));
+ format = ast_best_codec(iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability);
+ strcpy(caller_pref_buf,"disabled");
+ strcpy(host_pref_buf,"disabled");
+ } else {
+ using_prefs = "mine";
+ if (ast_codec_pref_index(&iaxs[fr->callno]->rprefs, 0)) {
+ /* Do the opposite of what we tried above. */
+ if (ast_test_flag(iaxs[fr->callno], IAX_CODEC_USER_FIRST)) {
+ pref = iaxs[fr->callno]->prefs;
+ } else {
+ pref = iaxs[fr->callno]->rprefs;
+ using_prefs = "caller";
+ }
+ format = ast_codec_choose(&pref, iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability, 1);
+
+ } else /* if no codec_prefs IE do it the old way */
+ format = ast_best_codec(iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability);
+ }
+ }
+
+ if (!format) {
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_str(&ied0, IAX_IE_CAUSE, "Unable to negotiate codec");
+ iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL);
+ ast_log(LOG_ERROR, "No best format in 0x%x???\n", iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability);
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability 0x%x/0x%x incompatible with our capability 0x%x.\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->peerformat, iaxs[fr->callno]->peercapability, iaxs[fr->callno]->capability);
+ ast_set_flag(iaxs[fr->callno], IAX_ALREADYGONE);
+ break;
+ }
+ }
+ }
+ if (format) {
+ /* No authentication required, let them in */
+ memset(&ied1, 0, sizeof(ied1));
+ iax_ie_append_int(&ied1, IAX_IE_FORMAT, format);
+ send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACCEPT, 0, ied1.buf, ied1.pos, -1);
+ if (strcmp(iaxs[fr->callno]->exten, "TBD")) {
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED);
+ ast_verb(3, "Accepting UNAUTHENTICATED call from %s:\n"
+ "%srequested format = %s,\n"
+ "%srequested prefs = %s,\n"
+ "%sactual format = %s,\n"
+ "%shost prefs = %s,\n"
+ "%spriority = %s\n",
+ ast_inet_ntoa(sin.sin_addr),
+ VERBOSE_PREFIX_4,
+ ast_getformatname(iaxs[fr->callno]->peerformat),
+ VERBOSE_PREFIX_4,
+ caller_pref_buf,
+ VERBOSE_PREFIX_4,
+ ast_getformatname(format),
+ VERBOSE_PREFIX_4,
+ host_pref_buf,
+ VERBOSE_PREFIX_4,
+ using_prefs);
+
+ iaxs[fr->callno]->chosenformat = format;
+ ast_set_flag(iaxs[fr->callno], IAX_DELAYPBXSTART);
+ } else {
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD);
+ /* If this is a TBD call, we're ready but now what... */
+ ast_verb(3, "Accepted unauthenticated TBD call from %s\n", ast_inet_ntoa(sin.sin_addr));
+ }
+ }
+ }
+ break;
+ }
+ if (iaxs[fr->callno]->authmethods & IAX_AUTH_MD5)
+ merge_encryption(iaxs[fr->callno],ies.encmethods);
+ else
+ iaxs[fr->callno]->encmethods = 0;
+ if (!authenticate_request(fr->callno) && iaxs[fr->callno])
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_AUTHENTICATED);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ break;
+ case IAX_COMMAND_DPREQ:
+ /* Request status in the dialplan */
+ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD) &&
+ !ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED) && ies.called_number) {
+ if (iaxcompat) {
+ /* Spawn a thread for the lookup */
+ spawn_dp_lookup(fr->callno, iaxs[fr->callno]->context, ies.called_number, iaxs[fr->callno]->cid_num);
+ } else {
+ /* Just look it up */
+ dp_lookup(fr->callno, iaxs[fr->callno]->context, ies.called_number, iaxs[fr->callno]->cid_num, 1);
+ }
+ }
+ break;
+ case IAX_COMMAND_HANGUP:
+ ast_set_flag(iaxs[fr->callno], IAX_ALREADYGONE);
+ ast_debug(1, "Immediately destroying %d, having received hangup\n", fr->callno);
+ /* Set hangup cause according to remote */
+ if (ies.causecode && iaxs[fr->callno]->owner)
+ iaxs[fr->callno]->owner->hangupcause = ies.causecode;
+ /* Send ack immediately, before we destroy */
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ iax2_destroy(fr->callno);
+ break;
+ case IAX_COMMAND_REJECT:
+ /* Set hangup cause according to remote */
+ if (ies.causecode && iaxs[fr->callno]->owner)
+ iaxs[fr->callno]->owner->hangupcause = ies.causecode;
+
+ if (!ast_test_flag(iaxs[fr->callno], IAX_PROVISION)) {
+ if (iaxs[fr->callno]->owner && authdebug)
+ ast_log(LOG_WARNING, "Call rejected by %s: %s\n",
+ ast_inet_ntoa(iaxs[fr->callno]->addr.sin_addr),
+ ies.cause ? ies.cause : "<Unknown>");
+ ast_debug(1, "Immediately destroying %d, having received reject\n",
+ fr->callno);
+ }
+ /* Send ack immediately, before we destroy */
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK,
+ fr->ts, NULL, 0, fr->iseqno);
+ if (!ast_test_flag(iaxs[fr->callno], IAX_PROVISION))
+ iaxs[fr->callno]->error = EPERM;
+ iax2_destroy(fr->callno);
+ break;
+ case IAX_COMMAND_TRANSFER:
+ if (iaxs[fr->callno]->owner && ast_bridged_channel(iaxs[fr->callno]->owner) && ies.called_number) {
+ /* Set BLINDTRANSFER channel variables */
+ pbx_builtin_setvar_helper(iaxs[fr->callno]->owner, "BLINDTRANSFER", ast_bridged_channel(iaxs[fr->callno]->owner)->name);
+ pbx_builtin_setvar_helper(ast_bridged_channel(iaxs[fr->callno]->owner), "BLINDTRANSFER", iaxs[fr->callno]->owner->name);
+ if (!strcmp(ies.called_number, ast_parking_ext())) {
+ if (iax_park(ast_bridged_channel(iaxs[fr->callno]->owner), iaxs[fr->callno]->owner)) {
+ ast_log(LOG_WARNING, "Failed to park call on '%s'\n", ast_bridged_channel(iaxs[fr->callno]->owner)->name);
+ } else if (ast_bridged_channel(iaxs[fr->callno]->owner)) {
+ ast_debug(1, "Parked call on '%s'\n", ast_bridged_channel(iaxs[fr->callno]->owner)->name);
+ }
+ } else {
+ if (ast_async_goto(ast_bridged_channel(iaxs[fr->callno]->owner), iaxs[fr->callno]->context, ies.called_number, 1))
+ ast_log(LOG_WARNING, "Async goto of '%s' to '%s@%s' failed\n", ast_bridged_channel(iaxs[fr->callno]->owner)->name,
+ ies.called_number, iaxs[fr->callno]->context);
+ else {
+ ast_debug(1, "Async goto of '%s' to '%s@%s' started\n", ast_bridged_channel(iaxs[fr->callno]->owner)->name,
+ ies.called_number, iaxs[fr->callno]->context);
+ }
+ }
+ } else {
+ ast_debug(1, "Async goto not applicable on call %d\n", fr->callno);
+ }
+ break;
+ case IAX_COMMAND_ACCEPT:
+ /* Ignore if call is already up or needs authentication or is a TBD */
+ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED | IAX_STATE_TBD | IAX_STATE_AUTHENTICATED))
+ break;
+ if (ast_test_flag(iaxs[fr->callno], IAX_PROVISION)) {
+ /* Send ack immediately, before we destroy */
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ iax2_destroy(fr->callno);
+ break;
+ }
+ if (ies.format) {
+ iaxs[fr->callno]->peerformat = ies.format;
+ } else {
+ if (iaxs[fr->callno]->owner)
+ iaxs[fr->callno]->peerformat = iaxs[fr->callno]->owner->nativeformats;
+ else
+ iaxs[fr->callno]->peerformat = iaxs[fr->callno]->capability;
+ }
+ ast_verb(3, "Call accepted by %s (format %s)\n", ast_inet_ntoa(iaxs[fr->callno]->addr.sin_addr), ast_getformatname(iaxs[fr->callno]->peerformat));
+ if (!(iaxs[fr->callno]->peerformat & iaxs[fr->callno]->capability)) {
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_str(&ied0, IAX_IE_CAUSE, "Unable to negotiate codec");
+ iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL);
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Rejected call to %s, format 0x%x incompatible with our capability 0x%x.\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->peerformat, iaxs[fr->callno]->capability);
+ } else {
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED);
+ if (iaxs[fr->callno]->owner) {
+ /* Switch us to use a compatible format */
+ iaxs[fr->callno]->owner->nativeformats = iaxs[fr->callno]->peerformat;
+ ast_verb(3, "Format for call is %s\n", ast_getformatname(iaxs[fr->callno]->owner->nativeformats));
+retryowner2:
+ if (ast_channel_trylock(iaxs[fr->callno]->owner)) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ usleep(1);
+ ast_mutex_lock(&iaxsl[fr->callno]);
+ if (iaxs[fr->callno] && iaxs[fr->callno]->owner) goto retryowner2;
+ }
+
+ if (iaxs[fr->callno] && iaxs[fr->callno]->owner) {
+ /* Setup read/write formats properly. */
+ if (iaxs[fr->callno]->owner->writeformat)
+ ast_set_write_format(iaxs[fr->callno]->owner, iaxs[fr->callno]->owner->writeformat);
+ if (iaxs[fr->callno]->owner->readformat)
+ ast_set_read_format(iaxs[fr->callno]->owner, iaxs[fr->callno]->owner->readformat);
+ ast_channel_unlock(iaxs[fr->callno]->owner);
+ }
+ }
+ }
+ if (iaxs[fr->callno]) {
+ AST_LIST_LOCK(&dpcache);
+ AST_LIST_TRAVERSE(&iaxs[fr->callno]->dpentries, dp, peer_list)
+ if (!(dp->flags & CACHE_FLAG_TRANSMITTED))
+ iax2_dprequest(dp, fr->callno);
+ AST_LIST_UNLOCK(&dpcache);
+ }
+ break;
+ case IAX_COMMAND_POKE:
+ /* Send back a pong packet with the original timestamp */
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_PONG, fr->ts, NULL, 0, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ break;
+ case IAX_COMMAND_PING:
+ {
+ struct iax_ie_data pingied;
+ construct_rr(iaxs[fr->callno], &pingied);
+ /* Send back a pong packet with the original timestamp */
+ send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_PONG, fr->ts, pingied.buf, pingied.pos, -1);
+ }
+ break;
+ case IAX_COMMAND_PONG:
+ /* Calculate ping time */
+ iaxs[fr->callno]->pingtime = calc_timestamp(iaxs[fr->callno], 0, &f) - fr->ts;
+ /* save RR info */
+ save_rr(fr, &ies);
+
+ if (iaxs[fr->callno]->peerpoke) {
+ peer = iaxs[fr->callno]->peerpoke;
+ if ((peer->lastms < 0) || (peer->historicms > peer->maxms)) {
+ if (iaxs[fr->callno]->pingtime <= peer->maxms) {
+ ast_log(LOG_NOTICE, "Peer '%s' is now REACHABLE! Time: %d\n", peer->name, iaxs[fr->callno]->pingtime);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Reachable\r\nTime: %d\r\n", peer->name, iaxs[fr->callno]->pingtime);
+ ast_device_state_changed("IAX2/%s", peer->name); /* Activate notification */
+ }
+ } else if ((peer->historicms > 0) && (peer->historicms <= peer->maxms)) {
+ if (iaxs[fr->callno]->pingtime > peer->maxms) {
+ ast_log(LOG_NOTICE, "Peer '%s' is now TOO LAGGED (%d ms)!\n", peer->name, iaxs[fr->callno]->pingtime);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Lagged\r\nTime: %d\r\n", peer->name, iaxs[fr->callno]->pingtime);
+ ast_device_state_changed("IAX2/%s", peer->name); /* Activate notification */
+ }
+ }
+ peer->lastms = iaxs[fr->callno]->pingtime;
+ if (peer->smoothing && (peer->lastms > -1))
+ peer->historicms = (iaxs[fr->callno]->pingtime + peer->historicms) / 2;
+ else if (peer->smoothing && peer->lastms < 0)
+ peer->historicms = (0 + peer->historicms) / 2;
+ else
+ peer->historicms = iaxs[fr->callno]->pingtime;
+
+ /* Remove scheduled iax2_poke_noanswer */
+ if (peer->pokeexpire > -1) {
+ if (!ast_sched_del(sched, peer->pokeexpire)) {
+ peer_unref(peer);
+ peer->pokeexpire = -1;
+ }
+ }
+ /* Schedule the next cycle */
+ if ((peer->lastms < 0) || (peer->historicms > peer->maxms))
+ peer->pokeexpire = iax2_sched_add(sched, peer->pokefreqnotok, iax2_poke_peer_s, peer_ref(peer));
+ else
+ peer->pokeexpire = iax2_sched_add(sched, peer->pokefreqok, iax2_poke_peer_s, peer_ref(peer));
+ if (peer->pokeexpire == -1)
+ peer_unref(peer);
+ /* and finally send the ack */
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ /* And wrap up the qualify call */
+ iax2_destroy(fr->callno);
+ peer->callno = 0;
+ ast_debug(1, "Peer %s: got pong, lastms %d, historicms %d, maxms %d\n", peer->name, peer->lastms, peer->historicms, peer->maxms);
+ }
+ break;
+ case IAX_COMMAND_LAGRQ:
+ case IAX_COMMAND_LAGRP:
+ f.src = "LAGRQ";
+ f.mallocd = 0;
+ f.offset = 0;
+ f.samples = 0;
+ iax_frame_wrap(fr, &f);
+ if(f.subclass == IAX_COMMAND_LAGRQ) {
+ /* Received a LAGRQ - echo back a LAGRP */
+ fr->af.subclass = IAX_COMMAND_LAGRP;
+ iax2_send(iaxs[fr->callno], &fr->af, fr->ts, -1, 0, 0, 0);
+ } else {
+ /* Received LAGRP in response to our LAGRQ */
+ unsigned int ts;
+ /* This is a reply we've been given, actually measure the difference */
+ ts = calc_timestamp(iaxs[fr->callno], 0, &fr->af);
+ iaxs[fr->callno]->lag = ts - fr->ts;
+ if (iaxdebug)
+ ast_debug(1, "Peer %s lag measured as %dms\n",
+ ast_inet_ntoa(iaxs[fr->callno]->addr.sin_addr), iaxs[fr->callno]->lag);
+ }
+ break;
+ case IAX_COMMAND_AUTHREQ:
+ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED | IAX_STATE_TBD)) {
+ ast_log(LOG_WARNING, "Call on %s is already up, can't start on it\n", iaxs[fr->callno]->owner ? iaxs[fr->callno]->owner->name : "<Unknown>");
+ break;
+ }
+ if (authenticate_reply(iaxs[fr->callno], &iaxs[fr->callno]->addr, &ies, iaxs[fr->callno]->secret, iaxs[fr->callno]->outkey)) {
+ ast_log(LOG_WARNING,
+ "I don't know how to authenticate %s to %s\n",
+ ies.username ? ies.username : "<unknown>", ast_inet_ntoa(iaxs[fr->callno]->addr.sin_addr));
+ }
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ break;
+ case IAX_COMMAND_AUTHREP:
+ /* For security, always ack immediately */
+ if (delayreject)
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ /* Ignore once we've started */
+ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED | IAX_STATE_TBD)) {
+ ast_log(LOG_WARNING, "Call on %s is already up, can't start on it\n", iaxs[fr->callno]->owner ? iaxs[fr->callno]->owner->name : "<Unknown>");
+ break;
+ }
+ if (authenticate_verify(iaxs[fr->callno], &ies)) {
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Host %s failed to authenticate as %s\n", ast_inet_ntoa(iaxs[fr->callno]->addr.sin_addr), iaxs[fr->callno]->username);
+ memset(&ied0, 0, sizeof(ied0));
+ auth_fail(fr->callno, IAX_COMMAND_REJECT);
+ break;
+ }
+ if (strcasecmp(iaxs[fr->callno]->exten, "TBD")) {
+ /* This might re-enter the IAX code and need the lock */
+ exists = ast_exists_extension(NULL, iaxs[fr->callno]->context, iaxs[fr->callno]->exten, 1, iaxs[fr->callno]->cid_num);
+ } else
+ exists = 0;
+ if (strcmp(iaxs[fr->callno]->exten, "TBD") && !exists) {
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Rejected connect attempt from %s, request '%s@%s' does not exist\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->exten, iaxs[fr->callno]->context);
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_str(&ied0, IAX_IE_CAUSE, "No such context/extension");
+ iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_NO_ROUTE_DESTINATION);
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ } else {
+ /* Select an appropriate format */
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOPREFS)) {
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP)) {
+ using_prefs = "reqonly";
+ } else {
+ using_prefs = "disabled";
+ }
+ format = iaxs[fr->callno]->peerformat & iaxs[fr->callno]->capability;
+ memset(&pref, 0, sizeof(pref));
+ strcpy(caller_pref_buf, "disabled");
+ strcpy(host_pref_buf, "disabled");
+ } else {
+ using_prefs = "mine";
+ if (ies.codec_prefs)
+ ast_codec_pref_convert(&iaxs[fr->callno]->rprefs, ies.codec_prefs, 32, 0);
+ if (ast_codec_pref_index(&iaxs[fr->callno]->rprefs, 0)) {
+ if (ast_test_flag(iaxs[fr->callno], IAX_CODEC_USER_FIRST)) {
+ pref = iaxs[fr->callno]->rprefs;
+ using_prefs = "caller";
+ } else {
+ pref = iaxs[fr->callno]->prefs;
+ }
+ } else /* if no codec_prefs IE do it the old way */
+ pref = iaxs[fr->callno]->prefs;
+
+ format = ast_codec_choose(&pref, iaxs[fr->callno]->capability & iaxs[fr->callno]->peercapability, 0);
+ ast_codec_pref_string(&iaxs[fr->callno]->rprefs, caller_pref_buf, sizeof(caller_pref_buf) - 1);
+ ast_codec_pref_string(&iaxs[fr->callno]->prefs, host_pref_buf, sizeof(host_pref_buf) - 1);
+ }
+ if (!format) {
+ if(!ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP)) {
+ ast_debug(1, "We don't do requested format %s, falling back to peer capability %d\n", ast_getformatname(iaxs[fr->callno]->peerformat), iaxs[fr->callno]->peercapability);
+ format = iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability;
+ }
+ if (!format) {
+ if (authdebug) {
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP))
+ ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested 0x%x incompatible with our capability 0x%x.\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->peerformat, iaxs[fr->callno]->capability);
+ else
+ ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability 0x%x/0x%x incompatible with our capability 0x%x.\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->peerformat, iaxs[fr->callno]->peercapability, iaxs[fr->callno]->capability);
+ }
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_str(&ied0, IAX_IE_CAUSE, "Unable to negotiate codec");
+ iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL);
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ } else {
+ /* Pick one... */
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP)) {
+ if(!(iaxs[fr->callno]->peerformat & iaxs[fr->callno]->capability))
+ format = 0;
+ } else {
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOPREFS)) {
+ using_prefs = ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP) ? "reqonly" : "disabled";
+ memset(&pref, 0, sizeof(pref));
+ format = ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP) ?
+ iaxs[fr->callno]->peerformat : ast_best_codec(iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability);
+ strcpy(caller_pref_buf,"disabled");
+ strcpy(host_pref_buf,"disabled");
+ } else {
+ using_prefs = "mine";
+ if (ast_codec_pref_index(&iaxs[fr->callno]->rprefs, 0)) {
+ /* Do the opposite of what we tried above. */
+ if (ast_test_flag(iaxs[fr->callno], IAX_CODEC_USER_FIRST)) {
+ pref = iaxs[fr->callno]->prefs;
+ } else {
+ pref = iaxs[fr->callno]->rprefs;
+ using_prefs = "caller";
+ }
+ format = ast_codec_choose(&pref, iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability, 1);
+ } else /* if no codec_prefs IE do it the old way */
+ format = ast_best_codec(iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability);
+ }
+ }
+ if (!format) {
+ ast_log(LOG_ERROR, "No best format in 0x%x???\n", iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability);
+ if (authdebug) {
+ if(ast_test_flag(iaxs[fr->callno], IAX_CODEC_NOCAP))
+ ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested 0x%x incompatible with our capability 0x%x.\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->peerformat, iaxs[fr->callno]->capability);
+ else
+ ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability 0x%x/0x%x incompatible with our capability 0x%x.\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->peerformat, iaxs[fr->callno]->peercapability, iaxs[fr->callno]->capability);
+ }
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_str(&ied0, IAX_IE_CAUSE, "Unable to negotiate codec");
+ iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL);
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ }
+ }
+ }
+ if (format) {
+ /* Authentication received */
+ memset(&ied1, 0, sizeof(ied1));
+ iax_ie_append_int(&ied1, IAX_IE_FORMAT, format);
+ send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACCEPT, 0, ied1.buf, ied1.pos, -1);
+ if (strcmp(iaxs[fr->callno]->exten, "TBD")) {
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED);
+ ast_verb(3, "Accepting AUTHENTICATED call from %s:\n"
+ "%srequested format = %s,\n"
+ "%srequested prefs = %s,\n"
+ "%sactual format = %s,\n"
+ "%shost prefs = %s,\n"
+ "%spriority = %s\n",
+ ast_inet_ntoa(sin.sin_addr),
+ VERBOSE_PREFIX_4,
+ ast_getformatname(iaxs[fr->callno]->peerformat),
+ VERBOSE_PREFIX_4,
+ caller_pref_buf,
+ VERBOSE_PREFIX_4,
+ ast_getformatname(format),
+ VERBOSE_PREFIX_4,
+ host_pref_buf,
+ VERBOSE_PREFIX_4,
+ using_prefs);
+
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED);
+ if (!(c = ast_iax2_new(fr->callno, AST_STATE_RING, format)))
+ iax2_destroy(fr->callno);
+ else if (ies.vars) {
+ struct ast_datastore *variablestore;
+ struct ast_variable *var, *prev = NULL;
+ AST_LIST_HEAD(, ast_var_t) *varlist;
+ varlist = ast_calloc(1, sizeof(*varlist));
+ variablestore = ast_channel_datastore_alloc(&iax2_variable_datastore_info, NULL);
+ if (variablestore && varlist) {
+ variablestore->data = varlist;
+ variablestore->inheritance = DATASTORE_INHERIT_FOREVER;
+ AST_LIST_HEAD_INIT(varlist);
+ for (var = ies.vars; var; var = var->next) {
+ struct ast_var_t *newvar = ast_var_assign(var->name, var->value);
+ if (prev)
+ ast_free(prev);
+ prev = var;
+ if (!newvar) {
+ /* Don't abort list traversal, as this would leave ies.vars in an inconsistent state. */
+ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n");
+ } else {
+ AST_LIST_INSERT_TAIL(varlist, newvar, entries);
+ }
+ }
+ if (prev)
+ ast_free(prev);
+ ies.vars = NULL;
+ ast_channel_datastore_add(c, variablestore);
+ } else {
+ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n");
+ if (variablestore)
+ ast_channel_datastore_free(variablestore);
+ if (varlist)
+ ast_free(varlist);
+ }
+ }
+ } else {
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD);
+ /* If this is a TBD call, we're ready but now what... */
+ ast_verb(3, "Accepted AUTHENTICATED TBD call from %s\n", ast_inet_ntoa(sin.sin_addr));
+ }
+ }
+ }
+ break;
+ case IAX_COMMAND_DIAL:
+ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD)) {
+ ast_clear_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD);
+ ast_string_field_set(iaxs[fr->callno], exten, ies.called_number ? ies.called_number : "s");
+ if (!ast_exists_extension(NULL, iaxs[fr->callno]->context, iaxs[fr->callno]->exten, 1, iaxs[fr->callno]->cid_num)) {
+ if (authdebug)
+ ast_log(LOG_NOTICE, "Rejected dial attempt from %s, request '%s@%s' does not exist\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->exten, iaxs[fr->callno]->context);
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_str(&ied0, IAX_IE_CAUSE, "No such context/extension");
+ iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_NO_ROUTE_DESTINATION);
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ } else {
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED);
+ ast_verb(3, "Accepting DIAL from %s, formats = 0x%x\n", ast_inet_ntoa(sin.sin_addr), iaxs[fr->callno]->peerformat);
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED);
+ send_command(iaxs[fr->callno], AST_FRAME_CONTROL, AST_CONTROL_PROGRESS, 0, NULL, 0, -1);
+ if (!(c = ast_iax2_new(fr->callno, AST_STATE_RING, iaxs[fr->callno]->peerformat)))
+ iax2_destroy(fr->callno);
+ else if (ies.vars) {
+ struct ast_datastore *variablestore;
+ struct ast_variable *var, *prev = NULL;
+ AST_LIST_HEAD(, ast_var_t) *varlist;
+ varlist = ast_calloc(1, sizeof(*varlist));
+ variablestore = ast_channel_datastore_alloc(&iax2_variable_datastore_info, NULL);
+ if (variablestore && varlist) {
+ variablestore->data = varlist;
+ variablestore->inheritance = DATASTORE_INHERIT_FOREVER;
+ AST_LIST_HEAD_INIT(varlist);
+ for (var = ies.vars; var; var = var->next) {
+ struct ast_var_t *newvar = ast_var_assign(var->name, var->value);
+ if (prev)
+ ast_free(prev);
+ prev = var;
+ if (!newvar) {
+ /* Don't abort list traversal, as this would leave ies.vars in an inconsistent state. */
+ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n");
+ } else {
+ AST_LIST_INSERT_TAIL(varlist, newvar, entries);
+ }
+ }
+ if (prev)
+ ast_free(prev);
+ ies.vars = NULL;
+ ast_channel_datastore_add(c, variablestore);
+ } else {
+ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n");
+ if (variablestore)
+ ast_channel_datastore_free(variablestore);
+ if (varlist)
+ ast_free(varlist);
+ }
+ }
+ }
+ }
+ break;
+ case IAX_COMMAND_INVAL:
+ iaxs[fr->callno]->error = ENOTCONN;
+ ast_debug(1, "Immediately destroying %d, having received INVAL\n", fr->callno);
+ iax2_destroy(fr->callno);
+ ast_debug(1, "Destroying call %d\n", fr->callno);
+ break;
+ case IAX_COMMAND_VNAK:
+ ast_debug(1, "Received VNAK: resending outstanding frames\n");
+ /* Force retransmission */
+ vnak_retransmit(fr->callno, fr->iseqno);
+ break;
+ case IAX_COMMAND_REGREQ:
+ case IAX_COMMAND_REGREL:
+ /* For security, always ack immediately */
+ if (delayreject)
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ if (register_verify(fr->callno, &sin, &ies)) {
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ /* Send delayed failure */
+ auth_fail(fr->callno, IAX_COMMAND_REGREJ);
+ break;
+ }
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ if ((ast_strlen_zero(iaxs[fr->callno]->secret) && ast_strlen_zero(iaxs[fr->callno]->inkeys)) ||
+ ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_AUTHENTICATED | IAX_STATE_UNCHANGED)) {
+ if (f.subclass == IAX_COMMAND_REGREL)
+ memset(&sin, 0, sizeof(sin));
+ if (update_registry(&sin, fr->callno, ies.devicetype, fd, ies.refresh))
+ ast_log(LOG_WARNING, "Registry error\n");
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ if (ies.provverpres && ies.serviceident && sin.sin_addr.s_addr) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ check_provisioning(&sin, fd, ies.serviceident, ies.provver);
+ ast_mutex_lock(&iaxsl[fr->callno]);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ }
+ break;
+ }
+ registry_authrequest(fr->callno);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ break;
+ case IAX_COMMAND_REGACK:
+ if (iax2_ack_registry(&ies, &sin, fr->callno))
+ ast_log(LOG_WARNING, "Registration failure\n");
+ /* Send ack immediately, before we destroy */
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ iax2_destroy(fr->callno);
+ break;
+ case IAX_COMMAND_REGREJ:
+ if (iaxs[fr->callno]->reg) {
+ if (authdebug) {
+ ast_log(LOG_NOTICE, "Registration of '%s' rejected: '%s' from: '%s'\n", iaxs[fr->callno]->reg->username, ies.cause ? ies.cause : "<unknown>", ast_inet_ntoa(sin.sin_addr));
+ manager_event(EVENT_FLAG_SYSTEM, "Registry", "ChannelType: IAX2\r\nUsername: %s\r\nStatus: Rejected\r\nCause: %s\r\n", iaxs[fr->callno]->reg->username, ies.cause ? ies.cause : "<unknown>");
+ }
+ iaxs[fr->callno]->reg->regstate = REG_STATE_REJECTED;
+ }
+ /* Send ack immediately, before we destroy */
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ iax2_destroy(fr->callno);
+ break;
+ case IAX_COMMAND_REGAUTH:
+ /* Authentication request */
+ if (registry_rerequest(&ies, fr->callno, &sin)) {
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_str(&ied0, IAX_IE_CAUSE, "No authority found");
+ iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_FACILITY_NOT_SUBSCRIBED);
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ }
+ break;
+ case IAX_COMMAND_TXREJ:
+ iaxs[fr->callno]->transferring = 0;
+ ast_verb(3, "Channel '%s' unable to transfer\n", iaxs[fr->callno]->owner ? iaxs[fr->callno]->owner->name : "<Unknown>");
+ memset(&iaxs[fr->callno]->transfer, 0, sizeof(iaxs[fr->callno]->transfer));
+ if (iaxs[fr->callno]->bridgecallno) {
+ if (iaxs[iaxs[fr->callno]->bridgecallno]->transferring) {
+ iaxs[iaxs[fr->callno]->bridgecallno]->transferring = 0;
+ send_command(iaxs[iaxs[fr->callno]->bridgecallno], AST_FRAME_IAX, IAX_COMMAND_TXREJ, 0, NULL, 0, -1);
+ }
+ }
+ break;
+ case IAX_COMMAND_TXREADY:
+ if ((iaxs[fr->callno]->transferring == TRANSFER_BEGIN) ||
+ (iaxs[fr->callno]->transferring == TRANSFER_MBEGIN)) {
+ if (iaxs[fr->callno]->transferring == TRANSFER_MBEGIN)
+ iaxs[fr->callno]->transferring = TRANSFER_MREADY;
+ else
+ iaxs[fr->callno]->transferring = TRANSFER_READY;
+ ast_verb(3, "Channel '%s' ready to transfer\n", iaxs[fr->callno]->owner ? iaxs[fr->callno]->owner->name : "<Unknown>");
+ if (iaxs[fr->callno]->bridgecallno) {
+ if ((iaxs[iaxs[fr->callno]->bridgecallno]->transferring == TRANSFER_READY) ||
+ (iaxs[iaxs[fr->callno]->bridgecallno]->transferring == TRANSFER_MREADY)) {
+ /* They're both ready, now release them. */
+ if (iaxs[fr->callno]->transferring == TRANSFER_MREADY) {
+ ast_verb(3, "Attempting media bridge of %s and %s\n", iaxs[fr->callno]->owner ? iaxs[fr->callno]->owner->name : "<Unknown>",
+ iaxs[iaxs[fr->callno]->bridgecallno]->owner ? iaxs[iaxs[fr->callno]->bridgecallno]->owner->name : "<Unknown>");
+
+ iaxs[iaxs[fr->callno]->bridgecallno]->transferring = TRANSFER_MEDIA;
+ iaxs[fr->callno]->transferring = TRANSFER_MEDIA;
+
+ memset(&ied0, 0, sizeof(ied0));
+ memset(&ied1, 0, sizeof(ied1));
+ iax_ie_append_short(&ied0, IAX_IE_CALLNO, iaxs[iaxs[fr->callno]->bridgecallno]->peercallno);
+ iax_ie_append_short(&ied1, IAX_IE_CALLNO, iaxs[fr->callno]->peercallno);
+ send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_TXMEDIA, 0, ied0.buf, ied0.pos, -1);
+ send_command(iaxs[iaxs[fr->callno]->bridgecallno], AST_FRAME_IAX, IAX_COMMAND_TXMEDIA, 0, ied1.buf, ied1.pos, -1);
+ } else {
+ ast_verb(3, "Releasing %s and %s\n", iaxs[fr->callno]->owner ? iaxs[fr->callno]->owner->name : "<Unknown>",
+ iaxs[iaxs[fr->callno]->bridgecallno]->owner ? iaxs[iaxs[fr->callno]->bridgecallno]->owner->name : "<Unknown>");
+
+ iaxs[iaxs[fr->callno]->bridgecallno]->transferring = TRANSFER_RELEASED;
+ iaxs[fr->callno]->transferring = TRANSFER_RELEASED;
+ ast_set_flag(iaxs[iaxs[fr->callno]->bridgecallno], IAX_ALREADYGONE);
+ ast_set_flag(iaxs[fr->callno], IAX_ALREADYGONE);
+
+ /* Stop doing lag & ping requests */
+ stop_stuff(fr->callno);
+ stop_stuff(iaxs[fr->callno]->bridgecallno);
+
+ memset(&ied0, 0, sizeof(ied0));
+ memset(&ied1, 0, sizeof(ied1));
+ iax_ie_append_short(&ied0, IAX_IE_CALLNO, iaxs[iaxs[fr->callno]->bridgecallno]->peercallno);
+ iax_ie_append_short(&ied1, IAX_IE_CALLNO, iaxs[fr->callno]->peercallno);
+ send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_TXREL, 0, ied0.buf, ied0.pos, -1);
+ send_command(iaxs[iaxs[fr->callno]->bridgecallno], AST_FRAME_IAX, IAX_COMMAND_TXREL, 0, ied1.buf, ied1.pos, -1);
+ }
+
+ }
+ }
+ }
+ break;
+ case IAX_COMMAND_TXREQ:
+ try_transfer(iaxs[fr->callno], &ies);
+ break;
+ case IAX_COMMAND_TXCNT:
+ if (iaxs[fr->callno]->transferring)
+ send_command_transfer(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_TXACC, 0, NULL, 0);
+ break;
+ case IAX_COMMAND_TXREL:
+ /* Send ack immediately, rather than waiting until we've changed addresses */
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ complete_transfer(fr->callno, &ies);
+ stop_stuff(fr->callno); /* for attended transfer to work with libiax */
+ break;
+ case IAX_COMMAND_TXMEDIA:
+ if (iaxs[fr->callno]->transferring == TRANSFER_READY) {
+ AST_LIST_LOCK(&frame_queue);
+ AST_LIST_TRAVERSE(&frame_queue, cur, list) {
+ /* Cancel any outstanding frames and start anew */
+ if ((fr->callno == cur->callno) && (cur->transfer))
+ cur->retries = -1;
+ }
+ AST_LIST_UNLOCK(&frame_queue);
+ /* Start sending our media to the transfer address, but otherwise leave the call as-is */
+ iaxs[fr->callno]->transferring = TRANSFER_MEDIAPASS;
+ }
+ break;
+ case IAX_COMMAND_DPREP:
+ complete_dpreply(iaxs[fr->callno], &ies);
+ break;
+ case IAX_COMMAND_UNSUPPORT:
+ ast_log(LOG_NOTICE, "Peer did not understand our iax command '%d'\n", ies.iax_unknown);
+ break;
+ case IAX_COMMAND_FWDOWNL:
+ /* Firmware download */
+ memset(&ied0, 0, sizeof(ied0));
+ res = iax_firmware_append(&ied0, (unsigned char *)ies.devicetype, ies.fwdesc);
+ if (res < 0)
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ else if (res > 0)
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_FWDATA, 0, ied0.buf, ied0.pos, -1);
+ else
+ send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_FWDATA, 0, ied0.buf, ied0.pos, -1);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ break;
+ default:
+ ast_debug(1, "Unknown IAX command %d on %d/%d\n", f.subclass, fr->callno, iaxs[fr->callno]->peercallno);
+ memset(&ied0, 0, sizeof(ied0));
+ iax_ie_append_byte(&ied0, IAX_IE_IAX_UNKNOWN, f.subclass);
+ send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_UNSUPPORT, 0, ied0.buf, ied0.pos, -1);
+ }
+ /* Free remote variables (if any) */
+ if (ies.vars)
+ ast_variables_destroy(ies.vars);
+
+ /* Don't actually pass these frames along */
+ if ((f.subclass != IAX_COMMAND_ACK) &&
+ (f.subclass != IAX_COMMAND_TXCNT) &&
+ (f.subclass != IAX_COMMAND_TXACC) &&
+ (f.subclass != IAX_COMMAND_INVAL) &&
+ (f.subclass != IAX_COMMAND_VNAK)) {
+ if (iaxs[fr->callno] && iaxs[fr->callno]->aseqno != iaxs[fr->callno]->iseqno)
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ }
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ /* Unless this is an ACK or INVAL frame, ack it */
+ if (iaxs[fr->callno] && iaxs[fr->callno]->aseqno != iaxs[fr->callno]->iseqno)
+ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno);
+ } else if (minivid) {
+ f.frametype = AST_FRAME_VIDEO;
+ if (iaxs[fr->callno]->videoformat > 0)
+ f.subclass = iaxs[fr->callno]->videoformat | (ntohs(vh->ts) & 0x8000 ? 1 : 0);
+ else {
+ ast_log(LOG_WARNING, "Received mini frame before first full video frame\n ");
+ iax2_vnak(fr->callno);
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ f.datalen = res - sizeof(*vh);
+ if (f.datalen)
+ f.data = thread->buf + sizeof(*vh);
+ else
+ f.data = NULL;
+#ifdef IAXTESTS
+ if (test_resync) {
+ fr->ts = (iaxs[fr->callno]->last & 0xFFFF8000L) | ((ntohs(vh->ts) + test_resync) & 0x7fff);
+ } else
+#endif /* IAXTESTS */
+ fr->ts = (iaxs[fr->callno]->last & 0xFFFF8000L) | (ntohs(vh->ts) & 0x7fff);
+ } else {
+ /* A mini frame */
+ f.frametype = AST_FRAME_VOICE;
+ if (iaxs[fr->callno]->voiceformat > 0)
+ f.subclass = iaxs[fr->callno]->voiceformat;
+ else {
+ ast_debug(1, "Received mini frame before first full voice frame\n");
+ iax2_vnak(fr->callno);
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ f.datalen = res - sizeof(struct ast_iax2_mini_hdr);
+ if (f.datalen < 0) {
+ ast_log(LOG_WARNING, "Datalen < 0?\n");
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ if (f.datalen)
+ f.data = thread->buf + sizeof(*mh);
+ else
+ f.data = NULL;
+#ifdef IAXTESTS
+ if (test_resync) {
+ fr->ts = (iaxs[fr->callno]->last & 0xFFFF0000L) | ((ntohs(mh->ts) + test_resync) & 0xffff);
+ } else
+#endif /* IAXTESTS */
+ fr->ts = (iaxs[fr->callno]->last & 0xFFFF0000L) | ntohs(mh->ts);
+ /* FIXME? Surely right here would be the right place to undo timestamp wraparound? */
+ }
+ /* Don't pass any packets until we're started */
+ if (!ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED)) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ /* Common things */
+ f.src = "IAX2";
+ f.mallocd = 0;
+ f.offset = 0;
+ f.len = 0;
+ if (f.datalen && (f.frametype == AST_FRAME_VOICE)) {
+ f.samples = ast_codec_get_samples(&f);
+ /* We need to byteswap incoming slinear samples from network byte order */
+ if (f.subclass == AST_FORMAT_SLINEAR)
+ ast_frame_byteswap_be(&f);
+ } else
+ f.samples = 0;
+ iax_frame_wrap(fr, &f);
+
+ /* If this is our most recent packet, use it as our basis for timestamping */
+ if (iaxs[fr->callno] && iaxs[fr->callno]->last < fr->ts) {
+ /*iaxs[fr->callno]->last = fr->ts; (do it afterwards cos schedule/forward_delivery needs the last ts too)*/
+ fr->outoforder = 0;
+ } else {
+ if (iaxdebug && iaxs[fr->callno])
+ ast_debug(1, "Received out of order packet... (type=%d, subclass %d, ts = %d, last = %d)\n", f.frametype, f.subclass, fr->ts, iaxs[fr->callno]->last);
+ fr->outoforder = -1;
+ }
+ duped_fr = iaxfrdup2(fr);
+ if (duped_fr) {
+ schedule_delivery(duped_fr, updatehistory, 0, &fr->ts);
+ }
+ if (iaxs[fr->callno] && iaxs[fr->callno]->last < fr->ts) {
+ iaxs[fr->callno]->last = fr->ts;
+#if 1
+ if (iaxdebug)
+ ast_debug(1, "For call=%d, set last=%d\n", fr->callno, fr->ts);
+#endif
+ }
+
+ /* Always run again */
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+}
+
+/* Function to clean up process thread if it is cancelled */
+static void iax2_process_thread_cleanup(void *data)
+{
+ struct iax2_thread *thread = data;
+ ast_mutex_destroy(&thread->lock);
+ ast_cond_destroy(&thread->cond);
+ ast_free(thread);
+ ast_atomic_dec_and_test(&iaxactivethreadcount);
+}
+
+static void *iax2_process_thread(void *data)
+{
+ struct iax2_thread *thread = data;
+ struct timeval tv;
+ struct timespec ts;
+ int put_into_idle = 0;
+
+ ast_atomic_fetchadd_int(&iaxactivethreadcount,1);
+ pthread_cleanup_push(iax2_process_thread_cleanup, data);
+ for(;;) {
+ /* Wait for something to signal us to be awake */
+ ast_mutex_lock(&thread->lock);
+
+ /* Flag that we're ready to accept signals */
+ thread->ready_for_signal = 1;
+
+ /* Put into idle list if applicable */
+ if (put_into_idle)
+ insert_idle_thread(thread);
+
+ if (thread->type == IAX_THREAD_TYPE_DYNAMIC) {
+ struct iax2_thread *t = NULL;
+ /* Wait to be signalled or time out */
+ tv = ast_tvadd(ast_tvnow(), ast_samp2tv(30000, 1000));
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+ if (ast_cond_timedwait(&thread->cond, &thread->lock, &ts) == ETIMEDOUT) {
+ /* This thread was never put back into the available dynamic
+ * thread list, so just go away. */
+ if (!put_into_idle) {
+ ast_mutex_unlock(&thread->lock);
+ break;
+ }
+ AST_LIST_LOCK(&dynamic_list);
+ /* Account for the case where this thread is acquired *right* after a timeout */
+ if ((t = AST_LIST_REMOVE(&dynamic_list, thread, list)))
+ ast_atomic_fetchadd_int(&iaxdynamicthreadcount, -1);
+ AST_LIST_UNLOCK(&dynamic_list);
+ if (t) {
+ /* This dynamic thread timed out waiting for a task and was
+ * not acquired immediately after the timeout,
+ * so it's time to go away. */
+ ast_mutex_unlock(&thread->lock);
+ break;
+ }
+ /* Someone grabbed our thread *right* after we timed out.
+ * Wait for them to set us up with something to do and signal
+ * us to continue. */
+ tv = ast_tvadd(ast_tvnow(), ast_samp2tv(30000, 1000));
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+ if (ast_cond_timedwait(&thread->cond, &thread->lock, &ts) == ETIMEDOUT)
+ {
+ ast_mutex_unlock(&thread->lock);
+ break;
+ }
+ }
+ } else {
+ ast_cond_wait(&thread->cond, &thread->lock);
+ }
+
+ /* Go back into our respective list */
+ put_into_idle = 1;
+
+ ast_mutex_unlock(&thread->lock);
+
+ if (thread->iostate == IAX_IOSTATE_IDLE)
+ continue;
+
+ /* Add ourselves to the active list now */
+ AST_LIST_LOCK(&active_list);
+ AST_LIST_INSERT_HEAD(&active_list, thread, list);
+ AST_LIST_UNLOCK(&active_list);
+
+ /* See what we need to do */
+ switch(thread->iostate) {
+ case IAX_IOSTATE_READY:
+ thread->actions++;
+ thread->iostate = IAX_IOSTATE_PROCESSING;
+ socket_process(thread);
+ handle_deferred_full_frames(thread);
+ break;
+ case IAX_IOSTATE_SCHEDREADY:
+ thread->actions++;
+ thread->iostate = IAX_IOSTATE_PROCESSING;
+#ifdef SCHED_MULTITHREADED
+ thread->schedfunc(thread->scheddata);
+#endif
+ default:
+ break;
+ }
+ time(&thread->checktime);
+ thread->iostate = IAX_IOSTATE_IDLE;
+#ifdef DEBUG_SCHED_MULTITHREAD
+ thread->curfunc[0]='\0';
+#endif
+
+ /* Now... remove ourselves from the active list, and return to the idle list */
+ AST_LIST_LOCK(&active_list);
+ AST_LIST_REMOVE(&active_list, thread, list);
+ AST_LIST_UNLOCK(&active_list);
+
+ /* Make sure another frame didn't sneak in there after we thought we were done. */
+ handle_deferred_full_frames(thread);
+ }
+
+ /*!\note For some reason, idle threads are exiting without being removed
+ * from an idle list, which is causing memory corruption. Forcibly remove
+ * it from the list, if it's there.
+ */
+ AST_LIST_LOCK(&idle_list);
+ AST_LIST_REMOVE(&idle_list, thread, list);
+ AST_LIST_UNLOCK(&idle_list);
+
+ AST_LIST_LOCK(&dynamic_list);
+ AST_LIST_REMOVE(&dynamic_list, thread, list);
+ AST_LIST_UNLOCK(&dynamic_list);
+
+ /* I am exiting here on my own volition, I need to clean up my own data structures
+ * Assume that I am no longer in any of the lists (idle, active, or dynamic)
+ */
+ pthread_cleanup_pop(1);
+ return NULL;
+}
+
+static int iax2_do_register(struct iax2_registry *reg)
+{
+ struct iax_ie_data ied;
+ if (iaxdebug)
+ ast_debug(1, "Sending registration request for '%s'\n", reg->username);
+
+ if (reg->dnsmgr &&
+ ((reg->regstate == REG_STATE_TIMEOUT) || !reg->addr.sin_addr.s_addr)) {
+ /* Maybe the IP has changed, force DNS refresh */
+ ast_dnsmgr_refresh(reg->dnsmgr);
+ }
+
+ /*
+ * if IP has Changed, free allocated call to create a new one with new IP
+ * call has the pointer to IP and must be updated to the new one
+ */
+ if (reg->dnsmgr && ast_dnsmgr_changed(reg->dnsmgr) && (reg->callno > 0)) {
+ ast_mutex_lock(&iaxsl[reg->callno]);
+ iax2_destroy(reg->callno);
+ ast_mutex_unlock(&iaxsl[reg->callno]);
+ reg->callno = 0;
+ }
+ if (!reg->addr.sin_addr.s_addr) {
+ if (iaxdebug)
+ ast_debug(1, "Unable to send registration request for '%s' without IP address\n", reg->username);
+ /* Setup the next registration attempt */
+ reg->expire = iax2_sched_replace(reg->expire, sched,
+ (5 * reg->refresh / 6) * 1000, iax2_do_register_s, reg);
+ return -1;
+ }
+
+ if (!reg->callno) {
+ ast_debug(1, "Allocate call number\n");
+ reg->callno = find_callno(0, 0, &reg->addr, NEW_FORCE, defaultsockfd);
+ if (reg->callno < 1) {
+ ast_log(LOG_WARNING, "Unable to create call for registration\n");
+ return -1;
+ } else
+ ast_debug(1, "Registration created on call %d\n", reg->callno);
+ iaxs[reg->callno]->reg = reg;
+ }
+ /* Setup the next registration a little early */
+ reg->expire = iax2_sched_replace(reg->expire, sched,
+ (5 * reg->refresh / 6) * 1000, iax2_do_register_s, reg);
+ /* Send the request */
+ memset(&ied, 0, sizeof(ied));
+ iax_ie_append_str(&ied, IAX_IE_USERNAME, reg->username);
+ iax_ie_append_short(&ied, IAX_IE_REFRESH, reg->refresh);
+ send_command(iaxs[reg->callno],AST_FRAME_IAX, IAX_COMMAND_REGREQ, 0, ied.buf, ied.pos, -1);
+ reg->regstate = REG_STATE_REGSENT;
+ return 0;
+}
+
+static int iax2_provision(struct sockaddr_in *end, int sockfd, char *dest, const char *template, int force)
+{
+ /* Returns 1 if provisioned, -1 if not able to find destination, or 0 if no provisioning
+ is found for template */
+ struct iax_ie_data provdata;
+ struct iax_ie_data ied;
+ unsigned int sig;
+ struct sockaddr_in sin;
+ int callno;
+ struct create_addr_info cai;
+
+ memset(&cai, 0, sizeof(cai));
+
+ ast_debug(1, "Provisioning '%s' from template '%s'\n", dest, template);
+
+ if (iax_provision_build(&provdata, &sig, template, force)) {
+ ast_debug(1, "No provisioning found for template '%s'\n", template);
+ return 0;
+ }
+
+ if (end) {
+ memcpy(&sin, end, sizeof(sin));
+ cai.sockfd = sockfd;
+ } else if (create_addr(dest, NULL, &sin, &cai))
+ return -1;
+
+ /* Build the rest of the message */
+ memset(&ied, 0, sizeof(ied));
+ iax_ie_append_raw(&ied, IAX_IE_PROVISIONING, provdata.buf, provdata.pos);
+
+ callno = find_callno(0, 0, &sin, NEW_FORCE, cai.sockfd);
+ if (!callno)
+ return -1;
+
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno]) {
+ /* Schedule autodestruct in case they don't ever give us anything back */
+ iaxs[callno]->autoid = iax2_sched_replace(iaxs[callno]->autoid,
+ sched, 15000, auto_hangup, (void *)(long)callno);
+ ast_set_flag(iaxs[callno], IAX_PROVISION);
+ /* Got a call number now, so go ahead and send the provisioning information */
+ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_PROVISION, 0, ied.buf, ied.pos, -1);
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+
+ return 1;
+}
+
+static char *papp = "IAX2Provision";
+static char *psyn = "Provision a calling IAXy with a given template";
+static char *pdescrip =
+" IAX2Provision([template]): Provisions the calling IAXy (assuming\n"
+"the calling entity is in fact an IAXy) with the given template or\n"
+"default if one is not specified. Returns -1 on error or 0 on success.\n";
+
+/*! iax2provision
+\ingroup applications
+*/
+static int iax2_prov_app(struct ast_channel *chan, void *data)
+{
+ int res;
+ char *sdata;
+ char *opts;
+ int force =0;
+ unsigned short callno = PTR_TO_CALLNO(chan->tech_pvt);
+ if (ast_strlen_zero(data))
+ data = "default";
+ sdata = ast_strdupa(data);
+ opts = strchr(sdata, '|');
+ if (opts)
+ *opts='\0';
+
+ if (chan->tech != &iax2_tech) {
+ ast_log(LOG_NOTICE, "Can't provision a non-IAX device!\n");
+ return -1;
+ }
+ if (!callno || !iaxs[callno] || !iaxs[callno]->addr.sin_addr.s_addr) {
+ ast_log(LOG_NOTICE, "Can't provision something with no IP?\n");
+ return -1;
+ }
+ res = iax2_provision(&iaxs[callno]->addr, iaxs[callno]->sockfd, NULL, sdata, force);
+ ast_verb(3, "Provisioned IAXY at '%s' with '%s'= %d\n",
+ ast_inet_ntoa(iaxs[callno]->addr.sin_addr),
+ sdata, res);
+ return res;
+}
+
+static char *handle_cli_iax2_provision(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int force = 0;
+ int res;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 provision";
+ e->usage =
+ "Usage: iax2 provision <host> <template> [forced]\n"
+ " Provisions the given peer or IP address using a template\n"
+ " matching either 'template' or '*' if the template is not\n"
+ " found. If 'forced' is specified, even empty provisioning\n"
+ " fields will be provisioned as empty fields.\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 3)
+ return iax_prov_complete_template(a->line, a->word, a->pos, a->n);
+ return NULL;
+ }
+
+ if (a->argc < 4)
+ return CLI_SHOWUSAGE;
+ if (a->argc > 4) {
+ if (!strcasecmp(a->argv[4], "forced"))
+ force = 1;
+ else
+ return CLI_SHOWUSAGE;
+ }
+ res = iax2_provision(NULL, -1, a->argv[2], a->argv[3], force);
+ if (res < 0)
+ ast_cli(a->fd, "Unable to find peer/address '%s'\n", a->argv[2]);
+ else if (res < 1)
+ ast_cli(a->fd, "No template (including wildcard) matching '%s'\n", a->argv[3]);
+ else
+ ast_cli(a->fd, "Provisioning '%s' with template '%s'%s\n", a->argv[2], a->argv[3], force ? ", forced" : "");
+ return CLI_SUCCESS;
+}
+
+static void __iax2_poke_noanswer(const void *data)
+{
+ struct iax2_peer *peer = (struct iax2_peer *)data;
+ if (peer->lastms > -1) {
+ ast_log(LOG_NOTICE, "Peer '%s' is now UNREACHABLE! Time: %d\n", peer->name, peer->lastms);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: IAX2\r\nPeer: IAX2/%s\r\nPeerStatus: Unreachable\r\nTime: %d\r\n", peer->name, peer->lastms);
+ ast_device_state_changed("IAX2/%s", peer->name); /* Activate notification */
+ }
+ if (peer->callno > 0) {
+ ast_mutex_lock(&iaxsl[peer->callno]);
+ iax2_destroy(peer->callno);
+ ast_mutex_unlock(&iaxsl[peer->callno]);
+ }
+ peer->callno = 0;
+ peer->lastms = -1;
+ /* Try again quickly */
+ peer->pokeexpire = iax2_sched_add(sched, peer->pokefreqnotok, iax2_poke_peer_s, peer_ref(peer));
+ if (peer->pokeexpire == -1)
+ peer_unref(peer);
+}
+
+static int iax2_poke_noanswer(const void *data)
+{
+ struct iax2_peer *peer = (struct iax2_peer *)data;
+ peer->pokeexpire = -1;
+#ifdef SCHED_MULTITHREADED
+ if (schedule_action(__iax2_poke_noanswer, data))
+#endif
+ __iax2_poke_noanswer(data);
+ peer_unref(peer);
+ return 0;
+}
+
+static int iax2_poke_peer_cb(void *obj, void *arg, int flags)
+{
+ struct iax2_peer *peer = obj;
+
+ iax2_poke_peer(peer, 0);
+
+ return 0;
+}
+
+static int iax2_poke_peer(struct iax2_peer *peer, int heldcall)
+{
+ if (!peer->maxms || (!peer->addr.sin_addr.s_addr && !peer->dnsmgr)) {
+ /* IF we have no IP without dnsmgr, or this isn't to be monitored, return
+ immediately after clearing things out */
+ peer->lastms = 0;
+ peer->historicms = 0;
+ peer->pokeexpire = -1;
+ peer->callno = 0;
+ return 0;
+ }
+ if (peer->callno > 0) {
+ ast_log(LOG_NOTICE, "Still have a callno...\n");
+ ast_mutex_lock(&iaxsl[peer->callno]);
+ iax2_destroy(peer->callno);
+ ast_mutex_unlock(&iaxsl[peer->callno]);
+ }
+ if (heldcall)
+ ast_mutex_unlock(&iaxsl[heldcall]);
+ peer->callno = find_callno(0, 0, &peer->addr, NEW_FORCE, peer->sockfd);
+ if (heldcall)
+ ast_mutex_lock(&iaxsl[heldcall]);
+ if (peer->callno < 1) {
+ ast_log(LOG_WARNING, "Unable to allocate call for poking peer '%s'\n", peer->name);
+ return -1;
+ }
+
+ /* Speed up retransmission times for this qualify call */
+ iaxs[peer->callno]->pingtime = peer->maxms / 4 + 1;
+ iaxs[peer->callno]->peerpoke = peer;
+
+ if (peer->pokeexpire > -1) {
+ if (!ast_sched_del(sched, peer->pokeexpire)) {
+ peer->pokeexpire = -1;
+ peer_unref(peer);
+ }
+ }
+
+ /* Queue up a new task to handle no reply */
+ /* If the host is already unreachable then use the unreachable interval instead */
+ if (peer->lastms < 0)
+ peer->pokeexpire = iax2_sched_add(sched, peer->pokefreqnotok, iax2_poke_noanswer, peer_ref(peer));
+ else
+ peer->pokeexpire = iax2_sched_add(sched, DEFAULT_MAXMS * 2, iax2_poke_noanswer, peer_ref(peer));
+
+ if (peer->pokeexpire == -1)
+ peer_unref(peer);
+
+ /* And send the poke */
+ send_command(iaxs[peer->callno], AST_FRAME_IAX, IAX_COMMAND_POKE, 0, NULL, 0, -1);
+
+ return 0;
+}
+
+static void free_context(struct iax2_context *con)
+{
+ struct iax2_context *conl;
+ while(con) {
+ conl = con;
+ con = con->next;
+ ast_free(conl);
+ }
+}
+
+static struct ast_channel *iax2_request(const char *type, int format, void *data, int *cause)
+{
+ int callno;
+ int res;
+ int fmt, native;
+ struct sockaddr_in sin;
+ struct ast_channel *c;
+ struct parsed_dial_string pds;
+ struct create_addr_info cai;
+ char *tmpstr;
+
+ memset(&pds, 0, sizeof(pds));
+ tmpstr = ast_strdupa(data);
+ parse_dial_string(tmpstr, &pds);
+
+ memset(&cai, 0, sizeof(cai));
+ cai.capability = iax2_capability;
+
+ ast_copy_flags(&cai, &globalflags, IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF);
+
+ if (!pds.peer) {
+ ast_log(LOG_WARNING, "No peer given\n");
+ return NULL;
+ }
+
+
+ /* Populate our address from the given */
+ if (create_addr(pds.peer, NULL, &sin, &cai)) {
+ *cause = AST_CAUSE_UNREGISTERED;
+ return NULL;
+ }
+
+ if (pds.port)
+ sin.sin_port = htons(atoi(pds.port));
+
+ callno = find_callno(0, 0, &sin, NEW_FORCE, cai.sockfd);
+ if (callno < 1) {
+ ast_log(LOG_WARNING, "Unable to create call\n");
+ *cause = AST_CAUSE_CONGESTION;
+ return NULL;
+ }
+
+ ast_mutex_lock(&iaxsl[callno]);
+
+ /* If this is a trunk, update it now */
+ ast_copy_flags(iaxs[callno], &cai, IAX_TRUNK | IAX_SENDANI | IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF);
+ if (ast_test_flag(&cai, IAX_TRUNK)) {
+ int new_callno;
+ if ((new_callno = make_trunk(callno, 1)) != -1)
+ callno = new_callno;
+ }
+ iaxs[callno]->maxtime = cai.maxtime;
+ if (cai.found)
+ ast_string_field_set(iaxs[callno], host, pds.peer);
+
+ c = ast_iax2_new(callno, AST_STATE_DOWN, cai.capability);
+
+ ast_mutex_unlock(&iaxsl[callno]);
+
+ if (c) {
+ /* Choose a format we can live with */
+ if (c->nativeformats & format)
+ c->nativeformats &= format;
+ else {
+ native = c->nativeformats;
+ fmt = format;
+ res = ast_translator_best_choice(&fmt, &native);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to create translator path for %s to %s on %s\n",
+ ast_getformatname(c->nativeformats), ast_getformatname(fmt), c->name);
+ ast_hangup(c);
+ return NULL;
+ }
+ c->nativeformats = native;
+ }
+ c->readformat = ast_best_codec(c->nativeformats);
+ c->writeformat = c->readformat;
+ }
+
+ return c;
+}
+
+static void *sched_thread(void *ignore)
+{
+ int count;
+ int res;
+ struct timeval tv;
+ struct timespec ts;
+
+ for (;;) {
+ res = ast_sched_wait(sched);
+ if ((res > 1000) || (res < 0))
+ res = 1000;
+ tv = ast_tvadd(ast_tvnow(), ast_samp2tv(res, 1000));
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+
+ pthread_testcancel();
+ ast_mutex_lock(&sched_lock);
+ ast_cond_timedwait(&sched_cond, &sched_lock, &ts);
+ ast_mutex_unlock(&sched_lock);
+ pthread_testcancel();
+
+ count = ast_sched_runq(sched);
+ if (count >= 20)
+ ast_debug(1, "chan_iax2: ast_sched_runq ran %d scheduled tasks all at once\n", count);
+ }
+
+ return NULL;
+}
+
+static void *network_thread(void *ignore)
+{
+ /* Our job is simple: Send queued messages, retrying if necessary. Read frames
+ from the network, and queue them for delivery to the channels */
+ int res, count, wakeup;
+ struct iax_frame *f;
+
+ if (timingfd > -1)
+ ast_io_add(io, timingfd, timing_read, AST_IO_IN | AST_IO_PRI, NULL);
+
+ for(;;) {
+ pthread_testcancel();
+
+ /* Go through the queue, sending messages which have not yet been
+ sent, and scheduling retransmissions if appropriate */
+ AST_LIST_LOCK(&frame_queue);
+ count = 0;
+ wakeup = -1;
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&frame_queue, f, list) {
+ if (f->sentyet)
+ continue;
+
+ /* Try to lock the pvt, if we can't... don't fret - defer it till later */
+ if (ast_mutex_trylock(&iaxsl[f->callno])) {
+ wakeup = 1;
+ continue;
+ }
+
+ f->sentyet = 1;
+
+ if (iaxs[f->callno]) {
+ send_packet(f);
+ count++;
+ }
+
+ ast_mutex_unlock(&iaxsl[f->callno]);
+
+ if (f->retries < 0) {
+ /* This is not supposed to be retransmitted */
+ AST_LIST_REMOVE_CURRENT(list);
+ /* Free the iax frame */
+ iax_frame_free(f);
+ } else {
+ /* We need reliable delivery. Schedule a retransmission */
+ f->retries++;
+ f->retrans = iax2_sched_add(sched, f->retrytime, attempt_transmit, f);
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&frame_queue);
+
+ pthread_testcancel();
+ if (count >= 20)
+ ast_debug(1, "chan_iax2: Sent %d queued outbound frames all at once\n", count);
+
+ /* Now do the IO, and run scheduled tasks */
+ res = ast_io_wait(io, wakeup);
+ if (res >= 0) {
+ if (res >= 20)
+ ast_debug(1, "chan_iax2: ast_io_wait ran %d I/Os all at once\n", res);
+ }
+ }
+ return NULL;
+}
+
+static int start_network_thread(void)
+{
+ struct iax2_thread *thread;
+ int threadcount = 0;
+ int x;
+ for (x = 0; x < iaxthreadcount; x++) {
+ thread = ast_calloc(1, sizeof(*thread));
+ if (thread) {
+ thread->type = IAX_THREAD_TYPE_POOL;
+ thread->threadnum = ++threadcount;
+ ast_mutex_init(&thread->lock);
+ ast_cond_init(&thread->cond, NULL);
+ if (ast_pthread_create_detached(&thread->threadid, NULL, iax2_process_thread, thread)) {
+ ast_log(LOG_WARNING, "Failed to create new thread!\n");
+ ast_free(thread);
+ thread = NULL;
+ }
+ AST_LIST_LOCK(&idle_list);
+ AST_LIST_INSERT_TAIL(&idle_list, thread, list);
+ AST_LIST_UNLOCK(&idle_list);
+ }
+ }
+ ast_pthread_create_background(&schedthreadid, NULL, sched_thread, NULL);
+ ast_pthread_create_background(&netthreadid, NULL, network_thread, NULL);
+ ast_verb(2, "%d helper threads started\n", threadcount);
+ return 0;
+}
+
+static struct iax2_context *build_context(const char *context)
+{
+ struct iax2_context *con;
+
+ if ((con = ast_calloc(1, sizeof(*con))))
+ ast_copy_string(con->context, context, sizeof(con->context));
+
+ return con;
+}
+
+static int get_auth_methods(const char *value)
+{
+ int methods = 0;
+ if (strstr(value, "rsa"))
+ methods |= IAX_AUTH_RSA;
+ if (strstr(value, "md5"))
+ methods |= IAX_AUTH_MD5;
+ if (strstr(value, "plaintext"))
+ methods |= IAX_AUTH_PLAINTEXT;
+ return methods;
+}
+
+
+/*! \brief Check if address can be used as packet source.
+ \return 0 address available, 1 address unavailable, -1 error
+*/
+static int check_srcaddr(struct sockaddr *sa, socklen_t salen)
+{
+ int sd;
+ int res;
+
+ sd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sd < 0) {
+ ast_log(LOG_ERROR, "Socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ res = bind(sd, sa, salen);
+ if (res < 0) {
+ ast_debug(1, "Can't bind: %s\n", strerror(errno));
+ close(sd);
+ return 1;
+ }
+
+ close(sd);
+ return 0;
+}
+
+/*! \brief Parse the "sourceaddress" value,
+ lookup in netsock list and set peer's sockfd. Defaults to defaultsockfd if
+ not found. */
+static int peer_set_srcaddr(struct iax2_peer *peer, const char *srcaddr)
+{
+ struct sockaddr_in sin;
+ int nonlocal = 1;
+ int port = IAX_DEFAULT_PORTNO;
+ int sockfd = defaultsockfd;
+ char *tmp;
+ char *addr;
+ char *portstr;
+
+ if (!(tmp = ast_strdupa(srcaddr)))
+ return -1;
+
+ addr = strsep(&tmp, ":");
+ portstr = tmp;
+
+ if (portstr) {
+ port = atoi(portstr);
+ if (port < 1)
+ port = IAX_DEFAULT_PORTNO;
+ }
+
+ if (!ast_get_ip(&sin, addr)) {
+ struct ast_netsock *sock;
+ int res;
+
+ sin.sin_port = 0;
+ sin.sin_family = AF_INET;
+ res = check_srcaddr((struct sockaddr *) &sin, sizeof(sin));
+ if (res == 0) {
+ /* ip address valid. */
+ sin.sin_port = htons(port);
+ if (!(sock = ast_netsock_find(netsock, &sin)))
+ sock = ast_netsock_find(outsock, &sin);
+ if (sock) {
+ sockfd = ast_netsock_sockfd(sock);
+ nonlocal = 0;
+ } else {
+ unsigned int orig_saddr = sin.sin_addr.s_addr;
+ /* INADDR_ANY matches anyway! */
+ sin.sin_addr.s_addr = INADDR_ANY;
+ if (ast_netsock_find(netsock, &sin)) {
+ sin.sin_addr.s_addr = orig_saddr;
+ sock = ast_netsock_bind(outsock, io, srcaddr, port, tos, cos, socket_read, NULL);
+ if (sock) {
+ sockfd = ast_netsock_sockfd(sock);
+ ast_netsock_unref(sock);
+ nonlocal = 0;
+ } else {
+ nonlocal = 2;
+ }
+ }
+ }
+ }
+ }
+
+ peer->sockfd = sockfd;
+
+ if (nonlocal == 1) {
+ ast_log(LOG_WARNING, "Non-local or unbound address specified (%s) in sourceaddress for '%s', reverting to default\n",
+ srcaddr, peer->name);
+ return -1;
+ } else if (nonlocal == 2) {
+ ast_log(LOG_WARNING, "Unable to bind to sourceaddress '%s' for '%s', reverting to default\n",
+ srcaddr, peer->name);
+ return -1;
+ } else {
+ ast_debug(1, "Using sourceaddress %s for '%s'\n", srcaddr, peer->name);
+ return 0;
+ }
+}
+
+static void peer_destructor(void *obj)
+{
+ struct iax2_peer *peer = obj;
+
+ ast_free_ha(peer->ha);
+
+ if (peer->callno > 0) {
+ ast_mutex_lock(&iaxsl[peer->callno]);
+ iax2_destroy(peer->callno);
+ ast_mutex_unlock(&iaxsl[peer->callno]);
+ }
+
+ register_peer_exten(peer, 0);
+
+ if (peer->dnsmgr)
+ ast_dnsmgr_release(peer->dnsmgr);
+
+ if (peer->mwi_event_sub)
+ ast_event_unsubscribe(peer->mwi_event_sub);
+
+ ast_string_field_free_memory(peer);
+}
+
+/*! \brief Create peer structure based on configuration */
+static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly)
+{
+ struct iax2_peer *peer = NULL;
+ struct ast_ha *oldha = NULL;
+ int maskfound = 0;
+ int found = 0;
+ int firstpass = 1;
+ struct iax2_peer tmp_peer = {
+ .name = name,
+ };
+
+ if (!temponly) {
+ peer = ao2_find(peers, &tmp_peer, OBJ_POINTER);
+ if (peer && !ast_test_flag(peer, IAX_DELME))
+ firstpass = 0;
+ }
+
+ if (peer) {
+ found++;
+ if (firstpass) {
+ oldha = peer->ha;
+ peer->ha = NULL;
+ }
+ unlink_peer(peer);
+ } else if ((peer = ao2_alloc(sizeof(*peer), peer_destructor))) {
+ peer->expire = -1;
+ peer->pokeexpire = -1;
+ peer->sockfd = defaultsockfd;
+ if (ast_string_field_init(peer, 32))
+ peer = peer_unref(peer);
+ }
+
+ if (peer) {
+ if (firstpass) {
+ ast_copy_flags(peer, &globalflags, IAX_USEJITTERBUF | IAX_FORCEJITTERBUF);
+ peer->encmethods = iax2_encryption;
+ peer->adsi = adsi;
+ ast_string_field_set(peer,secret,"");
+ if (!found) {
+ ast_string_field_set(peer, name, name);
+ peer->addr.sin_port = htons(IAX_DEFAULT_PORTNO);
+ peer->expiry = min_reg_expire;
+ }
+ peer->prefs = prefs;
+ peer->capability = iax2_capability;
+ peer->smoothing = 0;
+ peer->pokefreqok = DEFAULT_FREQ_OK;
+ peer->pokefreqnotok = DEFAULT_FREQ_NOTOK;
+ ast_string_field_set(peer,context,"");
+ ast_string_field_set(peer,peercontext,"");
+ ast_clear_flag(peer, IAX_HASCALLERID);
+ ast_string_field_set(peer, cid_name, "");
+ ast_string_field_set(peer, cid_num, "");
+ }
+
+ if (!v) {
+ v = alt;
+ alt = NULL;
+ }
+ while(v) {
+ if (!strcasecmp(v->name, "secret")) {
+ ast_string_field_set(peer, secret, v->value);
+ } else if (!strcasecmp(v->name, "mailbox")) {
+ ast_string_field_set(peer, mailbox, v->value);
+ } else if (!strcasecmp(v->name, "mohinterpret")) {
+ ast_string_field_set(peer, mohinterpret, v->value);
+ } else if (!strcasecmp(v->name, "mohsuggest")) {
+ ast_string_field_set(peer, mohsuggest, v->value);
+ } else if (!strcasecmp(v->name, "dbsecret")) {
+ ast_string_field_set(peer, dbsecret, v->value);
+ } else if (!strcasecmp(v->name, "trunk")) {
+ ast_set2_flag(peer, ast_true(v->value), IAX_TRUNK);
+ if (ast_test_flag(peer, IAX_TRUNK) && (timingfd < 0)) {
+ ast_log(LOG_WARNING, "Unable to support trunking on peer '%s' without zaptel timing\n", peer->name);
+ ast_clear_flag(peer, IAX_TRUNK);
+ }
+ } else if (!strcasecmp(v->name, "auth")) {
+ peer->authmethods = get_auth_methods(v->value);
+ } else if (!strcasecmp(v->name, "encryption")) {
+ peer->encmethods = get_encrypt_methods(v->value);
+ } else if (!strcasecmp(v->name, "transfer")) {
+ if (!strcasecmp(v->value, "mediaonly")) {
+ ast_set_flags_to(peer, IAX_NOTRANSFER|IAX_TRANSFERMEDIA, IAX_TRANSFERMEDIA);
+ } else if (ast_true(v->value)) {
+ ast_set_flags_to(peer, IAX_NOTRANSFER|IAX_TRANSFERMEDIA, 0);
+ } else
+ ast_set_flags_to(peer, IAX_NOTRANSFER|IAX_TRANSFERMEDIA, IAX_NOTRANSFER);
+ } else if (!strcasecmp(v->name, "jitterbuffer")) {
+ ast_set2_flag(peer, ast_true(v->value), IAX_USEJITTERBUF);
+ } else if (!strcasecmp(v->name, "forcejitterbuffer")) {
+ ast_set2_flag(peer, ast_true(v->value), IAX_FORCEJITTERBUF);
+ } else if (!strcasecmp(v->name, "host")) {
+ if (!strcasecmp(v->value, "dynamic")) {
+ /* They'll register with us */
+ ast_set_flag(peer, IAX_DYNAMIC);
+ if (!found) {
+ /* Initialize stuff iff we're not found, otherwise
+ we keep going with what we had */
+ memset(&peer->addr.sin_addr, 0, 4);
+ if (peer->addr.sin_port) {
+ /* If we've already got a port, make it the default rather than absolute */
+ peer->defaddr.sin_port = peer->addr.sin_port;
+ peer->addr.sin_port = 0;
+ }
+ }
+ } else {
+ /* Non-dynamic. Make sure we become that way if we're not */
+ if (peer->expire > -1)
+ ast_sched_del(sched, peer->expire);
+ peer->expire = -1;
+ ast_clear_flag(peer, IAX_DYNAMIC);
+ if (ast_dnsmgr_lookup(v->value, &peer->addr.sin_addr, &peer->dnsmgr))
+ return peer_unref(peer);
+ if (!peer->addr.sin_port)
+ peer->addr.sin_port = htons(IAX_DEFAULT_PORTNO);
+ }
+ if (!maskfound)
+ inet_aton("255.255.255.255", &peer->mask);
+ } else if (!strcasecmp(v->name, "defaultip")) {
+ if (ast_get_ip(&peer->defaddr, v->value))
+ return peer_unref(peer);
+ } else if (!strcasecmp(v->name, "sourceaddress")) {
+ peer_set_srcaddr(peer, v->value);
+ } else if (!strcasecmp(v->name, "permit") ||
+ !strcasecmp(v->name, "deny")) {
+ peer->ha = ast_append_ha(v->name, v->value, peer->ha, NULL);
+ } else if (!strcasecmp(v->name, "mask")) {
+ maskfound++;
+ inet_aton(v->value, &peer->mask);
+ } else if (!strcasecmp(v->name, "context")) {
+ ast_string_field_set(peer, context, v->value);
+ } else if (!strcasecmp(v->name, "regexten")) {
+ ast_string_field_set(peer, regexten, v->value);
+ } else if (!strcasecmp(v->name, "peercontext")) {
+ ast_string_field_set(peer, peercontext, v->value);
+ } else if (!strcasecmp(v->name, "port")) {
+ if (ast_test_flag(peer, IAX_DYNAMIC))
+ peer->defaddr.sin_port = htons(atoi(v->value));
+ else
+ peer->addr.sin_port = htons(atoi(v->value));
+ } else if (!strcasecmp(v->name, "username")) {
+ ast_string_field_set(peer, username, v->value);
+ } else if (!strcasecmp(v->name, "allow")) {
+ ast_parse_allow_disallow(&peer->prefs, &peer->capability, v->value, 1);
+ } else if (!strcasecmp(v->name, "disallow")) {
+ ast_parse_allow_disallow(&peer->prefs, &peer->capability, v->value, 0);
+ } else if (!strcasecmp(v->name, "callerid")) {
+ if (!ast_strlen_zero(v->value)) {
+ char name2[80];
+ char num2[80];
+ ast_callerid_split(v->value, name2, 80, num2, 80);
+ ast_string_field_set(peer, cid_name, name2);
+ ast_string_field_set(peer, cid_num, num2);
+ ast_set_flag(peer, IAX_HASCALLERID);
+ } else {
+ ast_clear_flag(peer, IAX_HASCALLERID);
+ ast_string_field_set(peer, cid_name, "");
+ ast_string_field_set(peer, cid_num, "");
+ }
+ } else if (!strcasecmp(v->name, "fullname")) {
+ if (!ast_strlen_zero(v->value)) {
+ ast_string_field_set(peer, cid_name, v->value);
+ ast_set_flag(peer, IAX_HASCALLERID);
+ } else {
+ ast_string_field_set(peer, cid_name, "");
+ if (ast_strlen_zero(peer->cid_num))
+ ast_clear_flag(peer, IAX_HASCALLERID);
+ }
+ } else if (!strcasecmp(v->name, "cid_number")) {
+ if (!ast_strlen_zero(v->value)) {
+ ast_string_field_set(peer, cid_num, v->value);
+ ast_set_flag(peer, IAX_HASCALLERID);
+ } else {
+ ast_string_field_set(peer, cid_num, "");
+ if (ast_strlen_zero(peer->cid_name))
+ ast_clear_flag(peer, IAX_HASCALLERID);
+ }
+ } else if (!strcasecmp(v->name, "sendani")) {
+ ast_set2_flag(peer, ast_true(v->value), IAX_SENDANI);
+ } else if (!strcasecmp(v->name, "inkeys")) {
+ ast_string_field_set(peer, inkeys, v->value);
+ } else if (!strcasecmp(v->name, "outkey")) {
+ ast_string_field_set(peer, outkey, v->value);
+ } else if (!strcasecmp(v->name, "qualify")) {
+ if (!strcasecmp(v->value, "no")) {
+ peer->maxms = 0;
+ } else if (!strcasecmp(v->value, "yes")) {
+ peer->maxms = DEFAULT_MAXMS;
+ } else if (sscanf(v->value, "%d", &peer->maxms) != 1) {
+ ast_log(LOG_WARNING, "Qualification of peer '%s' should be 'yes', 'no', or a number of milliseconds at line %d of iax.conf\n", peer->name, v->lineno);
+ peer->maxms = 0;
+ }
+ } else if (!strcasecmp(v->name, "qualifysmoothing")) {
+ peer->smoothing = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "qualifyfreqok")) {
+ if (sscanf(v->value, "%d", &peer->pokefreqok) != 1) {
+ ast_log(LOG_WARNING, "Qualification testing frequency of peer '%s' when OK should a number of milliseconds at line %d of iax.conf\n", peer->name, v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "qualifyfreqnotok")) {
+ if (sscanf(v->value, "%d", &peer->pokefreqnotok) != 1) {
+ ast_log(LOG_WARNING, "Qualification testing frequency of peer '%s' when NOT OK should be a number of milliseconds at line %d of iax.conf\n", peer->name, v->lineno);
+ } else ast_log(LOG_WARNING, "Set peer->pokefreqnotok to %d\n", peer->pokefreqnotok);
+ } else if (!strcasecmp(v->name, "timezone")) {
+ ast_string_field_set(peer, zonetag, v->value);
+ } else if (!strcasecmp(v->name, "adsi")) {
+ peer->adsi = ast_true(v->value);
+ }/* else if (strcasecmp(v->name,"type")) */
+ /* ast_log(LOG_WARNING, "Ignoring %s\n", v->name); */
+ v = v->next;
+ if (!v) {
+ v = alt;
+ alt = NULL;
+ }
+ }
+ if (!peer->authmethods)
+ peer->authmethods = IAX_AUTH_MD5 | IAX_AUTH_PLAINTEXT;
+ ast_clear_flag(peer, IAX_DELME);
+ /* Make sure these are IPv4 addresses */
+ peer->addr.sin_family = AF_INET;
+ }
+
+ if (oldha)
+ ast_free_ha(oldha);
+
+ if (!ast_strlen_zero(peer->mailbox)) {
+ char *mailbox, *context;
+ context = mailbox = ast_strdupa(peer->mailbox);
+ strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+ peer->mwi_event_sub = ast_event_subscribe(AST_EVENT_MWI, mwi_event_cb, NULL,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
+ AST_EVENT_IE_END);
+ }
+
+ return peer;
+}
+
+static void user_destructor(void *obj)
+{
+ struct iax2_user *user = obj;
+
+ ast_free_ha(user->ha);
+ free_context(user->contexts);
+ if(user->vars) {
+ ast_variables_destroy(user->vars);
+ user->vars = NULL;
+ }
+ ast_string_field_free_memory(user);
+}
+
+/*! \brief Create in-memory user structure from configuration */
+static struct iax2_user *build_user(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly)
+{
+ struct iax2_user *user = NULL;
+ struct iax2_context *con, *conl = NULL;
+ struct ast_ha *oldha = NULL;
+ struct iax2_context *oldcon = NULL;
+ int format;
+ int firstpass=1;
+ int oldcurauthreq = 0;
+ char *varname = NULL, *varval = NULL;
+ struct ast_variable *tmpvar = NULL;
+ struct iax2_user tmp_user = {
+ .name = name,
+ };
+
+ if (!temponly) {
+ user = ao2_find(users, &tmp_user, OBJ_POINTER);
+ if (user && !ast_test_flag(user, IAX_DELME))
+ firstpass = 0;
+ }
+
+ if (user) {
+ if (firstpass) {
+ oldcurauthreq = user->curauthreq;
+ oldha = user->ha;
+ oldcon = user->contexts;
+ user->ha = NULL;
+ user->contexts = NULL;
+ }
+ /* Already in the list, remove it and it will be added back (or FREE'd) */
+ ao2_unlink(users, user);
+ } else {
+ user = ao2_alloc(sizeof(*user), user_destructor);
+ }
+
+ if (user) {
+ if (firstpass) {
+ ast_string_field_free_memory(user);
+ memset(user, 0, sizeof(struct iax2_user));
+ if (ast_string_field_init(user, 32)) {
+ user = user_unref(user);
+ goto cleanup;
+ }
+ user->maxauthreq = maxauthreq;
+ user->curauthreq = oldcurauthreq;
+ user->prefs = prefs;
+ user->capability = iax2_capability;
+ user->encmethods = iax2_encryption;
+ user->adsi = adsi;
+ ast_string_field_set(user, name, name);
+ ast_string_field_set(user, language, language);
+ ast_copy_flags(user, &globalflags, IAX_USEJITTERBUF | IAX_FORCEJITTERBUF | IAX_CODEC_USER_FIRST | IAX_CODEC_NOPREFS | IAX_CODEC_NOCAP);
+ ast_clear_flag(user, IAX_HASCALLERID);
+ ast_string_field_set(user, cid_name, "");
+ ast_string_field_set(user, cid_num, "");
+ }
+ if (!v) {
+ v = alt;
+ alt = NULL;
+ }
+ while(v) {
+ if (!strcasecmp(v->name, "context")) {
+ con = build_context(v->value);
+ if (con) {
+ if (conl)
+ conl->next = con;
+ else
+ user->contexts = con;
+ conl = con;
+ }
+ } else if (!strcasecmp(v->name, "permit") ||
+ !strcasecmp(v->name, "deny")) {
+ user->ha = ast_append_ha(v->name, v->value, user->ha, NULL);
+ } else if (!strcasecmp(v->name, "setvar")) {
+ varname = ast_strdupa(v->value);
+ if (varname && (varval = strchr(varname,'='))) {
+ *varval = '\0';
+ varval++;
+ if((tmpvar = ast_variable_new(varname, varval, ""))) {
+ tmpvar->next = user->vars;
+ user->vars = tmpvar;
+ }
+ }
+ } else if (!strcasecmp(v->name, "allow")) {
+ ast_parse_allow_disallow(&user->prefs, &user->capability, v->value, 1);
+ } else if (!strcasecmp(v->name, "disallow")) {
+ ast_parse_allow_disallow(&user->prefs, &user->capability,v->value, 0);
+ } else if (!strcasecmp(v->name, "trunk")) {
+ ast_set2_flag(user, ast_true(v->value), IAX_TRUNK);
+ if (ast_test_flag(user, IAX_TRUNK) && (timingfd < 0)) {
+ ast_log(LOG_WARNING, "Unable to support trunking on user '%s' without zaptel timing\n", user->name);
+ ast_clear_flag(user, IAX_TRUNK);
+ }
+ } else if (!strcasecmp(v->name, "auth")) {
+ user->authmethods = get_auth_methods(v->value);
+ } else if (!strcasecmp(v->name, "encryption")) {
+ user->encmethods = get_encrypt_methods(v->value);
+ } else if (!strcasecmp(v->name, "transfer")) {
+ if (!strcasecmp(v->value, "mediaonly")) {
+ ast_set_flags_to(user, IAX_NOTRANSFER|IAX_TRANSFERMEDIA, IAX_TRANSFERMEDIA);
+ } else if (ast_true(v->value)) {
+ ast_set_flags_to(user, IAX_NOTRANSFER|IAX_TRANSFERMEDIA, 0);
+ } else
+ ast_set_flags_to(user, IAX_NOTRANSFER|IAX_TRANSFERMEDIA, IAX_NOTRANSFER);
+ } else if (!strcasecmp(v->name, "codecpriority")) {
+ if(!strcasecmp(v->value, "caller"))
+ ast_set_flag(user, IAX_CODEC_USER_FIRST);
+ else if(!strcasecmp(v->value, "disabled"))
+ ast_set_flag(user, IAX_CODEC_NOPREFS);
+ else if(!strcasecmp(v->value, "reqonly")) {
+ ast_set_flag(user, IAX_CODEC_NOCAP);
+ ast_set_flag(user, IAX_CODEC_NOPREFS);
+ }
+ } else if (!strcasecmp(v->name, "jitterbuffer")) {
+ ast_set2_flag(user, ast_true(v->value), IAX_USEJITTERBUF);
+ } else if (!strcasecmp(v->name, "forcejitterbuffer")) {
+ ast_set2_flag(user, ast_true(v->value), IAX_FORCEJITTERBUF);
+ } else if (!strcasecmp(v->name, "dbsecret")) {
+ ast_string_field_set(user, dbsecret, v->value);
+ } else if (!strcasecmp(v->name, "secret")) {
+ if (!ast_strlen_zero(user->secret)) {
+ char *old = ast_strdupa(user->secret);
+
+ ast_string_field_build(user, secret, "%s;%s", old, v->value);
+ } else
+ ast_string_field_set(user, secret, v->value);
+ } else if (!strcasecmp(v->name, "callerid")) {
+ if (!ast_strlen_zero(v->value) && strcasecmp(v->value, "asreceived")) {
+ char name2[80];
+ char num2[80];
+ ast_callerid_split(v->value, name2, sizeof(name2), num2, sizeof(num2));
+ ast_string_field_set(user, cid_name, name2);
+ ast_string_field_set(user, cid_num, num2);
+ ast_set_flag(user, IAX_HASCALLERID);
+ } else {
+ ast_clear_flag(user, IAX_HASCALLERID);
+ ast_string_field_set(user, cid_name, "");
+ ast_string_field_set(user, cid_num, "");
+ }
+ } else if (!strcasecmp(v->name, "fullname")) {
+ if (!ast_strlen_zero(v->value)) {
+ ast_string_field_set(user, cid_name, v->value);
+ ast_set_flag(user, IAX_HASCALLERID);
+ } else {
+ ast_string_field_set(user, cid_name, "");
+ if (ast_strlen_zero(user->cid_num))
+ ast_clear_flag(user, IAX_HASCALLERID);
+ }
+ } else if (!strcasecmp(v->name, "cid_number")) {
+ if (!ast_strlen_zero(v->value)) {
+ ast_string_field_set(user, cid_num, v->value);
+ ast_set_flag(user, IAX_HASCALLERID);
+ } else {
+ ast_string_field_set(user, cid_num, "");
+ if (ast_strlen_zero(user->cid_name))
+ ast_clear_flag(user, IAX_HASCALLERID);
+ }
+ } else if (!strcasecmp(v->name, "accountcode")) {
+ ast_string_field_set(user, accountcode, v->value);
+ } else if (!strcasecmp(v->name, "mohinterpret")) {
+ ast_string_field_set(user, mohinterpret, v->value);
+ } else if (!strcasecmp(v->name, "mohsuggest")) {
+ ast_string_field_set(user, mohsuggest, v->value);
+ } else if (!strcasecmp(v->name, "language")) {
+ ast_string_field_set(user, language, v->value);
+ } else if (!strcasecmp(v->name, "amaflags")) {
+ format = ast_cdr_amaflags2int(v->value);
+ if (format < 0) {
+ ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
+ } else {
+ user->amaflags = format;
+ }
+ } else if (!strcasecmp(v->name, "inkeys")) {
+ ast_string_field_set(user, inkeys, v->value);
+ } else if (!strcasecmp(v->name, "maxauthreq")) {
+ user->maxauthreq = atoi(v->value);
+ if (user->maxauthreq < 0)
+ user->maxauthreq = 0;
+ } else if (!strcasecmp(v->name, "adsi")) {
+ user->adsi = ast_true(v->value);
+ }/* else if (strcasecmp(v->name,"type")) */
+ /* ast_log(LOG_WARNING, "Ignoring %s\n", v->name); */
+ v = v->next;
+ if (!v) {
+ v = alt;
+ alt = NULL;
+ }
+ }
+ if (!user->authmethods) {
+ if (!ast_strlen_zero(user->secret)) {
+ user->authmethods = IAX_AUTH_MD5 | IAX_AUTH_PLAINTEXT;
+ if (!ast_strlen_zero(user->inkeys))
+ user->authmethods |= IAX_AUTH_RSA;
+ } else if (!ast_strlen_zero(user->inkeys)) {
+ user->authmethods = IAX_AUTH_RSA;
+ } else {
+ user->authmethods = IAX_AUTH_MD5 | IAX_AUTH_PLAINTEXT;
+ }
+ }
+ ast_clear_flag(user, IAX_DELME);
+ }
+cleanup:
+ if (oldha)
+ ast_free_ha(oldha);
+ if (oldcon)
+ free_context(oldcon);
+ return user;
+}
+
+static int peer_delme_cb(void *obj, void *arg, int flags)
+{
+ struct iax2_peer *peer = obj;
+
+ ast_set_flag(peer, IAX_DELME);
+
+ return 0;
+}
+
+static int user_delme_cb(void *obj, void *arg, int flags)
+{
+ struct iax2_user *user = obj;
+
+ ast_set_flag(user, IAX_DELME);
+
+ return 0;
+}
+
+static void delete_users(void)
+{
+ struct iax2_registry *reg;
+
+ ao2_callback(users, 0, user_delme_cb, NULL);
+
+ AST_LIST_LOCK(&registrations);
+ while ((reg = AST_LIST_REMOVE_HEAD(&registrations, entry))) {
+ if (reg->expire > -1)
+ ast_sched_del(sched, reg->expire);
+ if (reg->callno) {
+ ast_mutex_lock(&iaxsl[reg->callno]);
+ if (iaxs[reg->callno]) {
+ iaxs[reg->callno]->reg = NULL;
+ iax2_destroy(reg->callno);
+ }
+ ast_mutex_unlock(&iaxsl[reg->callno]);
+ }
+ if (reg->dnsmgr)
+ ast_dnsmgr_release(reg->dnsmgr);
+ ast_free(reg);
+ }
+ AST_LIST_UNLOCK(&registrations);
+
+ ao2_callback(peers, 0, peer_delme_cb, NULL);
+}
+
+static void prune_users(void)
+{
+ struct iax2_user *user;
+ struct ao2_iterator i;
+
+ i = ao2_iterator_init(users, 0);
+ while ((user = ao2_iterator_next(&i))) {
+ if (ast_test_flag(user, IAX_DELME))
+ ao2_unlink(users, user);
+ user_unref(user);
+ }
+}
+
+/* Prune peers who still are supposed to be deleted */
+static void prune_peers(void)
+{
+ struct iax2_peer *peer;
+ struct ao2_iterator i;
+
+ i = ao2_iterator_init(peers, 0);
+ while ((peer = ao2_iterator_next(&i))) {
+ if (ast_test_flag(peer, IAX_DELME))
+ unlink_peer(peer);
+ peer_unref(peer);
+ }
+}
+
+static void set_timing(void)
+{
+#ifdef HAVE_ZAPTEL
+ int bs = trunkfreq * 8;
+ if (timingfd > -1) {
+ if (
+#ifdef ZT_TIMERACK
+ ioctl(timingfd, ZT_TIMERCONFIG, &bs) &&
+#endif
+ ioctl(timingfd, ZT_SET_BLOCKSIZE, &bs))
+ ast_log(LOG_WARNING, "Unable to set blocksize on timing source\n");
+ }
+#endif
+}
+
+static void set_config_destroy(void)
+{
+ strcpy(accountcode, "");
+ strcpy(language, "");
+ strcpy(mohinterpret, "default");
+ strcpy(mohsuggest, "");
+ global_max_trunk_mtu = MAX_TRUNK_MTU;
+ trunkmaxsize = MAX_TRUNKDATA;
+ amaflags = 0;
+ delayreject = 0;
+ ast_clear_flag((&globalflags), IAX_NOTRANSFER);
+ ast_clear_flag((&globalflags), IAX_TRANSFERMEDIA);
+ ast_clear_flag((&globalflags), IAX_USEJITTERBUF);
+ ast_clear_flag((&globalflags), IAX_FORCEJITTERBUF);
+ delete_users();
+}
+
+/*! \brief Load configuration */
+static int set_config(char *config_file, int reload)
+{
+ struct ast_config *cfg, *ucfg;
+ int capability=iax2_capability;
+ struct ast_variable *v;
+ char *cat;
+ const char *utype;
+ const char *tosval;
+ int format;
+ int portno = IAX_DEFAULT_PORTNO;
+ int x;
+ int mtuv;
+ struct iax2_user *user;
+ struct iax2_peer *peer;
+ struct ast_netsock *ns;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+#if 0
+ static unsigned short int last_port=0;
+#endif
+
+ cfg = ast_config_load(config_file, config_flags);
+
+ if (!cfg) {
+ ast_log(LOG_ERROR, "Unable to load config %s\n", config_file);
+ return -1;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
+ ucfg = ast_config_load("users.conf", config_flags);
+ if (ucfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+ /* Otherwise we need to reread both files */
+ ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
+ cfg = ast_config_load(config_file, config_flags);
+ } else { /* iax.conf changed, gotta reread users.conf, too */
+ ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
+ ucfg = ast_config_load("users.conf", config_flags);
+ }
+
+ if (reload) {
+ set_config_destroy();
+ }
+
+ /* Reset global codec prefs */
+ memset(&prefs, 0 , sizeof(struct ast_codec_pref));
+
+ /* Reset Global Flags */
+ memset(&globalflags, 0, sizeof(globalflags));
+ ast_set_flag(&globalflags, IAX_RTUPDATE);
+
+#ifdef SO_NO_CHECK
+ nochecksums = 0;
+#endif
+
+ min_reg_expire = IAX_DEFAULT_REG_EXPIRE;
+ max_reg_expire = IAX_DEFAULT_REG_EXPIRE;
+
+ maxauthreq = 3;
+
+ srvlookup = 0;
+
+ v = ast_variable_browse(cfg, "general");
+
+ /* Seed initial tos value */
+ tosval = ast_variable_retrieve(cfg, "general", "tos");
+ if (tosval) {
+ if (ast_str2tos(tosval, &tos))
+ ast_log(LOG_WARNING, "Invalid tos value, refer to QoS documentation\n");
+ }
+ /* Seed initial cos value */
+ tosval = ast_variable_retrieve(cfg, "general", "cos");
+ if (tosval) {
+ if (ast_str2cos(tosval, &cos))
+ ast_log(LOG_WARNING, "Invalid cos value, refer to QoS documentation\n");
+ }
+ while(v) {
+ if (!strcasecmp(v->name, "bindport")){
+ if (reload)
+ ast_log(LOG_NOTICE, "Ignoring bindport on reload\n");
+ else
+ portno = atoi(v->value);
+ } else if (!strcasecmp(v->name, "pingtime"))
+ ping_time = atoi(v->value);
+ else if (!strcasecmp(v->name, "iaxthreadcount")) {
+ if (reload) {
+ if (atoi(v->value) != iaxthreadcount)
+ ast_log(LOG_NOTICE, "Ignoring any changes to iaxthreadcount during reload\n");
+ } else {
+ iaxthreadcount = atoi(v->value);
+ if (iaxthreadcount < 1) {
+ ast_log(LOG_NOTICE, "iaxthreadcount must be at least 1.\n");
+ iaxthreadcount = 1;
+ } else if (iaxthreadcount > 256) {
+ ast_log(LOG_NOTICE, "limiting iaxthreadcount to 256\n");
+ iaxthreadcount = 256;
+ }
+ }
+ } else if (!strcasecmp(v->name, "iaxmaxthreadcount")) {
+ if (reload) {
+ AST_LIST_LOCK(&dynamic_list);
+ iaxmaxthreadcount = atoi(v->value);
+ AST_LIST_UNLOCK(&dynamic_list);
+ } else {
+ iaxmaxthreadcount = atoi(v->value);
+ if (iaxmaxthreadcount < 0) {
+ ast_log(LOG_NOTICE, "iaxmaxthreadcount must be at least 0.\n");
+ iaxmaxthreadcount = 0;
+ } else if (iaxmaxthreadcount > 256) {
+ ast_log(LOG_NOTICE, "Limiting iaxmaxthreadcount to 256\n");
+ iaxmaxthreadcount = 256;
+ }
+ }
+ } else if (!strcasecmp(v->name, "nochecksums")) {
+#ifdef SO_NO_CHECK
+ if (ast_true(v->value))
+ nochecksums = 1;
+ else
+ nochecksums = 0;
+#else
+ if (ast_true(v->value))
+ ast_log(LOG_WARNING, "Disabling RTP checksums is not supported on this operating system!\n");
+#endif
+ }
+ else if (!strcasecmp(v->name, "maxjitterbuffer"))
+ maxjitterbuffer = atoi(v->value);
+ else if (!strcasecmp(v->name, "resyncthreshold"))
+ resyncthreshold = atoi(v->value);
+ else if (!strcasecmp(v->name, "maxjitterinterps"))
+ maxjitterinterps = atoi(v->value);
+ else if (!strcasecmp(v->name, "jittertargetextra"))
+ jittertargetextra = atoi(v->value);
+ else if (!strcasecmp(v->name, "lagrqtime"))
+ lagrq_time = atoi(v->value);
+ else if (!strcasecmp(v->name, "maxregexpire"))
+ max_reg_expire = atoi(v->value);
+ else if (!strcasecmp(v->name, "minregexpire"))
+ min_reg_expire = atoi(v->value);
+ else if (!strcasecmp(v->name, "bindaddr")) {
+ if (reload) {
+ ast_log(LOG_NOTICE, "Ignoring bindaddr on reload\n");
+ } else {
+ if (!(ns = ast_netsock_bind(netsock, io, v->value, portno, tos, cos, socket_read, NULL))) {
+ ast_log(LOG_WARNING, "Unable apply binding to '%s' at line %d\n", v->value, v->lineno);
+ } else {
+ if (strchr(v->value, ':'))
+ ast_verb(2, "Binding IAX2 to '%s'\n", v->value);
+ else
+ ast_verb(2, "Binding IAX2 to '%s:%d'\n", v->value, portno);
+ if (defaultsockfd < 0)
+ defaultsockfd = ast_netsock_sockfd(ns);
+ ast_netsock_unref(ns);
+ }
+ }
+ } else if (!strcasecmp(v->name, "authdebug"))
+ authdebug = ast_true(v->value);
+ else if (!strcasecmp(v->name, "encryption"))
+ iax2_encryption = get_encrypt_methods(v->value);
+ else if (!strcasecmp(v->name, "transfer")) {
+ if (!strcasecmp(v->value, "mediaonly")) {
+ ast_set_flags_to((&globalflags), IAX_NOTRANSFER|IAX_TRANSFERMEDIA, IAX_TRANSFERMEDIA);
+ } else if (ast_true(v->value)) {
+ ast_set_flags_to((&globalflags), IAX_NOTRANSFER|IAX_TRANSFERMEDIA, 0);
+ } else
+ ast_set_flags_to((&globalflags), IAX_NOTRANSFER|IAX_TRANSFERMEDIA, IAX_NOTRANSFER);
+ } else if (!strcasecmp(v->name, "codecpriority")) {
+ if(!strcasecmp(v->value, "caller"))
+ ast_set_flag((&globalflags), IAX_CODEC_USER_FIRST);
+ else if(!strcasecmp(v->value, "disabled"))
+ ast_set_flag((&globalflags), IAX_CODEC_NOPREFS);
+ else if(!strcasecmp(v->value, "reqonly")) {
+ ast_set_flag((&globalflags), IAX_CODEC_NOCAP);
+ ast_set_flag((&globalflags), IAX_CODEC_NOPREFS);
+ }
+ } else if (!strcasecmp(v->name, "jitterbuffer"))
+ ast_set2_flag((&globalflags), ast_true(v->value), IAX_USEJITTERBUF);
+ else if (!strcasecmp(v->name, "forcejitterbuffer"))
+ ast_set2_flag((&globalflags), ast_true(v->value), IAX_FORCEJITTERBUF);
+ else if (!strcasecmp(v->name, "delayreject"))
+ delayreject = ast_true(v->value);
+ else if (!strcasecmp(v->name, "rtcachefriends"))
+ ast_set2_flag((&globalflags), ast_true(v->value), IAX_RTCACHEFRIENDS);
+ else if (!strcasecmp(v->name, "rtignoreregexpire"))
+ ast_set2_flag((&globalflags), ast_true(v->value), IAX_RTIGNOREREGEXPIRE);
+ else if (!strcasecmp(v->name, "rtupdate"))
+ ast_set2_flag((&globalflags), ast_true(v->value), IAX_RTUPDATE);
+ else if (!strcasecmp(v->name, "trunktimestamps"))
+ ast_set2_flag(&globalflags, ast_true(v->value), IAX_TRUNKTIMESTAMPS);
+ else if (!strcasecmp(v->name, "rtautoclear")) {
+ int i = atoi(v->value);
+ if(i > 0)
+ global_rtautoclear = i;
+ else
+ i = 0;
+ ast_set2_flag((&globalflags), i || ast_true(v->value), IAX_RTAUTOCLEAR);
+ } else if (!strcasecmp(v->name, "trunkfreq")) {
+ trunkfreq = atoi(v->value);
+ if (trunkfreq < 10)
+ trunkfreq = 10;
+ } else if (!strcasecmp(v->name, "trunkmtu")) {
+ mtuv = atoi(v->value);
+ if (mtuv == 0 )
+ global_max_trunk_mtu = 0;
+ else if (mtuv >= 172 && mtuv < 4000)
+ global_max_trunk_mtu = mtuv;
+ else
+ ast_log(LOG_NOTICE, "trunkmtu value out of bounds (%d) at line %d\n",
+ mtuv, v->lineno);
+ } else if (!strcasecmp(v->name, "trunkmaxsize")) {
+ trunkmaxsize = atoi(v->value);
+ if (trunkmaxsize == 0)
+ trunkmaxsize = MAX_TRUNKDATA;
+ } else if (!strcasecmp(v->name, "autokill")) {
+ if (sscanf(v->value, "%d", &x) == 1) {
+ if (x >= 0)
+ autokill = x;
+ else
+ ast_log(LOG_NOTICE, "Nice try, but autokill has to be >0 or 'yes' or 'no' at line %d\n", v->lineno);
+ } else if (ast_true(v->value)) {
+ autokill = DEFAULT_MAXMS;
+ } else {
+ autokill = 0;
+ }
+ } else if (!strcasecmp(v->name, "bandwidth")) {
+ if (!strcasecmp(v->value, "low")) {
+ capability = IAX_CAPABILITY_LOWBANDWIDTH;
+ } else if (!strcasecmp(v->value, "medium")) {
+ capability = IAX_CAPABILITY_MEDBANDWIDTH;
+ } else if (!strcasecmp(v->value, "high")) {
+ capability = IAX_CAPABILITY_FULLBANDWIDTH;
+ } else
+ ast_log(LOG_WARNING, "bandwidth must be either low, medium, or high\n");
+ } else if (!strcasecmp(v->name, "allow")) {
+ ast_parse_allow_disallow(&prefs, &capability, v->value, 1);
+ } else if (!strcasecmp(v->name, "disallow")) {
+ ast_parse_allow_disallow(&prefs, &capability, v->value, 0);
+ } else if (!strcasecmp(v->name, "register")) {
+ iax2_register(v->value, v->lineno);
+ } else if (!strcasecmp(v->name, "iaxcompat")) {
+ iaxcompat = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "regcontext")) {
+ ast_copy_string(regcontext, v->value, sizeof(regcontext));
+ /* Create context if it doesn't exist already */
+ if (!ast_context_find(regcontext))
+ ast_context_create(NULL, regcontext, "IAX2");
+ } else if (!strcasecmp(v->name, "tos")) {
+ if (ast_str2tos(v->value, &tos))
+ ast_log(LOG_WARNING, "Invalid tos value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos")) {
+ if (ast_str2cos(v->value, &cos))
+ ast_log(LOG_WARNING, "Invalid cos value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "accountcode")) {
+ ast_copy_string(accountcode, v->value, sizeof(accountcode));
+ } else if (!strcasecmp(v->name, "mohinterpret")) {
+ ast_copy_string(mohinterpret, v->value, sizeof(user->mohinterpret));
+ } else if (!strcasecmp(v->name, "mohsuggest")) {
+ ast_copy_string(mohsuggest, v->value, sizeof(user->mohsuggest));
+ } else if (!strcasecmp(v->name, "amaflags")) {
+ format = ast_cdr_amaflags2int(v->value);
+ if (format < 0) {
+ ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
+ } else {
+ amaflags = format;
+ }
+ } else if (!strcasecmp(v->name, "language")) {
+ ast_copy_string(language, v->value, sizeof(language));
+ } else if (!strcasecmp(v->name, "maxauthreq")) {
+ maxauthreq = atoi(v->value);
+ if (maxauthreq < 0)
+ maxauthreq = 0;
+ } else if (!strcasecmp(v->name, "adsi")) {
+ adsi = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "srvlookup")) {
+ srvlookup = ast_true(v->value);
+ } /*else if (strcasecmp(v->name,"type")) */
+ /* ast_log(LOG_WARNING, "Ignoring %s\n", v->name); */
+ v = v->next;
+ }
+
+ if (defaultsockfd < 0) {
+ if (!(ns = ast_netsock_bind(netsock, io, "0.0.0.0", portno, tos, cos, socket_read, NULL))) {
+ ast_log(LOG_ERROR, "Unable to create network socket: %s\n", strerror(errno));
+ } else {
+ ast_verb(2, "Binding IAX2 to default address 0.0.0.0:%d\n", portno);
+ defaultsockfd = ast_netsock_sockfd(ns);
+ ast_netsock_unref(ns);
+ }
+ }
+ if (reload) {
+ ast_netsock_release(outsock);
+ outsock = ast_netsock_list_alloc();
+ if (!outsock) {
+ ast_log(LOG_ERROR, "Could not allocate outsock list.\n");
+ return -1;
+ }
+ ast_netsock_init(outsock);
+ }
+
+ if (min_reg_expire > max_reg_expire) {
+ ast_log(LOG_WARNING, "Minimum registration interval of %d is more than maximum of %d, resetting minimum to %d\n",
+ min_reg_expire, max_reg_expire, max_reg_expire);
+ min_reg_expire = max_reg_expire;
+ }
+ iax2_capability = capability;
+
+ if (ucfg) {
+ struct ast_variable *gen;
+ int genhasiax;
+ int genregisteriax;
+ const char *hasiax, *registeriax;
+
+ genhasiax = ast_true(ast_variable_retrieve(ucfg, "general", "hasiax"));
+ genregisteriax = ast_true(ast_variable_retrieve(ucfg, "general", "registeriax"));
+ gen = ast_variable_browse(ucfg, "general");
+ cat = ast_category_browse(ucfg, NULL);
+ while (cat) {
+ if (strcasecmp(cat, "general")) {
+ hasiax = ast_variable_retrieve(ucfg, cat, "hasiax");
+ registeriax = ast_variable_retrieve(ucfg, cat, "registeriax");
+ if (ast_true(hasiax) || (!hasiax && genhasiax)) {
+ /* Start with general parameters, then specific parameters, user and peer */
+ user = build_user(cat, gen, ast_variable_browse(ucfg, cat), 0);
+ if (user) {
+ ao2_link(users, user);
+ user = user_unref(user);
+ }
+ peer = build_peer(cat, gen, ast_variable_browse(ucfg, cat), 0);
+ if (peer) {
+ if (ast_test_flag(peer, IAX_DYNAMIC))
+ reg_source_db(peer);
+ ao2_link(peers, peer);
+ peer = peer_unref(peer);
+ }
+ }
+ if (ast_true(registeriax) || (!registeriax && genregisteriax)) {
+ char tmp[256];
+ const char *host = ast_variable_retrieve(ucfg, cat, "host");
+ const char *username = ast_variable_retrieve(ucfg, cat, "username");
+ const char *secret = ast_variable_retrieve(ucfg, cat, "secret");
+ if (!host)
+ host = ast_variable_retrieve(ucfg, "general", "host");
+ if (!username)
+ username = ast_variable_retrieve(ucfg, "general", "username");
+ if (!secret)
+ secret = ast_variable_retrieve(ucfg, "general", "secret");
+ if (!ast_strlen_zero(username) && !ast_strlen_zero(host)) {
+ if (!ast_strlen_zero(secret))
+ snprintf(tmp, sizeof(tmp), "%s:%s@%s", username, secret, host);
+ else
+ snprintf(tmp, sizeof(tmp), "%s@%s", username, host);
+ iax2_register(tmp, 0);
+ }
+ }
+ }
+ cat = ast_category_browse(ucfg, cat);
+ }
+ ast_config_destroy(ucfg);
+ }
+
+ cat = ast_category_browse(cfg, NULL);
+ while(cat) {
+ if (strcasecmp(cat, "general")) {
+ utype = ast_variable_retrieve(cfg, cat, "type");
+ if (utype) {
+ if (!strcasecmp(utype, "user") || !strcasecmp(utype, "friend")) {
+ user = build_user(cat, ast_variable_browse(cfg, cat), NULL, 0);
+ if (user) {
+ ao2_link(users, user);
+ user = user_unref(user);
+ }
+ }
+ if (!strcasecmp(utype, "peer") || !strcasecmp(utype, "friend")) {
+ peer = build_peer(cat, ast_variable_browse(cfg, cat), NULL, 0);
+ if (peer) {
+ if (ast_test_flag(peer, IAX_DYNAMIC))
+ reg_source_db(peer);
+ ao2_link(peers, peer);
+ peer = peer_unref(peer);
+ }
+ } else if (strcasecmp(utype, "user")) {
+ ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", utype, cat, config_file);
+ }
+ } else
+ ast_log(LOG_WARNING, "Section '%s' lacks type\n", cat);
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+ ast_config_destroy(cfg);
+ set_timing();
+ return 1;
+}
+
+static void poke_all_peers(void)
+{
+ struct ao2_iterator i;
+ struct iax2_peer *peer;
+
+ i = ao2_iterator_init(peers, 0);
+ while ((peer = ao2_iterator_next(&i))) {
+ iax2_poke_peer(peer, 0);
+ peer_unref(peer);
+ }
+}
+static int reload_config(void)
+{
+ char *config = "iax.conf";
+ struct iax2_registry *reg;
+
+ if (set_config(config, 1) > 0) {
+ prune_peers();
+ prune_users();
+ trunk_timed = trunk_untimed = 0;
+ trunk_nmaxmtu = trunk_maxmtu = 0;
+
+ AST_LIST_LOCK(&registrations);
+ AST_LIST_TRAVERSE(&registrations, reg, entry)
+ iax2_do_register(reg);
+ AST_LIST_UNLOCK(&registrations);
+
+ /* Qualify hosts, too */
+ poke_all_peers();
+ }
+
+ reload_firmware(0);
+ iax_provision_reload(1);
+
+ return 0;
+}
+
+static char *handle_cli_iax2_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 reload";
+ e->usage =
+ "Usage: iax2 reload\n"
+ " Reloads IAX configuration from iax.conf\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ reload_config();
+
+ return CLI_SUCCESS;
+}
+
+static int reload(void)
+{
+ return reload_config();
+}
+
+static int cache_get_callno_locked(const char *data)
+{
+ struct sockaddr_in sin;
+ int x;
+ int callno;
+ struct iax_ie_data ied;
+ struct create_addr_info cai;
+ struct parsed_dial_string pds;
+ char *tmpstr;
+
+ for (x=0; x<IAX_MAX_CALLS; x++) {
+ /* Look for an *exact match* call. Once a call is negotiated, it can only
+ look up entries for a single context */
+ if (!ast_mutex_trylock(&iaxsl[x])) {
+ if (iaxs[x] && !strcasecmp(data, iaxs[x]->dproot))
+ return x;
+ ast_mutex_unlock(&iaxsl[x]);
+ }
+ }
+
+ /* No match found, we need to create a new one */
+
+ memset(&cai, 0, sizeof(cai));
+ memset(&ied, 0, sizeof(ied));
+ memset(&pds, 0, sizeof(pds));
+
+ tmpstr = ast_strdupa(data);
+ parse_dial_string(tmpstr, &pds);
+
+ /* Populate our address from the given */
+ if (create_addr(pds.peer, NULL, &sin, &cai))
+ return -1;
+
+ ast_debug(1, "peer: %s, username: %s, password: %s, context: %s\n",
+ pds.peer, pds.username, pds.password, pds.context);
+
+ callno = find_callno(0, 0, &sin, NEW_FORCE, cai.sockfd);
+ if (callno < 1) {
+ ast_log(LOG_WARNING, "Unable to create call\n");
+ return -1;
+ }
+
+ ast_mutex_lock(&iaxsl[callno]);
+ ast_string_field_set(iaxs[callno], dproot, data);
+ iaxs[callno]->capability = IAX_CAPABILITY_FULLBANDWIDTH;
+
+ iax_ie_append_short(&ied, IAX_IE_VERSION, IAX_PROTO_VERSION);
+ iax_ie_append_str(&ied, IAX_IE_CALLED_NUMBER, "TBD");
+ /* the string format is slightly different from a standard dial string,
+ because the context appears in the 'exten' position
+ */
+ if (pds.exten)
+ iax_ie_append_str(&ied, IAX_IE_CALLED_CONTEXT, pds.exten);
+ if (pds.username)
+ iax_ie_append_str(&ied, IAX_IE_USERNAME, pds.username);
+ iax_ie_append_int(&ied, IAX_IE_FORMAT, IAX_CAPABILITY_FULLBANDWIDTH);
+ iax_ie_append_int(&ied, IAX_IE_CAPABILITY, IAX_CAPABILITY_FULLBANDWIDTH);
+ /* Keep password handy */
+ if (pds.password)
+ ast_string_field_set(iaxs[callno], secret, pds.password);
+ if (pds.key)
+ ast_string_field_set(iaxs[callno], outkey, pds.key);
+ /* Start the call going */
+ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_NEW, 0, ied.buf, ied.pos, -1);
+
+ return callno;
+}
+
+static struct iax2_dpcache *find_cache(struct ast_channel *chan, const char *data, const char *context, const char *exten, int priority)
+{
+ struct iax2_dpcache *dp = NULL;
+ struct timeval tv = ast_tvnow();
+ int x, com[2], timeout, old = 0, outfd, abort, callno;
+ struct ast_channel *c = NULL;
+ struct ast_frame *f = NULL;
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&dpcache, dp, cache_list) {
+ if (ast_tvcmp(tv, dp->expiry) > 0) {
+ AST_LIST_REMOVE_CURRENT(cache_list);
+ if ((dp->flags & CACHE_FLAG_PENDING) || dp->callno)
+ ast_log(LOG_WARNING, "DP still has peer field or pending or callno (flags = %d, peer = blah, callno = %d)\n", dp->flags, dp->callno);
+ else
+ ast_free(dp);
+ continue;
+ }
+ if (!strcmp(dp->peercontext, data) && !strcmp(dp->exten, exten))
+ break;
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ if (!dp) {
+ /* No matching entry. Create a new one. */
+ /* First, can we make a callno? */
+ if ((callno = cache_get_callno_locked(data)) < 0) {
+ ast_log(LOG_WARNING, "Unable to generate call for '%s'\n", data);
+ return NULL;
+ }
+ if (!(dp = ast_calloc(1, sizeof(*dp)))) {
+ ast_mutex_unlock(&iaxsl[callno]);
+ return NULL;
+ }
+ ast_copy_string(dp->peercontext, data, sizeof(dp->peercontext));
+ ast_copy_string(dp->exten, exten, sizeof(dp->exten));
+ dp->expiry = ast_tvnow();
+ dp->orig = dp->expiry;
+ /* Expires in 30 mins by default */
+ dp->expiry.tv_sec += iaxdefaultdpcache;
+ dp->flags = CACHE_FLAG_PENDING;
+ for (x=0;x<sizeof(dp->waiters) / sizeof(dp->waiters[0]); x++)
+ dp->waiters[x] = -1;
+ /* Insert into the lists */
+ AST_LIST_INSERT_TAIL(&dpcache, dp, cache_list);
+ AST_LIST_INSERT_TAIL(&iaxs[callno]->dpentries, dp, peer_list);
+ /* Send the request if we're already up */
+ if (ast_test_flag(&iaxs[callno]->state, IAX_STATE_STARTED))
+ iax2_dprequest(dp, callno);
+ ast_mutex_unlock(&iaxsl[callno]);
+ }
+
+ /* By here we must have a dp */
+ if (dp->flags & CACHE_FLAG_PENDING) {
+ /* Okay, here it starts to get nasty. We need a pipe now to wait
+ for a reply to come back so long as it's pending */
+ for (x=0;x<sizeof(dp->waiters) / sizeof(dp->waiters[0]); x++) {
+ /* Find an empty slot */
+ if (dp->waiters[x] < 0)
+ break;
+ }
+ if (x >= sizeof(dp->waiters) / sizeof(dp->waiters[0])) {
+ ast_log(LOG_WARNING, "No more waiter positions available\n");
+ return NULL;
+ }
+ if (pipe(com)) {
+ ast_log(LOG_WARNING, "Unable to create pipe for comm\n");
+ return NULL;
+ }
+ dp->waiters[x] = com[1];
+ /* Okay, now we wait */
+ timeout = iaxdefaulttimeout * 1000;
+ /* Temporarily unlock */
+ AST_LIST_UNLOCK(&dpcache);
+ /* Defer any dtmf */
+ if (chan)
+ old = ast_channel_defer_dtmf(chan);
+ abort = 0;
+ while(timeout) {
+ c = ast_waitfor_nandfds(&chan, chan ? 1 : 0, &com[0], 1, NULL, &outfd, &timeout);
+ if (outfd > -1)
+ break;
+ if (!c)
+ continue;
+ if (!(f = ast_read(c))) {
+ abort = 1;
+ break;
+ }
+ ast_frfree(f);
+ }
+ if (!timeout) {
+ ast_log(LOG_WARNING, "Timeout waiting for %s exten %s\n", data, exten);
+ }
+ AST_LIST_LOCK(&dpcache);
+ dp->waiters[x] = -1;
+ close(com[1]);
+ close(com[0]);
+ if (abort) {
+ /* Don't interpret anything, just abort. Not sure what th epoint
+ of undeferring dtmf on a hung up channel is but hey whatever */
+ if (!old && chan)
+ ast_channel_undefer_dtmf(chan);
+ return NULL;
+ }
+ if (!(dp->flags & CACHE_FLAG_TIMEOUT)) {
+ /* Now to do non-independent analysis the results of our wait */
+ if (dp->flags & CACHE_FLAG_PENDING) {
+ /* Still pending... It's a timeout. Wake everybody up. Consider it no longer
+ pending. Don't let it take as long to timeout. */
+ dp->flags &= ~CACHE_FLAG_PENDING;
+ dp->flags |= CACHE_FLAG_TIMEOUT;
+ /* Expire after only 60 seconds now. This is designed to help reduce backlog in heavily loaded
+ systems without leaving it unavailable once the server comes back online */
+ dp->expiry.tv_sec = dp->orig.tv_sec + 60;
+ for (x=0;x<sizeof(dp->waiters) / sizeof(dp->waiters[0]); x++)
+ if (dp->waiters[x] > -1)
+ write(dp->waiters[x], "asdf", 4);
+ }
+ }
+ /* Our caller will obtain the rest */
+ if (!old && chan)
+ ast_channel_undefer_dtmf(chan);
+ }
+ return dp;
+}
+
+/*! \brief Part of the IAX2 switch interface */
+static int iax2_exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ int res = 0;
+ struct iax2_dpcache *dp = NULL;
+#if 0
+ ast_log(LOG_NOTICE, "iax2_exists: con: %s, exten: %s, pri: %d, cid: %s, data: %s\n", context, exten, priority, callerid ? callerid : "<unknown>", data);
+#endif
+ if ((priority != 1) && (priority != 2))
+ return 0;
+
+ AST_LIST_LOCK(&dpcache);
+ if ((dp = find_cache(chan, data, context, exten, priority))) {
+ if (dp->flags & CACHE_FLAG_EXISTS)
+ res = 1;
+ } else {
+ ast_log(LOG_WARNING, "Unable to make DP cache\n");
+ }
+ AST_LIST_UNLOCK(&dpcache);
+
+ return res;
+}
+
+/*! \brief part of the IAX2 dial plan switch interface */
+static int iax2_canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ int res = 0;
+ struct iax2_dpcache *dp = NULL;
+#if 0
+ ast_log(LOG_NOTICE, "iax2_canmatch: con: %s, exten: %s, pri: %d, cid: %s, data: %s\n", context, exten, priority, callerid ? callerid : "<unknown>", data);
+#endif
+ if ((priority != 1) && (priority != 2))
+ return 0;
+
+ AST_LIST_LOCK(&dpcache);
+ if ((dp = find_cache(chan, data, context, exten, priority))) {
+ if (dp->flags & CACHE_FLAG_CANEXIST)
+ res = 1;
+ } else {
+ ast_log(LOG_WARNING, "Unable to make DP cache\n");
+ }
+ AST_LIST_UNLOCK(&dpcache);
+
+ return res;
+}
+
+/*! \brief Part of the IAX2 Switch interface */
+static int iax2_matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ int res = 0;
+ struct iax2_dpcache *dp = NULL;
+#if 0
+ ast_log(LOG_NOTICE, "iax2_matchmore: con: %s, exten: %s, pri: %d, cid: %s, data: %s\n", context, exten, priority, callerid ? callerid : "<unknown>", data);
+#endif
+ if ((priority != 1) && (priority != 2))
+ return 0;
+
+ AST_LIST_LOCK(&dpcache);
+ if ((dp = find_cache(chan, data, context, exten, priority))) {
+ if (dp->flags & CACHE_FLAG_MATCHMORE)
+ res = 1;
+ } else {
+ ast_log(LOG_WARNING, "Unable to make DP cache\n");
+ }
+ AST_LIST_UNLOCK(&dpcache);
+
+ return res;
+}
+
+/*! \brief Execute IAX2 dialplan switch */
+static int iax2_exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ char odata[256];
+ char req[256];
+ char *ncontext;
+ struct iax2_dpcache *dp = NULL;
+ struct ast_app *dial = NULL;
+#if 0
+ ast_log(LOG_NOTICE, "iax2_exec: con: %s, exten: %s, pri: %d, cid: %s, data: %s, newstack: %d\n", context, exten, priority, callerid ? callerid : "<unknown>", data, newstack);
+#endif
+ if (priority == 2) {
+ /* Indicate status, can be overridden in dialplan */
+ const char *dialstatus = pbx_builtin_getvar_helper(chan, "DIALSTATUS");
+ if (dialstatus) {
+ dial = pbx_findapp(dialstatus);
+ if (dial)
+ pbx_exec(chan, dial, "");
+ }
+ return -1;
+ } else if (priority != 1)
+ return -1;
+
+ AST_LIST_LOCK(&dpcache);
+ if ((dp = find_cache(chan, data, context, exten, priority))) {
+ if (dp->flags & CACHE_FLAG_EXISTS) {
+ ast_copy_string(odata, data, sizeof(odata));
+ ncontext = strchr(odata, '/');
+ if (ncontext) {
+ *ncontext = '\0';
+ ncontext++;
+ snprintf(req, sizeof(req), "IAX2/%s/%s@%s", odata, exten, ncontext);
+ } else {
+ snprintf(req, sizeof(req), "IAX2/%s/%s", odata, exten);
+ }
+ ast_verb(3, "Executing Dial('%s')\n", req);
+ } else {
+ AST_LIST_UNLOCK(&dpcache);
+ ast_log(LOG_WARNING, "Can't execute nonexistent extension '%s[@%s]' in data '%s'\n", exten, context, data);
+ return -1;
+ }
+ }
+ AST_LIST_UNLOCK(&dpcache);
+
+ if ((dial = pbx_findapp("Dial")))
+ return pbx_exec(chan, dial, req);
+ else
+ ast_log(LOG_WARNING, "No dial application registered\n");
+
+ return -1;
+}
+
+static int function_iaxpeer(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ struct iax2_peer *peer;
+ char *peername, *colname;
+
+ peername = ast_strdupa(data);
+
+ /* if our channel, return the IP address of the endpoint of current channel */
+ if (!strcmp(peername,"CURRENTCHANNEL")) {
+ unsigned short callno;
+ if (chan->tech != &iax2_tech)
+ return -1;
+ callno = PTR_TO_CALLNO(chan->tech_pvt);
+ ast_copy_string(buf, iaxs[callno]->addr.sin_addr.s_addr ? ast_inet_ntoa(iaxs[callno]->addr.sin_addr) : "", len);
+ return 0;
+ }
+
+ if ((colname = strchr(peername, ':'))) /*! \todo : will be removed after the 1.4 relese */
+ *colname++ = '\0';
+ else if ((colname = strchr(peername, '|')))
+ *colname++ = '\0';
+ else
+ colname = "ip";
+
+ if (!(peer = find_peer(peername, 1)))
+ return -1;
+
+ if (!strcasecmp(colname, "ip")) {
+ ast_copy_string(buf, peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "", len);
+ } else if (!strcasecmp(colname, "status")) {
+ peer_status(peer, buf, len);
+ } else if (!strcasecmp(colname, "mailbox")) {
+ ast_copy_string(buf, peer->mailbox, len);
+ } else if (!strcasecmp(colname, "context")) {
+ ast_copy_string(buf, peer->context, len);
+ } else if (!strcasecmp(colname, "expire")) {
+ snprintf(buf, len, "%d", peer->expire);
+ } else if (!strcasecmp(colname, "dynamic")) {
+ ast_copy_string(buf, (ast_test_flag(peer, IAX_DYNAMIC) ? "yes" : "no"), len);
+ } else if (!strcasecmp(colname, "callerid_name")) {
+ ast_copy_string(buf, peer->cid_name, len);
+ } else if (!strcasecmp(colname, "callerid_num")) {
+ ast_copy_string(buf, peer->cid_num, len);
+ } else if (!strcasecmp(colname, "codecs")) {
+ ast_getformatname_multiple(buf, len -1, peer->capability);
+ } else if (!strncasecmp(colname, "codec[", 6)) {
+ char *codecnum, *ptr;
+ int index = 0, codec = 0;
+
+ codecnum = strchr(colname, '[');
+ *codecnum = '\0';
+ codecnum++;
+ if ((ptr = strchr(codecnum, ']'))) {
+ *ptr = '\0';
+ }
+ index = atoi(codecnum);
+ if((codec = ast_codec_pref_index(&peer->prefs, index))) {
+ ast_copy_string(buf, ast_getformatname(codec), len);
+ }
+ }
+
+ peer_unref(peer);
+
+ return 0;
+}
+
+struct ast_custom_function iaxpeer_function = {
+ .name = "IAXPEER",
+ .synopsis = "Gets IAX peer information",
+ .syntax = "IAXPEER(<peername|CURRENTCHANNEL>[|item])",
+ .read = function_iaxpeer,
+ .desc = "If peername specified, valid items are:\n"
+ "- ip (default) The IP address.\n"
+ "- status The peer's status (if qualify=yes)\n"
+ "- mailbox The configured mailbox.\n"
+ "- context The configured context.\n"
+ "- expire The epoch time of the next expire.\n"
+ "- dynamic Is it dynamic? (yes/no).\n"
+ "- callerid_name The configured Caller ID name.\n"
+ "- callerid_num The configured Caller ID number.\n"
+ "- codecs The configured codecs.\n"
+ "- codec[x] Preferred codec index number 'x' (beginning with zero).\n"
+ "\n"
+ "If CURRENTCHANNEL specified, returns IP address of current channel\n"
+ "\n"
+};
+
+static int acf_channel_write(struct ast_channel *chan, const char *function, char *args, const char *value)
+{
+ struct chan_iax2_pvt *pvt;
+ unsigned int callno;
+ int res = 0;
+
+ if (!chan || chan->tech != &iax2_tech) {
+ ast_log(LOG_ERROR, "This function requires a valid IAX2 channel\n");
+ return -1;
+ }
+
+ callno = PTR_TO_CALLNO(chan->tech_pvt);
+ ast_mutex_lock(&iaxsl[callno]);
+ if (!(pvt = iaxs[callno])) {
+ ast_mutex_unlock(&iaxsl[callno]);
+ return -1;
+ }
+
+ if (!strcasecmp(args, "osptoken"))
+ ast_string_field_set(pvt, osptoken, value);
+ else
+ res = -1;
+
+ ast_mutex_unlock(&iaxsl[callno]);
+
+ return res;
+}
+
+static int acf_channel_read(struct ast_channel *chan, const char *funcname, char *args, char *buf, size_t buflen)
+{
+ struct chan_iax2_pvt *pvt;
+ unsigned int callno;
+ int res = 0;
+
+ if (!chan || chan->tech != &iax2_tech) {
+ ast_log(LOG_ERROR, "This function requires a valid IAX2 channel\n");
+ return -1;
+ }
+
+ callno = PTR_TO_CALLNO(chan->tech_pvt);
+ ast_mutex_lock(&iaxsl[callno]);
+ if (!(pvt = iaxs[callno])) {
+ ast_mutex_unlock(&iaxsl[callno]);
+ return -1;
+ }
+
+ if (!strcasecmp(args, "osptoken"))
+ ast_copy_string(buf, pvt->osptoken, buflen);
+ else
+ res = -1;
+
+ ast_mutex_unlock(&iaxsl[callno]);
+
+ return res;
+}
+
+/*! \brief Part of the device state notification system ---*/
+static int iax2_devicestate(void *data)
+{
+ struct parsed_dial_string pds;
+ char *tmp = ast_strdupa(data);
+ struct iax2_peer *p;
+ int res = AST_DEVICE_INVALID;
+
+ memset(&pds, 0, sizeof(pds));
+ parse_dial_string(tmp, &pds);
+ if (ast_strlen_zero(pds.peer))
+ return res;
+
+ ast_debug(3, "Checking device state for device %s\n", pds.peer);
+
+ /* SLD: FIXME: second call to find_peer during registration */
+ if (!(p = find_peer(pds.peer, 1)))
+ return res;
+
+ res = AST_DEVICE_UNAVAILABLE;
+ ast_debug(3, "iax2_devicestate: Found peer. What's device state of %s? addr=%d, defaddr=%d maxms=%d, lastms=%d\n",
+ pds.peer, p->addr.sin_addr.s_addr, p->defaddr.sin_addr.s_addr, p->maxms, p->lastms);
+
+ if ((p->addr.sin_addr.s_addr || p->defaddr.sin_addr.s_addr) &&
+ (!p->maxms || ((p->lastms > -1) && (p->historicms <= p->maxms)))) {
+ /* Peer is registered, or have default IP address
+ and a valid registration */
+ if (p->historicms == 0 || p->historicms <= p->maxms)
+ /* let the core figure out whether it is in use or not */
+ res = AST_DEVICE_UNKNOWN;
+ }
+
+ peer_unref(p);
+
+ return res;
+}
+
+static struct ast_switch iax2_switch =
+{
+ name: "IAX2",
+ description: "IAX Remote Dialplan Switch",
+ exists: iax2_exists,
+ canmatch: iax2_canmatch,
+ exec: iax2_exec,
+ matchmore: iax2_matchmore,
+};
+
+/*
+ { { "iax2", "show", "cache", NULL },
+ iax2_show_cache, "Display IAX cached dialplan",
+ show_cache_usage },
+
+ { { "iax2", "show", "channels", NULL },
+ iax2_show_channels, "List active IAX channels",
+ show_channels_usage },
+
+ { { "iax2", "show", "firmware", NULL },
+ iax2_show_firmware, "List available IAX firmwares",
+ show_firmware_usage },
+
+ { { "iax2", "show", "netstats", NULL },
+ iax2_show_netstats, "List active IAX channel netstats",
+ show_netstats_usage },
+
+ { { "iax2", "show", "peers", NULL },
+ iax2_show_peers, "List defined IAX peers",
+ show_peers_usage },
+
+ { { "iax2", "show", "registry", NULL },
+ iax2_show_registry, "Display IAX registration status",
+ show_reg_usage },
+
+ { { "iax2", "show", "stats", NULL },
+ iax2_show_stats, "Display IAX statistics",
+ show_stats_usage },
+
+ { { "iax2", "show", "threads", NULL },
+ iax2_show_threads, "Display IAX helper thread info",
+ show_threads_usage },
+
+ { { "iax2", "unregister", NULL },
+ iax2_unregister, "Unregister (force expiration) an IAX2 peer from the registry",
+ unregister_usage, complete_iax2_unregister },
+
+ { { "iax2", "set", "mtu", NULL },
+ iax2_set_mtu, "Set the IAX systemwide trunking MTU",
+ set_mtu_usage, NULL, NULL },
+
+ { { "iax2", "show", "users", NULL },
+ iax2_show_users, "List defined IAX users",
+ show_users_usage },
+
+ { { "iax2", "prune", "realtime", NULL },
+ iax2_prune_realtime, "Prune a cached realtime lookup",
+ prune_realtime_usage, complete_iax2_show_peer },
+
+ { { "iax2", "reload", NULL },
+ iax2_reload, "Reload IAX configuration",
+ iax2_reload_usage },
+
+ { { "iax2", "show", "peer", NULL },
+ iax2_show_peer, "Show details on specific IAX peer",
+ show_peer_usage, complete_iax2_show_peer },
+
+ { { "iax2", "set", "debug", NULL },
+ iax2_do_debug, "Enable IAX debugging",
+ debug_usage },
+
+ { { "iax2", "set", "debug", "trunk", NULL },
+ iax2_do_trunk_debug, "Enable IAX trunk debugging",
+ debug_trunk_usage },
+
+ { { "iax2", "set", "debug", "jb", NULL },
+ iax2_do_jb_debug, "Enable IAX jitterbuffer debugging",
+ debug_jb_usage },
+
+ { { "iax2", "set", "debug", "off", NULL },
+ iax2_no_debug, "Disable IAX debugging",
+ no_debug_usage },
+
+ { { "iax2", "set", "debug", "trunk", "off", NULL },
+ iax2_no_trunk_debug, "Disable IAX trunk debugging",
+ no_debug_trunk_usage },
+
+ { { "iax2", "set", "debug", "jb", "off", NULL },
+ iax2_no_jb_debug, "Disable IAX jitterbuffer debugging",
+ no_debug_jb_usage },
+
+ { { "iax2", "test", "losspct", NULL },
+ iax2_test_losspct, "Set IAX2 incoming frame loss percentage",
+ iax2_test_losspct_usage },
+
+ { { "iax2", "provision", NULL },
+ iax2_prov_cmd, "Provision an IAX device",
+ show_prov_usage, iax2_prov_complete_template_3rd },
+
+#ifdef IAXTESTS
+ { { "iax2", "test", "late", NULL },
+ iax2_test_late, "Test the receipt of a late frame",
+ iax2_test_late_usage },
+
+ { { "iax2", "test", "resync", NULL },
+ iax2_test_resync, "Test a resync in received timestamps",
+ iax2_test_resync_usage },
+
+ { { "iax2", "test", "jitter", NULL },
+ iax2_test_jitter, "Simulates jitter for testing",
+ iax2_test_jitter_usage },
+#endif
+*/
+
+static struct ast_cli_entry cli_iax2[] = {
+ AST_CLI_DEFINE(handle_cli_iax2_provision, "Provision an IAX device"),
+ AST_CLI_DEFINE(handle_cli_iax2_prune_realtime, "Prune a cached realtime lookup"),
+ AST_CLI_DEFINE(handle_cli_iax2_reload, "Reload IAX configuration"),
+ AST_CLI_DEFINE(handle_cli_iax2_set_mtu, "Set the IAX systemwide trunking MTU"),
+ AST_CLI_DEFINE(handle_cli_iax2_set_debug, "Enable IAX debugging"),
+ AST_CLI_DEFINE(handle_cli_iax2_set_debug_trunk, "Enable IAX trunk debugging"),
+ AST_CLI_DEFINE(handle_cli_iax2_set_debug_jb, "Enable IAX jitterbuffer debugging"),
+ AST_CLI_DEFINE(handle_cli_iax2_set_debug_off, "Disable IAX debugging"),
+ AST_CLI_DEFINE(handle_cli_iax2_set_debug_trunk_off, "Disable IAX trunk debugging"),
+ AST_CLI_DEFINE(handle_cli_iax2_set_debug_jb_off, "Disable IAX jitterbuffer debugging"),
+ AST_CLI_DEFINE(handle_cli_iax2_show_cache, "Display IAX cached dialplan"),
+ AST_CLI_DEFINE(handle_cli_iax2_show_channels, "List active IAX channels"),
+ AST_CLI_DEFINE(handle_cli_iax2_show_firmware, "List available IAX firmware"),
+ AST_CLI_DEFINE(handle_cli_iax2_show_netstats, "List active IAX channel netstats"),
+ AST_CLI_DEFINE(handle_cli_iax2_show_peer, "Show details on specific IAX peer"),
+ AST_CLI_DEFINE(handle_cli_iax2_show_peers, "List defined IAX peers"),
+ AST_CLI_DEFINE(handle_cli_iax2_show_registry, "Display IAX registration status"),
+ AST_CLI_DEFINE(handle_cli_iax2_show_stats, "Display IAX statistics"),
+ AST_CLI_DEFINE(handle_cli_iax2_show_threads, "Display IAX helper thread info"),
+ AST_CLI_DEFINE(handle_cli_iax2_show_users, "List defined IAX users"),
+ AST_CLI_DEFINE(handle_cli_iax2_test_losspct, "Set IAX2 incoming frame loss percentage"),
+ AST_CLI_DEFINE(handle_cli_iax2_unregister, "Unregister (force expiration) an IAX2 peer from the registry"),
+#ifdef IAXTESTS
+ AST_CLI_DEFINE(handle_cli_iax2_test_jitter, "Simulates jitter for testing"),
+ AST_CLI_DEFINE(handle_cli_iax2_test_late, "Test the receipt of a late frame"),
+ AST_CLI_DEFINE(handle_cli_iax2_test_resync, "Test a resync in received timestamps"),
+#endif /* IAXTESTS */
+};
+
+static int __unload_module(void)
+{
+ struct iax2_thread *thread = NULL;
+ struct ast_context *con;
+ int x;
+
+ /* Make sure threads do not hold shared resources when they are canceled */
+
+ /* Grab the sched lock resource to keep it away from threads about to die */
+ /* Cancel the network thread, close the net socket */
+ if (netthreadid != AST_PTHREADT_NULL) {
+ AST_LIST_LOCK(&frame_queue);
+ ast_mutex_lock(&sched_lock);
+ pthread_cancel(netthreadid);
+ ast_cond_signal(&sched_cond);
+ ast_mutex_unlock(&sched_lock); /* Release the schedule lock resource */
+ AST_LIST_UNLOCK(&frame_queue);
+ pthread_join(netthreadid, NULL);
+ }
+ if (schedthreadid != AST_PTHREADT_NULL) {
+ ast_mutex_lock(&sched_lock);
+ pthread_cancel(schedthreadid);
+ ast_cond_signal(&sched_cond);
+ ast_mutex_unlock(&sched_lock);
+ pthread_join(schedthreadid, NULL);
+ }
+
+ /* Call for all threads to halt */
+ AST_LIST_LOCK(&idle_list);
+ while ((thread = AST_LIST_REMOVE_HEAD(&idle_list, list)))
+ pthread_cancel(thread->threadid);
+ AST_LIST_UNLOCK(&idle_list);
+
+ AST_LIST_LOCK(&active_list);
+ while ((thread = AST_LIST_REMOVE_HEAD(&active_list, list)))
+ pthread_cancel(thread->threadid);
+ AST_LIST_UNLOCK(&active_list);
+
+ AST_LIST_LOCK(&dynamic_list);
+ while ((thread = AST_LIST_REMOVE_HEAD(&dynamic_list, list)))
+ pthread_cancel(thread->threadid);
+ AST_LIST_UNLOCK(&dynamic_list);
+
+ /* Wait for threads to exit */
+ while(0 < iaxactivethreadcount)
+ usleep(10000);
+
+ ast_netsock_release(netsock);
+ ast_netsock_release(outsock);
+ for (x = 0; x < IAX_MAX_CALLS; x++) {
+ if (iaxs[x])
+ iax2_destroy(x);
+ }
+ ast_manager_unregister( "IAXpeers" );
+ ast_manager_unregister( "IAXpeerlist" );
+ ast_manager_unregister( "IAXnetstats" );
+ ast_unregister_application(papp);
+ ast_cli_unregister_multiple(cli_iax2, sizeof(cli_iax2) / sizeof(struct ast_cli_entry));
+ ast_unregister_switch(&iax2_switch);
+ ast_channel_unregister(&iax2_tech);
+ delete_users();
+ iax_provision_unload();
+ sched_context_destroy(sched);
+ reload_firmware(1);
+
+ for (x = 0; x < IAX_MAX_CALLS; x++)
+ ast_mutex_destroy(&iaxsl[x]);
+
+ ao2_ref(peers, -1);
+ ao2_ref(users, -1);
+
+ con = ast_context_find(regcontext);
+ if (con)
+ ast_context_destroy(con, "IAX2");
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_custom_function_unregister(&iaxpeer_function);
+ ast_custom_function_unregister(&iaxvar_function);
+ return __unload_module();
+}
+
+static int peer_set_sock_cb(void *obj, void *arg, int flags)
+{
+ struct iax2_peer *peer = obj;
+
+ if (peer->sockfd < 0)
+ peer->sockfd = defaultsockfd;
+
+ return 0;
+}
+
+/*! \brief Load IAX2 module, load configuraiton ---*/
+static int load_module(void)
+{
+ char *config = "iax.conf";
+ int x = 0;
+ struct iax2_registry *reg = NULL;
+
+ peers = ao2_container_alloc(MAX_PEER_BUCKETS, peer_hash_cb, peer_cmp_cb);
+ if (!peers)
+ return AST_MODULE_LOAD_FAILURE;
+ users = ao2_container_alloc(MAX_USER_BUCKETS, user_hash_cb, user_cmp_cb);
+ if (!users) {
+ ao2_ref(peers, -1);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ ast_custom_function_register(&iaxpeer_function);
+ ast_custom_function_register(&iaxvar_function);
+
+ iax_set_output(iax_debug_output);
+ iax_set_error(iax_error_output);
+ jb_setoutput(jb_error_output, jb_warning_output, NULL);
+
+#ifdef HAVE_ZAPTEL
+#ifdef ZT_TIMERACK
+ timingfd = open("/dev/zap/timer", O_RDWR);
+ if (timingfd < 0)
+#endif
+ timingfd = open("/dev/zap/pseudo", O_RDWR);
+ if (timingfd < 0)
+ ast_log(LOG_WARNING, "Unable to open IAX timing interface: %s\n", strerror(errno));
+#endif
+
+ memset(iaxs, 0, sizeof(iaxs));
+
+ for (x=0;x<IAX_MAX_CALLS;x++)
+ ast_mutex_init(&iaxsl[x]);
+
+ ast_cond_init(&sched_cond, NULL);
+
+ if (!(sched = sched_context_create())) {
+ ast_log(LOG_ERROR, "Failed to create scheduler context\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ if (!(io = io_context_create())) {
+ ast_log(LOG_ERROR, "Failed to create I/O context\n");
+ sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ if (!(netsock = ast_netsock_list_alloc())) {
+ ast_log(LOG_ERROR, "Failed to create netsock list\n");
+ io_context_destroy(io);
+ sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ ast_netsock_init(netsock);
+
+ outsock = ast_netsock_list_alloc();
+ if (!outsock) {
+ ast_log(LOG_ERROR, "Could not allocate outsock list.\n");
+ io_context_destroy(io);
+ sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ ast_netsock_init(outsock);
+
+ ast_cli_register_multiple(cli_iax2, sizeof(cli_iax2) / sizeof(struct ast_cli_entry));
+
+ ast_register_application(papp, iax2_prov_app, psyn, pdescrip);
+
+ ast_manager_register( "IAXpeers", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_iax2_show_peers, "List IAX Peers" );
+ ast_manager_register( "IAXpeerlist", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_iax2_show_peer_list, "List IAX Peers" );
+ ast_manager_register( "IAXnetstats", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_iax2_show_netstats, "Show IAX Netstats" );
+
+ if(set_config(config, 0) == -1)
+ return AST_MODULE_LOAD_DECLINE;
+
+ if (ast_channel_register(&iax2_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class %s\n", "IAX2");
+ __unload_module();
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ if (ast_register_switch(&iax2_switch))
+ ast_log(LOG_ERROR, "Unable to register IAX switch\n");
+
+ if (start_network_thread()) {
+ ast_log(LOG_ERROR, "Unable to start network thread\n");
+ __unload_module();
+ return AST_MODULE_LOAD_FAILURE;
+ } else
+ ast_verb(2, "IAX Ready and Listening\n");
+
+ AST_LIST_LOCK(&registrations);
+ AST_LIST_TRAVERSE(&registrations, reg, entry)
+ iax2_do_register(reg);
+ AST_LIST_UNLOCK(&registrations);
+
+ ao2_callback(peers, 0, peer_set_sock_cb, NULL);
+ ao2_callback(peers, 0, iax2_poke_peer_cb, NULL);
+
+
+ reload_firmware(0);
+ iax_provision_reload(0);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Inter Asterisk eXchange (Ver 2)",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/trunk/channels/chan_jingle.c b/trunk/channels/chan_jingle.c
new file mode 100644
index 000000000..87633978b
--- /dev/null
+++ b/trunk/channels/chan_jingle.c
@@ -0,0 +1,1862 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Matt O'Gorman <mogorman@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \author Matt O'Gorman <mogorman@digium.com>
+ *
+ * \brief Jingle Channel Driver
+ *
+ * \extref Iksemel http://iksemel.jabberstudio.org/
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <depend>iksemel</depend>
+ <depend>res_jabber</depend>
+ <use>openssl</use>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/signal.h>
+#include <iksemel.h>
+#include <pthread.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/rtp.h"
+#include "asterisk/acl.h"
+#include "asterisk/callerid.h"
+#include "asterisk/file.h"
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/manager.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/astobj.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/jabber.h"
+#include "asterisk/jingle.h"
+
+#define JINGLE_CONFIG "jingle.conf"
+
+/*! Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = ""
+};
+static struct ast_jb_conf global_jbconf;
+
+enum jingle_protocol {
+ AJI_PROTOCOL_UDP,
+ AJI_PROTOCOL_SSLTCP,
+};
+
+enum jingle_connect_type {
+ AJI_CONNECT_HOST,
+ AJI_CONNECT_PRFLX,
+ AJI_CONNECT_RELAY,
+ AJI_CONNECT_SRFLX,
+};
+
+struct jingle_pvt {
+ ast_mutex_t lock; /*!< Channel private lock */
+ time_t laststun;
+ struct jingle *parent; /*!< Parent client */
+ char sid[100];
+ char them[AJI_MAX_JIDLEN];
+ char ring[10]; /*!< Message ID of ring */
+ iksrule *ringrule; /*!< Rule for matching RING request */
+ int initiator; /*!< If we're the initiator */
+ int alreadygone;
+ int capability;
+ struct ast_codec_pref prefs;
+ struct jingle_candidate *theircandidates;
+ struct jingle_candidate *ourcandidates;
+ char cid_num[80]; /*!< Caller ID num */
+ char cid_name[80]; /*!< Caller ID name */
+ char exten[80]; /*!< Called extension */
+ struct ast_channel *owner; /*!< Master Channel */
+ struct ast_rtp *rtp; /*!< RTP audio session */
+ struct ast_rtp *vrtp; /*!< RTP video session */
+ int jointcapability; /*!< Supported capability at both ends (codecs ) */
+ int peercapability;
+ struct jingle_pvt *next; /* Next entity */
+};
+
+struct jingle_candidate {
+ unsigned int component; /*!< ex. : 1 for RTP, 2 for RTCP */
+ unsigned int foundation; /*!< Function of IP, protocol, type */
+ unsigned int generation;
+ char ip[16];
+ unsigned int network;
+ unsigned int port;
+ unsigned int priority;
+ enum jingle_protocol protocol;
+ char password[100];
+ enum jingle_connect_type type;
+ char ufrag[100];
+ unsigned int preference;
+ struct jingle_candidate *next;
+};
+
+struct jingle {
+ ASTOBJ_COMPONENTS(struct jingle);
+ struct aji_client *connection;
+ struct aji_buddy *buddy;
+ struct jingle_pvt *p;
+ struct ast_codec_pref prefs;
+ int amaflags; /*!< AMA Flags */
+ char user[100];
+ char context[100];
+ char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Account code */
+ int capability;
+ ast_group_t callgroup; /*!< Call group */
+ ast_group_t pickupgroup; /*!< Pickup group */
+ int callingpres; /*!< Calling presentation */
+ int allowguest;
+ char language[MAX_LANGUAGE]; /*!< Default language for prompts */
+ char musicclass[MAX_MUSICCLASS]; /*!< Music on Hold class */
+};
+
+struct jingle_container {
+ ASTOBJ_CONTAINER_COMPONENTS(struct jingle);
+};
+
+static const char desc[] = "Jingle Channel";
+static const char type[] = "Jingle";
+
+static int global_capability = AST_FORMAT_ULAW | AST_FORMAT_ALAW | AST_FORMAT_GSM | AST_FORMAT_H263;
+
+AST_MUTEX_DEFINE_STATIC(jinglelock); /*!< Protect the interface list (of jingle_pvt's) */
+
+/* Forward declarations */
+static struct ast_channel *jingle_request(const char *type, int format, void *data, int *cause);
+static int jingle_digit_begin(struct ast_channel *ast, char digit);
+static int jingle_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int jingle_call(struct ast_channel *ast, char *dest, int timeout);
+static int jingle_hangup(struct ast_channel *ast);
+static int jingle_answer(struct ast_channel *ast);
+static int jingle_newcall(struct jingle *client, ikspak *pak);
+static struct ast_frame *jingle_read(struct ast_channel *ast);
+static int jingle_write(struct ast_channel *ast, struct ast_frame *f);
+static int jingle_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+static int jingle_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static int jingle_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
+static struct jingle_pvt *jingle_alloc(struct jingle *client, const char *from, const char *sid);
+static char *jingle_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *jingle_do_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+/*----- RTP interface functions */
+static int jingle_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp,
+ struct ast_rtp *vrtp, struct ast_rtp *tpeer, int codecs, int nat_active);
+static enum ast_rtp_get_result jingle_get_rtp_peer(struct ast_channel *chan, struct ast_rtp **rtp);
+static int jingle_get_codec(struct ast_channel *chan);
+
+/*! \brief PBX interface structure for channel registration */
+static const struct ast_channel_tech jingle_tech = {
+ .type = "Jingle",
+ .description = "Jingle Channel Driver",
+ .capabilities = AST_FORMAT_AUDIO_MASK,
+ .requester = jingle_request,
+ .send_digit_begin = jingle_digit_begin,
+ .send_digit_end = jingle_digit_end,
+ .bridge = ast_rtp_bridge,
+ .call = jingle_call,
+ .hangup = jingle_hangup,
+ .answer = jingle_answer,
+ .read = jingle_read,
+ .write = jingle_write,
+ .exception = jingle_read,
+ .indicate = jingle_indicate,
+ .fixup = jingle_fixup,
+ .send_html = jingle_sendhtml,
+ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER
+};
+
+static struct sockaddr_in bindaddr = { 0, }; /*!< The address we bind to */
+
+static struct sched_context *sched; /*!< The scheduling context */
+static struct io_context *io; /*!< The IO context */
+static struct in_addr __ourip;
+
+
+/*! \brief RTP driver interface */
+static struct ast_rtp_protocol jingle_rtp = {
+ type: "Jingle",
+ get_rtp_info: jingle_get_rtp_peer,
+ set_rtp_peer: jingle_set_rtp_peer,
+ get_codec: jingle_get_codec,
+};
+
+static struct ast_cli_entry jingle_cli[] = {
+ AST_CLI_DEFINE(jingle_do_reload, "Reload Jingle configuration"),
+ AST_CLI_DEFINE(jingle_show_channels, "Show Jingle channels"),
+};
+
+
+static char externip[16];
+
+static struct jingle_container jingle_list;
+
+static void jingle_member_destroy(struct jingle *obj)
+{
+ ast_free(obj);
+}
+
+static struct jingle *find_jingle(char *name, char *connection)
+{
+ struct jingle *jingle = NULL;
+
+ jingle = ASTOBJ_CONTAINER_FIND(&jingle_list, name);
+ if (!jingle && strchr(name, '@'))
+ jingle = ASTOBJ_CONTAINER_FIND_FULL(&jingle_list, name, user,,, strcasecmp);
+
+ if (!jingle) { /* guest call */
+ ASTOBJ_CONTAINER_TRAVERSE(&jingle_list, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ if (!strcasecmp(iterator->name, "guest")) {
+ if (!strcasecmp(iterator->connection->jid->partial, connection)) {
+ jingle = iterator;
+ } else if (!strcasecmp(iterator->connection->name, connection)) {
+ jingle = iterator;
+ }
+ }
+ ASTOBJ_UNLOCK(iterator);
+
+ if (jingle)
+ break;
+ });
+
+ }
+ return jingle;
+}
+
+
+static void add_codec_to_answer(const struct jingle_pvt *p, int codec, iks *dcodecs)
+{
+ char *format = ast_getformatname(codec);
+
+ if (!strcasecmp("ulaw", format)) {
+ iks *payload_eg711u, *payload_pcmu;
+ payload_pcmu = iks_new("payload-type");
+ iks_insert_attrib(payload_pcmu, "id", "0");
+ iks_insert_attrib(payload_pcmu, "name", "PCMU");
+ payload_eg711u = iks_new("payload-type");
+ iks_insert_attrib(payload_eg711u, "id", "100");
+ iks_insert_attrib(payload_eg711u, "name", "EG711U");
+ iks_insert_node(dcodecs, payload_pcmu);
+ iks_insert_node(dcodecs, payload_eg711u);
+ }
+ if (!strcasecmp("alaw", format)) {
+ iks *payload_eg711a;
+ iks *payload_pcma = iks_new("payload-type");
+ iks_insert_attrib(payload_pcma, "id", "8");
+ iks_insert_attrib(payload_pcma, "name", "PCMA");
+ payload_eg711a = iks_new("payload-type");
+ iks_insert_attrib(payload_eg711a, "id", "101");
+ iks_insert_attrib(payload_eg711a, "name", "EG711A");
+ iks_insert_node(dcodecs, payload_pcma);
+ iks_insert_node(dcodecs, payload_eg711a);
+ }
+ if (!strcasecmp("ilbc", format)) {
+ iks *payload_ilbc = iks_new("payload-type");
+ iks_insert_attrib(payload_ilbc, "id", "97");
+ iks_insert_attrib(payload_ilbc, "name", "iLBC");
+ iks_insert_node(dcodecs, payload_ilbc);
+ }
+ if (!strcasecmp("g723", format)) {
+ iks *payload_g723 = iks_new("payload-type");
+ iks_insert_attrib(payload_g723, "id", "4");
+ iks_insert_attrib(payload_g723, "name", "G723");
+ iks_insert_node(dcodecs, payload_g723);
+ }
+ ast_rtp_lookup_code(p->rtp, 1, codec);
+}
+
+static int jingle_accept_call(struct jingle *client, struct jingle_pvt *p)
+{
+ struct jingle_pvt *tmp = client->p;
+ struct aji_client *c = client->connection;
+ iks *iq, *jingle, *dcodecs, *payload_red, *payload_audio, *payload_cn;
+ int x;
+ int pref_codec = 0;
+ int alreadysent = 0;
+
+ if (p->initiator)
+ return 1;
+
+ iq = iks_new("iq");
+ jingle = iks_new(JINGLE_NODE);
+ dcodecs = iks_new("description");
+ if (iq && jingle && dcodecs) {
+ iks_insert_attrib(dcodecs, "xmlns", JINGLE_AUDIO_RTP_NS);
+
+ for (x = 0; x < 32; x++) {
+ if (!(pref_codec = ast_codec_pref_index(&client->prefs, x)))
+ break;
+ if (!(client->capability & pref_codec))
+ continue;
+ if (alreadysent & pref_codec)
+ continue;
+ add_codec_to_answer(p, pref_codec, dcodecs);
+ alreadysent |= pref_codec;
+ }
+ payload_red = iks_new("payload-type");
+ iks_insert_attrib(payload_red, "id", "117");
+ iks_insert_attrib(payload_red, "name", "red");
+ payload_audio = iks_new("payload-type");
+ iks_insert_attrib(payload_audio, "id", "106");
+ iks_insert_attrib(payload_audio, "name", "audio/telephone-event");
+ payload_cn = iks_new("payload-type");
+ iks_insert_attrib(payload_cn, "id", "13");
+ iks_insert_attrib(payload_cn, "name", "CN");
+
+
+ iks_insert_attrib(iq, "type", "set");
+ iks_insert_attrib(iq, "to", (p->them) ? p->them : client->user);
+ iks_insert_attrib(iq, "id", client->connection->mid);
+ ast_aji_increment_mid(client->connection->mid);
+
+ iks_insert_attrib(jingle, "xmlns", JINGLE_NS);
+ iks_insert_attrib(jingle, "action", JINGLE_ACCEPT);
+ iks_insert_attrib(jingle, "initiator", p->initiator ? client->connection->jid->full : p->them);
+ iks_insert_attrib(jingle, JINGLE_SID, tmp->sid);
+ iks_insert_node(iq, jingle);
+ iks_insert_node(jingle, dcodecs);
+ iks_insert_node(dcodecs, payload_red);
+ iks_insert_node(dcodecs, payload_audio);
+ iks_insert_node(dcodecs, payload_cn);
+
+ ast_aji_send(c, iq);
+ iks_delete(payload_red);
+ iks_delete(payload_audio);
+ iks_delete(payload_cn);
+ iks_delete(dcodecs);
+ iks_delete(jingle);
+ iks_delete(iq);
+ }
+ return 1;
+}
+
+static int jingle_ringing_ack(void *data, ikspak *pak)
+{
+ struct jingle_pvt *p = data;
+
+ if (p->ringrule)
+ iks_filter_remove_rule(p->parent->connection->f, p->ringrule);
+ p->ringrule = NULL;
+ if (p->owner)
+ ast_queue_control(p->owner, AST_CONTROL_RINGING);
+ return IKS_FILTER_EAT;
+}
+
+static int jingle_answer(struct ast_channel *ast)
+{
+ struct jingle_pvt *p = ast->tech_pvt;
+ struct jingle *client = p->parent;
+ int res = 0;
+
+ ast_debug(1, "Answer!\n");
+ ast_mutex_lock(&p->lock);
+ jingle_accept_call(client, p);
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static enum ast_rtp_get_result jingle_get_rtp_peer(struct ast_channel *chan, struct ast_rtp **rtp)
+{
+ struct jingle_pvt *p = chan->tech_pvt;
+ enum ast_rtp_get_result res = AST_RTP_GET_FAILED;
+
+ if (!p)
+ return res;
+
+ ast_mutex_lock(&p->lock);
+ if (p->rtp) {
+ *rtp = p->rtp;
+ res = AST_RTP_TRY_PARTIAL;
+ }
+ ast_mutex_unlock(&p->lock);
+
+ return res;
+}
+
+static int jingle_get_codec(struct ast_channel *chan)
+{
+ struct jingle_pvt *p = chan->tech_pvt;
+ return p->peercapability;
+}
+
+static int jingle_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp, struct ast_rtp *vrtp, struct ast_rtp *tpeer, int codecs, int nat_active)
+{
+ struct jingle_pvt *p;
+
+ p = chan->tech_pvt;
+ if (!p)
+ return -1;
+ ast_mutex_lock(&p->lock);
+
+/* if (rtp)
+ ast_rtp_get_peer(rtp, &p->redirip);
+ else
+ memset(&p->redirip, 0, sizeof(p->redirip));
+ p->redircodecs = codecs; */
+
+ /* Reset lastrtprx timer */
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int jingle_response(struct jingle *client, ikspak *pak, const char *reasonstr, const char *reasonstr2)
+{
+ iks *response = NULL, *error = NULL, *reason = NULL;
+ int res = -1;
+
+ response = iks_new("iq");
+ if (response) {
+ iks_insert_attrib(response, "type", "result");
+ iks_insert_attrib(response, "from", client->connection->jid->full);
+ iks_insert_attrib(response, "to", iks_find_attrib(pak->x, "from"));
+ iks_insert_attrib(response, "id", iks_find_attrib(pak->x, "id"));
+ if (reasonstr) {
+ error = iks_new("error");
+ if (error) {
+ iks_insert_attrib(error, "type", "cancel");
+ reason = iks_new(reasonstr);
+ if (reason)
+ iks_insert_node(error, reason);
+ iks_insert_node(response, error);
+ }
+ }
+ ast_aji_send(client->connection, response);
+ if (reason)
+ iks_delete(reason);
+ if (error)
+ iks_delete(error);
+ iks_delete(response);
+ res = 0;
+ }
+ return res;
+}
+
+static int jingle_is_answered(struct jingle *client, ikspak *pak)
+{
+ struct jingle_pvt *tmp;
+
+ ast_debug(1, "The client is %s\n", client->name);
+ /* Make sure our new call doesn't exist yet */
+ for (tmp = client->p; tmp; tmp = tmp->next) {
+ if (iks_find_with_attrib(pak->x, JINGLE_NODE, JINGLE_SID, tmp->sid))
+ break;
+ }
+
+ if (tmp) {
+ if (tmp->owner)
+ ast_queue_control(tmp->owner, AST_CONTROL_ANSWER);
+ } else
+ ast_log(LOG_NOTICE, "Whoa, didn't find call!\n");
+ jingle_response(client, pak, NULL, NULL);
+ return 1;
+}
+
+static int jingle_handle_dtmf(struct jingle *client, ikspak *pak)
+{
+ struct jingle_pvt *tmp;
+ iks *dtmfnode = NULL, *dtmfchild = NULL;
+ char *dtmf;
+ /* Make sure our new call doesn't exist yet */
+ for (tmp = client->p; tmp; tmp = tmp->next) {
+ if (iks_find_with_attrib(pak->x, JINGLE_NODE, JINGLE_SID, tmp->sid))
+ break;
+ }
+
+ if (tmp) {
+ if(iks_find_with_attrib(pak->x, "dtmf-method", "method", "rtp")) {
+ jingle_response(client,pak,
+ "feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'",
+ "unsupported-dtmf-method xmlns='http://www.xmpp.org/extensions/xep-0181.html#ns-errors'");
+ return -1;
+ }
+ if ((dtmfnode = iks_find(pak->x, "dtmf"))) {
+ if((dtmf = iks_find_attrib(dtmfnode, "code"))) {
+ if(iks_find_with_attrib(pak->x, "dtmf", "action", "button-up")) {
+ struct ast_frame f = {AST_FRAME_DTMF_BEGIN, };
+ f.subclass = dtmf[0];
+ ast_queue_frame(tmp->owner, &f);
+ ast_verbose("JINGLE! DTMF-relay event received: %c\n", f.subclass);
+ } else if(iks_find_with_attrib(pak->x, "dtmf", "action", "button-down")) {
+ struct ast_frame f = {AST_FRAME_DTMF_END, };
+ f.subclass = dtmf[0];
+ ast_queue_frame(tmp->owner, &f);
+ ast_verbose("JINGLE! DTMF-relay event received: %c\n", f.subclass);
+ } else if(iks_find_attrib(pak->x, "dtmf")) { /* 250 millasecond default */
+ struct ast_frame f = {AST_FRAME_DTMF, };
+ f.subclass = dtmf[0];
+ ast_queue_frame(tmp->owner, &f);
+ ast_verbose("JINGLE! DTMF-relay event received: %c\n", f.subclass);
+ }
+ }
+ } else if ((dtmfnode = iks_find_with_attrib(pak->x, JINGLE_NODE, "action", "session-info"))) {
+ if((dtmfchild = iks_find(dtmfnode, "dtmf"))) {
+ if((dtmf = iks_find_attrib(dtmfchild, "code"))) {
+ if(iks_find_with_attrib(dtmfnode, "dtmf", "action", "button-up")) {
+ struct ast_frame f = {AST_FRAME_DTMF_END, };
+ f.subclass = dtmf[0];
+ ast_queue_frame(tmp->owner, &f);
+ ast_verbose("JINGLE! DTMF-relay event received: %c\n", f.subclass);
+ } else if(iks_find_with_attrib(dtmfnode, "dtmf", "action", "button-down")) {
+ struct ast_frame f = {AST_FRAME_DTMF_BEGIN, };
+ f.subclass = dtmf[0];
+ ast_queue_frame(tmp->owner, &f);
+ ast_verbose("JINGLE! DTMF-relay event received: %c\n", f.subclass);
+ }
+ }
+ }
+ }
+ jingle_response(client, pak, NULL, NULL);
+ return 1;
+ } else
+ ast_log(LOG_NOTICE, "Whoa, didn't find call!\n");
+
+ jingle_response(client, pak, NULL, NULL);
+ return 1;
+}
+
+
+static int jingle_hangup_farend(struct jingle *client, ikspak *pak)
+{
+ struct jingle_pvt *tmp;
+
+ ast_debug(1, "The client is %s\n", client->name);
+ /* Make sure our new call doesn't exist yet */
+ for (tmp = client->p; tmp; tmp = tmp->next) {
+ if (iks_find_with_attrib(pak->x, JINGLE_NODE, JINGLE_SID, tmp->sid))
+ break;
+ }
+
+ if (tmp) {
+ tmp->alreadygone = 1;
+ if (tmp->owner)
+ ast_queue_hangup(tmp->owner);
+ } else
+ ast_log(LOG_NOTICE, "Whoa, didn't find call!\n");
+ jingle_response(client, pak, NULL, NULL);
+ return 1;
+}
+
+static int jingle_create_candidates(struct jingle *client, struct jingle_pvt *p, char *sid, char *from)
+{
+ struct jingle_candidate *tmp;
+ struct aji_client *c = client->connection;
+ struct jingle_candidate *ours1 = NULL, *ours2 = NULL;
+ struct sockaddr_in sin;
+ struct sockaddr_in dest;
+ struct in_addr us;
+ struct in_addr externaddr;
+ iks *iq, *jingle, *content, *transport, *candidate;
+ char component[16], foundation[16], generation[16], network[16], pass[16], port[7], priority[16], user[16];
+
+
+ iq = iks_new("iq");
+ jingle = iks_new(JINGLE_NODE);
+ content = iks_new("content");
+ transport = iks_new("transport");
+ candidate = iks_new("candidate");
+ if (!iq || !jingle || !content || !transport || !candidate) {
+ ast_log(LOG_ERROR, "Memory allocation error\n");
+ goto safeout;
+ }
+ ours1 = ast_calloc(1, sizeof(*ours1));
+ ours2 = ast_calloc(1, sizeof(*ours2));
+ if (!ours1 || !ours2)
+ goto safeout;
+
+ iks_insert_node(iq, jingle);
+ iks_insert_node(jingle, content);
+ iks_insert_node(content, transport);
+ iks_insert_node(transport, candidate);
+
+ for (; p; p = p->next) {
+ if (!strcasecmp(p->sid, sid))
+ break;
+ }
+
+ if (!p) {
+ ast_log(LOG_NOTICE, "No matching jingle session - SID %s!\n", sid);
+ goto safeout;
+ }
+
+ ast_rtp_get_us(p->rtp, &sin);
+ ast_find_ourip(&us, bindaddr);
+
+ /* Setup our first jingle candidate */
+ ours1->component = 1;
+ ours1->foundation = (unsigned int)bindaddr.sin_addr.s_addr | AJI_CONNECT_HOST | AJI_PROTOCOL_UDP;
+ ours1->generation = 0;
+ ast_copy_string(ours1->ip, ast_inet_ntoa(us), sizeof(ours1->ip));
+ ours1->network = 0;
+ ours1->port = ntohs(sin.sin_port);
+ ours1->priority = 1678246398;
+ ours1->protocol = AJI_PROTOCOL_UDP;
+ snprintf(pass, sizeof(pass), "%08lx%08lx", ast_random(), ast_random());
+ ast_copy_string(ours1->password, pass, sizeof(ours1->password));
+ ours1->type = AJI_CONNECT_HOST;
+ snprintf(user, sizeof(user), "%08lx%08lx", ast_random(), ast_random());
+ ast_copy_string(ours1->ufrag, user, sizeof(ours1->ufrag));
+ p->ourcandidates = ours1;
+
+ if (!ast_strlen_zero(externip)) {
+ /* XXX We should really stun for this one not just go with externip XXX */
+ if (inet_aton(externip, &externaddr))
+ ast_log(LOG_WARNING, "Invalid extern IP : %s\n", externip);
+
+ ours2->component = 1;
+ ours2->foundation = (unsigned int)externaddr.s_addr | AJI_CONNECT_PRFLX | AJI_PROTOCOL_UDP;
+ ours2->generation = 0;
+ ast_copy_string(ours2->ip, externip, sizeof(ours2->ip));
+ ours2->network = 0;
+ ours2->port = ntohs(sin.sin_port);
+ ours2->priority = 1678246397;
+ ours2->protocol = AJI_PROTOCOL_UDP;
+ snprintf(pass, sizeof(pass), "%08lx%08lx", ast_random(), ast_random());
+ ast_copy_string(ours2->password, pass, sizeof(ours2->password));
+ ours2->type = AJI_CONNECT_PRFLX;
+
+ snprintf(user, sizeof(user), "%08lx%08lx", ast_random(), ast_random());
+ ast_copy_string(ours2->ufrag, user, sizeof(ours2->ufrag));
+ ours1->next = ours2;
+ ours2 = NULL;
+ }
+ ours1 = NULL;
+ dest.sin_addr = __ourip;
+ dest.sin_port = sin.sin_port;
+
+
+ for (tmp = p->ourcandidates; tmp; tmp = tmp->next) {
+ snprintf(component, sizeof(component), "%u", tmp->component);
+ snprintf(foundation, sizeof(foundation), "%u", tmp->foundation);
+ snprintf(generation, sizeof(generation), "%u", tmp->generation);
+ snprintf(network, sizeof(network), "%u", tmp->network);
+ snprintf(port, sizeof(port), "%u", tmp->port);
+ snprintf(priority, sizeof(priority), "%u", tmp->priority);
+
+ iks_insert_attrib(iq, "from", c->jid->full);
+ iks_insert_attrib(iq, "to", from);
+ iks_insert_attrib(iq, "type", "set");
+ iks_insert_attrib(iq, "id", c->mid);
+ ast_aji_increment_mid(c->mid);
+ iks_insert_attrib(jingle, "action", JINGLE_NEGOTIATE);
+ iks_insert_attrib(jingle, JINGLE_SID, sid);
+ iks_insert_attrib(jingle, "initiator", (p->initiator) ? c->jid->full : from);
+ iks_insert_attrib(jingle, "xmlns", JINGLE_NS);
+ iks_insert_attrib(content, "creator", p->initiator ? "initiator" : "responder");
+ iks_insert_attrib(content, "name", "asterisk-audio-content");
+ iks_insert_attrib(transport, "xmlns", JINGLE_ICE_UDP_NS);
+ iks_insert_attrib(candidate, "component", component);
+ iks_insert_attrib(candidate, "foundation", foundation);
+ iks_insert_attrib(candidate, "generation", generation);
+ iks_insert_attrib(candidate, "ip", tmp->ip);
+ iks_insert_attrib(candidate, "network", network);
+ iks_insert_attrib(candidate, "port", port);
+ iks_insert_attrib(candidate, "priority", priority);
+ switch (tmp->protocol) {
+ case AJI_PROTOCOL_UDP:
+ iks_insert_attrib(candidate, "protocol", "udp");
+ break;
+ case AJI_PROTOCOL_SSLTCP:
+ iks_insert_attrib(candidate, "protocol", "ssltcp");
+ break;
+ }
+ iks_insert_attrib(candidate, "pwd", tmp->password);
+ switch (tmp->type) {
+ case AJI_CONNECT_HOST:
+ iks_insert_attrib(candidate, "type", "host");
+ break;
+ case AJI_CONNECT_PRFLX:
+ iks_insert_attrib(candidate, "type", "prflx");
+ break;
+ case AJI_CONNECT_RELAY:
+ iks_insert_attrib(candidate, "type", "relay");
+ break;
+ case AJI_CONNECT_SRFLX:
+ iks_insert_attrib(candidate, "type", "srflx");
+ break;
+ }
+ iks_insert_attrib(candidate, "ufrag", tmp->ufrag);
+
+ ast_aji_send(c, iq);
+ }
+ p->laststun = 0;
+
+safeout:
+ if (ours1)
+ ast_free(ours1);
+ if (ours2)
+ ast_free(ours2);
+ if (iq)
+ iks_delete(iq);
+ if (jingle)
+ iks_delete(jingle);
+ if (content)
+ iks_delete(content);
+ if (transport)
+ iks_delete(transport);
+ if (candidate)
+ iks_delete(candidate);
+ return 1;
+}
+
+static struct jingle_pvt *jingle_alloc(struct jingle *client, const char *from, const char *sid)
+{
+ struct jingle_pvt *tmp = NULL;
+ struct aji_resource *resources = NULL;
+ struct aji_buddy *buddy;
+ char idroster[200];
+
+ ast_debug(1, "The client is %s for alloc\n", client->name);
+ if (!sid && !strchr(from, '/')) { /* I started call! */
+ if (!strcasecmp(client->name, "guest")) {
+ buddy = ASTOBJ_CONTAINER_FIND(&client->connection->buddies, from);
+ if (buddy)
+ resources = buddy->resources;
+ } else if (client->buddy)
+ resources = client->buddy->resources;
+ while (resources) {
+ if (resources->cap->jingle) {
+ break;
+ }
+ resources = resources->next;
+ }
+ if (resources)
+ snprintf(idroster, sizeof(idroster), "%s/%s", from, resources->resource);
+ else {
+ ast_log(LOG_ERROR, "no jingle capable clients to talk to.\n");
+ return NULL;
+ }
+ }
+ if (!(tmp = ast_calloc(1, sizeof(*tmp)))) {
+ return NULL;
+ }
+
+ memcpy(&tmp->prefs, &client->prefs, sizeof(tmp->prefs));
+
+ if (sid) {
+ ast_copy_string(tmp->sid, sid, sizeof(tmp->sid));
+ ast_copy_string(tmp->them, from, sizeof(tmp->them));
+ } else {
+ snprintf(tmp->sid, sizeof(tmp->sid), "%08lx%08lx", ast_random(), ast_random());
+ ast_copy_string(tmp->them, idroster, sizeof(tmp->them));
+ tmp->initiator = 1;
+ }
+ tmp->rtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
+ tmp->parent = client;
+ if (!tmp->rtp) {
+ ast_log(LOG_WARNING, "Out of RTP sessions?\n");
+ ast_free(tmp);
+ return NULL;
+ }
+ ast_copy_string(tmp->exten, "s", sizeof(tmp->exten));
+ ast_mutex_init(&tmp->lock);
+ ast_mutex_lock(&jinglelock);
+ tmp->next = client->p;
+ client->p = tmp;
+ ast_mutex_unlock(&jinglelock);
+ return tmp;
+}
+
+/*! \brief Start new jingle channel */
+static struct ast_channel *jingle_new(struct jingle *client, struct jingle_pvt *i, int state, const char *title)
+{
+ struct ast_channel *tmp;
+ int fmt;
+ int what;
+ const char *str;
+
+ if (title)
+ str = title;
+ else
+ str = i->them;
+ tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, "", "", "", 0, "Jingle/%s-%04lx", str, ast_random() & 0xffff);
+ if (!tmp) {
+ ast_log(LOG_WARNING, "Unable to allocate Jingle channel structure!\n");
+ return NULL;
+ }
+ tmp->tech = &jingle_tech;
+
+ /* Select our native format based on codec preference until we receive
+ something from another device to the contrary. */
+ if (i->jointcapability)
+ what = i->jointcapability;
+ else if (i->capability)
+ what = i->capability;
+ else
+ what = global_capability;
+
+ /* Set Frame packetization */
+ if (i->rtp)
+ ast_rtp_codec_setpref(i->rtp, &i->prefs);
+
+ tmp->nativeformats = ast_codec_choose(&i->prefs, what, 1) | (i->jointcapability & AST_FORMAT_VIDEO_MASK);
+ fmt = ast_best_codec(tmp->nativeformats);
+
+ if (i->rtp) {
+ ast_channel_set_fd(tmp, 0, ast_rtp_fd(i->rtp));
+ ast_channel_set_fd(tmp, 1, ast_rtcp_fd(i->rtp));
+ }
+ if (i->vrtp) {
+ ast_channel_set_fd(tmp, 2, ast_rtp_fd(i->vrtp));
+ ast_channel_set_fd(tmp, 3, ast_rtcp_fd(i->vrtp));
+ }
+ if (state == AST_STATE_RING)
+ tmp->rings = 1;
+ tmp->adsicpe = AST_ADSI_UNAVAILABLE;
+ tmp->writeformat = fmt;
+ tmp->rawwriteformat = fmt;
+ tmp->readformat = fmt;
+ tmp->rawreadformat = fmt;
+ tmp->tech_pvt = i;
+
+ tmp->callgroup = client->callgroup;
+ tmp->pickupgroup = client->pickupgroup;
+ tmp->cid.cid_pres = client->callingpres;
+ if (!ast_strlen_zero(client->accountcode))
+ ast_string_field_set(tmp, accountcode, client->accountcode);
+ if (client->amaflags)
+ tmp->amaflags = client->amaflags;
+ if (!ast_strlen_zero(client->language))
+ ast_string_field_set(tmp, language, client->language);
+ if (!ast_strlen_zero(client->musicclass))
+ ast_string_field_set(tmp, musicclass, client->musicclass);
+ i->owner = tmp;
+ ast_copy_string(tmp->context, client->context, sizeof(tmp->context));
+ ast_copy_string(tmp->exten, i->exten, sizeof(tmp->exten));
+ /* Don't use ast_set_callerid() here because it will
+ * generate an unnecessary NewCallerID event */
+ tmp->cid.cid_ani = ast_strdup(i->cid_num);
+ if (!ast_strlen_zero(i->exten) && strcmp(i->exten, "s"))
+ tmp->cid.cid_dnid = ast_strdup(i->exten);
+ tmp->priority = 1;
+ if (i->rtp)
+ ast_jb_configure(tmp, &global_jbconf);
+ if (state != AST_STATE_DOWN && ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ tmp->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ ast_hangup(tmp);
+ tmp = NULL;
+ }
+
+ return tmp;
+}
+
+static int jingle_action(struct jingle *client, struct jingle_pvt *p, const char *action)
+{
+ iks *iq, *jingle = NULL;
+ int res = -1;
+
+ iq = iks_new("iq");
+ jingle = iks_new("jingle");
+
+ if (iq) {
+ iks_insert_attrib(iq, "type", "set");
+ iks_insert_attrib(iq, "from", client->connection->jid->full);
+ iks_insert_attrib(iq, "to", p->them);
+ iks_insert_attrib(iq, "id", client->connection->mid);
+ ast_aji_increment_mid(client->connection->mid);
+ if (jingle) {
+ iks_insert_attrib(jingle, "action", action);
+ iks_insert_attrib(jingle, JINGLE_SID, p->sid);
+ iks_insert_attrib(jingle, "initiator", p->initiator ? client->connection->jid->full : p->them);
+ iks_insert_attrib(jingle, "xmlns", JINGLE_NS);
+
+ iks_insert_node(iq, jingle);
+
+ ast_aji_send(client->connection, iq);
+ iks_delete(jingle);
+ res = 0;
+ }
+ iks_delete(iq);
+ }
+ return res;
+}
+
+static void jingle_free_candidates(struct jingle_candidate *candidate)
+{
+ struct jingle_candidate *last;
+ while (candidate) {
+ last = candidate;
+ candidate = candidate->next;
+ ast_free(last);
+ }
+}
+
+static void jingle_free_pvt(struct jingle *client, struct jingle_pvt *p)
+{
+ struct jingle_pvt *cur, *prev = NULL;
+ cur = client->p;
+ while (cur) {
+ if (cur == p) {
+ if (prev)
+ prev->next = p->next;
+ else
+ client->p = p->next;
+ break;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+ if (p->ringrule)
+ iks_filter_remove_rule(p->parent->connection->f, p->ringrule);
+ if (p->owner)
+ ast_log(LOG_WARNING, "Uh oh, there's an owner, this is going to be messy.\n");
+ if (p->rtp)
+ ast_rtp_destroy(p->rtp);
+ if (p->vrtp)
+ ast_rtp_destroy(p->vrtp);
+ jingle_free_candidates(p->theircandidates);
+ ast_free(p);
+}
+
+
+static int jingle_newcall(struct jingle *client, ikspak *pak)
+{
+ struct jingle_pvt *p, *tmp = client->p;
+ struct ast_channel *chan;
+ int res;
+ iks *payload_type;
+
+ /* Make sure our new call doesn't exist yet */
+ while (tmp) {
+ if (iks_find_with_attrib(pak->x, JINGLE_NODE, JINGLE_SID, tmp->sid)) {
+ ast_log(LOG_NOTICE, "Ignoring duplicate call setup on SID %s\n", tmp->sid);
+ jingle_response(client, pak, "out-of-order", NULL);
+ return -1;
+ }
+ tmp = tmp->next;
+ }
+
+ p = jingle_alloc(client, pak->from->partial, iks_find_attrib(pak->query, JINGLE_SID));
+ if (!p) {
+ ast_log(LOG_WARNING, "Unable to allocate jingle structure!\n");
+ return -1;
+ }
+ chan = jingle_new(client, p, AST_STATE_DOWN, pak->from->user);
+ if (chan) {
+ ast_mutex_lock(&p->lock);
+ ast_copy_string(p->them, pak->from->full, sizeof(p->them));
+ if (iks_find_attrib(pak->query, JINGLE_SID)) {
+ ast_copy_string(p->sid, iks_find_attrib(pak->query, JINGLE_SID),
+ sizeof(p->sid));
+ }
+
+ payload_type = iks_child(iks_child(iks_child(iks_child(pak->x))));
+ while (payload_type) {
+ ast_rtp_set_m_type(p->rtp, atoi(iks_find_attrib(payload_type, "id")));
+ ast_rtp_set_rtpmap_type(p->rtp, atoi(iks_find_attrib(payload_type, "id")), "audio", iks_find_attrib(payload_type, "name"), 0);
+ payload_type = iks_next(payload_type);
+ }
+
+ ast_mutex_unlock(&p->lock);
+ ast_setstate(chan, AST_STATE_RING);
+ res = ast_pbx_start(chan);
+
+ switch (res) {
+ case AST_PBX_FAILED:
+ ast_log(LOG_WARNING, "Failed to start PBX :(\n");
+ jingle_response(client, pak, "service-unavailable", NULL);
+ break;
+ case AST_PBX_CALL_LIMIT:
+ ast_log(LOG_WARNING, "Failed to start PBX (call limit reached) \n");
+ jingle_response(client, pak, "service-unavailable", NULL);
+ break;
+ case AST_PBX_SUCCESS:
+ jingle_response(client, pak, NULL, NULL);
+ jingle_create_candidates(client, p,
+ iks_find_attrib(pak->query, JINGLE_SID),
+ iks_find_attrib(pak->x, "from"));
+ /* nothing to do */
+ break;
+ }
+ } else {
+ jingle_free_pvt(client, p);
+ }
+ return 1;
+}
+
+static int jingle_update_stun(struct jingle *client, struct jingle_pvt *p)
+{
+ struct jingle_candidate *tmp;
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ struct sockaddr_in sin;
+
+ if (time(NULL) == p->laststun)
+ return 0;
+
+ tmp = p->theircandidates;
+ p->laststun = time(NULL);
+ while (tmp) {
+ char username[256];
+ hp = ast_gethostbyname(tmp->ip, &ahp);
+ sin.sin_family = AF_INET;
+ memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+ sin.sin_port = htons(tmp->port);
+ snprintf(username, sizeof(username), "%s:%s", tmp->ufrag, p->ourcandidates->ufrag);
+
+ ast_rtp_stun_request(p->rtp, &sin, username);
+ tmp = tmp->next;
+ }
+ return 1;
+}
+
+static int jingle_add_candidate(struct jingle *client, ikspak *pak)
+{
+ struct jingle_pvt *p = NULL, *tmp = NULL;
+ struct aji_client *c = client->connection;
+ struct jingle_candidate *newcandidate = NULL;
+ iks *traversenodes = NULL, *receipt = NULL;
+
+ for (tmp = client->p; tmp; tmp = tmp->next) {
+ if (iks_find_with_attrib(pak->x, JINGLE_NODE, JINGLE_SID, tmp->sid)) {
+ p = tmp;
+ break;
+ }
+ }
+
+ if (!p)
+ return -1;
+
+ traversenodes = pak->query;
+ while(traversenodes) {
+ if(!strcasecmp(iks_name(traversenodes), "jingle")) {
+ traversenodes = iks_child(traversenodes);
+ continue;
+ }
+ if(!strcasecmp(iks_name(traversenodes), "content")) {
+ traversenodes = iks_child(traversenodes);
+ continue;
+ }
+ if(!strcasecmp(iks_name(traversenodes), "transport")) {
+ traversenodes = iks_child(traversenodes);
+ continue;
+ }
+
+ if(!strcasecmp(iks_name(traversenodes), "candidate")) {
+ newcandidate = ast_calloc(1, sizeof(*newcandidate));
+ if (!newcandidate)
+ return 0;
+ ast_copy_string(newcandidate->ip, iks_find_attrib(traversenodes, "ip"), sizeof(newcandidate->ip));
+ newcandidate->port = atoi(iks_find_attrib(traversenodes, "port"));
+ ast_copy_string(newcandidate->password, iks_find_attrib(traversenodes, "pwd"), sizeof(newcandidate->password));
+ if (!strcasecmp(iks_find_attrib(traversenodes, "protocol"), "udp"))
+ newcandidate->protocol = AJI_PROTOCOL_UDP;
+ else if (!strcasecmp(iks_find_attrib(traversenodes, "protocol"), "ssltcp"))
+ newcandidate->protocol = AJI_PROTOCOL_SSLTCP;
+
+ if (!strcasecmp(iks_find_attrib(traversenodes, "type"), "host"))
+ newcandidate->type = AJI_CONNECT_HOST;
+ else if (!strcasecmp(iks_find_attrib(traversenodes, "type"), "prflx"))
+ newcandidate->type = AJI_CONNECT_PRFLX;
+ else if (!strcasecmp(iks_find_attrib(traversenodes, "type"), "relay"))
+ newcandidate->type = AJI_CONNECT_RELAY;
+ else if (!strcasecmp(iks_find_attrib(traversenodes, "type"), "srflx"))
+ newcandidate->type = AJI_CONNECT_SRFLX;
+
+ newcandidate->network = atoi(iks_find_attrib(traversenodes, "network"));
+ newcandidate->generation = atoi(iks_find_attrib(traversenodes, "generation"));
+ newcandidate->next = NULL;
+
+ newcandidate->next = p->theircandidates;
+ p->theircandidates = newcandidate;
+ p->laststun = 0;
+ jingle_update_stun(p->parent, p);
+ newcandidate = NULL;
+ }
+ traversenodes = iks_next(traversenodes);
+ }
+
+ receipt = iks_new("iq");
+ iks_insert_attrib(receipt, "type", "result");
+ iks_insert_attrib(receipt, "from", c->jid->full);
+ iks_insert_attrib(receipt, "to", iks_find_attrib(pak->x, "from"));
+ iks_insert_attrib(receipt, "id", iks_find_attrib(pak->x, "id"));
+ ast_aji_send(c, receipt);
+ iks_delete(receipt);
+
+ return 1;
+}
+
+static struct ast_frame *jingle_rtp_read(struct ast_channel *ast, struct jingle_pvt *p)
+{
+ struct ast_frame *f;
+
+ if (!p->rtp)
+ return &ast_null_frame;
+ f = ast_rtp_read(p->rtp);
+ jingle_update_stun(p->parent, p);
+ if (p->owner) {
+ /* We already hold the channel lock */
+ if (f->frametype == AST_FRAME_VOICE) {
+ if (f->subclass != (p->owner->nativeformats & AST_FORMAT_AUDIO_MASK)) {
+ ast_debug(1, "Oooh, format changed to %d\n", f->subclass);
+ p->owner->nativeformats =
+ (p->owner->nativeformats & AST_FORMAT_VIDEO_MASK) | f->subclass;
+ ast_set_read_format(p->owner, p->owner->readformat);
+ ast_set_write_format(p->owner, p->owner->writeformat);
+ }
+/* if ((ast_test_flag(p, SIP_DTMF) == SIP_DTMF_INBAND) && p->vad) {
+ f = ast_dsp_process(p->owner, p->vad, f);
+ if (f && (f->frametype == AST_FRAME_DTMF))
+ ast_debug(1, "* Detected inband DTMF '%c'\n", f->subclass);
+ } */
+ }
+ }
+ return f;
+}
+
+static struct ast_frame *jingle_read(struct ast_channel *ast)
+{
+ struct ast_frame *fr;
+ struct jingle_pvt *p = ast->tech_pvt;
+
+ ast_mutex_lock(&p->lock);
+ fr = jingle_rtp_read(ast, p);
+ ast_mutex_unlock(&p->lock);
+ return fr;
+}
+
+/*! \brief Send frame to media channel (rtp) */
+static int jingle_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct jingle_pvt *p = ast->tech_pvt;
+ int res = 0;
+
+ switch (frame->frametype) {
+ case AST_FRAME_VOICE:
+ if (!(frame->subclass & ast->nativeformats)) {
+ ast_log(LOG_WARNING,
+ "Asked to transmit frame type %d, while native formats is %d (read/write = %d/%d)\n",
+ frame->subclass, ast->nativeformats, ast->readformat,
+ ast->writeformat);
+ return 0;
+ }
+ if (p) {
+ ast_mutex_lock(&p->lock);
+ if (p->rtp) {
+ res = ast_rtp_write(p->rtp, frame);
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ break;
+ case AST_FRAME_VIDEO:
+ if (p) {
+ ast_mutex_lock(&p->lock);
+ if (p->vrtp) {
+ res = ast_rtp_write(p->vrtp, frame);
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ break;
+ case AST_FRAME_IMAGE:
+ return 0;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Can't send %d type frames with Jingle write\n",
+ frame->frametype);
+ return 0;
+ }
+
+ return res;
+}
+
+static int jingle_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct jingle_pvt *p = newchan->tech_pvt;
+ ast_mutex_lock(&p->lock);
+
+ if ((p->owner != oldchan)) {
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ if (p->owner == oldchan)
+ p->owner = newchan;
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int jingle_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+ int res = 0;
+
+ switch (condition) {
+ case AST_CONTROL_HOLD:
+ ast_moh_start(ast, data, NULL);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_moh_stop(ast);
+ break;
+ default:
+ ast_log(LOG_NOTICE, "Don't know how to indicate condition '%d'\n", condition);
+ res = -1;
+ }
+
+ return res;
+}
+
+static int jingle_digit(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct jingle_pvt *p = ast->tech_pvt;
+ struct jingle *client = p->parent;
+ iks *iq, *jingle, *dtmf;
+ char buffer[2] = {digit, '\0'};
+ iq = iks_new("iq");
+ jingle = iks_new("jingle");
+ dtmf = iks_new("dtmf");
+ if(!iq || !jingle || !dtmf) {
+ if(iq)
+ iks_delete(iq);
+ if(jingle)
+ iks_delete(jingle);
+ if(dtmf)
+ iks_delete(dtmf);
+ ast_log(LOG_ERROR, "Did not send dtmf do to memory issue\n");
+ return -1;
+ }
+
+ iks_insert_attrib(iq, "type", "set");
+ iks_insert_attrib(iq, "to", p->them);
+ iks_insert_attrib(iq, "from", client->connection->jid->full);
+ iks_insert_attrib(iq, "id", client->connection->mid);
+ ast_aji_increment_mid(client->connection->mid);
+ iks_insert_attrib(jingle, "xmlns", JINGLE_NS);
+ iks_insert_attrib(jingle, "action", "session-info");
+ iks_insert_attrib(jingle, "initiator", p->initiator ? client->connection->jid->full : p->them);
+ iks_insert_attrib(jingle, "sid", p->sid);
+ iks_insert_attrib(dtmf, "xmlns", JINGLE_DTMF_NS);
+ iks_insert_attrib(dtmf, "code", buffer);
+ iks_insert_node(iq, jingle);
+ iks_insert_node(jingle, dtmf);
+
+ ast_mutex_lock(&p->lock);
+ if (ast->dtmff.frametype == AST_FRAME_DTMF_BEGIN || duration == 0) {
+ iks_insert_attrib(dtmf, "action", "button-down");
+ } else if (ast->dtmff.frametype == AST_FRAME_DTMF_END || duration != 0) {
+ iks_insert_attrib(dtmf, "action", "button-up");
+ }
+ ast_aji_send(client->connection, iq);
+ iks_delete(iq);
+ iks_delete(jingle);
+ iks_delete(dtmf);
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int jingle_digit_begin(struct ast_channel *chan, char digit)
+{
+ return jingle_digit(chan, digit, 0);
+}
+
+static int jingle_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ return jingle_digit(ast, digit, duration);
+}
+
+static int jingle_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
+{
+ ast_log(LOG_NOTICE, "XXX Implement jingle sendhtml XXX\n");
+
+ return -1;
+}
+static int jingle_transmit_invite(struct jingle_pvt *p)
+{
+ struct jingle *aux = NULL;
+ struct aji_client *client = NULL;
+ iks *iq, *jingle, *content, *description, *transport;
+ iks *payload_eg711u, *payload_pcmu;
+
+ aux = p->parent;
+ client = aux->connection;
+ iq = iks_new("iq");
+ jingle = iks_new(JINGLE_NODE);
+ content = iks_new("content");
+ description = iks_new("description");
+ transport = iks_new("transport");
+ payload_pcmu = iks_new("payload-type");
+ payload_eg711u = iks_new("payload-type");
+
+ iks_insert_attrib(iq, "type", "set");
+ iks_insert_attrib(iq, "to", p->them);
+ iks_insert_attrib(iq, "from", client->jid->full);
+ iks_insert_attrib(iq, "id", client->mid);
+ ast_aji_increment_mid(client->mid);
+ iks_insert_attrib(jingle, "action", JINGLE_INITIATE);
+ iks_insert_attrib(jingle, JINGLE_SID, p->sid);
+ iks_insert_attrib(jingle, "initiator", client->jid->full);
+ iks_insert_attrib(jingle, "xmlns", JINGLE_NS);
+ iks_insert_attrib(content, "creator", "initiator");
+ iks_insert_attrib(content, "name", "asterisk-audio-content");
+ iks_insert_attrib(content, "profile", "RTP/AVP");
+ iks_insert_attrib(description, "xmlns", JINGLE_AUDIO_RTP_NS);
+ iks_insert_attrib(transport, "xmlns", JINGLE_ICE_UDP_NS);
+ iks_insert_attrib(payload_pcmu, "id", "0");
+ iks_insert_attrib(payload_pcmu, "name", "PCMU");
+ iks_insert_attrib(payload_eg711u, "id", "100");
+ iks_insert_attrib(payload_eg711u, "name", "EG711U");
+
+ iks_insert_node(description, payload_pcmu);
+ iks_insert_node(description, payload_eg711u);
+ iks_insert_node(content, description);
+ iks_insert_node(content, transport);
+ iks_insert_node(jingle, content);
+ iks_insert_node(iq, jingle);
+
+ ast_aji_send(client, iq);
+
+ iks_delete(iq);
+ iks_delete(jingle);
+ iks_delete(content);
+ iks_delete(description);
+ iks_delete(transport);
+ iks_delete(payload_eg711u);
+ iks_delete(payload_pcmu);
+ return 0;
+}
+
+/* Not in use right now.
+static int jingle_auto_congest(void *nothing)
+{
+ struct jingle_pvt *p = nothing;
+
+ ast_mutex_lock(&p->lock);
+ if (p->owner) {
+ if (!ast_channel_trylock(p->owner)) {
+ ast_log(LOG_NOTICE, "Auto-congesting %s\n", p->owner->name);
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ ast_channel_unlock(p->owner);
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+*/
+
+/*! \brief Initiate new call, part of PBX interface
+ * dest is the dial string */
+static int jingle_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ struct jingle_pvt *p = ast->tech_pvt;
+
+ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "jingle_call called on %s, neither down nor reserved\n", ast->name);
+ return -1;
+ }
+
+ ast_setstate(ast, AST_STATE_RING);
+ p->jointcapability = p->capability;
+ if (!p->ringrule) {
+ ast_copy_string(p->ring, p->parent->connection->mid, sizeof(p->ring));
+ p->ringrule = iks_filter_add_rule(p->parent->connection->f, jingle_ringing_ack, p,
+ IKS_RULE_ID, p->ring, IKS_RULE_DONE);
+ } else
+ ast_log(LOG_WARNING, "Whoa, already have a ring rule!\n");
+
+ jingle_transmit_invite(p);
+ jingle_create_candidates(p->parent, p, p->sid, p->them);
+
+ return 0;
+}
+
+/*! \brief Hangup a call through the jingle proxy channel */
+static int jingle_hangup(struct ast_channel *ast)
+{
+ struct jingle_pvt *p = ast->tech_pvt;
+ struct jingle *client;
+
+ ast_mutex_lock(&p->lock);
+ client = p->parent;
+ p->owner = NULL;
+ ast->tech_pvt = NULL;
+ if (!p->alreadygone)
+ jingle_action(client, p, JINGLE_TERMINATE);
+ ast_mutex_unlock(&p->lock);
+
+ jingle_free_pvt(client, p);
+
+ return 0;
+}
+
+/*! \brief Part of PBX interface */
+static struct ast_channel *jingle_request(const char *type, int format, void *data, int *cause)
+{
+ struct jingle_pvt *p = NULL;
+ struct jingle *client = NULL;
+ char *sender = NULL, *to = NULL, *s = NULL;
+ struct ast_channel *chan = NULL;
+
+ if (data) {
+ s = ast_strdupa(data);
+ if (s) {
+ sender = strsep(&s, "/");
+ if (sender && (sender[0] != '\0'))
+ to = strsep(&s, "/");
+ if (!to) {
+ ast_log(LOG_ERROR, "Bad arguments in Jingle Dialstring: %s\n", (char*) data);
+ return NULL;
+ }
+ }
+ }
+ client = find_jingle(to, sender);
+ if (!client) {
+ ast_log(LOG_WARNING, "Could not find recipient.\n");
+ return NULL;
+ }
+ ASTOBJ_WRLOCK(client);
+ p = jingle_alloc(client, to, NULL);
+ if (p)
+ chan = jingle_new(client, p, AST_STATE_DOWN, to);
+ ASTOBJ_UNLOCK(client);
+
+ return chan;
+}
+
+/*! \brief CLI command "jingle show channels" */
+static char *jingle_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT "%-30.30s %-30.30s %-15.15s %-5.5s %-5.5s \n"
+ struct jingle_pvt *p;
+ struct ast_channel *chan;
+ int numchans = 0;
+ char them[AJI_MAX_JIDLEN];
+ char *jid = NULL;
+ char *resource = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "jingle show channels";
+ e->usage =
+ "Usage: jingle show channels\n"
+ " Shows current state of the Jingle channels.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&jinglelock);
+ ast_cli(a->fd, FORMAT, "Channel", "Jabber ID", "Resource", "Read", "Write");
+ ASTOBJ_CONTAINER_TRAVERSE(&jingle_list, 1, {
+ ASTOBJ_WRLOCK(iterator);
+ p = iterator->p;
+ while(p) {
+ chan = p->owner;
+ ast_copy_string(them, p->them, sizeof(them));
+ jid = them;
+ resource = strchr(them, '/');
+ if (!resource)
+ resource = "None";
+ else {
+ *resource = '\0';
+ resource ++;
+ }
+ if (chan)
+ ast_cli(a->fd, FORMAT,
+ chan->name,
+ jid,
+ resource,
+ ast_getformatname(chan->readformat),
+ ast_getformatname(chan->writeformat)
+ );
+ else
+ ast_log(LOG_WARNING, "No available channel\n");
+ numchans ++;
+ p = p->next;
+ }
+ ASTOBJ_UNLOCK(iterator);
+ });
+
+ ast_mutex_unlock(&jinglelock);
+
+ ast_cli(a->fd, "%d active jingle channel%s\n", numchans, (numchans != 1) ? "s" : "");
+ return CLI_SUCCESS;
+#undef FORMAT
+}
+
+/*! \brief CLI command "jingle reload" */
+static char *jingle_do_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "jingle reload";
+ e->usage =
+ "Usage: jingle reload\n"
+ " Reload jingle channel driver.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ return CLI_SUCCESS;
+}
+
+static int jingle_parser(void *data, ikspak *pak)
+{
+ struct jingle *client = ASTOBJ_REF((struct jingle *) data);
+ ast_log(LOG_NOTICE, "Filter matched\n");
+
+ if (iks_find_with_attrib(pak->x, JINGLE_NODE, "action", JINGLE_INITIATE)) {
+ /* New call */
+ jingle_newcall(client, pak);
+ } else if (iks_find_with_attrib(pak->x, JINGLE_NODE, "action", JINGLE_NEGOTIATE)) {
+ ast_debug(3, "About to add candidate!\n");
+ jingle_add_candidate(client, pak);
+ ast_debug(3, "Candidate Added!\n");
+ } else if (iks_find_with_attrib(pak->x, JINGLE_NODE, "action", JINGLE_ACCEPT)) {
+ jingle_is_answered(client, pak);
+ } else if (iks_find_with_attrib(pak->x, JINGLE_NODE, "action", JINGLE_INFO)) {
+ jingle_handle_dtmf(client, pak);
+ } else if (iks_find_with_attrib(pak->x, JINGLE_NODE, "action", JINGLE_TERMINATE)) {
+ jingle_hangup_farend(client, pak);
+ } else if (iks_find_with_attrib(pak->x, JINGLE_NODE, "action", "reject")) {
+ jingle_hangup_farend(client, pak);
+ }
+ ASTOBJ_UNREF(client, jingle_member_destroy);
+ return IKS_FILTER_EAT;
+}
+/* Not using this anymore probably take out soon
+static struct jingle_candidate *jingle_create_candidate(char *args)
+{
+ char *name, *type, *preference, *protocol;
+ struct jingle_candidate *res;
+ res = ast_calloc(1, sizeof(*res));
+ if (args)
+ name = args;
+ if ((args = strchr(args, ','))) {
+ *args = '\0';
+ args++;
+ preference = args;
+ }
+ if ((args = strchr(args, ','))) {
+ *args = '\0';
+ args++;
+ protocol = args;
+ }
+ if ((args = strchr(args, ','))) {
+ *args = '\0';
+ args++;
+ type = args;
+ }
+ if (name)
+ ast_copy_string(res->name, name, sizeof(res->name));
+ if (preference) {
+ res->preference = atof(preference);
+ }
+ if (protocol) {
+ if (!strcasecmp("udp", protocol))
+ res->protocol = AJI_PROTOCOL_UDP;
+ if (!strcasecmp("ssltcp", protocol))
+ res->protocol = AJI_PROTOCOL_SSLTCP;
+ }
+ if (type) {
+ if (!strcasecmp("host", type))
+ res->type = AJI_CONNECT_HOST;
+ if (!strcasecmp("prflx", type))
+ res->type = AJI_CONNECT_PRFLX;
+ if (!strcasecmp("relay", type))
+ res->type = AJI_CONNECT_RELAY;
+ if (!strcasecmp("srflx", type))
+ res->type = AJI_CONNECT_SRFLX;
+ }
+
+ return res;
+}
+*/
+
+static int jingle_create_member(char *label, struct ast_variable *var, int allowguest,
+ struct ast_codec_pref prefs, char *context,
+ struct jingle *member)
+{
+ struct aji_client *client;
+
+ if (!member)
+ ast_log(LOG_WARNING, "Out of memory.\n");
+
+ ast_copy_string(member->name, label, sizeof(member->name));
+ ast_copy_string(member->user, label, sizeof(member->user));
+ ast_copy_string(member->context, context, sizeof(member->context));
+ member->allowguest = allowguest;
+ member->prefs = prefs;
+ while (var) {
+#if 0
+ struct jingle_candidate *candidate = NULL;
+#endif
+ if (!strcasecmp(var->name, "username"))
+ ast_copy_string(member->user, var->value, sizeof(member->user));
+ else if (!strcasecmp(var->name, "disallow"))
+ ast_parse_allow_disallow(&member->prefs, &member->capability, var->value, 0);
+ else if (!strcasecmp(var->name, "allow"))
+ ast_parse_allow_disallow(&member->prefs, &member->capability, var->value, 1);
+ else if (!strcasecmp(var->name, "context"))
+ ast_copy_string(member->context, var->value, sizeof(member->context));
+#if 0
+ else if (!strcasecmp(var->name, "candidate")) {
+ candidate = jingle_create_candidate(var->value);
+ if (candidate) {
+ candidate->next = member->ourcandidates;
+ member->ourcandidates = candidate;
+ }
+ }
+#endif
+ else if (!strcasecmp(var->name, "connection")) {
+ if ((client = ast_aji_get_client(var->value))) {
+ member->connection = client;
+ iks_filter_add_rule(client->f, jingle_parser, member,
+ IKS_RULE_TYPE, IKS_PAK_IQ,
+ IKS_RULE_FROM_PARTIAL, member->user,
+ IKS_RULE_NS, JINGLE_NS,
+ IKS_RULE_DONE);
+ } else {
+ ast_log(LOG_ERROR, "connection referenced not found!\n");
+ return 0;
+ }
+ }
+ var = var->next;
+ }
+ if (member->connection && member->user)
+ member->buddy = ASTOBJ_CONTAINER_FIND(&member->connection->buddies, member->user);
+ else {
+ ast_log(LOG_ERROR, "No Connection or Username!\n");
+ }
+ return 1;
+}
+
+static int jingle_load_config(void)
+{
+ char *cat = NULL;
+ struct ast_config *cfg = NULL;
+ char context[100];
+ int allowguest = 1;
+ struct ast_variable *var;
+ struct jingle *member;
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ struct ast_codec_pref prefs;
+ struct aji_client_container *clients;
+ struct jingle_candidate *global_candidates = NULL;
+ struct ast_flags config_flags = { 0 };
+
+ cfg = ast_config_load(JINGLE_CONFIG, config_flags);
+ if (!cfg)
+ return 0;
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ cat = ast_category_browse(cfg, NULL);
+ for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, var->name, var->value))
+ continue;
+
+ if (!strcasecmp(var->name, "allowguest"))
+ allowguest =
+ (ast_true(ast_variable_retrieve(cfg, "general", "allowguest"))) ? 1 : 0;
+ else if (!strcasecmp(var->name, "disallow"))
+ ast_parse_allow_disallow(&prefs, &global_capability, var->value, 0);
+ else if (!strcasecmp(var->name, "allow"))
+ ast_parse_allow_disallow(&prefs, &global_capability, var->value, 1);
+ else if (!strcasecmp(var->name, "context"))
+ ast_copy_string(context, var->value, sizeof(context));
+ else if (!strcasecmp(var->name, "externip"))
+ ast_copy_string(externip, var->value, sizeof(externip));
+ else if (!strcasecmp(var->name, "bindaddr")) {
+ if (!(hp = ast_gethostbyname(var->value, &ahp))) {
+ ast_log(LOG_WARNING, "Invalid address: %s\n", var->value);
+ } else {
+ memcpy(&bindaddr.sin_addr, hp->h_addr, sizeof(bindaddr.sin_addr));
+ }
+ }
+/* Idea to allow for custom candidates */
+/*
+ else if (!strcasecmp(var->name, "candidate")) {
+ candidate = jingle_create_candidate(var->value);
+ if (candidate) {
+ candidate->next = global_candidates;
+ global_candidates = candidate;
+ }
+ }
+*/
+ }
+ while (cat) {
+ if (strcasecmp(cat, "general")) {
+ var = ast_variable_browse(cfg, cat);
+ member = ast_calloc(1, sizeof(*member));
+ ASTOBJ_INIT(member);
+ ASTOBJ_WRLOCK(member);
+ if (!strcasecmp(cat, "guest")) {
+ ast_copy_string(member->name, "guest", sizeof(member->name));
+ ast_copy_string(member->user, "guest", sizeof(member->user));
+ ast_copy_string(member->context, context, sizeof(member->context));
+ member->allowguest = allowguest;
+ member->prefs = prefs;
+ while (var) {
+ if (!strcasecmp(var->name, "disallow"))
+ ast_parse_allow_disallow(&member->prefs, &member->capability,
+ var->value, 0);
+ else if (!strcasecmp(var->name, "allow"))
+ ast_parse_allow_disallow(&member->prefs, &member->capability,
+ var->value, 1);
+ else if (!strcasecmp(var->name, "context"))
+ ast_copy_string(member->context, var->value,
+ sizeof(member->context));
+/* Idea to allow for custom candidates */
+/*
+ else if (!strcasecmp(var->name, "candidate")) {
+ candidate = jingle_create_candidate(var->value);
+ if (candidate) {
+ candidate->next = member->ourcandidates;
+ member->ourcandidates = candidate;
+ }
+ }
+*/
+ var = var->next;
+ }
+ ASTOBJ_UNLOCK(member);
+ clients = ast_aji_get_clients();
+ if (clients) {
+ ASTOBJ_CONTAINER_TRAVERSE(clients, 1, {
+ ASTOBJ_WRLOCK(iterator);
+ ASTOBJ_WRLOCK(member);
+ member->connection = iterator;
+ iks_filter_add_rule(iterator->f, jingle_parser, member, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_NS, JINGLE_NS, IKS_RULE_DONE);
+ iks_filter_add_rule(iterator->f, jingle_parser, member, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_NS, JINGLE_DTMF_NS, IKS_RULE_DONE);
+ ASTOBJ_UNLOCK(member);
+ ASTOBJ_CONTAINER_LINK(&jingle_list, member);
+ ASTOBJ_UNLOCK(iterator);
+ });
+ } else {
+ ASTOBJ_UNLOCK(member);
+ ASTOBJ_UNREF(member, jingle_member_destroy);
+ }
+ } else {
+ ASTOBJ_UNLOCK(member);
+ if (jingle_create_member(cat, var, allowguest, prefs, context, member))
+ ASTOBJ_CONTAINER_LINK(&jingle_list, member);
+ ASTOBJ_UNREF(member, jingle_member_destroy);
+ }
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+ jingle_free_candidates(global_candidates);
+ return 1;
+}
+
+/*! \brief Load module into PBX, register channel */
+static int load_module(void)
+{
+ ASTOBJ_CONTAINER_INIT(&jingle_list);
+ if (!jingle_load_config()) {
+ ast_log(LOG_ERROR, "Unable to read config file %s. Not loading module.\n", JINGLE_CONFIG);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ sched = sched_context_create();
+ if (!sched)
+ ast_log(LOG_WARNING, "Unable to create schedule context\n");
+
+ io = io_context_create();
+ if (!io)
+ ast_log(LOG_WARNING, "Unable to create I/O context\n");
+
+ if (ast_find_ourip(&__ourip, bindaddr)) {
+ ast_log(LOG_WARNING, "Unable to get own IP address, Jingle disabled\n");
+ return 0;
+ }
+
+ ast_rtp_proto_register(&jingle_rtp);
+ ast_cli_register_multiple(jingle_cli, sizeof(jingle_cli) / sizeof(jingle_cli[0]));
+ /* Make sure we can register our channel type */
+ if (ast_channel_register(&jingle_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class %s\n", type);
+ return -1;
+ }
+ return 0;
+}
+
+/*! \brief Reload module */
+static int reload(void)
+{
+ return 0;
+}
+
+/*! \brief Unload the jingle channel from Asterisk */
+static int unload_module(void)
+{
+ struct jingle_pvt *privates = NULL;
+ ast_cli_unregister_multiple(jingle_cli, sizeof(jingle_cli) / sizeof(jingle_cli[0]));
+ /* First, take us out of the channel loop */
+ ast_channel_unregister(&jingle_tech);
+ ast_rtp_proto_unregister(&jingle_rtp);
+
+ if (!ast_mutex_lock(&jinglelock)) {
+ /* Hangup all interfaces if they have an owner */
+ ASTOBJ_CONTAINER_TRAVERSE(&jingle_list, 1, {
+ ASTOBJ_WRLOCK(iterator);
+ privates = iterator->p;
+ while(privates) {
+ if (privates->owner)
+ ast_softhangup(privates->owner, AST_SOFTHANGUP_APPUNLOAD);
+ privates = privates->next;
+ }
+ iterator->p = NULL;
+ ASTOBJ_UNLOCK(iterator);
+ });
+ ast_mutex_unlock(&jinglelock);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the monitor\n");
+ return -1;
+ }
+ ASTOBJ_CONTAINER_DESTROYALL(&jingle_list, jingle_member_destroy);
+ ASTOBJ_CONTAINER_DESTROY(&jingle_list);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Jingle Channel Driver",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/trunk/channels/chan_local.c b/trunk/channels/chan_local.c
new file mode 100644
index 000000000..e42368e32
--- /dev/null
+++ b/trunk/channels/chan_local.c
@@ -0,0 +1,761 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \brief Local Proxy Channel
+ *
+ * \ingroup channel_drivers
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <fcntl.h>
+#include <sys/signal.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/rtp.h"
+#include "asterisk/acl.h"
+#include "asterisk/callerid.h"
+#include "asterisk/file.h"
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/manager.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/devicestate.h"
+
+static const char tdesc[] = "Local Proxy Channel Driver";
+
+#define IS_OUTBOUND(a,b) (a == b->chan ? 1 : 0)
+
+static struct ast_jb_conf g_jb_conf = {
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = "",
+};
+
+static struct ast_channel *local_request(const char *type, int format, void *data, int *cause);
+static int local_digit_begin(struct ast_channel *ast, char digit);
+static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int local_call(struct ast_channel *ast, char *dest, int timeout);
+static int local_hangup(struct ast_channel *ast);
+static int local_answer(struct ast_channel *ast);
+static struct ast_frame *local_read(struct ast_channel *ast);
+static int local_write(struct ast_channel *ast, struct ast_frame *f);
+static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
+static int local_sendtext(struct ast_channel *ast, const char *text);
+static int local_devicestate(void *data);
+
+/* PBX interface structure for channel registration */
+static const struct ast_channel_tech local_tech = {
+ .type = "Local",
+ .description = tdesc,
+ .capabilities = -1,
+ .requester = local_request,
+ .send_digit_begin = local_digit_begin,
+ .send_digit_end = local_digit_end,
+ .call = local_call,
+ .hangup = local_hangup,
+ .answer = local_answer,
+ .read = local_read,
+ .write = local_write,
+ .write_video = local_write,
+ .exception = local_read,
+ .indicate = local_indicate,
+ .fixup = local_fixup,
+ .send_html = local_sendhtml,
+ .send_text = local_sendtext,
+ .devicestate = local_devicestate,
+};
+
+struct local_pvt {
+ ast_mutex_t lock; /* Channel private lock */
+ unsigned int flags; /* Private flags */
+ char context[AST_MAX_CONTEXT]; /* Context to call */
+ char exten[AST_MAX_EXTENSION]; /* Extension to call */
+ int reqformat; /* Requested format */
+ struct ast_jb_conf jb_conf; /*!< jitterbuffer configuration for this local channel */
+ struct ast_channel *owner; /* Master Channel */
+ struct ast_channel *chan; /* Outbound channel */
+ struct ast_module_user *u_owner; /*! reference to keep the module loaded while in use */
+ struct ast_module_user *u_chan; /*! reference to keep the module loaded while in use */
+ AST_LIST_ENTRY(local_pvt) list; /* Next entity */
+};
+
+#define LOCAL_GLARE_DETECT (1 << 0) /*!< Detect glare on hangup */
+#define LOCAL_CANCEL_QUEUE (1 << 1) /*!< Cancel queue */
+#define LOCAL_ALREADY_MASQED (1 << 2) /*!< Already masqueraded */
+#define LOCAL_LAUNCHED_PBX (1 << 3) /*!< PBX was launched */
+#define LOCAL_NO_OPTIMIZATION (1 << 4) /*!< Do not optimize using masquerading */
+
+static AST_LIST_HEAD_STATIC(locals, local_pvt);
+
+/*! \brief Adds devicestate to local channels */
+static int local_devicestate(void *data)
+{
+ char *exten = ast_strdupa(data);
+ char *context = NULL, *opts = NULL;
+ int res;
+ struct local_pvt *lp;
+
+ if (!(context = strchr(exten, '@'))) {
+ ast_log(LOG_WARNING, "Someone used Local/%s somewhere without a @context. This is bad.\n", exten);
+ return AST_DEVICE_INVALID;
+ }
+
+ *context++ = '\0';
+
+ /* Strip options if they exist */
+ if ((opts = strchr(context, '/')))
+ *opts = '\0';
+
+ ast_debug(3, "Checking if extension %s@%s exists (devicestate)\n", exten, context);
+
+ res = ast_exists_extension(NULL, context, exten, 1, NULL);
+ if (!res)
+ return AST_DEVICE_INVALID;
+
+ res = AST_DEVICE_NOT_INUSE;
+ AST_LIST_LOCK(&locals);
+ AST_LIST_TRAVERSE(&locals, lp, list) {
+ if (!strcmp(exten, lp->exten) && !strcmp(context, lp->context) && lp->owner) {
+ res = AST_DEVICE_INUSE;
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&locals);
+
+ return res;
+}
+
+static int local_queue_frame(struct local_pvt *p, int isoutbound, struct ast_frame *f, struct ast_channel *us)
+{
+ struct ast_channel *other = NULL;
+
+retrylock:
+
+ /* Recalculate outbound channel */
+ other = isoutbound ? p->owner : p->chan;
+
+ /* Set glare detection */
+ ast_set_flag(p, LOCAL_GLARE_DETECT);
+ if (ast_test_flag(p, LOCAL_CANCEL_QUEUE)) {
+ /* We had a glare on the hangup. Forget all this business,
+ return and destroy p. */
+ ast_mutex_unlock(&p->lock);
+ ast_mutex_destroy(&p->lock);
+ ast_free(p);
+ return -1;
+ }
+ if (!other) {
+ ast_clear_flag(p, LOCAL_GLARE_DETECT);
+ return 0;
+ }
+ if (ast_channel_trylock(other)) {
+ /* Failed to lock. Release main lock and try again */
+ ast_mutex_unlock(&p->lock);
+ if (us) {
+ if (ast_channel_unlock(us)) {
+ ast_log(LOG_WARNING, "%s wasn't locked while sending %d/%d\n",
+ us->name, f->frametype, f->subclass);
+ us = NULL;
+ }
+ }
+ /* Wait just a bit */
+ usleep(1);
+ /* Only we can destroy ourselves, so we can't disappear here */
+ if (us)
+ ast_channel_lock(us);
+ ast_mutex_lock(&p->lock);
+ goto retrylock;
+ }
+ ast_queue_frame(other, f);
+ ast_channel_unlock(other);
+ ast_clear_flag(p, LOCAL_GLARE_DETECT);
+ return 0;
+}
+
+static int local_answer(struct ast_channel *ast)
+{
+ struct local_pvt *p = ast->tech_pvt;
+ int isoutbound;
+ int res = -1;
+
+ if (!p)
+ return -1;
+
+ ast_mutex_lock(&p->lock);
+ isoutbound = IS_OUTBOUND(ast, p);
+ if (isoutbound) {
+ /* Pass along answer since somebody answered us */
+ struct ast_frame answer = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
+ res = local_queue_frame(p, isoutbound, &answer, ast);
+ } else
+ ast_log(LOG_WARNING, "Huh? Local is being asked to answer?\n");
+ if (!res)
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static void check_bridge(struct local_pvt *p, int isoutbound)
+{
+ if (ast_test_flag(p, LOCAL_ALREADY_MASQED) || ast_test_flag(p, LOCAL_NO_OPTIMIZATION) || !p->chan || !p->owner || (p->chan->_bridge != ast_bridged_channel(p->chan)))
+ return;
+
+ /* only do the masquerade if we are being called on the outbound channel,
+ if it has been bridged to another channel and if there are no pending
+ frames on the owner channel (because they would be transferred to the
+ outbound channel during the masquerade)
+ */
+ if (isoutbound && p->chan->_bridge /* Not ast_bridged_channel! Only go one step! */ && AST_LIST_EMPTY(&p->owner->readq)) {
+ /* Masquerade bridged channel into owner */
+ /* Lock everything we need, one by one, and give up if
+ we can't get everything. Remember, we'll get another
+ chance in just a little bit */
+ if (!ast_channel_trylock(p->chan->_bridge)) {
+ if (!ast_check_hangup(p->chan->_bridge)) {
+ if (!ast_channel_trylock(p->owner)) {
+ if (!ast_check_hangup(p->owner)) {
+ ast_channel_masquerade(p->owner, p->chan->_bridge);
+ ast_set_flag(p, LOCAL_ALREADY_MASQED);
+ }
+ ast_channel_unlock(p->owner);
+ }
+ ast_channel_unlock(p->chan->_bridge);
+ }
+ }
+ /* We only allow masquerading in one 'direction'... it's important to preserve the state
+ (group variables, etc.) that live on p->chan->_bridge (and were put there by the dialplan)
+ when the local channels go away.
+ */
+#if 0
+ } else if (!isoutbound && p->owner && p->owner->_bridge && p->chan && AST_LIST_EMPTY(&p->chan->readq)) {
+ /* Masquerade bridged channel into chan */
+ if (!ast_mutex_trylock(&(p->owner->_bridge)->lock)) {
+ if (!ast_check_hangup(p->owner->_bridge)) {
+ if (!ast_mutex_trylock(&p->chan->lock)) {
+ if (!ast_check_hangup(p->chan)) {
+ ast_channel_masquerade(p->chan, p->owner->_bridge);
+ ast_set_flag(p, LOCAL_ALREADY_MASQED);
+ }
+ ast_mutex_unlock(&p->chan->lock);
+ }
+ }
+ ast_mutex_unlock(&(p->owner->_bridge)->lock);
+ }
+#endif
+ }
+}
+
+static struct ast_frame *local_read(struct ast_channel *ast)
+{
+ return &ast_null_frame;
+}
+
+static int local_write(struct ast_channel *ast, struct ast_frame *f)
+{
+ struct local_pvt *p = ast->tech_pvt;
+ int res = -1;
+ int isoutbound;
+
+ if (!p)
+ return -1;
+
+ /* Just queue for delivery to the other side */
+ ast_mutex_lock(&p->lock);
+ isoutbound = IS_OUTBOUND(ast, p);
+ if (f && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO))
+ check_bridge(p, isoutbound);
+ if (!ast_test_flag(p, LOCAL_ALREADY_MASQED))
+ res = local_queue_frame(p, isoutbound, f, ast);
+ else {
+ ast_debug(1, "Not posting to queue since already masked on '%s'\n", ast->name);
+ res = 0;
+ }
+ if (!res)
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct local_pvt *p = newchan->tech_pvt;
+
+ if (!p)
+ return -1;
+
+ ast_mutex_lock(&p->lock);
+
+ if ((p->owner != oldchan) && (p->chan != oldchan)) {
+ ast_log(LOG_WARNING, "Old channel wasn't %p but was %p/%p\n", oldchan, p->owner, p->chan);
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ if (p->owner == oldchan)
+ p->owner = newchan;
+ else
+ p->chan = newchan;
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+ struct local_pvt *p = ast->tech_pvt;
+ int res = 0;
+ struct ast_frame f = { AST_FRAME_CONTROL, };
+ int isoutbound;
+
+ if (!p)
+ return -1;
+
+ /* If this is an MOH hold or unhold, do it on the Local channel versus real channel */
+ if (condition == AST_CONTROL_HOLD) {
+ ast_moh_start(ast, data, NULL);
+ } else if (condition == AST_CONTROL_UNHOLD) {
+ ast_moh_stop(ast);
+ } else {
+ /* Queue up a frame representing the indication as a control frame */
+ ast_mutex_lock(&p->lock);
+ isoutbound = IS_OUTBOUND(ast, p);
+ f.subclass = condition;
+ f.data = (void*)data;
+ f.datalen = datalen;
+ if (!(res = local_queue_frame(p, isoutbound, &f, ast)))
+ ast_mutex_unlock(&p->lock);
+ }
+
+ return res;
+}
+
+static int local_digit_begin(struct ast_channel *ast, char digit)
+{
+ struct local_pvt *p = ast->tech_pvt;
+ int res = -1;
+ struct ast_frame f = { AST_FRAME_DTMF_BEGIN, };
+ int isoutbound;
+
+ if (!p)
+ return -1;
+
+ ast_mutex_lock(&p->lock);
+ isoutbound = IS_OUTBOUND(ast, p);
+ f.subclass = digit;
+ if (!(res = local_queue_frame(p, isoutbound, &f, ast)))
+ ast_mutex_unlock(&p->lock);
+
+ return res;
+}
+
+static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct local_pvt *p = ast->tech_pvt;
+ int res = -1;
+ struct ast_frame f = { AST_FRAME_DTMF_END, };
+ int isoutbound;
+
+ if (!p)
+ return -1;
+
+ ast_mutex_lock(&p->lock);
+ isoutbound = IS_OUTBOUND(ast, p);
+ f.subclass = digit;
+ f.len = duration;
+ if (!(res = local_queue_frame(p, isoutbound, &f, ast)))
+ ast_mutex_unlock(&p->lock);
+
+ return res;
+}
+
+static int local_sendtext(struct ast_channel *ast, const char *text)
+{
+ struct local_pvt *p = ast->tech_pvt;
+ int res = -1;
+ struct ast_frame f = { AST_FRAME_TEXT, };
+ int isoutbound;
+
+ if (!p)
+ return -1;
+
+ ast_mutex_lock(&p->lock);
+ isoutbound = IS_OUTBOUND(ast, p);
+ f.data = (char *) text;
+ f.datalen = strlen(text) + 1;
+ if (!(res = local_queue_frame(p, isoutbound, &f, ast)))
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
+{
+ struct local_pvt *p = ast->tech_pvt;
+ int res = -1;
+ struct ast_frame f = { AST_FRAME_HTML, };
+ int isoutbound;
+
+ if (!p)
+ return -1;
+
+ ast_mutex_lock(&p->lock);
+ isoutbound = IS_OUTBOUND(ast, p);
+ f.subclass = subclass;
+ f.data = (char *)data;
+ f.datalen = datalen;
+ if (!(res = local_queue_frame(p, isoutbound, &f, ast)))
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+/*! \brief Initiate new call, part of PBX interface
+ * dest is the dial string */
+static int local_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ struct local_pvt *p = ast->tech_pvt;
+ int res;
+ struct ast_var_t *varptr = NULL, *new;
+ size_t len, namelen;
+
+ if (!p)
+ return -1;
+
+ ast_mutex_lock(&p->lock);
+
+ /*
+ * Note that cid_num and cid_name aren't passed in the ast_channel_alloc
+ * call, so it's done here instead.
+ */
+ p->chan->cid.cid_num = ast_strdup(p->owner->cid.cid_num);
+ p->chan->cid.cid_name = ast_strdup(p->owner->cid.cid_name);
+ p->chan->cid.cid_rdnis = ast_strdup(p->owner->cid.cid_rdnis);
+ p->chan->cid.cid_ani = ast_strdup(p->owner->cid.cid_ani);
+ p->chan->cid.cid_pres = p->owner->cid.cid_pres;
+ ast_string_field_set(p->chan, language, p->owner->language);
+ ast_string_field_set(p->chan, accountcode, p->owner->accountcode);
+ p->chan->cdrflags = p->owner->cdrflags;
+
+ /* copy the channel variables from the incoming channel to the outgoing channel */
+ /* Note that due to certain assumptions, they MUST be in the same order */
+ AST_LIST_TRAVERSE(&p->owner->varshead, varptr, entries) {
+ namelen = strlen(varptr->name);
+ len = sizeof(struct ast_var_t) + namelen + strlen(varptr->value) + 2;
+ if ((new = ast_calloc(1, len))) {
+ memcpy(new, varptr, len);
+ new->value = &(new->name[0]) + namelen + 1;
+ AST_LIST_INSERT_TAIL(&p->chan->varshead, new, entries);
+ }
+ }
+ ast_channel_datastore_inherit(p->owner, p->chan);
+
+ /* Start switch on sub channel */
+ if (!(res = ast_pbx_start(p->chan)))
+ ast_set_flag(p, LOCAL_LAUNCHED_PBX);
+
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+/*! \brief Hangup a call through the local proxy channel */
+static int local_hangup(struct ast_channel *ast)
+{
+ struct local_pvt *p = ast->tech_pvt;
+ int isoutbound;
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_HANGUP };
+ struct ast_channel *ochan = NULL;
+ int glaredetect = 0, res = 0;
+
+ if (!p)
+ return -1;
+
+ ast_mutex_lock(&p->lock);
+ if (p->chan && ast_test_flag(ast, AST_FLAG_ANSWERED_ELSEWHERE))
+ ast_set_flag(p->chan, AST_FLAG_ANSWERED_ELSEWHERE);
+ isoutbound = IS_OUTBOUND(ast, p);
+ if (isoutbound) {
+ const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
+ if ((status) && (p->owner)) {
+ /* Deadlock avoidance */
+ while (ast_channel_trylock(p->owner)) {
+ ast_mutex_unlock(&p->lock);
+ usleep(1);
+ ast_mutex_lock(&p->lock);
+ }
+ pbx_builtin_setvar_helper(p->owner, "CHANLOCALSTATUS", status);
+ ast_channel_unlock(p->owner);
+ }
+ p->chan = NULL;
+ ast_clear_flag(p, LOCAL_LAUNCHED_PBX);
+ ast_module_user_remove(p->u_chan);
+ } else {
+ p->owner = NULL;
+ ast_module_user_remove(p->u_owner);
+ }
+
+ ast->tech_pvt = NULL;
+
+ if (!p->owner && !p->chan) {
+ /* Okay, done with the private part now, too. */
+ glaredetect = ast_test_flag(p, LOCAL_GLARE_DETECT);
+ /* If we have a queue holding, don't actually destroy p yet, but
+ let local_queue do it. */
+ if (glaredetect)
+ ast_set_flag(p, LOCAL_CANCEL_QUEUE);
+ ast_mutex_unlock(&p->lock);
+ /* Remove from list */
+ AST_LIST_LOCK(&locals);
+ AST_LIST_REMOVE(&locals, p, list);
+ AST_LIST_UNLOCK(&locals);
+ /* Grab / release lock just in case */
+ ast_mutex_lock(&p->lock);
+ ast_mutex_unlock(&p->lock);
+ /* And destroy */
+ if (!glaredetect) {
+ ast_mutex_destroy(&p->lock);
+ ast_free(p);
+ }
+ return 0;
+ }
+ if (p->chan && !ast_test_flag(p, LOCAL_LAUNCHED_PBX))
+ /* Need to actually hangup since there is no PBX */
+ ochan = p->chan;
+ else
+ res = local_queue_frame(p, isoutbound, &f, NULL);
+ if (!res)
+ ast_mutex_unlock(&p->lock);
+ if (ochan)
+ ast_hangup(ochan);
+ return 0;
+}
+
+/*! \brief Create a call structure */
+static struct local_pvt *local_alloc(const char *data, int format)
+{
+ struct local_pvt *tmp = NULL;
+ char *c = NULL, *opts = NULL;
+
+ if (!(tmp = ast_calloc(1, sizeof(*tmp))))
+ return NULL;
+
+ /* Initialize private structure information */
+ ast_mutex_init(&tmp->lock);
+ ast_copy_string(tmp->exten, data, sizeof(tmp->exten));
+
+ memcpy(&tmp->jb_conf, &g_jb_conf, sizeof(tmp->jb_conf));
+
+ /* Look for options */
+ if ((opts = strchr(tmp->exten, '/'))) {
+ *opts++ = '\0';
+ if (strchr(opts, 'n'))
+ ast_set_flag(tmp, LOCAL_NO_OPTIMIZATION);
+ if (strchr(opts, 'j')) {
+ if (ast_test_flag(tmp, LOCAL_NO_OPTIMIZATION))
+ ast_set_flag(&tmp->jb_conf, AST_JB_ENABLED);
+ else {
+ ast_log(LOG_ERROR, "You must use the 'n' option for chan_local "
+ "to use the 'j' option to enable the jitterbuffer\n");
+ }
+ }
+ }
+
+ /* Look for a context */
+ if ((c = strchr(tmp->exten, '@')))
+ *c++ = '\0';
+
+ ast_copy_string(tmp->context, c ? c : "default", sizeof(tmp->context));
+
+ tmp->reqformat = format;
+
+ if (!ast_exists_extension(NULL, tmp->context, tmp->exten, 1, NULL)) {
+ ast_log(LOG_NOTICE, "No such extension/context %s@%s creating local channel\n", tmp->exten, tmp->context);
+ ast_mutex_destroy(&tmp->lock);
+ ast_free(tmp);
+ tmp = NULL;
+ } else {
+ /* Add to list */
+ AST_LIST_LOCK(&locals);
+ AST_LIST_INSERT_HEAD(&locals, tmp, list);
+ AST_LIST_UNLOCK(&locals);
+ }
+
+ return tmp;
+}
+
+/*! \brief Start new local channel */
+static struct ast_channel *local_new(struct local_pvt *p, int state)
+{
+ struct ast_channel *tmp = NULL, *tmp2 = NULL;
+ int randnum = ast_random() & 0xffff, fmt = 0;
+ const char *t;
+ int ama;
+
+ /* Allocate two new Asterisk channels */
+ /* safe accountcode */
+ if (p->owner && p->owner->accountcode)
+ t = p->owner->accountcode;
+ else
+ t = "";
+
+ if (p->owner)
+ ama = p->owner->amaflags;
+ else
+ ama = 0;
+ if (!(tmp = ast_channel_alloc(1, state, 0, 0, t, p->exten, p->context, ama, "Local/%s@%s-%04x;1", p->exten, p->context, randnum))
+ || !(tmp2 = ast_channel_alloc(1, AST_STATE_RING, 0, 0, t, p->exten, p->context, ama, "Local/%s@%s-%04x;2", p->exten, p->context, randnum))) {
+ if (tmp)
+ ast_channel_free(tmp);
+ if (tmp2)
+ ast_channel_free(tmp2);
+ ast_log(LOG_WARNING, "Unable to allocate channel structure(s)\n");
+ return NULL;
+ }
+
+ tmp2->tech = tmp->tech = &local_tech;
+
+ tmp->nativeformats = p->reqformat;
+ tmp2->nativeformats = p->reqformat;
+
+ /* Determine our read/write format and set it on each channel */
+ fmt = ast_best_codec(p->reqformat);
+ tmp->writeformat = fmt;
+ tmp2->writeformat = fmt;
+ tmp->rawwriteformat = fmt;
+ tmp2->rawwriteformat = fmt;
+ tmp->readformat = fmt;
+ tmp2->readformat = fmt;
+ tmp->rawreadformat = fmt;
+ tmp2->rawreadformat = fmt;
+
+ tmp->tech_pvt = p;
+ tmp2->tech_pvt = p;
+
+ p->owner = tmp;
+ p->chan = tmp2;
+ p->u_owner = ast_module_user_add(p->owner);
+ p->u_chan = ast_module_user_add(p->chan);
+
+ ast_copy_string(tmp->context, p->context, sizeof(tmp->context));
+ ast_copy_string(tmp2->context, p->context, sizeof(tmp2->context));
+ ast_copy_string(tmp2->exten, p->exten, sizeof(tmp->exten));
+ tmp->priority = 1;
+ tmp2->priority = 1;
+
+ ast_jb_configure(tmp, &p->jb_conf);
+
+ return tmp;
+}
+
+
+/*! \brief Part of PBX interface */
+static struct ast_channel *local_request(const char *type, int format, void *data, int *cause)
+{
+ struct local_pvt *p = NULL;
+ struct ast_channel *chan = NULL;
+
+ /* Allocate a new private structure and then Asterisk channel */
+ if ((p = local_alloc(data, format)))
+ chan = local_new(p, AST_STATE_DOWN);
+
+ return chan;
+}
+
+/*! \brief CLI command "local show channels" */
+static char *locals_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct local_pvt *p = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "local show channels";
+ e->usage =
+ "Usage: local show channels\n"
+ " Provides summary information on active local proxy channels.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ AST_LIST_LOCK(&locals);
+ if (!AST_LIST_EMPTY(&locals)) {
+ AST_LIST_TRAVERSE(&locals, p, list) {
+ ast_mutex_lock(&p->lock);
+ ast_cli(a->fd, "%s -- %s@%s\n", p->owner ? p->owner->name : "<unowned>", p->exten, p->context);
+ ast_mutex_unlock(&p->lock);
+ }
+ } else
+ ast_cli(a->fd, "No local channels in use\n");
+ AST_LIST_UNLOCK(&locals);
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_local[] = {
+ AST_CLI_DEFINE(locals_show, "List status of local channels"),
+};
+
+/*! \brief Load module into PBX, register channel */
+static int load_module(void)
+{
+ /* Make sure we can register our channel type */
+ if (ast_channel_register(&local_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'Local'\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ ast_cli_register_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+/*! \brief Unload the local proxy channel from Asterisk */
+static int unload_module(void)
+{
+ struct local_pvt *p = NULL;
+
+ /* First, take us out of the channel loop */
+ ast_cli_unregister_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
+ ast_channel_unregister(&local_tech);
+ if (!AST_LIST_LOCK(&locals)) {
+ /* Hangup all interfaces if they have an owner */
+ AST_LIST_TRAVERSE(&locals, p, list) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ }
+ AST_LIST_UNLOCK(&locals);
+ AST_LIST_HEAD_DESTROY(&locals);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the monitor\n");
+ return -1;
+ }
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Local Proxy Channel");
diff --git a/trunk/channels/chan_mgcp.c b/trunk/channels/chan_mgcp.c
new file mode 100644
index 000000000..624013283
--- /dev/null
+++ b/trunk/channels/chan_mgcp.c
@@ -0,0 +1,4398 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Implementation of Media Gateway Control Protocol
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \par See also
+ * \arg \ref Config_mgcp
+ *
+ * \ingroup channel_drivers
+ */
+/*** MODULEINFO
+ <depend>res_features</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/signal.h>
+#include <signal.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/rtp.h"
+#include "asterisk/acl.h"
+#include "asterisk/callerid.h"
+#include "asterisk/cli.h"
+#include "asterisk/say.h"
+#include "asterisk/cdr.h"
+#include "asterisk/astdb.h"
+#include "asterisk/features.h"
+#include "asterisk/app.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/utils.h"
+#include "asterisk/netsock.h"
+#include "asterisk/causes.h"
+#include "asterisk/dsp.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/event.h"
+
+/*
+ * Define to work around buggy dlink MGCP phone firmware which
+ * appears not to know that "rt" is part of the "G" package.
+ */
+/* #define DLINK_BUGGY_FIRMWARE */
+
+#define MGCPDUMPER
+#define DEFAULT_EXPIRY 120
+#define MAX_EXPIRY 3600
+#define CANREINVITE 1
+
+#ifndef INADDR_NONE
+#define INADDR_NONE (in_addr_t)(-1)
+#endif
+
+/*! Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = ""
+};
+static struct ast_jb_conf global_jbconf;
+
+static const char tdesc[] = "Media Gateway Control Protocol (MGCP)";
+static const char config[] = "mgcp.conf";
+
+#define MGCP_DTMF_RFC2833 (1 << 0)
+#define MGCP_DTMF_INBAND (1 << 1)
+#define MGCP_DTMF_HYBRID (1 << 2)
+
+#define DEFAULT_MGCP_GW_PORT 2427 /*!< From RFC 2705 */
+#define DEFAULT_MGCP_CA_PORT 2727 /*!< From RFC 2705 */
+#define MGCP_MAX_PACKET 1500 /*!< Also from RFC 2543, should sub headers tho */
+#define DEFAULT_RETRANS 1000 /*!< How frequently to retransmit */
+#define MAX_RETRANS 5 /*!< Try only 5 times for retransmissions */
+
+/*! MGCP rtp stream modes { */
+#define MGCP_CX_SENDONLY 0
+#define MGCP_CX_RECVONLY 1
+#define MGCP_CX_SENDRECV 2
+#define MGCP_CX_CONF 3
+#define MGCP_CX_CONFERENCE 3
+#define MGCP_CX_MUTE 4
+#define MGCP_CX_INACTIVE 4
+/*! } */
+
+static char *mgcp_cxmodes[] = {
+ "sendonly",
+ "recvonly",
+ "sendrecv",
+ "confrnce",
+ "inactive"
+};
+
+enum {
+ MGCP_CMD_EPCF,
+ MGCP_CMD_CRCX,
+ MGCP_CMD_MDCX,
+ MGCP_CMD_DLCX,
+ MGCP_CMD_RQNT,
+ MGCP_CMD_NTFY,
+ MGCP_CMD_AUEP,
+ MGCP_CMD_AUCX,
+ MGCP_CMD_RSIP
+};
+
+static char context[AST_MAX_EXTENSION] = "default";
+
+static char language[MAX_LANGUAGE] = "";
+static char musicclass[MAX_MUSICCLASS] = "";
+static char cid_num[AST_MAX_EXTENSION] = "";
+static char cid_name[AST_MAX_EXTENSION] = "";
+
+static int dtmfmode = 0;
+static int nat = 0;
+
+static ast_group_t cur_callergroup = 0;
+static ast_group_t cur_pickupgroup = 0;
+
+static unsigned int tos = 0;
+static unsigned int tos_audio = 0;
+static unsigned int cos = 0;
+static unsigned int cos_audio = 0;
+
+static int immediate = 0;
+
+static int callwaiting = 0;
+
+static int callreturn = 0;
+
+static int slowsequence = 0;
+
+static int threewaycalling = 0;
+
+/*! This is for flashhook transfers */
+static int transfer = 0;
+
+static int cancallforward = 0;
+
+static int singlepath = 0;
+
+static int canreinvite = CANREINVITE;
+
+static char accountcode[AST_MAX_ACCOUNT_CODE] = "";
+
+static char mailbox[AST_MAX_EXTENSION];
+
+static int amaflags = 0;
+
+static int adsi = 0;
+
+static unsigned int oseq;
+
+/*! Wait up to 16 seconds for first digit (FXO logic) */
+static int firstdigittimeout = 16000;
+
+/*! How long to wait for following digits (FXO logic) */
+static int gendigittimeout = 8000;
+
+/*! How long to wait for an extra digit, if there is an ambiguous match */
+static int matchdigittimeout = 3000;
+
+/*! Protect the monitoring thread, so only one process can kill or start it, and not
+ when it's doing something critical. */
+AST_MUTEX_DEFINE_STATIC(netlock);
+
+AST_MUTEX_DEFINE_STATIC(monlock);
+
+/*! This is the thread for the monitor which checks for input on the channels
+ which are not currently in use. */
+static pthread_t monitor_thread = AST_PTHREADT_NULL;
+
+static int restart_monitor(void);
+
+static int capability = AST_FORMAT_ULAW;
+static int nonCodecCapability = AST_RTP_DTMF;
+
+static char ourhost[MAXHOSTNAMELEN];
+static struct in_addr __ourip;
+static int ourport;
+
+static int mgcpdebug = 0;
+
+static struct sched_context *sched;
+static struct io_context *io;
+/*! The private structures of the mgcp channels are linked for
+ ! selecting outgoing channels */
+
+#define MGCP_MAX_HEADERS 64
+#define MGCP_MAX_LINES 64
+
+struct mgcp_request {
+ int len;
+ char *verb;
+ char *identifier;
+ char *endpoint;
+ char *version;
+ int headers; /*!< MGCP Headers */
+ char *header[MGCP_MAX_HEADERS];
+ int lines; /*!< SDP Content */
+ char *line[MGCP_MAX_LINES];
+ char data[MGCP_MAX_PACKET];
+ int cmd; /*!< int version of verb = command */
+ unsigned int trid; /*!< int version of identifier = transaction id */
+ struct mgcp_request *next; /*!< next in the queue */
+};
+
+/*! \brief mgcp_message: MGCP message for queuing up */
+struct mgcp_message {
+ struct mgcp_endpoint *owner_ep;
+ struct mgcp_subchannel *owner_sub;
+ int retrans;
+ unsigned long expire;
+ unsigned int seqno;
+ int len;
+ struct mgcp_message *next;
+ char buf[0];
+};
+
+#define RESPONSE_TIMEOUT 30 /*!< in seconds */
+
+struct mgcp_response {
+ time_t whensent;
+ int len;
+ int seqno;
+ struct mgcp_response *next;
+ char buf[0];
+};
+
+#define MAX_SUBS 2
+
+#define SUB_REAL 0
+#define SUB_ALT 1
+
+struct mgcp_subchannel {
+ /*! subchannel magic string.
+ Needed to prove that any subchannel pointer passed by asterisk
+ really points to a valid subchannel memory area.
+ Ugly.. But serves the purpose for the time being.
+ */
+#define MGCP_SUBCHANNEL_MAGIC "!978!"
+ char magic[6];
+ ast_mutex_t lock;
+ int id;
+ struct ast_channel *owner;
+ struct mgcp_endpoint *parent;
+ struct ast_rtp *rtp;
+ struct sockaddr_in tmpdest;
+ char txident[80]; /*! \todo FIXME txident is replaced by rqnt_ident in endpoint.
+ This should be obsoleted */
+ char cxident[80];
+ char callid[80];
+ int cxmode;
+ struct mgcp_request *cx_queue; /*!< pending CX commands */
+ ast_mutex_t cx_queue_lock; /*!< CX queue lock */
+ int nat;
+ int iseq; /*!< Not used? RTP? */
+ int outgoing;
+ int alreadygone;
+ struct mgcp_subchannel *next; /*!< for out circular linked list */
+};
+
+#define MGCP_ONHOOK 1
+#define MGCP_OFFHOOK 2
+
+#define TYPE_TRUNK 1
+#define TYPE_LINE 2
+
+struct mgcp_endpoint {
+ ast_mutex_t lock;
+ char name[80];
+ struct mgcp_subchannel *sub; /*!< Pointer to our current connection, channel and stuff */
+ char accountcode[AST_MAX_ACCOUNT_CODE];
+ char exten[AST_MAX_EXTENSION]; /*!< Extention where to start */
+ char context[AST_MAX_EXTENSION];
+ char language[MAX_LANGUAGE];
+ char cid_num[AST_MAX_EXTENSION]; /*!< Caller*ID number */
+ char cid_name[AST_MAX_EXTENSION]; /*!< Caller*ID name */
+ char lastcallerid[AST_MAX_EXTENSION]; /*!< Last Caller*ID */
+ char dtmf_buf[AST_MAX_EXTENSION]; /*!< place to collect digits be */
+ char call_forward[AST_MAX_EXTENSION]; /*!< Last Caller*ID */
+ char musicclass[MAX_MUSICCLASS];
+ char curtone[80]; /*!< Current tone */
+ char mailbox[AST_MAX_EXTENSION];
+ struct ast_event_sub *mwi_event_sub;
+ ast_group_t callgroup;
+ ast_group_t pickupgroup;
+ int callwaiting;
+ int hascallwaiting;
+ int transfer;
+ int threewaycalling;
+ int singlepath;
+ int cancallforward;
+ int canreinvite;
+ int callreturn;
+ int dnd; /* How does this affect callwait? Do we just deny a mgcp_request if we're dnd? */
+ int hascallerid;
+ int hidecallerid;
+ int dtmfmode;
+ int amaflags;
+ int type;
+ int slowsequence; /*!< MS: Sequence the endpoint as a whole */
+ int group;
+ int iseq; /*!< Not used? */
+ int lastout; /*!< tracking this on the subchannels. Is it needed here? */
+ int needdestroy; /*!< Not used? */
+ int capability;
+ int nonCodecCapability;
+ int onhooktime;
+ int msgstate; /*!< voicemail message state */
+ int immediate;
+ int hookstate;
+ int adsi;
+ char rqnt_ident[80]; /*!< request identifier */
+ struct mgcp_request *rqnt_queue; /*!< pending RQNT commands */
+ ast_mutex_t rqnt_queue_lock;
+ struct mgcp_request *cmd_queue; /*!< pending commands other than RQNT */
+ ast_mutex_t cmd_queue_lock;
+ int delme; /*!< needed for reload */
+ int needaudit; /*!< needed for reload */
+ struct ast_dsp *dsp; /*!< XXX Should there be a dsp/subchannel? XXX */
+ /* owner is tracked on the subchannels, and the *sub indicates whos in charge */
+ /* struct ast_channel *owner; */
+ /* struct ast_rtp *rtp; */
+ /* struct sockaddr_in tmpdest; */
+ /* message go the the endpoint and not the channel so they stay here */
+ struct mgcp_endpoint *next;
+ struct mgcp_gateway *parent;
+};
+
+static struct mgcp_gateway {
+ /* A gateway containing one or more endpoints */
+ char name[80];
+ int isnamedottedip; /*!< is the name FQDN or dotted ip */
+ struct sockaddr_in addr;
+ struct sockaddr_in defaddr;
+ struct in_addr ourip;
+ int dynamic;
+ int expire; /*!< XXX Should we ever expire dynamic registrations? XXX */
+ struct mgcp_endpoint *endpoints;
+ struct ast_ha *ha;
+/* obsolete
+ time_t lastouttime;
+ int lastout;
+ int messagepending;
+*/
+/* Wildcard endpoint name */
+ char wcardep[30];
+ struct mgcp_message *msgs; /*!< gw msg queue */
+ ast_mutex_t msgs_lock; /*!< queue lock */
+ int retransid; /*!< retrans timer id */
+ int delme; /*!< needed for reload */
+ struct mgcp_response *responses;
+ struct mgcp_gateway *next;
+} *gateways;
+
+AST_MUTEX_DEFINE_STATIC(mgcp_reload_lock);
+static int mgcp_reloading = 0;
+
+/*! \brief gatelock: mutex for gateway/endpoint lists */
+AST_MUTEX_DEFINE_STATIC(gatelock);
+
+static int mgcpsock = -1;
+
+static struct sockaddr_in bindaddr;
+
+static struct ast_frame *mgcp_read(struct ast_channel *ast);
+static int transmit_response(struct mgcp_subchannel *sub, char *msg, struct mgcp_request *req, char *msgrest);
+static int transmit_notify_request(struct mgcp_subchannel *sub, char *tone);
+static int transmit_modify_request(struct mgcp_subchannel *sub);
+static int transmit_notify_request_with_callerid(struct mgcp_subchannel *sub, char *tone, char *callernum, char *callername);
+static int transmit_modify_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp *rtp, int codecs);
+static int transmit_connection_del(struct mgcp_subchannel *sub);
+static int transmit_audit_endpoint(struct mgcp_endpoint *p);
+static void start_rtp(struct mgcp_subchannel *sub);
+static void handle_response(struct mgcp_endpoint *p, struct mgcp_subchannel *sub,
+ int result, unsigned int ident, struct mgcp_request *resp);
+static void dump_cmd_queues(struct mgcp_endpoint *p, struct mgcp_subchannel *sub);
+static char *mgcp_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static int reload_config(int reload);
+
+static struct ast_channel *mgcp_request(const char *type, int format, void *data, int *cause);
+static int mgcp_call(struct ast_channel *ast, char *dest, int timeout);
+static int mgcp_hangup(struct ast_channel *ast);
+static int mgcp_answer(struct ast_channel *ast);
+static struct ast_frame *mgcp_read(struct ast_channel *ast);
+static int mgcp_write(struct ast_channel *ast, struct ast_frame *frame);
+static int mgcp_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen);
+static int mgcp_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static int mgcp_senddigit_begin(struct ast_channel *ast, char digit);
+static int mgcp_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int mgcp_devicestate(void *data);
+static void add_header_offhook(struct mgcp_subchannel *sub, struct mgcp_request *resp);
+
+static const struct ast_channel_tech mgcp_tech = {
+ .type = "MGCP",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_ULAW,
+ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
+ .requester = mgcp_request,
+ .devicestate = mgcp_devicestate,
+ .call = mgcp_call,
+ .hangup = mgcp_hangup,
+ .answer = mgcp_answer,
+ .read = mgcp_read,
+ .write = mgcp_write,
+ .indicate = mgcp_indicate,
+ .fixup = mgcp_fixup,
+ .send_digit_begin = mgcp_senddigit_begin,
+ .send_digit_end = mgcp_senddigit_end,
+ .bridge = ast_rtp_bridge,
+};
+
+static void mwi_event_cb(const struct ast_event *event, void *userdata)
+{
+ /* This module does not handle MWI in an event-based manner. However, it
+ * subscribes to MWI for each mailbox that is configured so that the core
+ * knows that we care about it. Then, chan_mgcp will get the MWI from the
+ * event cache instead of checking the mailbox directly. */
+}
+
+static int has_voicemail(struct mgcp_endpoint *p)
+{
+ int new_msgs;
+ struct ast_event *event;
+ char *mailbox, *context;
+
+ context = mailbox = ast_strdupa(p->mailbox);
+ strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+
+ event = ast_event_get_cached(AST_EVENT_MWI,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
+ AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_EXISTS,
+ AST_EVENT_IE_END);
+
+ if (event) {
+ new_msgs = ast_event_get_ie_uint(event, AST_EVENT_IE_NEWMSGS);
+ ast_event_destroy(event);
+ } else
+ new_msgs = ast_app_has_voicemail(p->mailbox, NULL);
+
+ return new_msgs;
+}
+
+static int unalloc_sub(struct mgcp_subchannel *sub)
+{
+ struct mgcp_endpoint *p = sub->parent;
+ if (p->sub == sub) {
+ ast_log(LOG_WARNING, "Trying to unalloc the real channel %s@%s?!?\n", p->name, p->parent->name);
+ return -1;
+ }
+ ast_debug(1, "Released sub %d of channel %s@%s\n", sub->id, p->name, p->parent->name);
+
+ sub->owner = NULL;
+ if (!ast_strlen_zero(sub->cxident)) {
+ transmit_connection_del(sub);
+ }
+ sub->cxident[0] = '\0';
+ sub->callid[0] = '\0';
+ sub->cxmode = MGCP_CX_INACTIVE;
+ sub->outgoing = 0;
+ sub->alreadygone = 0;
+ memset(&sub->tmpdest, 0, sizeof(sub->tmpdest));
+ if (sub->rtp) {
+ ast_rtp_destroy(sub->rtp);
+ sub->rtp = NULL;
+ }
+ dump_cmd_queues(NULL, sub); /* SC */
+ return 0;
+}
+
+/* modified for new transport mechanism */
+static int __mgcp_xmit(struct mgcp_gateway *gw, char *data, int len)
+{
+ int res;
+ if (gw->addr.sin_addr.s_addr)
+ res=sendto(mgcpsock, data, len, 0, (struct sockaddr *)&gw->addr, sizeof(struct sockaddr_in));
+ else
+ res=sendto(mgcpsock, data, len, 0, (struct sockaddr *)&gw->defaddr, sizeof(struct sockaddr_in));
+ if (res != len) {
+ ast_log(LOG_WARNING, "mgcp_xmit returned %d: %s\n", res, strerror(errno));
+ }
+ return res;
+}
+
+static int resend_response(struct mgcp_subchannel *sub, struct mgcp_response *resp)
+{
+ struct mgcp_endpoint *p = sub->parent;
+ int res;
+ if (mgcpdebug) {
+ ast_verbose("Retransmitting:\n%s\n to %s:%d\n", resp->buf, ast_inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port));
+ }
+ res = __mgcp_xmit(p->parent, resp->buf, resp->len);
+ if (res > 0)
+ res = 0;
+ return res;
+}
+
+static int send_response(struct mgcp_subchannel *sub, struct mgcp_request *req)
+{
+ struct mgcp_endpoint *p = sub->parent;
+ int res;
+ if (mgcpdebug) {
+ ast_verbose("Transmitting:\n%s\n to %s:%d\n", req->data, ast_inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port));
+ }
+ res = __mgcp_xmit(p->parent, req->data, req->len);
+ if (res > 0)
+ res = 0;
+ return res;
+}
+
+/* modified for new transport framework */
+static void dump_queue(struct mgcp_gateway *gw, struct mgcp_endpoint *p)
+{
+ struct mgcp_message *cur, *q = NULL, *w, *prev;
+
+ ast_mutex_lock(&gw->msgs_lock);
+ prev = NULL, cur = gw->msgs;
+ while (cur) {
+ if (!p || cur->owner_ep == p) {
+ if (prev)
+ prev->next = cur->next;
+ else
+ gw->msgs = cur->next;
+
+ ast_log(LOG_NOTICE, "Removing message from %s transaction %u\n",
+ gw->name, cur->seqno);
+
+ w = cur;
+ cur = cur->next;
+ if (q) {
+ w->next = q;
+ } else {
+ w->next = NULL;
+ }
+ q = w;
+ } else {
+ prev = cur, cur=cur->next;
+ }
+ }
+ ast_mutex_unlock(&gw->msgs_lock);
+
+ while (q) {
+ cur = q;
+ q = q->next;
+ ast_free(cur);
+ }
+}
+
+static void mgcp_queue_frame(struct mgcp_subchannel *sub, struct ast_frame *f)
+{
+ for(;;) {
+ if (sub->owner) {
+ if (!ast_channel_trylock(sub->owner)) {
+ ast_queue_frame(sub->owner, f);
+ ast_channel_unlock(sub->owner);
+ break;
+ } else {
+ ast_mutex_unlock(&sub->lock);
+ usleep(1);
+ ast_mutex_lock(&sub->lock);
+ }
+ } else
+ break;
+ }
+}
+
+static void mgcp_queue_hangup(struct mgcp_subchannel *sub)
+{
+ for(;;) {
+ if (sub->owner) {
+ if (!ast_channel_trylock(sub->owner)) {
+ ast_queue_hangup(sub->owner);
+ ast_channel_unlock(sub->owner);
+ break;
+ } else {
+ ast_mutex_unlock(&sub->lock);
+ usleep(1);
+ ast_mutex_lock(&sub->lock);
+ }
+ } else
+ break;
+ }
+}
+
+static void mgcp_queue_control(struct mgcp_subchannel *sub, int control)
+{
+ struct ast_frame f = { AST_FRAME_CONTROL, };
+ f.subclass = control;
+ return mgcp_queue_frame(sub, &f);
+}
+
+static int retrans_pkt(const void *data)
+{
+ struct mgcp_gateway *gw = (struct mgcp_gateway *)data;
+ struct mgcp_message *cur, *exq = NULL, *w, *prev;
+ int res = 0;
+
+ /* find out expired msgs */
+ ast_mutex_lock(&gw->msgs_lock);
+
+ prev = NULL, cur = gw->msgs;
+ while (cur) {
+ if (cur->retrans < MAX_RETRANS) {
+ cur->retrans++;
+ if (mgcpdebug) {
+ ast_verbose("Retransmitting #%d transaction %u on [%s]\n",
+ cur->retrans, cur->seqno, gw->name);
+ }
+ __mgcp_xmit(gw, cur->buf, cur->len);
+
+ prev = cur;
+ cur = cur->next;
+ } else {
+ if (prev)
+ prev->next = cur->next;
+ else
+ gw->msgs = cur->next;
+
+ ast_log(LOG_WARNING, "Maximum retries exceeded for transaction %u on [%s]\n",
+ cur->seqno, gw->name);
+
+ w = cur;
+ cur = cur->next;
+
+ if (exq) {
+ w->next = exq;
+ } else {
+ w->next = NULL;
+ }
+ exq = w;
+ }
+ }
+
+ if (!gw->msgs) {
+ gw->retransid = -1;
+ res = 0;
+ } else {
+ res = 1;
+ }
+ ast_mutex_unlock(&gw->msgs_lock);
+
+ while (exq) {
+ cur = exq;
+ /* time-out transaction */
+ handle_response(cur->owner_ep, cur->owner_sub, 406, cur->seqno, NULL);
+ exq = exq->next;
+ ast_free(cur);
+ }
+
+ return res;
+}
+
+/* modified for the new transaction mechanism */
+static int mgcp_postrequest(struct mgcp_endpoint *p, struct mgcp_subchannel *sub,
+ char *data, int len, unsigned int seqno)
+{
+ struct mgcp_message *msg;
+ struct mgcp_message *cur;
+ struct mgcp_gateway *gw;
+ struct timeval tv;
+
+ msg = ast_malloc(sizeof(*msg) + len);
+ if (!msg) {
+ return -1;
+ }
+ gw = ((p && p->parent) ? p->parent : NULL);
+ if (!gw) {
+ ast_free(msg);
+ return -1;
+ }
+/* SC
+ time(&t);
+ if (gw->messagepending && (gw->lastouttime + 20 < t)) {
+ ast_log(LOG_NOTICE, "Timeout waiting for response to message:%d, lastouttime: %ld, now: %ld. Dumping pending queue\n",
+ gw->msgs ? gw->msgs->seqno : -1, (long) gw->lastouttime, (long) t);
+ dump_queue(sub->parent);
+ }
+*/
+ msg->owner_sub = sub;
+ msg->owner_ep = p;
+ msg->seqno = seqno;
+ msg->next = NULL;
+ msg->len = len;
+ msg->retrans = 0;
+ memcpy(msg->buf, data, msg->len);
+
+ ast_mutex_lock(&gw->msgs_lock);
+ cur = gw->msgs;
+ if (cur) {
+ while(cur->next)
+ cur = cur->next;
+ cur->next = msg;
+ } else {
+ gw->msgs = msg;
+ }
+
+ tv = ast_tvnow();
+ msg->expire = tv.tv_sec * 1000 + tv.tv_usec / 1000 + DEFAULT_RETRANS;
+
+ if (gw->retransid == -1)
+ gw->retransid = ast_sched_add(sched, DEFAULT_RETRANS, retrans_pkt, (void *)gw);
+ ast_mutex_unlock(&gw->msgs_lock);
+/* SC
+ if (!gw->messagepending) {
+ gw->messagepending = 1;
+ gw->lastout = seqno;
+ gw->lastouttime = t;
+*/
+ __mgcp_xmit(gw, msg->buf, msg->len);
+ /* XXX Should schedule retransmission XXX */
+/* SC
+ } else
+ ast_debug(1, "Deferring transmission of transaction %d\n", seqno);
+*/
+ return 0;
+}
+
+/* modified for new transport */
+static int send_request(struct mgcp_endpoint *p, struct mgcp_subchannel *sub,
+ struct mgcp_request *req, unsigned int seqno)
+{
+ int res = 0;
+ struct mgcp_request **queue, *q, *r, *t;
+ ast_mutex_t *l;
+
+ ast_debug(1, "Slow sequence is %d\n", p->slowsequence);
+ if (p->slowsequence) {
+ queue = &p->cmd_queue;
+ l = &p->cmd_queue_lock;
+ ast_mutex_lock(l);
+ } else {
+ switch (req->cmd) {
+ case MGCP_CMD_DLCX:
+ queue = &sub->cx_queue;
+ l = &sub->cx_queue_lock;
+ ast_mutex_lock(l);
+ q = sub->cx_queue;
+ /* delete pending cx cmds */
+ while (q) {
+ r = q->next;
+ ast_free(q);
+ q = r;
+ }
+ *queue = NULL;
+ break;
+
+ case MGCP_CMD_CRCX:
+ case MGCP_CMD_MDCX:
+ queue = &sub->cx_queue;
+ l = &sub->cx_queue_lock;
+ ast_mutex_lock(l);
+ break;
+
+ case MGCP_CMD_RQNT:
+ queue = &p->rqnt_queue;
+ l = &p->rqnt_queue_lock;
+ ast_mutex_lock(l);
+ break;
+
+ default:
+ queue = &p->cmd_queue;
+ l = &p->cmd_queue_lock;
+ ast_mutex_lock(l);
+ break;
+ }
+ }
+
+ r = ast_malloc(sizeof(*r));
+ if (!r) {
+ ast_log(LOG_WARNING, "Cannot post MGCP request: insufficient memory\n");
+ ast_mutex_unlock(l);
+ return -1;
+ }
+ memcpy(r, req, sizeof(*r));
+
+ if (!(*queue)) {
+ if (mgcpdebug) {
+ ast_verbose("Posting Request:\n%s to %s:%d\n", req->data,
+ ast_inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port));
+ }
+
+ res = mgcp_postrequest(p, sub, req->data, req->len, seqno);
+ } else {
+ if (mgcpdebug) {
+ ast_verbose("Queueing Request:\n%s to %s:%d\n", req->data,
+ ast_inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port));
+ }
+ }
+
+ /* XXX find tail. We could also keep tail in the data struct for faster access */
+ for (t = *queue; t && t->next; t = t->next);
+
+ r->next = NULL;
+ if (t)
+ t->next = r;
+ else
+ *queue = r;
+
+ ast_mutex_unlock(l);
+
+ return res;
+}
+
+static int mgcp_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ int res;
+ struct mgcp_endpoint *p;
+ struct mgcp_subchannel *sub;
+ char tone[50] = "";
+ const char *distinctive_ring = NULL;
+ struct varshead *headp;
+ struct ast_var_t *current;
+
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP mgcp_call(%s)\n", ast->name);
+ }
+ sub = ast->tech_pvt;
+ p = sub->parent;
+ headp = &ast->varshead;
+ AST_LIST_TRAVERSE(headp,current,entries) {
+ /* Check whether there is an ALERT_INFO variable */
+ if (strcasecmp(ast_var_name(current),"ALERT_INFO") == 0) {
+ distinctive_ring = ast_var_value(current);
+ }
+ }
+
+ ast_mutex_lock(&sub->lock);
+ switch (p->hookstate) {
+ case MGCP_OFFHOOK:
+ if (!ast_strlen_zero(distinctive_ring)) {
+ snprintf(tone, sizeof(tone), "L/wt%s", distinctive_ring);
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP distinctive callwait %s\n", tone);
+ }
+ } else {
+ ast_copy_string(tone, "L/wt", sizeof(tone));
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP normal callwait %s\n", tone);
+ }
+ }
+ break;
+ case MGCP_ONHOOK:
+ default:
+ if (!ast_strlen_zero(distinctive_ring)) {
+ snprintf(tone, sizeof(tone), "L/r%s", distinctive_ring);
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP distinctive ring %s\n", tone);
+ }
+ } else {
+ ast_copy_string(tone, "L/rg", sizeof(tone));
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP default ring\n");
+ }
+ }
+ break;
+ }
+
+ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "mgcp_call called on %s, neither down nor reserved\n", ast->name);
+ ast_mutex_unlock(&sub->lock);
+ return -1;
+ }
+
+ res = 0;
+ sub->outgoing = 1;
+ sub->cxmode = MGCP_CX_RECVONLY;
+ if (p->type == TYPE_LINE) {
+ if (!sub->rtp) {
+ start_rtp(sub);
+ } else {
+ transmit_modify_request(sub);
+ }
+
+ if (sub->next->owner && !ast_strlen_zero(sub->next->cxident) && !ast_strlen_zero(sub->next->callid)) {
+ /* try to prevent a callwait from disturbing the other connection */
+ sub->next->cxmode = MGCP_CX_RECVONLY;
+ transmit_modify_request(sub->next);
+ }
+
+ transmit_notify_request_with_callerid(sub, tone, ast->cid.cid_num, ast->cid.cid_name);
+ ast_setstate(ast, AST_STATE_RINGING);
+
+ if (sub->next->owner && !ast_strlen_zero(sub->next->cxident) && !ast_strlen_zero(sub->next->callid)) {
+ /* Put the connection back in sendrecv */
+ sub->next->cxmode = MGCP_CX_SENDRECV;
+ transmit_modify_request(sub->next);
+ }
+ } else {
+ ast_log(LOG_NOTICE, "Don't know how to dial on trunks yet\n");
+ res = -1;
+ }
+ ast_mutex_unlock(&sub->lock);
+ ast_queue_control(ast, AST_CONTROL_RINGING);
+ return res;
+}
+
+static int mgcp_hangup(struct ast_channel *ast)
+{
+ struct mgcp_subchannel *sub = ast->tech_pvt;
+ struct mgcp_endpoint *p = sub->parent;
+
+ ast_debug(1, "mgcp_hangup(%s)\n", ast->name);
+ if (!ast->tech_pvt) {
+ ast_debug(1, "Asked to hangup channel not connected\n");
+ return 0;
+ }
+ if (strcmp(sub->magic, MGCP_SUBCHANNEL_MAGIC)) {
+ ast_debug(1, "Invalid magic. MGCP subchannel freed up already.\n");
+ return 0;
+ }
+ ast_mutex_lock(&sub->lock);
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP mgcp_hangup(%s) on %s@%s\n", ast->name, p->name, p->parent->name);
+ }
+
+ if ((p->dtmfmode & MGCP_DTMF_INBAND) && p->dsp) {
+ /* check whether other channel is active. */
+ if (!sub->next->owner) {
+ if (p->dtmfmode & MGCP_DTMF_HYBRID)
+ p->dtmfmode &= ~MGCP_DTMF_INBAND;
+ if (mgcpdebug) {
+ ast_verb(2, "MGCP free dsp on %s@%s\n", p->name, p->parent->name);
+ }
+ ast_dsp_free(p->dsp);
+ p->dsp = NULL;
+ }
+ }
+
+ sub->owner = NULL;
+ if (!ast_strlen_zero(sub->cxident)) {
+ transmit_connection_del(sub);
+ }
+ sub->cxident[0] = '\0';
+ if ((sub == p->sub) && sub->next->owner) {
+ if (p->hookstate == MGCP_OFFHOOK) {
+ if (sub->next->owner && ast_bridged_channel(sub->next->owner)) {
+ transmit_notify_request_with_callerid(p->sub, "L/wt", ast_bridged_channel(sub->next->owner)->cid.cid_num, ast_bridged_channel(sub->next->owner)->cid.cid_name);
+ }
+ } else {
+ /* set our other connection as the primary and swith over to it */
+ p->sub = sub->next;
+ p->sub->cxmode = MGCP_CX_RECVONLY;
+ transmit_modify_request(p->sub);
+ if (sub->next->owner && ast_bridged_channel(sub->next->owner)) {
+ transmit_notify_request_with_callerid(p->sub, "L/rg", ast_bridged_channel(sub->next->owner)->cid.cid_num, ast_bridged_channel(sub->next->owner)->cid.cid_name);
+ }
+ }
+
+ } else if ((sub == p->sub->next) && p->hookstate == MGCP_OFFHOOK) {
+ transmit_notify_request(sub, "L/v");
+ } else if (p->hookstate == MGCP_OFFHOOK) {
+ transmit_notify_request(sub, "L/ro");
+ } else {
+ transmit_notify_request(sub, "");
+ }
+
+ ast->tech_pvt = NULL;
+ sub->alreadygone = 0;
+ sub->outgoing = 0;
+ sub->cxmode = MGCP_CX_INACTIVE;
+ sub->callid[0] = '\0';
+ /* Reset temporary destination */
+ memset(&sub->tmpdest, 0, sizeof(sub->tmpdest));
+ if (sub->rtp) {
+ ast_rtp_destroy(sub->rtp);
+ sub->rtp = NULL;
+ }
+
+ ast_module_unref(ast_module_info->self);
+
+ if ((p->hookstate == MGCP_ONHOOK) && (!sub->next->rtp)) {
+ p->hidecallerid = 0;
+ if (p->hascallwaiting && !p->callwaiting) {
+ ast_verb(3, "Enabling call waiting on %s\n", ast->name);
+ p->callwaiting = -1;
+ }
+ if (has_voicemail(p)) {
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP mgcp_hangup(%s) on %s@%s set vmwi(+)\n",
+ ast->name, p->name, p->parent->name);
+ }
+ transmit_notify_request(sub, "L/vmwi(+)");
+ } else {
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP mgcp_hangup(%s) on %s@%s set vmwi(-)\n",
+ ast->name, p->name, p->parent->name);
+ }
+ transmit_notify_request(sub, "L/vmwi(-)");
+ }
+ }
+ ast_mutex_unlock(&sub->lock);
+ return 0;
+}
+
+static char *handle_mgcp_show_endpoints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct mgcp_gateway *mg;
+ struct mgcp_endpoint *me;
+ int hasendpoints = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mgcp show endpoints";
+ e->usage =
+ "Usage: mgcp show endpoints\n"
+ " Lists all endpoints known to the MGCP (Media Gateway Control Protocol) subsystem.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ ast_mutex_lock(&gatelock);
+ mg = gateways;
+ while(mg) {
+ me = mg->endpoints;
+ ast_cli(a->fd, "Gateway '%s' at %s (%s)\n", mg->name, mg->addr.sin_addr.s_addr ? ast_inet_ntoa(mg->addr.sin_addr) : ast_inet_ntoa(mg->defaddr.sin_addr), mg->dynamic ? "Dynamic" : "Static");
+ while(me) {
+ /* Don't show wilcard endpoint */
+ if (strcmp(me->name, mg->wcardep) != 0)
+ ast_cli(a->fd, " -- '%s@%s in '%s' is %s\n", me->name, mg->name, me->context, me->sub->owner ? "active" : "idle");
+ hasendpoints = 1;
+ me = me->next;
+ }
+ if (!hasendpoints) {
+ ast_cli(a->fd, " << No Endpoints Defined >> ");
+ }
+ mg = mg->next;
+ }
+ ast_mutex_unlock(&gatelock);
+ return CLI_SUCCESS;
+}
+
+static char *handle_mgcp_audit_endpoint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct mgcp_gateway *mg;
+ struct mgcp_endpoint *me;
+ int found = 0;
+ char *ename,*gname, *c;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mgcp audit endpoint";
+ e->usage =
+ "Usage: mgcp audit endpoint <endpointid>\n"
+ " Lists the capabilities of an endpoint in the MGCP (Media Gateway Control Protocol) subsystem.\n"
+ " mgcp debug MUST be on to see the results of this command.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (!mgcpdebug) {
+ return CLI_SHOWUSAGE;
+ }
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+ /* split the name into parts by null */
+ ename = a->argv[3];
+ gname = ename;
+ while (*gname) {
+ if (*gname == '@') {
+ *gname = 0;
+ gname++;
+ break;
+ }
+ gname++;
+ }
+ if (gname[0] == '[')
+ gname++;
+ if ((c = strrchr(gname, ']')))
+ *c = '\0';
+ ast_mutex_lock(&gatelock);
+ mg = gateways;
+ while(mg) {
+ if (!strcasecmp(mg->name, gname)) {
+ me = mg->endpoints;
+ while(me) {
+ if (!strcasecmp(me->name, ename)) {
+ found = 1;
+ transmit_audit_endpoint(me);
+ break;
+ }
+ me = me->next;
+ }
+ if (found) {
+ break;
+ }
+ }
+ mg = mg->next;
+ }
+ if (!found) {
+ ast_cli(a->fd, " << Could not find endpoint >> ");
+ }
+ ast_mutex_unlock(&gatelock);
+ return CLI_SUCCESS;
+}
+
+static char *handle_mgcp_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mgcp set debug";
+ e->usage =
+ "Usage: mgcp set debug\n"
+ " Enables dumping of MGCP packets for debugging purposes\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ mgcpdebug = 1;
+ ast_cli(a->fd, "MGCP Debugging Enabled\n");
+ return CLI_SUCCESS;
+}
+
+static char *handle_mgcp_set_debug_off(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mgcp set debug off";
+ e->usage =
+ "Usage: mgcp set debug off\n"
+ " Disables dumping of MGCP packets for debugging purposes\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+ mgcpdebug = 0;
+ ast_cli(a->fd, "MGCP Debugging Disabled\n");
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_mgcp[] = {
+ AST_CLI_DEFINE(handle_mgcp_audit_endpoint, "Audit specified MGCP endpoint"),
+ AST_CLI_DEFINE(handle_mgcp_show_endpoints, "List defined MGCP endpoints"),
+ AST_CLI_DEFINE(handle_mgcp_set_debug, "Enable MGCP debugging"),
+ AST_CLI_DEFINE(handle_mgcp_set_debug_off, "Disable MGCP debugging"),
+ AST_CLI_DEFINE(mgcp_reload, "Reload MGCP configuration"),
+};
+
+static int mgcp_answer(struct ast_channel *ast)
+{
+ int res = 0;
+ struct mgcp_subchannel *sub = ast->tech_pvt;
+ struct mgcp_endpoint *p = sub->parent;
+
+ ast_mutex_lock(&sub->lock);
+ sub->cxmode = MGCP_CX_SENDRECV;
+ if (!sub->rtp) {
+ start_rtp(sub);
+ } else {
+ transmit_modify_request(sub);
+ }
+ ast_verb(3, "MGCP mgcp_answer(%s) on %s@%s-%d\n",
+ ast->name, p->name, p->parent->name, sub->id);
+ if (ast->_state != AST_STATE_UP) {
+ ast_setstate(ast, AST_STATE_UP);
+ ast_debug(1, "mgcp_answer(%s)\n", ast->name);
+ transmit_notify_request(sub, "");
+ transmit_modify_request(sub);
+ }
+ ast_mutex_unlock(&sub->lock);
+ return res;
+}
+
+static struct ast_frame *mgcp_rtp_read(struct mgcp_subchannel *sub)
+{
+ /* Retrieve audio/etc from channel. Assumes sub->lock is already held. */
+ struct ast_frame *f;
+
+ f = ast_rtp_read(sub->rtp);
+ /* Don't send RFC2833 if we're not supposed to */
+ if (f && (f->frametype == AST_FRAME_DTMF) && !(sub->parent->dtmfmode & MGCP_DTMF_RFC2833))
+ return &ast_null_frame;
+ if (sub->owner) {
+ /* We already hold the channel lock */
+ if (f->frametype == AST_FRAME_VOICE) {
+ if (f->subclass != sub->owner->nativeformats) {
+ ast_debug(1, "Oooh, format changed to %d\n", f->subclass);
+ sub->owner->nativeformats = f->subclass;
+ ast_set_read_format(sub->owner, sub->owner->readformat);
+ ast_set_write_format(sub->owner, sub->owner->writeformat);
+ }
+ /* Courtesy fearnor aka alex@pilosoft.com */
+ if ((sub->parent->dtmfmode & MGCP_DTMF_INBAND) && (sub->parent->dsp)) {
+#if 0
+ ast_log(LOG_NOTICE, "MGCP ast_dsp_process\n");
+#endif
+ f = ast_dsp_process(sub->owner, sub->parent->dsp, f);
+ }
+ }
+ }
+ return f;
+}
+
+
+static struct ast_frame *mgcp_read(struct ast_channel *ast)
+{
+ struct ast_frame *f;
+ struct mgcp_subchannel *sub = ast->tech_pvt;
+ ast_mutex_lock(&sub->lock);
+ f = mgcp_rtp_read(sub);
+ ast_mutex_unlock(&sub->lock);
+ return f;
+}
+
+static int mgcp_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct mgcp_subchannel *sub = ast->tech_pvt;
+ int res = 0;
+ if (frame->frametype != AST_FRAME_VOICE) {
+ if (frame->frametype == AST_FRAME_IMAGE)
+ return 0;
+ else {
+ ast_log(LOG_WARNING, "Can't send %d type frames with MGCP write\n", frame->frametype);
+ return 0;
+ }
+ } else {
+ if (!(frame->subclass & ast->nativeformats)) {
+ ast_log(LOG_WARNING, "Asked to transmit frame type %d, while native formats is %d (read/write = %d/%d)\n",
+ frame->subclass, ast->nativeformats, ast->readformat, ast->writeformat);
+ return -1;
+ }
+ }
+ if (sub) {
+ ast_mutex_lock(&sub->lock);
+ if ((sub->parent->sub == sub) || !sub->parent->singlepath) {
+ if (sub->rtp) {
+ res = ast_rtp_write(sub->rtp, frame);
+ }
+ }
+ ast_mutex_unlock(&sub->lock);
+ }
+ return res;
+}
+
+static int mgcp_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct mgcp_subchannel *sub = newchan->tech_pvt;
+
+ ast_mutex_lock(&sub->lock);
+ ast_log(LOG_NOTICE, "mgcp_fixup(%s, %s)\n", oldchan->name, newchan->name);
+ if (sub->owner != oldchan) {
+ ast_mutex_unlock(&sub->lock);
+ ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, sub->owner);
+ return -1;
+ }
+ sub->owner = newchan;
+ ast_mutex_unlock(&sub->lock);
+ return 0;
+}
+
+static int mgcp_senddigit_begin(struct ast_channel *ast, char digit)
+{
+ struct mgcp_subchannel *sub = ast->tech_pvt;
+ struct mgcp_endpoint *p = sub->parent;
+ int res = 0;
+
+ ast_mutex_lock(&sub->lock);
+ if (p->dtmfmode & MGCP_DTMF_INBAND || p->dtmfmode & MGCP_DTMF_HYBRID) {
+ ast_log(LOG_DEBUG, "Sending DTMF using inband/hybrid\n");
+ res = -1; /* Let asterisk play inband indications */
+ } else if (p->dtmfmode & MGCP_DTMF_RFC2833) {
+ ast_log(LOG_DEBUG, "Sending DTMF using RFC2833");
+ ast_rtp_senddigit_begin(sub->rtp, digit);
+ } else {
+ ast_log(LOG_ERROR, "Don't know about DTMF_MODE %d\n", p->dtmfmode);
+ }
+ ast_mutex_unlock(&sub->lock);
+
+ return res;
+}
+
+static int mgcp_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct mgcp_subchannel *sub = ast->tech_pvt;
+ struct mgcp_endpoint *p = sub->parent;
+ int res = 0;
+ char tmp[4];
+
+ ast_mutex_lock(&sub->lock);
+ if (p->dtmfmode & MGCP_DTMF_INBAND || p->dtmfmode & MGCP_DTMF_HYBRID) {
+ ast_log(LOG_DEBUG, "Stopping DTMF using inband/hybrid\n");
+ res = -1; /* Tell Asterisk to stop inband indications */
+ } else if (p->dtmfmode & MGCP_DTMF_RFC2833) {
+ ast_log(LOG_DEBUG, "Stopping DTMF using RFC2833\n");
+ tmp[0] = 'D';
+ tmp[1] = '/';
+ tmp[2] = digit;
+ tmp[3] = '\0';
+ transmit_notify_request(sub, tmp);
+ ast_rtp_senddigit_end(sub->rtp, digit);
+ } else {
+ ast_log(LOG_ERROR, "Don't know about DTMF_MODE %d\n", p->dtmfmode);
+ }
+ ast_mutex_unlock(&sub->lock);
+
+ return res;
+}
+
+/*!
+ * \brief mgcp_devicestate: channel callback for device status monitoring
+ * \param data tech/resource name of MGCP device to query
+ *
+ * Callback for device state management in channel subsystem
+ * to obtain device status (up/down) of a specific MGCP endpoint
+ *
+ * \return device status result (from devicestate.h) AST_DEVICE_INVALID (not available) or AST_DEVICE_UNKNOWN (available but unknown state)
+ */
+static int mgcp_devicestate(void *data)
+{
+ struct mgcp_gateway *g;
+ struct mgcp_endpoint *e = NULL;
+ char *tmp, *endpt, *gw;
+ int ret = AST_DEVICE_INVALID;
+
+ endpt = ast_strdupa(data);
+ if ((tmp = strchr(endpt, '@'))) {
+ *tmp++ = '\0';
+ gw = tmp;
+ } else
+ goto error;
+
+ ast_mutex_lock(&gatelock);
+ g = gateways;
+ while (g) {
+ if (strcasecmp(g->name, gw) == 0) {
+ e = g->endpoints;
+ break;
+ }
+ g = g->next;
+ }
+
+ if (!e)
+ goto error;
+
+ while (e) {
+ if (strcasecmp(e->name, endpt) == 0)
+ break;
+ e = e->next;
+ }
+
+ if (!e)
+ goto error;
+
+ /*
+ * As long as the gateway/endpoint is valid, we'll
+ * assume that the device is available and its state
+ * can be tracked.
+ */
+ ret = AST_DEVICE_UNKNOWN;
+
+error:
+ ast_mutex_unlock(&gatelock);
+ return ret;
+}
+
+static char *control2str(int ind) {
+ switch (ind) {
+ case AST_CONTROL_HANGUP:
+ return "Other end has hungup";
+ case AST_CONTROL_RING:
+ return "Local ring";
+ case AST_CONTROL_RINGING:
+ return "Remote end is ringing";
+ case AST_CONTROL_ANSWER:
+ return "Remote end has answered";
+ case AST_CONTROL_BUSY:
+ return "Remote end is busy";
+ case AST_CONTROL_TAKEOFFHOOK:
+ return "Make it go off hook";
+ case AST_CONTROL_OFFHOOK:
+ return "Line is off hook";
+ case AST_CONTROL_CONGESTION:
+ return "Congestion (circuits busy)";
+ case AST_CONTROL_FLASH:
+ return "Flash hook";
+ case AST_CONTROL_WINK:
+ return "Wink";
+ case AST_CONTROL_OPTION:
+ return "Set a low-level option";
+ case AST_CONTROL_RADIO_KEY:
+ return "Key Radio";
+ case AST_CONTROL_RADIO_UNKEY:
+ return "Un-Key Radio";
+ }
+ return "UNKNOWN";
+}
+
+static int mgcp_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen)
+{
+ struct mgcp_subchannel *sub = ast->tech_pvt;
+ int res = 0;
+
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP asked to indicate %d '%s' condition on channel %s\n",
+ ind, control2str(ind), ast->name);
+ }
+ ast_mutex_lock(&sub->lock);
+ switch(ind) {
+ case AST_CONTROL_RINGING:
+#ifdef DLINK_BUGGY_FIRMWARE
+ transmit_notify_request(sub, "rt");
+#else
+ transmit_notify_request(sub, "G/rt");
+#endif
+ break;
+ case AST_CONTROL_BUSY:
+ transmit_notify_request(sub, "L/bz");
+ break;
+ case AST_CONTROL_CONGESTION:
+ transmit_notify_request(sub, "G/cg");
+ break;
+ case AST_CONTROL_HOLD:
+ ast_moh_start(ast, data, NULL);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_moh_stop(ast);
+ break;
+ case -1:
+ transmit_notify_request(sub, "");
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", ind);
+ res = -1;
+ }
+ ast_mutex_unlock(&sub->lock);
+ return res;
+}
+
+static struct ast_channel *mgcp_new(struct mgcp_subchannel *sub, int state)
+{
+ struct ast_channel *tmp;
+ struct mgcp_endpoint *i = sub->parent;
+ int fmt;
+
+ tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, i->amaflags, "MGCP/%s@%s-%d", i->name, i->parent->name, sub->id);
+ if (tmp) {
+ tmp->tech = &mgcp_tech;
+ tmp->nativeformats = i->capability;
+ if (!tmp->nativeformats)
+ tmp->nativeformats = capability;
+ fmt = ast_best_codec(tmp->nativeformats);
+ ast_string_field_build(tmp, name, "MGCP/%s@%s-%d", i->name, i->parent->name, sub->id);
+ if (sub->rtp)
+ ast_channel_set_fd(tmp, 0, ast_rtp_fd(sub->rtp));
+ if (i->dtmfmode & (MGCP_DTMF_INBAND | MGCP_DTMF_HYBRID)) {
+ i->dsp = ast_dsp_new();
+ ast_dsp_set_features(i->dsp,DSP_FEATURE_DTMF_DETECT);
+ /* this is to prevent clipping of dtmf tones during dsp processing */
+ ast_dsp_digitmode(i->dsp, DSP_DIGITMODE_NOQUELCH);
+ } else {
+ i->dsp = NULL;
+ }
+ if (state == AST_STATE_RING)
+ tmp->rings = 1;
+ tmp->writeformat = fmt;
+ tmp->rawwriteformat = fmt;
+ tmp->readformat = fmt;
+ tmp->rawreadformat = fmt;
+ tmp->tech_pvt = sub;
+ if (!ast_strlen_zero(i->language))
+ ast_string_field_set(tmp, language, i->language);
+ if (!ast_strlen_zero(i->accountcode))
+ ast_string_field_set(tmp, accountcode, i->accountcode);
+ if (i->amaflags)
+ tmp->amaflags = i->amaflags;
+ sub->owner = tmp;
+ ast_module_ref(ast_module_info->self);
+ tmp->callgroup = i->callgroup;
+ tmp->pickupgroup = i->pickupgroup;
+ ast_string_field_set(tmp, call_forward, i->call_forward);
+ ast_copy_string(tmp->context, i->context, sizeof(tmp->context));
+ ast_copy_string(tmp->exten, i->exten, sizeof(tmp->exten));
+
+ /* Don't use ast_set_callerid() here because it will
+ * generate a needless NewCallerID event */
+ tmp->cid.cid_ani = ast_strdup(i->cid_num);
+
+ if (!i->adsi)
+ tmp->adsicpe = AST_ADSI_UNAVAILABLE;
+ tmp->priority = 1;
+ if (sub->rtp)
+ ast_jb_configure(tmp, &global_jbconf);
+ if (state != AST_STATE_DOWN) {
+ if (ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ ast_hangup(tmp);
+ tmp = NULL;
+ }
+ }
+ ast_verb(3, "MGCP mgcp_new(%s) created in state: %s\n",
+ tmp->name, ast_state2str(state));
+ } else {
+ ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+ }
+ return tmp;
+}
+
+static char* get_sdp_by_line(char* line, char *name, int nameLen)
+{
+ if (strncasecmp(line, name, nameLen) == 0 && line[nameLen] == '=') {
+ char* r = line + nameLen + 1;
+ while (*r && (*r < 33)) ++r;
+ return r;
+ }
+ return "";
+}
+
+static char *get_sdp(struct mgcp_request *req, char *name)
+{
+ int x;
+ int len = strlen(name);
+ char *r;
+
+ for (x=0; x<req->lines; x++) {
+ r = get_sdp_by_line(req->line[x], name, len);
+ if (r[0] != '\0') return r;
+ }
+ return "";
+}
+
+static void sdpLineNum_iterator_init(int* iterator)
+{
+ *iterator = 0;
+}
+
+static char* get_sdp_iterate(int* iterator, struct mgcp_request *req, char *name)
+{
+ int len = strlen(name);
+ char *r;
+ while (*iterator < req->lines) {
+ r = get_sdp_by_line(req->line[(*iterator)++], name, len);
+ if (r[0] != '\0') return r;
+ }
+ return "";
+}
+
+static char *__get_header(struct mgcp_request *req, char *name, int *start)
+{
+ int x;
+ int len = strlen(name);
+ char *r;
+ for (x=*start;x<req->headers;x++) {
+ if (!strncasecmp(req->header[x], name, len) &&
+ (req->header[x][len] == ':')) {
+ r = req->header[x] + len + 1;
+ while(*r && (*r < 33))
+ r++;
+ *start = x+1;
+ return r;
+ }
+ }
+ /* Don't return NULL, so get_header is always a valid pointer */
+ return "";
+}
+
+static char *get_header(struct mgcp_request *req, char *name)
+{
+ int start = 0;
+ return __get_header(req, name, &start);
+}
+
+/*! \brief get_csv: (SC:) get comma separated value */
+static char *get_csv(char *c, int *len, char **next)
+{
+ char *s;
+
+ *next = NULL, *len = 0;
+ if (!c) return NULL;
+
+ while (*c && (*c < 33 || *c == ','))
+ c++;
+
+ s = c;
+ while (*c && (*c >= 33 && *c != ','))
+ c++, (*len)++;
+ *next = c;
+
+ if (*len == 0)
+ s = NULL, *next = NULL;
+
+ return s;
+}
+
+static struct mgcp_subchannel *find_subchannel_and_lock(char *name, int msgid, struct sockaddr_in *sin)
+{
+ struct mgcp_endpoint *p = NULL;
+ struct mgcp_subchannel *sub = NULL;
+ struct mgcp_gateway *g;
+ char tmp[256] = "";
+ char *at = NULL, *c;
+ int found = 0;
+ if (name) {
+ ast_copy_string(tmp, name, sizeof(tmp));
+ at = strchr(tmp, '@');
+ if (!at) {
+ ast_log(LOG_NOTICE, "Endpoint '%s' has no at sign!\n", name);
+ return NULL;
+ }
+ *at++ = '\0';
+ }
+ ast_mutex_lock(&gatelock);
+ if (at && (at[0] == '[')) {
+ at++;
+ c = strrchr(at, ']');
+ if (c)
+ *c = '\0';
+ }
+ g = gateways;
+ while(g) {
+ if ((!name || !strcasecmp(g->name, at)) &&
+ (sin || g->addr.sin_addr.s_addr || g->defaddr.sin_addr.s_addr)) {
+ /* Found the gateway. If it's dynamic, save it's address -- now for the endpoint */
+ if (sin && g->dynamic && name) {
+ if ((g->addr.sin_addr.s_addr != sin->sin_addr.s_addr) ||
+ (g->addr.sin_port != sin->sin_port)) {
+ memcpy(&g->addr, sin, sizeof(g->addr));
+ if (ast_ouraddrfor(&g->addr.sin_addr, &g->ourip))
+ memcpy(&g->ourip, &__ourip, sizeof(g->ourip));
+ ast_verb(3, "Registered MGCP gateway '%s' at %s port %d\n", g->name, ast_inet_ntoa(g->addr.sin_addr), ntohs(g->addr.sin_port));
+ }
+ }
+ /* not dynamic, check if the name matches */
+ else if (name) {
+ if (strcasecmp(g->name, at)) {
+ g = g->next;
+ continue;
+ }
+ }
+ /* not dynamic, no name, check if the addr matches */
+ else if (!name && sin) {
+ if ((g->addr.sin_addr.s_addr != sin->sin_addr.s_addr) ||
+ (g->addr.sin_port != sin->sin_port)) {
+ g = g->next;
+ continue;
+ }
+ } else {
+ g = g->next;
+ continue;
+ }
+ /* SC */
+ p = g->endpoints;
+ while(p) {
+ ast_debug(1, "Searching on %s@%s for subchannel\n",
+ p->name, g->name);
+ if (msgid) {
+#if 0 /* new transport mech */
+ sub = p->sub;
+ do {
+ ast_debug(1, "Searching on %s@%s-%d for subchannel with lastout: %d\n",
+ p->name, g->name, sub->id, msgid);
+ if (sub->lastout == msgid) {
+ ast_debug(1, "Found subchannel sub%d to handle request %d sub->lastout: %d\n",
+ sub->id, msgid, sub->lastout);
+ found = 1;
+ break;
+ }
+ sub = sub->next;
+ } while (sub != p->sub);
+ if (found) {
+ break;
+ }
+#endif
+ /* SC */
+ sub = p->sub;
+ found = 1;
+ /* SC */
+ break;
+ } else if (name && !strcasecmp(p->name, tmp)) {
+ ast_debug(1, "Coundn't determine subchannel, assuming current master %s@%s-%d\n",
+ p->name, g->name, p->sub->id);
+ sub = p->sub;
+ found = 1;
+ break;
+ }
+ p = p->next;
+ }
+ if (sub && found) {
+ ast_mutex_lock(&sub->lock);
+ break;
+ }
+ }
+ g = g->next;
+ }
+ ast_mutex_unlock(&gatelock);
+ if (!sub) {
+ if (name) {
+ if (g)
+ ast_log(LOG_NOTICE, "Endpoint '%s' not found on gateway '%s'\n", tmp, at);
+ else
+ ast_log(LOG_NOTICE, "Gateway '%s' (and thus its endpoint '%s') does not exist\n", at, tmp);
+ }
+ }
+ return sub;
+}
+
+static void parse(struct mgcp_request *req)
+{
+ /* Divide fields by NULL's */
+ char *c;
+ int f = 0;
+ c = req->data;
+
+ /* First header starts immediately */
+ req->header[f] = c;
+ while(*c) {
+ if (*c == '\n') {
+ /* We've got a new header */
+ *c = 0;
+#if 0
+ printf("Header: %s (%d)\n", req->header[f], strlen(req->header[f]));
+#endif
+ if (ast_strlen_zero(req->header[f])) {
+ /* Line by itself means we're now in content */
+ c++;
+ break;
+ }
+ if (f >= MGCP_MAX_HEADERS - 1) {
+ ast_log(LOG_WARNING, "Too many MGCP headers...\n");
+ } else
+ f++;
+ req->header[f] = c + 1;
+ } else if (*c == '\r') {
+ /* Ignore but eliminate \r's */
+ *c = 0;
+ }
+ c++;
+ }
+ /* Check for last header */
+ if (!ast_strlen_zero(req->header[f]))
+ f++;
+ req->headers = f;
+ /* Now we process any mime content */
+ f = 0;
+ req->line[f] = c;
+ while(*c) {
+ if (*c == '\n') {
+ /* We've got a new line */
+ *c = 0;
+#if 0
+ printf("Line: %s (%d)\n", req->line[f], strlen(req->line[f]));
+#endif
+ if (f >= MGCP_MAX_LINES - 1) {
+ ast_log(LOG_WARNING, "Too many SDP lines...\n");
+ } else
+ f++;
+ req->line[f] = c + 1;
+ } else if (*c == '\r') {
+ /* Ignore and eliminate \r's */
+ *c = 0;
+ }
+ c++;
+ }
+ /* Check for last line */
+ if (!ast_strlen_zero(req->line[f]))
+ f++;
+ req->lines = f;
+ /* Parse up the initial header */
+ c = req->header[0];
+ while(*c && *c < 33) c++;
+ /* First the verb */
+ req->verb = c;
+ while(*c && (*c > 32)) c++;
+ if (*c) {
+ *c = '\0';
+ c++;
+ while(*c && (*c < 33)) c++;
+ req->identifier = c;
+ while(*c && (*c > 32)) c++;
+ if (*c) {
+ *c = '\0';
+ c++;
+ while(*c && (*c < 33)) c++;
+ req->endpoint = c;
+ while(*c && (*c > 32)) c++;
+ if (*c) {
+ *c = '\0';
+ c++;
+ while(*c && (*c < 33)) c++;
+ req->version = c;
+ while(*c && (*c > 32)) c++;
+ while(*c && (*c < 33)) c++;
+ while(*c && (*c > 32)) c++;
+ *c = '\0';
+ }
+ }
+ }
+
+ if (mgcpdebug) {
+ ast_verbose("Verb: '%s', Identifier: '%s', Endpoint: '%s', Version: '%s'\n",
+ req->verb, req->identifier, req->endpoint, req->version);
+ ast_verbose("%d headers, %d lines\n", req->headers, req->lines);
+ }
+ if (*c)
+ ast_log(LOG_WARNING, "Odd content, extra stuff left over ('%s')\n", c);
+}
+
+static int process_sdp(struct mgcp_subchannel *sub, struct mgcp_request *req)
+{
+ char *m;
+ char *c;
+ char *a;
+ char host[258];
+ int len;
+ int portno;
+ int peercapability, peerNonCodecCapability;
+ struct sockaddr_in sin;
+ char *codecs;
+ struct ast_hostent ahp; struct hostent *hp;
+ int codec, codec_count=0;
+ int iterator;
+ struct mgcp_endpoint *p = sub->parent;
+
+ /* Get codec and RTP info from SDP */
+ m = get_sdp(req, "m");
+ c = get_sdp(req, "c");
+ if (ast_strlen_zero(m) || ast_strlen_zero(c)) {
+ ast_log(LOG_WARNING, "Insufficient information for SDP (m = '%s', c = '%s')\n", m, c);
+ return -1;
+ }
+ if (sscanf(c, "IN IP4 %256s", host) != 1) {
+ ast_log(LOG_WARNING, "Invalid host in c= line, '%s'\n", c);
+ return -1;
+ }
+ /* XXX This could block for a long time, and block the main thread! XXX */
+ hp = ast_gethostbyname(host, &ahp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "Unable to lookup host in c= line, '%s'\n", c);
+ return -1;
+ }
+ if (sscanf(m, "audio %d RTP/AVP %n", &portno, &len) != 1) {
+ ast_log(LOG_WARNING, "Unable to determine port number for RTP in '%s'\n", m);
+ return -1;
+ }
+ sin.sin_family = AF_INET;
+ memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+ sin.sin_port = htons(portno);
+ ast_rtp_set_peer(sub->rtp, &sin);
+#if 0
+ printf("Peer RTP is at port %s:%d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+#endif
+ /* Scan through the RTP payload types specified in a "m=" line: */
+ ast_rtp_pt_clear(sub->rtp);
+ codecs = ast_strdupa(m + len);
+ while (!ast_strlen_zero(codecs)) {
+ if (sscanf(codecs, "%d%n", &codec, &len) != 1) {
+ if (codec_count)
+ break;
+ ast_log(LOG_WARNING, "Error in codec string '%s' at '%s'\n", m, codecs);
+ return -1;
+ }
+ ast_rtp_set_m_type(sub->rtp, codec);
+ codec_count++;
+ codecs += len;
+ }
+
+ /* Next, scan through each "a=rtpmap:" line, noting each */
+ /* specified RTP payload type (with corresponding MIME subtype): */
+ sdpLineNum_iterator_init(&iterator);
+ while ((a = get_sdp_iterate(&iterator, req, "a"))[0] != '\0') {
+ char* mimeSubtype = ast_strdupa(a); /* ensures we have enough space */
+ if (sscanf(a, "rtpmap: %u %[^/]/", &codec, mimeSubtype) != 2)
+ continue;
+ /* Note: should really look at the 'freq' and '#chans' params too */
+ ast_rtp_set_rtpmap_type(sub->rtp, codec, "audio", mimeSubtype, 0);
+ }
+
+ /* Now gather all of the codecs that were asked for: */
+ ast_rtp_get_current_formats(sub->rtp, &peercapability, &peerNonCodecCapability);
+ p->capability = capability & peercapability;
+ if (mgcpdebug) {
+ ast_verbose("Capabilities: us - %d, them - %d, combined - %d\n",
+ capability, peercapability, p->capability);
+ ast_verbose("Non-codec capabilities: us - %d, them - %d, combined - %d\n",
+ nonCodecCapability, peerNonCodecCapability, p->nonCodecCapability);
+ }
+ if (!p->capability) {
+ ast_log(LOG_WARNING, "No compatible codecs!\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int add_header(struct mgcp_request *req, char *var, char *value)
+{
+ if (req->len >= sizeof(req->data) - 4) {
+ ast_log(LOG_WARNING, "Out of space, can't add anymore\n");
+ return -1;
+ }
+ if (req->lines) {
+ ast_log(LOG_WARNING, "Can't add more headers when lines have been added\n");
+ return -1;
+ }
+ req->header[req->headers] = req->data + req->len;
+ snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s: %s\r\n", var, value);
+ req->len += strlen(req->header[req->headers]);
+ if (req->headers < MGCP_MAX_HEADERS)
+ req->headers++;
+ else {
+ ast_log(LOG_WARNING, "Out of header space\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int add_line(struct mgcp_request *req, char *line)
+{
+ if (req->len >= sizeof(req->data) - 4) {
+ ast_log(LOG_WARNING, "Out of space, can't add anymore\n");
+ return -1;
+ }
+ if (!req->lines) {
+ /* Add extra empty return */
+ ast_copy_string(req->data + req->len, "\r\n", sizeof(req->data) - req->len);
+ req->len += strlen(req->data + req->len);
+ }
+ req->line[req->lines] = req->data + req->len;
+ snprintf(req->line[req->lines], sizeof(req->data) - req->len, "%s", line);
+ req->len += strlen(req->line[req->lines]);
+ if (req->lines < MGCP_MAX_LINES)
+ req->lines++;
+ else {
+ ast_log(LOG_WARNING, "Out of line space\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int init_resp(struct mgcp_request *req, char *resp, struct mgcp_request *orig, char *resprest)
+{
+ /* Initialize a response */
+ if (req->headers || req->len) {
+ ast_log(LOG_WARNING, "Request already initialized?!?\n");
+ return -1;
+ }
+ req->header[req->headers] = req->data + req->len;
+ snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s %s %s\r\n", resp, orig->identifier, resprest);
+ req->len += strlen(req->header[req->headers]);
+ if (req->headers < MGCP_MAX_HEADERS)
+ req->headers++;
+ else
+ ast_log(LOG_WARNING, "Out of header space\n");
+ return 0;
+}
+
+static int init_req(struct mgcp_endpoint *p, struct mgcp_request *req, char *verb)
+{
+ /* Initialize a response */
+ if (req->headers || req->len) {
+ ast_log(LOG_WARNING, "Request already initialized?!?\n");
+ return -1;
+ }
+ req->header[req->headers] = req->data + req->len;
+ /* check if we need brackets around the gw name */
+ if (p->parent->isnamedottedip)
+ snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s %d %s@[%s] MGCP 1.0\r\n", verb, oseq, p->name, p->parent->name);
+ else
+ snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s %d %s@%s MGCP 1.0\r\n", verb, oseq, p->name, p->parent->name);
+ req->len += strlen(req->header[req->headers]);
+ if (req->headers < MGCP_MAX_HEADERS)
+ req->headers++;
+ else
+ ast_log(LOG_WARNING, "Out of header space\n");
+ return 0;
+}
+
+
+static int respprep(struct mgcp_request *resp, struct mgcp_endpoint *p, char *msg, struct mgcp_request *req, char *msgrest)
+{
+ memset(resp, 0, sizeof(*resp));
+ init_resp(resp, msg, req, msgrest);
+ return 0;
+}
+
+static int reqprep(struct mgcp_request *req, struct mgcp_endpoint *p, char *verb)
+{
+ memset(req, 0, sizeof(struct mgcp_request));
+ oseq++;
+ if (oseq > 999999999)
+ oseq = 1;
+ init_req(p, req, verb);
+ return 0;
+}
+
+static int transmit_response(struct mgcp_subchannel *sub, char *msg, struct mgcp_request *req, char *msgrest)
+{
+ struct mgcp_request resp;
+ struct mgcp_endpoint *p = sub->parent;
+ struct mgcp_response *mgr;
+
+ respprep(&resp, p, msg, req, msgrest);
+ mgr = ast_calloc(1, sizeof(*mgr) + resp.len + 1);
+ if (mgr) {
+ /* Store MGCP response in case we have to retransmit */
+ sscanf(req->identifier, "%d", &mgr->seqno);
+ time(&mgr->whensent);
+ mgr->len = resp.len;
+ memcpy(mgr->buf, resp.data, resp.len);
+ mgr->buf[resp.len] = '\0';
+ mgr->next = p->parent->responses;
+ p->parent->responses = mgr;
+ }
+ return send_response(sub, &resp);
+}
+
+
+static int add_sdp(struct mgcp_request *resp, struct mgcp_subchannel *sub, struct ast_rtp *rtp)
+{
+ int len;
+ int codec;
+ char costr[80];
+ struct sockaddr_in sin;
+ char v[256];
+ char s[256];
+ char o[256];
+ char c[256];
+ char t[256];
+ char m[256] = "";
+ char a[1024] = "";
+ int x;
+ struct sockaddr_in dest;
+ struct mgcp_endpoint *p = sub->parent;
+ /* XXX We break with the "recommendation" and send our IP, in order that our
+ peer doesn't have to ast_gethostbyname() us XXX */
+ len = 0;
+ if (!sub->rtp) {
+ ast_log(LOG_WARNING, "No way to add SDP without an RTP structure\n");
+ return -1;
+ }
+ ast_rtp_get_us(sub->rtp, &sin);
+ if (rtp) {
+ ast_rtp_get_peer(rtp, &dest);
+ } else {
+ if (sub->tmpdest.sin_addr.s_addr) {
+ dest.sin_addr = sub->tmpdest.sin_addr;
+ dest.sin_port = sub->tmpdest.sin_port;
+ /* Reset temporary destination */
+ memset(&sub->tmpdest, 0, sizeof(sub->tmpdest));
+ } else {
+ dest.sin_addr = p->parent->ourip;
+ dest.sin_port = sin.sin_port;
+ }
+ }
+ if (mgcpdebug) {
+ ast_verbose("We're at %s port %d\n", ast_inet_ntoa(p->parent->ourip), ntohs(sin.sin_port));
+ }
+ ast_copy_string(v, "v=0\r\n", sizeof(v));
+ snprintf(o, sizeof(o), "o=root %d %d IN IP4 %s\r\n", (int)getpid(), (int)getpid(), ast_inet_ntoa(dest.sin_addr));
+ ast_copy_string(s, "s=session\r\n", sizeof(s));
+ snprintf(c, sizeof(c), "c=IN IP4 %s\r\n", ast_inet_ntoa(dest.sin_addr));
+ ast_copy_string(t, "t=0 0\r\n", sizeof(t));
+ snprintf(m, sizeof(m), "m=audio %d RTP/AVP", ntohs(dest.sin_port));
+ for (x = 1; x <= AST_FORMAT_AUDIO_MASK; x <<= 1) {
+ if (p->capability & x) {
+ if (mgcpdebug) {
+ ast_verbose("Answering with capability %d\n", x);
+ }
+ codec = ast_rtp_lookup_code(sub->rtp, 1, x);
+ if (codec > -1) {
+ snprintf(costr, sizeof(costr), " %d", codec);
+ strncat(m, costr, sizeof(m) - strlen(m) - 1);
+ snprintf(costr, sizeof(costr), "a=rtpmap:%d %s/8000\r\n", codec, ast_rtp_lookup_mime_subtype(1, x, 0));
+ strncat(a, costr, sizeof(a) - strlen(a) - 1);
+ }
+ }
+ }
+ for (x = 1; x <= AST_RTP_MAX; x <<= 1) {
+ if (p->nonCodecCapability & x) {
+ if (mgcpdebug) {
+ ast_verbose("Answering with non-codec capability %d\n", x);
+ }
+ codec = ast_rtp_lookup_code(sub->rtp, 0, x);
+ if (codec > -1) {
+ snprintf(costr, sizeof(costr), " %d", codec);
+ strncat(m, costr, sizeof(m) - strlen(m) - 1);
+ snprintf(costr, sizeof(costr), "a=rtpmap:%d %s/8000\r\n", codec, ast_rtp_lookup_mime_subtype(0, x, 0));
+ strncat(a, costr, sizeof(a) - strlen(a) - 1);
+ if (x == AST_RTP_DTMF) {
+ /* Indicate we support DTMF... Not sure about 16,
+ but MSN supports it so dang it, we will too... */
+ snprintf(costr, sizeof costr, "a=fmtp:%d 0-16\r\n", codec);
+ strncat(a, costr, sizeof(a) - strlen(a) - 1);
+ }
+ }
+ }
+ }
+ strncat(m, "\r\n", sizeof(m) - strlen(m) - 1);
+ len = strlen(v) + strlen(s) + strlen(o) + strlen(c) + strlen(t) + strlen(m) + strlen(a);
+ snprintf(costr, sizeof(costr), "%d", len);
+ add_line(resp, v);
+ add_line(resp, o);
+ add_line(resp, s);
+ add_line(resp, c);
+ add_line(resp, t);
+ add_line(resp, m);
+ add_line(resp, a);
+ return 0;
+}
+
+static int transmit_modify_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp *rtp, int codecs)
+{
+ struct mgcp_request resp;
+ char local[256];
+ char tmp[80];
+ int x;
+ int capability;
+ struct mgcp_endpoint *p = sub->parent;
+
+ capability = p->capability;
+ if (codecs)
+ capability = codecs;
+ if (ast_strlen_zero(sub->cxident) && rtp) {
+ /* We don't have a CXident yet, store the destination and
+ wait a bit */
+ ast_rtp_get_peer(rtp, &sub->tmpdest);
+ return 0;
+ }
+ ast_copy_string(local, "p:20", sizeof(local));
+ for (x = 1; x <= AST_FORMAT_AUDIO_MASK; x <<= 1) {
+ if (p->capability & x) {
+ snprintf(tmp, sizeof(tmp), ", a:%s", ast_rtp_lookup_mime_subtype(1, x, 0));
+ strncat(local, tmp, sizeof(local) - strlen(local) - 1);
+ }
+ }
+ reqprep(&resp, p, "MDCX");
+ add_header(&resp, "C", sub->callid);
+ add_header(&resp, "L", local);
+ add_header(&resp, "M", mgcp_cxmodes[sub->cxmode]);
+ /* X header should not be sent. kept for compatibility */
+ add_header(&resp, "X", sub->txident);
+ add_header(&resp, "I", sub->cxident);
+ /*add_header(&resp, "S", "");*/
+ add_sdp(&resp, sub, rtp);
+ /* fill in new fields */
+ resp.cmd = MGCP_CMD_MDCX;
+ resp.trid = oseq;
+ return send_request(p, sub, &resp, oseq); /* SC */
+}
+
+static int transmit_connect_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp *rtp)
+{
+ struct mgcp_request resp;
+ char local[256];
+ char tmp[80];
+ int x;
+ struct mgcp_endpoint *p = sub->parent;
+
+ ast_copy_string(local, "p:20", sizeof(local));
+ for (x = 1; x <= AST_FORMAT_AUDIO_MASK; x <<= 1) {
+ if (p->capability & x) {
+ snprintf(tmp, sizeof(tmp), ", a:%s", ast_rtp_lookup_mime_subtype(1, x, 0));
+ strncat(local, tmp, sizeof(local) - strlen(local) - 1);
+ }
+ }
+ if (mgcpdebug) {
+ ast_verb(3, "Creating connection for %s@%s-%d in cxmode: %s callid: %s\n",
+ p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode], sub->callid);
+ }
+ reqprep(&resp, p, "CRCX");
+ add_header(&resp, "C", sub->callid);
+ add_header(&resp, "L", local);
+ add_header(&resp, "M", mgcp_cxmodes[sub->cxmode]);
+ /* X header should not be sent. kept for compatibility */
+ add_header(&resp, "X", sub->txident);
+ /*add_header(&resp, "S", "");*/
+ add_sdp(&resp, sub, rtp);
+ /* fill in new fields */
+ resp.cmd = MGCP_CMD_CRCX;
+ resp.trid = oseq;
+ return send_request(p, sub, &resp, oseq); /* SC */
+}
+
+static int transmit_notify_request(struct mgcp_subchannel *sub, char *tone)
+{
+ struct mgcp_request resp;
+ struct mgcp_endpoint *p = sub->parent;
+
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP Asked to indicate tone: %s on %s@%s-%d in cxmode: %s\n",
+ tone, p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode]);
+ }
+ ast_copy_string(p->curtone, tone, sizeof(p->curtone));
+ reqprep(&resp, p, "RQNT");
+ add_header(&resp, "X", p->rqnt_ident); /* SC */
+ switch (p->hookstate) {
+ case MGCP_ONHOOK:
+ add_header(&resp, "R", "L/hd(N)");
+ break;
+ case MGCP_OFFHOOK:
+ add_header_offhook(sub, &resp);
+ break;
+ }
+ if (!ast_strlen_zero(tone)) {
+ add_header(&resp, "S", tone);
+ }
+ /* fill in new fields */
+ resp.cmd = MGCP_CMD_RQNT;
+ resp.trid = oseq;
+ return send_request(p, NULL, &resp, oseq); /* SC */
+}
+
+static int transmit_notify_request_with_callerid(struct mgcp_subchannel *sub, char *tone, char *callernum, char *callername)
+{
+ struct mgcp_request resp;
+ char tone2[256];
+ char *l, *n;
+ struct timeval t = ast_tvnow();
+ struct ast_tm tm;
+ struct mgcp_endpoint *p = sub->parent;
+
+ ast_localtime(&t, &tm, NULL);
+ n = callername;
+ l = callernum;
+ if (!n)
+ n = "";
+ if (!l)
+ l = "";
+
+ /* Keep track of last callerid for blacklist and callreturn */
+ ast_copy_string(p->lastcallerid, l, sizeof(p->lastcallerid));
+
+ snprintf(tone2, sizeof(tone2), "%s,L/ci(%02d/%02d/%02d/%02d,%s,%s)", tone,
+ tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, l, n);
+ ast_copy_string(p->curtone, tone, sizeof(p->curtone));
+ reqprep(&resp, p, "RQNT");
+ add_header(&resp, "X", p->rqnt_ident); /* SC */
+ switch (p->hookstate) {
+ case MGCP_ONHOOK:
+ add_header(&resp, "R", "L/hd(N)");
+ break;
+ case MGCP_OFFHOOK:
+ add_header_offhook(sub, &resp);
+ break;
+ }
+ if (!ast_strlen_zero(tone2)) {
+ add_header(&resp, "S", tone2);
+ }
+ if (mgcpdebug) {
+ ast_verb(3, "MGCP Asked to indicate tone: %s on %s@%s-%d in cxmode: %s\n",
+ tone2, p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode]);
+ }
+ /* fill in new fields */
+ resp.cmd = MGCP_CMD_RQNT;
+ resp.trid = oseq;
+ return send_request(p, NULL, &resp, oseq); /* SC */
+}
+
+static int transmit_modify_request(struct mgcp_subchannel *sub)
+{
+ struct mgcp_request resp;
+ struct mgcp_endpoint *p = sub->parent;
+
+ if (ast_strlen_zero(sub->cxident)) {
+ /* We don't have a CXident yet, store the destination and
+ wait a bit */
+ return 0;
+ }
+ if (mgcpdebug) {
+ ast_verb(3, "Modified %s@%s-%d with new mode: %s on callid: %s\n",
+ p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode], sub->callid);
+ }
+ reqprep(&resp, p, "MDCX");
+ add_header(&resp, "C", sub->callid);
+ add_header(&resp, "M", mgcp_cxmodes[sub->cxmode]);
+ /* X header should not be sent. kept for compatibility */
+ add_header(&resp, "X", sub->txident);
+ add_header(&resp, "I", sub->cxident);
+ switch (sub->parent->hookstate) {
+ case MGCP_ONHOOK:
+ add_header(&resp, "R", "L/hd(N)");
+ break;
+ case MGCP_OFFHOOK:
+ add_header_offhook(sub, &resp);
+ break;
+ }
+ /* fill in new fields */
+ resp.cmd = MGCP_CMD_MDCX;
+ resp.trid = oseq;
+ return send_request(p, sub, &resp, oseq); /* SC */
+}
+
+
+static void add_header_offhook(struct mgcp_subchannel *sub, struct mgcp_request *resp)
+{
+ struct mgcp_endpoint *p = sub->parent;
+
+ if (p && p->sub && p->sub->owner && p->sub->owner->_state >= AST_STATE_RINGING && (p->dtmfmode & (MGCP_DTMF_INBAND | MGCP_DTMF_HYBRID)))
+ add_header(resp, "R", "L/hu(N),L/hf(N)");
+ else
+ add_header(resp, "R", "L/hu(N),L/hf(N),D/[0-9#*](N)");
+}
+
+static int transmit_audit_endpoint(struct mgcp_endpoint *p)
+{
+ struct mgcp_request resp;
+ reqprep(&resp, p, "AUEP");
+ /* removed unknown param VS */
+ /*add_header(&resp, "F", "A,R,D,S,X,N,I,T,O,ES,E,MD,M");*/
+ add_header(&resp, "F", "A");
+ /* fill in new fields */
+ resp.cmd = MGCP_CMD_AUEP;
+ resp.trid = oseq;
+ return send_request(p, NULL, &resp, oseq); /* SC */
+}
+
+static int transmit_connection_del(struct mgcp_subchannel *sub)
+{
+ struct mgcp_endpoint *p = sub->parent;
+ struct mgcp_request resp;
+
+ if (mgcpdebug) {
+ ast_verb(3, "Delete connection %s %s@%s-%d with new mode: %s on callid: %s\n",
+ sub->cxident, p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode], sub->callid);
+ }
+ reqprep(&resp, p, "DLCX");
+ /* check if call id is avail */
+ if (sub->callid[0])
+ add_header(&resp, "C", sub->callid);
+ /* X header should not be sent. kept for compatibility */
+ add_header(&resp, "X", sub->txident);
+ /* check if cxident is avail */
+ if (sub->cxident[0])
+ add_header(&resp, "I", sub->cxident);
+ /* fill in new fields */
+ resp.cmd = MGCP_CMD_DLCX;
+ resp.trid = oseq;
+ return send_request(p, sub, &resp, oseq); /* SC */
+}
+
+static int transmit_connection_del_w_params(struct mgcp_endpoint *p, char *callid, char *cxident)
+{
+ struct mgcp_request resp;
+
+ if (mgcpdebug) {
+ ast_verb(3, "Delete connection %s %s@%s on callid: %s\n",
+ cxident ? cxident : "", p->name, p->parent->name, callid ? callid : "");
+ }
+ reqprep(&resp, p, "DLCX");
+ /* check if call id is avail */
+ if (callid && *callid)
+ add_header(&resp, "C", callid);
+ /* check if cxident is avail */
+ if (cxident && *cxident)
+ add_header(&resp, "I", cxident);
+ /* fill in new fields */
+ resp.cmd = MGCP_CMD_DLCX;
+ resp.trid = oseq;
+ return send_request(p, p->sub, &resp, oseq);
+}
+
+/*! \brief dump_cmd_queues: (SC:) cleanup pending commands */
+static void dump_cmd_queues(struct mgcp_endpoint *p, struct mgcp_subchannel *sub)
+{
+ struct mgcp_request *t, *q;
+
+ if (p) {
+ ast_mutex_lock(&p->rqnt_queue_lock);
+ for (q = p->rqnt_queue; q; t = q->next, ast_free(q), q=t);
+ p->rqnt_queue = NULL;
+ ast_mutex_unlock(&p->rqnt_queue_lock);
+
+ ast_mutex_lock(&p->cmd_queue_lock);
+ for (q = p->cmd_queue; q; t = q->next, ast_free(q), q=t);
+ p->cmd_queue = NULL;
+ ast_mutex_unlock(&p->cmd_queue_lock);
+
+ ast_mutex_lock(&p->sub->cx_queue_lock);
+ for (q = p->sub->cx_queue; q; t = q->next, ast_free(q), q=t);
+ p->sub->cx_queue = NULL;
+ ast_mutex_unlock(&p->sub->cx_queue_lock);
+
+ ast_mutex_lock(&p->sub->next->cx_queue_lock);
+ for (q = p->sub->next->cx_queue; q; t = q->next, ast_free(q), q=t);
+ p->sub->next->cx_queue = NULL;
+ ast_mutex_unlock(&p->sub->next->cx_queue_lock);
+ } else if (sub) {
+ ast_mutex_lock(&sub->cx_queue_lock);
+ for (q = sub->cx_queue; q; t = q->next, ast_free(q), q=t);
+ sub->cx_queue = NULL;
+ ast_mutex_unlock(&sub->cx_queue_lock);
+ }
+}
+
+
+/*! \brief find_command: (SC:) remove command transaction from queue */
+static struct mgcp_request *find_command(struct mgcp_endpoint *p, struct mgcp_subchannel *sub,
+ struct mgcp_request **queue, ast_mutex_t *l, int ident)
+{
+ struct mgcp_request *prev, *req;
+
+ ast_mutex_lock(l);
+ for (prev = NULL, req = *queue; req; prev = req, req = req->next) {
+ if (req->trid == ident) {
+ /* remove from queue */
+ if (!prev)
+ *queue = req->next;
+ else
+ prev->next = req->next;
+
+ /* send next pending command */
+ if (*queue) {
+ if (mgcpdebug) {
+ ast_verbose("Posting Queued Request:\n%s to %s:%d\n", (*queue)->data,
+ ast_inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port));
+ }
+
+ mgcp_postrequest(p, sub, (*queue)->data, (*queue)->len, (*queue)->trid);
+ }
+ break;
+ }
+ }
+ ast_mutex_unlock(l);
+ return req;
+}
+
+/* modified for new transport mechanism */
+static void handle_response(struct mgcp_endpoint *p, struct mgcp_subchannel *sub,
+ int result, unsigned int ident, struct mgcp_request *resp)
+{
+ char *c;
+ struct mgcp_request *req;
+ struct mgcp_gateway *gw = p->parent;
+
+ if (result < 200) {
+ /* provisional response */
+ return;
+ }
+
+ if (p->slowsequence)
+ req = find_command(p, sub, &p->cmd_queue, &p->cmd_queue_lock, ident);
+ else if (sub)
+ req = find_command(p, sub, &sub->cx_queue, &sub->cx_queue_lock, ident);
+ else if (!(req = find_command(p, sub, &p->rqnt_queue, &p->rqnt_queue_lock, ident)))
+ req = find_command(p, sub, &p->cmd_queue, &p->cmd_queue_lock, ident);
+
+ if (!req) {
+ ast_verb(3, "No command found on [%s] for transaction %d. Ignoring...\n",
+ gw->name, ident);
+ return;
+ }
+
+ if (p && (result >= 400) && (result <= 599)) {
+ switch (result) {
+ case 401:
+ p->hookstate = MGCP_OFFHOOK;
+ break;
+ case 402:
+ p->hookstate = MGCP_ONHOOK;
+ break;
+ case 406:
+ ast_log(LOG_NOTICE, "Transaction %d timed out\n", ident);
+ break;
+ case 407:
+ ast_log(LOG_NOTICE, "Transaction %d aborted\n", ident);
+ break;
+ }
+ if (sub) {
+ if (sub->owner) {
+ ast_log(LOG_NOTICE, "Terminating on result %d from %s@%s-%d\n",
+ result, p->name, p->parent->name, sub ? sub->id:-1);
+ mgcp_queue_hangup(sub);
+ }
+ } else {
+ if (p->sub->next->owner) {
+ ast_log(LOG_NOTICE, "Terminating on result %d from %s@%s-%d\n",
+ result, p->name, p->parent->name, sub ? sub->id:-1);
+ mgcp_queue_hangup(p->sub);
+ }
+
+ if (p->sub->owner) {
+ ast_log(LOG_NOTICE, "Terminating on result %d from %s@%s-%d\n",
+ result, p->name, p->parent->name, sub ? sub->id:-1);
+ mgcp_queue_hangup(p->sub);
+ }
+
+ dump_cmd_queues(p, NULL);
+ }
+ }
+
+ if (resp) {
+ if (req->cmd == MGCP_CMD_CRCX) {
+ if ((c = get_header(resp, "I"))) {
+ if (!ast_strlen_zero(c) && sub) {
+ /* if we are hanging up do not process this conn. */
+ if (sub->owner) {
+ if (!ast_strlen_zero(sub->cxident)) {
+ if (strcasecmp(c, sub->cxident)) {
+ ast_log(LOG_WARNING, "Subchannel already has a cxident. sub->cxident: %s requested %s\n", sub->cxident, c);
+ }
+ }
+ ast_copy_string(sub->cxident, c, sizeof(sub->cxident));
+ if (sub->tmpdest.sin_addr.s_addr) {
+ transmit_modify_with_sdp(sub, NULL, 0);
+ }
+ } else {
+ /* XXX delete this one
+ callid and conn id may already be lost.
+ so the following del conn may have a side effect of
+ cleaning up the next subchannel */
+ transmit_connection_del(sub);
+ }
+ }
+ }
+ }
+
+ if (req->cmd == MGCP_CMD_AUEP) {
+ /* check stale connection ids */
+ if ((c = get_header(resp, "I"))) {
+ char *v, *n;
+ int len;
+ while ((v = get_csv(c, &len, &n))) {
+ if (len) {
+ if (strncasecmp(v, p->sub->cxident, len) &&
+ strncasecmp(v, p->sub->next->cxident, len)) {
+ /* connection id not found. delete it */
+ char cxident[80] = "";
+
+ if (len > (sizeof(cxident) - 1))
+ len = sizeof(cxident) - 1;
+ ast_copy_string(cxident, v, len);
+ ast_verb(3, "Non existing connection id %s on %s@%s \n",
+ cxident, p->name, gw->name);
+ transmit_connection_del_w_params(p, NULL, cxident);
+ }
+ }
+ c = n;
+ }
+ }
+
+ /* Try to determine the hookstate returned from an audit endpoint command */
+ if ((c = get_header(resp, "ES"))) {
+ if (!ast_strlen_zero(c)) {
+ if (strstr(c, "hu")) {
+ if (p->hookstate != MGCP_ONHOOK) {
+ /* XXX cleanup if we think we are offhook XXX */
+ if ((p->sub->owner || p->sub->next->owner ) &&
+ p->hookstate == MGCP_OFFHOOK)
+ mgcp_queue_hangup(sub);
+ p->hookstate = MGCP_ONHOOK;
+
+ /* update the requested events according to the new hookstate */
+ transmit_notify_request(p->sub, "");
+
+ ast_verb(3, "Setting hookstate of %s@%s to ONHOOK\n", p->name, gw->name);
+ }
+ } else if (strstr(c, "hd")) {
+ if (p->hookstate != MGCP_OFFHOOK) {
+ p->hookstate = MGCP_OFFHOOK;
+
+ /* update the requested events according to the new hookstate */
+ transmit_notify_request(p->sub, "");
+
+ ast_verb(3, "Setting hookstate of %s@%s to OFFHOOK\n", p->name, gw->name);
+ }
+ }
+ }
+ }
+ }
+
+ if (resp && resp->lines) {
+ /* do not process sdp if we are hanging up. this may be a late response */
+ if (sub && sub->owner) {
+ if (!sub->rtp)
+ start_rtp(sub);
+ if (sub->rtp)
+ process_sdp(sub, resp);
+ }
+ }
+ }
+
+ ast_free(req);
+}
+
+static void start_rtp(struct mgcp_subchannel *sub)
+{
+ ast_mutex_lock(&sub->lock);
+ /* check again to be on the safe side */
+ if (sub->rtp) {
+ ast_rtp_destroy(sub->rtp);
+ sub->rtp = NULL;
+ }
+ /* Allocate the RTP now */
+ sub->rtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
+ if (sub->rtp && sub->owner)
+ ast_channel_set_fd(sub->owner, 0, ast_rtp_fd(sub->rtp));
+ if (sub->rtp) {
+ ast_rtp_setqos(sub->rtp, tos_audio, cos_audio, "MGCP RTP");
+ ast_rtp_setnat(sub->rtp, sub->nat);
+ }
+#if 0
+ ast_rtp_set_callback(p->rtp, rtpready);
+ ast_rtp_set_data(p->rtp, p);
+#endif
+ /* Make a call*ID */
+ snprintf(sub->callid, sizeof(sub->callid), "%08lx%s", ast_random(), sub->txident);
+ /* Transmit the connection create */
+ transmit_connect_with_sdp(sub, NULL);
+ ast_mutex_unlock(&sub->lock);
+}
+
+static void *mgcp_ss(void *data)
+{
+ struct ast_channel *chan = data;
+ struct mgcp_subchannel *sub = chan->tech_pvt;
+ struct mgcp_endpoint *p = sub->parent;
+ /* char exten[AST_MAX_EXTENSION] = ""; */
+ int len = 0;
+ int timeout = firstdigittimeout;
+ int res= 0;
+ int getforward = 0;
+ int loop_pause = 100;
+
+ len = strlen(p->dtmf_buf);
+
+ while(len < AST_MAX_EXTENSION-1) {
+ res = 1; /* Assume that we will get a digit */
+ while (strlen(p->dtmf_buf) == len){
+ ast_safe_sleep(chan, loop_pause);
+ timeout -= loop_pause;
+ if (timeout <= 0){
+ res = 0;
+ break;
+ }
+ res = 1;
+ }
+
+ timeout = 0;
+ len = strlen(p->dtmf_buf);
+
+ if (!ast_ignore_pattern(chan->context, p->dtmf_buf)) {
+ /*res = tone_zone_play_tone(p->subs[index].zfd, -1);*/
+ ast_indicate(chan, -1);
+ } else {
+ /* XXX Redundant? We should already be playing dialtone */
+ /*tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALTONE);*/
+ transmit_notify_request(sub, "L/dl");
+ }
+ if (ast_exists_extension(chan, chan->context, p->dtmf_buf, 1, p->cid_num)) {
+ if (!res || !ast_matchmore_extension(chan, chan->context, p->dtmf_buf, 1, p->cid_num)) {
+ if (getforward) {
+ /* Record this as the forwarding extension */
+ ast_copy_string(p->call_forward, p->dtmf_buf, sizeof(p->call_forward));
+ ast_verb(3, "Setting call forward to '%s' on channel %s\n",
+ p->call_forward, chan->name);
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ if (res)
+ break;
+ usleep(500000);
+ /*res = tone_zone_play_tone(p->subs[index].zfd, -1);*/
+ ast_indicate(chan, -1);
+ sleep(1);
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALTONE);*/
+ transmit_notify_request(sub, "L/dl");
+ len = 0;
+ getforward = 0;
+ } else {
+ /*res = tone_zone_play_tone(p->subs[index].zfd, -1);*/
+ ast_indicate(chan, -1);
+ ast_copy_string(chan->exten, p->dtmf_buf, sizeof(chan->exten));
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ ast_set_callerid(chan,
+ p->hidecallerid ? "" : p->cid_num,
+ p->hidecallerid ? "" : p->cid_name,
+ chan->cid.cid_ani ? NULL : p->cid_num);
+ ast_setstate(chan, AST_STATE_RING);
+ /*zt_enable_ec(p);*/
+ if (p->dtmfmode & MGCP_DTMF_HYBRID) {
+ p->dtmfmode |= MGCP_DTMF_INBAND;
+ ast_indicate(chan, -1);
+ }
+ res = ast_pbx_run(chan);
+ if (res) {
+ ast_log(LOG_WARNING, "PBX exited non-zero\n");
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);*/
+ /*transmit_notify_request(p, "nbz", 1);*/
+ transmit_notify_request(sub, "G/cg");
+ }
+ return NULL;
+ }
+ } else {
+ /* It's a match, but they just typed a digit, and there is an ambiguous match,
+ so just set the timeout to matchdigittimeout and wait some more */
+ timeout = matchdigittimeout;
+ }
+ } else if (res == 0) {
+ ast_debug(1, "not enough digits (and no ambiguous match)...\n");
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);*/
+ transmit_notify_request(sub, "G/cg");
+ /*zt_wait_event(p->subs[index].zfd);*/
+ ast_hangup(chan);
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ return NULL;
+ } else if (p->hascallwaiting && p->callwaiting && !strcmp(p->dtmf_buf, "*70")) {
+ ast_verb(3, "Disabling call waiting on %s\n", chan->name);
+ /* Disable call waiting if enabled */
+ p->callwaiting = 0;
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ len = 0;
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ timeout = firstdigittimeout;
+ } else if (!strcmp(p->dtmf_buf,ast_pickup_ext())) {
+ /* Scan all channels and see if any there
+ * ringing channqels with that have call groups
+ * that equal this channels pickup group
+ */
+ if (ast_pickup_call(chan)) {
+ ast_log(LOG_WARNING, "No call pickup possible...\n");
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);*/
+ transmit_notify_request(sub, "G/cg");
+ }
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ ast_hangup(chan);
+ return NULL;
+ } else if (!p->hidecallerid && !strcmp(p->dtmf_buf, "*67")) {
+ ast_verb(3, "Disabling Caller*ID on %s\n", chan->name);
+ /* Disable Caller*ID if enabled */
+ p->hidecallerid = 1;
+ ast_set_callerid(chan, "", "", NULL);
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ len = 0;
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ timeout = firstdigittimeout;
+ } else if (p->callreturn && !strcmp(p->dtmf_buf, "*69")) {
+ res = 0;
+ if (!ast_strlen_zero(p->lastcallerid)) {
+ res = ast_say_digit_str(chan, p->lastcallerid, "", chan->language);
+ }
+ if (!res)
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ break;
+ } else if (!strcmp(p->dtmf_buf, "*78")) {
+ /* Do not disturb */
+ ast_verb(3, "Enabled DND on channel %s\n", chan->name);
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ p->dnd = 1;
+ getforward = 0;
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ len = 0;
+ } else if (!strcmp(p->dtmf_buf, "*79")) {
+ /* Do not disturb */
+ ast_verb(3, "Disabled DND on channel %s\n", chan->name);
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ p->dnd = 0;
+ getforward = 0;
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ len = 0;
+ } else if (p->cancallforward && !strcmp(p->dtmf_buf, "*72")) {
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ getforward = 1;
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ len = 0;
+ } else if (p->cancallforward && !strcmp(p->dtmf_buf, "*73")) {
+ ast_verb(3, "Cancelling call forwarding on channel %s\n", chan->name);
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ memset(p->call_forward, 0, sizeof(p->call_forward));
+ getforward = 0;
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ len = 0;
+ } else if (!strcmp(p->dtmf_buf, ast_parking_ext()) &&
+ sub->next->owner && ast_bridged_channel(sub->next->owner)) {
+ /* This is a three way call, the main call being a real channel,
+ and we're parking the first call. */
+ ast_masq_park_call(ast_bridged_channel(sub->next->owner), chan, 0, NULL);
+ ast_verb(3, "Parking call to '%s'\n", chan->name);
+ break;
+ } else if (!ast_strlen_zero(p->lastcallerid) && !strcmp(p->dtmf_buf, "*60")) {
+ ast_verb(3, "Blacklisting number %s\n", p->lastcallerid);
+ res = ast_db_put("blacklist", p->lastcallerid, "1");
+ if (!res) {
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ len = 0;
+ }
+ } else if (p->hidecallerid && !strcmp(p->dtmf_buf, "*82")) {
+ ast_verb(3, "Enabling Caller*ID on %s\n", chan->name);
+ /* Enable Caller*ID if enabled */
+ p->hidecallerid = 0;
+ ast_set_callerid(chan, p->cid_num, p->cid_name, NULL);
+ /*res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ len = 0;
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ timeout = firstdigittimeout;
+ } else if (!ast_canmatch_extension(chan, chan->context, p->dtmf_buf, 1, chan->cid.cid_num) &&
+ ((p->dtmf_buf[0] != '*') || (strlen(p->dtmf_buf) > 2))) {
+ ast_debug(1, "Can't match %s from '%s' in context %s\n", p->dtmf_buf, chan->cid.cid_num ? chan->cid.cid_num : "<Unknown Caller>", chan->context);
+ break;
+ }
+ if (!timeout)
+ timeout = gendigittimeout;
+ if (len && !ast_ignore_pattern(chan->context, p->dtmf_buf))
+ /*tone_zone_play_tone(p->subs[index].zfd, -1);*/
+ ast_indicate(chan, -1);
+ }
+#if 0
+ for (;;) {
+ res = ast_waitfordigit(chan, to);
+ if (!res) {
+ ast_debug(1, "Timeout...\n");
+ break;
+ }
+ if (res < 0) {
+ ast_debug(1, "Got hangup...\n");
+ ast_hangup(chan);
+ break;
+ }
+ exten[pos++] = res;
+ if (!ast_ignore_pattern(chan->context, exten))
+ ast_indicate(chan, -1);
+ if (ast_matchmore_extension(chan, chan->context, exten, 1, chan->callerid)) {
+ if (ast_exists_extension(chan, chan->context, exten, 1, chan->callerid))
+ to = 3000;
+ else
+ to = 8000;
+ } else
+ break;
+ }
+ if (ast_exists_extension(chan, chan->context, exten, 1, chan->callerid)) {
+ ast_copy_string(chan->exten, exten, sizeof(chan->exten)1);
+ if (!p->rtp) {
+ start_rtp(p);
+ }
+ ast_setstate(chan, AST_STATE_RING);
+ chan->rings = 1;
+ if (ast_pbx_run(chan)) {
+ ast_log(LOG_WARNING, "Unable to launch PBX on %s\n", chan->name);
+ } else {
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ return NULL;
+ }
+ }
+#endif
+ ast_hangup(chan);
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ return NULL;
+}
+
+static int attempt_transfer(struct mgcp_endpoint *p)
+{
+ /* *************************
+ * I hope this works.
+ * Copied out of chan_zap
+ * Cross your fingers
+ * *************************/
+
+ /* In order to transfer, we need at least one of the channels to
+ actually be in a call bridge. We can't conference two applications
+ together (but then, why would we want to?) */
+ if (ast_bridged_channel(p->sub->owner)) {
+ /* The three-way person we're about to transfer to could still be in MOH, so
+ stop if now if appropriate */
+ if (ast_bridged_channel(p->sub->next->owner))
+ ast_queue_control(p->sub->next->owner, AST_CONTROL_UNHOLD);
+ if (p->sub->owner->_state == AST_STATE_RINGING) {
+ ast_indicate(ast_bridged_channel(p->sub->next->owner), AST_CONTROL_RINGING);
+ }
+ if (ast_channel_masquerade(p->sub->next->owner, ast_bridged_channel(p->sub->owner))) {
+ ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n",
+ ast_bridged_channel(p->sub->owner)->name, p->sub->next->owner->name);
+ return -1;
+ }
+ /* Orphan the channel */
+ unalloc_sub(p->sub->next);
+ } else if (ast_bridged_channel(p->sub->next->owner)) {
+ if (p->sub->owner->_state == AST_STATE_RINGING) {
+ ast_indicate(ast_bridged_channel(p->sub->next->owner), AST_CONTROL_RINGING);
+ }
+ ast_queue_control(p->sub->next->owner, AST_CONTROL_UNHOLD);
+ if (ast_channel_masquerade(p->sub->owner, ast_bridged_channel(p->sub->next->owner))) {
+ ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n",
+ ast_bridged_channel(p->sub->next->owner)->name, p->sub->owner->name);
+ return -1;
+ }
+ /*swap_subs(p, SUB_THREEWAY, SUB_REAL);*/
+ ast_verb(3, "Swapping %d for %d on %s@%s\n", p->sub->id, p->sub->next->id, p->name, p->parent->name);
+ p->sub = p->sub->next;
+ unalloc_sub(p->sub->next);
+ /* Tell the caller not to hangup */
+ return 1;
+ } else {
+ ast_debug(1, "Neither %s nor %s are in a bridge, nothing to transfer\n",
+ p->sub->owner->name, p->sub->next->owner->name);
+ p->sub->next->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ if (p->sub->next->owner) {
+ p->sub->next->alreadygone = 1;
+ mgcp_queue_hangup(p->sub->next);
+ }
+ }
+ return 0;
+}
+
+static void handle_hd_hf(struct mgcp_subchannel *sub, char *ev)
+{
+ struct mgcp_endpoint *p = sub->parent;
+ struct ast_channel *c;
+ pthread_t t;
+
+ /* Off hook / answer */
+ if (sub->outgoing) {
+ /* Answered */
+ if (sub->owner) {
+ if (ast_bridged_channel(sub->owner))
+ ast_queue_control(sub->owner, AST_CONTROL_UNHOLD);
+ sub->cxmode = MGCP_CX_SENDRECV;
+ if (!sub->rtp) {
+ start_rtp(sub);
+ } else {
+ transmit_modify_request(sub);
+ }
+ /*transmit_notify_request(sub, "aw");*/
+ transmit_notify_request(sub, "");
+ mgcp_queue_control(sub, AST_CONTROL_ANSWER);
+ }
+ } else {
+ /* Start switch */
+ /*sub->cxmode = MGCP_CX_SENDRECV;*/
+ if (!sub->owner) {
+ if (!sub->rtp) {
+ start_rtp(sub);
+ } else {
+ transmit_modify_request(sub);
+ }
+ if (p->immediate) {
+ /* The channel is immediately up. Start right away */
+#ifdef DLINK_BUGGY_FIRMWARE
+ transmit_notify_request(sub, "rt");
+#else
+ transmit_notify_request(sub, "G/rt");
+#endif
+ c = mgcp_new(sub, AST_STATE_RING);
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to start PBX on channel %s@%s\n", p->name, p->parent->name);
+ transmit_notify_request(sub, "G/cg");
+ ast_hangup(c);
+ }
+ } else {
+ if (has_voicemail(p)) {
+ transmit_notify_request(sub, "L/sl");
+ } else {
+ transmit_notify_request(sub, "L/dl");
+ }
+ c = mgcp_new(sub, AST_STATE_DOWN);
+ if (c) {
+ if (ast_pthread_create_detached(&t, NULL, mgcp_ss, c)) {
+ ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno));
+ ast_hangup(c);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", p->name, p->parent->name);
+ }
+ }
+ } else {
+ if (p->hookstate == MGCP_OFFHOOK) {
+ ast_log(LOG_WARNING, "Off hook, but already have owner on %s@%s\n", p->name, p->parent->name);
+ } else {
+ ast_log(LOG_WARNING, "On hook, but already have owner on %s@%s\n", p->name, p->parent->name);
+ ast_log(LOG_WARNING, "If we're onhook why are we here trying to handle a hd or hf?\n");
+ }
+ if (ast_bridged_channel(sub->owner))
+ ast_queue_control(sub->owner, AST_CONTROL_UNHOLD);
+ sub->cxmode = MGCP_CX_SENDRECV;
+ if (!sub->rtp) {
+ start_rtp(sub);
+ } else {
+ transmit_modify_request(sub);
+ }
+ /*transmit_notify_request(sub, "aw");*/
+ transmit_notify_request(sub, "");
+ /*ast_queue_control(sub->owner, AST_CONTROL_ANSWER);*/
+ }
+ }
+}
+
+static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req, struct sockaddr_in *sin)
+{
+ char *ev, *s;
+ struct ast_frame f = { 0, };
+ struct mgcp_endpoint *p = sub->parent;
+ struct mgcp_gateway *g = NULL;
+ int res;
+
+ if (mgcpdebug) {
+ ast_verbose("Handling request '%s' on %s@%s\n", req->verb, p->name, p->parent->name);
+ }
+ /* Clear out potential response */
+ if (!strcasecmp(req->verb, "RSIP")) {
+ /* Test if this RSIP request is just a keepalive */
+ if(!strcasecmp( get_header(req, "RM"), "X-keepalive")) {
+ ast_verb(3, "Received keepalive request from %s@%s\n", p->name, p->parent->name);
+ transmit_response(sub, "200", req, "OK");
+ } else {
+ dump_queue(p->parent, p);
+ dump_cmd_queues(p, NULL);
+
+ if ((strcmp(p->name, p->parent->wcardep) != 0)) {
+ ast_verb(3, "Resetting interface %s@%s\n", p->name, p->parent->name);
+ }
+ /* For RSIP on wildcard we reset all endpoints */
+ if (!strcmp(p->name, p->parent->wcardep)) {
+ /* Reset all endpoints */
+ struct mgcp_endpoint *tmp_ep;
+
+ g = p->parent;
+ tmp_ep = g->endpoints;
+ while (tmp_ep) {
+ /*if ((strcmp(tmp_ep->name, "*") != 0) && (strcmp(tmp_ep->name, "aaln/" "*") != 0)) {*/
+ if (strcmp(tmp_ep->name, g->wcardep) != 0) {
+ struct mgcp_subchannel *tmp_sub, *first_sub;
+ ast_verb(3, "Resetting interface %s@%s\n", tmp_ep->name, p->parent->name);
+
+ first_sub = tmp_ep->sub;
+ tmp_sub = tmp_ep->sub;
+ while (tmp_sub) {
+ mgcp_queue_hangup(tmp_sub);
+ tmp_sub = tmp_sub->next;
+ if (tmp_sub == first_sub)
+ break;
+ }
+ }
+ tmp_ep = tmp_ep->next;
+ }
+ } else if (sub->owner) {
+ mgcp_queue_hangup(sub);
+ }
+ transmit_response(sub, "200", req, "OK");
+ /* We dont send NTFY or AUEP to wildcard ep */
+ if (strcmp(p->name, p->parent->wcardep) != 0) {
+ transmit_notify_request(sub, "");
+ /* Audit endpoint.
+ Idea is to prevent lost lines due to race conditions
+ */
+ transmit_audit_endpoint(p);
+ }
+ }
+ } else if (!strcasecmp(req->verb, "NTFY")) {
+ /* Acknowledge and be sure we keep looking for the same things */
+ transmit_response(sub, "200", req, "OK");
+ /* Notified of an event */
+ ev = get_header(req, "O");
+ s = strchr(ev, '/');
+ if (s) ev = s + 1;
+ ast_debug(1, "Endpoint '%s@%s-%d' observed '%s'\n", p->name, p->parent->name, sub->id, ev);
+ /* Keep looking for events unless this was a hangup */
+ if (strcasecmp(ev, "hu") && strcasecmp(ev, "hd") && strcasecmp(ev, "ping")) {
+ transmit_notify_request(sub, p->curtone);
+ }
+ if (!strcasecmp(ev, "hd")) {
+ p->hookstate = MGCP_OFFHOOK;
+ sub->cxmode = MGCP_CX_SENDRECV;
+ handle_hd_hf(sub, ev);
+ } else if (!strcasecmp(ev, "hf")) {
+ /* We can assume we are offhook if we received a hookflash */
+ /* First let's just do call wait and ignore threeway */
+ /* We're currently in charge */
+ if (p->hookstate != MGCP_OFFHOOK) {
+ /* Cisco c7940 sends hf even if the phone is onhook */
+ /* Thanks to point on IRC for pointing this out */
+ return -1;
+ }
+ /* do not let * conference two down channels */
+ if (sub->owner && sub->owner->_state == AST_STATE_DOWN && !sub->next->owner)
+ return -1;
+
+ if (p->callwaiting || p->transfer || p->threewaycalling) {
+ ast_verb(3, "Swapping %d for %d on %s@%s\n", p->sub->id, p->sub->next->id, p->name, p->parent->name);
+ p->sub = p->sub->next;
+
+ /* transfer control to our next subchannel */
+ if (!sub->next->owner) {
+ /* plave the first call on hold and start up a new call */
+ sub->cxmode = MGCP_CX_MUTE;
+ ast_verb(3, "MGCP Muting %d on %s@%s\n", sub->id, p->name, p->parent->name);
+ transmit_modify_request(sub);
+ if (sub->owner && ast_bridged_channel(sub->owner))
+ ast_queue_control(sub->owner, AST_CONTROL_HOLD);
+ sub->next->cxmode = MGCP_CX_RECVONLY;
+ handle_hd_hf(sub->next, ev);
+ } else if (sub->owner && sub->next->owner) {
+ /* We've got two active calls lets decide whether or not to conference or just flip flop */
+ if ((!sub->outgoing) && (!sub->next->outgoing)) {
+ /* We made both calls lets conferenct */
+ ast_verb(3, "MGCP Conferencing %d and %d on %s@%s\n",
+ sub->id, sub->next->id, p->name, p->parent->name);
+ sub->cxmode = MGCP_CX_CONF;
+ sub->next->cxmode = MGCP_CX_CONF;
+ if (ast_bridged_channel(sub->next->owner))
+ ast_queue_control(sub->next->owner, AST_CONTROL_UNHOLD);
+ transmit_modify_request(sub);
+ transmit_modify_request(sub->next);
+ } else {
+ /* Let's flipflop between calls */
+ /* XXX Need to check for state up ??? */
+ /* XXX Need a way to indicate the current call, or maybe the call that's waiting */
+ ast_verb(3, "We didn't make one of the calls FLIPFLOP %d and %d on %s@%s\n",
+ sub->id, sub->next->id, p->name, p->parent->name);
+ sub->cxmode = MGCP_CX_MUTE;
+ ast_verb(3, "MGCP Muting %d on %s@%s\n", sub->id, p->name, p->parent->name);
+ transmit_modify_request(sub);
+ if (ast_bridged_channel(sub->owner))
+ ast_queue_control(sub->owner, AST_CONTROL_HOLD);
+
+ if (ast_bridged_channel(sub->next->owner))
+ ast_queue_control(sub->next->owner, AST_CONTROL_HOLD);
+
+ handle_hd_hf(sub->next, ev);
+ }
+ } else {
+ /* We've most likely lost one of our calls find an active call and bring it up */
+ if (sub->owner) {
+ p->sub = sub;
+ } else if (sub->next->owner) {
+ p->sub = sub->next;
+ } else {
+ /* We seem to have lost both our calls */
+ /* XXX - What do we do now? */
+ return -1;
+ }
+ if (ast_bridged_channel(p->sub->owner))
+ ast_queue_control(p->sub->owner, AST_CONTROL_UNHOLD);
+ p->sub->cxmode = MGCP_CX_SENDRECV;
+ transmit_modify_request(p->sub);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Callwaiting, call transfer or threeway calling not enabled on endpoint %s@%s\n",
+ p->name, p->parent->name);
+ }
+ } else if (!strcasecmp(ev, "hu")) {
+ p->hookstate = MGCP_ONHOOK;
+ sub->cxmode = MGCP_CX_RECVONLY;
+ ast_debug(1, "MGCP %s@%s Went on hook\n", p->name, p->parent->name);
+ /* Do we need to send MDCX before a DLCX ?
+ if (sub->rtp) {
+ transmit_modify_request(sub);
+ }
+ */
+ if (p->transfer && (sub->owner && sub->next->owner) && ((!sub->outgoing) || (!sub->next->outgoing))) {
+ /* We're allowed to transfer, we have two avtive calls and */
+ /* we made at least one of the calls. Let's try and transfer */
+ ast_mutex_lock(&p->sub->next->lock);
+ res = attempt_transfer(p);
+ if (res < 0) {
+ if (p->sub->next->owner) {
+ sub->next->alreadygone = 1;
+ mgcp_queue_hangup(sub->next);
+ }
+ } else if (res) {
+ ast_log(LOG_WARNING, "Transfer attempt failed\n");
+ ast_mutex_unlock(&p->sub->next->lock);
+ return -1;
+ }
+ ast_mutex_unlock(&p->sub->next->lock);
+ } else {
+ /* Hangup the current call */
+ /* If there is another active call, mgcp_hangup will ring the phone with the other call */
+ if (sub->owner) {
+ sub->alreadygone = 1;
+ mgcp_queue_hangup(sub);
+ } else {
+ ast_verb(3, "MGCP handle_request(%s@%s-%d) ast_channel already destroyed, resending DLCX.\n",
+ p->name, p->parent->name, sub->id);
+ /* Instruct the other side to remove the connection since it apparently *
+ * still thinks the channel is active. *
+ * For Cisco IAD2421 /BAK/ */
+ transmit_connection_del(sub);
+ }
+ }
+ if ((p->hookstate == MGCP_ONHOOK) && (!sub->rtp) && (!sub->next->rtp)) {
+ p->hidecallerid = 0;
+ if (p->hascallwaiting && !p->callwaiting) {
+ ast_verb(3, "Enabling call waiting on MGCP/%s@%s-%d\n", p->name, p->parent->name, sub->id);
+ p->callwaiting = -1;
+ }
+ if (has_voicemail(p)) {
+ ast_verb(3, "MGCP handle_request(%s@%s) set vmwi(+)\n", p->name, p->parent->name);
+ transmit_notify_request(sub, "L/vmwi(+)");
+ } else {
+ ast_verb(3, "MGCP handle_request(%s@%s) set vmwi(-)\n", p->name, p->parent->name);
+ transmit_notify_request(sub, "L/vmwi(-)");
+ }
+ }
+ } else if ((strlen(ev) == 1) &&
+ (((ev[0] >= '0') && (ev[0] <= '9')) ||
+ ((ev[0] >= 'A') && (ev[0] <= 'D')) ||
+ (ev[0] == '*') || (ev[0] == '#'))) {
+ if (sub && sub->owner && (sub->owner->_state >= AST_STATE_UP)) {
+ f.frametype = AST_FRAME_DTMF;
+ f.subclass = ev[0];
+ f.src = "mgcp";
+ /* XXX MUST queue this frame to all subs in threeway call if threeway call is active */
+ mgcp_queue_frame(sub, &f);
+ ast_mutex_lock(&sub->next->lock);
+ if (sub->next->owner)
+ mgcp_queue_frame(sub->next, &f);
+ ast_mutex_unlock(&sub->next->lock);
+ if (strstr(p->curtone, "wt") && (ev[0] == 'A')) {
+ memset(p->curtone, 0, sizeof(p->curtone));
+ }
+ } else {
+ p->dtmf_buf[strlen(p->dtmf_buf)] = ev[0];
+ p->dtmf_buf[strlen(p->dtmf_buf)] = '\0';
+ }
+ } else if (!strcasecmp(ev, "T")) {
+ /* Digit timeout -- unimportant */
+ } else if (!strcasecmp(ev, "ping")) {
+ /* ping -- unimportant */
+ } else {
+ ast_log(LOG_NOTICE, "Received unknown event '%s' from %s@%s\n", ev, p->name, p->parent->name);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Unknown verb '%s' received from %s\n", req->verb, ast_inet_ntoa(sin->sin_addr));
+ transmit_response(sub, "510", req, "Unknown verb");
+ }
+ return 0;
+}
+
+static int find_and_retrans(struct mgcp_subchannel *sub, struct mgcp_request *req)
+{
+ int seqno=0;
+ time_t now;
+ struct mgcp_response *prev = NULL, *cur, *next, *answer=NULL;
+ time(&now);
+ if (sscanf(req->identifier, "%d", &seqno) != 1)
+ seqno = 0;
+ cur = sub->parent->parent->responses;
+ while(cur) {
+ next = cur->next;
+ if (now - cur->whensent > RESPONSE_TIMEOUT) {
+ /* Delete this entry */
+ if (prev)
+ prev->next = next;
+ else
+ sub->parent->parent->responses = next;
+ ast_free(cur);
+ } else {
+ if (seqno == cur->seqno)
+ answer = cur;
+ prev = cur;
+ }
+ cur = next;
+ }
+ if (answer) {
+ resend_response(sub, answer);
+ return 1;
+ }
+ return 0;
+}
+
+static int mgcpsock_read(int *id, int fd, short events, void *ignore)
+{
+ struct mgcp_request req;
+ struct sockaddr_in sin;
+ struct mgcp_subchannel *sub;
+ int res;
+ socklen_t len;
+ int result;
+ int ident;
+ len = sizeof(sin);
+ memset(&req, 0, sizeof(req));
+ res = recvfrom(mgcpsock, req.data, sizeof(req.data) - 1, 0, (struct sockaddr *)&sin, &len);
+ if (res < 0) {
+ if (errno != ECONNREFUSED)
+ ast_log(LOG_WARNING, "Recv error: %s\n", strerror(errno));
+ return 1;
+ }
+ req.data[res] = '\0';
+ req.len = res;
+ if (mgcpdebug) {
+ ast_verbose("MGCP read: \n%s\nfrom %s:%d\n", req.data, ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+ }
+ parse(&req);
+ if (req.headers < 1) {
+ /* Must have at least one header */
+ return 1;
+ }
+ if (ast_strlen_zero(req.identifier)) {
+ ast_log(LOG_NOTICE, "Message from %s missing identifier\n", ast_inet_ntoa(sin.sin_addr));
+ return 1;
+ }
+
+ if (sscanf(req.verb, "%d", &result) && sscanf(req.identifier, "%d", &ident)) {
+ /* Try to find who this message is for, if it's important */
+ sub = find_subchannel_and_lock(NULL, ident, &sin);
+ if (sub) {
+ struct mgcp_gateway *gw = sub->parent->parent;
+ struct mgcp_message *cur, *prev;
+
+ ast_mutex_unlock(&sub->lock);
+ ast_mutex_lock(&gw->msgs_lock);
+ for (prev = NULL, cur = gw->msgs; cur; prev = cur, cur = cur->next) {
+ if (cur->seqno == ident) {
+ ast_debug(1, "Got response back on transaction %d\n", ident);
+ if (prev)
+ prev->next = cur->next;
+ else
+ gw->msgs = cur->next;
+ break;
+ }
+ }
+
+ /* stop retrans timer if the queue is empty */
+ if (!gw->msgs && (gw->retransid != -1)) {
+ ast_sched_del(sched, gw->retransid);
+ gw->retransid = -1;
+ }
+
+ ast_mutex_unlock(&gw->msgs_lock);
+ if (cur) {
+ handle_response(cur->owner_ep, cur->owner_sub, result, ident, &req);
+ ast_free(cur);
+ return 1;
+ }
+
+ ast_log(LOG_NOTICE, "Got response back on [%s] for transaction %d we aren't sending?\n",
+ gw->name, ident);
+ }
+ } else {
+ if (ast_strlen_zero(req.endpoint) ||
+ ast_strlen_zero(req.version) ||
+ ast_strlen_zero(req.verb)) {
+ ast_log(LOG_NOTICE, "Message must have a verb, an idenitifier, version, and endpoint\n");
+ return 1;
+ }
+ /* Process request, with iflock held */
+ sub = find_subchannel_and_lock(req.endpoint, 0, &sin);
+ if (sub) {
+ /* look first to find a matching response in the queue */
+ if (!find_and_retrans(sub, &req))
+ /* pass the request off to the currently mastering subchannel */
+ handle_request(sub, &req, &sin);
+ ast_mutex_unlock(&sub->lock);
+ }
+ }
+ return 1;
+}
+
+static int *mgcpsock_read_id = NULL;
+
+static void *do_monitor(void *data)
+{
+ int res;
+ int reloading;
+ /*struct mgcp_gateway *g;*/
+ /*struct mgcp_endpoint *e;*/
+ /*time_t thispass = 0, lastpass = 0;*/
+
+ /* Add an I/O event to our UDP socket */
+ if (mgcpsock > -1)
+ mgcpsock_read_id = ast_io_add(io, mgcpsock, mgcpsock_read, AST_IO_IN, NULL);
+
+ /* This thread monitors all the frame relay interfaces which are not yet in use
+ (and thus do not have a separate thread) indefinitely */
+ /* From here on out, we die whenever asked */
+ for(;;) {
+ /* Check for a reload request */
+ ast_mutex_lock(&mgcp_reload_lock);
+ reloading = mgcp_reloading;
+ mgcp_reloading = 0;
+ ast_mutex_unlock(&mgcp_reload_lock);
+ if (reloading) {
+ ast_verb(1, "Reloading MGCP\n");
+ reload_config(1);
+ /* Add an I/O event to our UDP socket */
+ if (mgcpsock > -1)
+ mgcpsock_read_id = ast_io_add(io, mgcpsock, mgcpsock_read, AST_IO_IN, NULL);
+ }
+
+ /* Check for interfaces needing to be killed */
+ /* Don't let anybody kill us right away. Nobody should lock the interface list
+ and wait for the monitor list, but the other way around is okay. */
+ ast_mutex_lock(&monlock);
+ /* Lock the network interface */
+ ast_mutex_lock(&netlock);
+
+#if 0
+ /* XXX THIS IS COMPLETELY HOSED */
+ /* The gateway goes into a state of panic */
+ /* If the vmwi indicator is sent while it is reseting interfaces */
+ lastpass = thispass;
+ thispass = time(NULL);
+ g = gateways;
+ while(g) {
+ if (thispass != lastpass) {
+ e = g->endpoints;
+ while(e) {
+ if (e->type == TYPE_LINE) {
+ res = has_voicemail(e);
+ if ((e->msgstate != res) && (e->hookstate == MGCP_ONHOOK) && (!e->rtp)){
+ if (res) {
+ transmit_notify_request(e, "L/vmwi(+)");
+ } else {
+ transmit_notify_request(e, "L/vmwi(-)");
+ }
+ e->msgstate = res;
+ e->onhooktime = thispass;
+ }
+ }
+ e = e->next;
+ }
+ }
+ g = g->next;
+ }
+#endif
+ /* Okay, now that we know what to do, release the network lock */
+ ast_mutex_unlock(&netlock);
+ /* And from now on, we're okay to be killed, so release the monitor lock as well */
+ ast_mutex_unlock(&monlock);
+ pthread_testcancel();
+ /* Wait for sched or io */
+ res = ast_sched_wait(sched);
+ /* copied from chan_sip.c */
+ if ((res < 0) || (res > 1000))
+ res = 1000;
+ res = ast_io_wait(io, res);
+ ast_mutex_lock(&monlock);
+ if (res >= 0)
+ ast_sched_runq(sched);
+ ast_mutex_unlock(&monlock);
+ }
+ /* Never reached */
+ return NULL;
+}
+
+static int restart_monitor(void)
+{
+ /* If we're supposed to be stopped -- stay stopped */
+ if (monitor_thread == AST_PTHREADT_STOP)
+ return 0;
+ if (ast_mutex_lock(&monlock)) {
+ ast_log(LOG_WARNING, "Unable to lock monitor\n");
+ return -1;
+ }
+ if (monitor_thread == pthread_self()) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_WARNING, "Cannot kill myself\n");
+ return -1;
+ }
+ if (monitor_thread != AST_PTHREADT_NULL) {
+ /* Wake up the thread */
+ pthread_kill(monitor_thread, SIGURG);
+ } else {
+ /* Start a new monitor */
+ if (ast_pthread_create_background(&monitor_thread, NULL, do_monitor, NULL) < 0) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
+ return -1;
+ }
+ }
+ ast_mutex_unlock(&monlock);
+ return 0;
+}
+
+static struct ast_channel *mgcp_request(const char *type, int format, void *data, int *cause)
+{
+ int oldformat;
+ struct mgcp_subchannel *sub;
+ struct ast_channel *tmpc = NULL;
+ char tmp[256];
+ char *dest = data;
+
+ oldformat = format;
+ format &= capability;
+ if (!format) {
+ ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", format);
+ return NULL;
+ }
+ ast_copy_string(tmp, dest, sizeof(tmp));
+ if (ast_strlen_zero(tmp)) {
+ ast_log(LOG_NOTICE, "MGCP Channels require an endpoint\n");
+ return NULL;
+ }
+ sub = find_subchannel_and_lock(tmp, 0, NULL);
+ if (!sub) {
+ ast_log(LOG_WARNING, "Unable to find MGCP endpoint '%s'\n", tmp);
+ *cause = AST_CAUSE_UNREGISTERED;
+ return NULL;
+ }
+
+ ast_verb(3, "MGCP mgcp_request(%s)\n", tmp);
+ ast_verb(3, "MGCP cw: %d, dnd: %d, so: %d, sno: %d\n",
+ sub->parent->callwaiting, sub->parent->dnd, sub->owner ? 1 : 0, sub->next->owner ? 1: 0);
+ /* Must be busy */
+ if (((sub->parent->callwaiting) && ((sub->owner) && (sub->next->owner))) ||
+ ((!sub->parent->callwaiting) && (sub->owner)) ||
+ (sub->parent->dnd && (ast_strlen_zero(sub->parent->call_forward)))) {
+ if (sub->parent->hookstate == MGCP_ONHOOK) {
+ if (has_voicemail(sub->parent)) {
+ transmit_notify_request(sub,"L/vmwi(+)");
+ } else {
+ transmit_notify_request(sub,"L/vmwi(-)");
+ }
+ }
+ *cause = AST_CAUSE_BUSY;
+ ast_mutex_unlock(&sub->lock);
+ return NULL;
+ }
+ tmpc = mgcp_new(sub->owner ? sub->next : sub, AST_STATE_DOWN);
+ ast_mutex_unlock(&sub->lock);
+ if (!tmpc)
+ ast_log(LOG_WARNING, "Unable to make channel for '%s'\n", tmp);
+ restart_monitor();
+ return tmpc;
+}
+
+/* modified for reload support */
+/*! \brief build_gateway: parse mgcp.conf and create gateway/endpoint structures */
+static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v)
+{
+ struct mgcp_gateway *gw;
+ struct mgcp_endpoint *e;
+ struct mgcp_subchannel *sub;
+ /*char txident[80];*/
+ int i=0, y=0;
+ int gw_reload = 0;
+ int ep_reload = 0;
+ canreinvite = CANREINVITE;
+
+ /* locate existing gateway */
+ gw = gateways;
+ while (gw) {
+ if (!strcasecmp(cat, gw->name)) {
+ /* gateway already exists */
+ gw->delme = 0;
+ gw_reload = 1;
+ break;
+ }
+ gw = gw->next;
+ }
+
+ if (!gw)
+ gw = ast_calloc(1, sizeof(*gw));
+
+ if (gw) {
+ if (!gw_reload) {
+ gw->expire = -1;
+ gw->retransid = -1; /* SC */
+ ast_mutex_init(&gw->msgs_lock);
+ ast_copy_string(gw->name, cat, sizeof(gw->name));
+ /* check if the name is numeric ip */
+ if ((strchr(gw->name, '.')) && inet_addr(gw->name) != INADDR_NONE)
+ gw->isnamedottedip = 1;
+ }
+ while(v) {
+ if (!strcasecmp(v->name, "host")) {
+ if (!strcasecmp(v->value, "dynamic")) {
+ /* They'll register with us */
+ gw->dynamic = 1;
+ memset(&gw->addr.sin_addr, 0, 4);
+ if (gw->addr.sin_port) {
+ /* If we've already got a port, make it the default rather than absolute */
+ gw->defaddr.sin_port = gw->addr.sin_port;
+ gw->addr.sin_port = 0;
+ }
+ } else {
+ /* Non-dynamic. Make sure we become that way if we're not */
+ if (gw->expire > -1)
+ ast_sched_del(sched, gw->expire);
+ gw->expire = -1;
+ gw->dynamic = 0;
+ if (ast_get_ip(&gw->addr, v->value)) {
+ if (!gw_reload) {
+ ast_mutex_destroy(&gw->msgs_lock);
+ ast_free(gw);
+ }
+ return NULL;
+ }
+ }
+ } else if (!strcasecmp(v->name, "defaultip")) {
+ if (ast_get_ip(&gw->defaddr, v->value)) {
+ if (!gw_reload) {
+ ast_mutex_destroy(&gw->msgs_lock);
+ ast_free(gw);
+ }
+ return NULL;
+ }
+ } else if (!strcasecmp(v->name, "permit") ||
+ !strcasecmp(v->name, "deny")) {
+ gw->ha = ast_append_ha(v->name, v->value, gw->ha, NULL);
+ } else if (!strcasecmp(v->name, "port")) {
+ gw->addr.sin_port = htons(atoi(v->value));
+ } else if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(context, v->value, sizeof(context));
+ } else if (!strcasecmp(v->name, "dtmfmode")) {
+ if (!strcasecmp(v->value, "inband"))
+ dtmfmode = MGCP_DTMF_INBAND;
+ else if (!strcasecmp(v->value, "rfc2833"))
+ dtmfmode = MGCP_DTMF_RFC2833;
+ else if (!strcasecmp(v->value, "hybrid"))
+ dtmfmode = MGCP_DTMF_HYBRID;
+ else if (!strcasecmp(v->value, "none"))
+ dtmfmode = 0;
+ else
+ ast_log(LOG_WARNING, "'%s' is not a valid DTMF mode at line %d\n", v->value, v->lineno);
+ } else if (!strcasecmp(v->name, "nat")) {
+ nat = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callerid")) {
+ if (!strcasecmp(v->value, "asreceived")) {
+ cid_num[0] = '\0';
+ cid_name[0] = '\0';
+ } else {
+ ast_callerid_split(v->value, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num));
+ }
+ } else if (!strcasecmp(v->name, "language")) {
+ ast_copy_string(language, v->value, sizeof(language));
+ } else if (!strcasecmp(v->name, "accountcode")) {
+ ast_copy_string(accountcode, v->value, sizeof(accountcode));
+ } else if (!strcasecmp(v->name, "amaflags")) {
+ y = ast_cdr_amaflags2int(v->value);
+ if (y < 0) {
+ ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno);
+ } else {
+ amaflags = y;
+ }
+ } else if (!strcasecmp(v->name, "musiconhold")) {
+ ast_copy_string(musicclass, v->value, sizeof(musicclass));
+ } else if (!strcasecmp(v->name, "callgroup")) {
+ cur_callergroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "pickupgroup")) {
+ cur_pickupgroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "immediate")) {
+ immediate = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "cancallforward")) {
+ cancallforward = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "singlepath")) {
+ singlepath = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "canreinvite")) {
+ canreinvite = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "mailbox")) {
+ ast_copy_string(mailbox, v->value, sizeof(mailbox));
+ } else if (!strcasecmp(v->name, "adsi")) {
+ adsi = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callreturn")) {
+ callreturn = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callwaiting")) {
+ callwaiting = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "slowsequence")) {
+ slowsequence = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "transfer")) {
+ transfer = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "threewaycalling")) {
+ threewaycalling = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "wcardep")) {
+ /* locate existing endpoint */
+ e = gw->endpoints;
+ while (e) {
+ if (!strcasecmp(v->value, e->name)) {
+ /* endpoint already exists */
+ e->delme = 0;
+ ep_reload = 1;
+ break;
+ }
+ e = e->next;
+ }
+
+ if (!e) {
+ /* Allocate wildcard endpoint */
+ e = ast_calloc(1, sizeof(*e));
+ ep_reload = 0;
+ }
+
+ if (e) {
+ if (!ep_reload) {
+ memset(e, 0, sizeof(struct mgcp_endpoint));
+ ast_mutex_init(&e->lock);
+ ast_mutex_init(&e->rqnt_queue_lock);
+ ast_mutex_init(&e->cmd_queue_lock);
+ ast_copy_string(e->name, v->value, sizeof(e->name));
+ e->needaudit = 1;
+ }
+ ast_copy_string(gw->wcardep, v->value, sizeof(gw->wcardep));
+ /* XXX Should we really check for uniqueness?? XXX */
+ ast_copy_string(e->accountcode, accountcode, sizeof(e->accountcode));
+ ast_copy_string(e->context, context, sizeof(e->context));
+ ast_copy_string(e->cid_num, cid_num, sizeof(e->cid_num));
+ ast_copy_string(e->cid_name, cid_name, sizeof(e->cid_name));
+ ast_copy_string(e->language, language, sizeof(e->language));
+ ast_copy_string(e->musicclass, musicclass, sizeof(e->musicclass));
+ ast_copy_string(e->mailbox, mailbox, sizeof(e->mailbox));
+ if (!ast_strlen_zero(e->mailbox)) {
+ char *mailbox, *context;
+ context = mailbox = ast_strdupa(e->mailbox);
+ strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+ e->mwi_event_sub = ast_event_subscribe(AST_EVENT_MWI, mwi_event_cb, NULL,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
+ AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_EXISTS,
+ AST_EVENT_IE_END);
+ }
+ snprintf(e->rqnt_ident, sizeof(e->rqnt_ident), "%08lx", ast_random());
+ e->msgstate = -1;
+ e->amaflags = amaflags;
+ e->capability = capability;
+ e->parent = gw;
+ e->dtmfmode = dtmfmode;
+ if (!ep_reload && e->sub && e->sub->rtp)
+ e->dtmfmode |= MGCP_DTMF_INBAND;
+ e->adsi = adsi;
+ e->type = TYPE_LINE;
+ e->immediate = immediate;
+ e->callgroup=cur_callergroup;
+ e->pickupgroup=cur_pickupgroup;
+ e->callreturn = callreturn;
+ e->cancallforward = cancallforward;
+ e->singlepath = singlepath;
+ e->canreinvite = canreinvite;
+ e->callwaiting = callwaiting;
+ e->hascallwaiting = callwaiting;
+ e->slowsequence = slowsequence;
+ e->transfer = transfer;
+ e->threewaycalling = threewaycalling;
+ e->onhooktime = time(NULL);
+ /* ASSUME we're onhook */
+ e->hookstate = MGCP_ONHOOK;
+ if (!ep_reload) {
+ /*snprintf(txident, sizeof(txident), "%08lx", ast_random());*/
+ for (i = 0; i < MAX_SUBS; i++) {
+ sub = ast_calloc(1, sizeof(*sub));
+ if (sub) {
+ ast_verb(3, "Allocating subchannel '%d' on %s@%s\n", i, e->name, gw->name);
+ ast_mutex_init(&sub->lock);
+ ast_mutex_init(&sub->cx_queue_lock);
+ sub->parent = e;
+ sub->id = i;
+ snprintf(sub->txident, sizeof(sub->txident), "%08lx", ast_random());
+ /*stnrcpy(sub->txident, txident, sizeof(sub->txident) - 1);*/
+ sub->cxmode = MGCP_CX_INACTIVE;
+ sub->nat = nat;
+ sub->next = e->sub;
+ e->sub = sub;
+ } else {
+ /* XXX Should find a way to clean up our memory */
+ ast_log(LOG_WARNING, "Out of memory allocating subchannel\n");
+ return NULL;
+ }
+ }
+ /* Make out subs a circular linked list so we can always sping through the whole bunch */
+ sub = e->sub;
+ /* find the end of the list */
+ while(sub->next){
+ sub = sub->next;
+ }
+ /* set the last sub->next to the first sub */
+ sub->next = e->sub;
+
+ e->next = gw->endpoints;
+ gw->endpoints = e;
+ }
+ }
+ } else if (!strcasecmp(v->name, "trunk") ||
+ !strcasecmp(v->name, "line")) {
+
+ /* locate existing endpoint */
+ e = gw->endpoints;
+ while (e) {
+ if (!strcasecmp(v->value, e->name)) {
+ /* endpoint already exists */
+ e->delme = 0;
+ ep_reload = 1;
+ break;
+ }
+ e = e->next;
+ }
+
+ if (!e) {
+ e = ast_calloc(1, sizeof(*e));
+ ep_reload = 0;
+ }
+
+ if (e) {
+ if (!ep_reload) {
+ ast_mutex_init(&e->lock);
+ ast_mutex_init(&e->rqnt_queue_lock);
+ ast_mutex_init(&e->cmd_queue_lock);
+ ast_copy_string(e->name, v->value, sizeof(e->name));
+ e->needaudit = 1;
+ }
+ /* XXX Should we really check for uniqueness?? XXX */
+ ast_copy_string(e->accountcode, accountcode, sizeof(e->accountcode));
+ ast_copy_string(e->context, context, sizeof(e->context));
+ ast_copy_string(e->cid_num, cid_num, sizeof(e->cid_num));
+ ast_copy_string(e->cid_name, cid_name, sizeof(e->cid_name));
+ ast_copy_string(e->language, language, sizeof(e->language));
+ ast_copy_string(e->musicclass, musicclass, sizeof(e->musicclass));
+ ast_copy_string(e->mailbox, mailbox, sizeof(e->mailbox));
+ if (!ast_strlen_zero(mailbox)) {
+ ast_verb(3, "Setting mailbox '%s' on %s@%s\n", mailbox, gw->name, e->name);
+ }
+ if (!ep_reload) {
+ /* XXX potential issue due to reload */
+ e->msgstate = -1;
+ e->parent = gw;
+ }
+ e->amaflags = amaflags;
+ e->capability = capability;
+ e->dtmfmode = dtmfmode;
+ e->adsi = adsi;
+ if (!strcasecmp(v->name, "trunk"))
+ e->type = TYPE_TRUNK;
+ else
+ e->type = TYPE_LINE;
+
+ e->immediate = immediate;
+ e->callgroup=cur_callergroup;
+ e->pickupgroup=cur_pickupgroup;
+ e->callreturn = callreturn;
+ e->cancallforward = cancallforward;
+ e->canreinvite = canreinvite;
+ e->singlepath = singlepath;
+ e->callwaiting = callwaiting;
+ e->hascallwaiting = callwaiting;
+ e->slowsequence = slowsequence;
+ e->transfer = transfer;
+ e->threewaycalling = threewaycalling;
+ if (!ep_reload) {
+ e->onhooktime = time(NULL);
+ /* ASSUME we're onhook */
+ e->hookstate = MGCP_ONHOOK;
+ snprintf(e->rqnt_ident, sizeof(e->rqnt_ident), "%08lx", ast_random());
+ }
+
+ for (i = 0, sub = NULL; i < MAX_SUBS; i++) {
+ if (!ep_reload) {
+ sub = ast_calloc(1, sizeof(*sub));
+ } else {
+ if (!sub)
+ sub = e->sub;
+ else
+ sub = sub->next;
+ }
+
+ if (sub) {
+ if (!ep_reload) {
+ ast_verb(3, "Allocating subchannel '%d' on %s@%s\n", i, e->name, gw->name);
+ ast_mutex_init(&sub->lock);
+ ast_mutex_init(&sub->cx_queue_lock);
+ ast_copy_string(sub->magic, MGCP_SUBCHANNEL_MAGIC, sizeof(sub->magic));
+ sub->parent = e;
+ sub->id = i;
+ snprintf(sub->txident, sizeof(sub->txident), "%08lx", ast_random());
+ sub->cxmode = MGCP_CX_INACTIVE;
+ sub->next = e->sub;
+ e->sub = sub;
+ }
+ sub->nat = nat;
+ } else {
+ /* XXX Should find a way to clean up our memory */
+ ast_log(LOG_WARNING, "Out of memory allocating subchannel\n");
+ return NULL;
+ }
+ }
+ if (!ep_reload) {
+ /* Make out subs a circular linked list so we can always sping through the whole bunch */
+ sub = e->sub;
+ /* find the end of the list */
+ while (sub->next) {
+ sub = sub->next;
+ }
+ /* set the last sub->next to the first sub */
+ sub->next = e->sub;
+
+ e->next = gw->endpoints;
+ gw->endpoints = e;
+ }
+ }
+ } else
+ ast_log(LOG_WARNING, "Don't know keyword '%s' at line %d\n", v->name, v->lineno);
+ v = v->next;
+ }
+ }
+ if (!ntohl(gw->addr.sin_addr.s_addr) && !gw->dynamic) {
+ ast_log(LOG_WARNING, "Gateway '%s' lacks IP address and isn't dynamic\n", gw->name);
+ if (!gw_reload) {
+ ast_mutex_destroy(&gw->msgs_lock);
+ ast_free(gw);
+ }
+ return NULL;
+ }
+ gw->defaddr.sin_family = AF_INET;
+ gw->addr.sin_family = AF_INET;
+ if (gw->defaddr.sin_addr.s_addr && !ntohs(gw->defaddr.sin_port))
+ gw->defaddr.sin_port = htons(DEFAULT_MGCP_GW_PORT);
+ if (gw->addr.sin_addr.s_addr && !ntohs(gw->addr.sin_port))
+ gw->addr.sin_port = htons(DEFAULT_MGCP_GW_PORT);
+ if (gw->addr.sin_addr.s_addr)
+ if (ast_ouraddrfor(&gw->addr.sin_addr, &gw->ourip))
+ memcpy(&gw->ourip, &__ourip, sizeof(gw->ourip));
+
+ return (gw_reload ? NULL : gw);
+}
+
+static enum ast_rtp_get_result mgcp_get_rtp_peer(struct ast_channel *chan, struct ast_rtp **rtp)
+{
+ struct mgcp_subchannel *sub = NULL;
+
+ if (!(sub = chan->tech_pvt) || !(sub->rtp))
+ return AST_RTP_GET_FAILED;
+
+ *rtp = sub->rtp;
+
+ if (sub->parent->canreinvite)
+ return AST_RTP_TRY_NATIVE;
+ else
+ return AST_RTP_TRY_PARTIAL;
+}
+
+static int mgcp_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp, struct ast_rtp *vrtp, struct ast_rtp *trtp, int codecs, int nat_active)
+{
+ /* XXX Is there such thing as video support with MGCP? XXX */
+ struct mgcp_subchannel *sub;
+ sub = chan->tech_pvt;
+ if (sub && !sub->alreadygone) {
+ transmit_modify_with_sdp(sub, rtp, codecs);
+ return 0;
+ }
+ return -1;
+}
+
+static struct ast_rtp_protocol mgcp_rtp = {
+ .type = "MGCP",
+ .get_rtp_info = mgcp_get_rtp_peer,
+ .set_rtp_peer = mgcp_set_rtp_peer,
+};
+
+static void destroy_endpoint(struct mgcp_endpoint *e)
+{
+ struct mgcp_subchannel *sub = e->sub->next, *s;
+ int i;
+
+ for (i = 0; i < MAX_SUBS; i++) {
+ ast_mutex_lock(&sub->lock);
+ if (!ast_strlen_zero(sub->cxident)) {
+ transmit_connection_del(sub);
+ }
+ if (sub->rtp) {
+ ast_rtp_destroy(sub->rtp);
+ sub->rtp = NULL;
+ }
+ memset(sub->magic, 0, sizeof(sub->magic));
+ mgcp_queue_hangup(sub);
+ dump_cmd_queues(NULL, sub);
+ ast_mutex_unlock(&sub->lock);
+ sub = sub->next;
+ }
+
+ if (e->dsp) {
+ ast_dsp_free(e->dsp);
+ }
+
+ dump_queue(e->parent, e);
+ dump_cmd_queues(e, NULL);
+
+ sub = e->sub;
+ for (i = 0; (i < MAX_SUBS) && sub; i++) {
+ s = sub;
+ sub = sub->next;
+ ast_mutex_destroy(&s->lock);
+ ast_mutex_destroy(&s->cx_queue_lock);
+ ast_free(s);
+ }
+
+ if (e->mwi_event_sub)
+ ast_event_unsubscribe(e->mwi_event_sub);
+
+ ast_mutex_destroy(&e->lock);
+ ast_mutex_destroy(&e->rqnt_queue_lock);
+ ast_mutex_destroy(&e->cmd_queue_lock);
+ ast_free(e);
+}
+
+static void destroy_gateway(struct mgcp_gateway *g)
+{
+ if (g->ha)
+ ast_free_ha(g->ha);
+
+ dump_queue(g, NULL);
+
+ ast_free(g);
+}
+
+static void prune_gateways(void)
+{
+ struct mgcp_gateway *g, *z, *r;
+ struct mgcp_endpoint *e, *p, *t;
+
+ ast_mutex_lock(&gatelock);
+
+ /* prune gateways */
+ for (z = NULL, g = gateways; g;) {
+ /* prune endpoints */
+ for (p = NULL, e = g->endpoints; e; ) {
+ if (e->delme || g->delme) {
+ t = e;
+ e = e->next;
+ if (!p)
+ g->endpoints = e;
+ else
+ p->next = e;
+ destroy_endpoint(t);
+ } else {
+ p = e;
+ e = e->next;
+ }
+ }
+
+ if (g->delme) {
+ r = g;
+ g = g->next;
+ if (!z)
+ gateways = g;
+ else
+ z->next = g;
+
+ destroy_gateway(r);
+ } else {
+ z = g;
+ g = g->next;
+ }
+ }
+
+ ast_mutex_unlock(&gatelock);
+}
+
+static int reload_config(int reload)
+{
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ struct mgcp_gateway *g;
+ struct mgcp_endpoint *e;
+ char *cat;
+ struct ast_hostent ahp;
+ struct hostent *hp;
+ int format;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+
+ if (gethostname(ourhost, sizeof(ourhost)-1)) {
+ ast_log(LOG_WARNING, "Unable to get hostname, MGCP disabled\n");
+ return 0;
+ }
+ cfg = ast_config_load(config, config_flags);
+
+ /* We *must* have a config file otherwise stop immediately */
+ if (!cfg) {
+ ast_log(LOG_NOTICE, "Unable to load config %s, MGCP disabled\n", config);
+ return 0;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+
+ memset(&bindaddr, 0, sizeof(bindaddr));
+ dtmfmode = 0;
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ v = ast_variable_browse(cfg, "general");
+ while (v) {
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value)) {
+ v = v->next;
+ continue;
+ }
+
+ /* Create the interface list */
+ if (!strcasecmp(v->name, "bindaddr")) {
+ if (!(hp = ast_gethostbyname(v->value, &ahp))) {
+ ast_log(LOG_WARNING, "Invalid address: %s\n", v->value);
+ } else {
+ memcpy(&bindaddr.sin_addr, hp->h_addr, sizeof(bindaddr.sin_addr));
+ }
+ } else if (!strcasecmp(v->name, "allow")) {
+ format = ast_getformatbyname(v->value);
+ if (format < 1)
+ ast_log(LOG_WARNING, "Cannot allow unknown format '%s'\n", v->value);
+ else
+ capability |= format;
+ } else if (!strcasecmp(v->name, "disallow")) {
+ format = ast_getformatbyname(v->value);
+ if (format < 1)
+ ast_log(LOG_WARNING, "Cannot disallow unknown format '%s'\n", v->value);
+ else
+ capability &= ~format;
+ } else if (!strcasecmp(v->name, "tos")) {
+ if (ast_str2tos(v->value, &tos))
+ ast_log(LOG_WARNING, "Invalid tos value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "tos_audio")) {
+ if (ast_str2tos(v->value, &tos_audio))
+ ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos")) {
+ if (ast_str2cos(v->value, &cos))
+ ast_log(LOG_WARNING, "Invalid cos value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos_audio")) {
+ if (ast_str2cos(v->value, &cos_audio))
+ ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "port")) {
+ if (sscanf(v->value, "%d", &ourport) == 1) {
+ bindaddr.sin_port = htons(ourport);
+ } else {
+ ast_log(LOG_WARNING, "Invalid port number '%s' at line %d of %s\n", v->value, v->lineno, config);
+ }
+ }
+ v = v->next;
+ }
+
+ /* mark existing entries for deletion */
+ ast_mutex_lock(&gatelock);
+ g = gateways;
+ while (g) {
+ g->delme = 1;
+ e = g->endpoints;
+ while (e) {
+ e->delme = 1;
+ e = e->next;
+ }
+ g = g->next;
+ }
+ ast_mutex_unlock(&gatelock);
+
+ cat = ast_category_browse(cfg, NULL);
+ while(cat) {
+ if (strcasecmp(cat, "general")) {
+ ast_mutex_lock(&gatelock);
+ g = build_gateway(cat, ast_variable_browse(cfg, cat));
+ if (g) {
+ ast_verb(3, "Added gateway '%s'\n", g->name);
+ g->next = gateways;
+ gateways = g;
+ }
+ ast_mutex_unlock(&gatelock);
+
+ /* FS: process queue and IO */
+ if (monitor_thread == pthread_self()) {
+ if (sched) ast_sched_runq(sched);
+ if (io) ast_io_wait(io, 10);
+ }
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+
+ /* prune deleted entries etc. */
+ prune_gateways();
+
+ if (ntohl(bindaddr.sin_addr.s_addr)) {
+ memcpy(&__ourip, &bindaddr.sin_addr, sizeof(__ourip));
+ } else {
+ hp = ast_gethostbyname(ourhost, &ahp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "Unable to get our IP address, MGCP disabled\n");
+ ast_config_destroy(cfg);
+ return 0;
+ }
+ memcpy(&__ourip, hp->h_addr, sizeof(__ourip));
+ }
+ if (!ntohs(bindaddr.sin_port))
+ bindaddr.sin_port = ntohs(DEFAULT_MGCP_CA_PORT);
+ bindaddr.sin_family = AF_INET;
+ ast_mutex_lock(&netlock);
+ if (mgcpsock > -1)
+ close(mgcpsock);
+
+ if (mgcpsock_read_id != NULL)
+ ast_io_remove(io, mgcpsock_read_id);
+ mgcpsock_read_id = NULL;
+
+ mgcpsock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (mgcpsock < 0) {
+ ast_log(LOG_WARNING, "Unable to create MGCP socket: %s\n", strerror(errno));
+ } else {
+ if (bind(mgcpsock, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) {
+ ast_log(LOG_WARNING, "Failed to bind to %s:%d: %s\n",
+ ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port),
+ strerror(errno));
+ close(mgcpsock);
+ mgcpsock = -1;
+ } else {
+ ast_verb(2, "MGCP Listening on %s:%d\n",
+ ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port));
+ ast_netsock_set_qos(mgcpsock, tos, cos, "MGCP");
+ }
+ }
+ ast_mutex_unlock(&netlock);
+ ast_config_destroy(cfg);
+
+ /* send audit only to the new endpoints */
+ g = gateways;
+ while (g) {
+ e = g->endpoints;
+ while (e && e->needaudit) {
+ e->needaudit = 0;
+ transmit_audit_endpoint(e);
+ ast_verb(3, "MGCP Auditing endpoint %s@%s for hookstate\n", e->name, g->name);
+ e = e->next;
+ }
+ g = g->next;
+ }
+
+ return 0;
+}
+
+/*! \brief load_module: PBX load module - initialization ---*/
+static int load_module(void)
+{
+ if (!(sched = sched_context_create())) {
+ ast_log(LOG_WARNING, "Unable to create schedule context\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ if (!(io = io_context_create())) {
+ ast_log(LOG_WARNING, "Unable to create I/O context\n");
+ sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ if (reload_config(0))
+ return AST_MODULE_LOAD_DECLINE;
+
+ /* Make sure we can register our mgcp channel type */
+ if (ast_channel_register(&mgcp_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'MGCP'\n");
+ io_context_destroy(io);
+ sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ ast_rtp_proto_register(&mgcp_rtp);
+ ast_cli_register_multiple(cli_mgcp, sizeof(cli_mgcp) / sizeof(struct ast_cli_entry));
+
+ /* And start the monitor for the first time */
+ restart_monitor();
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static char *mgcp_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ static int deprecated = 0;
+
+ if (e) {
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mgcp reload";
+ e->usage =
+ "Usage: mgcp reload\n"
+ " 'mgcp reload' is deprecated. Please use 'reload chan_mgcp.so' instead.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ }
+
+ if (!deprecated && a && a->argc > 0) {
+ ast_log(LOG_WARNING, "'mgcp reload' is deprecated. Please use 'reload chan_mgcp.so' instead.\n");
+ deprecated = 1;
+ }
+
+ ast_mutex_lock(&mgcp_reload_lock);
+ if (mgcp_reloading) {
+ ast_verbose("Previous mgcp reload not yet done\n");
+ } else
+ mgcp_reloading = 1;
+ ast_mutex_unlock(&mgcp_reload_lock);
+ restart_monitor();
+ return CLI_SUCCESS;
+}
+
+static int reload(void)
+{
+ mgcp_reload(NULL, 0, NULL);
+ return 0;
+}
+
+static int unload_module(void)
+{
+ struct mgcp_endpoint *e;
+ struct mgcp_gateway *g;
+
+ /* Check to see if we're reloading */
+ if (ast_mutex_trylock(&mgcp_reload_lock)) {
+ ast_log(LOG_WARNING, "MGCP is currently reloading. Unable to remove module.\n");
+ return -1;
+ } else {
+ mgcp_reloading = 1;
+ ast_mutex_unlock(&mgcp_reload_lock);
+ }
+
+ /* First, take us out of the channel loop */
+ ast_channel_unregister(&mgcp_tech);
+
+ /* Shut down the monitoring thread */
+ if (!ast_mutex_lock(&monlock)) {
+ if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP)) {
+ pthread_cancel(monitor_thread);
+ pthread_kill(monitor_thread, SIGURG);
+ pthread_join(monitor_thread, NULL);
+ }
+ monitor_thread = AST_PTHREADT_STOP;
+ ast_mutex_unlock(&monlock);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the monitor\n");
+ /* We always want to leave this in a consistent state */
+ ast_channel_register(&mgcp_tech);
+ mgcp_reloading = 0;
+ mgcp_reload(NULL, 0, NULL);
+ return -1;
+ }
+
+ if (!ast_mutex_lock(&gatelock)) {
+ for (g = gateways; g; g = g->next) {
+ g->delme = 1;
+ for (e = g->endpoints; e; e = e->next)
+ e->delme = 1;
+ }
+
+ prune_gateways();
+ ast_mutex_unlock(&gatelock);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the gateways list.\n");
+ /* We always want to leave this in a consistent state */
+ ast_channel_register(&mgcp_tech);
+ /* Allow the monitor to restart */
+ monitor_thread = AST_PTHREADT_NULL;
+ mgcp_reloading = 0;
+ mgcp_reload(NULL, 0, NULL);
+ return -1;
+ }
+
+ close(mgcpsock);
+ ast_rtp_proto_unregister(&mgcp_rtp);
+ ast_cli_unregister_multiple(cli_mgcp, sizeof(cli_mgcp) / sizeof(struct ast_cli_entry));
+ sched_context_destroy(sched);
+
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Media Gateway Control Protocol (MGCP)",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/trunk/channels/chan_misdn.c b/trunk/channels/chan_misdn.c
new file mode 100644
index 000000000..a0153ef4d
--- /dev/null
+++ b/trunk/channels/chan_misdn.c
@@ -0,0 +1,5747 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2004 - 2006, Christian Richter
+ *
+ * Christian Richter <crich@beronet.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ *
+ */
+
+/*!
+ * \file
+ *
+ * \brief the chan_misdn channel driver for Asterisk
+ *
+ * \author Christian Richter <crich@beronet.com>
+ *
+ * \extref MISDN http://www.misdn.org/
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <depend>isdnnet</depend>
+ <depend>misdn</depend>
+ <depend>suppserv</depend>
+ ***/
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <pthread.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <sys/file.h>
+#include <semaphore.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/io.h"
+#include "asterisk/frame.h"
+#include "asterisk/translate.h"
+#include "asterisk/cli.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/dsp.h"
+#include "asterisk/file.h"
+#include "asterisk/callerid.h"
+#include "asterisk/indications.h"
+#include "asterisk/app.h"
+#include "asterisk/features.h"
+#include "asterisk/term.h"
+#include "asterisk/sched.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/causes.h"
+
+#include "chan_misdn_config.h"
+#include "isdn_lib.h"
+
+char global_tracefile[BUFFERSIZE + 1];
+
+static int g_config_initialized = 0;
+
+struct misdn_jb{
+ int size;
+ int upper_threshold;
+ char *samples, *ok;
+ int wp,rp;
+ int state_empty;
+ int state_full;
+ int state_buffer;
+ int bytes_wrote;
+ ast_mutex_t mutexjb;
+};
+
+
+
+/*! \brief allocates the jb-structure and initialise the elements*/
+struct misdn_jb *misdn_jb_init(int size, int upper_threshold);
+
+/*! \brief frees the data and destroys the given jitterbuffer struct */
+void misdn_jb_destroy(struct misdn_jb *jb);
+
+/*! \brief fills the jitterbuffer with len data returns < 0 if there was an
+error (bufferoverun). */
+int misdn_jb_fill(struct misdn_jb *jb, const char *data, int len);
+
+/*! \brief gets len bytes out of the jitterbuffer if available, else only the
+available data is returned and the return value indicates the number
+of data. */
+int misdn_jb_empty(struct misdn_jb *jb, char *data, int len);
+
+static char *complete_ch(struct ast_cli_args *a);
+static char *complete_debug_port(struct ast_cli_args *a);
+static char *complete_show_config(struct ast_cli_args *a);
+
+/* BEGIN: chan_misdn.h */
+
+ast_mutex_t release_lock;
+
+enum misdn_chan_state {
+ MISDN_NOTHING=0, /*!< at beginning */
+ MISDN_WAITING4DIGS, /*!< when waiting for infos */
+ MISDN_EXTCANTMATCH, /*!< when asterisk couldnt match our ext */
+ MISDN_INCOMING_SETUP, /*!< for incoming setups*/
+ MISDN_DIALING, /*!< when pbx_start */
+ MISDN_PROGRESS, /*!< we got a progress */
+ MISDN_PROCEEDING, /*!< we got a progress */
+ MISDN_CALLING, /*!< when misdn_call is called */
+ MISDN_CALLING_ACKNOWLEDGE, /*!< when we get SETUP_ACK */
+ MISDN_ALERTING, /*!< when Alerting */
+ MISDN_BUSY, /*!< when BUSY */
+ MISDN_CONNECTED, /*!< when connected */
+ MISDN_PRECONNECTED, /*!< when connected */
+ MISDN_DISCONNECTED, /*!< when connected */
+ MISDN_RELEASED, /*!< when connected */
+ MISDN_BRIDGED, /*!< when bridged */
+ MISDN_CLEANING, /*!< when hangup from * but we were connected before */
+ MISDN_HUNGUP_FROM_MISDN, /*!< when DISCONNECT/RELEASE/REL_COMP cam from misdn */
+ MISDN_HUNGUP_FROM_AST, /*!< when DISCONNECT/RELEASE/REL_COMP came out of */
+ /* misdn_hangup */
+ MISDN_HOLDED, /*!< if this chan is holded */
+ MISDN_HOLD_DISCONNECT, /*!< if this chan is holded */
+
+};
+
+#define ORG_AST 1
+#define ORG_MISDN 2
+
+struct hold_info {
+ int port;
+ int channel;
+};
+
+struct chan_list {
+
+ char allowed_bearers[BUFFERSIZE + 1];
+
+ enum misdn_chan_state state;
+ int need_queue_hangup;
+ int need_hangup;
+ int need_busy;
+
+ int originator;
+ int noautorespond_on_setup;
+
+ int norxtone;
+ int notxtone;
+
+ int toggle_ec;
+
+ int incoming_early_audio;
+
+ int ignore_dtmf;
+
+ int pipe[2];
+ char ast_rd_buf[4096];
+ struct ast_frame frame;
+
+ int faxdetect; /*!< 0:no 1:yes 2:yes+nojump */
+ int faxdetect_timeout;
+ struct timeval faxdetect_tv;
+ int faxhandled;
+
+ int ast_dsp;
+
+ int jb_len;
+ int jb_upper_threshold;
+ struct misdn_jb *jb;
+
+ struct ast_dsp *dsp;
+ struct ast_trans_pvt *trans;
+
+ struct ast_channel * ast;
+
+ int dummy;
+
+ struct misdn_bchannel *bc;
+
+ struct hold_info hold_info;
+
+ unsigned int l3id;
+ int addr;
+
+ char context[BUFFERSIZE];
+
+ int zero_read_cnt;
+ int dropped_frame_cnt;
+
+ int far_alerting;
+
+ int nttimeout;
+
+ int other_pid;
+ struct chan_list *other_ch;
+
+ const struct ind_tone_zone_sound *ts;
+
+ int overlap_dial;
+ int overlap_dial_task;
+ ast_mutex_t overlap_tv_lock;
+ struct timeval overlap_tv;
+
+ struct chan_list *peer;
+ struct chan_list *next;
+ struct chan_list *prev;
+ struct chan_list *first;
+};
+
+
+
+void export_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch);
+void import_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch);
+
+struct robin_list {
+ char *group;
+ int port;
+ int channel;
+ struct robin_list *next;
+ struct robin_list *prev;
+};
+static struct robin_list *robin = NULL;
+
+
+
+static struct ast_frame *process_ast_dsp(struct chan_list *tmp, struct ast_frame *frame);
+
+
+
+static inline void free_robin_list_r (struct robin_list *r)
+{
+ if (r) {
+ if (r->next)
+ free_robin_list_r(r->next);
+ if (r->group)
+ ast_free(r->group);
+ ast_free(r);
+ }
+}
+
+static void free_robin_list ( void )
+{
+ free_robin_list_r(robin);
+ robin = NULL;
+}
+
+static struct robin_list* get_robin_position (char *group)
+{
+ struct robin_list *new;
+ struct robin_list *iter = robin;
+ for (; iter; iter = iter->next) {
+ if (!strcasecmp(iter->group, group))
+ return iter;
+ }
+ new = ast_calloc(1, sizeof(*new));
+ new->group = strndup(group, strlen(group));
+ new->channel = 1;
+ if (robin) {
+ new->next = robin;
+ robin->prev = new;
+ }
+ robin = new;
+ return robin;
+}
+
+
+/*! \brief the main schedule context for stuff like l1 watcher, overlap dial, ... */
+static struct sched_context *misdn_tasks = NULL;
+static pthread_t misdn_tasks_thread;
+
+static int *misdn_ports;
+
+static void chan_misdn_log(int level, int port, char *tmpl, ...);
+
+static struct ast_channel *misdn_new(struct chan_list *cl, int state, char *exten, char *callerid, int format, int port, int c);
+static void send_digit_to_chan(struct chan_list *cl, char digit );
+
+static void hangup_chan(struct chan_list *ch);
+static int pbx_start_chan(struct chan_list *ch);
+
+#define MISDN_ASTERISK_TECH_PVT(ast) ast->tech_pvt
+#define MISDN_ASTERISK_PVT(ast) 1
+
+#include "asterisk/strings.h"
+
+/* #define MISDN_DEBUG 1 */
+
+static const char misdn_type[] = "mISDN";
+
+static int tracing = 0 ;
+
+/*! \brief Only alaw and mulaw is allowed for now */
+static int prefformat = AST_FORMAT_ALAW ; /* AST_FORMAT_SLINEAR ; AST_FORMAT_ULAW | */
+
+static int *misdn_debug;
+static int *misdn_debug_only;
+static int max_ports;
+
+static int *misdn_in_calls;
+static int *misdn_out_calls;
+
+
+struct chan_list dummy_cl;
+
+struct chan_list *cl_te=NULL;
+ast_mutex_t cl_te_lock;
+
+static enum event_response_e
+cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data);
+
+static void send_cause2ast(struct ast_channel *ast, struct misdn_bchannel*bc, struct chan_list *ch);
+
+static void cl_queue_chan(struct chan_list **list, struct chan_list *chan);
+static void cl_dequeue_chan(struct chan_list **list, struct chan_list *chan);
+static struct chan_list *find_chan_by_bc(struct chan_list *list, struct misdn_bchannel *bc);
+static struct chan_list *find_chan_by_pid(struct chan_list *list, int pid);
+
+
+
+static int dialtone_indicate(struct chan_list *cl);
+static int hanguptone_indicate(struct chan_list *cl);
+static int stop_indicate(struct chan_list *cl);
+
+static int start_bc_tones(struct chan_list *cl);
+static int stop_bc_tones(struct chan_list *cl);
+static void release_chan(struct misdn_bchannel *bc);
+
+static int misdn_check_l2l1(struct ast_channel *chan, void *data);
+static int misdn_set_opt_exec(struct ast_channel *chan, void *data);
+static int misdn_facility_exec(struct ast_channel *chan, void *data);
+
+int chan_misdn_jb_empty(struct misdn_bchannel *bc, char *buf, int len);
+
+
+void debug_numplan(int port, int numplan, char *type);
+
+
+int add_out_calls(int port);
+int add_in_calls(int port);
+
+
+#ifdef MISDN_1_2
+static int update_pipeline_config(struct misdn_bchannel *bc);
+#else
+static int update_ec_config(struct misdn_bchannel *bc);
+#endif
+
+
+
+
+/*protos*/
+
+int chan_misdn_jb_empty ( struct misdn_bchannel *bc, char *buf, int len);
+
+/*************** Helpers *****************/
+
+static struct chan_list * get_chan_by_ast(struct ast_channel *ast)
+{
+ struct chan_list *tmp;
+
+ for (tmp=cl_te; tmp; tmp = tmp->next) {
+ if ( tmp->ast == ast ) return tmp;
+ }
+
+ return NULL;
+}
+
+static struct chan_list * get_chan_by_ast_name(char *name)
+{
+ struct chan_list *tmp;
+
+ for (tmp=cl_te; tmp; tmp = tmp->next) {
+ if ( tmp->ast && strcmp(tmp->ast->name,name) == 0) return tmp;
+ }
+
+ return NULL;
+}
+
+
+
+struct allowed_bearers {
+ int cap;
+ int val;
+ char *name;
+};
+
+struct allowed_bearers allowed_bearers_array[]={
+ {INFO_CAPABILITY_SPEECH,1,"speech"},
+ {INFO_CAPABILITY_AUDIO_3_1K,2,"3_1khz"},
+ {INFO_CAPABILITY_DIGITAL_UNRESTRICTED,4,"digital_unrestricted"},
+ {INFO_CAPABILITY_DIGITAL_RESTRICTED,8,"digital_restriced"},
+ {INFO_CAPABILITY_VIDEO,16,"video"}
+};
+
+static char *bearer2str(int cap) {
+ static char *bearers[]={
+ "Speech",
+ "Audio 3.1k",
+ "Unres Digital",
+ "Res Digital",
+ "Video",
+ "Unknown Bearer"
+ };
+
+ switch (cap) {
+ case INFO_CAPABILITY_SPEECH:
+ return bearers[0];
+ break;
+ case INFO_CAPABILITY_AUDIO_3_1K:
+ return bearers[1];
+ break;
+ case INFO_CAPABILITY_DIGITAL_UNRESTRICTED:
+ return bearers[2];
+ break;
+ case INFO_CAPABILITY_DIGITAL_RESTRICTED:
+ return bearers[3];
+ break;
+ case INFO_CAPABILITY_VIDEO:
+ return bearers[4];
+ break;
+ default:
+ return bearers[5];
+ break;
+ }
+}
+
+
+static void print_facility(struct FacParm *fac, struct misdn_bchannel *bc)
+{
+ switch (fac->Function) {
+#ifdef HAVE_MISDN_FAC_RESULT
+ case Fac_RESULT:
+ chan_misdn_log(0, bc->port," --> Received RESULT Operation\n");
+ break;
+#endif
+#ifdef HAVE_MISDN_FAC_ERROR
+ case Fac_ERROR:
+ chan_misdn_log(0, bc->port," --> Received Error Operation\n");
+ chan_misdn_log(0, bc->port," --> Value:%d Error:%s\n",fac->u.ERROR.errorValue, fac->u.ERROR.error);
+ break;
+#endif
+ case Fac_CD:
+ chan_misdn_log(1,bc->port," --> calldeflect to: %s, screened: %s\n", fac->u.CDeflection.DeflectedToNumber,
+ fac->u.CDeflection.PresentationAllowed ? "yes" : "no");
+ break;
+ case Fac_AOCDCurrency:
+ if (fac->u.AOCDcur.chargeNotAvailable)
+ chan_misdn_log(1,bc->port," --> AOCD currency: charge not available\n");
+ else if (fac->u.AOCDcur.freeOfCharge)
+ chan_misdn_log(1,bc->port," --> AOCD currency: free of charge\n");
+ else if (fac->u.AOCDchu.billingId >= 0)
+ chan_misdn_log(1,bc->port," --> AOCD currency: currency:%s amount:%d multiplier:%d typeOfChargingInfo:%d billingId:%d\n",
+ fac->u.AOCDcur.currency, fac->u.AOCDcur.currencyAmount, fac->u.AOCDcur.multiplier,
+ (fac->u.AOCDcur.typeOfChargingInfo == 0) ? "subTotal" : "total", fac->u.AOCDcur.billingId);
+ else
+ chan_misdn_log(1,bc->port," --> AOCD currency: currency:%s amount:%d multiplier:%d typeOfChargingInfo:%d\n",
+ fac->u.AOCDcur.currency, fac->u.AOCDcur.currencyAmount, fac->u.AOCDcur.multiplier,
+ (fac->u.AOCDcur.typeOfChargingInfo == 0) ? "subTotal" : "total");
+ break;
+ case Fac_AOCDChargingUnit:
+ if (fac->u.AOCDchu.chargeNotAvailable)
+ chan_misdn_log(1,bc->port," --> AOCD charging unit: charge not available\n");
+ else if (fac->u.AOCDchu.freeOfCharge)
+ chan_misdn_log(1,bc->port," --> AOCD charging unit: free of charge\n");
+ else if (fac->u.AOCDchu.billingId >= 0)
+ chan_misdn_log(1,bc->port," --> AOCD charging unit: recordedUnits:%d typeOfChargingInfo:%s billingId:%d\n",
+ fac->u.AOCDchu.recordedUnits, (fac->u.AOCDchu.typeOfChargingInfo == 0) ? "subTotal" : "total", fac->u.AOCDchu.billingId);
+ else
+ chan_misdn_log(1,bc->port," --> AOCD charging unit: recordedUnits:%d typeOfChargingInfo:%s\n",
+ fac->u.AOCDchu.recordedUnits, (fac->u.AOCDchu.typeOfChargingInfo == 0) ? "subTotal" : "total");
+ break;
+ case Fac_None:
+ default:
+ chan_misdn_log(1,bc->port," --> unknown facility\n");
+ }
+}
+
+static void print_bearer(struct misdn_bchannel *bc)
+{
+
+ chan_misdn_log(2, bc->port, " --> Bearer: %s\n",bearer2str(bc->capability));
+
+ switch(bc->law) {
+ case INFO_CODEC_ALAW:
+ chan_misdn_log(2, bc->port, " --> Codec: Alaw\n");
+ break;
+ case INFO_CODEC_ULAW:
+ chan_misdn_log(2, bc->port, " --> Codec: Ulaw\n");
+ break;
+ }
+}
+
+static void export_aoc_vars(int originator, struct ast_channel *ast, struct misdn_bchannel *bc)
+{
+ char buf[128];
+
+ if (!bc->AOCD_need_export || !ast)
+ return;
+
+ if (originator == ORG_AST) {
+ ast = ast_bridged_channel(ast);
+ if (!ast)
+ return;
+ }
+
+ switch (bc->AOCDtype) {
+ case Fac_AOCDCurrency:
+ pbx_builtin_setvar_helper(ast, "AOCD_Type", "currency");
+ if (bc->AOCD.currency.chargeNotAvailable)
+ pbx_builtin_setvar_helper(ast, "AOCD_ChargeAvailable", "no");
+ else {
+ pbx_builtin_setvar_helper(ast, "AOCD_ChargeAvailable", "yes");
+ if (bc->AOCD.currency.freeOfCharge)
+ pbx_builtin_setvar_helper(ast, "AOCD_FreeOfCharge", "yes");
+ else {
+ pbx_builtin_setvar_helper(ast, "AOCD_FreeOfCharge", "no");
+ if (snprintf(buf, sizeof(buf), "%d %s", bc->AOCD.currency.currencyAmount * bc->AOCD.currency.multiplier, bc->AOCD.currency.currency) < sizeof(buf)) {
+ pbx_builtin_setvar_helper(ast, "AOCD_Amount", buf);
+ if (bc->AOCD.currency.billingId >= 0 && snprintf(buf, sizeof(buf), "%d", bc->AOCD.currency.billingId) < sizeof(buf))
+ pbx_builtin_setvar_helper(ast, "AOCD_BillingId", buf);
+ }
+ }
+ }
+ break;
+ case Fac_AOCDChargingUnit:
+ pbx_builtin_setvar_helper(ast, "AOCD_Type", "charging_unit");
+ if (bc->AOCD.chargingUnit.chargeNotAvailable)
+ pbx_builtin_setvar_helper(ast, "AOCD_ChargeAvailable", "no");
+ else {
+ pbx_builtin_setvar_helper(ast, "AOCD_ChargeAvailable", "yes");
+ if (bc->AOCD.chargingUnit.freeOfCharge)
+ pbx_builtin_setvar_helper(ast, "AOCD_FreeOfCharge", "yes");
+ else {
+ pbx_builtin_setvar_helper(ast, "AOCD_FreeOfCharge", "no");
+ if (snprintf(buf, sizeof(buf), "%d", bc->AOCD.chargingUnit.recordedUnits) < sizeof(buf)) {
+ pbx_builtin_setvar_helper(ast, "AOCD_RecordedUnits", buf);
+ if (bc->AOCD.chargingUnit.billingId >= 0 && snprintf(buf, sizeof(buf), "%d", bc->AOCD.chargingUnit.billingId) < sizeof(buf))
+ pbx_builtin_setvar_helper(ast, "AOCD_BillingId", buf);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ bc->AOCD_need_export = 0;
+}
+
+/*************** Helpers END *************/
+
+static void sighandler(int sig)
+{}
+
+static void* misdn_tasks_thread_func (void *data)
+{
+ int wait;
+ struct sigaction sa;
+
+ sa.sa_handler = sighandler;
+ sa.sa_flags = SA_NODEFER;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGUSR1);
+ sigaction(SIGUSR1, &sa, NULL);
+
+ sem_post((sem_t *)data);
+
+ while (1) {
+ wait = ast_sched_wait(misdn_tasks);
+ if (wait < 0)
+ wait = 8000;
+ if (poll(NULL, 0, wait) < 0)
+ chan_misdn_log(4, 0, "Waking up misdn_tasks thread\n");
+ ast_sched_runq(misdn_tasks);
+ }
+ return NULL;
+}
+
+static void misdn_tasks_init (void)
+{
+ sem_t blocker;
+ int i = 5;
+
+ if (sem_init(&blocker, 0, 0)) {
+ perror("chan_misdn: Failed to initialize semaphore!");
+ exit(1);
+ }
+
+ chan_misdn_log(4, 0, "Starting misdn_tasks thread\n");
+
+ misdn_tasks = sched_context_create();
+ pthread_create(&misdn_tasks_thread, NULL, misdn_tasks_thread_func, &blocker);
+
+ while (sem_wait(&blocker) && --i);
+ sem_destroy(&blocker);
+}
+
+static void misdn_tasks_destroy (void)
+{
+ if (misdn_tasks) {
+ chan_misdn_log(4, 0, "Killing misdn_tasks thread\n");
+ if ( pthread_cancel(misdn_tasks_thread) == 0 ) {
+ cb_log(4, 0, "Joining misdn_tasks thread\n");
+ pthread_join(misdn_tasks_thread, NULL);
+ }
+ sched_context_destroy(misdn_tasks);
+ }
+}
+
+static inline void misdn_tasks_wakeup (void)
+{
+ pthread_kill(misdn_tasks_thread, SIGUSR1);
+}
+
+static inline int _misdn_tasks_add_variable (int timeout, ast_sched_cb callback, const void *data, int variable)
+{
+ int task_id;
+
+ if (!misdn_tasks) {
+ misdn_tasks_init();
+ }
+ task_id = ast_sched_add_variable(misdn_tasks, timeout, callback, data, variable);
+ misdn_tasks_wakeup();
+
+ return task_id;
+}
+
+static int misdn_tasks_add (int timeout, ast_sched_cb callback, const void *data)
+{
+ return _misdn_tasks_add_variable(timeout, callback, data, 0);
+}
+
+static int misdn_tasks_add_variable (int timeout, ast_sched_cb callback, const void *data)
+{
+ return _misdn_tasks_add_variable(timeout, callback, data, 1);
+}
+
+static void misdn_tasks_remove (int task_id)
+{
+ ast_sched_del(misdn_tasks, task_id);
+}
+
+static int misdn_l1_task (const void *data)
+{
+ misdn_lib_isdn_l1watcher(*(int *)data);
+ chan_misdn_log(5, *(int *)data, "L1watcher timeout\n");
+ return 1;
+}
+
+static int misdn_overlap_dial_task (const void *data)
+{
+ struct timeval tv_end, tv_now;
+ int diff;
+ struct chan_list *ch = (struct chan_list *)data;
+
+ chan_misdn_log(4, ch->bc->port, "overlap dial task, chan_state: %d\n", ch->state);
+
+ if (ch->state != MISDN_WAITING4DIGS) {
+ ch->overlap_dial_task = -1;
+ return 0;
+ }
+
+ ast_mutex_lock(&ch->overlap_tv_lock);
+ tv_end = ch->overlap_tv;
+ ast_mutex_unlock(&ch->overlap_tv_lock);
+
+ tv_end.tv_sec += ch->overlap_dial;
+ tv_now = ast_tvnow();
+
+ diff = ast_tvdiff_ms(tv_end, tv_now);
+
+ if (diff <= 100) {
+ char *dad=ch->bc->dad, sexten[]="s";
+ /* if we are 100ms near the timeout, we are satisfied.. */
+ stop_indicate(ch);
+
+ if (ast_strlen_zero(ch->bc->dad)) {
+ dad=sexten;
+ strcpy(ch->ast->exten, sexten);
+ }
+
+ if (ast_exists_extension(ch->ast, ch->context, dad, 1, ch->bc->oad)) {
+ ch->state=MISDN_DIALING;
+ if (pbx_start_chan(ch) < 0) {
+ chan_misdn_log(-1, ch->bc->port, "ast_pbx_start returned < 0 in misdn_overlap_dial_task\n");
+ goto misdn_overlap_dial_task_disconnect;
+ }
+ } else {
+misdn_overlap_dial_task_disconnect:
+ hanguptone_indicate(ch);
+ ch->bc->out_cause=1;
+ ch->state=MISDN_CLEANING;
+ misdn_lib_send_event(ch->bc, EVENT_DISCONNECT);
+ }
+ ch->overlap_dial_task = -1;
+ return 0;
+ } else
+ return diff;
+}
+
+static void send_digit_to_chan(struct chan_list *cl, char digit )
+{
+ static const char* dtmf_tones[] = {
+ "!941+1336/100,!0/100", /* 0 */
+ "!697+1209/100,!0/100", /* 1 */
+ "!697+1336/100,!0/100", /* 2 */
+ "!697+1477/100,!0/100", /* 3 */
+ "!770+1209/100,!0/100", /* 4 */
+ "!770+1336/100,!0/100", /* 5 */
+ "!770+1477/100,!0/100", /* 6 */
+ "!852+1209/100,!0/100", /* 7 */
+ "!852+1336/100,!0/100", /* 8 */
+ "!852+1477/100,!0/100", /* 9 */
+ "!697+1633/100,!0/100", /* A */
+ "!770+1633/100,!0/100", /* B */
+ "!852+1633/100,!0/100", /* C */
+ "!941+1633/100,!0/100", /* D */
+ "!941+1209/100,!0/100", /* * */
+ "!941+1477/100,!0/100" }; /* # */
+ struct ast_channel *chan=cl->ast;
+
+ if (digit >= '0' && digit <='9')
+ ast_playtones_start(chan,0,dtmf_tones[digit-'0'], 0);
+ else if (digit >= 'A' && digit <= 'D')
+ ast_playtones_start(chan,0,dtmf_tones[digit-'A'+10], 0);
+ else if (digit == '*')
+ ast_playtones_start(chan,0,dtmf_tones[14], 0);
+ else if (digit == '#')
+ ast_playtones_start(chan,0,dtmf_tones[15], 0);
+ else {
+ /* not handled */
+ ast_debug(1, "Unable to handle DTMF tone '%c' for '%s'\n", digit, chan->name);
+ }
+}
+
+/*** CLI HANDLING ***/
+static char *handle_cli_misdn_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int level;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn set debug";
+ e->usage =
+ "Usage: misdn set debug <level> [only] | [port <port> [only]]\n"
+ " Set the debug level of the mISDN channel.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_debug_port(a);
+ }
+
+ if (a->argc < 4 || a->argc > 7)
+ return CLI_SHOWUSAGE;
+
+ level = atoi(a->argv[3]);
+
+ switch (a->argc) {
+ case 4:
+ case 5:
+ {
+ int only = 0, i;
+ if (a->argc == 5) {
+ if (strncasecmp(a->argv[4], "only", strlen(a->argv[4])))
+ return CLI_SHOWUSAGE;
+ else
+ only = 1;
+ }
+
+ for (i = 0; i <= max_ports; i++) {
+ misdn_debug[i] = level;
+ misdn_debug_only[i] = only;
+ }
+ ast_cli(a->fd, "changing debug level for all ports to %d%s\n",misdn_debug[0], only?" (only)":"");
+ }
+ break;
+ case 6:
+ case 7:
+ {
+ int port;
+ if (strncasecmp(a->argv[4], "port", strlen(a->argv[4])))
+ return CLI_SHOWUSAGE;
+ port = atoi(a->argv[5]);
+ if (port <= 0 || port > max_ports) {
+ switch (max_ports) {
+ case 0:
+ ast_cli(a->fd, "port number not valid! no ports available so you won't get lucky with any number here...\n");
+ break;
+ case 1:
+ ast_cli(a->fd, "port number not valid! only port 1 is availble.\n");
+ break;
+ default:
+ ast_cli(a->fd, "port number not valid! only ports 1 to %d are available.\n", max_ports);
+ }
+ return 0;
+ }
+ if (a->argc == 7) {
+ if (strncasecmp(a->argv[6], "only", strlen(a->argv[6])))
+ return CLI_SHOWUSAGE;
+ else
+ misdn_debug_only[port] = 1;
+ } else
+ misdn_debug_only[port] = 0;
+ misdn_debug[port] = level;
+ ast_cli(a->fd, "changing debug level to %d%s for port %d\n", misdn_debug[port], misdn_debug_only[port]?" (only)":"", port);
+ }
+ }
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_set_crypt_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn set crypt debug";
+ e->usage =
+ "Usage: misdn set crypt debug <level>\n"
+ " Set the crypt debug level of the mISDN channel. Level\n"
+ " must be 1 or 2.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 5)
+ return CLI_SHOWUSAGE;
+
+ /* Is this supposed to not do anything? */
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_port_block(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn port block";
+ e->usage =
+ "Usage: misdn port block <port>\n"
+ " Block the specified port by <port>.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ misdn_lib_port_block(atoi(a->argv[3]));
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_port_unblock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn port unblock";
+ e->usage =
+ "Usage: misdn port unblock <port>\n"
+ " Unblock the port specified by <port>.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ misdn_lib_port_unblock(atoi(a->argv[3]));
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_restart_port(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn restart port";
+ e->usage =
+ "Usage: misdn restart port <port>\n"
+ " Restart the given port.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ misdn_lib_port_restart(atoi(a->argv[3]));
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_restart_pid(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn restart pid";
+ e->usage =
+ "Usage: misdn restart pid <pid>\n"
+ " Restart the given pid\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ misdn_lib_pid_restart(atoi(a->argv[3]));
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_port_up(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn port up";
+ e->usage =
+ "Usage: misdn port up <port>\n"
+ " Try to establish L1 on the given port.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ misdn_lib_get_port_up(atoi(a->argv[3]));
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_port_down(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn port down";
+ e->usage =
+ "Usage: misdn port down <port>\n"
+ " Try to deacivate the L1 on the given port.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ misdn_lib_get_port_down(atoi(a->argv[3]));
+
+ return CLI_SUCCESS;
+}
+
+static inline void show_config_description(int fd, enum misdn_cfg_elements elem)
+{
+ char section[BUFFERSIZE];
+ char name[BUFFERSIZE];
+ char desc[BUFFERSIZE];
+ char def[BUFFERSIZE];
+ char tmp[BUFFERSIZE];
+
+ misdn_cfg_get_name(elem, tmp, sizeof(tmp));
+ term_color(name, tmp, COLOR_BRWHITE, 0, sizeof(tmp));
+ misdn_cfg_get_desc(elem, desc, sizeof(desc), def, sizeof(def));
+
+ if (elem < MISDN_CFG_LAST)
+ term_color(section, "PORTS SECTION", COLOR_YELLOW, 0, sizeof(section));
+ else
+ term_color(section, "GENERAL SECTION", COLOR_YELLOW, 0, sizeof(section));
+
+ if (*def)
+ ast_cli(fd, "[%s] %s (Default: %s)\n\t%s\n", section, name, def, desc);
+ else
+ ast_cli(fd, "[%s] %s\n\t%s\n", section, name, desc);
+
+ return;
+}
+
+static char *handle_cli_misdn_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char buffer[BUFFERSIZE];
+ enum misdn_cfg_elements elem;
+ int linebreak;
+ int onlyport = -1;
+ int ok = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn show config";
+ e->usage =
+ "Usage: misdn show config [<port> | description <config element> | descriptions [general|ports]]\n"
+ " Use 0 for <port> to only print the general config.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_show_config(a);
+ }
+
+ if (a->argc >= 4) {
+ if (!strcmp(a->argv[3], "description")) {
+ if (a->argc == 5) {
+ enum misdn_cfg_elements elem = misdn_cfg_get_elem(a->argv[4]);
+ if (elem == MISDN_CFG_FIRST)
+ ast_cli(a->fd, "Unknown element: %s\n", a->argv[4]);
+ else
+ show_config_description(a->fd, elem);
+ return CLI_SUCCESS;
+ }
+ return CLI_SHOWUSAGE;
+ } else if (!strcmp(a->argv[3], "descriptions")) {
+ if ((a->argc == 4) || ((a->argc == 5) && !strcmp(a->argv[4], "general"))) {
+ for (elem = MISDN_GEN_FIRST + 1; elem < MISDN_GEN_LAST; ++elem) {
+ show_config_description(a->fd, elem);
+ ast_cli(a->fd, "\n");
+ }
+ ok = 1;
+ }
+ if ((a->argc == 4) || ((a->argc == 5) && !strcmp(a->argv[4], "ports"))) {
+ for (elem = MISDN_CFG_FIRST + 1; elem < MISDN_CFG_LAST - 1 /* the ptp hack, remove the -1 when ptp is gone */; ++elem) {
+ show_config_description(a->fd, elem);
+ ast_cli(a->fd, "\n");
+ }
+ ok = 1;
+ }
+ return ok ? CLI_SUCCESS : CLI_SHOWUSAGE;
+ } else if (!sscanf(a->argv[3], "%d", &onlyport) || onlyport < 0) {
+ ast_cli(a->fd, "Unknown option: %s\n", a->argv[3]);
+ return CLI_SHOWUSAGE;
+ }
+ } else if (a->argc == 3 || onlyport == 0) {
+ ast_cli(a->fd, "mISDN General-Config:\n");
+ for (elem = MISDN_GEN_FIRST + 1, linebreak = 1; elem < MISDN_GEN_LAST; elem++, linebreak++) {
+ misdn_cfg_get_config_string(0, elem, buffer, sizeof(buffer));
+ ast_cli(a->fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : "");
+ }
+ ast_cli(a->fd, "\n");
+ }
+
+ if (onlyport < 0) {
+ int port = misdn_cfg_get_next_port(0);
+ for (; port > 0; port = misdn_cfg_get_next_port(port)) {
+ ast_cli(a->fd, "\n[PORT %d]\n", port);
+ for (elem = MISDN_CFG_FIRST + 1, linebreak = 1; elem < MISDN_CFG_LAST; elem++, linebreak++) {
+ misdn_cfg_get_config_string(port, elem, buffer, sizeof(buffer));
+ ast_cli(a->fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : "");
+ }
+ ast_cli(a->fd, "\n");
+ }
+ }
+
+ if (onlyport > 0) {
+ if (misdn_cfg_is_port_valid(onlyport)) {
+ ast_cli(a->fd, "[PORT %d]\n", onlyport);
+ for (elem = MISDN_CFG_FIRST + 1, linebreak = 1; elem < MISDN_CFG_LAST; elem++, linebreak++) {
+ misdn_cfg_get_config_string(onlyport, elem, buffer, sizeof(buffer));
+ ast_cli(a->fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : "");
+ }
+ ast_cli(a->fd, "\n");
+ } else {
+ ast_cli(a->fd, "Port %d is not active!\n", onlyport);
+ }
+ }
+
+ return CLI_SUCCESS;
+}
+
+struct state_struct {
+ enum misdn_chan_state state;
+ char txt[255];
+};
+
+static struct state_struct state_array[] = {
+ {MISDN_NOTHING,"NOTHING"}, /* at beginning */
+ {MISDN_WAITING4DIGS,"WAITING4DIGS"}, /* when waiting for infos */
+ {MISDN_EXTCANTMATCH,"EXTCANTMATCH"}, /* when asterisk couldnt match our ext */
+ {MISDN_INCOMING_SETUP,"INCOMING SETUP"}, /* when pbx_start */
+ {MISDN_DIALING,"DIALING"}, /* when pbx_start */
+ {MISDN_PROGRESS,"PROGRESS"}, /* when pbx_start */
+ {MISDN_PROCEEDING,"PROCEEDING"}, /* when pbx_start */
+ {MISDN_CALLING,"CALLING"}, /* when misdn_call is called */
+ {MISDN_CALLING_ACKNOWLEDGE,"CALLING_ACKNOWLEDGE"}, /* when misdn_call is called */
+ {MISDN_ALERTING,"ALERTING"}, /* when Alerting */
+ {MISDN_BUSY,"BUSY"}, /* when BUSY */
+ {MISDN_CONNECTED,"CONNECTED"}, /* when connected */
+ {MISDN_PRECONNECTED,"PRECONNECTED"}, /* when connected */
+ {MISDN_DISCONNECTED,"DISCONNECTED"}, /* when connected */
+ {MISDN_RELEASED,"RELEASED"}, /* when connected */
+ {MISDN_BRIDGED,"BRIDGED"}, /* when bridged */
+ {MISDN_CLEANING,"CLEANING"}, /* when hangup from * but we were connected before */
+ {MISDN_HUNGUP_FROM_MISDN,"HUNGUP_FROM_MISDN"}, /* when DISCONNECT/RELEASE/REL_COMP cam from misdn */
+ {MISDN_HOLDED,"HOLDED"}, /* when DISCONNECT/RELEASE/REL_COMP cam from misdn */
+ {MISDN_HOLD_DISCONNECT,"HOLD_DISCONNECT"}, /* when DISCONNECT/RELEASE/REL_COMP cam from misdn */
+ {MISDN_HUNGUP_FROM_AST,"HUNGUP_FROM_AST"} /* when DISCONNECT/RELEASE/REL_COMP came out of */
+ /* misdn_hangup */
+};
+
+static const char *misdn_get_ch_state(struct chan_list *p)
+{
+ int i;
+ static char state[8];
+
+ if( !p) return NULL;
+
+ for (i = 0; i < sizeof(state_array) / sizeof(struct state_struct); i++) {
+ if (state_array[i].state == p->state)
+ return state_array[i].txt;
+ }
+
+ snprintf(state, sizeof(state), "%d", p->state) ;
+
+ return state;
+}
+
+
+
+static void reload_config(void)
+{
+ int i, cfg_debug;
+
+ if (!g_config_initialized) {
+ ast_log(LOG_WARNING, "chan_misdn is not initialized properly, still reloading ?\n");
+ return ;
+ }
+
+ free_robin_list();
+ misdn_cfg_reload();
+ misdn_cfg_update_ptp();
+ misdn_cfg_get(0, MISDN_GEN_TRACEFILE, global_tracefile, sizeof(global_tracefile));
+ misdn_cfg_get(0, MISDN_GEN_DEBUG, &cfg_debug, sizeof(cfg_debug));
+
+ for (i = 0; i <= max_ports; i++) {
+ misdn_debug[i] = cfg_debug;
+ misdn_debug_only[i] = 0;
+ }
+}
+
+static char *handle_cli_misdn_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn reload";
+ e->usage =
+ "Usage: misdn reload\n"
+ " Reload internal mISDN config, read from the config\n"
+ " file.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 2)
+ return CLI_SHOWUSAGE;
+
+ ast_cli(a->fd, "Reloading mISDN configuration\n");
+ reload_config();
+ return CLI_SUCCESS;
+}
+
+static void print_bc_info (int fd, struct chan_list *help, struct misdn_bchannel *bc)
+{
+ struct ast_channel *ast = help->ast;
+ ast_cli(fd,
+ "* Pid:%d Prt:%d Ch:%d Mode:%s Org:%s dad:%s oad:%s rad:%s ctx:%s state:%s\n",
+
+ bc->pid, bc->port, bc->channel,
+ bc->nt ? "NT" : "TE",
+ help->originator == ORG_AST ? "*" : "I",
+ ast ? ast->exten : NULL,
+ ast ? ast->cid.cid_num : NULL,
+ bc->rad,
+ ast ? ast->context : NULL,
+ misdn_get_ch_state(help)
+ );
+ if (misdn_debug[bc->port] > 0)
+ ast_cli(fd,
+ " --> astname: %s\n"
+ " --> ch_l3id: %x\n"
+ " --> ch_addr: %x\n"
+ " --> bc_addr: %x\n"
+ " --> bc_l3id: %x\n"
+ " --> display: %s\n"
+ " --> activated: %d\n"
+ " --> state: %s\n"
+ " --> capability: %s\n"
+#ifdef MISDN_1_2
+ " --> pipeline: %s\n"
+#else
+ " --> echo_cancel: %d\n"
+#endif
+ " --> notone : rx %d tx:%d\n"
+ " --> bc_hold: %d\n",
+ help->ast->name,
+ help->l3id,
+ help->addr,
+ bc->addr,
+ bc ? bc->l3_id : -1,
+ bc->display,
+
+ bc->active,
+ bc_state2str(bc->bc_state),
+ bearer2str(bc->capability),
+#ifdef MISDN_1_2
+ bc->pipeline,
+#else
+ bc->ec_enable,
+#endif
+
+ help->norxtone, help->notxtone,
+ bc->holded
+ );
+
+}
+
+static char *handle_cli_misdn_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_list *help = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn show channels";
+ e->usage =
+ "Usage: misdn show channels\n"
+ " Show the internal mISDN channel list\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ help = cl_te;
+
+ ast_cli(a->fd, "Channel List: %p\n", cl_te);
+
+ for (; help; help = help->next) {
+ struct misdn_bchannel *bc = help->bc;
+ struct ast_channel *ast = help->ast;
+ if (misdn_debug[0] > 2)
+ ast_cli(a->fd, "Bc:%p Ast:%p\n", bc, ast);
+ if (bc) {
+ print_bc_info(a->fd, help, bc);
+ } else {
+ if (help->state == MISDN_HOLDED) {
+ ast_cli(a->fd, "ITS A HOLDED BC:\n");
+ ast_cli(a->fd, " --> l3_id: %x\n"
+ " --> dad:%s oad:%s\n"
+ " --> hold_port: %d\n"
+ " --> hold_channel: %d\n",
+ help->l3id,
+ ast->exten,
+ ast->cid.cid_num,
+ help->hold_info.port,
+ help->hold_info.channel
+ );
+ } else {
+ ast_cli(a->fd, "* Channel in unknown STATE !!! Exten:%s, Callerid:%s\n", ast->exten, ast->cid.cid_num);
+ }
+ }
+ }
+
+ misdn_dump_chanlist();
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_list *help = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn show channel";
+ e->usage =
+ "Usage: misdn show channel <channel>\n"
+ " Show an internal mISDN channel\n.";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_ch(a);
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ help = cl_te;
+
+ for (; help; help = help->next) {
+ struct misdn_bchannel *bc = help->bc;
+ struct ast_channel *ast = help->ast;
+
+ if (bc && ast) {
+ if (!strcasecmp(ast->name, a->argv[3])) {
+ print_bc_info(a->fd, help, bc);
+ break;
+ }
+ }
+ }
+
+ return CLI_SUCCESS;
+}
+
+ast_mutex_t lock;
+int MAXTICS = 8;
+
+static char *handle_cli_misdn_set_tics(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn set tics";
+ e->usage =
+ "Usage: misdn set tics <value>\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ MAXTICS = atoi(a->argv[3]);
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_show_stacks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int port;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn show stacks";
+ e->usage =
+ "Usage: misdn show stacks\n"
+ " Show internal mISDN stack_list.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ ast_cli(a->fd, "BEGIN STACK_LIST:\n");
+ for (port = misdn_cfg_get_next_port(0); port > 0;
+ port = misdn_cfg_get_next_port(port)) {
+ char buf[128];
+ get_show_stack_details(port, buf);
+ ast_cli(a->fd," %s Debug:%d%s\n", buf, misdn_debug[port], misdn_debug_only[port] ? "(only)" : "");
+ }
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_show_ports_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int port;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn show ports stats";
+ e->usage =
+ "Usage: misdn show ports stats\n"
+ " Show mISDNs channel's call statistics per port.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ ast_cli(a->fd, "Port\tin_calls\tout_calls\n");
+ for (port = misdn_cfg_get_next_port(0); port > 0;
+ port = misdn_cfg_get_next_port(port)) {
+ ast_cli(a->fd, "%d\t%d\t\t%d\n", port, misdn_in_calls[port], misdn_out_calls[port]);
+ }
+ ast_cli(a->fd, "\n");
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_show_port(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int port;
+ char buf[128];
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn show port";
+ e->usage =
+ "Usage: misdn show port <port>\n"
+ " Show detailed information for given port.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ port = atoi(a->argv[3]);
+
+ ast_cli(a->fd, "BEGIN STACK_LIST:\n");
+ get_show_stack_details(port, buf);
+ ast_cli(a->fd, " %s Debug:%d%s\n", buf, misdn_debug[port], misdn_debug_only[port] ? "(only)" : "");
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_send_facility(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *channame;
+ char *nr;
+ struct chan_list *tmp;
+ int port;
+ char *served_nr;
+ struct misdn_bchannel dummy, *bc=&dummy;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn send facility";
+ e->usage = "Usage: misdn send facility <type> <channel|port> \"<args>\" \n"
+ "\t type is one of:\n"
+ "\t - calldeflect\n"
+ "\t - CFActivate\n"
+ "\t - CFDeactivate\n";
+
+ return NULL;
+ case CLI_GENERATE:
+ return complete_ch(a);
+ }
+
+ if (a->argc < 5)
+ return CLI_SHOWUSAGE;
+
+ if (strstr(a->argv[3], "calldeflect")) {
+ if (a->argc < 6) {
+ ast_verbose("calldeflect requires 1 arg: ToNumber\n\n");
+ return 0;
+ }
+ channame = a->argv[4];
+ nr = a->argv[5];
+
+ ast_verbose("Sending Calldeflection (%s) to %s\n", nr, channame);
+ tmp = get_chan_by_ast_name(channame);
+ if (!tmp) {
+ ast_verbose("Sending CD with nr %s to %s failed: Channel does not exist.\n",nr, channame);
+ return 0;
+ }
+
+ if (strlen(nr) >= 15) {
+ ast_verbose("Sending CD with nr %s to %s failed: Number too long (up to 15 digits are allowed).\n",nr, channame);
+ return 0;
+ }
+ tmp->bc->fac_out.Function = Fac_CD;
+ ast_copy_string((char *)tmp->bc->fac_out.u.CDeflection.DeflectedToNumber, nr, sizeof(tmp->bc->fac_out.u.CDeflection.DeflectedToNumber));
+ misdn_lib_send_event(tmp->bc, EVENT_FACILITY);
+ } else if (strstr(a->argv[3],"CFActivate")) {
+ if (a->argc < 7) {
+ ast_verbose("CFActivate requires 2 args: 1.FromNumber, 2.ToNumber\n\n");
+ return 0;
+ }
+ port = atoi(a->argv[4]);
+ served_nr = a->argv[5];
+ nr = a->argv[6];
+
+ misdn_make_dummy(bc, port, 0, misdn_lib_port_is_nt(port), 0);
+
+ ast_verbose("Sending CFActivate Port:(%d) FromNr. (%s) to Nr. (%s)\n", port, served_nr, nr);
+
+ bc->fac_out.Function = Fac_CFActivate;
+ bc->fac_out.u.CFActivate.BasicService = 0; //All Services
+ bc->fac_out.u.CFActivate.Procedure = 0; //Unconditional
+ ast_copy_string((char *)bc->fac_out.u.CFActivate.ServedUserNumber, served_nr, sizeof(bc->fac_out.u.CFActivate.ServedUserNumber));
+ ast_copy_string((char *)bc->fac_out.u.CFActivate.ForwardedToNumber, nr, sizeof(bc->fac_out.u.CFActivate.ForwardedToNumber));
+
+ misdn_lib_send_event(bc, EVENT_FACILITY);
+ } else if (strstr(a->argv[3],"CFDeactivate")) {
+
+ if (a->argc < 6) {
+ ast_verbose("CFActivate requires 1 arg: FromNumber\n\n");
+ return 0;
+ }
+ port = atoi(a->argv[4]);
+ served_nr = a->argv[5];
+
+ misdn_make_dummy(bc, port, 0, misdn_lib_port_is_nt(port), 0);
+ ast_verbose("Sending CFDeactivate Port:(%d) FromNr. (%s)\n", port, served_nr);
+
+ bc->fac_out.Function = Fac_CFDeactivate;
+ bc->fac_out.u.CFDeactivate.BasicService = 0; //All Services
+ bc->fac_out.u.CFDeactivate.Procedure = 0; //Unconditional
+
+ ast_copy_string((char *)bc->fac_out.u.CFActivate.ServedUserNumber, served_nr, sizeof(bc->fac_out.u.CFActivate.ServedUserNumber));
+ misdn_lib_send_event(bc, EVENT_FACILITY);
+ }
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_send_restart(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn send restart";
+ e->usage =
+ "Usage: misdn send restart [port [channel]]\n"
+ " Send a restart for every bchannel on the given port.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc < 4 || a->argc > 5)
+ return CLI_SHOWUSAGE;
+
+ if (a->argc == 5)
+ misdn_lib_send_restart(atoi(a->argv[3]), atoi(a->argv[4]));
+ else
+ misdn_lib_send_restart(atoi(a->argv[3]), -1);
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_send_digit(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *channame;
+ char *msg;
+ struct chan_list *tmp;
+ int i, msglen;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn send digit";
+ e->usage =
+ "Usage: misdn send digit <channel> \"<msg>\" \n"
+ " Send <digit> to <channel> as DTMF Tone\n"
+ " when channel is a mISDN channel\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_ch(a);
+ }
+
+ if (a->argc != 5)
+ return CLI_SHOWUSAGE;
+
+ channame = a->argv[3];
+ msg = a->argv[4];
+ msglen = strlen(msg);
+
+ ast_cli(a->fd, "Sending %s to %s\n", msg, channame);
+
+ tmp = get_chan_by_ast_name(channame);
+ if (!tmp) {
+ ast_cli(a->fd, "Sending %s to %s failed Channel does not exist\n", msg, channame);
+ return CLI_SUCCESS;
+ }
+#if 1
+ for (i = 0; i < msglen; i++) {
+ ast_cli(a->fd, "Sending: %c\n", msg[i]);
+ send_digit_to_chan(tmp, msg[i]);
+ /* res = ast_safe_sleep(tmp->ast, 250); */
+ usleep(250000);
+ /* res = ast_waitfor(tmp->ast,100); */
+ }
+#else
+ ast_dtmf_stream(tmp->ast, NULL, msg, 250);
+#endif
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_toggle_echocancel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *channame;
+ struct chan_list *tmp;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn toggle echocancel";
+ e->usage =
+ "Usage: misdn toggle echocancel <channel>\n"
+ " Toggle EchoCancel on mISDN Channel.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_ch(a);
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ channame = a->argv[3];
+
+ ast_cli(a->fd, "Toggling EchoCancel on %s\n", channame);
+
+ tmp = get_chan_by_ast_name(channame);
+ if (!tmp) {
+ ast_cli(a->fd, "Toggling EchoCancel %s failed Channel does not exist\n", channame);
+ return CLI_SUCCESS;
+ }
+
+ tmp->toggle_ec = tmp->toggle_ec?0:1;
+
+ if (tmp->toggle_ec) {
+#ifdef MISDN_1_2
+ update_pipeline_config(tmp->bc);
+#else
+ update_ec_config(tmp->bc);
+#endif
+ manager_ec_enable(tmp->bc);
+ } else {
+ manager_ec_disable(tmp->bc);
+ }
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_misdn_send_display(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *channame;
+ char *msg;
+ struct chan_list *tmp;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "misdn send display";
+ e->usage =
+ "Usage: misdn send display <channel> \"<msg>\" \n"
+ " Send <msg> to <channel> as Display Message\n"
+ " when channel is a mISDN channel\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_ch(a);
+ }
+
+ if (a->argc != 5)
+ return CLI_SHOWUSAGE;
+
+ channame = a->argv[3];
+ msg = a->argv[4];
+
+ ast_cli(a->fd, "Sending %s to %s\n", msg, channame);
+ tmp = get_chan_by_ast_name(channame);
+
+ if (tmp && tmp->bc) {
+ ast_copy_string(tmp->bc->display, msg, sizeof(tmp->bc->display));
+ misdn_lib_send_event(tmp->bc, EVENT_INFORMATION);
+ } else {
+ ast_cli(a->fd, "No such channel %s\n", channame);
+ return CLI_SUCCESS;
+ }
+
+ return CLI_SUCCESS;
+}
+
+static char *complete_ch(struct ast_cli_args *a)
+{
+ return ast_complete_channels(a->line, a->word, a->pos, a->n, 3);
+}
+
+static char *complete_debug_port (struct ast_cli_args *a)
+{
+ if (a->n)
+ return NULL;
+
+ switch (a->pos) {
+ case 4:
+ if (a->word[0] == 'p')
+ return ast_strdup("port");
+ else if (a->word[0] == 'o')
+ return ast_strdup("only");
+ break;
+ case 6:
+ if (a->word[0] == 'o')
+ return ast_strdup("only");
+ break;
+ }
+ return NULL;
+}
+
+static char *complete_show_config(struct ast_cli_args *a)
+{
+ char buffer[BUFFERSIZE];
+ enum misdn_cfg_elements elem;
+ int wordlen = strlen(a->word);
+ int which = 0;
+ int port = 0;
+
+ switch (a->pos) {
+ case 3:
+ if ((!strncmp(a->word, "description", wordlen)) && (++which > a->n))
+ return ast_strdup("description");
+ if ((!strncmp(a->word, "descriptions", wordlen)) && (++which > a->n))
+ return ast_strdup("descriptions");
+ if ((!strncmp(a->word, "0", wordlen)) && (++which > a->n))
+ return ast_strdup("0");
+ while ((port = misdn_cfg_get_next_port(port)) != -1) {
+ snprintf(buffer, sizeof(buffer), "%d", port);
+ if ((!strncmp(a->word, buffer, wordlen)) && (++which > a->n)) {
+ return ast_strdup(buffer);
+ }
+ }
+ break;
+ case 4:
+ if (strstr(a->line, "description ")) {
+ for (elem = MISDN_CFG_FIRST + 1; elem < MISDN_GEN_LAST; ++elem) {
+ if ((elem == MISDN_CFG_LAST) || (elem == MISDN_GEN_FIRST))
+ continue;
+ misdn_cfg_get_name(elem, buffer, sizeof(buffer));
+ if (!wordlen || !strncmp(a->word, buffer, wordlen)) {
+ if (++which > a->n)
+ return ast_strdup(buffer);
+ }
+ }
+ } else if (strstr(a->line, "descriptions ")) {
+ if ((!wordlen || !strncmp(a->word, "general", wordlen)) && (++which > a->n))
+ return ast_strdup("general");
+ if ((!wordlen || !strncmp(a->word, "ports", wordlen)) && (++which > a->n))
+ return ast_strdup("ports");
+ }
+ break;
+ }
+ return NULL;
+}
+
+static struct ast_cli_entry chan_misdn_clis[] = {
+ AST_CLI_DEFINE(handle_cli_misdn_port_block, "Block the given port"),
+ AST_CLI_DEFINE(handle_cli_misdn_port_down, "Try to deacivate the L1 on the given port"),
+ AST_CLI_DEFINE(handle_cli_misdn_port_unblock, "Unblock the given port"),
+ AST_CLI_DEFINE(handle_cli_misdn_port_up, "Try to establish L1 on the given port"),
+ AST_CLI_DEFINE(handle_cli_misdn_reload, "Reload internal mISDN config, read from the config file"),
+ AST_CLI_DEFINE(handle_cli_misdn_restart_pid, "Restart the given pid"),
+ AST_CLI_DEFINE(handle_cli_misdn_restart_port, "Restart the given port"),
+ AST_CLI_DEFINE(handle_cli_misdn_show_channel, "Show an internal mISDN channel"),
+ AST_CLI_DEFINE(handle_cli_misdn_show_channels, "Show the internal mISDN channel list"),
+ AST_CLI_DEFINE(handle_cli_misdn_show_config, "Show internal mISDN config, read from the config file"),
+ AST_CLI_DEFINE(handle_cli_misdn_show_port, "Show detailed information for given port"),
+ AST_CLI_DEFINE(handle_cli_misdn_show_ports_stats, "Show mISDNs channel's call statistics per port"),
+ AST_CLI_DEFINE(handle_cli_misdn_show_stacks, "Show internal mISDN stack_list"),
+ AST_CLI_DEFINE(handle_cli_misdn_send_facility, "Sends a Facility Message to the mISDN Channel"),
+ AST_CLI_DEFINE(handle_cli_misdn_send_digit, "Send DTMF digit to mISDN Channel"),
+ AST_CLI_DEFINE(handle_cli_misdn_send_display, "Send Text to mISDN Channel"),
+ AST_CLI_DEFINE(handle_cli_misdn_send_restart, "Send a restart for every bchannel on the given port"),
+ AST_CLI_DEFINE(handle_cli_misdn_set_crypt_debug, "Set CryptDebuglevel of chan_misdn, at the moment, level={1,2}"),
+ AST_CLI_DEFINE(handle_cli_misdn_set_debug, "Set Debuglevel of chan_misdn"),
+ AST_CLI_DEFINE(handle_cli_misdn_set_tics, "???"),
+ AST_CLI_DEFINE(handle_cli_misdn_toggle_echocancel, "Toggle EchoCancel on mISDN Channel"),
+};
+
+static int update_config(struct chan_list *ch, int orig)
+{
+ struct ast_channel *ast;
+ struct misdn_bchannel *bc;
+ int port, hdlc = 0;
+ int pres, screen;
+
+ if (!ch) {
+ ast_log(LOG_WARNING, "Cannot configure without chanlist\n");
+ return -1;
+ }
+
+ ast = ch->ast;
+ bc = ch->bc;
+ if (! ast || ! bc) {
+ ast_log(LOG_WARNING, "Cannot configure without ast || bc\n");
+ return -1;
+ }
+
+ port = bc->port;
+
+ chan_misdn_log(7, port, "update_config: Getting Config\n");
+
+ misdn_cfg_get(port, MISDN_CFG_HDLC, &hdlc, sizeof(int));
+
+ if (hdlc) {
+ switch (bc->capability) {
+ case INFO_CAPABILITY_DIGITAL_UNRESTRICTED:
+ case INFO_CAPABILITY_DIGITAL_RESTRICTED:
+ chan_misdn_log(1, bc->port, " --> CONF HDLC\n");
+ bc->hdlc = 1;
+ break;
+ }
+ }
+
+
+ misdn_cfg_get(port, MISDN_CFG_PRES, &pres, sizeof(pres));
+ misdn_cfg_get(port, MISDN_CFG_SCREEN, &screen, sizeof(screen));
+ chan_misdn_log(2, port, " --> pres: %d screen: %d\n", pres, screen);
+
+ if ( (pres + screen) < 0 ) {
+
+ chan_misdn_log(2, port, " --> pres: %x\n", ast->cid.cid_pres);
+
+ switch (ast->cid.cid_pres & 0x60) {
+
+ case AST_PRES_RESTRICTED:
+ bc->pres = 1;
+ chan_misdn_log(2, port, " --> PRES: Restricted (0x1)\n");
+ break;
+ case AST_PRES_UNAVAILABLE:
+ bc->pres = 2;
+ chan_misdn_log(2, port, " --> PRES: Unavailable (0x2)\n");
+ break;
+ default:
+ bc->pres = 0;
+ chan_misdn_log(2, port, " --> PRES: Allowed (0x0)\n");
+ }
+
+ switch (ast->cid.cid_pres & 0x3) {
+
+ case AST_PRES_USER_NUMBER_UNSCREENED:
+ bc->screen = 0;
+ chan_misdn_log(2, port, " --> SCREEN: Unscreened (0x0)\n");
+ break;
+ case AST_PRES_USER_NUMBER_PASSED_SCREEN:
+ bc->screen = 1;
+ chan_misdn_log(2, port, " --> SCREEN: Passed Screen (0x1)\n");
+ break;
+ case AST_PRES_USER_NUMBER_FAILED_SCREEN:
+ bc->screen = 2;
+ chan_misdn_log(2, port, " --> SCREEN: Failed Screen (0x2)\n");
+ break;
+ case AST_PRES_NETWORK_NUMBER:
+ bc->screen = 3;
+ chan_misdn_log(2, port, " --> SCREEN: Network Nr. (0x3)\n");
+ break;
+ default:
+ bc->screen = 0;
+ chan_misdn_log(2, port, " --> SCREEN: Unscreened (0x0)\n");
+ }
+ } else {
+ bc->screen = screen;
+ bc->pres = pres;
+ }
+
+ return 0;
+}
+
+
+static void config_jitterbuffer(struct chan_list *ch)
+{
+ struct misdn_bchannel *bc = ch->bc;
+ int len = ch->jb_len, threshold = ch->jb_upper_threshold;
+
+ chan_misdn_log(5, bc->port, "config_jb: Called\n");
+
+ if (! len) {
+ chan_misdn_log(1, bc->port, "config_jb: Deactivating Jitterbuffer\n");
+ bc->nojitter=1;
+ } else {
+ if (len <= 100 || len > 8000) {
+ chan_misdn_log(0, bc->port, "config_jb: Jitterbuffer out of Bounds, setting to 1000\n");
+ len = 1000;
+ }
+
+ if ( threshold > len ) {
+ chan_misdn_log(0, bc->port, "config_jb: Jitterbuffer Threshold > Jitterbuffer setting to Jitterbuffer -1\n");
+ }
+
+ if ( ch->jb) {
+ cb_log(0, bc->port, "config_jb: We've got a Jitterbuffer Already on this port.\n");
+ misdn_jb_destroy(ch->jb);
+ ch->jb = NULL;
+ }
+
+ ch->jb=misdn_jb_init(len, threshold);
+
+ if (!ch->jb )
+ bc->nojitter = 1;
+ }
+}
+
+
+void debug_numplan(int port, int numplan, char *type)
+{
+ switch (numplan) {
+ case NUMPLAN_INTERNATIONAL:
+ chan_misdn_log(2, port, " --> %s: International\n", type);
+ break;
+ case NUMPLAN_NATIONAL:
+ chan_misdn_log(2, port, " --> %s: National\n", type);
+ break;
+ case NUMPLAN_SUBSCRIBER:
+ chan_misdn_log(2, port, " --> %s: Subscriber\n", type);
+ break;
+ case NUMPLAN_UNKNOWN:
+ chan_misdn_log(2, port, " --> %s: Unknown\n", type);
+ break;
+ /* Maybe we should cut off the prefix if present ? */
+ default:
+ chan_misdn_log(0, port, " --> !!!! Wrong dialplan setting, please see the misdn.conf sample file\n ");
+ break;
+ }
+}
+
+
+#ifdef MISDN_1_2
+static int update_pipeline_config(struct misdn_bchannel *bc)
+{
+ int ec;
+
+ misdn_cfg_get(bc->port, MISDN_CFG_PIPELINE, bc->pipeline, sizeof(bc->pipeline));
+
+ if (*bc->pipeline)
+ return 0;
+
+ misdn_cfg_get(bc->port, MISDN_CFG_ECHOCANCEL, &ec, sizeof(ec));
+ if (ec == 1)
+ ast_copy_string(bc->pipeline, "mg2ec", sizeof(bc->pipeline));
+ else if (ec > 1)
+ snprintf(bc->pipeline, sizeof(bc->pipeline), "mg2ec(deftaps=%d)", ec);
+
+ return 0;
+}
+#else
+static int update_ec_config(struct misdn_bchannel *bc)
+{
+ int ec;
+ int port = bc->port;
+
+ misdn_cfg_get(port, MISDN_CFG_ECHOCANCEL, &ec, sizeof(ec));
+
+ if (ec == 1) {
+ bc->ec_enable = 1;
+ } else if (ec > 1) {
+ bc->ec_enable = 1;
+ bc->ec_deftaps = ec;
+ }
+
+ return 0;
+}
+#endif
+
+
+static int read_config(struct chan_list *ch, int orig)
+{
+ struct ast_channel *ast;
+ struct misdn_bchannel *bc;
+ int port, hdlc = 0;
+ char lang[BUFFERSIZE + 1], localmusicclass[BUFFERSIZE + 1], faxdetect[BUFFERSIZE + 1];
+ char buf[256], buf2[256];
+ ast_group_t pg, cg;
+
+ if (!ch) {
+ ast_log(LOG_WARNING, "Cannot configure without chanlist\n");
+ return -1;
+ }
+
+ ast = ch->ast;
+ bc = ch->bc;
+ if (! ast || ! bc) {
+ ast_log(LOG_WARNING, "Cannot configure without ast || bc\n");
+ return -1;
+ }
+
+ port = bc->port;
+ chan_misdn_log(1, port, "read_config: Getting Config\n");
+
+ misdn_cfg_get(port, MISDN_CFG_LANGUAGE, lang, sizeof(lang));
+ ast_string_field_set(ast, language, lang);
+
+ misdn_cfg_get(port, MISDN_CFG_MUSICCLASS, localmusicclass, sizeof(localmusicclass));
+ ast_string_field_set(ast, musicclass, localmusicclass);
+
+ misdn_cfg_get(port, MISDN_CFG_TXGAIN, &bc->txgain, sizeof(bc->txgain));
+ misdn_cfg_get(port, MISDN_CFG_RXGAIN, &bc->rxgain, sizeof(bc->rxgain));
+
+ misdn_cfg_get(port, MISDN_CFG_INCOMING_EARLY_AUDIO, &ch->incoming_early_audio, sizeof(ch->incoming_early_audio));
+
+ misdn_cfg_get(port, MISDN_CFG_SENDDTMF, &bc->send_dtmf, sizeof(bc->send_dtmf));
+
+ misdn_cfg_get( port, MISDN_CFG_ASTDTMF, &ch->ast_dsp, sizeof(int));
+
+ if (ch->ast_dsp) {
+ ch->ignore_dtmf=1;
+ }
+
+ misdn_cfg_get(port, MISDN_CFG_NEED_MORE_INFOS, &bc->need_more_infos, sizeof(bc->need_more_infos));
+ misdn_cfg_get(port, MISDN_CFG_NTTIMEOUT, &ch->nttimeout, sizeof(ch->nttimeout));
+
+ misdn_cfg_get(port, MISDN_CFG_NOAUTORESPOND_ON_SETUP, &ch->noautorespond_on_setup, sizeof(ch->noautorespond_on_setup));
+
+ misdn_cfg_get(port, MISDN_CFG_FAR_ALERTING, &ch->far_alerting, sizeof(ch->far_alerting));
+
+ misdn_cfg_get(port, MISDN_CFG_ALLOWED_BEARERS, &ch->allowed_bearers, sizeof(ch->allowed_bearers));
+
+ misdn_cfg_get(port, MISDN_CFG_FAXDETECT, faxdetect, sizeof(faxdetect));
+
+ misdn_cfg_get(port, MISDN_CFG_HDLC, &hdlc, sizeof(hdlc));
+
+ if (hdlc) {
+ switch (bc->capability) {
+ case INFO_CAPABILITY_DIGITAL_UNRESTRICTED:
+ case INFO_CAPABILITY_DIGITAL_RESTRICTED:
+ chan_misdn_log(1, bc->port, " --> CONF HDLC\n");
+ bc->hdlc = 1;
+ break;
+ }
+
+ }
+ /*Initialize new Jitterbuffer*/
+ misdn_cfg_get(port, MISDN_CFG_JITTERBUFFER, &ch->jb_len, sizeof(ch->jb_len));
+ misdn_cfg_get(port, MISDN_CFG_JITTERBUFFER_UPPER_THRESHOLD, &ch->jb_upper_threshold, sizeof(ch->jb_upper_threshold));
+
+ config_jitterbuffer(ch);
+
+ misdn_cfg_get(bc->port, MISDN_CFG_CONTEXT, ch->context, sizeof(ch->context));
+
+ ast_copy_string(ast->context, ch->context, sizeof(ast->context));
+
+#ifdef MISDN_1_2
+ update_pipeline_config(bc);
+#else
+ update_ec_config(bc);
+#endif
+
+ misdn_cfg_get(bc->port, MISDN_CFG_EARLY_BCONNECT, &bc->early_bconnect, sizeof(bc->early_bconnect));
+
+ misdn_cfg_get(port, MISDN_CFG_PICKUPGROUP, &pg, sizeof(pg));
+ misdn_cfg_get(port, MISDN_CFG_CALLGROUP, &cg, sizeof(cg));
+
+ chan_misdn_log(5, port, " --> * CallGrp:%s PickupGrp:%s\n", ast_print_group(buf, sizeof(buf), cg), ast_print_group(buf2, sizeof(buf2), pg));
+ ast->pickupgroup = pg;
+ ast->callgroup = cg;
+
+ if (orig == ORG_AST) {
+ char callerid[BUFFERSIZE + 1];
+
+ misdn_cfg_get(port, MISDN_CFG_TE_CHOOSE_CHANNEL, &(bc->te_choose_channel), sizeof(bc->te_choose_channel));
+
+ if (strstr(faxdetect, "outgoing") || strstr(faxdetect, "both")) {
+ if (strstr(faxdetect, "nojump"))
+ ch->faxdetect = 2;
+ else
+ ch->faxdetect = 1;
+ }
+
+ misdn_cfg_get(port, MISDN_CFG_CALLERID, callerid, sizeof(callerid));
+ if ( ! ast_strlen_zero(callerid) ) {
+ chan_misdn_log(1, port, " --> * Setting Cid to %s\n", callerid);
+ ast_copy_string(bc->oad, callerid, sizeof(bc->oad));
+ }
+
+ misdn_cfg_get(port, MISDN_CFG_DIALPLAN, &bc->dnumplan, sizeof(bc->dnumplan));
+ misdn_cfg_get(port, MISDN_CFG_LOCALDIALPLAN, &bc->onumplan, sizeof(bc->onumplan));
+ misdn_cfg_get(port, MISDN_CFG_CPNDIALPLAN, &bc->cpnnumplan, sizeof(bc->cpnnumplan));
+ debug_numplan(port, bc->dnumplan, "TON");
+ debug_numplan(port, bc->onumplan, "LTON");
+ debug_numplan(port, bc->cpnnumplan, "CTON");
+
+ ch->overlap_dial = 0;
+ } else { /** ORIGINATOR MISDN **/
+ char prefix[BUFFERSIZE + 1] = "";
+
+ if (strstr(faxdetect, "incoming") || strstr(faxdetect, "both")) {
+ if (strstr(faxdetect, "nojump"))
+ ch->faxdetect = 2;
+ else
+ ch->faxdetect = 1;
+ }
+
+ misdn_cfg_get(port, MISDN_CFG_CPNDIALPLAN, &bc->cpnnumplan, sizeof(bc->cpnnumplan));
+ debug_numplan(port, bc->cpnnumplan, "CTON");
+
+ switch (bc->onumplan) {
+ case NUMPLAN_INTERNATIONAL:
+ misdn_cfg_get(bc->port, MISDN_CFG_INTERNATPREFIX, prefix, sizeof(prefix));
+ break;
+
+ case NUMPLAN_NATIONAL:
+ misdn_cfg_get(bc->port, MISDN_CFG_NATPREFIX, prefix, sizeof(prefix));
+ break;
+ default:
+ break;
+ }
+
+ ast_copy_string(buf, bc->oad, sizeof(buf));
+ snprintf(bc->oad, sizeof(bc->oad), "%s%s", prefix, buf);
+
+ if (!ast_strlen_zero(bc->dad)) {
+ ast_copy_string(bc->orig_dad, bc->dad, sizeof(bc->orig_dad));
+ }
+
+ if ( ast_strlen_zero(bc->dad) && !ast_strlen_zero(bc->keypad)) {
+ ast_copy_string(bc->dad, bc->keypad, sizeof(bc->dad));
+ }
+
+ prefix[0] = 0;
+
+ switch (bc->dnumplan) {
+ case NUMPLAN_INTERNATIONAL:
+ misdn_cfg_get(bc->port, MISDN_CFG_INTERNATPREFIX, prefix, sizeof(prefix));
+ break;
+ case NUMPLAN_NATIONAL:
+ misdn_cfg_get(bc->port, MISDN_CFG_NATPREFIX, prefix, sizeof(prefix));
+ break;
+ default:
+ break;
+ }
+
+ ast_copy_string(buf, bc->dad, sizeof(buf));
+ snprintf(bc->dad, sizeof(bc->dad), "%s%s", prefix, buf);
+
+ if (strcmp(bc->dad, ast->exten)) {
+ ast_copy_string(ast->exten, bc->dad, sizeof(ast->exten));
+ }
+
+ ast_set_callerid(ast, bc->oad, NULL, bc->oad);
+
+ if ( !ast_strlen_zero(bc->rad) ) {
+ if (ast->cid.cid_rdnis)
+ ast_free(ast->cid.cid_rdnis);
+ ast->cid.cid_rdnis = ast_strdup(bc->rad);
+ }
+
+ misdn_cfg_get(bc->port, MISDN_CFG_OVERLAP_DIAL, &ch->overlap_dial, sizeof(ch->overlap_dial));
+ ast_mutex_init(&ch->overlap_tv_lock);
+ } /* ORIG MISDN END */
+
+ ch->overlap_dial_task = -1;
+
+ if (ch->faxdetect || ch->ast_dsp) {
+ misdn_cfg_get(port, MISDN_CFG_FAXDETECT_TIMEOUT, &ch->faxdetect_timeout, sizeof(ch->faxdetect_timeout));
+ if (!ch->dsp)
+ ch->dsp = ast_dsp_new();
+ if (ch->dsp) {
+ if (ch->faxdetect)
+ ast_dsp_set_features(ch->dsp, DSP_FEATURE_DTMF_DETECT | DSP_FEATURE_FAX_DETECT);
+ else
+ ast_dsp_set_features(ch->dsp, DSP_FEATURE_DTMF_DETECT );
+ }
+ if (!ch->trans)
+ ch->trans = ast_translator_build_path(AST_FORMAT_SLINEAR, AST_FORMAT_ALAW);
+ }
+
+ /* AOCD initialization */
+ bc->AOCDtype = Fac_None;
+
+ return 0;
+}
+
+
+/*****************************/
+/*** AST Indications Start ***/
+/*****************************/
+
+static int misdn_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ int port = 0;
+ int r;
+ int exceed;
+ int bridging;
+ struct chan_list *ch = MISDN_ASTERISK_TECH_PVT(ast);
+ struct misdn_bchannel *newbc;
+ char *opts = NULL, *ext, *tokb;
+ char *dest_cp = ast_strdupa(dest);
+
+ ext = strtok_r(dest_cp, "/", &tokb);
+
+ if (ext) {
+ ext = strtok_r(NULL, "/", &tokb);
+ if (ext) {
+ opts = strtok_r(NULL, "/", &tokb);
+ } else {
+ chan_misdn_log(0, 0, "misdn_call: No Extension given!\n");
+ return -1;
+ }
+ }
+
+ if (!ast) {
+ ast_log(LOG_WARNING, " --> ! misdn_call called on ast_channel *ast where ast == NULL\n");
+ return -1;
+ }
+
+ if (((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) || !dest ) {
+ ast_log(LOG_WARNING, " --> ! misdn_call called on %s, neither down nor reserved (or dest==NULL)\n", ast->name);
+ ast->hangupcause = AST_CAUSE_NORMAL_TEMPORARY_FAILURE;
+ ast_setstate(ast, AST_STATE_DOWN);
+ return -1;
+ }
+
+ if (!ch) {
+ ast_log(LOG_WARNING, " --> ! misdn_call called on %s, neither down nor reserved (or dest==NULL)\n", ast->name);
+ ast->hangupcause = AST_CAUSE_NORMAL_TEMPORARY_FAILURE;
+ ast_setstate(ast, AST_STATE_DOWN);
+ return -1;
+ }
+
+ newbc = ch->bc;
+
+ if (!newbc) {
+ ast_log(LOG_WARNING, " --> ! misdn_call called on %s, neither down nor reserved (or dest==NULL)\n", ast->name);
+ ast->hangupcause = AST_CAUSE_NORMAL_TEMPORARY_FAILURE;
+ ast_setstate(ast, AST_STATE_DOWN);
+ return -1;
+ }
+
+ port = newbc->port;
+
+ if ((exceed = add_out_calls(port))) {
+ char tmp[16];
+ snprintf(tmp, sizeof(tmp), "%d", exceed);
+ pbx_builtin_setvar_helper(ast, "MAX_OVERFLOW", tmp);
+ return -1;
+ }
+
+ chan_misdn_log(1, port, "* CALL: %s\n", dest);
+
+ chan_misdn_log(2, port, " --> * dad:%s tech:%s ctx:%s\n", ast->exten, ast->name, ast->context);
+
+ chan_misdn_log(3, port, " --> * adding2newbc ext %s\n", ast->exten);
+ if (ast->exten) {
+ ast_copy_string(ast->exten, ext, sizeof(ast->exten));
+ ast_copy_string(newbc->dad, ext, sizeof(newbc->dad));
+ }
+
+ ast_copy_string(newbc->rad, S_OR(ast->cid.cid_rdnis, ""), sizeof(newbc->rad));
+
+ chan_misdn_log(3, port, " --> * adding2newbc callerid %s\n", ast->cid.cid_num);
+ if (ast_strlen_zero(newbc->oad) && !ast_strlen_zero(ast->cid.cid_num)) {
+ ast_copy_string(newbc->oad, ast->cid.cid_num, sizeof(newbc->oad));
+ }
+
+ newbc->capability = ast->transfercapability;
+ pbx_builtin_setvar_helper(ast, "TRANSFERCAPABILITY", ast_transfercapability2str(newbc->capability));
+ if ( ast->transfercapability == INFO_CAPABILITY_DIGITAL_UNRESTRICTED) {
+ chan_misdn_log(2, port, " --> * Call with flag Digital\n");
+ }
+
+ /* update screening and presentation */
+ update_config(ch, ORG_AST);
+
+ /* fill in some ies from channel vary*/
+ import_ch(ast, newbc, ch);
+
+ /* Finally The Options Override Everything */
+ if (opts)
+ misdn_set_opt_exec(ast, opts);
+ else
+ chan_misdn_log(2, port, "NO OPTS GIVEN\n");
+
+ /*check for bridging*/
+ misdn_cfg_get(0, MISDN_GEN_BRIDGING, &bridging, sizeof(bridging));
+ if (bridging && ch->other_ch) {
+#ifdef MISDN_1_2
+ chan_misdn_log(1, port, "Disabling EC (aka Pipeline) on both Sides\n");
+ *ch->bc->pipeline = 0;
+ *ch->other_ch->bc->pipeline = 0;
+#else
+ chan_misdn_log(1, port, "Disabling EC on both Sides\n");
+ ch->bc->ec_enable = 0;
+ ch->other_ch->bc->ec_enable = 0;
+#endif
+ }
+
+ r = misdn_lib_send_event( newbc, EVENT_SETUP );
+
+ /** we should have l3id after sending setup **/
+ ch->l3id = newbc->l3_id;
+
+ if ( r == -ENOCHAN ) {
+ chan_misdn_log(0, port, " --> * Theres no Channel at the moment .. !\n");
+ chan_misdn_log(1, port, " --> * SEND: State Down pid:%d\n", newbc ? newbc->pid : -1);
+ ast->hangupcause = AST_CAUSE_NORMAL_CIRCUIT_CONGESTION;
+ ast_setstate(ast, AST_STATE_DOWN);
+ return -1;
+ }
+
+ chan_misdn_log(2, port, " --> * SEND: State Dialing pid:%d\n", newbc ? newbc->pid : 1);
+
+ ast_setstate(ast, AST_STATE_DIALING);
+ ast->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+
+ if (newbc->nt)
+ stop_bc_tones(ch);
+
+ ch->state = MISDN_CALLING;
+
+ return 0;
+}
+
+
+static int misdn_answer(struct ast_channel *ast)
+{
+ struct chan_list *p;
+ const char *tmp;
+
+ if (!ast || ! (p = MISDN_ASTERISK_TECH_PVT(ast)) ) return -1;
+
+ chan_misdn_log(1, p ? (p->bc ? p->bc->port : 0) : 0, "* ANSWER:\n");
+
+ if (!p) {
+ ast_log(LOG_WARNING, " --> Channel not connected ??\n");
+ ast_queue_hangup(ast);
+ }
+
+ if (!p->bc) {
+ chan_misdn_log(1, 0, " --> Got Answer, but theres no bc obj ??\n");
+
+ ast_queue_hangup(ast);
+ }
+
+ tmp = pbx_builtin_getvar_helper(p->ast, "CRYPT_KEY");
+
+ if (!ast_strlen_zero(tmp)) {
+ chan_misdn_log(1, p->bc->port, " --> Connection will be BF crypted\n");
+ ast_copy_string(p->bc->crypt_key, tmp, sizeof(p->bc->crypt_key));
+ } else {
+ chan_misdn_log(3, p->bc->port, " --> Connection is without BF encryption\n");
+ }
+
+ tmp = pbx_builtin_getvar_helper(ast, "MISDN_DIGITAL_TRANS");
+ if (!ast_strlen_zero(tmp) && ast_true(tmp)) {
+ chan_misdn_log(1, p->bc->port, " --> Connection is transparent digital\n");
+ p->bc->nodsp = 1;
+ p->bc->hdlc = 0;
+ p->bc->nojitter = 1;
+ }
+
+ p->state = MISDN_CONNECTED;
+ stop_indicate(p);
+
+ if ( ast_strlen_zero(p->bc->cad) ) {
+ chan_misdn_log(2,p->bc->port," --> empty cad using dad\n");
+ ast_copy_string(p->bc->cad, p->bc->dad, sizeof(p->bc->cad));
+ }
+
+ misdn_lib_send_event( p->bc, EVENT_CONNECT);
+ start_bc_tones(p);
+
+ return 0;
+}
+
+static int misdn_digit_begin(struct ast_channel *chan, char digit)
+{
+ /* XXX Modify this callback to support Asterisk controlling the length of DTMF */
+ return 0;
+}
+
+static int misdn_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct chan_list *p;
+ struct misdn_bchannel *bc;
+ char buf[2] = { digit, 0 };
+
+ if (!ast || ! (p=MISDN_ASTERISK_TECH_PVT(ast))) return -1;
+
+ bc = p->bc;
+ chan_misdn_log(1, bc ? bc->port : 0, "* IND : Digit %c\n", digit);
+
+ if (!bc) {
+ ast_log(LOG_WARNING, " --> !! Got Digit Event withut having bchannel Object\n");
+ return -1;
+ }
+
+ switch (p->state ) {
+ case MISDN_CALLING:
+ if (strlen(bc->infos_pending) < sizeof(bc->infos_pending) - 1)
+ strncat(bc->infos_pending, buf, sizeof(bc->infos_pending) - 1);
+ break;
+ case MISDN_CALLING_ACKNOWLEDGE:
+ ast_copy_string(bc->info_dad, buf, sizeof(bc->info_dad));
+ if (strlen(bc->dad) < sizeof(bc->dad) - 1)
+ strncat(bc->dad, buf, sizeof(bc->dad) - 1);
+ ast_copy_string(p->ast->exten, bc->dad, sizeof(p->ast->exten));
+ misdn_lib_send_event( bc, EVENT_INFORMATION);
+ break;
+ default:
+ /* Do not send Digits in CONNECTED State, when
+ * the other side is too mISDN. */
+ if (p->other_ch )
+ return 0;
+
+ if ( bc->send_dtmf )
+ send_digit_to_chan(p,digit);
+ break;
+}
+
+ return 0;
+}
+
+
+static int misdn_fixup(struct ast_channel *oldast, struct ast_channel *ast)
+{
+ struct chan_list *p;
+
+ if (!ast || ! (p=MISDN_ASTERISK_TECH_PVT(ast) )) return -1;
+
+ chan_misdn_log(1, p->bc ? p->bc->port : 0, "* IND: Got Fixup State:%s L3id:%x\n", misdn_get_ch_state(p), p->l3id);
+
+ p->ast = ast;
+
+ return 0;
+}
+
+
+
+static int misdn_indication(struct ast_channel *ast, int cond, const void *data, size_t datalen)
+{
+ struct chan_list *p;
+
+ if (!ast || ! (p=MISDN_ASTERISK_TECH_PVT(ast))) {
+ ast_log(LOG_WARNING, "Returned -1 in misdn_indication\n");
+ return -1;
+ }
+
+ if (!p->bc ) {
+ chan_misdn_log(1, 0, "* IND : Indication from %s\n", ast->exten);
+ ast_log(LOG_WARNING, "Private Pointer but no bc ?\n");
+ return -1;
+ }
+
+ chan_misdn_log(5, p->bc->port, "* IND : Indication [%d] from %s\n", cond, ast->exten);
+
+ switch (cond) {
+ case AST_CONTROL_BUSY:
+ chan_misdn_log(1, p->bc->port, "* IND :\tbusy pid:%d\n", p->bc ? p->bc->pid : -1);
+ ast_setstate(ast, AST_STATE_BUSY);
+
+ p->bc->out_cause = AST_CAUSE_USER_BUSY;
+ if (p->state != MISDN_CONNECTED) {
+ start_bc_tones(p);
+ misdn_lib_send_event( p->bc, EVENT_DISCONNECT);
+ } else {
+ chan_misdn_log(-1, p->bc->port, " --> !! Got Busy in Connected State !?! ast:%s\n", ast->name);
+ }
+ return -1;
+ case AST_CONTROL_RING:
+ chan_misdn_log(1, p->bc->port, "* IND :\tring pid:%d\n", p->bc ? p->bc->pid : -1);
+ return -1;
+ case AST_CONTROL_RINGING:
+ chan_misdn_log(1, p->bc->port, "* IND :\tringing pid:%d\n", p->bc ? p->bc->pid : -1);
+ switch (p->state) {
+ case MISDN_ALERTING:
+ chan_misdn_log(2, p->bc->port, " --> * IND :\tringing pid:%d but I was Ringing before, so ignoreing it\n", p->bc ? p->bc->pid : -1);
+ break;
+ case MISDN_CONNECTED:
+ chan_misdn_log(2, p->bc->port, " --> * IND :\tringing pid:%d but Connected, so just send TONE_ALERTING without state changes \n", p->bc ? p->bc->pid : -1);
+ return -1;
+ default:
+ p->state = MISDN_ALERTING;
+ chan_misdn_log(2, p->bc->port, " --> * IND :\tringing pid:%d\n", p->bc ? p->bc->pid : -1);
+ misdn_lib_send_event( p->bc, EVENT_ALERTING);
+
+ if (p->other_ch && p->other_ch->bc) {
+ if (misdn_inband_avail(p->other_ch->bc)) {
+ chan_misdn_log(2, p->bc->port, " --> other End is mISDN and has inband info available\n");
+ break;
+ }
+
+ if (!p->other_ch->bc->nt) {
+ chan_misdn_log(2, p->bc->port, " --> other End is mISDN TE so it has inband info for sure (?)\n");
+ break;
+ }
+ }
+
+ chan_misdn_log(3, p->bc->port, " --> * SEND: State Ring pid:%d\n", p->bc ? p->bc->pid : -1);
+ ast_setstate(ast, AST_STATE_RINGING);
+
+ if ( !p->bc->nt && (p->originator == ORG_MISDN) && !p->incoming_early_audio )
+ chan_misdn_log(2, p->bc->port, " --> incoming_early_audio off\n");
+ else
+ return -1;
+ }
+ break;
+ case AST_CONTROL_ANSWER:
+ chan_misdn_log(1, p->bc->port, " --> * IND :\tanswer pid:%d\n", p->bc ? p->bc->pid : -1);
+ start_bc_tones(p);
+ break;
+ case AST_CONTROL_TAKEOFFHOOK:
+ chan_misdn_log(1, p->bc->port, " --> *\ttakeoffhook pid:%d\n", p->bc ? p->bc->pid : -1);
+ return -1;
+ case AST_CONTROL_OFFHOOK:
+ chan_misdn_log(1, p->bc->port, " --> *\toffhook pid:%d\n", p->bc ? p->bc->pid : -1);
+ return -1;
+ case AST_CONTROL_FLASH:
+ chan_misdn_log(1, p->bc->port, " --> *\tflash pid:%d\n", p->bc ? p->bc->pid : -1);
+ break;
+ case AST_CONTROL_PROGRESS:
+ chan_misdn_log(1, p->bc->port, " --> * IND :\tprogress pid:%d\n", p->bc ? p->bc->pid : -1);
+ misdn_lib_send_event( p->bc, EVENT_PROGRESS);
+ break;
+ case AST_CONTROL_PROCEEDING:
+ chan_misdn_log(1, p->bc->port, " --> * IND :\tproceeding pid:%d\n", p->bc ? p->bc->pid : -1);
+ misdn_lib_send_event( p->bc, EVENT_PROCEEDING);
+ break;
+ case AST_CONTROL_CONGESTION:
+ chan_misdn_log(1, p->bc->port, " --> * IND :\tcongestion pid:%d\n", p->bc ? p->bc->pid : -1);
+
+ p->bc->out_cause = AST_CAUSE_SWITCH_CONGESTION;
+ start_bc_tones(p);
+ misdn_lib_send_event( p->bc, EVENT_DISCONNECT);
+
+ if (p->bc->nt) {
+ hanguptone_indicate(p);
+ }
+ break;
+ case -1 :
+ chan_misdn_log(1, p->bc->port, " --> * IND :\t-1! (stop indication) pid:%d\n", p->bc ? p->bc->pid : -1);
+
+ stop_indicate(p);
+
+ if (p->state == MISDN_CONNECTED)
+ start_bc_tones(p);
+ break;
+ case AST_CONTROL_HOLD:
+ chan_misdn_log(1, p->bc->port, " --> *\tHOLD pid:%d\n", p->bc ? p->bc->pid : -1);
+ break;
+ case AST_CONTROL_UNHOLD:
+ chan_misdn_log(1, p->bc->port, " --> *\tUNHOLD pid:%d\n", p->bc ? p->bc->pid : -1);
+ break;
+ default:
+ chan_misdn_log(1, p->bc->port, " --> * Unknown Indication:%d pid:%d\n", cond, p->bc ? p->bc->pid : -1);
+ }
+
+ return 0;
+}
+
+static int misdn_hangup(struct ast_channel *ast)
+{
+ struct chan_list *p;
+ struct misdn_bchannel *bc = NULL;
+ const char *varcause = NULL;
+
+ ast_debug(1, "misdn_hangup(%s)\n", ast->name);
+
+ if (!ast || ! (p=MISDN_ASTERISK_TECH_PVT(ast) ) ) return -1;
+
+ if (!p) {
+ chan_misdn_log(3, 0, "misdn_hangup called, without chan_list obj.\n");
+ return 0 ;
+ }
+
+ bc = p->bc;
+
+ if (bc) {
+ const char *tmp=pbx_builtin_getvar_helper(ast,"MISDN_USERUSER");
+ if (tmp) {
+ ast_log(LOG_NOTICE, "MISDN_USERUSER: %s\n", tmp);
+ strcpy(bc->uu, tmp);
+ bc->uulen=strlen(bc->uu);
+ }
+ }
+
+ MISDN_ASTERISK_TECH_PVT(ast) = NULL;
+ p->ast = NULL;
+
+ if (ast->_state == AST_STATE_RESERVED ||
+ p->state == MISDN_NOTHING ||
+ p->state == MISDN_HOLDED ||
+ p->state == MISDN_HOLD_DISCONNECT ) {
+
+ CLEAN_CH:
+ /* between request and call */
+ ast_debug(1, "State Reserved (or nothing) => chanIsAvail\n");
+ MISDN_ASTERISK_TECH_PVT(ast) = NULL;
+
+ ast_mutex_lock(&release_lock);
+ cl_dequeue_chan(&cl_te, p);
+ close(p->pipe[0]);
+ close(p->pipe[1]);
+ ast_free(p);
+ ast_mutex_unlock(&release_lock);
+
+ if (bc)
+ misdn_lib_release(bc);
+
+ return 0;
+ }
+
+ if (!bc) {
+ ast_log(LOG_WARNING, "Hangup with private but no bc ? state:%s l3id:%x\n", misdn_get_ch_state(p), p->l3id);
+ goto CLEAN_CH;
+ }
+
+
+ p->need_hangup = 0;
+ p->need_queue_hangup = 0;
+ p->need_busy = 0;
+
+
+ if (!p->bc->nt)
+ stop_bc_tones(p);
+
+ bc->out_cause = ast->hangupcause ? ast->hangupcause : AST_CAUSE_NORMAL_CLEARING;
+
+ if ( (varcause = pbx_builtin_getvar_helper(ast, "HANGUPCAUSE")) ||
+ (varcause = pbx_builtin_getvar_helper(ast, "PRI_CAUSE"))) {
+ int tmpcause = atoi(varcause);
+ bc->out_cause = tmpcause ? tmpcause : AST_CAUSE_NORMAL_CLEARING;
+ }
+
+ chan_misdn_log(1, bc->port, "* IND : HANGUP\tpid:%d ctx:%s dad:%s oad:%s State:%s\n", p->bc ? p->bc->pid : -1, ast->context, ast->exten, ast->cid.cid_num, misdn_get_ch_state(p));
+ chan_misdn_log(3, bc->port, " --> l3id:%x\n", p->l3id);
+ chan_misdn_log(3, bc->port, " --> cause:%d\n", bc->cause);
+ chan_misdn_log(2, bc->port, " --> out_cause:%d\n", bc->out_cause);
+ chan_misdn_log(2, bc->port, " --> state:%s\n", misdn_get_ch_state(p));
+
+ switch (p->state) {
+ case MISDN_INCOMING_SETUP:
+ case MISDN_CALLING:
+ p->state = MISDN_CLEANING;
+ /* This is the only place in misdn_hangup, where we
+ * can call release_chan, else it might create lot's of trouble
+ * */
+ ast_log(LOG_NOTICE, "release channel, in CALLING/INCOMING_SETUP state.. no other events happened\n");
+ release_chan(bc);
+ misdn_lib_send_event( bc, EVENT_RELEASE_COMPLETE);
+ break;
+ case MISDN_HOLDED:
+ case MISDN_DIALING:
+ start_bc_tones(p);
+ hanguptone_indicate(p);
+
+ p->state=MISDN_CLEANING;
+ if (bc->need_disconnect)
+ misdn_lib_send_event( bc, EVENT_DISCONNECT);
+ break;
+ case MISDN_CALLING_ACKNOWLEDGE:
+ start_bc_tones(p);
+ hanguptone_indicate(p);
+
+ if (bc->need_disconnect)
+ misdn_lib_send_event( bc, EVENT_DISCONNECT);
+ break;
+
+ case MISDN_ALERTING:
+ case MISDN_PROGRESS:
+ case MISDN_PROCEEDING:
+ if (p->originator != ORG_AST)
+ hanguptone_indicate(p);
+
+ /*p->state=MISDN_CLEANING;*/
+ if (bc->need_disconnect)
+ misdn_lib_send_event( bc, EVENT_DISCONNECT);
+ break;
+ case MISDN_CONNECTED:
+ case MISDN_PRECONNECTED:
+ /* Alerting or Disconect */
+ if (p->bc->nt) {
+ start_bc_tones(p);
+ hanguptone_indicate(p);
+ p->bc->progress_indicator = 8;
+ }
+ if (bc->need_disconnect)
+ misdn_lib_send_event( bc, EVENT_DISCONNECT);
+
+ /*p->state=MISDN_CLEANING;*/
+ break;
+ case MISDN_DISCONNECTED:
+ if (bc->need_release)
+ misdn_lib_send_event( bc, EVENT_RELEASE);
+ p->state = MISDN_CLEANING; /* MISDN_HUNGUP_FROM_AST; */
+ break;
+
+ case MISDN_RELEASED:
+ case MISDN_CLEANING:
+ p->state = MISDN_CLEANING;
+ break;
+
+ case MISDN_BUSY:
+ break;
+
+ case MISDN_HOLD_DISCONNECT:
+ /* need to send release here */
+ chan_misdn_log(1, bc->port, " --> cause %d\n", bc->cause);
+ chan_misdn_log(1, bc->port, " --> out_cause %d\n", bc->out_cause);
+
+ bc->out_cause = -1;
+ if (bc->need_release)
+ misdn_lib_send_event(bc, EVENT_RELEASE);
+ p->state = MISDN_CLEANING;
+ break;
+ default:
+ if (bc->nt) {
+ bc->out_cause = -1;
+ if (bc->need_release)
+ misdn_lib_send_event(bc, EVENT_RELEASE);
+ p->state = MISDN_CLEANING;
+ } else {
+ if (bc->need_disconnect)
+ misdn_lib_send_event(bc, EVENT_DISCONNECT);
+ }
+ }
+
+ p->state = MISDN_CLEANING;
+
+ chan_misdn_log(3, bc->port, " --> Channel: %s hanguped new state:%s\n", ast->name, misdn_get_ch_state(p));
+
+ return 0;
+}
+
+
+static struct ast_frame *process_ast_dsp(struct chan_list *tmp, struct ast_frame *frame)
+{
+ struct ast_frame *f,*f2;
+
+ if (tmp->trans) {
+ f2 = ast_translate(tmp->trans, frame, 0);
+ f = ast_dsp_process(tmp->ast, tmp->dsp, f2);
+ } else {
+ chan_misdn_log(0, tmp->bc->port, "No T-Path found\n");
+ return NULL;
+ }
+
+ if (!f || (f->frametype != AST_FRAME_DTMF))
+ return frame;
+
+ ast_debug(1, "Detected inband DTMF digit: %c\n", f->subclass);
+
+ if (tmp->faxdetect && (f->subclass == 'f')) {
+ /* Fax tone -- Handle and return NULL */
+ if (!tmp->faxhandled) {
+ struct ast_channel *ast = tmp->ast;
+ tmp->faxhandled++;
+ chan_misdn_log(0, tmp->bc->port, "Fax detected, preparing %s for fax transfer.\n", ast->name);
+ tmp->bc->rxgain = 0;
+ isdn_lib_update_rxgain(tmp->bc);
+ tmp->bc->txgain = 0;
+ isdn_lib_update_txgain(tmp->bc);
+#ifdef MISDN_1_2
+ *tmp->bc->pipeline = 0;
+#else
+ tmp->bc->ec_enable = 0;
+#endif
+ isdn_lib_update_ec(tmp->bc);
+ isdn_lib_stop_dtmf(tmp->bc);
+ switch (tmp->faxdetect) {
+ case 1:
+ if (strcmp(ast->exten, "fax")) {
+ char *context;
+ char context_tmp[BUFFERSIZE];
+ misdn_cfg_get(tmp->bc->port, MISDN_CFG_FAXDETECT_CONTEXT, &context_tmp, sizeof(context_tmp));
+ context = ast_strlen_zero(context_tmp) ? (ast_strlen_zero(ast->macrocontext) ? ast->context : ast->macrocontext) : context_tmp;
+ if (ast_exists_extension(ast, context, "fax", 1, ast->cid.cid_num)) {
+ ast_verb(3, "Redirecting %s to fax extension (context:%s)\n", ast->name, context);
+ /* Save the DID/DNIS when we transfer the fax call to a "fax" extension */
+ pbx_builtin_setvar_helper(ast,"FAXEXTEN",ast->exten);
+ if (ast_async_goto(ast, context, "fax", 1))
+ ast_log(LOG_WARNING, "Failed to async goto '%s' into fax of '%s'\n", ast->name, context);
+ } else
+ ast_log(LOG_NOTICE, "Fax detected, but no fax extension ctx:%s exten:%s\n", context, ast->exten);
+ } else {
+ ast_debug(1, "Already in a fax extension, not redirecting\n");
+ }
+ break;
+ case 2:
+ ast_verb(3, "Not redirecting %s to fax extension, nojump is set.\n", ast->name);
+ break;
+ }
+ } else {
+ ast_debug(1, "Fax already handled\n");
+ }
+ }
+
+ if (tmp->ast_dsp && (f->subclass != 'f')) {
+ chan_misdn_log(2, tmp->bc->port, " --> * SEND: DTMF (AST_DSP) :%c\n", f->subclass);
+ }
+
+ return f;
+}
+
+
+static struct ast_frame *misdn_read(struct ast_channel *ast)
+{
+ struct chan_list *tmp;
+ fd_set rrfs;
+ struct timeval tv;
+ int len, t;
+
+ if (!ast) {
+ chan_misdn_log(1, 0, "misdn_read called without ast\n");
+ return NULL;
+ }
+ if (!(tmp = MISDN_ASTERISK_TECH_PVT(ast))) {
+ chan_misdn_log(1, 0, "misdn_read called without ast->pvt\n");
+ return NULL;
+ }
+
+ if (!tmp->bc && !(tmp->state == MISDN_HOLDED)) {
+ chan_misdn_log(1, 0, "misdn_read called without bc\n");
+ return NULL;
+ }
+
+ tv.tv_sec=0;
+ tv.tv_usec=20000;
+
+ FD_ZERO(&rrfs);
+ FD_SET(tmp->pipe[0],&rrfs);
+
+ t=select(FD_SETSIZE,&rrfs,NULL, NULL,&tv);
+
+ if (!t) {
+ chan_misdn_log(3, tmp->bc->port, "read Select Timed out\n");
+ len=160;
+ }
+
+ if (t<0) {
+ chan_misdn_log(-1, tmp->bc->port, "Select Error (err=%s)\n",strerror(errno));
+ return NULL;
+ }
+
+ if (FD_ISSET(tmp->pipe[0],&rrfs)) {
+ len=read(tmp->pipe[0],tmp->ast_rd_buf,sizeof(tmp->ast_rd_buf));
+
+ if (len<=0) {
+ /* we hangup here, since our pipe is closed */
+ chan_misdn_log(2,tmp->bc->port,"misdn_read: Pipe closed, hanging up\n");
+ return NULL;
+ }
+
+ } else {
+ return NULL;
+ }
+
+ tmp->frame.frametype = AST_FRAME_VOICE;
+ tmp->frame.subclass = AST_FORMAT_ALAW;
+ tmp->frame.datalen = len;
+ tmp->frame.samples = len;
+ tmp->frame.mallocd = 0;
+ tmp->frame.offset = 0;
+ tmp->frame.delivery = ast_tv(0,0);
+ tmp->frame.src = NULL;
+ tmp->frame.data = tmp->ast_rd_buf;
+
+ if (tmp->faxdetect && !tmp->faxhandled) {
+ if (tmp->faxdetect_timeout) {
+ if (ast_tvzero(tmp->faxdetect_tv)) {
+ tmp->faxdetect_tv = ast_tvnow();
+ chan_misdn_log(2, tmp->bc->port, "faxdetect: starting detection with timeout: %ds ...\n", tmp->faxdetect_timeout);
+ return process_ast_dsp(tmp, &tmp->frame);
+ } else {
+ struct timeval tv_now = ast_tvnow();
+ int diff = ast_tvdiff_ms(tv_now, tmp->faxdetect_tv);
+ if (diff <= (tmp->faxdetect_timeout * 1000)) {
+ chan_misdn_log(5, tmp->bc->port, "faxdetect: detecting ...\n");
+ return process_ast_dsp(tmp, &tmp->frame);
+ } else {
+ chan_misdn_log(2, tmp->bc->port, "faxdetect: stopping detection (time ran out) ...\n");
+ tmp->faxdetect = 0;
+ return &tmp->frame;
+ }
+ }
+ } else {
+ chan_misdn_log(5, tmp->bc->port, "faxdetect: detecting ... (no timeout)\n");
+ return process_ast_dsp(tmp, &tmp->frame);
+ }
+ } else {
+ if (tmp->ast_dsp)
+ return process_ast_dsp(tmp, &tmp->frame);
+ else
+ return &tmp->frame;
+ }
+}
+
+
+static int misdn_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct chan_list *ch;
+ int i = 0;
+
+ if (!ast || ! (ch = MISDN_ASTERISK_TECH_PVT(ast)) ) return -1;
+
+ if (ch->state == MISDN_HOLDED) {
+ chan_misdn_log(7, 0, "misdn_write: Returning because holded\n");
+ return 0;
+ }
+
+ if (!ch->bc ) {
+ ast_log(LOG_WARNING, "private but no bc\n");
+ return -1;
+ }
+
+ if (ch->notxtone) {
+ chan_misdn_log(7, ch->bc->port, "misdn_write: Returning because notxone\n");
+ return 0;
+ }
+
+
+ if (!frame->subclass) {
+ chan_misdn_log(4, ch->bc->port, "misdn_write: * prods us\n");
+ return 0;
+ }
+
+ if (!(frame->subclass & prefformat)) {
+
+ chan_misdn_log(-1, ch->bc->port, "Got Unsupported Frame with Format:%d\n", frame->subclass);
+ return 0;
+ }
+
+
+ if (!frame->samples ) {
+ chan_misdn_log(4, ch->bc->port, "misdn_write: zero write\n");
+
+ if (!strcmp(frame->src,"ast_prod")) {
+ chan_misdn_log(1, ch->bc->port, "misdn_write: state (%s) prodded.\n", misdn_get_ch_state(ch));
+
+ if (ch->ts) {
+ chan_misdn_log(4, ch->bc->port, "Starting Playtones\n");
+ misdn_lib_tone_generator_start(ch->bc);
+ }
+ return 0;
+ }
+
+ return -1;
+ }
+
+ if ( ! ch->bc->addr ) {
+ chan_misdn_log(8, ch->bc->port, "misdn_write: no addr for bc dropping:%d\n", frame->samples);
+ return 0;
+ }
+
+#ifdef MISDN_DEBUG
+ {
+ int i, max = 5 > frame->samples ? frame->samples : 5;
+
+ ast_debug(1, "write2mISDN %p %d bytes: ", p, frame->samples);
+
+ for (i = 0; i < max ; i++)
+ ast_debug(1, "%2.2x ", ((char*) frame->data)[i]);
+ }
+#endif
+
+ switch (ch->bc->bc_state) {
+ case BCHAN_ACTIVATED:
+ case BCHAN_BRIDGED:
+ break;
+ default:
+ if (!ch->dropped_frame_cnt)
+ chan_misdn_log(5, ch->bc->port, "BC not active (nor bridged) droping: %d frames addr:%x exten:%s cid:%s ch->state:%s bc_state:%d l3id:%x\n", frame->samples, ch->bc->addr, ast->exten, ast->cid.cid_num, misdn_get_ch_state( ch), ch->bc->bc_state, ch->bc->l3_id);
+
+ ch->dropped_frame_cnt++;
+ if (ch->dropped_frame_cnt > 100) {
+ ch->dropped_frame_cnt = 0;
+ chan_misdn_log(5, ch->bc->port, "BC not active (nor bridged) droping: %d frames addr:%x dropped > 100 frames!\n", frame->samples, ch->bc->addr);
+
+ }
+
+ return 0;
+ }
+
+ chan_misdn_log(9, ch->bc->port, "Sending :%d bytes 2 MISDN\n", frame->samples);
+ if ( !ch->bc->nojitter && misdn_cap_is_speech(ch->bc->capability) ) {
+ /* Buffered Transmit (triggert by read from isdn side)*/
+ if (misdn_jb_fill(ch->jb, frame->data, frame->samples) < 0) {
+ if (ch->bc->active)
+ cb_log(0, ch->bc->port, "Misdn Jitterbuffer Overflow.\n");
+ }
+
+ } else {
+ /*transmit without jitterbuffer*/
+ i=misdn_lib_tx2misdn_frm(ch->bc, frame->data, frame->samples);
+ }
+
+ return 0;
+}
+
+
+
+
+static enum ast_bridge_result misdn_bridge (struct ast_channel *c0,
+ struct ast_channel *c1, int flags,
+ struct ast_frame **fo,
+ struct ast_channel **rc,
+ int timeoutms)
+
+{
+ struct chan_list *ch1, *ch2;
+ struct ast_channel *carr[2], *who;
+ int to = -1;
+ struct ast_frame *f;
+ int p1_b, p2_b;
+ int bridging;
+
+ ch1 = get_chan_by_ast(c0);
+ ch2 = get_chan_by_ast(c1);
+
+ carr[0] = c0;
+ carr[1] = c1;
+
+ if (!(ch1 && ch2))
+ return -1;
+
+ misdn_cfg_get(ch1->bc->port, MISDN_CFG_BRIDGING, &p1_b, sizeof(p1_b));
+ misdn_cfg_get(ch2->bc->port, MISDN_CFG_BRIDGING, &p2_b, sizeof(p2_b));
+
+ if (! p1_b || ! p2_b) {
+ ast_log(LOG_NOTICE, "Falling back to Asterisk bridging\n");
+ return AST_BRIDGE_FAILED;
+ }
+
+ misdn_cfg_get(0, MISDN_GEN_BRIDGING, &bridging, sizeof(bridging));
+ if (bridging) {
+ /* trying to make a mISDN_dsp conference */
+ chan_misdn_log(1, ch1->bc->port, "I SEND: Making conference with Number:%d\n", ch1->bc->pid + 1);
+ misdn_lib_bridge(ch1->bc, ch2->bc);
+ }
+
+ ast_verb(3, "Native bridging %s and %s\n", c0->name, c1->name);
+
+ chan_misdn_log(1, ch1->bc->port, "* Making Native Bridge between %s and %s\n", ch1->bc->oad, ch2->bc->oad);
+
+ if (! (flags & AST_BRIDGE_DTMF_CHANNEL_0) )
+ ch1->ignore_dtmf = 1;
+
+ if (! (flags & AST_BRIDGE_DTMF_CHANNEL_1) )
+ ch2->ignore_dtmf = 1;
+
+ for (;/*ever*/;) {
+ to = -1;
+ who = ast_waitfor_n(carr, 2, &to);
+
+ if (!who) {
+ ast_log(LOG_NOTICE, "misdn_bridge: empty read, breaking out\n");
+ break;
+ }
+ f = ast_read(who);
+
+ if (!f || f->frametype == AST_FRAME_CONTROL) {
+ /* got hangup .. */
+
+ if (!f)
+ chan_misdn_log(4, ch1->bc->port, "Read Null Frame\n");
+ else
+ chan_misdn_log(4, ch1->bc->port, "Read Frame Controll class:%d\n", f->subclass);
+
+ *fo = f;
+ *rc = who;
+
+ break;
+ }
+
+ if ( f->frametype == AST_FRAME_DTMF ) {
+ chan_misdn_log(1, 0, "Read DTMF %d from %s\n", f->subclass, who->exten);
+
+ *fo = f;
+ *rc = who;
+ break;
+ }
+
+#if 0
+ if (f->frametype == AST_FRAME_VOICE) {
+ chan_misdn_log(1, ch1->bc->port, "I SEND: Splitting conference with Number:%d\n", ch1->bc->pid +1);
+
+ continue;
+ }
+#endif
+
+ if (who == c0) {
+ ast_write(c1, f);
+ }
+ else {
+ ast_write(c0, f);
+ }
+ }
+
+ chan_misdn_log(1, ch1->bc->port, "I SEND: Splitting conference with Number:%d\n", ch1->bc->pid + 1);
+
+ misdn_lib_split_bridge(ch1->bc, ch2->bc);
+
+ return AST_BRIDGE_COMPLETE;
+}
+
+/** AST INDICATIONS END **/
+
+static int dialtone_indicate(struct chan_list *cl)
+{
+ const struct ind_tone_zone_sound *ts = NULL;
+ struct ast_channel *ast = cl->ast;
+ int nd = 0;
+
+ if (!ast) {
+ chan_misdn_log(0, cl->bc->port, "No Ast in dialtone_indicate\n");
+ return -1;
+ }
+
+ misdn_cfg_get(cl->bc->port, MISDN_CFG_NODIALTONE, &nd, sizeof(nd));
+
+ if (nd) {
+ chan_misdn_log(1, cl->bc->port, "Not sending Dialtone, because config wants it\n");
+ return 0;
+ }
+
+ chan_misdn_log(3, cl->bc->port, " --> Dial\n");
+ ts = ast_get_indication_tone(ast->zone, "dial");
+ cl->ts = ts;
+
+ if (ts) {
+ cl->notxtone = 0;
+ cl->norxtone = 0;
+ /* This prods us in misdn_write */
+ ast_playtones_start(ast, 0, ts->data, 0);
+ }
+
+ return 0;
+}
+
+static int hanguptone_indicate(struct chan_list *cl)
+{
+ misdn_lib_send_tone(cl->bc, TONE_HANGUP);
+ return 0;
+}
+
+static int stop_indicate(struct chan_list *cl)
+{
+ struct ast_channel *ast = cl->ast;
+
+ if (!ast) {
+ chan_misdn_log(0, cl->bc->port, "No Ast in stop_indicate\n");
+ return -1;
+ }
+
+ chan_misdn_log(3, cl->bc->port, " --> None\n");
+ misdn_lib_tone_generator_stop(cl->bc);
+ ast_playtones_stop(ast);
+
+ cl->ts = NULL;
+ /*ast_deactivate_generator(ast);*/
+
+ return 0;
+}
+
+
+static int start_bc_tones(struct chan_list* cl)
+{
+ misdn_lib_tone_generator_stop(cl->bc);
+ cl->notxtone = 0;
+ cl->norxtone = 0;
+ return 0;
+}
+
+static int stop_bc_tones(struct chan_list *cl)
+{
+ if (!cl) return -1;
+
+ cl->notxtone = 1;
+ cl->norxtone = 1;
+
+ return 0;
+}
+
+
+static struct chan_list *init_chan_list(int orig)
+{
+ struct chan_list *cl;
+
+ cl = ast_calloc(1, sizeof(*cl));
+
+ if (!cl) {
+ chan_misdn_log(-1, 0, "misdn_request: malloc failed!");
+ return NULL;
+ }
+
+ cl->originator = orig;
+ cl->need_queue_hangup = 1;
+ cl->need_hangup = 1;
+ cl->need_busy = 1;
+ cl->overlap_dial_task = -1;
+
+ return cl;
+}
+
+static struct ast_channel *misdn_request(const char *type, int format, void *data, int *cause)
+{
+ struct ast_channel *tmp = NULL;
+ char group[BUFFERSIZE + 1] = "";
+ char buf[128];
+ char *buf2 = ast_strdupa(data), *ext = NULL, *port_str;
+ char *tokb = NULL, *p = NULL;
+ int channel = 0, port = 0;
+ struct misdn_bchannel *newbc = NULL;
+ int dec = 0;
+
+ struct chan_list *cl = init_chan_list(ORG_AST);
+
+ snprintf(buf, sizeof(buf), "%s/%s", misdn_type, (char*)data);
+
+ port_str = strtok_r(buf2, "/", &tokb);
+
+ ext = strtok_r(NULL, "/", &tokb);
+
+ if (port_str) {
+ if (port_str[0] == 'g' && port_str[1] == ':' ) {
+ /* We make a group call lets checkout which ports are in my group */
+ port_str += 2;
+ ast_copy_string(group, port_str, sizeof(group));
+ chan_misdn_log(2, 0, " --> Group Call group: %s\n", group);
+ } else if ((p = strchr(port_str, ':'))) {
+ /* we have a preselected channel */
+ *p = 0;
+ channel = atoi(++p);
+ port = atoi(port_str);
+ chan_misdn_log(2, port, " --> Call on preselected Channel (%d).\n", channel);
+ } else {
+ port = atoi(port_str);
+ }
+ } else {
+ ast_log(LOG_WARNING, " --> ! IND : CALL dad:%s WITHOUT PORT/Group, check extensions.conf\n", ext);
+ return NULL;
+ }
+
+ if (misdn_cfg_is_group_method(group, METHOD_STANDARD_DEC)) {
+ chan_misdn_log(4, port, " --> STARTING STANDARDDEC...\n");
+ dec = 1;
+ }
+
+ if (!ast_strlen_zero(group)) {
+ char cfg_group[BUFFERSIZE + 1];
+ struct robin_list *rr = NULL;
+
+ if (misdn_cfg_is_group_method(group, METHOD_ROUND_ROBIN)) {
+ chan_misdn_log(4, port, " --> STARTING ROUND ROBIN...\n");
+ rr = get_robin_position(group);
+ }
+
+ if (rr) {
+ int robin_channel = rr->channel;
+ int port_start;
+ int next_chan = 1;
+
+ do {
+ port_start = 0;
+ for (port = misdn_cfg_get_next_port_spin(rr->port); port > 0 && port != port_start;
+ port = misdn_cfg_get_next_port_spin(port)) {
+
+ if (!port_start)
+ port_start = port;
+
+ if (port >= port_start)
+ next_chan = 1;
+
+ if (port <= port_start && next_chan) {
+ int maxbchans=misdn_lib_get_maxchans(port);
+ if (++robin_channel >= maxbchans) {
+ robin_channel = 1;
+ }
+ next_chan = 0;
+ }
+
+ misdn_cfg_get(port, MISDN_CFG_GROUPNAME, cfg_group, sizeof(cfg_group));
+
+ if (!strcasecmp(cfg_group, group)) {
+ int port_up;
+ int check;
+ misdn_cfg_get(port, MISDN_CFG_PMP_L1_CHECK, &check, sizeof(check));
+ port_up = misdn_lib_port_up(port, check);
+
+ if (check && !port_up)
+ chan_misdn_log(1, port, "L1 is not Up on this Port\n");
+
+ if (check && port_up < 0) {
+ ast_log(LOG_WARNING, "This port (%d) is blocked\n", port);
+ }
+
+ if (port_up > 0) {
+ newbc = misdn_lib_get_free_bc(port, robin_channel, 0, 0);
+ if (newbc) {
+ chan_misdn_log(4, port, " Success! Found port:%d channel:%d\n", newbc->port, newbc->channel);
+ if (port_up)
+ chan_misdn_log(4, port, "portup:%d\n", port_up);
+ rr->port = newbc->port;
+ rr->channel = newbc->channel;
+ break;
+ }
+ }
+ }
+ }
+ } while (!newbc && robin_channel != rr->channel);
+
+ } else {
+ for (port = misdn_cfg_get_next_port(0); port > 0;
+ port = misdn_cfg_get_next_port(port)) {
+
+ misdn_cfg_get(port, MISDN_CFG_GROUPNAME, cfg_group, sizeof(cfg_group));
+
+ chan_misdn_log(3, port, "Group [%s] Port [%d]\n", group, port);
+ if (!strcasecmp(cfg_group, group)) {
+ int port_up;
+ int check;
+ misdn_cfg_get(port, MISDN_CFG_PMP_L1_CHECK, &check, sizeof(check));
+ port_up = misdn_lib_port_up(port, check);
+
+ chan_misdn_log(4, port, "portup:%d\n", port_up);
+
+ if (port_up > 0) {
+ newbc = misdn_lib_get_free_bc(port, 0, 0, dec);
+ if (newbc)
+ break;
+ }
+ }
+ }
+ }
+
+ /* Group dial failed ?*/
+ if (!newbc) {
+ ast_log(LOG_WARNING,
+ "Could not Dial out on group '%s'.\n"
+ "\tEither the L2 and L1 on all of these ports where DOWN (see 'show application misdn_check_l2l1')\n"
+ "\tOr there was no free channel on none of the ports\n\n"
+ , group);
+ return NULL;
+ }
+ } else { /* 'Normal' Port dial * Port dial */
+ if (channel)
+ chan_misdn_log(1, port, " --> preselected_channel: %d\n", channel);
+ newbc = misdn_lib_get_free_bc(port, channel, 0, dec);
+
+ if (!newbc) {
+ ast_log(LOG_WARNING, "Could not create channel on port:%d with extensions:%s\n", port, ext);
+ return NULL;
+ }
+ }
+
+
+ /* create ast_channel and link all the objects together */
+ cl->bc = newbc;
+
+ tmp = misdn_new(cl, AST_STATE_RESERVED, ext, NULL, format, port, channel);
+ if (!tmp) {
+ ast_log(LOG_ERROR,"Could not create Asterisk object\n");
+ return NULL;
+ }
+
+ cl->ast=tmp;
+
+ /* register chan in local list */
+ cl_queue_chan(&cl_te, cl) ;
+
+ /* fill in the config into the objects */
+ read_config(cl, ORG_AST);
+
+ /* important */
+ cl->need_hangup = 0;
+
+ return tmp;
+}
+
+
+static int misdn_send_text(struct ast_channel *chan, const char *text)
+{
+ struct chan_list *tmp = chan->tech_pvt;
+
+ if (tmp && tmp->bc) {
+ ast_copy_string(tmp->bc->display, text, sizeof(tmp->bc->display));
+ misdn_lib_send_event(tmp->bc, EVENT_INFORMATION);
+ } else {
+ ast_log(LOG_WARNING, "No chan_list but send_text request?\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct ast_channel_tech misdn_tech = {
+ .type = "mISDN",
+ .description = "Channel driver for mISDN Support (Bri/Pri)",
+ .capabilities = AST_FORMAT_ALAW ,
+ .requester = misdn_request,
+ .send_digit_begin = misdn_digit_begin,
+ .send_digit_end = misdn_digit_end,
+ .call = misdn_call,
+ .bridge = misdn_bridge,
+ .hangup = misdn_hangup,
+ .answer = misdn_answer,
+ .read = misdn_read,
+ .write = misdn_write,
+ .indicate = misdn_indication,
+ .fixup = misdn_fixup,
+ .send_text = misdn_send_text,
+ .properties = 0
+};
+
+static struct ast_channel_tech misdn_tech_wo_bridge = {
+ .type = "mISDN",
+ .description = "Channel driver for mISDN Support (Bri/Pri)",
+ .capabilities = AST_FORMAT_ALAW ,
+ .requester = misdn_request,
+ .send_digit_begin = misdn_digit_begin,
+ .send_digit_end = misdn_digit_end,
+ .call = misdn_call,
+ .hangup = misdn_hangup,
+ .answer = misdn_answer,
+ .read = misdn_read,
+ .write = misdn_write,
+ .indicate = misdn_indication,
+ .fixup = misdn_fixup,
+ .send_text = misdn_send_text,
+ .properties = 0
+};
+
+
+static int glob_channel = 0;
+
+static void update_name(struct ast_channel *tmp, int port, int c)
+{
+ int chan_offset = 0;
+ int tmp_port = misdn_cfg_get_next_port(0);
+ for (; tmp_port > 0; tmp_port = misdn_cfg_get_next_port(tmp_port)) {
+ if (tmp_port == port)
+ break;
+ chan_offset += misdn_lib_port_is_pri(tmp_port) ? 30 : 2;
+ }
+ if (c < 0)
+ c = 0;
+
+ ast_string_field_build(tmp, name, "%s/%d-u%d",
+ misdn_type, chan_offset+c, glob_channel++);
+
+ chan_misdn_log(3 , port, " --> updating channel name to [%s]\n", tmp->name);
+}
+
+static struct ast_channel *misdn_new(struct chan_list *chlist, int state, char *exten, char *callerid, int format, int port, int c)
+{
+ struct ast_channel *tmp;
+ char *cid_name = 0, *cid_num = 0;
+ int chan_offset = 0;
+ int tmp_port = misdn_cfg_get_next_port(0);
+ int bridging;
+
+ for (; tmp_port > 0; tmp_port = misdn_cfg_get_next_port(tmp_port)) {
+ if (tmp_port == port)
+ break;
+ chan_offset += misdn_lib_port_is_pri(tmp_port) ? 30 : 2;
+ }
+ if (c < 0)
+ c = 0;
+
+ if (callerid)
+ ast_callerid_parse(callerid, &cid_name, &cid_num);
+
+ tmp = ast_channel_alloc(1, state, cid_num, cid_name, "", exten, "", 0, "%s/%d-u%d", misdn_type, chan_offset + c, glob_channel++);
+
+ if (tmp) {
+ chan_misdn_log(2, 0, " --> * NEW CHANNEL dad:%s oad:%s\n", exten, callerid);
+
+ tmp->nativeformats = prefformat;
+
+ tmp->readformat = format;
+ tmp->rawreadformat = format;
+ tmp->writeformat = format;
+ tmp->rawwriteformat = format;
+
+ tmp->tech_pvt = chlist;
+
+ misdn_cfg_get(0, MISDN_GEN_BRIDGING, &bridging, sizeof(bridging));
+
+ if (bridging)
+ tmp->tech = &misdn_tech;
+ else
+ tmp->tech = &misdn_tech_wo_bridge;
+
+ tmp->writeformat = format;
+ tmp->readformat = format;
+ tmp->priority=1;
+
+ if (exten)
+ ast_copy_string(tmp->exten, exten, sizeof(tmp->exten));
+ else
+ chan_misdn_log(1, 0, "misdn_new: no exten given.\n");
+
+ if (callerid)
+ /* Don't use ast_set_callerid() here because it will
+ * generate a needless NewCallerID event */
+ tmp->cid.cid_ani = ast_strdup(cid_num);
+
+ if (pipe(chlist->pipe) < 0)
+ ast_log(LOG_ERROR, "Pipe failed\n");
+
+ ast_channel_set_fd(tmp, 0, chlist->pipe[0]);
+
+ if (state == AST_STATE_RING)
+ tmp->rings = 1;
+ else
+ tmp->rings = 0;
+
+ ast_jb_configure(tmp, misdn_get_global_jbconf());
+ } else {
+ chan_misdn_log(-1, 0, "Unable to allocate channel structure\n");
+ }
+
+ return tmp;
+}
+
+static struct chan_list *find_chan_by_bc(struct chan_list *list, struct misdn_bchannel *bc)
+{
+ struct chan_list *help = list;
+ for (; help; help = help->next) {
+ if (help->bc == bc) return help;
+ }
+
+ chan_misdn_log(6, bc->port, "$$$ find_chan: No channel found for oad:%s dad:%s\n", bc->oad, bc->dad);
+
+ return NULL;
+}
+
+static struct chan_list *find_chan_by_pid(struct chan_list *list, int pid)
+{
+ struct chan_list *help = list;
+ for (; help; help = help->next) {
+ if ( help->bc && (help->bc->pid == pid) ) return help;
+ }
+
+ chan_misdn_log(6, 0, "$$$ find_chan: No channel found for pid:%d\n", pid);
+
+ return NULL;
+}
+
+static struct chan_list *find_holded(struct chan_list *list, struct misdn_bchannel *bc)
+{
+ struct chan_list *help = list;
+
+ if (bc->pri) return NULL;
+
+ chan_misdn_log(6, bc->port, "$$$ find_holded: channel:%d oad:%s dad:%s\n", bc->channel, bc->oad, bc->dad);
+ for (;help; help = help->next) {
+ chan_misdn_log(4, bc->port, "$$$ find_holded: --> holded:%d channel:%d\n", help->state==MISDN_HOLDED, help->hold_info.channel);
+ if ( (help->state == MISDN_HOLDED) &&
+ (help->hold_info.port == bc->port) )
+ return help;
+ }
+ chan_misdn_log(6, bc->port, "$$$ find_chan: No channel found for oad:%s dad:%s\n", bc->oad, bc->dad);
+
+ return NULL;
+}
+
+
+static struct chan_list *find_holded_l3(struct chan_list *list, unsigned long l3_id, int w)
+{
+ struct chan_list *help = list;
+
+ for (; help; help = help->next) {
+ if ( (help->state == MISDN_HOLDED) &&
+ (help->l3id == l3_id)
+ )
+ return help;
+ }
+
+ return NULL;
+}
+
+static void cl_queue_chan(struct chan_list **list, struct chan_list *chan)
+{
+ chan_misdn_log(4, chan->bc ? chan->bc->port : 0, "* Queuing chan %p\n", chan);
+
+ ast_mutex_lock(&cl_te_lock);
+ if (!*list) {
+ *list = chan;
+ } else {
+ struct chan_list *help = *list;
+ for (; help->next; help = help->next);
+ help->next = chan;
+ }
+ chan->next = NULL;
+ ast_mutex_unlock(&cl_te_lock);
+}
+
+static void cl_dequeue_chan(struct chan_list **list, struct chan_list *chan)
+{
+ struct chan_list *help;
+
+ if (chan->dsp)
+ ast_dsp_free(chan->dsp);
+ if (chan->trans)
+ ast_translator_free_path(chan->trans);
+
+ ast_mutex_lock(&cl_te_lock);
+ if (!*list) {
+ ast_mutex_unlock(&cl_te_lock);
+ return;
+ }
+
+ if (*list == chan) {
+ *list = (*list)->next;
+ ast_mutex_unlock(&cl_te_lock);
+ return;
+ }
+
+ for (help = *list; help->next; help = help->next) {
+ if (help->next == chan) {
+ help->next = help->next->next;
+ ast_mutex_unlock(&cl_te_lock);
+ return;
+ }
+ }
+
+ ast_mutex_unlock(&cl_te_lock);
+}
+
+/** Channel Queue End **/
+
+
+int pbx_start_chan(struct chan_list *ch)
+{
+ int ret = ast_pbx_start(ch->ast);
+
+ if (ret >= 0)
+ ch->need_hangup = 0;
+ else
+ ch->need_hangup = 1;
+
+ return ret;
+}
+
+static void hangup_chan(struct chan_list *ch)
+{
+ int port = ch ? (ch->bc ? ch->bc->port : 0) : 0;
+ if (!ch) {
+ cb_log(1, 0, "Cannot hangup chan, no ch\n");
+ return;
+ }
+
+ cb_log(5, port, "hangup_chan called\n");
+
+ if (ch->need_hangup) {
+ cb_log(2, port, " --> hangup\n");
+ send_cause2ast(ch->ast, ch->bc, ch);
+ ch->need_hangup = 0;
+ ch->need_queue_hangup = 0;
+ if (ch->ast)
+ ast_hangup(ch->ast);
+ return;
+ }
+
+ if (!ch->need_queue_hangup) {
+ cb_log(2, port, " --> No need to queue hangup\n");
+ }
+
+ ch->need_queue_hangup = 0;
+ if (ch->ast) {
+ send_cause2ast(ch->ast, ch->bc, ch);
+
+ if (ch->ast)
+ ast_queue_hangup(ch->ast);
+ cb_log(2, port, " --> queue_hangup\n");
+ } else {
+ cb_log(1, port, "Cannot hangup chan, no ast\n");
+ }
+}
+
+/** Isdn asks us to release channel, pendant to misdn_hangup **/
+static void release_chan(struct misdn_bchannel *bc) {
+ struct ast_channel *ast=NULL;
+
+ ast_mutex_lock(&release_lock);
+ {
+ struct chan_list *ch=find_chan_by_bc(cl_te, bc);
+ if (!ch) {
+ chan_misdn_log(1, bc->port, "release_chan: Ch not found!\n");
+ ast_mutex_unlock(&release_lock);
+ return;
+ }
+
+ if (ch->ast) {
+ ast = ch->ast;
+ }
+
+ chan_misdn_log(5, bc->port, "release_chan: bc with l3id: %x\n", bc->l3_id);
+
+ /*releaseing jitterbuffer*/
+ if (ch->jb ) {
+ misdn_jb_destroy(ch->jb);
+ ch->jb = NULL;
+ } else {
+ if (!bc->nojitter)
+ chan_misdn_log(5, bc->port, "Jitterbuffer already destroyed.\n");
+ }
+
+ if (ch->overlap_dial) {
+ if (ch->overlap_dial_task != -1) {
+ misdn_tasks_remove(ch->overlap_dial_task);
+ ch->overlap_dial_task = -1;
+ }
+ ast_mutex_destroy(&ch->overlap_tv_lock);
+ }
+
+ if (ch->originator == ORG_AST) {
+ misdn_out_calls[bc->port]--;
+ } else {
+ misdn_in_calls[bc->port]--;
+ }
+
+ if (ch) {
+ close(ch->pipe[0]);
+ close(ch->pipe[1]);
+
+ if (ast && MISDN_ASTERISK_TECH_PVT(ast)) {
+ chan_misdn_log(1, bc->port, "* RELEASING CHANNEL pid:%d ctx:%s dad:%s oad:%s state: %s\n", bc ? bc->pid : -1, ast->context, ast->exten, ast->cid.cid_num, misdn_get_ch_state(ch));
+ chan_misdn_log(3, bc->port, " --> * State Down\n");
+ MISDN_ASTERISK_TECH_PVT(ast) = NULL;
+
+ if (ast->_state != AST_STATE_RESERVED) {
+ chan_misdn_log(3, bc->port, " --> Setting AST State to down\n");
+ ast_setstate(ast, AST_STATE_DOWN);
+ }
+ }
+
+ ch->state = MISDN_CLEANING;
+ cl_dequeue_chan(&cl_te, ch);
+
+ ast_free(ch);
+ } else {
+ /* chan is already cleaned, so exiting */
+ }
+
+ ast_mutex_unlock(&release_lock);
+ }
+/*** release end **/
+}
+
+static void misdn_transfer_bc(struct chan_list *tmp_ch, struct chan_list *holded_chan)
+{
+ chan_misdn_log(4, 0, "TRANSFERING %s to %s\n", holded_chan->ast->name, tmp_ch->ast->name);
+
+ tmp_ch->state = MISDN_HOLD_DISCONNECT;
+
+ ast_moh_stop(ast_bridged_channel(holded_chan->ast));
+
+ holded_chan->state=MISDN_CONNECTED;
+ /* misdn_lib_transfer(holded_chan->bc); */
+ ast_channel_masquerade(holded_chan->ast, ast_bridged_channel(tmp_ch->ast));
+}
+
+
+static void do_immediate_setup(struct misdn_bchannel *bc, struct chan_list *ch, struct ast_channel *ast)
+{
+ char *predial;
+ struct ast_frame fr;
+
+ predial = ast_strdupa(ast->exten);
+
+ ch->state = MISDN_DIALING;
+
+ if (!ch->noautorespond_on_setup) {
+ if (bc->nt) {
+ int ret;
+ ret = misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE );
+ } else {
+ int ret;
+ if ( misdn_lib_is_ptp(bc->port)) {
+ ret = misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE );
+ } else {
+ ret = misdn_lib_send_event(bc, EVENT_PROCEEDING );
+ }
+ }
+ } else {
+ ch->state = MISDN_INCOMING_SETUP;
+ }
+
+ chan_misdn_log(1, bc->port, "* Starting Ast ctx:%s dad:%s oad:%s with 's' extension\n", ast->context, ast->exten, ast->cid.cid_num);
+
+ strncpy(ast->exten, "s", 2);
+
+ if (pbx_start_chan(ch) < 0) {
+ ast = NULL;
+ hangup_chan(ch);
+ hanguptone_indicate(ch);
+
+ if (bc->nt)
+ misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE );
+ else
+ misdn_lib_send_event(bc, EVENT_DISCONNECT );
+ }
+
+
+ while (!ast_strlen_zero(predial) ) {
+ fr.frametype = AST_FRAME_DTMF;
+ fr.subclass = *predial;
+ fr.src = NULL;
+ fr.data = NULL;
+ fr.datalen = 0;
+ fr.samples = 0;
+ fr.mallocd = 0;
+ fr.offset = 0;
+ fr.delivery = ast_tv(0,0);
+
+ if (ch->ast && MISDN_ASTERISK_PVT(ch->ast) && MISDN_ASTERISK_TECH_PVT(ch->ast)) {
+ ast_queue_frame(ch->ast, &fr);
+ }
+ predial++;
+ }
+}
+
+
+
+static void send_cause2ast(struct ast_channel *ast, struct misdn_bchannel *bc, struct chan_list *ch) {
+ if (!ast) {
+ chan_misdn_log(1, 0, "send_cause2ast: No Ast\n");
+ return;
+ }
+ if (!bc) {
+ chan_misdn_log(1, 0, "send_cause2ast: No BC\n");
+ return;
+ }
+ if (!ch) {
+ chan_misdn_log(1, 0, "send_cause2ast: No Ch\n");
+ return;
+ }
+
+ ast->hangupcause = bc->cause;
+
+ switch (bc->cause) {
+
+ case 1: /** Congestion Cases **/
+ case 2:
+ case 3:
+ case 4:
+ case 22:
+ case 27:
+ /*
+ * Not Queueing the Congestion anymore, since we want to hear
+ * the inband message
+ *
+ chan_misdn_log(1, bc ? bc->port : 0, " --> * SEND: Queue Congestion pid:%d\n", bc ? bc->pid : -1);
+ ch->state = MISDN_BUSY;
+
+ ast_queue_control(ast, AST_CONTROL_CONGESTION);
+ */
+ break;
+
+ case 21:
+ case 17: /* user busy */
+
+ ch->state = MISDN_BUSY;
+
+ if (!ch->need_busy) {
+ chan_misdn_log(1, bc ? bc->port : 0, "Queued busy already\n");
+ break;
+ }
+
+ chan_misdn_log(1, bc ? bc->port : 0, " --> * SEND: Queue Busy pid:%d\n", bc ? bc->pid : -1);
+
+ ast_queue_control(ast, AST_CONTROL_BUSY);
+
+ ch->need_busy = 0;
+
+ break;
+ }
+}
+
+
+void import_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch)
+{
+ const char *tmp = pbx_builtin_getvar_helper(chan, "MISDN_PID");
+ if (tmp) {
+ ch->other_pid = atoi(tmp);
+ chan_misdn_log(3, bc->port, " --> IMPORT_PID: importing pid:%s\n", tmp);
+ if (ch->other_pid > 0) {
+ ch->other_ch = find_chan_by_pid(cl_te, ch->other_pid);
+ if (ch->other_ch)
+ ch->other_ch->other_ch = ch;
+ }
+ }
+
+ tmp = pbx_builtin_getvar_helper(chan, "MISDN_ADDRESS_COMPLETE");
+ if (tmp && (atoi(tmp) == 1)) {
+ bc->sending_complete = 1;
+ }
+
+ tmp = pbx_builtin_getvar_helper(chan, "MISDN_USERUSER");
+ if (tmp) {
+ ast_log(LOG_NOTICE, "MISDN_USERUSER: %s\n", tmp);
+ ast_copy_string(bc->uu, tmp, sizeof(bc->uu));
+ bc->uulen = strlen(bc->uu);
+ }
+
+ tmp = pbx_builtin_getvar_helper(chan, "MISDN_KEYPAD");
+ if (tmp)
+ ast_copy_string(bc->keypad, tmp, sizeof(bc->keypad));
+}
+
+void export_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch)
+{
+ char tmp[32];
+ chan_misdn_log(3, bc->port, " --> EXPORT_PID: pid:%d\n", bc->pid);
+ snprintf(tmp, sizeof(tmp), "%d", bc->pid);
+ pbx_builtin_setvar_helper(chan, "_MISDN_PID", tmp);
+
+ if (bc->sending_complete) {
+ snprintf(tmp, sizeof(tmp), "%d", bc->sending_complete);
+ pbx_builtin_setvar_helper(chan, "MISDN_ADDRESS_COMPLETE", tmp);
+ }
+
+ if (bc->urate) {
+ snprintf(tmp, sizeof(tmp), "%d", bc->urate);
+ pbx_builtin_setvar_helper(chan, "MISDN_URATE", tmp);
+ }
+
+ if (bc->uulen)
+ pbx_builtin_setvar_helper(chan, "MISDN_USERUSER", bc->uu);
+
+ if (!ast_strlen_zero(bc->keypad))
+ pbx_builtin_setvar_helper(chan, "MISDN_KEYPAD", bc->keypad);
+}
+
+int add_in_calls(int port)
+{
+ int max_in_calls;
+
+ misdn_cfg_get(port, MISDN_CFG_MAX_IN, &max_in_calls, sizeof(max_in_calls));
+ misdn_in_calls[port]++;
+
+ if (max_in_calls >= 0 && max_in_calls < misdn_in_calls[port]) {
+ ast_log(LOG_NOTICE, "Marking Incoming Call on port[%d]\n", port);
+ return misdn_in_calls[port] - max_in_calls;
+ }
+
+ return 0;
+}
+
+int add_out_calls(int port)
+{
+ int max_out_calls;
+
+ misdn_cfg_get(port, MISDN_CFG_MAX_OUT, &max_out_calls, sizeof(max_out_calls));
+
+ if (max_out_calls >= 0 && max_out_calls <= misdn_out_calls[port]) {
+ ast_log(LOG_NOTICE, "Rejecting Outgoing Call on port[%d]\n", port);
+ return (misdn_out_calls[port] + 1) - max_out_calls;
+ }
+
+ misdn_out_calls[port]++;
+
+ return 0;
+}
+
+static void start_pbx(struct chan_list *ch, struct misdn_bchannel *bc, struct ast_channel *chan) {
+ if (pbx_start_chan(ch) < 0) {
+ hangup_chan(ch);
+ chan_misdn_log(-1, bc->port, "ast_pbx_start returned <0 in SETUP\n");
+ if (bc->nt) {
+ hanguptone_indicate(ch);
+ misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
+ } else
+ misdn_lib_send_event(bc, EVENT_RELEASE);
+ }
+}
+
+static void wait_for_digits(struct chan_list *ch, struct misdn_bchannel *bc, struct ast_channel *chan) {
+ ch->state=MISDN_WAITING4DIGS;
+ misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE );
+ if (bc->nt && !bc->dad[0])
+ dialtone_indicate(ch);
+}
+
+
+/************************************************************/
+/* Receive Events from isdn_lib here */
+/************************************************************/
+static enum event_response_e
+cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
+{
+ struct chan_list *ch = find_chan_by_bc(cl_te, bc);
+
+ if (event != EVENT_BCHAN_DATA && event != EVENT_TONE_GENERATE) { /* Debug Only Non-Bchan */
+ int debuglevel = 1;
+ if ( event == EVENT_CLEANUP && !user_data)
+ debuglevel = 5;
+
+ chan_misdn_log(debuglevel, bc->port, "I IND :%s oad:%s dad:%s pid:%d state:%s\n", manager_isdn_get_info(event), bc->oad, bc->dad, bc->pid, ch ? misdn_get_ch_state(ch) : "none");
+ if (debuglevel == 1) {
+ misdn_lib_log_ies(bc);
+ chan_misdn_log(4, bc->port, " --> bc_state:%s\n", bc_state2str(bc->bc_state));
+ }
+ }
+
+ if (!ch) {
+ switch(event) {
+ case EVENT_SETUP:
+ case EVENT_DISCONNECT:
+ case EVENT_PORT_ALARM:
+ case EVENT_RETRIEVE:
+ case EVENT_NEW_BC:
+ case EVENT_FACILITY:
+ break;
+ case EVENT_RELEASE_COMPLETE:
+ chan_misdn_log(1, bc->port, " --> no Ch, so we've already released.\n");
+ break;
+ case EVENT_CLEANUP:
+ case EVENT_TONE_GENERATE:
+ case EVENT_BCHAN_DATA:
+ return -1;
+ default:
+ chan_misdn_log(1, bc->port, "Chan not existing at the moment bc->l3id:%x bc:%p event:%s port:%d channel:%d\n", bc->l3_id, bc, manager_isdn_get_info(event), bc->port, bc->channel);
+ return -1;
+ }
+ }
+
+ if (ch) {
+ switch (event) {
+ case EVENT_TONE_GENERATE:
+ break;
+ case EVENT_DISCONNECT:
+ case EVENT_RELEASE:
+ case EVENT_RELEASE_COMPLETE:
+ case EVENT_CLEANUP:
+ case EVENT_TIMEOUT:
+ if (!ch->ast)
+ chan_misdn_log(3, bc->port, "ast_hangup already called, so we have no ast ptr anymore in event(%s)\n", manager_isdn_get_info(event));
+ break;
+ default:
+ if (!ch->ast || !MISDN_ASTERISK_PVT(ch->ast) || !MISDN_ASTERISK_TECH_PVT(ch->ast)) {
+ if (event != EVENT_BCHAN_DATA)
+ ast_log(LOG_NOTICE, "No Ast or No private Pointer in Event (%d:%s)\n", event, manager_isdn_get_info(event));
+ return -1;
+ }
+ }
+ }
+
+
+ switch (event) {
+ case EVENT_PORT_ALARM:
+ {
+ int boa = 0;
+ misdn_cfg_get(bc->port, MISDN_CFG_ALARM_BLOCK, &boa, sizeof(boa));
+ if (boa) {
+ cb_log(1, bc->port, " --> blocking\n");
+ misdn_lib_port_block(bc->port);
+ }
+ }
+ break;
+ case EVENT_BCHAN_ACTIVATED:
+ break;
+
+ case EVENT_NEW_CHANNEL:
+ update_name(ch->ast,bc->port,bc->channel);
+ break;
+
+ case EVENT_NEW_L3ID:
+ ch->l3id=bc->l3_id;
+ ch->addr=bc->addr;
+ break;
+
+ case EVENT_NEW_BC:
+ if (!ch) {
+ ch = find_holded(cl_te,bc);
+ }
+
+ if (!ch) {
+ ast_log(LOG_WARNING, "NEW_BC without chan_list?\n");
+ break;
+ }
+
+ if (bc)
+ ch->bc = (struct misdn_bchannel *)user_data;
+ break;
+
+ case EVENT_DTMF_TONE:
+ {
+ /* sending INFOS as DTMF-Frames :) */
+ struct ast_frame fr = { 0, };
+ fr.frametype = AST_FRAME_DTMF;
+ fr.subclass = bc->dtmf ;
+ fr.src = NULL;
+ fr.data = NULL;
+ fr.datalen = 0;
+ fr.samples = 0;
+ fr.mallocd = 0;
+ fr.offset = 0;
+ fr.delivery = ast_tv(0,0);
+
+ if (!ch->ignore_dtmf) {
+ chan_misdn_log(2, bc->port, " --> DTMF:%c\n", bc->dtmf);
+ ast_queue_frame(ch->ast, &fr);
+ } else {
+ chan_misdn_log(2, bc->port, " --> Ingoring DTMF:%c due to bridge flags\n", bc->dtmf);
+ }
+ }
+ break;
+ case EVENT_STATUS:
+ break;
+
+ case EVENT_INFORMATION:
+ {
+ if ( ch->state != MISDN_CONNECTED )
+ stop_indicate(ch);
+
+ if (!ch->ast)
+ break;
+
+ if (ch->state == MISDN_WAITING4DIGS ) {
+ /* Ok, incomplete Setup, waiting till extension exists */
+ if (ast_strlen_zero(bc->info_dad) && ! ast_strlen_zero(bc->keypad)) {
+ chan_misdn_log(1, bc->port, " --> using keypad as info\n");
+ ast_copy_string(bc->info_dad, bc->keypad, sizeof(bc->info_dad));
+ }
+
+ strncat(bc->dad,bc->info_dad, sizeof(bc->dad) - 1);
+ ast_copy_string(ch->ast->exten, bc->dad, sizeof(ch->ast->exten));
+
+ /* Check for Pickup Request first */
+ if (!strcmp(ch->ast->exten, ast_pickup_ext())) {
+ if (ast_pickup_call(ch->ast)) {
+ hangup_chan(ch);
+ } else {
+ struct ast_channel *chan = ch->ast;
+ ch->state = MISDN_CALLING_ACKNOWLEDGE;
+ ast_setstate(chan, AST_STATE_DOWN);
+ hangup_chan(ch);
+ ch->ast = NULL;
+ break;
+ }
+ }
+
+ if (!ast_canmatch_extension(ch->ast, ch->context, bc->dad, 1, bc->oad)) {
+ if (ast_exists_extension(ch->ast, ch->context, "i", 1, bc->oad)) {
+ ast_log(LOG_WARNING, "Extension can never match, So jumping to 'i' extension. port(%d)\n", bc->port);
+ strcpy(ch->ast->exten, "i");
+
+ ch->state = MISDN_DIALING;
+ start_pbx(ch, bc, ch->ast);
+ break;
+ }
+
+ ast_log(LOG_WARNING, "Extension can never match, so disconnecting on port(%d)."
+ "maybe you want to add an 'i' extension to catch this case.\n",
+ bc->port);
+
+ if (bc->nt)
+ hanguptone_indicate(ch);
+ ch->state = MISDN_EXTCANTMATCH;
+ bc->out_cause = 1;
+
+ misdn_lib_send_event(bc, EVENT_DISCONNECT);
+ break;
+ }
+
+ if (ch->overlap_dial) {
+ ast_mutex_lock(&ch->overlap_tv_lock);
+ ch->overlap_tv = ast_tvnow();
+ ast_mutex_unlock(&ch->overlap_tv_lock);
+ if (ch->overlap_dial_task == -1) {
+ ch->overlap_dial_task =
+ misdn_tasks_add_variable(ch->overlap_dial, misdn_overlap_dial_task, ch);
+ }
+ break;
+ }
+
+ if (ast_exists_extension(ch->ast, ch->context, bc->dad, 1, bc->oad)) {
+
+ ch->state = MISDN_DIALING;
+ start_pbx(ch, bc, ch->ast);
+ }
+ } else {
+ /* sending INFOS as DTMF-Frames :) */
+ struct ast_frame fr;
+ int digits;
+ fr.frametype = AST_FRAME_DTMF;
+ fr.subclass = bc->info_dad[0] ;
+ fr.src = NULL;
+ fr.data = NULL;
+ fr.datalen = 0;
+ fr.samples = 0;
+ fr.mallocd = 0;
+ fr.offset = 0;
+ fr.delivery = ast_tv(0,0);
+
+ misdn_cfg_get(0, MISDN_GEN_APPEND_DIGITS2EXTEN, &digits, sizeof(digits));
+ if (ch->state != MISDN_CONNECTED ) {
+ if (digits) {
+ strncat(bc->dad, bc->info_dad, sizeof(bc->dad) - 1);
+ ast_copy_string(ch->ast->exten, bc->dad, sizeof(ch->ast->exten));
+ ast_cdr_update(ch->ast);
+ }
+
+ ast_queue_frame(ch->ast, &fr);
+ }
+ }
+ }
+ break;
+ case EVENT_SETUP:
+ {
+ struct chan_list *ch = find_chan_by_bc(cl_te, bc);
+ int msn_valid = misdn_cfg_is_msn_valid(bc->port, bc->dad);
+ struct ast_channel *chan;
+ int exceed;
+ int pres,screen;
+ int ai;
+ int im;
+
+ if (ch) {
+ switch (ch->state) {
+ case MISDN_NOTHING:
+ ch = NULL;
+ break;
+ default:
+ chan_misdn_log(1, bc->port, " --> Ignoring Call we have already one\n");
+ return RESPONSE_IGNORE_SETUP_WITHOUT_CLOSE; /* Ignore MSNs which are not in our List */
+ }
+ }
+
+ if (!bc->nt && ! msn_valid) {
+ chan_misdn_log(1, bc->port, " --> Ignoring Call, its not in our MSN List\n");
+ return RESPONSE_IGNORE_SETUP; /* Ignore MSNs which are not in our List */
+ }
+
+ if (bc->cw) {
+ int cause;
+ chan_misdn_log(0, bc->port, " --> Call Waiting on PMP sending RELEASE_COMPLETE\n");
+ misdn_cfg_get(bc->port, MISDN_CFG_REJECT_CAUSE, &cause, sizeof(cause));
+ bc->out_cause = cause ? cause : 16;
+ return RESPONSE_RELEASE_SETUP;
+ }
+
+ print_bearer(bc);
+
+ if (!bc->nt && ! msn_valid) {
+ chan_misdn_log(1, bc->port, " --> Ignoring Call, its not in our MSN List\n");
+ return RESPONSE_IGNORE_SETUP; /* Ignore MSNs which are not in our List */
+ }
+
+ if (bc->cw) {
+ int cause;
+ chan_misdn_log(0, bc->port, " --> Call Waiting on PMP sending RELEASE_COMPLETE\n");
+ misdn_cfg_get(bc->port, MISDN_CFG_REJECT_CAUSE, &cause, sizeof(cause));
+ bc->out_cause = cause ? cause : 16;
+ return RESPONSE_RELEASE_SETUP;
+ }
+
+ print_bearer(bc);
+
+ ch = init_chan_list(ORG_MISDN);
+
+ if (!ch) {
+ chan_misdn_log(-1, bc->port, "cb_events: malloc for chan_list failed!\n");
+ return 0;
+ }
+
+ ch->bc = bc;
+ ch->l3id = bc->l3_id;
+ ch->addr = bc->addr;
+ ch->originator = ORG_MISDN;
+
+ chan = misdn_new(ch, AST_STATE_RESERVED, bc->dad, bc->oad, AST_FORMAT_ALAW, bc->port, bc->channel);
+
+ if (!chan) {
+ misdn_lib_send_event(bc,EVENT_RELEASE_COMPLETE);
+ ast_log(LOG_ERROR, "cb_events: misdn_new failed !\n");
+ return 0;
+ }
+
+ ch->ast = chan;
+
+ if ((exceed = add_in_calls(bc->port))) {
+ char tmp[16];
+ snprintf(tmp, sizeof(tmp), "%d", exceed);
+ pbx_builtin_setvar_helper(chan, "MAX_OVERFLOW", tmp);
+ }
+
+ read_config(ch, ORG_MISDN);
+
+ export_ch(chan, bc, ch);
+
+ ch->ast->rings = 1;
+ ast_setstate(ch->ast, AST_STATE_RINGING);
+
+ switch (bc->pres) {
+ case 1:
+ pres = AST_PRES_RESTRICTED;
+ chan_misdn_log(2, bc->port, " --> PRES: Restricted (1)\n");
+ break;
+ case 2:
+ pres = AST_PRES_UNAVAILABLE;
+ chan_misdn_log(2, bc->port, " --> PRES: Restricted (2)\n");
+ break;
+ default:
+ pres = AST_PRES_ALLOWED;
+ chan_misdn_log(2, bc->port, " --> PRES: Restricted (%d)\n", bc->pres);
+ }
+
+ switch (bc->screen) {
+ case 0:
+ screen = AST_PRES_USER_NUMBER_UNSCREENED;
+ chan_misdn_log(2, bc->port, " --> SCREEN: Unscreened (0)\n");
+ break;
+ case 1:
+ screen = AST_PRES_USER_NUMBER_PASSED_SCREEN;
+ chan_misdn_log(2, bc->port, " --> SCREEN: Passed screen (1)\n");
+ break;
+ case 2:
+ screen = AST_PRES_USER_NUMBER_FAILED_SCREEN;
+ chan_misdn_log(2, bc->port, " --> SCREEN: failed screen (2)\n");
+ break;
+ case 3:
+ screen = AST_PRES_NETWORK_NUMBER;
+ chan_misdn_log(2, bc->port, " --> SCREEN: Network Number (3)\n");
+ break;
+ default:
+ screen = AST_PRES_USER_NUMBER_UNSCREENED;
+ chan_misdn_log(2, bc->port, " --> SCREEN: Unscreened (%d)\n", bc->screen);
+ }
+
+ chan->cid.cid_pres = pres + screen;
+
+ pbx_builtin_setvar_helper(chan, "TRANSFERCAPABILITY", ast_transfercapability2str(bc->capability));
+ chan->transfercapability = bc->capability;
+
+ switch (bc->capability) {
+ case INFO_CAPABILITY_DIGITAL_UNRESTRICTED:
+ pbx_builtin_setvar_helper(chan, "CALLTYPE", "DIGITAL");
+ break;
+ default:
+ pbx_builtin_setvar_helper(chan, "CALLTYPE", "SPEECH");
+ }
+
+ /** queue new chan **/
+ cl_queue_chan(&cl_te, ch);
+
+ if (!strstr(ch->allowed_bearers, "all")) {
+ int i;
+ for (i = 0; i < sizeof(allowed_bearers_array) / sizeof(struct allowed_bearers); i++) {
+ if (allowed_bearers_array[i].cap == bc->capability) {
+ if (!strstr(ch->allowed_bearers, allowed_bearers_array[i].name)) {
+ chan_misdn_log(0, bc->port, "Bearer Not allowed\b");
+ bc->out_cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
+
+ ch->state = MISDN_EXTCANTMATCH;
+ misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE );
+ return RESPONSE_OK;
+ }
+ }
+
+ }
+ }
+
+ /* Check for Pickup Request first */
+ if (!strcmp(chan->exten, ast_pickup_ext())) {
+ if (!ch->noautorespond_on_setup) {
+ int ret;/** Sending SETUP_ACK**/
+ ret = misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE );
+ } else {
+ ch->state = MISDN_INCOMING_SETUP;
+ }
+ if (ast_pickup_call(chan)) {
+ hangup_chan(ch);
+ } else {
+ ch->state = MISDN_CALLING_ACKNOWLEDGE;
+ ast_setstate(chan, AST_STATE_DOWN);
+ hangup_chan(ch);
+ ch->ast = NULL;
+ break;
+ }
+ }
+
+ /*
+ added support for s extension hope it will help those poor cretains
+ which haven't overlap dial.
+ */
+ misdn_cfg_get(bc->port, MISDN_CFG_ALWAYS_IMMEDIATE, &ai, sizeof(ai));
+ if (ai) {
+ do_immediate_setup(bc, ch, chan);
+ break;
+ }
+
+ /* check if we should jump into s when we have no dad */
+ misdn_cfg_get(bc->port, MISDN_CFG_IMMEDIATE, &im, sizeof(im));
+ if (im && ast_strlen_zero(bc->dad)) {
+ do_immediate_setup(bc, ch, chan);
+ break;
+ }
+
+ chan_misdn_log(5, bc->port, "CONTEXT:%s\n", ch->context);
+ if(!ast_canmatch_extension(ch->ast, ch->context, bc->dad, 1, bc->oad)) {
+ if (ast_exists_extension(ch->ast, ch->context, "i", 1, bc->oad)) {
+ ast_log(LOG_WARNING, "Extension can never match, So jumping to 'i' extension. port(%d)\n", bc->port);
+ strcpy(ch->ast->exten, "i");
+ misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE);
+ ch->state = MISDN_DIALING;
+ start_pbx(ch, bc, chan);
+ break;
+ }
+
+ ast_log(LOG_WARNING, "Extension can never match, so disconnecting on port(%d)."
+ "maybe you want to add an 'i' extension to catch this case.\n",
+ bc->port);
+ if (bc->nt)
+ hanguptone_indicate(ch);
+
+ ch->state = MISDN_EXTCANTMATCH;
+ bc->out_cause = AST_CAUSE_UNALLOCATED;
+
+ if (bc->nt)
+ misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE );
+ else
+ misdn_lib_send_event(bc, EVENT_RELEASE );
+
+ break;
+ }
+
+ /* Whatever happens, when sending_complete is set or we are PTMP TE, we will definitely
+ * jump into the dialplan, when the dialed extension does not exist, the 's' extension
+ * will be used by Asterisk automatically. */
+ if (bc->sending_complete || (!bc->nt && !misdn_lib_is_ptp(bc->port))) {
+ if (!ch->noautorespond_on_setup) {
+ ch->state=MISDN_DIALING;
+ misdn_lib_send_event(bc, EVENT_PROCEEDING );
+ } else {
+ ch->state = MISDN_INCOMING_SETUP;
+ }
+ start_pbx(ch, bc, chan);
+ break;
+ }
+
+
+ /*
+ * When we are NT and overlapdial is set and if
+ * the number is empty, we wait for the ISDN timeout
+ * instead of our own timer.
+ */
+ if (ch->overlap_dial && bc->nt && !bc->dad[0] ) {
+ wait_for_digits(ch, bc, chan);
+ break;
+ }
+
+ /*
+ * If overlapdial we will definitely send a SETUP_ACKNOWLEDGE and wait for more
+ * Infos with a Interdigit Timeout.
+ * */
+ if (ch->overlap_dial) {
+ ast_mutex_lock(&ch->overlap_tv_lock);
+ ch->overlap_tv = ast_tvnow();
+ ast_mutex_unlock(&ch->overlap_tv_lock);
+
+ wait_for_digits(ch, bc, chan);
+ if (ch->overlap_dial_task == -1)
+ ch->overlap_dial_task =
+ misdn_tasks_add_variable(ch->overlap_dial, misdn_overlap_dial_task, ch);
+
+ break;
+ }
+
+ /* If the extension does not exist and we're not TE_PTMP we wait for more digis
+ * without interdigit timeout.
+ * */
+ if (!ast_exists_extension(ch->ast, ch->context, bc->dad, 1, bc->oad)) {
+ wait_for_digits(ch, bc, chan);
+ break;
+ }
+
+ /*
+ * If the extension exists let's just jump into it.
+ * */
+ if (ast_exists_extension(ch->ast, ch->context, bc->dad, 1, bc->oad)) {
+ if (bc->need_more_infos)
+ misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE );
+ else
+ misdn_lib_send_event(bc, EVENT_PROCEEDING);
+
+ ch->state = MISDN_DIALING;
+ start_pbx(ch, bc, chan);
+ break;
+ }
+ }
+ break;
+
+ case EVENT_SETUP_ACKNOWLEDGE:
+ {
+ ch->state = MISDN_CALLING_ACKNOWLEDGE;
+
+ if (bc->channel)
+ update_name(ch->ast,bc->port,bc->channel);
+
+ if (!ast_strlen_zero(bc->infos_pending)) {
+ /* TX Pending Infos */
+ strncat(bc->dad, bc->infos_pending, sizeof(bc->dad) - strlen(bc->dad) - 1);
+
+ if (!ch->ast)
+ break;
+ ast_copy_string(ch->ast->exten, bc->dad, sizeof(ch->ast->exten));
+ ast_copy_string(bc->info_dad, bc->infos_pending, sizeof(bc->info_dad));
+ ast_copy_string(bc->infos_pending, "", sizeof(bc->infos_pending));
+
+ misdn_lib_send_event(bc, EVENT_INFORMATION);
+ }
+ }
+ break;
+ case EVENT_PROCEEDING:
+ {
+
+ if (misdn_cap_is_speech(bc->capability) &&
+ misdn_inband_avail(bc) ) {
+ start_bc_tones(ch);
+ }
+
+ ch->state = MISDN_PROCEEDING;
+
+ if (!ch->ast)
+ break;
+
+ ast_queue_control(ch->ast, AST_CONTROL_PROCEEDING);
+ }
+ break;
+ case EVENT_PROGRESS:
+
+ if (bc->channel)
+ update_name(ch->ast, bc->port, bc->channel);
+
+ if (!bc->nt ) {
+ if ( misdn_cap_is_speech(bc->capability) &&
+ misdn_inband_avail(bc)
+ ) {
+ start_bc_tones(ch);
+ }
+
+ ch->state = MISDN_PROGRESS;
+
+ if (!ch->ast)
+ break;
+ ast_queue_control(ch->ast, AST_CONTROL_PROGRESS);
+ }
+ break;
+
+
+ case EVENT_ALERTING:
+ {
+ ch->state = MISDN_ALERTING;
+
+ if (!ch->ast)
+ break;
+
+ ast_queue_control(ch->ast, AST_CONTROL_RINGING);
+ ast_setstate(ch->ast, AST_STATE_RINGING);
+
+ cb_log(7, bc->port, " --> Set State Ringing\n");
+
+ if (misdn_cap_is_speech(bc->capability) && misdn_inband_avail(bc)) {
+ cb_log(1, bc->port, "Starting Tones, we have inband Data\n");
+ start_bc_tones(ch);
+ } else {
+ cb_log(3, bc->port, " --> We have no inband Data, the other end must create ringing\n");
+ if (ch->far_alerting) {
+ cb_log(1, bc->port, " --> The other end can not do ringing eh ?.. we must do all ourself..");
+ start_bc_tones(ch);
+ /*tone_indicate(ch, TONE_FAR_ALERTING);*/
+ }
+ }
+ }
+ break;
+ case EVENT_CONNECT:
+ {
+ struct ast_channel *bridged;
+
+ /*we answer when we've got our very new L3 ID from the NT stack */
+ misdn_lib_send_event(bc, EVENT_CONNECT_ACKNOWLEDGE);
+
+ if (!ch->ast)
+ break;
+
+ bridged = ast_bridged_channel(ch->ast);
+ stop_indicate(ch);
+
+ if (bridged && !strcasecmp(bridged->tech->type, "mISDN")) {
+ struct chan_list *bridged_ch = MISDN_ASTERISK_TECH_PVT(bridged);
+
+ chan_misdn_log(1, bc->port, " --> copying cpndialplan:%d and cad:%s to the A-Channel\n", bc->cpnnumplan, bc->cad);
+ if (bridged_ch) {
+ bridged_ch->bc->cpnnumplan = bc->cpnnumplan;
+ ast_copy_string(bridged_ch->bc->cad, bc->cad, sizeof(bridged_ch->bc->cad));
+ }
+ }
+ }
+ ch->l3id=bc->l3_id;
+ ch->addr=bc->addr;
+
+ start_bc_tones(ch);
+
+ ch->state = MISDN_CONNECTED;
+
+ ast_queue_control(ch->ast, AST_CONTROL_ANSWER);
+ break;
+ case EVENT_CONNECT_ACKNOWLEDGE:
+ {
+ ch->l3id = bc->l3_id;
+ ch->addr = bc->addr;
+
+ start_bc_tones(ch);
+
+ ch->state = MISDN_CONNECTED;
+ }
+ break;
+ case EVENT_DISCONNECT:
+ /*we might not have an ch->ast ptr here anymore*/
+ if (ch) {
+ struct chan_list *holded_ch = find_holded(cl_te, bc);
+
+ chan_misdn_log(3, bc->port, " --> org:%d nt:%d, inbandavail:%d state:%d\n", ch->originator, bc->nt, misdn_inband_avail(bc), ch->state);
+ if (ch->originator == ORG_AST && !bc->nt && misdn_inband_avail(bc) && ch->state != MISDN_CONNECTED) {
+ /* If there's inband information available (e.g. a
+ recorded message saying what was wrong with the
+ dialled number, or perhaps even giving an
+ alternative number, then play it instead of
+ immediately releasing the call */
+ chan_misdn_log(1, bc->port, " --> Inband Info Avail, not sending RELEASE\n");
+
+ ch->state = MISDN_DISCONNECTED;
+ start_bc_tones(ch);
+
+ if (ch->ast) {
+ ch->ast->hangupcause = bc->cause;
+ if (bc->cause == AST_CAUSE_USER_BUSY)
+ ast_queue_control(ch->ast, AST_CONTROL_BUSY);
+ }
+ ch->need_busy = 0;
+ break;
+ }
+
+ /*Check for holded channel, to implement transfer*/
+ if (holded_ch && holded_ch != ch && ch->ast && ch->state == MISDN_CONNECTED) {
+ cb_log(1, bc->port, " --> found holded ch\n");
+ misdn_transfer_bc(ch, holded_ch) ;
+ }
+
+ bc->need_disconnect = 0;
+
+ stop_bc_tones(ch);
+ hangup_chan(ch);
+#if 0
+ } else {
+ ch = find_holded_l3(cl_te, bc->l3_id,1);
+ if (ch) {
+ hangup_chan(ch);
+ }
+#endif
+ }
+ bc->out_cause = -1;
+ if (bc->need_release)
+ misdn_lib_send_event(bc, EVENT_RELEASE);
+ break;
+
+ case EVENT_RELEASE:
+ {
+ bc->need_disconnect = 0;
+ bc->need_release = 0;
+
+ hangup_chan(ch);
+ release_chan(bc);
+ }
+ break;
+ case EVENT_RELEASE_COMPLETE:
+ {
+ bc->need_disconnect = 0;
+ bc->need_release = 0;
+ bc->need_release_complete = 0;
+
+ stop_bc_tones(ch);
+ hangup_chan(ch);
+
+ if (ch)
+ ch->state = MISDN_CLEANING;
+
+ release_chan(bc);
+ }
+ break;
+ case EVENT_BCHAN_ERROR:
+ case EVENT_CLEANUP:
+ {
+ stop_bc_tones(ch);
+
+ switch (ch->state) {
+ case MISDN_CALLING:
+ bc->cause = AST_CAUSE_DESTINATION_OUT_OF_ORDER;
+ break;
+ default:
+ break;
+ }
+
+ hangup_chan(ch);
+ release_chan(bc);
+ }
+ break;
+
+ case EVENT_TONE_GENERATE:
+ {
+ int tone_len = bc->tone_cnt;
+ struct ast_channel *ast = ch->ast;
+ void *tmp;
+ int res;
+ int (*generate)(struct ast_channel *chan, void *tmp, int datalen, int samples);
+
+ chan_misdn_log(9, bc->port, "TONE_GEN: len:%d\n");
+
+ if (!ast)
+ break;
+
+ if (!ast->generator)
+ break;
+
+ tmp = ast->generatordata;
+ ast->generatordata = NULL;
+ generate = ast->generator->generate;
+
+ if (tone_len < 0 || tone_len > 512 ) {
+ ast_log(LOG_NOTICE, "TONE_GEN: len was %d, set to 128\n", tone_len);
+ tone_len = 128;
+ }
+
+ res = generate(ast, tmp, tone_len, tone_len);
+ ast->generatordata = tmp;
+
+ if (res) {
+ ast_log(LOG_WARNING, "Auto-deactivating generator\n");
+ ast_deactivate_generator(ast);
+ } else {
+ bc->tone_cnt = 0;
+ }
+ }
+ break;
+
+ case EVENT_BCHAN_DATA:
+ {
+ if (ch->bc->AOCD_need_export)
+ export_aoc_vars(ch->originator, ch->ast, ch->bc);
+ if (!misdn_cap_is_speech(ch->bc->capability) ) {
+ struct ast_frame frame;
+ /*In Data Modes we queue frames*/
+ frame.frametype = AST_FRAME_VOICE; /*we have no data frames yet*/
+ frame.subclass = AST_FORMAT_ALAW;
+ frame.datalen = bc->bframe_len;
+ frame.samples = bc->bframe_len;
+ frame.mallocd = 0;
+ frame.offset = 0;
+ frame.delivery = ast_tv(0,0);
+ frame.src = NULL;
+ frame.data = bc->bframe;
+
+ if (ch->ast)
+ ast_queue_frame(ch->ast, &frame);
+ } else {
+ fd_set wrfs;
+ struct timeval tv = { 0, 0 };
+ int t;
+
+ FD_ZERO(&wrfs);
+ FD_SET(ch->pipe[1], &wrfs);
+
+ t = select(FD_SETSIZE, NULL, &wrfs, NULL, &tv);
+
+ if (!t) {
+ chan_misdn_log(9, bc->port, "Select Timed out\n");
+ break;
+ }
+
+ if (t < 0) {
+ chan_misdn_log(-1, bc->port, "Select Error (err=%s)\n", strerror(errno));
+ break;
+ }
+
+ if (FD_ISSET(ch->pipe[1], &wrfs)) {
+ chan_misdn_log(9, bc->port, "writing %d bytes 2 asterisk\n", bc->bframe_len);
+ if (write(ch->pipe[1], bc->bframe, bc->bframe_len) <= 0) {
+ chan_misdn_log(0, bc->port, "Write returned <=0 (err=%s) --> hanging up channel\n", strerror(errno));
+
+ stop_bc_tones(ch);
+ hangup_chan(ch);
+ release_chan(bc);
+ }
+ } else {
+ chan_misdn_log(1, bc->port, "Write Pipe full!\n");
+ }
+ }
+ }
+ break;
+ case EVENT_TIMEOUT:
+ {
+ if (ch && bc)
+ chan_misdn_log(1, bc->port, "--> state: %s\n", misdn_get_ch_state(ch));
+
+ switch (ch->state) {
+ case MISDN_DIALING:
+ case MISDN_PROGRESS:
+ if (bc->nt && !ch->nttimeout)
+ break;
+
+ case MISDN_CALLING:
+ case MISDN_ALERTING:
+ case MISDN_PROCEEDING:
+ case MISDN_CALLING_ACKNOWLEDGE:
+ if (bc->nt) {
+ bc->progress_indicator = 8;
+ hanguptone_indicate(ch);
+ }
+
+ bc->out_cause = AST_CAUSE_UNALLOCATED;
+ misdn_lib_send_event(bc, EVENT_DISCONNECT);
+ break;
+
+ case MISDN_WAITING4DIGS:
+ if (bc->nt) {
+ bc->progress_indicator = 8;
+ bc->out_cause = AST_CAUSE_UNALLOCATED;
+ hanguptone_indicate(ch);
+ misdn_lib_send_event(bc, EVENT_DISCONNECT);
+ } else {
+ bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
+ misdn_lib_send_event(bc, EVENT_RELEASE);
+ }
+
+ break;
+
+ case MISDN_CLEANING:
+ chan_misdn_log(1,bc->port," --> in state cleaning .. so ingoring, the stack should clean it for us\n");
+ break;
+
+ default:
+ misdn_lib_send_event(bc,EVENT_RELEASE_COMPLETE);
+ }
+ }
+ break;
+
+
+ /***************************/
+ /** Suplementary Services **/
+ /***************************/
+ case EVENT_RETRIEVE:
+ {
+ struct ast_channel *hold_ast;
+
+ if (!ch) {
+ chan_misdn_log(4, bc->port, " --> no CH, searching in holded\n");
+ ch = find_holded_l3(cl_te, bc->l3_id, 1);
+ }
+
+ if (!ch) {
+ ast_log(LOG_WARNING, "Found no Holded channel, cannot Retrieve\n");
+ misdn_lib_send_event(bc, EVENT_RETRIEVE_REJECT);
+ break;
+ }
+
+ /*remember the channel again*/
+ ch->bc = bc;
+ ch->state = MISDN_CONNECTED;
+
+ ch->hold_info.port = 0;
+ ch->hold_info.channel = 0;
+
+ hold_ast = ast_bridged_channel(ch->ast);
+
+ if (hold_ast) {
+ ast_moh_stop(hold_ast);
+ }
+
+ if (misdn_lib_send_event(bc, EVENT_RETRIEVE_ACKNOWLEDGE) < 0) {
+ chan_misdn_log(4, bc->port, " --> RETRIEVE_ACK failed\n");
+ misdn_lib_send_event(bc, EVENT_RETRIEVE_REJECT);
+ }
+ }
+ break;
+
+ case EVENT_HOLD:
+ {
+ int hold_allowed;
+ struct ast_channel *bridged = ast_bridged_channel(ch->ast);
+
+ misdn_cfg_get(bc->port, MISDN_CFG_HOLD_ALLOWED, &hold_allowed, sizeof(hold_allowed));
+
+ if (!hold_allowed) {
+
+ chan_misdn_log(-1, bc->port, "Hold not allowed this port.\n");
+ misdn_lib_send_event(bc, EVENT_HOLD_REJECT);
+ break;
+ }
+
+ if (bridged) {
+ chan_misdn_log(2, bc->port, "Bridge Partner is of type: %s\n", bridged->tech->type);
+ ch->state = MISDN_HOLDED;
+ ch->l3id = bc->l3_id;
+
+ misdn_lib_send_event(bc, EVENT_HOLD_ACKNOWLEDGE);
+
+ /* XXX This should queue an AST_CONTROL_HOLD frame on this channel
+ * instead of starting moh on the bridged channel directly */
+ ast_moh_start(bridged, NULL, NULL);
+
+ /*forget the channel now*/
+ ch->bc = NULL;
+ ch->hold_info.port = bc->port;
+ ch->hold_info.channel = bc->channel;
+
+ } else {
+ misdn_lib_send_event(bc, EVENT_HOLD_REJECT);
+ chan_misdn_log(0, bc->port, "We aren't bridged to anybody\n");
+ }
+ }
+ break;
+
+ case EVENT_FACILITY:
+ print_facility(&(bc->fac_in), bc);
+
+ switch (bc->fac_in.Function) {
+#ifdef HAVE_MISDN_FAC_RESULT
+ case Fac_RESULT:
+ break;
+#endif
+ case Fac_CD:
+ if (ch) {
+ struct ast_channel *bridged = ast_bridged_channel(ch->ast);
+ struct chan_list *ch_br;
+ if (bridged && MISDN_ASTERISK_TECH_PVT(bridged)) {
+ ch_br = MISDN_ASTERISK_TECH_PVT(bridged);
+ /*ch->state = MISDN_FACILITY_DEFLECTED;*/
+ if (ch_br->bc) {
+ if (ast_exists_extension(bridged, ch->context, (char *)bc->fac_in.u.CDeflection.DeflectedToNumber, 1, bc->oad)) {
+ ch_br->state = MISDN_DIALING;
+ if (pbx_start_chan(ch_br) < 0) {
+ chan_misdn_log(-1, ch_br->bc->port, "ast_pbx_start returned < 0 in misdn_overlap_dial_task\n");
+ }
+ }
+ }
+ }
+ misdn_lib_send_event(bc, EVENT_DISCONNECT);
+ }
+ break;
+ case Fac_AOCDCurrency:
+ if (ch) {
+ bc->AOCDtype = Fac_AOCDCurrency;
+ memcpy(&(bc->AOCD.currency), &(bc->fac_in.u.AOCDcur), sizeof(bc->AOCD.currency));
+ bc->AOCD_need_export = 1;
+ export_aoc_vars(ch->originator, ch->ast, bc);
+ }
+ break;
+ case Fac_AOCDChargingUnit:
+ if (ch) {
+ bc->AOCDtype = Fac_AOCDChargingUnit;
+ memcpy(&(bc->AOCD.chargingUnit), &(bc->fac_in.u.AOCDchu), sizeof(bc->AOCD.chargingUnit));
+ bc->AOCD_need_export = 1;
+ export_aoc_vars(ch->originator, ch->ast, bc);
+ }
+ break;
+ case Fac_None:
+#ifdef HAVE_MISDN_FAC_ERROR
+ case Fac_ERROR:
+#endif
+ break;
+ default:
+ chan_misdn_log(0, bc->port," --> not yet handled: facility type:%p\n", bc->fac_in.Function);
+ }
+
+ break;
+
+ case EVENT_RESTART:
+
+ if (!bc->dummy) {
+ stop_bc_tones(ch);
+ release_chan(bc);
+ }
+ break;
+
+ default:
+ chan_misdn_log(1, 0, "Got Unknown Event\n");
+ break;
+ }
+
+ return RESPONSE_OK;
+}
+
+/** TE STUFF END **/
+
+/******************************************
+ *
+ * Asterisk Channel Endpoint END
+ *
+ *
+ *******************************************/
+
+
+
+static int unload_module(void)
+{
+ /* First, take us out of the channel loop */
+ ast_log(LOG_VERBOSE, "-- Unregistering mISDN Channel Driver --\n");
+
+ misdn_tasks_destroy();
+
+ if (!g_config_initialized)
+ return 0;
+
+ ast_cli_unregister_multiple(chan_misdn_clis, sizeof(chan_misdn_clis) / sizeof(struct ast_cli_entry));
+
+ /* ast_unregister_application("misdn_crypt"); */
+ ast_unregister_application("misdn_set_opt");
+ ast_unregister_application("misdn_facility");
+ ast_unregister_application("misdn_check_l2l1");
+
+ ast_channel_unregister(&misdn_tech);
+
+ free_robin_list();
+ misdn_cfg_destroy();
+ misdn_lib_destroy();
+
+ if (misdn_debug)
+ ast_free(misdn_debug);
+ if (misdn_debug_only)
+ ast_free(misdn_debug_only);
+ ast_free(misdn_ports);
+
+ return 0;
+}
+
+static int load_module(void)
+{
+ int i, port;
+ int ntflags = 0, ntkc = 0;
+ char ports[256] = "";
+ char tempbuf[BUFFERSIZE + 1];
+ char ntfile[BUFFERSIZE + 1];
+ struct misdn_lib_iface iface = {
+ .cb_event = cb_events,
+ .cb_log = chan_misdn_log,
+ .cb_jb_empty = chan_misdn_jb_empty,
+ };
+
+ max_ports = misdn_lib_maxports_get();
+
+ if (max_ports <= 0) {
+ ast_log(LOG_ERROR, "Unable to initialize mISDN\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ if (misdn_cfg_init(max_ports, 0)) {
+ ast_log(LOG_ERROR, "Unable to initialize misdn_config.\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ g_config_initialized = 1;
+
+ misdn_debug = ast_malloc(sizeof(int) * (max_ports + 1));
+ if (!misdn_debug) {
+ ast_log(LOG_ERROR, "Out of memory for misdn_debug\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ misdn_ports = ast_malloc(sizeof(int) * (max_ports + 1));
+ if (!misdn_ports) {
+ ast_log(LOG_ERROR, "Out of memory for misdn_ports\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ misdn_cfg_get(0, MISDN_GEN_DEBUG, &misdn_debug[0], sizeof(misdn_debug[0]));
+ for (i = 1; i <= max_ports; i++) {
+ misdn_debug[i] = misdn_debug[0];
+ misdn_ports[i] = i;
+ }
+ *misdn_ports = 0;
+ misdn_debug_only = ast_calloc(max_ports + 1, sizeof(int));
+
+ misdn_cfg_get(0, MISDN_GEN_TRACEFILE, tempbuf, sizeof(tempbuf));
+ if (!ast_strlen_zero(tempbuf))
+ tracing = 1;
+
+ misdn_in_calls = ast_malloc(sizeof(int) * (max_ports + 1));
+ misdn_out_calls = ast_malloc(sizeof(int) * (max_ports + 1));
+
+ for (i = 1; i <= max_ports; i++) {
+ misdn_in_calls[i] = 0;
+ misdn_out_calls[i] = 0;
+ }
+
+ ast_mutex_init(&cl_te_lock);
+ ast_mutex_init(&release_lock);
+
+ misdn_cfg_update_ptp();
+ misdn_cfg_get_ports_string(ports);
+
+ if (!ast_strlen_zero(ports))
+ chan_misdn_log(0, 0, "Got: %s from get_ports\n", ports);
+ if (misdn_lib_init(ports, &iface, NULL))
+ chan_misdn_log(0, 0, "No te ports initialized\n");
+
+ misdn_cfg_get(0, MISDN_GEN_NTDEBUGFLAGS, &ntflags, sizeof(ntflags));
+ misdn_cfg_get(0, MISDN_GEN_NTDEBUGFILE, &ntfile, sizeof(ntfile));
+ misdn_cfg_get( 0, MISDN_GEN_NTKEEPCALLS, &ntkc, sizeof(ntkc));
+
+ misdn_lib_nt_keepcalls(ntkc);
+ misdn_lib_nt_debug_init(ntflags, ntfile);
+
+ if (ast_channel_register(&misdn_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class %s\n", misdn_type);
+ unload_module();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ ast_cli_register_multiple(chan_misdn_clis, sizeof(chan_misdn_clis) / sizeof(struct ast_cli_entry));
+
+ ast_register_application("misdn_set_opt", misdn_set_opt_exec, "misdn_set_opt",
+ "misdn_set_opt(:<opt><optarg>:<opt><optarg>..):\n"
+ "Sets mISDN opts. and optargs\n"
+ "\n"
+ "The available options are:\n"
+ " d - Send display text on called phone, text is the optparam\n"
+ " n - don't detect dtmf tones on called channel\n"
+ " h - make digital outgoing call\n"
+ " c - make crypted outgoing call, param is keyindex\n"
+ " e - perform echo cancelation on this channel,\n"
+ " takes taps as arguments (32,64,128,256)\n"
+ " s - send Non Inband DTMF as inband\n"
+ " vr - rxgain control\n"
+ " vt - txgain control\n"
+ " i - Ignore detected dtmf tones, don't signal them to asterisk, they will be transported inband.\n"
+ );
+
+
+ ast_register_application("misdn_facility", misdn_facility_exec, "misdn_facility",
+ "misdn_facility(<FACILITY_TYPE>|<ARG1>|..)\n"
+ "Sends the Facility Message FACILITY_TYPE with \n"
+ "the given Arguments to the current ISDN Channel\n"
+ "Supported Facilities are:\n"
+ "\n"
+ "type=calldeflect args=Nr where to deflect\n"
+ );
+
+
+ ast_register_application("misdn_check_l2l1", misdn_check_l2l1, "misdn_check_l2l1",
+ "misdn_check_l2l1(<port>||g:<groupname>,timeout)"
+ "Checks if the L2 and L1 are up on either the given <port> or\n"
+ "on the ports in the group with <groupname>\n"
+ "If the L1/L2 are down, check_l2l1 gets up the L1/L2 and waits\n"
+ "for <timeout> seconds that this happens. Otherwise, nothing happens\n"
+ "\n"
+ "This application, ensures the L1/L2 state of the Ports in a group\n"
+ "it is intended to make the pmp_l1_check option redundant and to\n"
+ "fix a buggy switch config from your provider\n"
+ "\n"
+ "a sample dialplan would look like:\n\n"
+ "exten => _X.,1,misdn_check_l2l1(g:out|2)\n"
+ "exten => _X.,n,dial(mISDN/g:out/${EXTEN})\n"
+ "\n"
+ );
+
+
+ misdn_cfg_get(0, MISDN_GEN_TRACEFILE, global_tracefile, sizeof(global_tracefile));
+
+ /* start the l1 watchers */
+
+ for (port = misdn_cfg_get_next_port(0); port >= 0; port = misdn_cfg_get_next_port(port)) {
+ int l1timeout;
+ misdn_cfg_get(port, MISDN_CFG_L1_TIMEOUT, &l1timeout, sizeof(l1timeout));
+ if (l1timeout) {
+ chan_misdn_log(4, 0, "Adding L1watcher task: port:%d timeout:%ds\n", port, l1timeout);
+ misdn_tasks_add(l1timeout * 1000, misdn_l1_task, &misdn_ports[port]);
+ }
+ }
+
+ chan_misdn_log(0, 0, "-- mISDN Channel Driver Registered --\n");
+
+ return 0;
+}
+
+
+
+static int reload(void)
+{
+ reload_config();
+
+ return 0;
+}
+
+/*** SOME APPS ;)***/
+
+static int misdn_facility_exec(struct ast_channel *chan, void *data)
+{
+ struct chan_list *ch = MISDN_ASTERISK_TECH_PVT(chan);
+ char *parse, *tok, *tokb;
+
+ chan_misdn_log(0, 0, "TYPE: %s\n", chan->tech->type);
+
+ if (strcasecmp(chan->tech->type, "mISDN")) {
+ ast_log(LOG_WARNING, "misdn_facility makes only sense with chan_misdn channels!\n");
+ return -1;
+ }
+
+ if (ast_strlen_zero((char *)data)) {
+ ast_log(LOG_WARNING, "misdn_facility Requires arguments\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+ tok = strtok_r(parse, "|", &tokb) ;
+
+ if (!tok) {
+ ast_log(LOG_WARNING, "misdn_facility Requires arguments\n");
+ return -1;
+ }
+
+ if (!strcasecmp(tok, "calldeflect")) {
+ tok = strtok_r(NULL, "|", &tokb) ;
+
+ if (!tok) {
+ ast_log(LOG_WARNING, "Facility: Call Defl Requires arguments\n");
+ }
+
+ if (strlen(tok) >= sizeof(ch->bc->fac_out.u.CDeflection.DeflectedToNumber)) {
+ ast_log(LOG_WARNING, "Facility: Number argument too long (up to 15 digits are allowed). Ignoring.\n");
+ return 0;
+ }
+ ch->bc->fac_out.Function = Fac_CD;
+ ast_copy_string((char *)ch->bc->fac_out.u.CDeflection.DeflectedToNumber, tok, sizeof(ch->bc->fac_out.u.CDeflection.DeflectedToNumber));
+ misdn_lib_send_event(ch->bc, EVENT_FACILITY);
+ } else {
+ chan_misdn_log(1, ch->bc->port, "Unknown Facility: %s\n", tok);
+ }
+
+ return 0;
+}
+
+static int misdn_check_l2l1(struct ast_channel *chan, void *data)
+{
+ char *parse;
+ char group[BUFFERSIZE + 1];
+ char *port_str;
+ int port = 0;
+ int timeout;
+ int dowait = 0;
+ int port_up;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(grouppar);
+ AST_APP_ARG(timeout);
+ );
+
+ if (ast_strlen_zero((char *)data)) {
+ ast_log(LOG_WARNING, "misdn_check_l2l1 Requires arguments\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (args.argc != 2) {
+ ast_log(LOG_WARNING, "Wrong argument count\n");
+ return 0;
+ }
+
+ /*ast_log(LOG_NOTICE, "Arguments: group/port '%s' timeout '%s'\n", args.grouppar, args.timeout);*/
+ timeout = atoi(args.timeout);
+ port_str = args.grouppar;
+
+ if (port_str[0] == 'g' && port_str[1] == ':' ) {
+ /* We make a group call lets checkout which ports are in my group */
+ port_str += 2;
+ ast_copy_string(group, port_str, sizeof(group));
+ chan_misdn_log(2, 0, "Checking Ports in group: %s\n", group);
+
+ for ( port = misdn_cfg_get_next_port(port);
+ port > 0;
+ port = misdn_cfg_get_next_port(port)) {
+ char cfg_group[BUFFERSIZE + 1];
+
+ chan_misdn_log(2, 0, "trying port %d\n", port);
+
+ misdn_cfg_get(port, MISDN_CFG_GROUPNAME, cfg_group, sizeof(cfg_group));
+
+ if (!strcasecmp(cfg_group, group)) {
+ port_up = misdn_lib_port_up(port, 1);
+
+ if (!port_up) {
+ chan_misdn_log(2, 0, " --> port '%d'\n", port);
+ misdn_lib_get_port_up(port);
+ dowait = 1;
+ }
+ }
+ }
+
+ } else {
+ port = atoi(port_str);
+ chan_misdn_log(2, 0, "Checking Port: %d\n",port);
+ port_up = misdn_lib_port_up(port, 1);
+ if (!port_up) {
+ misdn_lib_get_port_up(port);
+ dowait = 1;
+ }
+ }
+
+ if (dowait) {
+ chan_misdn_log(2, 0, "Waiting for '%d' seconds\n", timeout);
+ ast_safe_sleep(chan, timeout * 1000);
+ }
+
+ return 0;
+}
+
+static int misdn_set_opt_exec(struct ast_channel *chan, void *data)
+{
+ struct chan_list *ch = MISDN_ASTERISK_TECH_PVT(chan);
+ char *tok, *tokb, *parse;
+ int keyidx = 0;
+ int rxgain = 0;
+ int txgain = 0;
+ int change_jitter = 0;
+
+ if (strcasecmp(chan->tech->type, "mISDN")) {
+ ast_log(LOG_WARNING, "misdn_set_opt makes only sense with chan_misdn channels!\n");
+ return -1;
+ }
+
+ if (ast_strlen_zero((char *)data)) {
+ ast_log(LOG_WARNING, "misdn_set_opt Requires arguments\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+ for (tok = strtok_r(parse, ":", &tokb);
+ tok;
+ tok = strtok_r(NULL, ":", &tokb) ) {
+ int neglect = 0;
+
+ if (tok[0] == '!' ) {
+ neglect = 1;
+ tok++;
+ }
+
+ switch(tok[0]) {
+
+ case 'd' :
+ ast_copy_string(ch->bc->display, ++tok, sizeof(ch->bc->display));
+ chan_misdn_log(1, ch->bc->port, "SETOPT: Display:%s\n", ch->bc->display);
+ break;
+
+ case 'n':
+ chan_misdn_log(1, ch->bc->port, "SETOPT: No DSP\n");
+ ch->bc->nodsp = 1;
+ break;
+
+ case 'j':
+ chan_misdn_log(1, ch->bc->port, "SETOPT: jitter\n");
+ tok++;
+ change_jitter = 1;
+
+ switch ( tok[0] ) {
+ case 'b':
+ ch->jb_len = atoi(++tok);
+ chan_misdn_log(1, ch->bc->port, " --> buffer_len:%d\n", ch->jb_len);
+ break;
+ case 't' :
+ ch->jb_upper_threshold = atoi(++tok);
+ chan_misdn_log(1, ch->bc->port, " --> upper_threshold:%d\n", ch->jb_upper_threshold);
+ break;
+ case 'n':
+ ch->bc->nojitter = 1;
+ chan_misdn_log(1, ch->bc->port, " --> nojitter\n");
+ break;
+ default:
+ ch->jb_len = 4000;
+ ch->jb_upper_threshold = 0;
+ chan_misdn_log(1, ch->bc->port, " --> buffer_len:%d (default)\n", ch->jb_len);
+ chan_misdn_log(1, ch->bc->port, " --> upper_threshold:%d (default)\n", ch->jb_upper_threshold);
+ }
+ break;
+ case 'v':
+ tok++;
+
+ switch (tok[0]) {
+ case 'r' :
+ rxgain = atoi(++tok);
+ if (rxgain < -8)
+ rxgain = -8;
+ if (rxgain > 8)
+ rxgain = 8;
+ ch->bc->rxgain = rxgain;
+ chan_misdn_log(1, ch->bc->port, "SETOPT: Volume:%d\n", rxgain);
+ break;
+ case 't':
+ txgain = atoi(++tok);
+ if (txgain < -8)
+ txgain = -8;
+ if (txgain > 8)
+ txgain = 8;
+ ch->bc->txgain = txgain;
+ chan_misdn_log(1, ch->bc->port, "SETOPT: Volume:%d\n", txgain);
+ break;
+ }
+ break;
+
+ case 'c':
+ keyidx = atoi(++tok);
+ {
+ char keys[4096];
+ char *key = NULL, *tmp = keys;
+ int i;
+ misdn_cfg_get(0, MISDN_GEN_CRYPT_KEYS, keys, sizeof(keys));
+
+ for (i = 0; i < keyidx; i++) {
+ key = strsep(&tmp, ",");
+ }
+
+ if (key) {
+ ast_copy_string(ch->bc->crypt_key, key, sizeof(ch->bc->crypt_key));
+ }
+
+ chan_misdn_log(0, ch->bc->port, "SETOPT: crypt with key:%s\n", ch->bc->crypt_key);
+ break;
+ }
+ case 'e':
+ chan_misdn_log(1, ch->bc->port, "SETOPT: EchoCancel\n");
+
+ if (neglect) {
+ chan_misdn_log(1, ch->bc->port, " --> disabled\n");
+#ifdef MISDN_1_2
+ *ch->bc->pipeline = 0;
+#else
+ ch->bc->ec_enable = 0;
+#endif
+ } else {
+#ifdef MISDN_1_2
+ update_pipeline_config(ch->bc);
+#else
+ ch->bc->ec_enable = 1;
+ ch->bc->orig = ch->originator;
+ tok++;
+ if (*tok) {
+ ch->bc->ec_deftaps = atoi(tok);
+ }
+#endif
+ }
+
+ break;
+ case 'h':
+ chan_misdn_log(1, ch->bc->port, "SETOPT: Digital\n");
+
+ if (strlen(tok) > 1 && tok[1] == '1') {
+ chan_misdn_log(1, ch->bc->port, "SETOPT: HDLC \n");
+ if (!ch->bc->hdlc) {
+ ch->bc->hdlc = 1;
+ }
+ }
+ ch->bc->capability = INFO_CAPABILITY_DIGITAL_UNRESTRICTED;
+ break;
+
+ case 's':
+ chan_misdn_log(1, ch->bc->port, "SETOPT: Send DTMF\n");
+ ch->bc->send_dtmf = 1;
+ break;
+
+ case 'f':
+ chan_misdn_log(1, ch->bc->port, "SETOPT: Faxdetect\n");
+ ch->faxdetect = 1;
+ misdn_cfg_get(ch->bc->port, MISDN_CFG_FAXDETECT_TIMEOUT, &ch->faxdetect_timeout, sizeof(ch->faxdetect_timeout));
+ break;
+
+ case 'a':
+ chan_misdn_log(1, ch->bc->port, "SETOPT: AST_DSP (for DTMF)\n");
+ ch->ast_dsp = 1;
+ break;
+
+ case 'p':
+ chan_misdn_log(1, ch->bc->port, "SETOPT: callerpres: %s\n", &tok[1]);
+ /* CRICH: callingpres!!! */
+ if (strstr(tok,"allowed")) {
+ ch->bc->pres = 0;
+ } else if (strstr(tok, "not_screened")) {
+ ch->bc->pres = 1;
+ }
+ break;
+ case 'i' :
+ chan_misdn_log(1, ch->bc->port, "Ignoring dtmf tones, just use them inband\n");
+ ch->ignore_dtmf=1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (change_jitter)
+ config_jitterbuffer(ch);
+
+ if (ch->faxdetect || ch->ast_dsp) {
+ if (!ch->dsp)
+ ch->dsp = ast_dsp_new();
+ if (ch->dsp)
+ ast_dsp_set_features(ch->dsp, DSP_FEATURE_DTMF_DETECT | DSP_FEATURE_FAX_DETECT);
+ if (!ch->trans)
+ ch->trans = ast_translator_build_path(AST_FORMAT_SLINEAR, AST_FORMAT_ALAW);
+ }
+
+ if (ch->ast_dsp) {
+ chan_misdn_log(1, ch->bc->port, "SETOPT: with AST_DSP we deactivate mISDN_dsp\n");
+ ch->bc->nodsp = 1;
+ ch->bc->nojitter = 1;
+ }
+
+ return 0;
+}
+
+
+int chan_misdn_jb_empty ( struct misdn_bchannel *bc, char *buf, int len)
+{
+ struct chan_list *ch = find_chan_by_bc(cl_te, bc);
+
+ if (ch && ch->jb) {
+ return misdn_jb_empty(ch->jb, buf, len);
+ }
+
+ return -1;
+}
+
+
+
+/*******************************************************/
+/***************** JITTERBUFFER ************************/
+/*******************************************************/
+
+
+/* allocates the jb-structure and initialise the elements*/
+struct misdn_jb *misdn_jb_init(int size, int upper_threshold)
+{
+ int i;
+ struct misdn_jb *jb;
+
+ jb = ast_malloc(sizeof(*jb));
+ if (!jb) {
+ chan_misdn_log(-1, 0, "No free Mem for jb\n");
+ return NULL;
+ }
+ jb->size = size;
+ jb->upper_threshold = upper_threshold;
+ jb->wp = 0;
+ jb->rp = 0;
+ jb->state_full = 0;
+ jb->state_empty = 0;
+ jb->bytes_wrote = 0;
+ jb->samples = ast_malloc(size * sizeof(char));
+
+ if (!jb->samples) {
+ ast_free(jb);
+ chan_misdn_log(-1, 0, "No free Mem for jb->samples\n");
+ return NULL;
+ }
+
+ jb->ok = ast_malloc(size * sizeof(char));
+
+ if (!jb->ok) {
+ ast_free(jb->samples);
+ ast_free(jb);
+ chan_misdn_log(-1, 0, "No free Mem for jb->ok\n");
+ return NULL;
+ }
+
+ for (i = 0; i < size; i++)
+ jb->ok[i] = 0;
+
+ ast_mutex_init(&jb->mutexjb);
+
+ return jb;
+}
+
+/* frees the data and destroys the given jitterbuffer struct */
+void misdn_jb_destroy(struct misdn_jb *jb)
+{
+ ast_mutex_destroy(&jb->mutexjb);
+
+ ast_free(jb->samples);
+ ast_free(jb);
+}
+
+/* fills the jitterbuffer with len data returns < 0 if there was an
+ error (bufferoverflow). */
+int misdn_jb_fill(struct misdn_jb *jb, const char *data, int len)
+{
+ int i, j, rp, wp;
+
+ if (!jb || ! data)
+ return 0;
+
+ ast_mutex_lock(&jb->mutexjb);
+
+ wp = jb->wp;
+ rp = jb->rp;
+
+ for (i = 0; i < len; i++) {
+ jb->samples[wp] = data[i];
+ jb->ok[wp] = 1;
+ wp = (wp != jb->size - 1) ? wp + 1 : 0;
+
+ if (wp == jb->rp)
+ jb->state_full = 1;
+ }
+
+ if (wp >= rp)
+ jb->state_buffer = wp - rp;
+ else
+ jb->state_buffer = jb->size - rp + wp;
+ chan_misdn_log(9, 0, "misdn_jb_fill: written:%d | Bufferstatus:%d p:%x\n", len, jb->state_buffer, jb);
+
+ if (jb->state_full) {
+ jb->wp = wp;
+
+ rp = wp;
+ for (j = 0; j < jb->upper_threshold; j++)
+ rp = rp != 0 ? rp - 1 : jb->size - 1;
+ jb->rp = rp;
+ jb->state_full = 0;
+ jb->state_empty = 1;
+
+ ast_mutex_unlock(&jb->mutexjb);
+
+ return -1;
+ }
+
+ if (!jb->state_empty) {
+ jb->bytes_wrote += len;
+ if (jb->bytes_wrote >= jb->upper_threshold) {
+ jb->state_empty = 1;
+ jb->bytes_wrote = 0;
+ }
+ }
+ jb->wp = wp;
+
+ ast_mutex_unlock(&jb->mutexjb);
+
+ return 0;
+}
+
+/* gets len bytes out of the jitterbuffer if available, else only the
+available data is returned and the return value indicates the number
+of data. */
+int misdn_jb_empty(struct misdn_jb *jb, char *data, int len)
+{
+ int i, wp, rp, read = 0;
+
+ ast_mutex_lock(&jb->mutexjb);
+
+ rp = jb->rp;
+ wp = jb->wp;
+
+ if (jb->state_empty) {
+ for (i = 0; i < len; i++) {
+ if (wp == rp) {
+ jb->rp = rp;
+ jb->state_empty = 0;
+
+ ast_mutex_unlock(&jb->mutexjb);
+
+ return read;
+ } else {
+ if (jb->ok[rp] == 1) {
+ data[i] = jb->samples[rp];
+ jb->ok[rp] = 0;
+ rp = (rp != jb->size - 1) ? rp + 1 : 0;
+ read += 1;
+ }
+ }
+ }
+
+ if (wp >= rp)
+ jb->state_buffer = wp - rp;
+ else
+ jb->state_buffer = jb->size - rp + wp;
+ chan_misdn_log(9, 0, "misdn_jb_empty: read:%d | Bufferstatus:%d p:%x\n", len, jb->state_buffer, jb);
+
+ jb->rp = rp;
+ } else
+ chan_misdn_log(9, 0, "misdn_jb_empty: Wait...requested:%d p:%x\n", len, jb);
+
+ ast_mutex_unlock(&jb->mutexjb);
+
+ return read;
+}
+
+
+
+
+/*******************************************************/
+/*************** JITTERBUFFER END *********************/
+/*******************************************************/
+
+
+
+
+void chan_misdn_log(int level, int port, char *tmpl, ...)
+{
+ va_list ap;
+ char buf[1024];
+ char port_buf[8];
+
+ if (! ((0 <= port) && (port <= max_ports))) {
+ ast_log(LOG_WARNING, "cb_log called with out-of-range port number! (%d)\n", port);
+ port = 0;
+ level = -1;
+ }
+
+ snprintf(port_buf, sizeof(port_buf), "P[%2d] ", port);
+
+ va_start(ap, tmpl);
+ vsnprintf(buf, sizeof(buf), tmpl, ap);
+ va_end(ap);
+
+ if (level == -1)
+ ast_log(LOG_WARNING, buf);
+
+ else if (misdn_debug_only[port] ?
+ (level == 1 && misdn_debug[port]) || (level == misdn_debug[port])
+ : level <= misdn_debug[port]) {
+
+ ast_console_puts(port_buf);
+ ast_console_puts(buf);
+ }
+
+ if ((level <= misdn_debug[0]) && !ast_strlen_zero(global_tracefile) ) {
+ char ctimebuf[30];
+ time_t tm = time(NULL);
+ char *tmp = ctime_r(&tm, ctimebuf), *p;
+
+ FILE *fp = fopen(global_tracefile, "a+");
+
+ p = strchr(tmp, '\n');
+ if (p)
+ *p = ':';
+
+ if (!fp) {
+ ast_console_puts("Error opening Tracefile: [ ");
+ ast_console_puts(global_tracefile);
+ ast_console_puts(" ] ");
+
+ ast_console_puts(strerror(errno));
+ ast_console_puts("\n");
+ return ;
+ }
+
+ fputs(tmp, fp);
+ fputs(" ", fp);
+ fputs(port_buf, fp);
+ fputs(" ", fp);
+ fputs(buf, fp);
+
+ fclose(fp);
+ }
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Channel driver for mISDN Support (BRI/PRI)",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+);
diff --git a/trunk/channels/chan_nbs.c b/trunk/channels/chan_nbs.c
new file mode 100644
index 000000000..e190a436c
--- /dev/null
+++ b/trunk/channels/chan_nbs.c
@@ -0,0 +1,292 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Network broadcast sound support channel driver
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <depend>nbs</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <nbs.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+
+static const char tdesc[] = "Network Broadcast Sound Driver";
+
+/* Only linear is allowed */
+static int prefformat = AST_FORMAT_SLINEAR;
+
+static char context[AST_MAX_EXTENSION] = "default";
+static char type[] = "NBS";
+
+/* NBS creates private structures on demand */
+
+struct nbs_pvt {
+ NBS *nbs;
+ struct ast_channel *owner; /* Channel we belong to, possibly NULL */
+ char app[16]; /* Our app */
+ char stream[80]; /* Our stream */
+ struct ast_frame fr; /* "null" frame */
+ struct ast_module_user *u; /*! for holding a reference to this module */
+};
+
+static struct ast_channel *nbs_request(const char *type, int format, void *data, int *cause);
+static int nbs_call(struct ast_channel *ast, char *dest, int timeout);
+static int nbs_hangup(struct ast_channel *ast);
+static struct ast_frame *nbs_xread(struct ast_channel *ast);
+static int nbs_xwrite(struct ast_channel *ast, struct ast_frame *frame);
+
+static const struct ast_channel_tech nbs_tech = {
+ .type = type,
+ .description = tdesc,
+ .capabilities = AST_FORMAT_SLINEAR,
+ .requester = nbs_request,
+ .call = nbs_call,
+ .hangup = nbs_hangup,
+ .read = nbs_xread,
+ .write = nbs_xwrite,
+};
+
+static int nbs_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ struct nbs_pvt *p;
+
+ p = ast->tech_pvt;
+
+ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "nbs_call called on %s, neither down nor reserved\n", ast->name);
+ return -1;
+ }
+ /* When we call, it just works, really, there's no destination... Just
+ ring the phone and wait for someone to answer */
+ ast_debug(1, "Calling %s on %s\n", dest, ast->name);
+
+ /* If we can't connect, return congestion */
+ if (nbs_connect(p->nbs)) {
+ ast_log(LOG_WARNING, "NBS Connection failed on %s\n", ast->name);
+ ast_queue_control(ast, AST_CONTROL_CONGESTION);
+ } else {
+ ast_setstate(ast, AST_STATE_RINGING);
+ ast_queue_control(ast, AST_CONTROL_ANSWER);
+ }
+
+ return 0;
+}
+
+static void nbs_destroy(struct nbs_pvt *p)
+{
+ if (p->nbs)
+ nbs_delstream(p->nbs);
+ ast_module_user_remove(p->u);
+ ast_free(p);
+}
+
+static struct nbs_pvt *nbs_alloc(void *data)
+{
+ struct nbs_pvt *p;
+ int flags = 0;
+ char stream[256];
+ char *opts;
+
+ ast_copy_string(stream, data, sizeof(stream));
+ if ((opts = strchr(stream, ':'))) {
+ *opts = '\0';
+ opts++;
+ } else
+ opts = "";
+ p = ast_calloc(1, sizeof(*p));
+ if (p) {
+ if (!ast_strlen_zero(opts)) {
+ if (strchr(opts, 'm'))
+ flags |= NBS_FLAG_MUTE;
+ if (strchr(opts, 'o'))
+ flags |= NBS_FLAG_OVERSPEAK;
+ if (strchr(opts, 'e'))
+ flags |= NBS_FLAG_EMERGENCY;
+ if (strchr(opts, 'O'))
+ flags |= NBS_FLAG_OVERRIDE;
+ } else
+ flags = NBS_FLAG_OVERSPEAK;
+
+ ast_copy_string(p->stream, stream, sizeof(p->stream));
+ p->nbs = nbs_newstream("asterisk", stream, flags);
+ if (!p->nbs) {
+ ast_log(LOG_WARNING, "Unable to allocate new NBS stream '%s' with flags %d\n", stream, flags);
+ ast_free(p);
+ p = NULL;
+ } else {
+ /* Set for 8000 hz mono, 640 samples */
+ nbs_setbitrate(p->nbs, 8000);
+ nbs_setchannels(p->nbs, 1);
+ nbs_setblocksize(p->nbs, 640);
+ nbs_setblocking(p->nbs, 0);
+ }
+ }
+ return p;
+}
+
+static int nbs_hangup(struct ast_channel *ast)
+{
+ struct nbs_pvt *p;
+ p = ast->tech_pvt;
+ ast_debug(1, "nbs_hangup(%s)\n", ast->name);
+ if (!ast->tech_pvt) {
+ ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
+ return 0;
+ }
+ nbs_destroy(p);
+ ast->tech_pvt = NULL;
+ ast_setstate(ast, AST_STATE_DOWN);
+ return 0;
+}
+
+static struct ast_frame *nbs_xread(struct ast_channel *ast)
+{
+ struct nbs_pvt *p = ast->tech_pvt;
+
+
+ /* Some nice norms */
+ p->fr.datalen = 0;
+ p->fr.samples = 0;
+ p->fr.data = NULL;
+ p->fr.src = type;
+ p->fr.offset = 0;
+ p->fr.mallocd=0;
+ p->fr.delivery.tv_sec = 0;
+ p->fr.delivery.tv_usec = 0;
+
+ ast_debug(1, "Returning null frame on %s\n", ast->name);
+
+ return &p->fr;
+}
+
+static int nbs_xwrite(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct nbs_pvt *p = ast->tech_pvt;
+ /* Write a frame of (presumably voice) data */
+ if (frame->frametype != AST_FRAME_VOICE) {
+ if (frame->frametype != AST_FRAME_IMAGE)
+ ast_log(LOG_WARNING, "Don't know what to do with frame type '%d'\n", frame->frametype);
+ return 0;
+ }
+ if (!(frame->subclass &
+ (AST_FORMAT_SLINEAR))) {
+ ast_log(LOG_WARNING, "Cannot handle frames in %d format\n", frame->subclass);
+ return 0;
+ }
+ if (ast->_state != AST_STATE_UP) {
+ /* Don't try tos end audio on-hook */
+ return 0;
+ }
+ if (nbs_write(p->nbs, frame->data, frame->datalen / 2) < 0)
+ return -1;
+ return 0;
+}
+
+static struct ast_channel *nbs_new(struct nbs_pvt *i, int state)
+{
+ struct ast_channel *tmp;
+ tmp = ast_channel_alloc(1, state, 0, 0, "", "s", context, 0, "NBS/%s", i->stream);
+ if (tmp) {
+ tmp->tech = &nbs_tech;
+ ast_channel_set_fd(tmp, 0, nbs_fd(i->nbs));
+ tmp->nativeformats = prefformat;
+ tmp->rawreadformat = prefformat;
+ tmp->rawwriteformat = prefformat;
+ tmp->writeformat = prefformat;
+ tmp->readformat = prefformat;
+ if (state == AST_STATE_RING)
+ tmp->rings = 1;
+ tmp->tech_pvt = i;
+ ast_copy_string(tmp->context, context, sizeof(tmp->context));
+ ast_copy_string(tmp->exten, "s", sizeof(tmp->exten));
+ ast_string_field_set(tmp, language, "");
+ i->owner = tmp;
+ i->u = ast_module_user_add(tmp);
+ if (state != AST_STATE_DOWN) {
+ if (ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ ast_hangup(tmp);
+ }
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+ return tmp;
+}
+
+
+static struct ast_channel *nbs_request(const char *type, int format, void *data, int *cause)
+{
+ int oldformat;
+ struct nbs_pvt *p;
+ struct ast_channel *tmp = NULL;
+
+ oldformat = format;
+ format &= (AST_FORMAT_SLINEAR);
+ if (!format) {
+ ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", oldformat);
+ return NULL;
+ }
+ p = nbs_alloc(data);
+ if (p) {
+ tmp = nbs_new(p, AST_STATE_DOWN);
+ if (!tmp)
+ nbs_destroy(p);
+ }
+ return tmp;
+}
+
+static int unload_module(void)
+{
+ /* First, take us out of the channel loop */
+ ast_channel_unregister(&nbs_tech);
+ return 0;
+}
+
+static int load_module(void)
+{
+ /* Make sure we can register our channel type */
+ if (ast_channel_register(&nbs_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class %s\n", type);
+ return -1;
+ }
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Network Broadcast Sound Support");
diff --git a/trunk/channels/chan_oss.c b/trunk/channels/chan_oss.c
new file mode 100644
index 000000000..4f40085fa
--- /dev/null
+++ b/trunk/channels/chan_oss.c
@@ -0,0 +1,1470 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2007, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * FreeBSD changes and multiple device support by Luigi Rizzo, 2005.05.25
+ * note-this code best seen with ts=8 (8-spaces tabs) in the editor
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+// #define HAVE_VIDEO_CONSOLE // uncomment to enable video
+/*! \file
+ *
+ * \brief Channel driver for OSS sound cards
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * \author Luigi Rizzo
+ *
+ * \par See also
+ * \arg \ref Config_oss
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <depend>ossaudio</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <ctype.h> /* isalnum() used here */
+#include <math.h>
+#include <sys/ioctl.h>
+
+#ifdef __linux
+#include <linux/soundcard.h>
+#elif defined(__FreeBSD__) || defined(__CYGWIN__)
+#include <sys/soundcard.h>
+#else
+#include <soundcard.h>
+#endif
+
+#include "asterisk/channel.h"
+#include "asterisk/file.h"
+#include "asterisk/callerid.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/cli.h"
+#include "asterisk/causes.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/app.h"
+
+#include "console_video.h"
+
+/*! Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = "",
+};
+static struct ast_jb_conf global_jbconf;
+
+/*
+ * Basic mode of operation:
+ *
+ * we have one keyboard (which receives commands from the keyboard)
+ * and multiple headset's connected to audio cards.
+ * Cards/Headsets are named as the sections of oss.conf.
+ * The section called [general] contains the default parameters.
+ *
+ * At any time, the keyboard is attached to one card, and you
+ * can switch among them using the command 'console foo'
+ * where 'foo' is the name of the card you want.
+ *
+ * oss.conf parameters are
+START_CONFIG
+
+[general]
+ ; General config options, with default values shown.
+ ; You should use one section per device, with [general] being used
+ ; for the first device and also as a template for other devices.
+ ;
+ ; All but 'debug' can go also in the device-specific sections.
+ ;
+ ; debug = 0x0 ; misc debug flags, default is 0
+
+ ; Set the device to use for I/O
+ ; device = /dev/dsp
+
+ ; Optional mixer command to run upon startup (e.g. to set
+ ; volume levels, mutes, etc.
+ ; mixer =
+
+ ; Software mic volume booster (or attenuator), useful for sound
+ ; cards or microphones with poor sensitivity. The volume level
+ ; is in dB, ranging from -20.0 to +20.0
+ ; boost = n ; mic volume boost in dB
+
+ ; Set the callerid for outgoing calls
+ ; callerid = John Doe <555-1234>
+
+ ; autoanswer = no ; no autoanswer on call
+ ; autohangup = yes ; hangup when other party closes
+ ; extension = s ; default extension to call
+ ; context = default ; default context for outgoing calls
+ ; language = "" ; default language
+
+ ; Default Music on Hold class to use when this channel is placed on hold in
+ ; the case that the music class is not set on the channel with
+ ; Set(CHANNEL(musicclass)=whatever) in the dialplan and the peer channel
+ ; putting this one on hold did not suggest a class to use.
+ ;
+ ; mohinterpret=default
+
+ ; If you set overridecontext to 'yes', then the whole dial string
+ ; will be interpreted as an extension, which is extremely useful
+ ; to dial SIP, IAX and other extensions which use the '@' character.
+ ; The default is 'no' just for backward compatibility, but the
+ ; suggestion is to change it.
+ ; overridecontext = no ; if 'no', the last @ will start the context
+ ; if 'yes' the whole string is an extension.
+
+ ; low level device parameters in case you have problems with the
+ ; device driver on your operating system. You should not touch these
+ ; unless you know what you are doing.
+ ; queuesize = 10 ; frames in device driver
+ ; frags = 8 ; argument to SETFRAGMENT
+
+ ;------------------------------ JITTER BUFFER CONFIGURATION --------------------------
+ ; jbenable = yes ; Enables the use of a jitterbuffer on the receiving side of an
+ ; OSS channel. Defaults to "no". An enabled jitterbuffer will
+ ; be used only if the sending side can create and the receiving
+ ; side can not accept jitter. The OSS channel can't accept jitter,
+ ; thus an enabled jitterbuffer on the receive OSS side will always
+ ; be used if the sending side can create jitter.
+
+ ; jbmaxsize = 200 ; Max length of the jitterbuffer in milliseconds.
+
+ ; jbresyncthreshold = 1000 ; Jump in the frame timestamps over which the jitterbuffer is
+ ; resynchronized. Useful to improve the quality of the voice, with
+ ; big jumps in/broken timestamps, usualy sent from exotic devices
+ ; and programs. Defaults to 1000.
+
+ ; jbimpl = fixed ; Jitterbuffer implementation, used on the receiving side of an OSS
+ ; channel. Two implementations are currenlty available - "fixed"
+ ; (with size always equals to jbmax-size) and "adaptive" (with
+ ; variable size, actually the new jb of IAX2). Defaults to fixed.
+
+ ; jblog = no ; Enables jitterbuffer frame logging. Defaults to "no".
+ ;-----------------------------------------------------------------------------------
+
+[card1]
+ ; device = /dev/dsp1 ; alternate device
+
+END_CONFIG
+
+.. and so on for the other cards.
+
+ */
+
+/*
+ * The following parameters are used in the driver:
+ *
+ * FRAME_SIZE the size of an audio frame, in samples.
+ * 160 is used almost universally, so you should not change it.
+ *
+ * FRAGS the argument for the SETFRAGMENT ioctl.
+ * Overridden by the 'frags' parameter in oss.conf
+ *
+ * Bits 0-7 are the base-2 log of the device's block size,
+ * bits 16-31 are the number of blocks in the driver's queue.
+ * There are a lot of differences in the way this parameter
+ * is supported by different drivers, so you may need to
+ * experiment a bit with the value.
+ * A good default for linux is 30 blocks of 64 bytes, which
+ * results in 6 frames of 320 bytes (160 samples).
+ * FreeBSD works decently with blocks of 256 or 512 bytes,
+ * leaving the number unspecified.
+ * Note that this only refers to the device buffer size,
+ * this module will then try to keep the lenght of audio
+ * buffered within small constraints.
+ *
+ * QUEUE_SIZE The max number of blocks actually allowed in the device
+ * driver's buffer, irrespective of the available number.
+ * Overridden by the 'queuesize' parameter in oss.conf
+ *
+ * Should be >=2, and at most as large as the hw queue above
+ * (otherwise it will never be full).
+ */
+
+#define FRAME_SIZE 160
+#define QUEUE_SIZE 10
+
+#if defined(__FreeBSD__)
+#define FRAGS 0x8
+#else
+#define FRAGS ( ( (6 * 5) << 16 ) | 0x6 )
+#endif
+
+/*
+ * XXX text message sizes are probably 256 chars, but i am
+ * not sure if there is a suitable definition anywhere.
+ */
+#define TEXT_SIZE 256
+
+#if 0
+#define TRYOPEN 1 /* try to open on startup */
+#endif
+#define O_CLOSE 0x444 /* special 'close' mode for device */
+/* Which device to use */
+#if defined( __OpenBSD__ ) || defined( __NetBSD__ )
+#define DEV_DSP "/dev/audio"
+#else
+#define DEV_DSP "/dev/dsp"
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+static char *config = "oss.conf"; /* default config file */
+
+static int oss_debug;
+
+/*!
+ * \brief descriptor for one of our channels.
+ *
+ * There is one used for 'default' values (from the [general] entry in
+ * the configuration file), and then one instance for each device
+ * (the default is cloned from [general], others are only created
+ * if the relevant section exists).
+ */
+struct chan_oss_pvt {
+ struct chan_oss_pvt *next;
+
+ char *name;
+ int total_blocks; /*!< total blocks in the output device */
+ int sounddev;
+ enum { M_UNSET, M_FULL, M_READ, M_WRITE } duplex;
+ int autoanswer;
+ int autohangup;
+ int hookstate;
+ char *mixer_cmd; /*!< initial command to issue to the mixer */
+ unsigned int queuesize; /*!< max fragments in queue */
+ unsigned int frags; /*!< parameter for SETFRAGMENT */
+
+ int warned; /*!< various flags used for warnings */
+#define WARN_used_blocks 1
+#define WARN_speed 2
+#define WARN_frag 4
+ int w_errors; /*!< overfull in the write path */
+ struct timeval lastopen;
+
+ int overridecontext;
+ int mute;
+
+ /*! boost support. BOOST_SCALE * 10 ^(BOOST_MAX/20) must
+ * be representable in 16 bits to avoid overflows.
+ */
+#define BOOST_SCALE (1<<9)
+#define BOOST_MAX 40 /*!< slightly less than 7 bits */
+ int boost; /*!< input boost, scaled by BOOST_SCALE */
+ char device[64]; /*!< device to open */
+
+ pthread_t sthread;
+
+ struct ast_channel *owner;
+
+ struct video_desc *env; /*!< parameters for video support */
+
+ char ext[AST_MAX_EXTENSION];
+ char ctx[AST_MAX_CONTEXT];
+ char language[MAX_LANGUAGE];
+ char cid_name[256]; /*XXX */
+ char cid_num[256]; /*XXX */
+ char mohinterpret[MAX_MUSICCLASS];
+
+ /*! buffers used in oss_write */
+ char oss_write_buf[FRAME_SIZE * 2];
+ int oss_write_dst;
+ /*! buffers used in oss_read - AST_FRIENDLY_OFFSET space for headers
+ * plus enough room for a full frame
+ */
+ char oss_read_buf[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET];
+ int readpos; /*!< read position above */
+ struct ast_frame read_f; /*!< returned by oss_read */
+};
+
+/*! forward declaration */
+static struct chan_oss_pvt *find_desc(char *dev);
+
+static char *oss_active; /*!< the active device */
+
+/*! \brief return the pointer to the video descriptor */
+struct video_desc *get_video_desc(struct ast_channel *c)
+{
+ struct chan_oss_pvt *o = c ? c->tech_pvt : find_desc(oss_active);
+ return o ? o->env : NULL;
+}
+static struct chan_oss_pvt oss_default = {
+ .sounddev = -1,
+ .duplex = M_UNSET, /* XXX check this */
+ .autoanswer = 1,
+ .autohangup = 1,
+ .queuesize = QUEUE_SIZE,
+ .frags = FRAGS,
+ .ext = "s",
+ .ctx = "default",
+ .readpos = AST_FRIENDLY_OFFSET, /* start here on reads */
+ .lastopen = { 0, 0 },
+ .boost = BOOST_SCALE,
+};
+
+
+static int setformat(struct chan_oss_pvt *o, int mode);
+
+static struct ast_channel *oss_request(const char *type, int format, void *data
+, int *cause);
+static int oss_digit_begin(struct ast_channel *c, char digit);
+static int oss_digit_end(struct ast_channel *c, char digit, unsigned int duration);
+static int oss_text(struct ast_channel *c, const char *text);
+static int oss_hangup(struct ast_channel *c);
+static int oss_answer(struct ast_channel *c);
+static struct ast_frame *oss_read(struct ast_channel *chan);
+static int oss_call(struct ast_channel *c, char *dest, int timeout);
+static int oss_write(struct ast_channel *chan, struct ast_frame *f);
+static int oss_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen);
+static int oss_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static char tdesc[] = "OSS Console Channel Driver";
+
+/* cannot do const because need to update some fields at runtime */
+static struct ast_channel_tech oss_tech = {
+ .type = "Console",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_SLINEAR, /* overwritten later */
+ .requester = oss_request,
+ .send_digit_begin = oss_digit_begin,
+ .send_digit_end = oss_digit_end,
+ .send_text = oss_text,
+ .hangup = oss_hangup,
+ .answer = oss_answer,
+ .read = oss_read,
+ .call = oss_call,
+ .write = oss_write,
+ .write_video = console_write_video,
+ .indicate = oss_indicate,
+ .fixup = oss_fixup,
+};
+
+/*!
+ * \brief returns a pointer to the descriptor with the given name
+ */
+static struct chan_oss_pvt *find_desc(char *dev)
+{
+ struct chan_oss_pvt *o = NULL;
+
+ if (!dev)
+ ast_log(LOG_WARNING, "null dev\n");
+
+ for (o = oss_default.next; o && o->name && dev && strcmp(o->name, dev) != 0; o = o->next);
+
+ if (!o)
+ ast_log(LOG_WARNING, "could not find <%s>\n", dev ? dev : "--no-device--");
+
+ return o;
+}
+
+/* !
+ * \brief split a string in extension-context, returns pointers to malloc'ed
+ * strings.
+ *
+ * If we do not have 'overridecontext' then the last @ is considered as
+ * a context separator, and the context is overridden.
+ * This is usually not very necessary as you can play with the dialplan,
+ * and it is nice not to need it because you have '@' in SIP addresses.
+ *
+ * \return the buffer address.
+ */
+static char *ast_ext_ctx(const char *src, char **ext, char **ctx)
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (ext == NULL || ctx == NULL)
+ return NULL; /* error */
+
+ *ext = *ctx = NULL;
+
+ if (src && *src != '\0')
+ *ext = ast_strdup(src);
+
+ if (*ext == NULL)
+ return NULL;
+
+ if (!o->overridecontext) {
+ /* parse from the right */
+ *ctx = strrchr(*ext, '@');
+ if (*ctx)
+ *(*ctx)++ = '\0';
+ }
+
+ return *ext;
+}
+
+/*!
+ * \brief Returns the number of blocks used in the audio output channel
+ */
+static int used_blocks(struct chan_oss_pvt *o)
+{
+ struct audio_buf_info info;
+
+ if (ioctl(o->sounddev, SNDCTL_DSP_GETOSPACE, &info)) {
+ if (!(o->warned & WARN_used_blocks)) {
+ ast_log(LOG_WARNING, "Error reading output space\n");
+ o->warned |= WARN_used_blocks;
+ }
+ return 1;
+ }
+
+ if (o->total_blocks == 0) {
+ if (0) /* debugging */
+ ast_log(LOG_WARNING, "fragtotal %d size %d avail %d\n", info.fragstotal, info.fragsize, info.fragments);
+ o->total_blocks = info.fragments;
+ }
+
+ return o->total_blocks - info.fragments;
+}
+
+/*! Write an exactly FRAME_SIZE sized frame */
+static int soundcard_writeframe(struct chan_oss_pvt *o, short *data)
+{
+ int res;
+
+ if (o->sounddev < 0)
+ setformat(o, O_RDWR);
+ if (o->sounddev < 0)
+ return 0; /* not fatal */
+ /*
+ * Nothing complex to manage the audio device queue.
+ * If the buffer is full just drop the extra, otherwise write.
+ * XXX in some cases it might be useful to write anyways after
+ * a number of failures, to restart the output chain.
+ */
+ res = used_blocks(o);
+ if (res > o->queuesize) { /* no room to write a block */
+ if (o->w_errors++ == 0 && (oss_debug & 0x4))
+ ast_log(LOG_WARNING, "write: used %d blocks (%d)\n", res, o->w_errors);
+ return 0;
+ }
+ o->w_errors = 0;
+ return write(o->sounddev, (void *)data, FRAME_SIZE * 2);
+}
+
+/*!
+ * reset and close the device if opened,
+ * then open and initialize it in the desired mode,
+ * trigger reads and writes so we can start using it.
+ */
+static int setformat(struct chan_oss_pvt *o, int mode)
+{
+ int fmt, desired, res, fd;
+
+ if (o->sounddev >= 0) {
+ ioctl(o->sounddev, SNDCTL_DSP_RESET, 0);
+ close(o->sounddev);
+ o->duplex = M_UNSET;
+ o->sounddev = -1;
+ }
+ if (mode == O_CLOSE) /* we are done */
+ return 0;
+ if (ast_tvdiff_ms(ast_tvnow(), o->lastopen) < 1000)
+ return -1; /* don't open too often */
+ o->lastopen = ast_tvnow();
+ fd = o->sounddev = open(o->device, mode | O_NONBLOCK);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Unable to re-open DSP device %s: %s\n", o->device, strerror(errno));
+ return -1;
+ }
+ if (o->owner)
+ ast_channel_set_fd(o->owner, 0, fd);
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ fmt = AFMT_S16_LE;
+#else
+ fmt = AFMT_S16_BE;
+#endif
+ res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set format to 16-bit signed\n");
+ return -1;
+ }
+ switch (mode) {
+ case O_RDWR:
+ res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
+ /* Check to see if duplex set (FreeBSD Bug) */
+ res = ioctl(fd, SNDCTL_DSP_GETCAPS, &fmt);
+ if (res == 0 && (fmt & DSP_CAP_DUPLEX)) {
+ ast_verb(2, "Console is full duplex\n");
+ o->duplex = M_FULL;
+ };
+ break;
+
+ case O_WRONLY:
+ o->duplex = M_WRITE;
+ break;
+
+ case O_RDONLY:
+ o->duplex = M_READ;
+ break;
+ }
+
+ fmt = 0;
+ res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Failed to set audio device to mono\n");
+ return -1;
+ }
+ fmt = desired = DEFAULT_SAMPLE_RATE; /* 8000 Hz desired */
+ res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt);
+
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Failed to set audio device to mono\n");
+ return -1;
+ }
+ if (fmt != desired) {
+ if (!(o->warned & WARN_speed)) {
+ ast_log(LOG_WARNING,
+ "Requested %d Hz, got %d Hz -- sound may be choppy\n",
+ desired, fmt);
+ o->warned |= WARN_speed;
+ }
+ }
+ /*
+ * on Freebsd, SETFRAGMENT does not work very well on some cards.
+ * Default to use 256 bytes, let the user override
+ */
+ if (o->frags) {
+ fmt = o->frags;
+ res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt);
+ if (res < 0) {
+ if (!(o->warned & WARN_frag)) {
+ ast_log(LOG_WARNING,
+ "Unable to set fragment size -- sound may be choppy\n");
+ o->warned |= WARN_frag;
+ }
+ }
+ }
+ /* on some cards, we need SNDCTL_DSP_SETTRIGGER to start outputting */
+ res = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
+ res = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &res);
+ /* it may fail if we are in half duplex, never mind */
+ return 0;
+}
+
+/*
+ * some of the standard methods supported by channels.
+ */
+static int oss_digit_begin(struct ast_channel *c, char digit)
+{
+ return 0;
+}
+
+static int oss_digit_end(struct ast_channel *c, char digit, unsigned int duration)
+{
+ /* no better use for received digits than print them */
+ ast_verbose(" << Console Received digit %c of duration %u ms >> \n",
+ digit, duration);
+ return 0;
+}
+
+static int oss_text(struct ast_channel *c, const char *text)
+{
+ /* print received messages */
+ ast_verbose(" << Console Received text %s >> \n", text);
+ return 0;
+}
+
+/*!
+ * \brief handler for incoming calls. Either autoanswer, or start ringing
+ */
+static int oss_call(struct ast_channel *c, char *dest, int timeout)
+{
+ struct chan_oss_pvt *o = c->tech_pvt;
+ struct ast_frame f = { 0, };
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(name);
+ AST_APP_ARG(flags);
+ );
+ char *parse = ast_strdupa(dest);
+
+ AST_NONSTANDARD_APP_ARGS(args, parse, '/');
+
+ ast_verbose(" << Call to device '%s' dnid '%s' rdnis '%s' on console from '%s' <%s> >>\n", dest, c->cid.cid_dnid, c->cid.cid_rdnis, c->cid.cid_name, c->cid.cid_num);
+ if (!ast_strlen_zero(args.flags) && strcasecmp(args.flags, "answer") == 0) {
+ f.frametype = AST_FRAME_CONTROL;
+ f.subclass = AST_CONTROL_ANSWER;
+ ast_queue_frame(c, &f);
+ } else if (!ast_strlen_zero(args.flags) && strcasecmp(args.flags, "noanswer") == 0) {
+ f.frametype = AST_FRAME_CONTROL;
+ f.subclass = AST_CONTROL_RINGING;
+ ast_queue_frame(c, &f);
+ ast_indicate(c, AST_CONTROL_RINGING);
+ } else if (o->autoanswer) {
+ ast_verbose(" << Auto-answered >> \n");
+ f.frametype = AST_FRAME_CONTROL;
+ f.subclass = AST_CONTROL_ANSWER;
+ ast_queue_frame(c, &f);
+ } else {
+ ast_verbose("<< Type 'answer' to answer, or use 'autoanswer' for future calls >> \n");
+ f.frametype = AST_FRAME_CONTROL;
+ f.subclass = AST_CONTROL_RINGING;
+ ast_queue_frame(c, &f);
+ ast_indicate(c, AST_CONTROL_RINGING);
+ }
+ return 0;
+}
+
+/*!
+ * \brief remote side answered the phone
+ */
+static int oss_answer(struct ast_channel *c)
+{
+ ast_verbose(" << Console call has been answered >> \n");
+ ast_setstate(c, AST_STATE_UP);
+ return 0;
+}
+
+static int oss_hangup(struct ast_channel *c)
+{
+ struct chan_oss_pvt *o = c->tech_pvt;
+
+ c->tech_pvt = NULL;
+ o->owner = NULL;
+ ast_verbose(" << Hangup on console >> \n");
+ console_video_uninit(o->env);
+ ast_module_unref(ast_module_info->self);
+ if (o->hookstate) {
+ if (o->autoanswer || o->autohangup) {
+ /* Assume auto-hangup too */
+ o->hookstate = 0;
+ setformat(o, O_CLOSE);
+ }
+ }
+ return 0;
+}
+
+/*! \brief used for data coming from the network */
+static int oss_write(struct ast_channel *c, struct ast_frame *f)
+{
+ int src;
+ struct chan_oss_pvt *o = c->tech_pvt;
+
+ /*
+ * we could receive a block which is not a multiple of our
+ * FRAME_SIZE, so buffer it locally and write to the device
+ * in FRAME_SIZE chunks.
+ * Keep the residue stored for future use.
+ */
+ src = 0; /* read position into f->data */
+ while (src < f->datalen) {
+ /* Compute spare room in the buffer */
+ int l = sizeof(o->oss_write_buf) - o->oss_write_dst;
+
+ if (f->datalen - src >= l) { /* enough to fill a frame */
+ memcpy(o->oss_write_buf + o->oss_write_dst, f->data + src, l);
+ soundcard_writeframe(o, (short *) o->oss_write_buf);
+ src += l;
+ o->oss_write_dst = 0;
+ } else { /* copy residue */
+ l = f->datalen - src;
+ memcpy(o->oss_write_buf + o->oss_write_dst, f->data + src, l);
+ src += l; /* but really, we are done */
+ o->oss_write_dst += l;
+ }
+ }
+ return 0;
+}
+
+static struct ast_frame *oss_read(struct ast_channel *c)
+{
+ int res;
+ struct chan_oss_pvt *o = c->tech_pvt;
+ struct ast_frame *f = &o->read_f;
+
+ /* XXX can be simplified returning &ast_null_frame */
+ /* prepare a NULL frame in case we don't have enough data to return */
+ bzero(f, sizeof(struct ast_frame));
+ f->frametype = AST_FRAME_NULL;
+ f->src = oss_tech.type;
+
+ res = read(o->sounddev, o->oss_read_buf + o->readpos, sizeof(o->oss_read_buf) - o->readpos);
+ if (res < 0) /* audio data not ready, return a NULL frame */
+ return f;
+
+ o->readpos += res;
+ if (o->readpos < sizeof(o->oss_read_buf)) /* not enough samples */
+ return f;
+
+ if (o->mute)
+ return f;
+
+ o->readpos = AST_FRIENDLY_OFFSET; /* reset read pointer for next frame */
+ if (c->_state != AST_STATE_UP) /* drop data if frame is not up */
+ return f;
+ /* ok we can build and deliver the frame to the caller */
+ f->frametype = AST_FRAME_VOICE;
+ f->subclass = AST_FORMAT_SLINEAR;
+ f->samples = FRAME_SIZE;
+ f->datalen = FRAME_SIZE * 2;
+ f->data = o->oss_read_buf + AST_FRIENDLY_OFFSET;
+ if (o->boost != BOOST_SCALE) { /* scale and clip values */
+ int i, x;
+ int16_t *p = (int16_t *) f->data;
+ for (i = 0; i < f->samples; i++) {
+ x = (p[i] * o->boost) / BOOST_SCALE;
+ if (x > 32767)
+ x = 32767;
+ else if (x < -32768)
+ x = -32768;
+ p[i] = x;
+ }
+ }
+
+ f->offset = AST_FRIENDLY_OFFSET;
+ return f;
+}
+
+static int oss_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct chan_oss_pvt *o = newchan->tech_pvt;
+ o->owner = newchan;
+ return 0;
+}
+
+static int oss_indicate(struct ast_channel *c, int cond, const void *data, size_t datalen)
+{
+ struct chan_oss_pvt *o = c->tech_pvt;
+ int res = 0;
+
+ switch (cond) {
+ case AST_CONTROL_BUSY:
+ case AST_CONTROL_CONGESTION:
+ case AST_CONTROL_RINGING:
+ case -1:
+ res = -1;
+ break;
+ case AST_CONTROL_PROGRESS:
+ case AST_CONTROL_PROCEEDING:
+ case AST_CONTROL_VIDUPDATE:
+ break;
+ case AST_CONTROL_HOLD:
+ ast_verbose(" << Console Has Been Placed on Hold >> \n");
+ ast_moh_start(c, data, o->mohinterpret);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_verbose(" << Console Has Been Retrieved from Hold >> \n");
+ ast_moh_stop(c);
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, c->name);
+ return -1;
+ }
+
+ return res;
+}
+
+/*!
+ * \brief allocate a new channel.
+ */
+static struct ast_channel *oss_new(struct chan_oss_pvt *o, char *ext, char *ctx, int state)
+{
+ struct ast_channel *c;
+
+ c = ast_channel_alloc(1, state, o->cid_num, o->cid_name, "", ext, ctx, 0, "OSS/%s", o->device + 5);
+ if (c == NULL)
+ return NULL;
+ c->tech = &oss_tech;
+ if (o->sounddev < 0)
+ setformat(o, O_RDWR);
+ ast_channel_set_fd(c, 0, o->sounddev); /* -1 if device closed, override later */
+ c->nativeformats = AST_FORMAT_SLINEAR;
+ /* if the console makes the call, add video to the offer */
+ if (state == AST_STATE_RINGING)
+ c->nativeformats |= console_video_formats;
+
+ c->readformat = AST_FORMAT_SLINEAR;
+ c->writeformat = AST_FORMAT_SLINEAR;
+ c->tech_pvt = o;
+
+ if (!ast_strlen_zero(o->language))
+ ast_string_field_set(c, language, o->language);
+ /* Don't use ast_set_callerid() here because it will
+ * generate a needless NewCallerID event */
+ c->cid.cid_ani = ast_strdup(o->cid_num);
+ if (!ast_strlen_zero(ext))
+ c->cid.cid_dnid = ast_strdup(ext);
+
+ o->owner = c;
+ ast_module_ref(ast_module_info->self);
+ ast_jb_configure(c, &global_jbconf);
+ if (state != AST_STATE_DOWN) {
+ if (ast_pbx_start(c)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", c->name);
+ ast_hangup(c);
+ o->owner = c = NULL;
+ /* XXX what about the channel itself ? */
+ }
+ }
+ console_video_start(get_video_desc(c), c); /* XXX cleanup */
+
+ return c;
+}
+
+static struct ast_channel *oss_request(const char *type, int format, void *data, int *cause)
+{
+ struct ast_channel *c;
+ struct chan_oss_pvt *o;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(name);
+ AST_APP_ARG(flags);
+ );
+ char *parse = ast_strdupa(data);
+
+ AST_NONSTANDARD_APP_ARGS(args, parse, '/');
+ o = find_desc(args.name);
+
+ ast_log(LOG_WARNING, "oss_request ty <%s> data 0x%p <%s>\n", type, data, (char *) data);
+ if (o == NULL) {
+ ast_log(LOG_NOTICE, "Device %s not found\n", args.name);
+ /* XXX we could default to 'dsp' perhaps ? */
+ return NULL;
+ }
+ if ((format & AST_FORMAT_SLINEAR) == 0) {
+ ast_log(LOG_NOTICE, "Format 0x%x unsupported\n", format);
+ return NULL;
+ }
+ if (o->owner) {
+ ast_log(LOG_NOTICE, "Already have a call (chan %p) on the OSS channel\n", o->owner);
+ *cause = AST_CAUSE_BUSY;
+ return NULL;
+ }
+ c = oss_new(o, NULL, NULL, AST_STATE_DOWN);
+ if (c == NULL) {
+ ast_log(LOG_WARNING, "Unable to create new OSS channel\n");
+ return NULL;
+ }
+ return c;
+}
+
+static void store_config_core(struct chan_oss_pvt *o, const char *var, const char *value);
+
+/*! Generic console command handler. Basically a wrapper for a subset
+ * of config file options which are also available from the CLI
+ */
+static char *console_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ const char *var, *value;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = CONSOLE_VIDEO_CMDS;
+ e->usage = "Usage: " CONSOLE_VIDEO_CMDS "...\n"
+ " Generic handler for console commands.\n";
+ return NULL;
+
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc < e->args)
+ return CLI_SHOWUSAGE;
+ if (o == NULL) {
+ ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
+ oss_active);
+ return CLI_FAILURE;
+ }
+ var = a->argv[e->args-1];
+ value = a->argc > e->args ? a->argv[e->args] : NULL;
+ if (value) /* handle setting */
+ store_config_core(o, var, value);
+ if (!console_video_cli(o->env, var, a->fd)) /* print video-related values */
+ return CLI_SUCCESS;
+ /* handle other values */
+ if (!strcasecmp(var, "device")) {
+ ast_cli(a->fd, "device is [%s]\n", o->device);
+ }
+ return CLI_SUCCESS;
+}
+
+static char *console_autoanswer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console autoanswer [on|off]";
+ e->usage =
+ "Usage: console autoanswer [on|off]\n"
+ " Enables or disables autoanswer feature. If used without\n"
+ " argument, displays the current on/off status of autoanswer.\n"
+ " The default value of autoanswer is in 'oss.conf'.\n";
+ return NULL;
+
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc == e->args - 1) {
+ ast_cli(a->fd, "Auto answer is %s.\n", o->autoanswer ? "on" : "off");
+ return CLI_SUCCESS;
+ }
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+ if (o == NULL) {
+ ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
+ oss_active);
+ return CLI_FAILURE;
+ }
+ if (!strcasecmp(a->argv[e->args-1], "on"))
+ o->autoanswer = 1;
+ else if (!strcasecmp(a->argv[e->args - 1], "off"))
+ o->autoanswer = 0;
+ else
+ return CLI_SHOWUSAGE;
+ return CLI_SUCCESS;
+}
+
+/*! \brief helper function for the answer key/cli command */
+static char *console_do_answer(int fd)
+{
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ if (!o->owner) {
+ if (fd > -1)
+ ast_cli(fd, "No one is calling us\n");
+ return CLI_FAILURE;
+ }
+ o->hookstate = 1;
+ ast_queue_frame(o->owner, &f);
+ return CLI_SUCCESS;
+}
+
+/*!
+ * \brief answer command from the console
+ */
+static char *console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console answer";
+ e->usage =
+ "Usage: console answer\n"
+ " Answers an incoming call on the console (OSS) channel.\n";
+ return NULL;
+
+ case CLI_GENERATE:
+ return NULL; /* no completion */
+ }
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+ return console_do_answer(a->fd);
+}
+
+/*!
+ * \brief Console send text CLI command
+ *
+ * \note concatenate all arguments into a single string. argv is NULL-terminated
+ * so we can use it right away
+ */
+static char *console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ char buf[TEXT_SIZE];
+
+ if (cmd == CLI_INIT) {
+ e->command = "console send text";
+ e->usage =
+ "Usage: console send text <message>\n"
+ " Sends a text message for display on the remote terminal.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc < e->args + 1)
+ return CLI_SHOWUSAGE;
+ if (!o->owner) {
+ ast_cli(a->fd, "Not in a call\n");
+ return CLI_FAILURE;
+ }
+ ast_join(buf, sizeof(buf) - 1, a->argv + e->args);
+ if (!ast_strlen_zero(buf)) {
+ struct ast_frame f = { 0, };
+ int i = strlen(buf);
+ buf[i] = '\n';
+ f.frametype = AST_FRAME_TEXT;
+ f.subclass = 0;
+ f.data = buf;
+ f.datalen = i + 1;
+ ast_queue_frame(o->owner, &f);
+ }
+ return CLI_SUCCESS;
+}
+
+static char *console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (cmd == CLI_INIT) {
+ e->command = "console hangup";
+ e->usage =
+ "Usage: console hangup\n"
+ " Hangs up any call currently placed on the console.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+ if (!o->owner && !o->hookstate) { /* XXX maybe only one ? */
+ ast_cli(a->fd, "No call to hang up\n");
+ return CLI_FAILURE;
+ }
+ o->hookstate = 0;
+ if (o->owner)
+ ast_queue_hangup(o->owner);
+ setformat(o, O_CLOSE);
+ return CLI_SUCCESS;
+}
+
+static char *console_flash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_FLASH };
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (cmd == CLI_INIT) {
+ e->command = "console flash";
+ e->usage =
+ "Usage: console flash\n"
+ " Flashes the call currently placed on the console.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+ if (!o->owner) { /* XXX maybe !o->hookstate too ? */
+ ast_cli(a->fd, "No call to flash\n");
+ return CLI_FAILURE;
+ }
+ o->hookstate = 0;
+ if (o->owner) /* XXX must be true, right ? */
+ ast_queue_frame(o->owner, &f);
+ return CLI_SUCCESS;
+}
+
+static char *console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char *s = NULL, *mye = NULL, *myc = NULL;
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (cmd == CLI_INIT) {
+ e->command = "console dial";
+ e->usage =
+ "Usage: console dial [extension[@context]]\n"
+ " Dials a given extension (and context if specified)\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc > e->args + 1)
+ return CLI_SHOWUSAGE;
+ if (o->owner) { /* already in a call */
+ int i;
+ struct ast_frame f = { AST_FRAME_DTMF, 0 };
+
+ if (a->argc == e->args) { /* argument is mandatory here */
+ ast_cli(a->fd, "Already in a call. You can only dial digits until you hangup.\n");
+ return CLI_FAILURE;
+ }
+ s = a->argv[e->args];
+ /* send the string one char at a time */
+ for (i = 0; i < strlen(s); i++) {
+ f.subclass = s[i];
+ ast_queue_frame(o->owner, &f);
+ }
+ return CLI_SUCCESS;
+ }
+ /* if we have an argument split it into extension and context */
+ if (a->argc == e->args + 1)
+ s = ast_ext_ctx(a->argv[e->args], &mye, &myc);
+ /* supply default values if needed */
+ if (mye == NULL)
+ mye = o->ext;
+ if (myc == NULL)
+ myc = o->ctx;
+ if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
+ o->hookstate = 1;
+ oss_new(o, mye, myc, AST_STATE_RINGING);
+ } else
+ ast_cli(a->fd, "No such extension '%s' in context '%s'\n", mye, myc);
+ if (s)
+ ast_free(s);
+ return CLI_SUCCESS;
+}
+
+static char *console_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ char *s;
+
+ if (cmd == CLI_INIT) {
+ e->command = "console {mute|unmute}";
+ e->usage =
+ "Usage: console {mute|unmute}\n"
+ " Mute/unmute the microphone.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+ s = a->argv[e->args-1];
+ if (!strcasecmp(s, "mute"))
+ o->mute = 1;
+ else if (!strcasecmp(s, "unmute"))
+ o->mute = 0;
+ else
+ return CLI_SHOWUSAGE;
+ return CLI_SUCCESS;
+}
+
+static char *console_transfer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ struct ast_channel *b = NULL;
+ char *tmp, *ext, *ctx;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console transfer";
+ e->usage =
+ "Usage: console transfer <extension>[@context]\n"
+ " Transfers the currently connected call to the given extension (and\n"
+ " context if specified)\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ if (o == NULL)
+ return CLI_FAILURE;
+ if (o->owner == NULL || (b = ast_bridged_channel(o->owner)) == NULL) {
+ ast_cli(a->fd, "There is no call to transfer\n");
+ return CLI_SUCCESS;
+ }
+
+ tmp = ast_ext_ctx(a->argv[2], &ext, &ctx);
+ if (ctx == NULL) /* supply default context if needed */
+ ctx = o->owner->context;
+ if (!ast_exists_extension(b, ctx, ext, 1, b->cid.cid_num))
+ ast_cli(a->fd, "No such extension exists\n");
+ else {
+ ast_cli(a->fd, "Whee, transferring %s to %s@%s.\n", b->name, ext, ctx);
+ if (ast_async_goto(b, ctx, ext, 1))
+ ast_cli(a->fd, "Failed to transfer :(\n");
+ }
+ if (tmp)
+ ast_free(tmp);
+ return CLI_SUCCESS;
+}
+
+static char *console_active(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console active";
+ e->usage =
+ "Usage: console active [device]\n"
+ " If used without a parameter, displays which device is the current\n"
+ " console. If a device is specified, the console sound device is changed to\n"
+ " the device specified.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc == 2)
+ ast_cli(a->fd, "active console is [%s]\n", oss_active);
+ else if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ else {
+ struct chan_oss_pvt *o;
+ if (strcmp(a->argv[2], "show") == 0) {
+ for (o = oss_default.next; o; o = o->next)
+ ast_cli(a->fd, "device [%s] exists\n", o->name);
+ return CLI_SUCCESS;
+ }
+ o = find_desc(a->argv[2]);
+ if (o == NULL)
+ ast_cli(a->fd, "No device [%s] exists\n", a->argv[2]);
+ else
+ oss_active = o->name;
+ }
+ return CLI_SUCCESS;
+}
+
+/*!
+ * \brief store the boost factor
+ */
+static void store_boost(struct chan_oss_pvt *o, const char *s)
+{
+ double boost = 0;
+ if (sscanf(s, "%lf", &boost) != 1) {
+ ast_log(LOG_WARNING, "invalid boost <%s>\n", s);
+ return;
+ }
+ if (boost < -BOOST_MAX) {
+ ast_log(LOG_WARNING, "boost %s too small, using %d\n", s, -BOOST_MAX);
+ boost = -BOOST_MAX;
+ } else if (boost > BOOST_MAX) {
+ ast_log(LOG_WARNING, "boost %s too large, using %d\n", s, BOOST_MAX);
+ boost = BOOST_MAX;
+ }
+ boost = exp(log(10) * boost / 20) * BOOST_SCALE;
+ o->boost = boost;
+ ast_log(LOG_WARNING, "setting boost %s to %d\n", s, o->boost);
+}
+
+static char *console_boost(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "console boost";
+ e->usage =
+ "Usage: console boost [boost in dB]\n"
+ " Sets or display mic boost in dB\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc == 2)
+ ast_cli(a->fd, "boost currently %5.1f\n", 20 * log10(((double) o->boost / (double) BOOST_SCALE)));
+ else if (a->argc == 3)
+ store_boost(o, a->argv[2]);
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_oss[] = {
+ AST_CLI_DEFINE(console_answer, "Answer an incoming console call"),
+ AST_CLI_DEFINE(console_hangup, "Hangup a call on the console"),
+ AST_CLI_DEFINE(console_flash, "Flash a call on the console"),
+ AST_CLI_DEFINE(console_dial, "Dial an extension on the console"),
+ AST_CLI_DEFINE(console_mute, "Disable/Enable mic input"),
+ AST_CLI_DEFINE(console_transfer, "Transfer a call to a different extension"),
+ AST_CLI_DEFINE(console_cmd, "Generic console command"),
+ AST_CLI_DEFINE(console_sendtext, "Send text to the remote device"),
+ AST_CLI_DEFINE(console_autoanswer, "Sets/displays autoanswer"),
+ AST_CLI_DEFINE(console_boost, "Sets/displays mic boost in dB"),
+ AST_CLI_DEFINE(console_active, "Sets/displays active console"),
+};
+
+/*!
+ * store the mixer argument from the config file, filtering possibly
+ * invalid or dangerous values (the string is used as argument for
+ * system("mixer %s")
+ */
+static void store_mixer(struct chan_oss_pvt *o, const char *s)
+{
+ int i;
+
+ for (i = 0; i < strlen(s); i++) {
+ if (!isalnum(s[i]) && index(" \t-/", s[i]) == NULL) {
+ ast_log(LOG_WARNING, "Suspect char %c in mixer cmd, ignoring:\n\t%s\n", s[i], s);
+ return;
+ }
+ }
+ if (o->mixer_cmd)
+ ast_free(o->mixer_cmd);
+ o->mixer_cmd = ast_strdup(s);
+ ast_log(LOG_WARNING, "setting mixer %s\n", s);
+}
+
+/*!
+ * store the callerid components
+ */
+static void store_callerid(struct chan_oss_pvt *o, const char *s)
+{
+ ast_callerid_split(s, o->cid_name, sizeof(o->cid_name), o->cid_num, sizeof(o->cid_num));
+}
+
+static void store_config_core(struct chan_oss_pvt *o, const char *var, const char *value)
+{
+ CV_START(var, value);
+
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, var, value))
+ return;
+
+ if (!console_video_config(&o->env, var, value))
+ return; /* matched there */
+ CV_BOOL("autoanswer", o->autoanswer);
+ CV_BOOL("autohangup", o->autohangup);
+ CV_BOOL("overridecontext", o->overridecontext);
+ CV_STR("device", o->device);
+ CV_UINT("frags", o->frags);
+ CV_UINT("debug", oss_debug);
+ CV_UINT("queuesize", o->queuesize);
+ CV_STR("context", o->ctx);
+ CV_STR("language", o->language);
+ CV_STR("mohinterpret", o->mohinterpret);
+ CV_STR("extension", o->ext);
+ CV_F("mixer", store_mixer(o, value));
+ CV_F("callerid", store_callerid(o, value)) ;
+ CV_F("boost", store_boost(o, value));
+
+ CV_END;
+}
+
+/*!
+ * grab fields from the config file, init the descriptor and open the device.
+ */
+static struct chan_oss_pvt *store_config(struct ast_config *cfg, char *ctg)
+{
+ struct ast_variable *v;
+ struct chan_oss_pvt *o;
+
+ if (ctg == NULL) {
+ o = &oss_default;
+ ctg = "general";
+ } else {
+ if (!(o = ast_calloc(1, sizeof(*o))))
+ return NULL;
+ *o = oss_default;
+ /* "general" is also the default thing */
+ if (strcmp(ctg, "general") == 0) {
+ o->name = ast_strdup("dsp");
+ oss_active = o->name;
+ goto openit;
+ }
+ o->name = ast_strdup(ctg);
+ }
+
+ strcpy(o->mohinterpret, "default");
+
+ o->lastopen = ast_tvnow(); /* don't leave it 0 or tvdiff may wrap */
+ /* fill other fields from configuration */
+ for (v = ast_variable_browse(cfg, ctg); v; v = v->next) {
+ store_config_core(o, v->name, v->value);
+ }
+ if (ast_strlen_zero(o->device))
+ ast_copy_string(o->device, DEV_DSP, sizeof(o->device));
+ if (o->mixer_cmd) {
+ char *cmd;
+
+ asprintf(&cmd, "mixer %s", o->mixer_cmd);
+ ast_log(LOG_WARNING, "running [%s]\n", cmd);
+ system(cmd);
+ ast_free(cmd);
+ }
+ if (o == &oss_default) /* we are done with the default */
+ return NULL;
+
+openit:
+#ifdef TRYOPEN
+ if (setformat(o, O_RDWR) < 0) { /* open device */
+ ast_verb(1, "Device %s not detected\n", ctg);
+ ast_verb(1, "Turn off OSS support by adding " "'noload=chan_oss.so' in /etc/asterisk/modules.conf\n");
+ goto error;
+ }
+ if (o->duplex != M_FULL)
+ ast_log(LOG_WARNING, "XXX I don't work right with non " "full-duplex sound cards XXX\n");
+#endif /* TRYOPEN */
+
+ /* link into list of devices */
+ if (o != &oss_default) {
+ o->next = oss_default.next;
+ oss_default.next = o;
+ }
+ return o;
+
+#ifdef TRYOPEN
+error:
+ if (o != &oss_default)
+ ast_free(o);
+ return NULL;
+#endif
+}
+
+static int load_module(void)
+{
+ struct ast_config *cfg = NULL;
+ char *ctg = NULL;
+ struct ast_flags config_flags = { 0 };
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ /* load config file */
+ if (!(cfg = ast_config_load(config, config_flags))) {
+ ast_log(LOG_NOTICE, "Unable to load config %s\n", config);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ do {
+ store_config(cfg, ctg);
+ } while ( (ctg = ast_category_browse(cfg, ctg)) != NULL);
+
+ ast_config_destroy(cfg);
+
+ if (find_desc(oss_active) == NULL) {
+ ast_log(LOG_NOTICE, "Device %s not found\n", oss_active);
+ /* XXX we could default to 'dsp' perhaps ? */
+ /* XXX should cleanup allocated memory etc. */
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ oss_tech.capabilities |= console_video_formats;
+
+ if (ast_channel_register(&oss_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel type 'OSS'\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ ast_cli_register_multiple(cli_oss, sizeof(cli_oss) / sizeof(struct ast_cli_entry));
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+
+static int unload_module(void)
+{
+ struct chan_oss_pvt *o;
+
+ ast_channel_unregister(&oss_tech);
+ ast_cli_unregister_multiple(cli_oss, sizeof(cli_oss) / sizeof(struct ast_cli_entry));
+
+ for (o = oss_default.next; o; o = o->next) {
+ close(o->sounddev);
+ if (o->owner)
+ ast_softhangup(o->owner, AST_SOFTHANGUP_APPUNLOAD);
+ if (o->owner) /* XXX how ??? */
+ return -1;
+ /* XXX what about the memory allocated ? */
+ }
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "OSS Console Channel Driver");
diff --git a/trunk/channels/chan_phone.c b/trunk/channels/chan_phone.c
new file mode 100644
index 000000000..233e5e829
--- /dev/null
+++ b/trunk/channels/chan_phone.c
@@ -0,0 +1,1450 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Generic Linux Telephony Interface driver
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <depend>ixjuser</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <ctype.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#ifdef HAVE_LINUX_COMPILER_H
+#include <linux/compiler.h>
+#endif
+#include <linux/telephony.h>
+/* Still use some IXJ specific stuff */
+#include <linux/version.h>
+#include <linux/ixjuser.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+#include "asterisk/callerid.h"
+#include "asterisk/causes.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/musiconhold.h"
+
+#include "DialTone.h"
+
+#ifdef QTI_PHONEJACK_TJ_PCI /* check for the newer quicknet driver v.3.1.0 which has this symbol */
+#define QNDRV_VER 310
+#else
+#define QNDRV_VER 100
+#endif
+
+#if QNDRV_VER > 100
+#ifdef __linux__
+#define IXJ_PHONE_RING_START(x) ioctl(p->fd, PHONE_RING_START, &x);
+#else /* FreeBSD and others */
+#define IXJ_PHONE_RING_START(x) ioctl(p->fd, PHONE_RING_START, x);
+#endif /* __linux__ */
+#else /* older driver */
+#define IXJ_PHONE_RING_START(x) ioctl(p->fd, PHONE_RING_START, &x);
+#endif
+
+#define DEFAULT_CALLER_ID "Unknown"
+#define PHONE_MAX_BUF 480
+#define DEFAULT_GAIN 0x100
+
+static const char tdesc[] = "Standard Linux Telephony API Driver";
+static const char config[] = "phone.conf";
+
+/* Default context for dialtone mode */
+static char context[AST_MAX_EXTENSION] = "default";
+
+/* Default language */
+static char language[MAX_LANGUAGE] = "";
+
+static int echocancel = AEC_OFF;
+
+static int silencesupression = 0;
+
+static int prefformat = AST_FORMAT_G729A | AST_FORMAT_G723_1 | AST_FORMAT_SLINEAR | AST_FORMAT_ULAW;
+
+/* Protect the interface list (of phone_pvt's) */
+AST_MUTEX_DEFINE_STATIC(iflock);
+
+/* Protect the monitoring thread, so only one process can kill or start it, and not
+ when it's doing something critical. */
+AST_MUTEX_DEFINE_STATIC(monlock);
+
+/* Boolean value whether the monitoring thread shall continue. */
+static unsigned int monitor;
+
+/* This is the thread for the monitor which checks for input on the channels
+ which are not currently in use. */
+static pthread_t monitor_thread = AST_PTHREADT_NULL;
+
+static int restart_monitor(void);
+
+/* The private structures of the Phone Jack channels are linked for
+ selecting outgoing channels */
+
+#define MODE_DIALTONE 1
+#define MODE_IMMEDIATE 2
+#define MODE_FXO 3
+#define MODE_FXS 4
+#define MODE_SIGMA 5
+
+static struct phone_pvt {
+ int fd; /* Raw file descriptor for this device */
+ struct ast_channel *owner; /* Channel we belong to, possibly NULL */
+ int mode; /* Is this in the */
+ int lastformat; /* Last output format */
+ int lastinput; /* Last input format */
+ int ministate; /* Miniature state, for dialtone mode */
+ char dev[256]; /* Device name */
+ struct phone_pvt *next; /* Next channel in list */
+ struct ast_frame fr; /* Frame */
+ char offset[AST_FRIENDLY_OFFSET];
+ char buf[PHONE_MAX_BUF]; /* Static buffer for reading frames */
+ int obuflen;
+ int dialtone;
+ int txgain, rxgain; /* gain control for playing, recording */
+ /* 0x100 - 1.0, 0x200 - 2.0, 0x80 - 0.5 */
+ int cpt; /* Call Progress Tone playing? */
+ int silencesupression;
+ char context[AST_MAX_EXTENSION];
+ char obuf[PHONE_MAX_BUF * 2];
+ char ext[AST_MAX_EXTENSION];
+ char language[MAX_LANGUAGE];
+ char cid_num[AST_MAX_EXTENSION];
+ char cid_name[AST_MAX_EXTENSION];
+} *iflist = NULL;
+
+static char cid_num[AST_MAX_EXTENSION];
+static char cid_name[AST_MAX_EXTENSION];
+
+static struct ast_channel *phone_request(const char *type, int format, void *data, int *cause);
+static int phone_digit_begin(struct ast_channel *ast, char digit);
+static int phone_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int phone_call(struct ast_channel *ast, char *dest, int timeout);
+static int phone_hangup(struct ast_channel *ast);
+static int phone_answer(struct ast_channel *ast);
+static struct ast_frame *phone_read(struct ast_channel *ast);
+static int phone_write(struct ast_channel *ast, struct ast_frame *frame);
+static struct ast_frame *phone_exception(struct ast_channel *ast);
+static int phone_send_text(struct ast_channel *ast, const char *text);
+static int phone_fixup(struct ast_channel *old, struct ast_channel *new);
+static int phone_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen);
+
+static const struct ast_channel_tech phone_tech = {
+ .type = "Phone",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_G723_1 | AST_FORMAT_SLINEAR | AST_FORMAT_ULAW | AST_FORMAT_G729A,
+ .requester = phone_request,
+ .send_digit_begin = phone_digit_begin,
+ .send_digit_end = phone_digit_end,
+ .call = phone_call,
+ .hangup = phone_hangup,
+ .answer = phone_answer,
+ .read = phone_read,
+ .write = phone_write,
+ .exception = phone_exception,
+ .indicate = phone_indicate,
+ .fixup = phone_fixup
+};
+
+static struct ast_channel_tech phone_tech_fxs = {
+ .type = "Phone",
+ .description = tdesc,
+ .requester = phone_request,
+ .send_digit_begin = phone_digit_begin,
+ .send_digit_end = phone_digit_end,
+ .call = phone_call,
+ .hangup = phone_hangup,
+ .answer = phone_answer,
+ .read = phone_read,
+ .write = phone_write,
+ .exception = phone_exception,
+ .write_video = phone_write,
+ .send_text = phone_send_text,
+ .indicate = phone_indicate,
+ .fixup = phone_fixup
+};
+
+static struct ast_channel_tech *cur_tech;
+
+static int phone_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen)
+{
+ struct phone_pvt *p = chan->tech_pvt;
+ int res=-1;
+ ast_debug(1, "Requested indication %d on channel %s\n", condition, chan->name);
+ switch(condition) {
+ case AST_CONTROL_FLASH:
+ ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_ON_HOOK);
+ usleep(320000);
+ ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_OFF_HOOK);
+ p->lastformat = -1;
+ res = 0;
+ break;
+ case AST_CONTROL_HOLD:
+ ast_moh_start(chan, data, NULL);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_moh_stop(chan);
+ break;
+ default:
+ ast_log(LOG_WARNING, "Condition %d is not supported on channel %s\n", condition, chan->name);
+ }
+ return res;
+}
+
+static int phone_fixup(struct ast_channel *old, struct ast_channel *new)
+{
+ struct phone_pvt *pvt = old->tech_pvt;
+ if (pvt && pvt->owner == old)
+ pvt->owner = new;
+ return 0;
+}
+
+static int phone_digit_begin(struct ast_channel *chan, char digit)
+{
+ /* XXX Modify this callback to let Asterisk support controlling the length of DTMF */
+ return 0;
+}
+
+static int phone_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct phone_pvt *p;
+ int outdigit;
+ p = ast->tech_pvt;
+ ast_debug(1, "Dialed %c\n", digit);
+ switch(digit) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ outdigit = digit - '0';
+ break;
+ case '*':
+ outdigit = 11;
+ break;
+ case '#':
+ outdigit = 12;
+ break;
+ case 'f': /*flash*/
+ case 'F':
+ ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_ON_HOOK);
+ usleep(320000);
+ ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_OFF_HOOK);
+ p->lastformat = -1;
+ return 0;
+ default:
+ ast_log(LOG_WARNING, "Unknown digit '%c'\n", digit);
+ return -1;
+ }
+ ast_debug(1, "Dialed %d\n", outdigit);
+ ioctl(p->fd, PHONE_PLAY_TONE, outdigit);
+ p->lastformat = -1;
+ return 0;
+}
+
+static int phone_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ struct phone_pvt *p;
+
+ PHONE_CID cid;
+ struct timeval UtcTime = ast_tvnow();
+ struct ast_tm tm;
+ int start;
+
+ ast_localtime(&UtcTime, &tm, NULL);
+
+ memset(&cid, 0, sizeof(PHONE_CID));
+ if(&tm != NULL) {
+ snprintf(cid.month, sizeof(cid.month), "%02d",(tm.tm_mon + 1));
+ snprintf(cid.day, sizeof(cid.day), "%02d", tm.tm_mday);
+ snprintf(cid.hour, sizeof(cid.hour), "%02d", tm.tm_hour);
+ snprintf(cid.min, sizeof(cid.min), "%02d", tm.tm_min);
+ }
+ /* the standard format of ast->callerid is: "name" <number>, but not always complete */
+ if (ast_strlen_zero(ast->cid.cid_name))
+ strcpy(cid.name, DEFAULT_CALLER_ID);
+ else
+ ast_copy_string(cid.name, ast->cid.cid_name, sizeof(cid.name));
+
+ if (ast->cid.cid_num)
+ ast_copy_string(cid.number, ast->cid.cid_num, sizeof(cid.number));
+
+ p = ast->tech_pvt;
+
+ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "phone_call called on %s, neither down nor reserved\n", ast->name);
+ return -1;
+ }
+ ast_debug(1, "Ringing %s on %s (%d)\n", dest, ast->name, ast->fds[0]);
+
+ start = IXJ_PHONE_RING_START(cid);
+ if (start == -1)
+ return -1;
+
+ if (p->mode == MODE_FXS) {
+ char *digit = strchr(dest, '/');
+ if (digit)
+ {
+ digit++;
+ while (*digit)
+ phone_digit_end(ast, *digit++, 0);
+ }
+ }
+
+ ast_setstate(ast, AST_STATE_RINGING);
+ ast_queue_control(ast, AST_CONTROL_RINGING);
+ return 0;
+}
+
+static int phone_hangup(struct ast_channel *ast)
+{
+ struct phone_pvt *p;
+ p = ast->tech_pvt;
+ ast_debug(1, "phone_hangup(%s)\n", ast->name);
+ if (!ast->tech_pvt) {
+ ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
+ return 0;
+ }
+ /* XXX Is there anything we can do to really hang up except stop recording? */
+ ast_setstate(ast, AST_STATE_DOWN);
+ if (ioctl(p->fd, PHONE_REC_STOP))
+ ast_log(LOG_WARNING, "Failed to stop recording\n");
+ if (ioctl(p->fd, PHONE_PLAY_STOP))
+ ast_log(LOG_WARNING, "Failed to stop playing\n");
+ if (ioctl(p->fd, PHONE_RING_STOP))
+ ast_log(LOG_WARNING, "Failed to stop ringing\n");
+ if (ioctl(p->fd, PHONE_CPT_STOP))
+ ast_log(LOG_WARNING, "Failed to stop sounds\n");
+
+ /* If it's an FXO, hang them up */
+ if (p->mode == MODE_FXO) {
+ if (ioctl(p->fd, PHONE_PSTN_SET_STATE, PSTN_ON_HOOK))
+ ast_debug(1, "ioctl(PHONE_PSTN_SET_STATE) failed on %s (%s)\n",ast->name, strerror(errno));
+ }
+
+ /* If they're off hook, give a busy signal */
+ if (ioctl(p->fd, PHONE_HOOKSTATE)) {
+ ast_debug(1, "Got hunghup, giving busy signal\n");
+ ioctl(p->fd, PHONE_BUSY);
+ p->cpt = 1;
+ }
+ p->lastformat = -1;
+ p->lastinput = -1;
+ p->ministate = 0;
+ p->obuflen = 0;
+ p->dialtone = 0;
+ memset(p->ext, 0, sizeof(p->ext));
+ ((struct phone_pvt *)(ast->tech_pvt))->owner = NULL;
+ ast_module_unref(ast_module_info->self);
+ ast_verb(3, "Hungup '%s'\n", ast->name);
+ ast->tech_pvt = NULL;
+ ast_setstate(ast, AST_STATE_DOWN);
+ restart_monitor();
+ return 0;
+}
+
+static int phone_setup(struct ast_channel *ast)
+{
+ struct phone_pvt *p;
+ p = ast->tech_pvt;
+ ioctl(p->fd, PHONE_CPT_STOP);
+ /* Nothing to answering really, just start recording */
+ if (ast->rawreadformat == AST_FORMAT_G729A) {
+ /* Prefer g729 */
+ ioctl(p->fd, PHONE_REC_STOP);
+ if (p->lastinput != AST_FORMAT_G729A) {
+ p->lastinput = AST_FORMAT_G729A;
+ if (ioctl(p->fd, PHONE_REC_CODEC, G729)) {
+ ast_log(LOG_WARNING, "Failed to set codec to g729\n");
+ return -1;
+ }
+ }
+ } else if (ast->rawreadformat == AST_FORMAT_G723_1) {
+ ioctl(p->fd, PHONE_REC_STOP);
+ if (p->lastinput != AST_FORMAT_G723_1) {
+ p->lastinput = AST_FORMAT_G723_1;
+ if (ioctl(p->fd, PHONE_REC_CODEC, G723_63)) {
+ ast_log(LOG_WARNING, "Failed to set codec to g723.1\n");
+ return -1;
+ }
+ }
+ } else if (ast->rawreadformat == AST_FORMAT_SLINEAR) {
+ ioctl(p->fd, PHONE_REC_STOP);
+ if (p->lastinput != AST_FORMAT_SLINEAR) {
+ p->lastinput = AST_FORMAT_SLINEAR;
+ if (ioctl(p->fd, PHONE_REC_CODEC, LINEAR16)) {
+ ast_log(LOG_WARNING, "Failed to set codec to signed linear 16\n");
+ return -1;
+ }
+ }
+ } else if (ast->rawreadformat == AST_FORMAT_ULAW) {
+ ioctl(p->fd, PHONE_REC_STOP);
+ if (p->lastinput != AST_FORMAT_ULAW) {
+ p->lastinput = AST_FORMAT_ULAW;
+ if (ioctl(p->fd, PHONE_REC_CODEC, ULAW)) {
+ ast_log(LOG_WARNING, "Failed to set codec to uLaw\n");
+ return -1;
+ }
+ }
+ } else if (p->mode == MODE_FXS) {
+ ioctl(p->fd, PHONE_REC_STOP);
+ if (p->lastinput != ast->rawreadformat) {
+ p->lastinput = ast->rawreadformat;
+ if (ioctl(p->fd, PHONE_REC_CODEC, ast->rawreadformat)) {
+ ast_log(LOG_WARNING, "Failed to set codec to %d\n",
+ ast->rawreadformat);
+ return -1;
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Can't do format %s\n", ast_getformatname(ast->rawreadformat));
+ return -1;
+ }
+ if (ioctl(p->fd, PHONE_REC_START)) {
+ ast_log(LOG_WARNING, "Failed to start recording\n");
+ return -1;
+ }
+ /* set the DTMF times (the default is too short) */
+ ioctl(p->fd, PHONE_SET_TONE_ON_TIME, 300);
+ ioctl(p->fd, PHONE_SET_TONE_OFF_TIME, 200);
+ return 0;
+}
+
+static int phone_answer(struct ast_channel *ast)
+{
+ struct phone_pvt *p;
+ p = ast->tech_pvt;
+ /* In case it's a LineJack, take it off hook */
+ if (p->mode == MODE_FXO) {
+ if (ioctl(p->fd, PHONE_PSTN_SET_STATE, PSTN_OFF_HOOK))
+ ast_debug(1, "ioctl(PHONE_PSTN_SET_STATE) failed on %s (%s)\n", ast->name, strerror(errno));
+ else
+ ast_debug(1, "Took linejack off hook\n");
+ }
+ phone_setup(ast);
+ ast_debug(1, "phone_answer(%s)\n", ast->name);
+ ast->rings = 0;
+ ast_setstate(ast, AST_STATE_UP);
+ return 0;
+}
+
+#if 0
+static char phone_2digit(char c)
+{
+ if (c == 12)
+ return '#';
+ else if (c == 11)
+ return '*';
+ else if ((c < 10) && (c >= 0))
+ return '0' + c - 1;
+ else
+ return '?';
+}
+#endif
+
+static struct ast_frame *phone_exception(struct ast_channel *ast)
+{
+ int res;
+ union telephony_exception phonee;
+ struct phone_pvt *p = ast->tech_pvt;
+ char digit;
+
+ /* Some nice norms */
+ p->fr.datalen = 0;
+ p->fr.samples = 0;
+ p->fr.data = NULL;
+ p->fr.src = "Phone";
+ p->fr.offset = 0;
+ p->fr.mallocd=0;
+ p->fr.delivery = ast_tv(0,0);
+
+ phonee.bytes = ioctl(p->fd, PHONE_EXCEPTION);
+ if (phonee.bits.dtmf_ready) {
+ ast_debug(1, "phone_exception(): DTMF\n");
+
+ /* We've got a digit -- Just handle this nicely and easily */
+ digit = ioctl(p->fd, PHONE_GET_DTMF_ASCII);
+ p->fr.subclass = digit;
+ p->fr.frametype = AST_FRAME_DTMF;
+ return &p->fr;
+ }
+ if (phonee.bits.hookstate) {
+ ast_debug(1, "Hookstate changed\n");
+ res = ioctl(p->fd, PHONE_HOOKSTATE);
+ /* See if we've gone on hook, if so, notify by returning NULL */
+ ast_debug(1, "New hookstate: %d\n", res);
+ if (!res && (p->mode != MODE_FXO))
+ return NULL;
+ else {
+ if (ast->_state == AST_STATE_RINGING) {
+ /* They've picked up the phone */
+ p->fr.frametype = AST_FRAME_CONTROL;
+ p->fr.subclass = AST_CONTROL_ANSWER;
+ phone_setup(ast);
+ ast_setstate(ast, AST_STATE_UP);
+ return &p->fr;
+ } else
+ ast_log(LOG_WARNING, "Got off hook in weird state %d\n", ast->_state);
+ }
+ }
+#if 1
+ if (phonee.bits.pstn_ring)
+ ast_verbose("Unit is ringing\n");
+ if (phonee.bits.caller_id) {
+ ast_verbose("We have caller ID\n");
+ }
+ if (phonee.bits.pstn_wink)
+ ast_verbose("Detected Wink\n");
+#endif
+ /* Strange -- nothing there.. */
+ p->fr.frametype = AST_FRAME_NULL;
+ p->fr.subclass = 0;
+ return &p->fr;
+}
+
+static struct ast_frame *phone_read(struct ast_channel *ast)
+{
+ int res;
+ struct phone_pvt *p = ast->tech_pvt;
+
+
+ /* Some nice norms */
+ p->fr.datalen = 0;
+ p->fr.samples = 0;
+ p->fr.data = NULL;
+ p->fr.src = "Phone";
+ p->fr.offset = 0;
+ p->fr.mallocd=0;
+ p->fr.delivery = ast_tv(0,0);
+
+ /* Try to read some data... */
+ CHECK_BLOCKING(ast);
+ res = read(p->fd, p->buf, PHONE_MAX_BUF);
+ ast_clear_flag(ast, AST_FLAG_BLOCKING);
+ if (res < 0) {
+#if 0
+ if (errno == EAGAIN) {
+ ast_log(LOG_WARNING, "Null frame received\n");
+ p->fr.frametype = AST_FRAME_NULL;
+ p->fr.subclass = 0;
+ return &p->fr;
+ }
+#endif
+ ast_log(LOG_WARNING, "Error reading: %s\n", strerror(errno));
+ return NULL;
+ }
+ p->fr.data = p->buf;
+ if (p->mode != MODE_FXS)
+ switch(p->buf[0] & 0x3) {
+ case '0':
+ case '1':
+ /* Normal */
+ break;
+ case '2':
+ case '3':
+ /* VAD/CNG, only send two words */
+ res = 4;
+ break;
+ }
+ p->fr.samples = 240;
+ p->fr.datalen = res;
+ p->fr.frametype = p->lastinput <= AST_FORMAT_AUDIO_MASK ?
+ AST_FRAME_VOICE :
+ p->lastinput <= AST_FORMAT_PNG ? AST_FRAME_IMAGE
+ : AST_FRAME_VIDEO;
+ p->fr.subclass = p->lastinput;
+ p->fr.offset = AST_FRIENDLY_OFFSET;
+ /* Byteswap from little-endian to native-endian */
+ if (p->fr.subclass == AST_FORMAT_SLINEAR)
+ ast_frame_byteswap_le(&p->fr);
+ return &p->fr;
+}
+
+static int phone_write_buf(struct phone_pvt *p, const char *buf, int len, int frlen, int swap)
+{
+ int res;
+ /* Store as much of the buffer as we can, then write fixed frames */
+ int space = sizeof(p->obuf) - p->obuflen;
+ /* Make sure we have enough buffer space to store the frame */
+ if (space < len)
+ len = space;
+ if (swap)
+ ast_swapcopy_samples(p->obuf+p->obuflen, buf, len/2);
+ else
+ memcpy(p->obuf + p->obuflen, buf, len);
+ p->obuflen += len;
+ while(p->obuflen > frlen) {
+ res = write(p->fd, p->obuf, frlen);
+ if (res != frlen) {
+ if (res < 1) {
+/*
+ * Card is in non-blocking mode now and it works well now, but there are
+ * lot of messages like this. So, this message is temporarily disabled.
+ */
+ return 0;
+ } else {
+ ast_log(LOG_WARNING, "Only wrote %d of %d bytes\n", res, frlen);
+ }
+ }
+ p->obuflen -= frlen;
+ /* Move memory if necessary */
+ if (p->obuflen)
+ memmove(p->obuf, p->obuf + frlen, p->obuflen);
+ }
+ return len;
+}
+
+static int phone_send_text(struct ast_channel *ast, const char *text)
+{
+ int length = strlen(text);
+ return phone_write_buf(ast->tech_pvt, text, length, length, 0) ==
+ length ? 0 : -1;
+}
+
+static int phone_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct phone_pvt *p = ast->tech_pvt;
+ int res;
+ int maxfr=0;
+ char *pos;
+ int sofar;
+ int expected;
+ int codecset = 0;
+ char tmpbuf[4];
+ /* Write a frame of (presumably voice) data */
+ if (frame->frametype != AST_FRAME_VOICE && p->mode != MODE_FXS) {
+ if (frame->frametype != AST_FRAME_IMAGE)
+ ast_log(LOG_WARNING, "Don't know what to do with frame type '%d'\n", frame->frametype);
+ return 0;
+ }
+ if (!(frame->subclass &
+ (AST_FORMAT_G723_1 | AST_FORMAT_SLINEAR | AST_FORMAT_ULAW | AST_FORMAT_G729A)) &&
+ p->mode != MODE_FXS) {
+ ast_log(LOG_WARNING, "Cannot handle frames in %d format\n", frame->subclass);
+ return -1;
+ }
+#if 0
+ /* If we're not in up mode, go into up mode now */
+ if (ast->_state != AST_STATE_UP) {
+ ast_setstate(ast, AST_STATE_UP);
+ phone_setup(ast);
+ }
+#else
+ if (ast->_state != AST_STATE_UP) {
+ /* Don't try tos end audio on-hook */
+ return 0;
+ }
+#endif
+ if (frame->subclass == AST_FORMAT_G729A) {
+ if (p->lastformat != AST_FORMAT_G729A) {
+ ioctl(p->fd, PHONE_PLAY_STOP);
+ ioctl(p->fd, PHONE_REC_STOP);
+ if (ioctl(p->fd, PHONE_PLAY_CODEC, G729)) {
+ ast_log(LOG_WARNING, "Unable to set G729 mode\n");
+ return -1;
+ }
+ if (ioctl(p->fd, PHONE_REC_CODEC, G729)) {
+ ast_log(LOG_WARNING, "Unable to set G729 mode\n");
+ return -1;
+ }
+ p->lastformat = AST_FORMAT_G729A;
+ p->lastinput = AST_FORMAT_G729A;
+ /* Reset output buffer */
+ p->obuflen = 0;
+ codecset = 1;
+ }
+ if (frame->datalen > 80) {
+ ast_log(LOG_WARNING, "Frame size too large for G.729 (%d bytes)\n", frame->datalen);
+ return -1;
+ }
+ maxfr = 80;
+ } else if (frame->subclass == AST_FORMAT_G723_1) {
+ if (p->lastformat != AST_FORMAT_G723_1) {
+ ioctl(p->fd, PHONE_PLAY_STOP);
+ ioctl(p->fd, PHONE_REC_STOP);
+ if (ioctl(p->fd, PHONE_PLAY_CODEC, G723_63)) {
+ ast_log(LOG_WARNING, "Unable to set G723.1 mode\n");
+ return -1;
+ }
+ if (ioctl(p->fd, PHONE_REC_CODEC, G723_63)) {
+ ast_log(LOG_WARNING, "Unable to set G723.1 mode\n");
+ return -1;
+ }
+ p->lastformat = AST_FORMAT_G723_1;
+ p->lastinput = AST_FORMAT_G723_1;
+ /* Reset output buffer */
+ p->obuflen = 0;
+ codecset = 1;
+ }
+ if (frame->datalen > 24) {
+ ast_log(LOG_WARNING, "Frame size too large for G.723.1 (%d bytes)\n", frame->datalen);
+ return -1;
+ }
+ maxfr = 24;
+ } else if (frame->subclass == AST_FORMAT_SLINEAR) {
+ if (p->lastformat != AST_FORMAT_SLINEAR) {
+ ioctl(p->fd, PHONE_PLAY_STOP);
+ ioctl(p->fd, PHONE_REC_STOP);
+ if (ioctl(p->fd, PHONE_PLAY_CODEC, LINEAR16)) {
+ ast_log(LOG_WARNING, "Unable to set 16-bit linear mode\n");
+ return -1;
+ }
+ if (ioctl(p->fd, PHONE_REC_CODEC, LINEAR16)) {
+ ast_log(LOG_WARNING, "Unable to set 16-bit linear mode\n");
+ return -1;
+ }
+ p->lastformat = AST_FORMAT_SLINEAR;
+ p->lastinput = AST_FORMAT_SLINEAR;
+ codecset = 1;
+ /* Reset output buffer */
+ p->obuflen = 0;
+ }
+ maxfr = 480;
+ } else if (frame->subclass == AST_FORMAT_ULAW) {
+ if (p->lastformat != AST_FORMAT_ULAW) {
+ ioctl(p->fd, PHONE_PLAY_STOP);
+ ioctl(p->fd, PHONE_REC_STOP);
+ if (ioctl(p->fd, PHONE_PLAY_CODEC, ULAW)) {
+ ast_log(LOG_WARNING, "Unable to set uLaw mode\n");
+ return -1;
+ }
+ if (ioctl(p->fd, PHONE_REC_CODEC, ULAW)) {
+ ast_log(LOG_WARNING, "Unable to set uLaw mode\n");
+ return -1;
+ }
+ p->lastformat = AST_FORMAT_ULAW;
+ p->lastinput = AST_FORMAT_ULAW;
+ codecset = 1;
+ /* Reset output buffer */
+ p->obuflen = 0;
+ }
+ maxfr = 240;
+ } else {
+ if (p->lastformat != frame->subclass) {
+ ioctl(p->fd, PHONE_PLAY_STOP);
+ ioctl(p->fd, PHONE_REC_STOP);
+ if (ioctl(p->fd, PHONE_PLAY_CODEC, frame->subclass)) {
+ ast_log(LOG_WARNING, "Unable to set %d mode\n",
+ frame->subclass);
+ return -1;
+ }
+ if (ioctl(p->fd, PHONE_REC_CODEC, frame->subclass)) {
+ ast_log(LOG_WARNING, "Unable to set %d mode\n",
+ frame->subclass);
+ return -1;
+ }
+ p->lastformat = frame->subclass;
+ p->lastinput = frame->subclass;
+ codecset = 1;
+ /* Reset output buffer */
+ p->obuflen = 0;
+ }
+ maxfr = 480;
+ }
+ if (codecset) {
+ ioctl(p->fd, PHONE_REC_DEPTH, 3);
+ ioctl(p->fd, PHONE_PLAY_DEPTH, 3);
+ if (ioctl(p->fd, PHONE_PLAY_START)) {
+ ast_log(LOG_WARNING, "Failed to start playback\n");
+ return -1;
+ }
+ if (ioctl(p->fd, PHONE_REC_START)) {
+ ast_log(LOG_WARNING, "Failed to start recording\n");
+ return -1;
+ }
+ }
+ /* If we get here, we have a frame of Appropriate data */
+ sofar = 0;
+ pos = frame->data;
+ while(sofar < frame->datalen) {
+ /* Write in no more than maxfr sized frames */
+ expected = frame->datalen - sofar;
+ if (maxfr < expected)
+ expected = maxfr;
+ /* XXX Internet Phone Jack does not handle the 4-byte VAD frame properly! XXX
+ we have to pad it to 24 bytes still. */
+ if (frame->datalen == 4) {
+ if (p->silencesupression) {
+ memset(tmpbuf + 4, 0, sizeof(tmpbuf) - 4);
+ memcpy(tmpbuf, frame->data, 4);
+ expected = 24;
+ res = phone_write_buf(p, tmpbuf, expected, maxfr, 0);
+ }
+ res = 4;
+ expected=4;
+ } else {
+ int swap = 0;
+#if __BYTE_ORDER == __BIG_ENDIAN
+ if (frame->subclass == AST_FORMAT_SLINEAR)
+ swap = 1; /* Swap big-endian samples to little-endian as we copy */
+#endif
+ res = phone_write_buf(p, pos, expected, maxfr, swap);
+ }
+ if (res != expected) {
+ if ((errno != EAGAIN) && (errno != EINTR)) {
+ if (res < 0)
+ ast_log(LOG_WARNING, "Write returned error (%s)\n", strerror(errno));
+ /*
+ * Card is in non-blocking mode now and it works well now, but there are
+ * lot of messages like this. So, this message is temporarily disabled.
+ */
+#if 0
+ else
+ ast_log(LOG_WARNING, "Only wrote %d of %d bytes\n", res, frame->datalen);
+#endif
+ return -1;
+ } else /* Pretend it worked */
+ res = expected;
+ }
+ sofar += res;
+ pos += res;
+ }
+ return 0;
+}
+
+static struct ast_channel *phone_new(struct phone_pvt *i, int state, char *context)
+{
+ struct ast_channel *tmp;
+ struct phone_codec_data codec;
+ tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, "", i->ext, i->context, 0, "Phone/%s", i->dev + 5);
+ if (tmp) {
+ tmp->tech = cur_tech;
+ ast_channel_set_fd(tmp, 0, i->fd);
+ /* XXX Switching formats silently causes kernel panics XXX */
+ if (i->mode == MODE_FXS &&
+ ioctl(i->fd, PHONE_QUERY_CODEC, &codec) == 0) {
+ if (codec.type == LINEAR16)
+ tmp->nativeformats =
+ tmp->rawreadformat =
+ tmp->rawwriteformat =
+ AST_FORMAT_SLINEAR;
+ else {
+ tmp->nativeformats =
+ tmp->rawreadformat =
+ tmp->rawwriteformat =
+ prefformat & ~AST_FORMAT_SLINEAR;
+ }
+ }
+ else {
+ tmp->nativeformats = prefformat;
+ tmp->rawreadformat = prefformat;
+ tmp->rawwriteformat = prefformat;
+ }
+ /* no need to call ast_setstate: the channel_alloc already did its job */
+ if (state == AST_STATE_RING)
+ tmp->rings = 1;
+ tmp->tech_pvt = i;
+ ast_copy_string(tmp->context, context, sizeof(tmp->context));
+ if (!ast_strlen_zero(i->ext))
+ ast_copy_string(tmp->exten, i->ext, sizeof(tmp->exten));
+ else
+ strcpy(tmp->exten, "s");
+ if (!ast_strlen_zero(i->language))
+ ast_string_field_set(tmp, language, i->language);
+
+ /* Don't use ast_set_callerid() here because it will
+ * generate a NewCallerID event before the NewChannel event */
+ tmp->cid.cid_ani = ast_strdup(i->cid_num);
+
+ i->owner = tmp;
+ ast_module_ref(ast_module_info->self);
+ if (state != AST_STATE_DOWN) {
+ if (state == AST_STATE_RING) {
+ ioctl(tmp->fds[0], PHONE_RINGBACK);
+ i->cpt = 1;
+ }
+ if (ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ ast_hangup(tmp);
+ }
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+ return tmp;
+}
+
+static void phone_mini_packet(struct phone_pvt *i)
+{
+ int res;
+ char buf[1024];
+ /* Ignore stuff we read... */
+ res = read(i->fd, buf, sizeof(buf));
+ if (res < 1) {
+ ast_log(LOG_WARNING, "Read returned %d: %s\n", res, strerror(errno));
+ return;
+ }
+}
+
+static void phone_check_exception(struct phone_pvt *i)
+{
+ int offhook=0;
+ char digit[2] = {0 , 0};
+ union telephony_exception phonee;
+ /* XXX Do something XXX */
+#if 0
+ ast_debug(1, "Exception!\n");
+#endif
+ phonee.bytes = ioctl(i->fd, PHONE_EXCEPTION);
+ if (phonee.bits.dtmf_ready) {
+ digit[0] = ioctl(i->fd, PHONE_GET_DTMF_ASCII);
+ if (i->mode == MODE_DIALTONE || i->mode == MODE_FXS || i->mode == MODE_SIGMA) {
+ ioctl(i->fd, PHONE_PLAY_STOP);
+ ioctl(i->fd, PHONE_REC_STOP);
+ ioctl(i->fd, PHONE_CPT_STOP);
+ i->dialtone = 0;
+ if (strlen(i->ext) < AST_MAX_EXTENSION - 1)
+ strncat(i->ext, digit, sizeof(i->ext) - strlen(i->ext) - 1);
+ if ((i->mode != MODE_FXS ||
+ !(phonee.bytes = ioctl(i->fd, PHONE_EXCEPTION)) ||
+ !phonee.bits.dtmf_ready) &&
+ ast_exists_extension(NULL, i->context, i->ext, 1, i->cid_num)) {
+ /* It's a valid extension in its context, get moving! */
+ phone_new(i, AST_STATE_RING, i->context);
+ /* No need to restart monitor, we are the monitor */
+ } else if (!ast_canmatch_extension(NULL, i->context, i->ext, 1, i->cid_num)) {
+ /* There is nothing in the specified extension that can match anymore.
+ Try the default */
+ if (ast_exists_extension(NULL, "default", i->ext, 1, i->cid_num)) {
+ /* Check the default, too... */
+ phone_new(i, AST_STATE_RING, "default");
+ /* XXX This should probably be justified better XXX */
+ } else if (!ast_canmatch_extension(NULL, "default", i->ext, 1, i->cid_num)) {
+ /* It's not a valid extension, give a busy signal */
+ ast_debug(1, "%s can't match anything in %s or default\n", i->ext, i->context);
+ ioctl(i->fd, PHONE_BUSY);
+ i->cpt = 1;
+ }
+ }
+#if 0
+ ast_verbose("Extension is %s\n", i->ext);
+#endif
+ }
+ }
+ if (phonee.bits.hookstate) {
+ offhook = ioctl(i->fd, PHONE_HOOKSTATE);
+ if (offhook) {
+ if (i->mode == MODE_IMMEDIATE) {
+ phone_new(i, AST_STATE_RING, i->context);
+ } else if (i->mode == MODE_DIALTONE) {
+ ast_module_ref(ast_module_info->self);
+ /* Reset the extension */
+ i->ext[0] = '\0';
+ /* Play the dialtone */
+ i->dialtone++;
+ ioctl(i->fd, PHONE_PLAY_STOP);
+ ioctl(i->fd, PHONE_PLAY_CODEC, ULAW);
+ ioctl(i->fd, PHONE_PLAY_START);
+ i->lastformat = -1;
+ } else if (i->mode == MODE_SIGMA) {
+ ast_module_ref(ast_module_info->self);
+ /* Reset the extension */
+ i->ext[0] = '\0';
+ /* Play the dialtone */
+ i->dialtone++;
+ ioctl(i->fd, PHONE_DIALTONE);
+ }
+ } else {
+ if (i->dialtone)
+ ast_module_unref(ast_module_info->self);
+ memset(i->ext, 0, sizeof(i->ext));
+ if (i->cpt)
+ {
+ ioctl(i->fd, PHONE_CPT_STOP);
+ i->cpt = 0;
+ }
+ ioctl(i->fd, PHONE_PLAY_STOP);
+ ioctl(i->fd, PHONE_REC_STOP);
+ i->dialtone = 0;
+ i->lastformat = -1;
+ }
+ }
+ if (phonee.bits.pstn_ring) {
+ ast_verbose("Unit is ringing\n");
+ phone_new(i, AST_STATE_RING, i->context);
+ }
+ if (phonee.bits.caller_id)
+ ast_verbose("We have caller ID\n");
+
+
+}
+
+static void *do_monitor(void *data)
+{
+ fd_set rfds, efds;
+ int n, res;
+ struct phone_pvt *i;
+ int tonepos = 0;
+ /* The tone we're playing this round */
+ struct timeval tv = {0,0};
+ int dotone;
+ /* This thread monitors all the frame relay interfaces which are not yet in use
+ (and thus do not have a separate thread) indefinitely */
+ while (monitor) {
+ /* Don't let anybody kill us right away. Nobody should lock the interface list
+ and wait for the monitor list, but the other way around is okay. */
+ /* Lock the interface list */
+ if (ast_mutex_lock(&iflock)) {
+ ast_log(LOG_ERROR, "Unable to grab interface lock\n");
+ return NULL;
+ }
+ /* Build the stuff we're going to select on, that is the socket of every
+ phone_pvt that does not have an associated owner channel */
+ n = -1;
+ FD_ZERO(&rfds);
+ FD_ZERO(&efds);
+ i = iflist;
+ dotone = 0;
+ while (i) {
+ if (FD_ISSET(i->fd, &rfds))
+ ast_log(LOG_WARNING, "Descriptor %d appears twice (%s)?\n", i->fd, i->dev);
+ if (!i->owner) {
+ /* This needs to be watched, as it lacks an owner */
+ FD_SET(i->fd, &rfds);
+ FD_SET(i->fd, &efds);
+ if (i->fd > n)
+ n = i->fd;
+ if (i->dialtone && i->mode != MODE_SIGMA) {
+ /* Remember we're going to have to come back and play
+ more dialtones */
+ if (ast_tvzero(tv)) {
+ /* If we're due for a dialtone, play one */
+ if (write(i->fd, DialTone + tonepos, 240) != 240)
+ ast_log(LOG_WARNING, "Dial tone write error\n");
+ }
+ dotone++;
+ }
+ }
+
+ i = i->next;
+ }
+ /* Okay, now that we know what to do, release the interface lock */
+ ast_mutex_unlock(&iflock);
+
+ /* Wait indefinitely for something to happen */
+ if (dotone && i && i->mode != MODE_SIGMA) {
+ /* If we're ready to recycle the time, set it to 30 ms */
+ tonepos += 240;
+ if (tonepos >= sizeof(DialTone))
+ tonepos = 0;
+ if (ast_tvzero(tv)) {
+ tv = ast_tv(30000, 0);
+ }
+ res = ast_select(n + 1, &rfds, NULL, &efds, &tv);
+ } else {
+ res = ast_select(n + 1, &rfds, NULL, &efds, NULL);
+ tv = ast_tv(0,0);
+ tonepos = 0;
+ }
+ /* Okay, select has finished. Let's see what happened. */
+ if (res < 0) {
+ ast_debug(1, "select return %d: %s\n", res, strerror(errno));
+ continue;
+ }
+ /* If there are no fd's changed, just continue, it's probably time
+ to play some more dialtones */
+ if (!res)
+ continue;
+ /* Alright, lock the interface list again, and let's look and see what has
+ happened */
+ if (ast_mutex_lock(&iflock)) {
+ ast_log(LOG_WARNING, "Unable to lock the interface list\n");
+ continue;
+ }
+
+ i = iflist;
+ for(; i; i=i->next) {
+ if (FD_ISSET(i->fd, &rfds)) {
+ if (i->owner) {
+ continue;
+ }
+ phone_mini_packet(i);
+ }
+ if (FD_ISSET(i->fd, &efds)) {
+ if (i->owner) {
+ continue;
+ }
+ phone_check_exception(i);
+ }
+ }
+ ast_mutex_unlock(&iflock);
+ }
+ return NULL;
+
+}
+
+static int restart_monitor()
+{
+ /* If we're supposed to be stopped -- stay stopped */
+ if (monitor_thread == AST_PTHREADT_STOP)
+ return 0;
+ if (ast_mutex_lock(&monlock)) {
+ ast_log(LOG_WARNING, "Unable to lock monitor\n");
+ return -1;
+ }
+ if (monitor_thread == pthread_self()) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_WARNING, "Cannot kill myself\n");
+ return -1;
+ }
+ if (monitor_thread != AST_PTHREADT_NULL) {
+ if (ast_mutex_lock(&iflock)) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_WARNING, "Unable to lock the interface list\n");
+ return -1;
+ }
+ monitor = 0;
+ while (pthread_kill(monitor_thread, SIGURG) == 0)
+ sched_yield();
+ pthread_join(monitor_thread, NULL);
+ ast_mutex_unlock(&iflock);
+ }
+ monitor = 1;
+ /* Start a new monitor */
+ if (ast_pthread_create_background(&monitor_thread, NULL, do_monitor, NULL) < 0) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
+ return -1;
+ }
+ ast_mutex_unlock(&monlock);
+ return 0;
+}
+
+static struct phone_pvt *mkif(const char *iface, int mode, int txgain, int rxgain)
+{
+ /* Make a phone_pvt structure for this interface */
+ struct phone_pvt *tmp;
+ int flags;
+
+ tmp = ast_calloc(1, sizeof(*tmp));
+ if (tmp) {
+ tmp->fd = open(iface, O_RDWR);
+ if (tmp->fd < 0) {
+ ast_log(LOG_WARNING, "Unable to open '%s'\n", iface);
+ ast_free(tmp);
+ return NULL;
+ }
+ if (mode == MODE_FXO) {
+ if (ioctl(tmp->fd, IXJCTL_PORT, PORT_PSTN)) {
+ ast_debug(1, "Unable to set port to PSTN\n");
+ }
+ } else {
+ if (ioctl(tmp->fd, IXJCTL_PORT, PORT_POTS))
+ if (mode != MODE_FXS)
+ ast_debug(1, "Unable to set port to POTS\n");
+ }
+ ioctl(tmp->fd, PHONE_PLAY_STOP);
+ ioctl(tmp->fd, PHONE_REC_STOP);
+ ioctl(tmp->fd, PHONE_RING_STOP);
+ ioctl(tmp->fd, PHONE_CPT_STOP);
+ if (ioctl(tmp->fd, PHONE_PSTN_SET_STATE, PSTN_ON_HOOK))
+ ast_debug(1, "ioctl(PHONE_PSTN_SET_STATE) failed on %s (%s)\n",iface, strerror(errno));
+ if (echocancel != AEC_OFF)
+ ioctl(tmp->fd, IXJCTL_AEC_START, echocancel);
+ if (silencesupression)
+ tmp->silencesupression = 1;
+#ifdef PHONE_VAD
+ ioctl(tmp->fd, PHONE_VAD, tmp->silencesupression);
+#endif
+ tmp->mode = mode;
+ flags = fcntl(tmp->fd, F_GETFL);
+ fcntl(tmp->fd, F_SETFL, flags | O_NONBLOCK);
+ tmp->owner = NULL;
+ tmp->lastformat = -1;
+ tmp->lastinput = -1;
+ tmp->ministate = 0;
+ memset(tmp->ext, 0, sizeof(tmp->ext));
+ ast_copy_string(tmp->language, language, sizeof(tmp->language));
+ ast_copy_string(tmp->dev, iface, sizeof(tmp->dev));
+ ast_copy_string(tmp->context, context, sizeof(tmp->context));
+ tmp->next = NULL;
+ tmp->obuflen = 0;
+ tmp->dialtone = 0;
+ tmp->cpt = 0;
+ ast_copy_string(tmp->cid_num, cid_num, sizeof(tmp->cid_num));
+ ast_copy_string(tmp->cid_name, cid_name, sizeof(tmp->cid_name));
+ tmp->txgain = txgain;
+ ioctl(tmp->fd, PHONE_PLAY_VOLUME, tmp->txgain);
+ tmp->rxgain = rxgain;
+ ioctl(tmp->fd, PHONE_REC_VOLUME, tmp->rxgain);
+ }
+ return tmp;
+}
+
+static struct ast_channel *phone_request(const char *type, int format, void *data, int *cause)
+{
+ int oldformat;
+ struct phone_pvt *p;
+ struct ast_channel *tmp = NULL;
+ char *name = data;
+
+ /* Search for an unowned channel */
+ if (ast_mutex_lock(&iflock)) {
+ ast_log(LOG_ERROR, "Unable to lock interface list???\n");
+ return NULL;
+ }
+ p = iflist;
+ while(p) {
+ if (p->mode == MODE_FXS ||
+ format & (AST_FORMAT_G729A | AST_FORMAT_G723_1 | AST_FORMAT_SLINEAR | AST_FORMAT_ULAW)) {
+ size_t length = strlen(p->dev + 5);
+ if (strncmp(name, p->dev + 5, length) == 0 &&
+ !isalnum(name[length])) {
+ if (!p->owner) {
+ tmp = phone_new(p, AST_STATE_DOWN, p->context);
+ break;
+ } else
+ *cause = AST_CAUSE_BUSY;
+ }
+ }
+ p = p->next;
+ }
+ ast_mutex_unlock(&iflock);
+ restart_monitor();
+ if (tmp == NULL) {
+ oldformat = format;
+ format &= (AST_FORMAT_G729A | AST_FORMAT_G723_1 | AST_FORMAT_SLINEAR | AST_FORMAT_ULAW);
+ if (!format) {
+ ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", oldformat);
+ return NULL;
+ }
+ }
+ return tmp;
+}
+
+/* parse gain value from config file */
+static int parse_gain_value(const char *gain_type, const char *value)
+{
+ float gain;
+
+ /* try to scan number */
+ if (sscanf(value, "%f", &gain) != 1)
+ {
+ ast_log(LOG_ERROR, "Invalid %s value '%s' in '%s' config\n",
+ value, gain_type, config);
+ return DEFAULT_GAIN;
+ }
+
+ /* multiplicate gain by 1.0 gain value */
+ gain = gain * (float)DEFAULT_GAIN;
+
+ /* percentage? */
+ if (value[strlen(value) - 1] == '%')
+ return (int)(gain / (float)100);
+
+ return (int)gain;
+}
+
+static int __unload_module(void)
+{
+ struct phone_pvt *p, *pl;
+ /* First, take us out of the channel loop */
+ if (cur_tech)
+ ast_channel_unregister(cur_tech);
+ if (!ast_mutex_lock(&iflock)) {
+ /* Hangup all interfaces if they have an owner */
+ p = iflist;
+ while(p) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ p = p->next;
+ }
+ iflist = NULL;
+ ast_mutex_unlock(&iflock);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the monitor\n");
+ return -1;
+ }
+ if (!ast_mutex_lock(&monlock)) {
+ if (monitor_thread > AST_PTHREADT_NULL) {
+ monitor = 0;
+ while (pthread_kill(monitor_thread, SIGURG) == 0)
+ sched_yield();
+ pthread_join(monitor_thread, NULL);
+ }
+ monitor_thread = AST_PTHREADT_STOP;
+ ast_mutex_unlock(&monlock);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the monitor\n");
+ return -1;
+ }
+
+ if (!ast_mutex_lock(&iflock)) {
+ /* Destroy all the interfaces and free their memory */
+ p = iflist;
+ while(p) {
+ /* Close the socket, assuming it's real */
+ if (p->fd > -1)
+ close(p->fd);
+ pl = p;
+ p = p->next;
+ /* Free associated memory */
+ ast_free(pl);
+ }
+ iflist = NULL;
+ ast_mutex_unlock(&iflock);
+ } else {
+ ast_log(LOG_WARNING, "Unable to lock the monitor\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ return __unload_module();
+}
+
+static int load_module(void)
+{
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ struct phone_pvt *tmp;
+ int mode = MODE_IMMEDIATE;
+ int txgain = DEFAULT_GAIN, rxgain = DEFAULT_GAIN; /* default gain 1.0 */
+ struct ast_flags config_flags = { 0 };
+
+ cfg = ast_config_load(config, config_flags);
+
+ /* We *must* have a config file otherwise stop immediately */
+ if (!cfg) {
+ ast_log(LOG_ERROR, "Unable to load config %s\n", config);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (ast_mutex_lock(&iflock)) {
+ /* It's a little silly to lock it, but we mind as well just to be sure */
+ ast_log(LOG_ERROR, "Unable to lock interface list???\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ v = ast_variable_browse(cfg, "interfaces");
+ while(v) {
+ /* Create the interface list */
+ if (!strcasecmp(v->name, "device")) {
+ tmp = mkif(v->value, mode, txgain, rxgain);
+ if (tmp) {
+ tmp->next = iflist;
+ iflist = tmp;
+
+ } else {
+ ast_log(LOG_ERROR, "Unable to register channel '%s'\n", v->value);
+ ast_config_destroy(cfg);
+ ast_mutex_unlock(&iflock);
+ __unload_module();
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ } else if (!strcasecmp(v->name, "silencesupression")) {
+ silencesupression = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "language")) {
+ ast_copy_string(language, v->value, sizeof(language));
+ } else if (!strcasecmp(v->name, "callerid")) {
+ ast_callerid_split(v->value, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num));
+ } else if (!strcasecmp(v->name, "mode")) {
+ if (!strncasecmp(v->value, "di", 2))
+ mode = MODE_DIALTONE;
+ else if (!strncasecmp(v->value, "sig", 3))
+ mode = MODE_SIGMA;
+ else if (!strncasecmp(v->value, "im", 2))
+ mode = MODE_IMMEDIATE;
+ else if (!strncasecmp(v->value, "fxs", 3)) {
+ mode = MODE_FXS;
+ prefformat = 0x01ff0000; /* All non-voice */
+ }
+ else if (!strncasecmp(v->value, "fx", 2))
+ mode = MODE_FXO;
+ else
+ ast_log(LOG_WARNING, "Unknown mode: %s\n", v->value);
+ } else if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(context, v->value, sizeof(context));
+ } else if (!strcasecmp(v->name, "format")) {
+ if (!strcasecmp(v->value, "g729")) {
+ prefformat = AST_FORMAT_G729A;
+ } else if (!strcasecmp(v->value, "g723.1")) {
+ prefformat = AST_FORMAT_G723_1;
+ } else if (!strcasecmp(v->value, "slinear")) {
+ if (mode == MODE_FXS)
+ prefformat |= AST_FORMAT_SLINEAR;
+ else prefformat = AST_FORMAT_SLINEAR;
+ } else if (!strcasecmp(v->value, "ulaw")) {
+ prefformat = AST_FORMAT_ULAW;
+ } else
+ ast_log(LOG_WARNING, "Unknown format '%s'\n", v->value);
+ } else if (!strcasecmp(v->name, "echocancel")) {
+ if (!strcasecmp(v->value, "off")) {
+ echocancel = AEC_OFF;
+ } else if (!strcasecmp(v->value, "low")) {
+ echocancel = AEC_LOW;
+ } else if (!strcasecmp(v->value, "medium")) {
+ echocancel = AEC_MED;
+ } else if (!strcasecmp(v->value, "high")) {
+ echocancel = AEC_HIGH;
+ } else
+ ast_log(LOG_WARNING, "Unknown echo cancellation '%s'\n", v->value);
+ } else if (!strcasecmp(v->name, "txgain")) {
+ txgain = parse_gain_value(v->name, v->value);
+ } else if (!strcasecmp(v->name, "rxgain")) {
+ rxgain = parse_gain_value(v->name, v->value);
+ }
+ v = v->next;
+ }
+ ast_mutex_unlock(&iflock);
+
+ if (mode == MODE_FXS) {
+ phone_tech_fxs.capabilities = prefformat;
+ cur_tech = &phone_tech_fxs;
+ } else
+ cur_tech = (struct ast_channel_tech *) &phone_tech;
+
+ /* Make sure we can register our Adtranphone channel type */
+
+ if (ast_channel_register(cur_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'Phone'\n");
+ ast_config_destroy(cfg);
+ __unload_module();
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ ast_config_destroy(cfg);
+ /* And start the monitor for the first time */
+ restart_monitor();
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Linux Telephony API Support");
diff --git a/trunk/channels/chan_sip.c b/trunk/channels/chan_sip.c
new file mode 100644
index 000000000..1d2f38feb
--- /dev/null
+++ b/trunk/channels/chan_sip.c
@@ -0,0 +1,21227 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Implementation of Session Initiation Protocol
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ *
+ * Implementation of RFC 3261 - without S/MIME, TCP and TLS support
+ * Configuration file \link Config_sip sip.conf \endlink
+ *
+ *
+ * \todo SIP over TCP
+ * \todo SIP over TLS
+ * \todo Better support of forking
+ * \todo VIA branch tag transaction checking
+ * \todo Transaction support
+ *
+ * \ingroup channel_drivers
+ *
+ * \par Overview of the handling of SIP sessions
+ * The SIP channel handles several types of SIP sessions, or dialogs,
+ * not all of them being "telephone calls".
+ * - Incoming calls that will be sent to the PBX core
+ * - Outgoing calls, generated by the PBX
+ * - SIP subscriptions and notifications of states and voicemail messages
+ * - SIP registrations, both inbound and outbound
+ * - SIP peer management (peerpoke, OPTIONS)
+ * - SIP text messages
+ *
+ * In the SIP channel, there's a list of active SIP dialogs, which includes
+ * all of these when they are active. "sip show channels" in the CLI will
+ * show most of these, excluding subscriptions which are shown by
+ * "sip show subscriptions"
+ *
+ * \par incoming packets
+ * Incoming packets are received in the monitoring thread, then handled by
+ * sipsock_read(). This function parses the packet and matches an existing
+ * dialog or starts a new SIP dialog.
+ *
+ * sipsock_read sends the packet to handle_incoming(), that parses a bit more.
+ * If it is a response to an outbound request, the packet is sent to handle_response().
+ * If it is a request, handle_incoming() sends it to one of a list of functions
+ * depending on the request type - INVITE, OPTIONS, REFER, BYE, CANCEL etc
+ * sipsock_read locks the ast_channel if it exists (an active call) and
+ * unlocks it after we have processed the SIP message.
+ *
+ * A new INVITE is sent to handle_request_invite(), that will end up
+ * starting a new channel in the PBX, the new channel after that executing
+ * in a separate channel thread. This is an incoming "call".
+ * When the call is answered, either by a bridged channel or the PBX itself
+ * the sip_answer() function is called.
+ *
+ * The actual media - Video or Audio - is mostly handled by the RTP subsystem
+ * in rtp.c
+ *
+ * \par Outbound calls
+ * Outbound calls are set up by the PBX through the sip_request_call()
+ * function. After that, they are activated by sip_call().
+ *
+ * \par Hanging up
+ * The PBX issues a hangup on both incoming and outgoing calls through
+ * the sip_hangup() function
+ */
+
+/*** MODULEINFO
+ <depend>res_features</depend>
+ ***/
+
+/*! \page sip_session_timers SIP Session Timers in Asterisk Chan_sip
+
+ The SIP Session-Timers is an extension of the SIP protocol that allows end-points and proxies to
+ refresh a session periodically. The sessions are kept alive by sending a RE-INVITE or UPDATE
+ request at a negotiated interval. If a session refresh fails then all the entities that support Session-
+ Timers clear their internal session state. In addition, UAs generate a BYE request in order to clear
+ the state in the proxies and the remote UA (this is done for the benefit of SIP entities in the path
+ that do not support Session-Timers).
+
+ The Session-Timers can be configured on a system-wide, per-user, or per-peer basis. The peruser/
+ per-peer settings override the global settings. The following new parameters have been
+ added to the sip.conf file.
+ session-timers=["accept", "originate", "refuse"]
+ session-expires=[integer]
+ session-minse=[integer]
+ session-refresher=["uas", "uac"]
+
+ The session-timers parameter in sip.conf defines the mode of operation of SIP session-timers feature in
+ Asterisk. The Asterisk can be configured in one of the following three modes:
+
+ 1. Accept :: In the "accept" mode, the Asterisk server honors session-timers requests
+ made by remote end-points. A remote end-point can request Asterisk to engage
+ session-timers by either sending it an INVITE request with a "Supported: timer"
+ header in it or by responding to Asterisk's INVITE with a 200 OK that contains
+ Session-Expires: header in it. In this mode, the Asterisk server does not
+ request session-timers from remote end-points. This is the default mode.
+ 2. Originate :: In the "originate" mode, the Asterisk server requests the remote
+ end-points to activate session-timers in addition to honoring such requests
+ made by the remote end-pints. In order to get as much protection as possible
+ against hanging SIP channels due to network or end-point failures, Asterisk
+ resends periodic re-INVITEs even if a remote end-point does not support
+ the session-timers feature.
+ 3. Refuse :: In the "refuse" mode, Asterisk acts as if it does not support session-
+ timers for inbound or outbound requests. If a remote end-point requests
+ session-timers in a dialog, then Asterisk ignores that request unless it's
+ noted as a requirement (Require: header), in which case the INVITE is
+ rejected with a 420 Bad Extension response.
+
+*/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/signal.h>
+#include <regex.h>
+
+#include "asterisk/network.h"
+#include "asterisk/paths.h" /* need ast_config_AST_SYSTEM_NAME */
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/rtp.h"
+#include "asterisk/udptl.h"
+#include "asterisk/acl.h"
+#include "asterisk/manager.h"
+#include "asterisk/callerid.h"
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/dsp.h"
+#include "asterisk/features.h"
+#include "asterisk/srv.h"
+#include "asterisk/astdb.h"
+#include "asterisk/causes.h"
+#include "asterisk/utils.h"
+#include "asterisk/file.h"
+#include "asterisk/astobj.h"
+#include "asterisk/dnsmgr.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/monitor.h"
+#include "asterisk/netsock.h"
+#include "asterisk/localtime.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/threadstorage.h"
+#include "asterisk/translate.h"
+#include "asterisk/version.h"
+#include "asterisk/event.h"
+#include "asterisk/tcptls.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define XMIT_ERROR -2
+
+/* #define VOCAL_DATA_HACK */
+
+#define DEFAULT_DEFAULT_EXPIRY 120
+#define DEFAULT_MIN_EXPIRY 60
+#define DEFAULT_MAX_EXPIRY 3600
+#define DEFAULT_REGISTRATION_TIMEOUT 20
+#define DEFAULT_MAX_FORWARDS "70"
+
+/* guard limit must be larger than guard secs */
+/* guard min must be < 1000, and should be >= 250 */
+#define EXPIRY_GUARD_SECS 15 /*!< How long before expiry do we reregister */
+#define EXPIRY_GUARD_LIMIT 30 /*!< Below here, we use EXPIRY_GUARD_PCT instead of
+ EXPIRY_GUARD_SECS */
+#define EXPIRY_GUARD_MIN 500 /*!< This is the minimum guard time applied. If
+ GUARD_PCT turns out to be lower than this, it
+ will use this time instead.
+ This is in milliseconds. */
+#define EXPIRY_GUARD_PCT 0.20 /*!< Percentage of expires timeout to use when
+ below EXPIRY_GUARD_LIMIT */
+#define DEFAULT_EXPIRY 900 /*!< Expire slowly */
+
+static int min_expiry = DEFAULT_MIN_EXPIRY; /*!< Minimum accepted registration time */
+static int max_expiry = DEFAULT_MAX_EXPIRY; /*!< Maximum accepted registration time */
+static int default_expiry = DEFAULT_DEFAULT_EXPIRY;
+static int expiry = DEFAULT_EXPIRY;
+
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+#define CALLERID_UNKNOWN "Unknown"
+
+#define DEFAULT_MAXMS 2000 /*!< Qualification: Must be faster than 2 seconds by default */
+#define DEFAULT_QUALIFYFREQ 60 * 1000 /*!< Qualification: How often to check for the host to be up */
+#define DEFAULT_FREQ_NOTOK 10 * 1000 /*!< Qualification: How often to check, if the host is down... */
+
+#define DEFAULT_RETRANS 1000 /*!< How frequently to retransmit Default: 2 * 500 ms in RFC 3261 */
+#define MAX_RETRANS 6 /*!< Try only 6 times for retransmissions, a total of 7 transmissions */
+#define SIP_TIMER_T1 500 /* SIP timer T1 (according to RFC 3261) */
+#define SIP_TRANS_TIMEOUT 64 * SIP_TIMER_T1/*!< SIP request timeout (rfc 3261) 64*T1
+ \todo Use known T1 for timeout (peerpoke)
+ */
+#define DEFAULT_TRANS_TIMEOUT -1 /* Use default SIP transaction timeout */
+#define MAX_AUTHTRIES 3 /*!< Try authentication three times, then fail */
+
+#define SIP_MAX_HEADERS 64 /*!< Max amount of SIP headers to read */
+#define SIP_MAX_LINES 64 /*!< Max amount of lines in SIP attachment (like SDP) */
+#define SIP_MAX_PACKET 4096 /*!< Also from RFC 3261 (2543), should sub headers tho */
+
+#define INITIAL_CSEQ 101 /*!< our initial sip sequence number */
+
+#define DEFAULT_MAX_SE 1800 /*!< Session-Timer Default Session-Expires period (RFC 4028) */
+#define DEFAULT_MIN_SE 90 /*!< Session-Timer Default Min-SE period (RFC 4028) */
+
+/*! \brief Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = ""
+};
+static struct ast_jb_conf global_jbconf; /*!< Global jitterbuffer configuration */
+
+static const char config[] = "sip.conf"; /*!< Main configuration file */
+static const char notify_config[] = "sip_notify.conf"; /*!< Configuration file for sending Notify with CLI commands to reconfigure or reboot phones */
+
+#define RTP 1
+#define NO_RTP 0
+
+/*! \brief Authorization scheme for call transfers
+\note Not a bitfield flag, since there are plans for other modes,
+ like "only allow transfers for authenticated devices" */
+enum transfermodes {
+ TRANSFER_OPENFORALL, /*!< Allow all SIP transfers */
+ TRANSFER_CLOSED, /*!< Allow no SIP transfers */
+};
+
+
+enum sip_result {
+ AST_SUCCESS = 0,
+ AST_FAILURE = -1,
+};
+
+/*! \brief States for the INVITE transaction, not the dialog
+ \note this is for the INVITE that sets up the dialog
+*/
+enum invitestates {
+ INV_NONE = 0, /*!< No state at all, maybe not an INVITE dialog */
+ INV_CALLING = 1, /*!< Invite sent, no answer */
+ INV_PROCEEDING = 2, /*!< We got/sent 1xx message */
+ INV_EARLY_MEDIA = 3, /*!< We got 18x message with to-tag back */
+ INV_COMPLETED = 4, /*!< Got final response with error. Wait for ACK, then CONFIRMED */
+ INV_CONFIRMED = 5, /*!< Confirmed response - we've got an ack (Incoming calls only) */
+ INV_TERMINATED = 6, /*!< Transaction done - either successful (AST_STATE_UP) or failed, but done
+ The only way out of this is a BYE from one side */
+ INV_CANCELLED = 7, /*!< Transaction cancelled by client or server in non-terminated state */
+};
+
+enum xmittype {
+ XMIT_CRITICAL = 2, /*!< Transmit critical SIP message reliably, with re-transmits.
+ If it fails, it's critical and will cause a teardown of the session */
+ XMIT_RELIABLE = 1, /*!< Transmit SIP message reliably, with re-transmits */
+ XMIT_UNRELIABLE = 0, /*!< Transmit SIP message without bothering with re-transmits */
+};
+
+enum parse_register_result {
+ PARSE_REGISTER_FAILED,
+ PARSE_REGISTER_UPDATE,
+ PARSE_REGISTER_QUERY,
+};
+
+enum subscriptiontype {
+ NONE = 0,
+ XPIDF_XML,
+ DIALOG_INFO_XML,
+ CPIM_PIDF_XML,
+ PIDF_XML,
+ MWI_NOTIFICATION
+};
+
+/*! \brief Subscription types that we support. We support
+ - dialoginfo updates (really device status, not dialog info as was the original intent of the standard)
+ - SIMPLE presence used for device status
+ - Voicemail notification subscriptions
+*/
+static const struct cfsubscription_types {
+ enum subscriptiontype type;
+ const char * const event;
+ const char * const mediatype;
+ const char * const text;
+} subscription_types[] = {
+ { NONE, "-", "unknown", "unknown" },
+ /* RFC 4235: SIP Dialog event package */
+ { DIALOG_INFO_XML, "dialog", "application/dialog-info+xml", "dialog-info+xml" },
+ { CPIM_PIDF_XML, "presence", "application/cpim-pidf+xml", "cpim-pidf+xml" }, /* RFC 3863 */
+ { PIDF_XML, "presence", "application/pidf+xml", "pidf+xml" }, /* RFC 3863 */
+ { XPIDF_XML, "presence", "application/xpidf+xml", "xpidf+xml" }, /* Pre-RFC 3863 with MS additions */
+ { MWI_NOTIFICATION, "message-summary", "application/simple-message-summary", "mwi" } /* RFC 3842: Mailbox notification */
+};
+
+
+/*! \brief Authentication types - proxy or www authentication
+ \note Endpoints, like Asterisk, should always use WWW authentication to
+ allow multiple authentications in the same call - to the proxy and
+ to the end point.
+*/
+enum sip_auth_type {
+ PROXY_AUTH = 407,
+ WWW_AUTH = 401,
+};
+
+/*! \brief Authentication result from check_auth* functions */
+enum check_auth_result {
+ AUTH_DONT_KNOW = -100, /*!< no result, need to check further */
+ /* XXX maybe this is the same as AUTH_NOT_FOUND */
+
+ AUTH_SUCCESSFUL = 0,
+ AUTH_CHALLENGE_SENT = 1,
+ AUTH_SECRET_FAILED = -1,
+ AUTH_USERNAME_MISMATCH = -2,
+ AUTH_NOT_FOUND = -3, /*!< returned by register_verify */
+ AUTH_FAKE_AUTH = -4,
+ AUTH_UNKNOWN_DOMAIN = -5,
+ AUTH_PEER_NOT_DYNAMIC = -6,
+ AUTH_ACL_FAILED = -7,
+};
+
+/*! \brief States for outbound registrations (with register= lines in sip.conf */
+enum sipregistrystate {
+ REG_STATE_UNREGISTERED = 0, /*!< We are not registred */
+ /* Initial state. We should have a timeout scheduled for the initial
+ * (or next) registration transmission, calling sip_reregister
+ */
+
+ REG_STATE_REGSENT, /*!< Registration request sent */
+ /* sent initial request, waiting for an ack or a timeout to
+ * retransmit the initial request.
+ */
+
+ REG_STATE_AUTHSENT, /*!< We have tried to authenticate */
+ /* entered after transmit_register with auth info,
+ * waiting for an ack.
+ */
+
+ REG_STATE_REGISTERED, /*!< Registered and done */
+
+ REG_STATE_REJECTED, /*!< Registration rejected */
+ /* only used when the remote party has an expire larger than
+ * our max-expire. This is a final state from which we do not
+ * recover (not sure how correctly).
+ */
+
+ REG_STATE_TIMEOUT, /*!< Registration timed out */
+ /* XXX unused */
+
+ REG_STATE_NOAUTH, /*!< We have no accepted credentials */
+ /* fatal - no chance to proceed */
+
+ REG_STATE_FAILED, /*!< Registration failed after several tries */
+ /* fatal - no chance to proceed */
+};
+
+/*! \brief Modes in which Asterisk can be configured to run SIP Session-Timers */
+enum st_mode {
+ SESSION_TIMER_MODE_INVALID = 0, /*!< Invalid value */
+ SESSION_TIMER_MODE_ACCEPT, /*!< Honor inbound Session-Timer requests */
+ SESSION_TIMER_MODE_ORIGINATE, /*!< Originate outbound and honor inbound requests */
+ SESSION_TIMER_MODE_REFUSE /*!< Ignore inbound Session-Timers requests */
+};
+
+/*! \brief The entity playing the refresher role for Session-Timers */
+enum st_refresher {
+ SESSION_TIMER_REFRESHER_AUTO, /*!< Negotiated */
+ SESSION_TIMER_REFRESHER_UAC, /*!< Session is refreshed by the UAC */
+ SESSION_TIMER_REFRESHER_UAS /*!< Session is refreshed by the UAS */
+};
+
+
+/*! \brief definition of a sip proxy server
+ *
+ * For outbound proxies, this is allocated in the SIP peer dynamically or
+ * statically as the global_outboundproxy. The pointer in a SIP message is just
+ * a pointer and should *not* be de-allocated.
+ */
+struct sip_proxy {
+ char name[MAXHOSTNAMELEN]; /*!< DNS name of domain/host or IP */
+ struct sockaddr_in ip; /*!< Currently used IP address and port */
+ time_t last_dnsupdate; /*!< When this was resolved */
+ int force; /*!< If it's an outbound proxy, Force use of this outbound proxy for all outbound requests */
+ /* Room for a SRV record chain based on the name */
+};
+
+/*! \brief States whether a SIP message can create a dialog in Asterisk. */
+enum can_create_dialog {
+ CAN_NOT_CREATE_DIALOG,
+ CAN_CREATE_DIALOG,
+ CAN_CREATE_DIALOG_UNSUPPORTED_METHOD,
+};
+
+/*! \brief SIP Request methods known by Asterisk
+
+ \note Do _NOT_ make any changes to this enum, or the array following it;
+ if you think you are doing the right thing, you are probably
+ not doing the right thing. If you think there are changes
+ needed, get someone else to review them first _before_
+ submitting a patch. If these two lists do not match properly
+ bad things will happen.
+*/
+
+enum sipmethod {
+ SIP_UNKNOWN, /*!< Unknown response */
+ SIP_RESPONSE, /*!< Not request, response to outbound request */
+ SIP_REGISTER, /*!< Registration to the mothership, tell us where you are located */
+ SIP_OPTIONS, /*!< Check capabilities of a device, used for "ping" too */
+ SIP_NOTIFY, /*!< Status update, Part of the event package standard, result of a SUBSCRIBE or a REFER */
+ SIP_INVITE, /*!< Set up a session */
+ SIP_ACK, /*!< End of a three-way handshake started with INVITE. */
+ SIP_PRACK, /*!< Reliable pre-call signalling. Not supported in Asterisk. */
+ SIP_BYE, /*!< End of a session */
+ SIP_REFER, /*!< Refer to another URI (transfer) */
+ SIP_SUBSCRIBE, /*!< Subscribe for updates (voicemail, session status, device status, presence) */
+ SIP_MESSAGE, /*!< Text messaging */
+ SIP_UPDATE, /*!< Update a dialog. We can send UPDATE; but not accept it */
+ SIP_INFO, /*!< Information updates during a session */
+ SIP_CANCEL, /*!< Cancel an INVITE */
+ SIP_PUBLISH, /*!< Not supported in Asterisk */
+ SIP_PING, /*!< Not supported at all, no standard but still implemented out there */
+};
+
+/*! \brief The core structure to setup dialogs. We parse incoming messages by using
+ structure and then route the messages according to the type.
+
+ \note Note that sip_methods[i].id == i must hold or the code breaks */
+static const struct cfsip_methods {
+ enum sipmethod id;
+ int need_rtp; /*!< when this is the 'primary' use for a pvt structure, does it need RTP? */
+ char * const text;
+ enum can_create_dialog can_create;
+} sip_methods[] = {
+ { SIP_UNKNOWN, RTP, "-UNKNOWN-", CAN_CREATE_DIALOG },
+ { SIP_RESPONSE, NO_RTP, "SIP/2.0", CAN_NOT_CREATE_DIALOG },
+ { SIP_REGISTER, NO_RTP, "REGISTER", CAN_CREATE_DIALOG },
+ { SIP_OPTIONS, NO_RTP, "OPTIONS", CAN_CREATE_DIALOG },
+ { SIP_NOTIFY, NO_RTP, "NOTIFY", CAN_CREATE_DIALOG },
+ { SIP_INVITE, RTP, "INVITE", CAN_CREATE_DIALOG },
+ { SIP_ACK, NO_RTP, "ACK", CAN_NOT_CREATE_DIALOG },
+ { SIP_PRACK, NO_RTP, "PRACK", CAN_NOT_CREATE_DIALOG },
+ { SIP_BYE, NO_RTP, "BYE", CAN_NOT_CREATE_DIALOG },
+ { SIP_REFER, NO_RTP, "REFER", CAN_CREATE_DIALOG },
+ { SIP_SUBSCRIBE, NO_RTP, "SUBSCRIBE", CAN_CREATE_DIALOG },
+ { SIP_MESSAGE, NO_RTP, "MESSAGE", CAN_CREATE_DIALOG },
+ { SIP_UPDATE, NO_RTP, "UPDATE", CAN_NOT_CREATE_DIALOG },
+ { SIP_INFO, NO_RTP, "INFO", CAN_NOT_CREATE_DIALOG },
+ { SIP_CANCEL, NO_RTP, "CANCEL", CAN_NOT_CREATE_DIALOG },
+ { SIP_PUBLISH, NO_RTP, "PUBLISH", CAN_CREATE_DIALOG_UNSUPPORTED_METHOD },
+ { SIP_PING, NO_RTP, "PING", CAN_CREATE_DIALOG_UNSUPPORTED_METHOD }
+};
+
+/*! Define SIP option tags, used in Require: and Supported: headers
+ We need to be aware of these properties in the phones to use
+ the replace: header. We should not do that without knowing
+ that the other end supports it...
+ This is nothing we can configure, we learn by the dialog
+ Supported: header on the REGISTER (peer) or the INVITE
+ (other devices)
+ We are not using many of these today, but will in the future.
+ This is documented in RFC 3261
+*/
+#define SUPPORTED 1
+#define NOT_SUPPORTED 0
+
+/* SIP options */
+#define SIP_OPT_REPLACES (1 << 0)
+#define SIP_OPT_100REL (1 << 1)
+#define SIP_OPT_TIMER (1 << 2)
+#define SIP_OPT_EARLY_SESSION (1 << 3)
+#define SIP_OPT_JOIN (1 << 4)
+#define SIP_OPT_PATH (1 << 5)
+#define SIP_OPT_PREF (1 << 6)
+#define SIP_OPT_PRECONDITION (1 << 7)
+#define SIP_OPT_PRIVACY (1 << 8)
+#define SIP_OPT_SDP_ANAT (1 << 9)
+#define SIP_OPT_SEC_AGREE (1 << 10)
+#define SIP_OPT_EVENTLIST (1 << 11)
+#define SIP_OPT_GRUU (1 << 12)
+#define SIP_OPT_TARGET_DIALOG (1 << 13)
+#define SIP_OPT_NOREFERSUB (1 << 14)
+#define SIP_OPT_HISTINFO (1 << 15)
+#define SIP_OPT_RESPRIORITY (1 << 16)
+#define SIP_OPT_UNKNOWN (1 << 17)
+
+
+/*! \brief List of well-known SIP options. If we get this in a require,
+ we should check the list and answer accordingly. */
+static const struct cfsip_options {
+ int id; /*!< Bitmap ID */
+ int supported; /*!< Supported by Asterisk ? */
+ char * const text; /*!< Text id, as in standard */
+} sip_options[] = { /* XXX used in 3 places */
+ /* RFC3891: Replaces: header for transfer */
+ { SIP_OPT_REPLACES, SUPPORTED, "replaces" },
+ /* One version of Polycom firmware has the wrong label */
+ { SIP_OPT_REPLACES, SUPPORTED, "replace" },
+ /* RFC3262: PRACK 100% reliability */
+ { SIP_OPT_100REL, NOT_SUPPORTED, "100rel" },
+ /* RFC4028: SIP Session-Timers */
+ { SIP_OPT_TIMER, SUPPORTED, "timer" },
+ /* RFC3959: SIP Early session support */
+ { SIP_OPT_EARLY_SESSION, NOT_SUPPORTED, "early-session" },
+ /* RFC3911: SIP Join header support */
+ { SIP_OPT_JOIN, NOT_SUPPORTED, "join" },
+ /* RFC3327: Path support */
+ { SIP_OPT_PATH, NOT_SUPPORTED, "path" },
+ /* RFC3840: Callee preferences */
+ { SIP_OPT_PREF, NOT_SUPPORTED, "pref" },
+ /* RFC3312: Precondition support */
+ { SIP_OPT_PRECONDITION, NOT_SUPPORTED, "precondition" },
+ /* RFC3323: Privacy with proxies*/
+ { SIP_OPT_PRIVACY, NOT_SUPPORTED, "privacy" },
+ /* RFC4092: Usage of the SDP ANAT Semantics in the SIP */
+ { SIP_OPT_SDP_ANAT, NOT_SUPPORTED, "sdp-anat" },
+ /* RFC3329: Security agreement mechanism */
+ { SIP_OPT_SEC_AGREE, NOT_SUPPORTED, "sec_agree" },
+ /* SIMPLE events: RFC4662 */
+ { SIP_OPT_EVENTLIST, NOT_SUPPORTED, "eventlist" },
+ /* GRUU: Globally Routable User Agent URI's */
+ { SIP_OPT_GRUU, NOT_SUPPORTED, "gruu" },
+ /* RFC4538: Target-dialog */
+ { SIP_OPT_TARGET_DIALOG,NOT_SUPPORTED, "tdialog" },
+ /* Disable the REFER subscription, RFC 4488 */
+ { SIP_OPT_NOREFERSUB, NOT_SUPPORTED, "norefersub" },
+ /* ietf-sip-history-info-06.txt */
+ { SIP_OPT_HISTINFO, NOT_SUPPORTED, "histinfo" },
+ /* ietf-sip-resource-priority-10.txt */
+ { SIP_OPT_RESPRIORITY, NOT_SUPPORTED, "resource-priority" },
+};
+
+
+/*! \brief SIP Methods we support
+ \todo This string should be set dynamically. We only support REFER and SUBSCRIBE is we have
+ allowsubscribe and allowrefer on in sip.conf.
+*/
+#define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY"
+
+/*! \brief SIP Extensions we support */
+#define SUPPORTED_EXTENSIONS "replaces, timer"
+
+/*! \brief Standard SIP and TLS port from RFC 3261. DO NOT CHANGE THIS */
+#define STANDARD_SIP_PORT 5060
+#define STANDARD_TLS_PORT 5061
+/* Note: in many SIP headers, absence of a port number implies port 5060,
+ * and this is why we cannot change the above constant.
+ * There is a limited number of places in asterisk where we could,
+ * in principle, use a different "default" port number, but
+ * we do not support this feature at the moment.
+ * You can run Asterisk with SIP on a different port with a configuration
+ * option. If you change this value, the signalling will be incorrect.
+ */
+
+/*! \name DefaultValues Default values, set and reset in reload_config before reading configuration
+
+ These are default values in the source. There are other recommended values in the
+ sip.conf.sample for new installations. These may differ to keep backwards compatibility,
+ yet encouraging new behaviour on new installations
+ */
+/*@{*/
+#define DEFAULT_CONTEXT "default"
+#define DEFAULT_MOHINTERPRET "default"
+#define DEFAULT_MOHSUGGEST ""
+#define DEFAULT_VMEXTEN "asterisk"
+#define DEFAULT_CALLERID "asterisk"
+#define DEFAULT_NOTIFYMIME "application/simple-message-summary"
+#define DEFAULT_ALLOWGUEST TRUE
+#define DEFAULT_CALLCOUNTER FALSE
+#define DEFAULT_SRVLOOKUP TRUE /*!< Recommended setting is ON */
+#define DEFAULT_COMPACTHEADERS FALSE
+#define DEFAULT_TOS_SIP 0 /*!< Call signalling packets should be marked as DSCP CS3, but the default is 0 to be compatible with previous versions. */
+#define DEFAULT_TOS_AUDIO 0 /*!< Audio packets should be marked as DSCP EF (Expedited Forwarding), but the default is 0 to be compatible with previous versions. */
+#define DEFAULT_TOS_VIDEO 0 /*!< Video packets should be marked as DSCP AF41, but the default is 0 to be compatible with previous versions. */
+#define DEFAULT_TOS_TEXT 0 /*!< Text packets should be marked as XXXX XXXX, but the default is 0 to be compatible with previous versions. */
+#define DEFAULT_COS_SIP 4
+#define DEFAULT_COS_AUDIO 5
+#define DEFAULT_COS_VIDEO 6
+#define DEFAULT_COS_TEXT 5
+#define DEFAULT_ALLOW_EXT_DOM TRUE
+#define DEFAULT_REALM "asterisk"
+#define DEFAULT_NOTIFYRINGING TRUE
+#define DEFAULT_PEDANTIC FALSE
+#define DEFAULT_AUTOCREATEPEER FALSE
+#define DEFAULT_QUALIFY FALSE
+#define DEFAULT_REGEXTENONQUALIFY FALSE
+#define DEFAULT_T1MIN 100 /*!< 100 MS for minimal roundtrip time */
+#define DEFAULT_MAX_CALL_BITRATE (384) /*!< Max bitrate for video */
+#ifndef DEFAULT_USERAGENT
+#define DEFAULT_USERAGENT "Asterisk PBX" /*!< Default Useragent: header unless re-defined in sip.conf */
+#define DEFAULT_SDPSESSION "Asterisk PBX" /*!< Default SDP session name, (s=) header unless re-defined in sip.conf */
+#define DEFAULT_SDPOWNER "root" /*!< Default SDP username field in (o=) header unless re-defined in sip.conf */
+#endif
+/*@}*/
+
+/*! \name DefaultSettings
+ Default setttings are used as a channel setting and as a default when
+ configuring devices
+*/
+/*@{*/
+static char default_context[AST_MAX_CONTEXT];
+static char default_subscribecontext[AST_MAX_CONTEXT];
+static char default_language[MAX_LANGUAGE];
+static char default_callerid[AST_MAX_EXTENSION];
+static char default_fromdomain[AST_MAX_EXTENSION];
+static char default_notifymime[AST_MAX_EXTENSION];
+static int default_qualify; /*!< Default Qualify= setting */
+static char default_vmexten[AST_MAX_EXTENSION];
+static char default_mohinterpret[MAX_MUSICCLASS]; /*!< Global setting for moh class to use when put on hold */
+static char default_mohsuggest[MAX_MUSICCLASS]; /*!< Global setting for moh class to suggest when putting
+ * a bridged channel on hold */
+static int default_maxcallbitrate; /*!< Maximum bitrate for call */
+static struct ast_codec_pref default_prefs; /*!< Default codec prefs */
+
+/*! \brief a place to store all global settings for the sip channel driver */
+struct sip_settings {
+ int peer_rtupdate; /*!< G: Update database with registration data for peer? */
+ int rtsave_sysname; /*!< G: Save system name at registration? */
+ int ignore_regexpire; /*!< G: Ignore expiration of peer */
+};
+
+static struct sip_settings sip_cfg;
+/*@}*/
+
+/*! \name GlobalSettings
+ Global settings apply to the channel (often settings you can change in the general section
+ of sip.conf
+*/
+/*@{*/
+static int global_directrtpsetup; /*!< Enable support for Direct RTP setup (no re-invites) */
+static int global_limitonpeers; /*!< Match call limit on peers only */
+static int global_rtautoclear; /*!< Realtime ?? */
+static int global_notifyringing; /*!< Send notifications on ringing */
+static int global_notifyhold; /*!< Send notifications on hold */
+static int global_alwaysauthreject; /*!< Send 401 Unauthorized for all failing requests */
+static int global_srvlookup; /*!< SRV Lookup on or off. Default is on */
+static int pedanticsipchecking; /*!< Extra checking ? Default off */
+static int autocreatepeer; /*!< Auto creation of peers at registration? Default off. */
+static int global_match_auth_username; /*!< Match auth username if available instead of From: Default off. */
+static int global_relaxdtmf; /*!< Relax DTMF */
+static int global_rtptimeout; /*!< Time out call if no RTP */
+static int global_rtpholdtimeout; /*!< Time out call if no RTP during hold */
+static int global_rtpkeepalive; /*!< Send RTP keepalives */
+static int global_reg_timeout;
+static int global_regattempts_max; /*!< Registration attempts before giving up */
+static int global_allowguest; /*!< allow unauthenticated users/peers to connect? */
+static int global_callcounter; /*!< Enable call counters for all devices. This is currently enabled by setting the peer
+ call-limit to 999. When we remove the call-limit from the code, we can make it
+ with just a boolean flag in the device structure */
+static int global_allowsubscribe; /*!< Flag for disabling ALL subscriptions, this is FALSE only if all peers are FALSE
+ the global setting is in globals_flags[1] */
+static unsigned int global_tos_sip; /*!< IP type of service for SIP packets */
+static unsigned int global_tos_audio; /*!< IP type of service for audio RTP packets */
+static unsigned int global_tos_video; /*!< IP type of service for video RTP packets */
+static unsigned int global_tos_text; /*!< IP type of service for text RTP packets */
+static unsigned int global_cos_sip; /*!< 802.1p class of service for SIP packets */
+static unsigned int global_cos_audio; /*!< 802.1p class of service for audio RTP packets */
+static unsigned int global_cos_video; /*!< 802.1p class of service for video RTP packets */
+static unsigned int global_cos_text; /*!< 802.1p class of service for text RTP packets */
+static int compactheaders; /*!< send compact sip headers */
+static int recordhistory; /*!< Record SIP history. Off by default */
+static int dumphistory; /*!< Dump history to verbose before destroying SIP dialog */
+static char global_realm[MAXHOSTNAMELEN]; /*!< Default realm */
+static char global_regcontext[AST_MAX_CONTEXT]; /*!< Context for auto-extensions */
+static char global_useragent[AST_MAX_EXTENSION]; /*!< Useragent for the SIP channel */
+static char global_sdpsession[AST_MAX_EXTENSION]; /*!< SDP session name for the SIP channel */
+static char global_sdpowner[AST_MAX_EXTENSION]; /*!< SDP owner name for the SIP channel */
+static int allow_external_domains; /*!< Accept calls to external SIP domains? */
+static int global_callevents; /*!< Whether we send manager events or not */
+static int global_t1; /*!< T1 time */
+static int global_t1min; /*!< T1 roundtrip time minimum */
+static int global_timer_b; /*!< Timer B - RFC 3261 Section 17.1.1.2 */
+static int global_regextenonqualify; /*!< Whether to add/remove regexten when qualifying peers */
+static int global_autoframing; /*!< Turn autoframing on or off. */
+static enum transfermodes global_allowtransfer; /*!< SIP Refer restriction scheme */
+static struct sip_proxy global_outboundproxy; /*!< Outbound proxy */
+static int global_matchexterniplocally; /*!< Match externip/externhost setting against localnet setting */
+static int global_qualifyfreq; /*!< Qualify frequency */
+
+
+/*! \brief Codecs that we support by default: */
+static int global_capability = AST_FORMAT_ULAW | AST_FORMAT_ALAW | AST_FORMAT_GSM | AST_FORMAT_H263;
+/*@}*/
+
+/* Object counters */
+static int suserobjs = 0; /*!< Static users */
+static int ruserobjs = 0; /*!< Realtime users */
+static int speerobjs = 0; /*!< Statis peers */
+static int rpeerobjs = 0; /*!< Realtime peers */
+static int apeerobjs = 0; /*!< Autocreated peer objects */
+static int regobjs = 0; /*!< Registry objects */
+
+static struct ast_flags global_flags[2] = {{0}}; /*!< global SIP_ flags */
+static char used_context[AST_MAX_CONTEXT]; /*!< name of automatically created context for unloading */
+
+static enum st_mode global_st_mode; /*!< Mode of operation for Session-Timers */
+static enum st_refresher global_st_refresher; /*!< Session-Timer refresher */
+static int global_min_se; /*!< Lowest threshold for session refresh interval */
+static int global_max_se; /*!< Highest threshold for session refresh interval */
+
+
+AST_MUTEX_DEFINE_STATIC(netlock);
+
+/*! \brief Protect the monitoring thread, so only one process can kill or start it, and not
+ when it's doing something critical. */
+
+AST_MUTEX_DEFINE_STATIC(monlock);
+
+AST_MUTEX_DEFINE_STATIC(sip_reload_lock);
+
+/*! \brief This is the thread for the monitor which checks for input on the channels
+ which are not currently in use. */
+static pthread_t monitor_thread = AST_PTHREADT_NULL;
+
+static int sip_reloading = FALSE; /*!< Flag for avoiding multiple reloads at the same time */
+static enum channelreloadreason sip_reloadreason; /*!< Reason for last reload/load of configuration */
+
+static struct sched_context *sched; /*!< The scheduling context */
+static struct io_context *io; /*!< The IO context */
+static int *sipsock_read_id; /*!< ID of IO entry for sipsock FD */
+
+#define DEC_CALL_LIMIT 0
+#define INC_CALL_LIMIT 1
+#define DEC_CALL_RINGING 2
+#define INC_CALL_RINGING 3
+
+/*!< Define some SIP transports */
+enum sip_transport {
+ SIP_TRANSPORT_UDP = 1,
+ SIP_TRANSPORT_TCP = 1 << 1,
+ SIP_TRANSPORT_TLS = 1 << 2,
+};
+
+struct sip_socket {
+ ast_mutex_t *lock;
+ enum sip_transport type;
+ int fd;
+ uint16_t port;
+ struct server_instance *ser;
+};
+
+/*! \brief sip_request: The data grabbed from the UDP socket
+ *
+ * \verbatim
+ * Incoming messages: we first store the data from the socket in data[],
+ * adding a trailing \0 to make string parsing routines happy.
+ * Then call parse_request() and req.method = find_sip_method();
+ * to initialize the other fields. The \r\n at the end of each line is
+ * replaced by \0, so that data[] is not a conforming SIP message anymore.
+ * After this processing, rlPart1 is set to non-NULL to remember
+ * that we can run get_header() on this kind of packet.
+ *
+ * parse_request() splits the first line as follows:
+ * Requests have in the first line method uri SIP/2.0
+ * rlPart1 = method; rlPart2 = uri;
+ * Responses have in the first line SIP/2.0 NNN description
+ * rlPart1 = SIP/2.0; rlPart2 = NNN + description;
+ *
+ * For outgoing packets, we initialize the fields with init_req() or init_resp()
+ * (which fills the first line to "METHOD uri SIP/2.0" or "SIP/2.0 code text"),
+ * and then fill the rest with add_header() and add_line().
+ * The \r\n at the end of the line are still there, so the get_header()
+ * and similar functions don't work on these packets.
+ * \endverbatim
+ */
+struct sip_request {
+ char *rlPart1; /*!< SIP Method Name or "SIP/2.0" protocol version */
+ char *rlPart2; /*!< The Request URI or Response Status */
+ int len; /*!< bytes used in data[], excluding trailing null terminator. Rarely used. */
+ int headers; /*!< # of SIP Headers */
+ int method; /*!< Method of this request */
+ int lines; /*!< Body Content */
+ unsigned int sdp_start; /*!< the line number where the SDP begins */
+ unsigned int sdp_end; /*!< the line number where the SDP ends */
+ char debug; /*!< print extra debugging if non zero */
+ char has_to_tag; /*!< non-zero if packet has To: tag */
+ char ignore; /*!< if non-zero This is a re-transmit, ignore it */
+ char *header[SIP_MAX_HEADERS];
+ char *line[SIP_MAX_LINES];
+ char data[SIP_MAX_PACKET];
+ struct sip_socket socket;
+};
+
+/*! \brief structure used in transfers */
+struct sip_dual {
+ struct ast_channel *chan1; /*!< First channel involved */
+ struct ast_channel *chan2; /*!< Second channel involved */
+ struct sip_request req; /*!< Request that caused the transfer (REFER) */
+ int seqno; /*!< Sequence number */
+};
+
+struct sip_pkt;
+
+/*! \brief Parameters to the transmit_invite function */
+struct sip_invite_param {
+ int addsipheaders; /*!< Add extra SIP headers */
+ const char *uri_options; /*!< URI options to add to the URI */
+ const char *vxml_url; /*!< VXML url for Cisco phones */
+ char *auth; /*!< Authentication */
+ char *authheader; /*!< Auth header */
+ enum sip_auth_type auth_type; /*!< Authentication type */
+ const char *replaces; /*!< Replaces header for call transfers */
+ int transfer; /*!< Flag - is this Invite part of a SIP transfer? (invite/replaces) */
+};
+
+/*! \brief Structure to save routing information for a SIP session */
+struct sip_route {
+ struct sip_route *next;
+ char hop[0];
+};
+
+/*! \brief Modes for SIP domain handling in the PBX */
+enum domain_mode {
+ SIP_DOMAIN_AUTO, /*!< This domain is auto-configured */
+ SIP_DOMAIN_CONFIG, /*!< This domain is from configuration */
+};
+
+/*! \brief Domain data structure.
+ \note In the future, we will connect this to a configuration tree specific
+ for this domain
+*/
+struct domain {
+ char domain[MAXHOSTNAMELEN]; /*!< SIP domain we are responsible for */
+ char context[AST_MAX_EXTENSION]; /*!< Incoming context for this domain */
+ enum domain_mode mode; /*!< How did we find this domain? */
+ AST_LIST_ENTRY(domain) list; /*!< List mechanics */
+};
+
+static AST_LIST_HEAD_STATIC(domain_list, domain); /*!< The SIP domain list */
+
+
+/*! \brief sip_history: Structure for saving transactions within a SIP dialog */
+struct sip_history {
+ AST_LIST_ENTRY(sip_history) list;
+ char event[0]; /* actually more, depending on needs */
+};
+
+AST_LIST_HEAD_NOLOCK(sip_history_head, sip_history); /*!< history list, entry in sip_pvt */
+
+/*! \brief sip_auth: Credentials for authentication to other SIP services */
+struct sip_auth {
+ char realm[AST_MAX_EXTENSION]; /*!< Realm in which these credentials are valid */
+ char username[256]; /*!< Username */
+ char secret[256]; /*!< Secret */
+ char md5secret[256]; /*!< MD5Secret */
+ struct sip_auth *next; /*!< Next auth structure in list */
+};
+
+/*! \name SIPflags
+ Various flags for the flags field in the pvt structure
+ Trying to sort these up (one or more of the following):
+ D: Dialog
+ P: Peer/user
+ G: Global flag
+ When flags are used by multiple structures, it is important that
+ they have a common layout so it is easy to copy them.
+*/
+/*@{*/
+#define SIP_OUTGOING (1 << 0) /*!< D: Direction of the last transaction in this dialog */
+#define SIP_RINGING (1 << 2) /*!< D: Have sent 180 ringing */
+#define SIP_PROGRESS_SENT (1 << 3) /*!< D: Have sent 183 message progress */
+#define SIP_NEEDREINVITE (1 << 4) /*!< D: Do we need to send another reinvite? */
+#define SIP_PENDINGBYE (1 << 5) /*!< D: Need to send bye after we ack? */
+#define SIP_GOTREFER (1 << 6) /*!< D: Got a refer? */
+#define SIP_CALL_LIMIT (1 << 7) /*!< D: Call limit enforced for this call */
+#define SIP_INC_COUNT (1 << 8) /*!< D: Did this dialog increment the counter of in-use calls? */
+#define SIP_INC_RINGING (1 << 9) /*!< D: Did this connection increment the counter of in-use calls? */
+#define SIP_DEFER_BYE_ON_TRANSFER (1 << 11) /*!< D: Do not hangup at first ast_hangup */
+
+#define SIP_PROMISCREDIR (1 << 12) /*!< DP: Promiscuous redirection */
+#define SIP_TRUSTRPID (1 << 13) /*!< DP: Trust RPID headers? */
+#define SIP_USEREQPHONE (1 << 14) /*!< DP: Add user=phone to numeric URI. Default off */
+#define SIP_USECLIENTCODE (1 << 15) /*!< DP: Trust X-ClientCode info message */
+
+/* DTMF flags - see str2dtmfmode() and dtmfmode2str() */
+#define SIP_DTMF (3 << 16) /*!< DP: DTMF Support: four settings, uses two bits */
+#define SIP_DTMF_RFC2833 (0 << 16) /*!< DP: DTMF Support: RTP DTMF - "rfc2833" */
+#define SIP_DTMF_INBAND (1 << 16) /*!< DP: DTMF Support: Inband audio, only for ULAW/ALAW - "inband" */
+#define SIP_DTMF_INFO (2 << 16) /*!< DP: DTMF Support: SIP Info messages - "info" */
+#define SIP_DTMF_AUTO (3 << 16) /*!< DP: DTMF Support: AUTO switch between rfc2833 and in-band DTMF */
+#define SIP_DTMF_SHORTINFO (4 << 16) /*!< DP: DTMF Support: SIP Info messages - "info" - short variant */
+
+/* NAT settings - see nat2str() */
+#define SIP_NAT (3 << 18) /*!< DP: four settings, uses two bits */
+#define SIP_NAT_NEVER (0 << 18) /*!< DP: No nat support */
+#define SIP_NAT_RFC3581 (1 << 18) /*!< DP: NAT RFC3581 */
+#define SIP_NAT_ROUTE (2 << 18) /*!< DP: NAT Only ROUTE */
+#define SIP_NAT_ALWAYS (3 << 18) /*!< DP: NAT Both ROUTE and RFC3581 */
+
+/* re-INVITE related settings */
+#define SIP_REINVITE (7 << 20) /*!< DP: three bits used */
+#define SIP_CAN_REINVITE (1 << 20) /*!< DP: allow peers to be reinvited to send media directly p2p */
+#define SIP_CAN_REINVITE_NAT (2 << 20) /*!< DP: allow media reinvite when new peer is behind NAT */
+#define SIP_REINVITE_UPDATE (4 << 20) /*!< DP: use UPDATE (RFC3311) when reinviting this peer */
+
+/* "insecure" settings - see insecure2str() */
+#define SIP_INSECURE (3 << 23) /*!< DP: two bits used */
+#define SIP_INSECURE_PORT (1 << 23) /*!< DP: don't require matching port for incoming requests */
+#define SIP_INSECURE_INVITE (1 << 24) /*!< DP: don't require authentication for incoming INVITEs */
+
+/* Sending PROGRESS in-band settings */
+#define SIP_PROG_INBAND (3 << 25) /*!< DP: three settings, uses two bits */
+#define SIP_PROG_INBAND_NEVER (0 << 25)
+#define SIP_PROG_INBAND_NO (1 << 25)
+#define SIP_PROG_INBAND_YES (2 << 25)
+
+#define SIP_SENDRPID (1 << 29) /*!< DP: Remote Party-ID Support */
+#define SIP_G726_NONSTANDARD (1 << 31) /*!< DP: Use non-standard packing for G726-32 data */
+
+/*! \brief Flags to copy from peer/user to dialog */
+#define SIP_FLAGS_TO_COPY \
+ (SIP_PROMISCREDIR | SIP_TRUSTRPID | SIP_SENDRPID | SIP_DTMF | SIP_REINVITE | \
+ SIP_PROG_INBAND | SIP_USECLIENTCODE | SIP_NAT | SIP_G726_NONSTANDARD | \
+ SIP_USEREQPHONE | SIP_INSECURE)
+/*@}*/
+
+/*! \name SIPflags2
+ a second page of flags (for flags[1] */
+/*@{*/
+/* realtime flags */
+#define SIP_PAGE2_RTCACHEFRIENDS (1 << 0) /*!< GP: Should we keep RT objects in memory for extended time? */
+#define SIP_PAGE2_RTAUTOCLEAR (1 << 2) /*!< GP: Should we clean memory from peers after expiry? */
+/* Space for addition of other realtime flags in the future */
+
+#define SIP_PAGE2_VIDEOSUPPORT (1 << 14) /*!< DP: Video supported if offered? */
+#define SIP_PAGE2_TEXTSUPPORT (1 << 15) /*!< GDP: Global text enable */
+#define SIP_PAGE2_ALLOWSUBSCRIBE (1 << 16) /*!< GP: Allow subscriptions from this peer? */
+#define SIP_PAGE2_ALLOWOVERLAP (1 << 17) /*!< DP: Allow overlap dialing ? */
+#define SIP_PAGE2_SUBSCRIBEMWIONLY (1 << 18) /*!< GP: Only issue MWI notification if subscribed to */
+
+#define SIP_PAGE2_T38SUPPORT (7 << 20) /*!< GDP: T38 Fax Passthrough Support */
+#define SIP_PAGE2_T38SUPPORT_UDPTL (1 << 20) /*!< GDP: T38 Fax Passthrough Support */
+#define SIP_PAGE2_T38SUPPORT_RTP (2 << 20) /*!< GDP: T38 Fax Passthrough Support (not implemented) */
+#define SIP_PAGE2_T38SUPPORT_TCP (4 << 20) /*!< GDP: T38 Fax Passthrough Support (not implemented) */
+
+#define SIP_PAGE2_CALL_ONHOLD (3 << 23) /*!< D: Call hold states: */
+#define SIP_PAGE2_CALL_ONHOLD_ACTIVE (1 << 23) /*!< D: Active hold */
+#define SIP_PAGE2_CALL_ONHOLD_ONEDIR (2 << 23) /*!< D: One directional hold */
+#define SIP_PAGE2_CALL_ONHOLD_INACTIVE (3 << 23) /*!< D: Inactive hold */
+
+#define SIP_PAGE2_RFC2833_COMPENSATE (1 << 25) /*!< DP: Compensate for buggy RFC2833 implementations */
+#define SIP_PAGE2_BUGGY_MWI (1 << 26) /*!< DP: Buggy CISCO MWI fix */
+#define SIP_PAGE2_REGISTERTRYING (1 << 29) /*!< DP: Send 100 Trying on REGISTER attempts */
+
+#define SIP_PAGE2_FLAGS_TO_COPY \
+ (SIP_PAGE2_ALLOWSUBSCRIBE | SIP_PAGE2_ALLOWOVERLAP | SIP_PAGE2_VIDEOSUPPORT | \
+ SIP_PAGE2_T38SUPPORT | SIP_PAGE2_RFC2833_COMPENSATE | SIP_PAGE2_BUGGY_MWI | \
+ SIP_PAGE2_TEXTSUPPORT )
+
+/*@}*/
+
+/*! \name SIPflagsT38
+ T.38 set of flags */
+
+/*@{*/
+#define T38FAX_FILL_BIT_REMOVAL (1 << 0) /*!< Default: 0 (unset)*/
+#define T38FAX_TRANSCODING_MMR (1 << 1) /*!< Default: 0 (unset)*/
+#define T38FAX_TRANSCODING_JBIG (1 << 2) /*!< Default: 0 (unset)*/
+/* Rate management */
+#define T38FAX_RATE_MANAGEMENT_TRANSFERED_TCF (0 << 3)
+#define T38FAX_RATE_MANAGEMENT_LOCAL_TCF (1 << 3) /*!< Unset for transferredTCF (UDPTL), set for localTCF (TPKT) */
+/* UDP Error correction */
+#define T38FAX_UDP_EC_NONE (0 << 4) /*!< two bits, if unset NO t38UDPEC field in T38 SDP*/
+#define T38FAX_UDP_EC_FEC (1 << 4) /*!< Set for t38UDPFEC */
+#define T38FAX_UDP_EC_REDUNDANCY (2 << 4) /*!< Set for t38UDPRedundancy */
+/* T38 Spec version */
+#define T38FAX_VERSION (3 << 6) /*!< two bits, 2 values so far, up to 4 values max */
+#define T38FAX_VERSION_0 (0 << 6) /*!< Version 0 */
+#define T38FAX_VERSION_1 (1 << 6) /*!< Version 1 */
+/* Maximum Fax Rate */
+#define T38FAX_RATE_2400 (1 << 8) /*!< 2400 bps t38FaxRate */
+#define T38FAX_RATE_4800 (1 << 9) /*!< 4800 bps t38FaxRate */
+#define T38FAX_RATE_7200 (1 << 10) /*!< 7200 bps t38FaxRate */
+#define T38FAX_RATE_9600 (1 << 11) /*!< 9600 bps t38FaxRate */
+#define T38FAX_RATE_12000 (1 << 12) /*!< 12000 bps t38FaxRate */
+#define T38FAX_RATE_14400 (1 << 13) /*!< 14400 bps t38FaxRate */
+
+/*!< This is default: NO MMR and JBIG transcoding, NO fill bit removal, transferredTCF TCF, UDP FEC, Version 0 and 9600 max fax rate */
+static int global_t38_capability = T38FAX_VERSION_0 | T38FAX_RATE_2400 | T38FAX_RATE_4800 | T38FAX_RATE_7200 | T38FAX_RATE_9600;
+/*@}*/
+
+/*! \brief debugging state
+ * We store separately the debugging requests from the config file
+ * and requests from the CLI. Debugging is enabled if either is set
+ * (which means that if sipdebug is set in the config file, we can
+ * only turn it off by reloading the config).
+ */
+enum sip_debug_e {
+ sip_debug_none = 0,
+ sip_debug_config = 1,
+ sip_debug_console = 2,
+};
+
+static enum sip_debug_e sipdebug;
+
+/*! \brief extra debugging for 'text' related events.
+ * At thie moment this is set together with sip_debug_console.
+ * It should either go away or be implemented properly.
+ */
+static int sipdebug_text;
+
+/*! \brief T38 States for a call */
+enum t38state {
+ T38_DISABLED = 0, /*!< Not enabled */
+ T38_LOCAL_DIRECT, /*!< Offered from local */
+ T38_LOCAL_REINVITE, /*!< Offered from local - REINVITE */
+ T38_PEER_DIRECT, /*!< Offered from peer */
+ T38_PEER_REINVITE, /*!< Offered from peer - REINVITE */
+ T38_ENABLED /*!< Negotiated (enabled) */
+};
+
+/*! \brief T.38 channel settings (at some point we need to make this alloc'ed */
+struct t38properties {
+ struct ast_flags t38support; /*!< Flag for udptl, rtp or tcp support for this session */
+ int capability; /*!< Our T38 capability */
+ int peercapability; /*!< Peers T38 capability */
+ int jointcapability; /*!< Supported T38 capability at both ends */
+ enum t38state state; /*!< T.38 state */
+};
+
+/*! \brief Parameters to know status of transfer */
+enum referstatus {
+ REFER_IDLE, /*!< No REFER is in progress */
+ REFER_SENT, /*!< Sent REFER to transferee */
+ REFER_RECEIVED, /*!< Received REFER from transferrer */
+ REFER_CONFIRMED, /*!< Refer confirmed with a 100 TRYING (unused) */
+ REFER_ACCEPTED, /*!< Accepted by transferee */
+ REFER_RINGING, /*!< Target Ringing */
+ REFER_200OK, /*!< Answered by transfer target */
+ REFER_FAILED, /*!< REFER declined - go on */
+ REFER_NOAUTH /*!< We had no auth for REFER */
+};
+
+/*! \brief generic struct to map between strings and integers.
+ * Fill it with x-s pairs, terminate with an entry with s = NULL;
+ * Then you can call map_x_s(...) to map an integer to a string,
+ * and map_s_x() for the string -> integer mapping.
+ */
+struct _map_x_s {
+ int x;
+ const char *s;
+};
+
+static const struct _map_x_s referstatusstrings[] = {
+ { REFER_IDLE, "<none>" },
+ { REFER_SENT, "Request sent" },
+ { REFER_RECEIVED, "Request received" },
+ { REFER_CONFIRMED, "Confirmed" },
+ { REFER_ACCEPTED, "Accepted" },
+ { REFER_RINGING, "Target ringing" },
+ { REFER_200OK, "Done" },
+ { REFER_FAILED, "Failed" },
+ { REFER_NOAUTH, "Failed - auth failure" },
+ { -1, NULL} /* terminator */
+};
+
+/*! \brief Structure to handle SIP transfers. Dynamically allocated when needed
+ \note OEJ: Should be moved to string fields */
+struct sip_refer {
+ char refer_to[AST_MAX_EXTENSION]; /*!< Place to store REFER-TO extension */
+ char refer_to_domain[AST_MAX_EXTENSION]; /*!< Place to store REFER-TO domain */
+ char refer_to_urioption[AST_MAX_EXTENSION]; /*!< Place to store REFER-TO uri options */
+ char refer_to_context[AST_MAX_EXTENSION]; /*!< Place to store REFER-TO context */
+ char referred_by[AST_MAX_EXTENSION]; /*!< Place to store REFERRED-BY extension */
+ char referred_by_name[AST_MAX_EXTENSION]; /*!< Place to store REFERRED-BY extension */
+ char refer_contact[AST_MAX_EXTENSION]; /*!< Place to store Contact info from a REFER extension */
+ char replaces_callid[BUFSIZ]; /*!< Replace info: callid */
+ char replaces_callid_totag[BUFSIZ/2]; /*!< Replace info: to-tag */
+ char replaces_callid_fromtag[BUFSIZ/2]; /*!< Replace info: from-tag */
+ struct sip_pvt *refer_call; /*!< Call we are referring. This is just a reference to a
+ * dialog owned by someone else, so we should not destroy
+ * it when the sip_refer object goes.
+ */
+ int attendedtransfer; /*!< Attended or blind transfer? */
+ int localtransfer; /*!< Transfer to local domain? */
+ enum referstatus status; /*!< REFER status */
+};
+
+
+/*! \brief Structure that encapsulates all attributes related to running
+ * SIP Session-Timers feature on a per dialog basis.
+ */
+struct sip_st_dlg {
+ int st_active; /*!< Session-Timers on/off */
+ int st_interval; /*!< Session-Timers negotiated session refresh interval */
+ int st_schedid; /*!< Session-Timers ast_sched scheduler id */
+ enum st_refresher st_ref; /*!< Session-Timers session refresher */
+ int st_expirys; /*!< Session-Timers number of expirys */
+ int st_active_peer_ua; /*!< Session-Timers on/off in peer UA */
+ int st_cached_min_se; /*!< Session-Timers cached Min-SE */
+ int st_cached_max_se; /*!< Session-Timers cached Session-Expires */
+ enum st_mode st_cached_mode; /*!< Session-Timers cached M.O. */
+ enum st_refresher st_cached_ref; /*!< Session-Timers cached refresher */
+};
+
+
+/*! \brief Structure that encapsulates all attributes related to configuration
+ * of SIP Session-Timers feature on a per user/peer basis.
+ */
+struct sip_st_cfg {
+ enum st_mode st_mode_oper; /*!< Mode of operation for Session-Timers */
+ enum st_refresher st_ref; /*!< Session-Timer refresher */
+ int st_min_se; /*!< Lowest threshold for session refresh interval */
+ int st_max_se; /*!< Highest threshold for session refresh interval */
+};
+
+
+
+
+/*! \brief sip_pvt: structures used for each SIP dialog, ie. a call, a registration, a subscribe.
+ * Created and initialized by sip_alloc(), the descriptor goes into the list of
+ * descriptors (dialoglist).
+ */
+struct sip_pvt {
+ struct sip_pvt *next; /*!< Next dialog in chain */
+ ast_mutex_t pvt_lock; /*!< Dialog private lock */
+ enum invitestates invitestate; /*!< Track state of SIP_INVITEs */
+ int method; /*!< SIP method that opened this dialog */
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(callid); /*!< Global CallID */
+ AST_STRING_FIELD(randdata); /*!< Random data */
+ AST_STRING_FIELD(accountcode); /*!< Account code */
+ AST_STRING_FIELD(realm); /*!< Authorization realm */
+ AST_STRING_FIELD(nonce); /*!< Authorization nonce */
+ AST_STRING_FIELD(opaque); /*!< Opaque nonsense */
+ AST_STRING_FIELD(qop); /*!< Quality of Protection, since SIP wasn't complicated enough yet. */
+ AST_STRING_FIELD(domain); /*!< Authorization domain */
+ AST_STRING_FIELD(from); /*!< The From: header */
+ AST_STRING_FIELD(useragent); /*!< User agent in SIP request */
+ AST_STRING_FIELD(exten); /*!< Extension where to start */
+ AST_STRING_FIELD(context); /*!< Context for this call */
+ AST_STRING_FIELD(subscribecontext); /*!< Subscribecontext */
+ AST_STRING_FIELD(subscribeuri); /*!< Subscribecontext */
+ AST_STRING_FIELD(fromdomain); /*!< Domain to show in the from field */
+ AST_STRING_FIELD(fromuser); /*!< User to show in the user field */
+ AST_STRING_FIELD(fromname); /*!< Name to show in the user field */
+ AST_STRING_FIELD(tohost); /*!< Host we should put in the "to" field */
+ AST_STRING_FIELD(todnid); /*!< DNID of this call (overrides host) */
+ AST_STRING_FIELD(language); /*!< Default language for this call */
+ AST_STRING_FIELD(mohinterpret); /*!< MOH class to use when put on hold */
+ AST_STRING_FIELD(mohsuggest); /*!< MOH class to suggest when putting a peer on hold */
+ AST_STRING_FIELD(rdnis); /*!< Referring DNIS */
+ AST_STRING_FIELD(redircause); /*!< Referring cause */
+ AST_STRING_FIELD(theirtag); /*!< Their tag */
+ AST_STRING_FIELD(username); /*!< [user] name */
+ AST_STRING_FIELD(peername); /*!< [peer] name, not set if [user] */
+ AST_STRING_FIELD(authname); /*!< Who we use for authentication */
+ AST_STRING_FIELD(uri); /*!< Original requested URI */
+ AST_STRING_FIELD(okcontacturi); /*!< URI from the 200 OK on INVITE */
+ AST_STRING_FIELD(peersecret); /*!< Password */
+ AST_STRING_FIELD(peermd5secret);
+ AST_STRING_FIELD(cid_num); /*!< Caller*ID number */
+ AST_STRING_FIELD(cid_name); /*!< Caller*ID name */
+ AST_STRING_FIELD(via); /*!< Via: header */
+ AST_STRING_FIELD(fullcontact); /*!< The Contact: that the UA registers with us */
+ /* we only store the part in <brackets> in this field. */
+ AST_STRING_FIELD(our_contact); /*!< Our contact header */
+ AST_STRING_FIELD(rpid); /*!< Our RPID header */
+ AST_STRING_FIELD(rpid_from); /*!< Our RPID From header */
+ AST_STRING_FIELD(url); /*!< URL to be sent with next message to peer */
+ );
+ struct sip_socket socket;
+ unsigned int ocseq; /*!< Current outgoing seqno */
+ unsigned int icseq; /*!< Current incoming seqno */
+ ast_group_t callgroup; /*!< Call group */
+ ast_group_t pickupgroup; /*!< Pickup group */
+ int lastinvite; /*!< Last Cseq of invite */
+ int lastnoninvite; /*!< Last Cseq of non-invite */
+ struct ast_flags flags[2]; /*!< SIP_ flags */
+
+ /* boolean or small integers that don't belong in flags */
+ char do_history; /*!< Set if we want to record history */
+ char alreadygone; /*!< already destroyed by our peer */
+ char needdestroy; /*!< need to be destroyed by the monitor thread */
+ char outgoing_call; /*!< this is an outgoing call */
+ char answered_elsewhere; /*!< This call is cancelled due to answer on another channel */
+ char novideo; /*!< Didn't get video in invite, don't offer */
+ char notext; /*!< Text not supported (?) */
+
+ int timer_t1; /*!< SIP timer T1, ms rtt */
+ int timer_b; /*!< SIP timer B, ms */
+ unsigned int sipoptions; /*!< Supported SIP options on the other end */
+ unsigned int reqsipoptions; /*!< Required SIP options on the other end */
+ struct ast_codec_pref prefs; /*!< codec prefs */
+ int capability; /*!< Special capability (codec) */
+ int jointcapability; /*!< Supported capability at both ends (codecs) */
+ int peercapability; /*!< Supported peer capability */
+ int prefcodec; /*!< Preferred codec (outbound only) */
+ int noncodeccapability; /*!< DTMF RFC2833 telephony-event */
+ int jointnoncodeccapability; /*!< Joint Non codec capability */
+ int redircodecs; /*!< Redirect codecs */
+ int maxcallbitrate; /*!< Maximum Call Bitrate for Video Calls */
+ struct sip_proxy *outboundproxy; /*!< Outbound proxy for this dialog */
+ struct t38properties t38; /*!< T38 settings */
+ struct sockaddr_in udptlredirip; /*!< Where our T.38 UDPTL should be going if not to us */
+ struct ast_udptl *udptl; /*!< T.38 UDPTL session */
+ int callingpres; /*!< Calling presentation */
+ int authtries; /*!< Times we've tried to authenticate */
+ int expiry; /*!< How long we take to expire */
+ long branch; /*!< The branch identifier of this session */
+ char tag[11]; /*!< Our tag for this session */
+ int sessionid; /*!< SDP Session ID */
+ int sessionversion; /*!< SDP Session Version */
+ int sessionversion_remote; /*!< Remote UA's SDP Session Version */
+ int session_modify; /*!< Session modification request true/false */
+ struct sockaddr_in sa; /*!< Our peer */
+ struct sockaddr_in redirip; /*!< Where our RTP should be going if not to us */
+ struct sockaddr_in vredirip; /*!< Where our Video RTP should be going if not to us */
+ struct sockaddr_in tredirip; /*!< Where our Text RTP should be going if not to us */
+ time_t lastrtprx; /*!< Last RTP received */
+ time_t lastrtptx; /*!< Last RTP sent */
+ int rtptimeout; /*!< RTP timeout time */
+ struct sockaddr_in recv; /*!< Received as */
+ struct sockaddr_in ourip; /*!< Our IP (as seen from the outside) */
+ struct ast_channel *owner; /*!< Who owns us (if we have an owner) */
+ struct sip_route *route; /*!< Head of linked list of routing steps (fm Record-Route) */
+ int route_persistant; /*!< Is this the "real" route? */
+ struct sip_auth *peerauth; /*!< Realm authentication */
+ int noncecount; /*!< Nonce-count */
+ char lastmsg[256]; /*!< Last Message sent/received */
+ int amaflags; /*!< AMA Flags */
+ int pendinginvite; /*!< Any pending invite ? (seqno of this) */
+ struct sip_request initreq; /*!< Latest request that opened a new transaction
+ within this dialog.
+ NOT the request that opened the dialog
+ */
+
+ int initid; /*!< Auto-congest ID if appropriate (scheduler) */
+ int waitid; /*!< Wait ID for scheduler after 491 or other delays */
+ int autokillid; /*!< Auto-kill ID (scheduler) */
+ enum transfermodes allowtransfer; /*!< REFER: restriction scheme */
+ struct sip_refer *refer; /*!< REFER: SIP transfer data structure */
+ enum subscriptiontype subscribed; /*!< SUBSCRIBE: Is this dialog a subscription? */
+ int stateid; /*!< SUBSCRIBE: ID for devicestate subscriptions */
+ int laststate; /*!< SUBSCRIBE: Last known extension state */
+ int dialogver; /*!< SUBSCRIBE: Version for subscription dialog-info */
+
+ struct ast_dsp *vad; /*!< Inband DTMF Detection dsp */
+
+ struct sip_peer *relatedpeer; /*!< If this dialog is related to a peer, which one
+ Used in peerpoke, mwi subscriptions */
+ struct sip_registry *registry; /*!< If this is a REGISTER dialog, to which registry */
+ struct ast_rtp *rtp; /*!< RTP Session */
+ struct ast_rtp *vrtp; /*!< Video RTP session */
+ struct ast_rtp *trtp; /*!< Text RTP session */
+ struct sip_pkt *packets; /*!< Packets scheduled for re-transmission */
+ struct sip_history_head *history; /*!< History of this SIP dialog */
+ size_t history_entries; /*!< Number of entires in the history */
+ struct ast_variable *chanvars; /*!< Channel variables to set for inbound call */
+ struct sip_invite_param *options; /*!< Options for INVITE */
+ int autoframing; /*!< The number of Asters we group in a Pyroflax
+ before strolling to the Grokyzpå
+ (A bit unsure of this, please correct if
+ you know more) */
+ struct sip_st_dlg *stimer; /*!< SIP Session-Timers */
+};
+
+
+/*! Max entires in the history list for a sip_pvt */
+#define MAX_HISTORY_ENTRIES 50
+
+/*!
+ * Here we implement the container for dialogs (sip_pvt), defining
+ * generic wrapper functions to ease the transition from the current
+ * implementation (a single linked list) to a different container.
+ * In addition to a reference to the container, we need functions to lock/unlock
+ * the container and individual items, and functions to add/remove
+ * references to the individual items.
+ */
+static struct sip_pvt *dialoglist = NULL;
+
+/*! \brief Protect the SIP dialog list (of sip_pvt's) */
+AST_MUTEX_DEFINE_STATIC(dialoglock);
+
+#ifndef DETECT_DEADLOCKS
+/*! \brief hide the way the list is locked/unlocked */
+static void dialoglist_lock(void)
+{
+ ast_mutex_lock(&dialoglock);
+}
+
+static void dialoglist_unlock(void)
+{
+ ast_mutex_unlock(&dialoglock);
+}
+#else
+/* we don't want to HIDE the information about where the lock was requested if trying to debug
+ * deadlocks! So, just make these macros! */
+#define dialoglist_lock(x) ast_mutex_lock(&dialoglock)
+#define dialoglist_unlock(x) ast_mutex_unlock(&dialoglock)
+#endif
+
+/*!
+ * when we create or delete references, make sure to use these
+ * functions so we keep track of the refcounts.
+ * To simplify the code, we allow a NULL to be passed to dialog_unref().
+ */
+static struct sip_pvt *dialog_ref(struct sip_pvt *p)
+{
+ return p;
+}
+
+static struct sip_pvt *dialog_unref(struct sip_pvt *p)
+{
+ return NULL;
+}
+
+/*! \brief sip packet - raw format for outbound packets that are sent or scheduled for transmission
+ * Packets are linked in a list, whose head is in the struct sip_pvt they belong to.
+ * Each packet holds a reference to the parent struct sip_pvt.
+ * This structure is allocated in __sip_reliable_xmit() and only for packets that
+ * require retransmissions.
+ */
+struct sip_pkt {
+ struct sip_pkt *next; /*!< Next packet in linked list */
+ int retrans; /*!< Retransmission number */
+ int method; /*!< SIP method for this packet */
+ int seqno; /*!< Sequence number */
+ char is_resp; /*!< 1 if this is a response packet (e.g. 200 OK), 0 if it is a request */
+ char is_fatal; /*!< non-zero if there is a fatal error */
+ struct sip_pvt *owner; /*!< Owner AST call */
+ int retransid; /*!< Retransmission ID */
+ int timer_a; /*!< SIP timer A, retransmission timer */
+ int timer_t1; /*!< SIP Timer T1, estimated RTT or 500 ms */
+ int packetlen; /*!< Length of packet */
+ char data[0];
+};
+
+/*! \brief Structure for SIP user data. User's place calls to us */
+struct sip_user {
+ /* Users who can access various contexts */
+ ASTOBJ_COMPONENTS(struct sip_user);
+ char secret[80]; /*!< Password */
+ char md5secret[80]; /*!< Password in md5 */
+ char context[AST_MAX_CONTEXT]; /*!< Default context for incoming calls */
+ char subscribecontext[AST_MAX_CONTEXT]; /* Default context for subscriptions */
+ char cid_num[80]; /*!< Caller ID num */
+ char cid_name[80]; /*!< Caller ID name */
+ char accountcode[AST_MAX_ACCOUNT_CODE]; /* Account code */
+ char language[MAX_LANGUAGE]; /*!< Default language for this user */
+ char mohinterpret[MAX_MUSICCLASS];/*!< Music on Hold class */
+ char mohsuggest[MAX_MUSICCLASS];/*!< Music on Hold class */
+ char useragent[256]; /*!< User agent in SIP request */
+ struct ast_codec_pref prefs; /*!< codec prefs */
+ ast_group_t callgroup; /*!< Call group */
+ ast_group_t pickupgroup; /*!< Pickup Group */
+ unsigned int sipoptions; /*!< Supported SIP options */
+ struct ast_flags flags[2]; /*!< SIP_ flags */
+
+ /* things that don't belong in flags */
+ char is_realtime; /*!< this is a 'realtime' user */
+
+ int amaflags; /*!< AMA flags for billing */
+ int callingpres; /*!< Calling id presentation */
+ int capability; /*!< Codec capability */
+ int inUse; /*!< Number of calls in use */
+ int call_limit; /*!< Limit of concurrent calls */
+ enum transfermodes allowtransfer; /*! SIP Refer restriction scheme */
+ struct ast_ha *ha; /*!< ACL setting */
+ struct ast_variable *chanvars; /*!< Variables to set for channel created by user */
+ int maxcallbitrate; /*!< Maximum Bitrate for a video call */
+ int autoframing;
+ struct sip_st_cfg stimer; /*!< SIP Session-Timers */
+};
+
+/*!
+ * \brief A peer's mailbox
+ *
+ * We could use STRINGFIELDS here, but for only two strings, it seems like
+ * too much effort ...
+ */
+struct sip_mailbox {
+ char *mailbox;
+ char *context;
+ /*! Associated MWI subscription */
+ struct ast_event_sub *event_sub;
+ AST_LIST_ENTRY(sip_mailbox) entry;
+};
+
+/*! \brief Structure for SIP peer data, we place calls to peers if registered or fixed IP address (host) */
+/* XXX field 'name' must be first otherwise sip_addrcmp() will fail */
+struct sip_peer {
+ ASTOBJ_COMPONENTS(struct sip_peer); /*!< name, refcount, objflags, object pointers */
+ /*!< peer->name is the unique name of this object */
+ struct sip_socket socket;
+ char secret[80]; /*!< Password */
+ char md5secret[80]; /*!< Password in MD5 */
+ struct sip_auth *auth; /*!< Realm authentication list */
+ char context[AST_MAX_CONTEXT]; /*!< Default context for incoming calls */
+ char subscribecontext[AST_MAX_CONTEXT]; /*!< Default context for subscriptions */
+ char username[80]; /*!< Temporary username until registration */
+ char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Account code */
+ int amaflags; /*!< AMA Flags (for billing) */
+ char tohost[MAXHOSTNAMELEN]; /*!< If not dynamic, IP address */
+ char regexten[AST_MAX_EXTENSION]; /*!< Extension to register (if regcontext is used) */
+ char fromuser[80]; /*!< From: user when calling this peer */
+ char fromdomain[MAXHOSTNAMELEN]; /*!< From: domain when calling this peer */
+ char fullcontact[256]; /*!< Contact registered with us (not in sip.conf) */
+ char cid_num[80]; /*!< Caller ID num */
+ char cid_name[80]; /*!< Caller ID name */
+ int callingpres; /*!< Calling id presentation */
+ int inUse; /*!< Number of calls in use */
+ int inRinging; /*!< Number of calls ringing */
+ int onHold; /*!< Peer has someone on hold */
+ int call_limit; /*!< Limit of concurrent calls */
+ int busy_level; /*!< Level of active channels where we signal busy */
+ enum transfermodes allowtransfer; /*! SIP Refer restriction scheme */
+ char vmexten[AST_MAX_EXTENSION]; /*!< Dialplan extension for MWI notify message*/
+ char language[MAX_LANGUAGE]; /*!< Default language for prompts */
+ char mohinterpret[MAX_MUSICCLASS];/*!< Music on Hold class */
+ char mohsuggest[MAX_MUSICCLASS];/*!< Music on Hold class */
+ char useragent[256]; /*!< User agent in SIP request (saved from registration) */
+ struct ast_codec_pref prefs; /*!< codec prefs */
+ int lastmsgssent;
+ unsigned int sipoptions; /*!< Supported SIP options */
+ struct ast_flags flags[2]; /*!< SIP_ flags */
+
+ /*! Mailboxes that this peer cares about */
+ AST_LIST_HEAD_NOLOCK(, sip_mailbox) mailboxes;
+
+ /* things that don't belong in flags */
+ char is_realtime; /*!< this is a 'realtime' peer */
+ char rt_fromcontact; /*!< P: copy fromcontact from realtime */
+ char host_dynamic; /*!< P: Dynamic Peers register with Asterisk */
+ char selfdestruct; /*!< P: Automatic peers need to destruct themselves */
+
+ int expire; /*!< When to expire this peer registration */
+ int capability; /*!< Codec capability */
+ int rtptimeout; /*!< RTP timeout */
+ int rtpholdtimeout; /*!< RTP Hold Timeout */
+ int rtpkeepalive; /*!< Send RTP packets for keepalive */
+ ast_group_t callgroup; /*!< Call group */
+ ast_group_t pickupgroup; /*!< Pickup group */
+ struct sip_proxy *outboundproxy; /*!< Outbound proxy for this peer */
+ struct ast_dnsmgr_entry *dnsmgr;/*!< DNS refresh manager for peer */
+ struct sockaddr_in addr; /*!< IP address of peer */
+ int maxcallbitrate; /*!< Maximum Bitrate for a video call */
+
+ /* Qualification */
+ struct sip_pvt *call; /*!< Call pointer */
+ int pokeexpire; /*!< When to expire poke (qualify= checking) */
+ int lastms; /*!< How long last response took (in ms), or -1 for no response */
+ int maxms; /*!< Max ms we will accept for the host to be up, 0 to not monitor */
+ int qualifyfreq; /*!< Qualification: How often to check for the host to be up */
+ struct timeval ps; /*!< Time for sending SIP OPTION in sip_pke_peer() */
+ struct sockaddr_in defaddr; /*!< Default IP address, used until registration */
+ struct ast_ha *ha; /*!< Access control list */
+ struct ast_variable *chanvars; /*!< Variables to set for channel created by user */
+ struct sip_pvt *mwipvt; /*!< Subscription for MWI */
+ int autoframing;
+ struct sip_st_cfg stimer; /*!< SIP Session-Timers */
+ int timer_t1; /*!< The maximum T1 value for the peer */
+ int timer_b; /*!< The maximum timer B (transaction timeouts) */
+};
+
+
+/*! \brief Registrations with other SIP proxies
+ * Created by sip_register(), the entry is linked in the 'regl' list,
+ * and never deleted (other than at 'sip reload' or module unload times).
+ * The entry always has a pending timeout, either waiting for an ACK to
+ * the REGISTER message (in which case we have to retransmit the request),
+ * or waiting for the next REGISTER message to be sent (either the initial one,
+ * or once the previously completed registration one expires).
+ * The registration can be in one of many states, though at the moment
+ * the handling is a bit mixed.
+ * Note that the entire evolution of sip_registry (transmissions,
+ * incoming packets and timeouts) is driven by one single thread,
+ * do_monitor(), so there is almost no synchronization issue.
+ * The only exception is the sip_pvt creation/lookup,
+ * as the dialoglist is also manipulated by other threads.
+ */
+struct sip_registry {
+ ASTOBJ_COMPONENTS_FULL(struct sip_registry,1,1);
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(callid); /*!< Global Call-ID */
+ AST_STRING_FIELD(realm); /*!< Authorization realm */
+ AST_STRING_FIELD(nonce); /*!< Authorization nonce */
+ AST_STRING_FIELD(opaque); /*!< Opaque nonsense */
+ AST_STRING_FIELD(qop); /*!< Quality of Protection, since SIP wasn't complicated enough yet. */
+ AST_STRING_FIELD(domain); /*!< Authorization domain */
+ AST_STRING_FIELD(username); /*!< Who we are registering as */
+ AST_STRING_FIELD(authuser); /*!< Who we *authenticate* as */
+ AST_STRING_FIELD(hostname); /*!< Domain or host we register to */
+ AST_STRING_FIELD(secret); /*!< Password in clear text */
+ AST_STRING_FIELD(md5secret); /*!< Password in md5 */
+ AST_STRING_FIELD(callback); /*!< Contact extension */
+ AST_STRING_FIELD(random);
+ );
+ enum sip_transport transport;
+ int portno; /*!< Optional port override */
+ int expire; /*!< Sched ID of expiration */
+ int expiry; /*!< Value to use for the Expires header */
+ int regattempts; /*!< Number of attempts (since the last success) */
+ int timeout; /*!< sched id of sip_reg_timeout */
+ int refresh; /*!< How often to refresh */
+ struct sip_pvt *call; /*!< create a sip_pvt structure for each outbound "registration dialog" in progress */
+ enum sipregistrystate regstate; /*!< Registration state (see above) */
+ struct timeval regtime; /*!< Last successful registration time */
+ int callid_valid; /*!< 0 means we haven't chosen callid for this registry yet. */
+ unsigned int ocseq; /*!< Sequence number we got to for REGISTERs for this registry */
+ struct sockaddr_in us; /*!< Who the server thinks we are */
+ int noncecount; /*!< Nonce-count */
+ char lastmsg[256]; /*!< Last Message sent/received */
+};
+
+struct sip_threadinfo {
+ int stop;
+ pthread_t threadid;
+ struct server_instance *ser;
+ enum sip_transport type; /* We keep a copy of the type here so we can display it in the connection list */
+ AST_LIST_ENTRY(sip_threadinfo) list;
+};
+
+/* --- Linked lists of various objects --------*/
+
+/*! \brief The thread list of TCP threads */
+static AST_LIST_HEAD_STATIC(threadl, sip_threadinfo);
+
+/*! \brief The user list: Users and friends */
+static struct ast_user_list {
+ ASTOBJ_CONTAINER_COMPONENTS(struct sip_user);
+} userl;
+
+/*! \brief The peer list: Peers and Friends */
+static struct ast_peer_list {
+ ASTOBJ_CONTAINER_COMPONENTS(struct sip_peer);
+} peerl;
+
+/*! \brief The register list: Other SIP proxies we register with and place calls to */
+static struct ast_register_list {
+ ASTOBJ_CONTAINER_COMPONENTS(struct sip_registry);
+ int recheck;
+} regl;
+
+static int temp_pvt_init(void *);
+static void temp_pvt_cleanup(void *);
+
+/*! \brief A per-thread temporary pvt structure */
+AST_THREADSTORAGE_CUSTOM(ts_temp_pvt, temp_pvt_init, temp_pvt_cleanup);
+
+/*! \brief Authentication list for realm authentication
+ * \todo Move the sip_auth list to AST_LIST */
+static struct sip_auth *authl = NULL;
+
+
+/* --- Sockets and networking --------------*/
+
+/*! \brief Main socket for SIP communication.
+ * sipsock is shared between the manager thread (which handles reload
+ * requests), the io handler (sipsock_read()) and the user routines that
+ * issue writes (using __sip_xmit()).
+ * The socket is -1 only when opening fails (this is a permanent condition),
+ * or when we are handling a reload() that changes its address (this is
+ * a transient situation during which we might have a harmless race, see
+ * below). Because the conditions for the race to be possible are extremely
+ * rare, we don't want to pay the cost of locking on every I/O.
+ * Rather, we remember that when the race may occur, communication is
+ * bound to fail anyways, so we just live with this event and let
+ * the protocol handle this above us.
+ */
+static int sipsock = -1;
+
+static struct sockaddr_in bindaddr; /*!< The address we bind to */
+
+/*! \brief our (internal) default address/port to put in SIP/SDP messages
+ * internip is initialized picking a suitable address from one of the
+ * interfaces, and the same port number we bind to. It is used as the
+ * default address/port in SIP messages, and as the default address
+ * (but not port) in SDP messages.
+ */
+static struct sockaddr_in internip;
+
+/*! \brief our external IP address/port for SIP sessions.
+ * externip.sin_addr is only set when we know we might be behind
+ * a NAT, and this is done using a variety of (mutually exclusive)
+ * ways from the config file:
+ *
+ * + with "externip = host[:port]" we specify the address/port explicitly.
+ * The address is looked up only once when (re)loading the config file;
+ *
+ * + with "externhost = host[:port]" we do a similar thing, but the
+ * hostname is stored in externhost, and the hostname->IP mapping
+ * is refreshed every 'externrefresh' seconds;
+ *
+ * + with "stunaddr = host[:port]" we run queries every externrefresh seconds
+ * to the specified server, and store the result in externip.
+ *
+ * Other variables (externhost, externexpire, externrefresh) are used
+ * to support the above functions.
+ */
+static struct sockaddr_in externip; /*!< External IP address if we are behind NAT */
+
+static char externhost[MAXHOSTNAMELEN]; /*!< External host name */
+static time_t externexpire; /*!< Expiration counter for re-resolving external host name in dynamic DNS */
+static int externrefresh = 10;
+static struct sockaddr_in stunaddr; /*!< stun server address */
+
+/*! \brief List of local networks
+ * We store "localnet" addresses from the config file into an access list,
+ * marked as 'DENY', so the call to ast_apply_ha() will return
+ * AST_SENSE_DENY for 'local' addresses, and AST_SENSE_ALLOW for 'non local'
+ * (i.e. presumably public) addresses.
+ */
+static struct ast_ha *localaddr; /*!< List of local networks, on the same side of NAT as this Asterisk */
+
+static int ourport_tcp;
+static int ourport_tls;
+static struct sockaddr_in debugaddr;
+
+static struct ast_config *notify_types; /*!< The list of manual NOTIFY types we know how to send */
+
+/*! some list management macros. */
+
+#define UNLINK(element, head, prev) do { \
+ if (prev) \
+ (prev)->next = (element)->next; \
+ else \
+ (head) = (element)->next; \
+ } while (0)
+
+/*---------------------------- Forward declarations of functions in chan_sip.c */
+/*! \note This is added to help splitting up chan_sip.c into several files
+ in coming releases */
+
+/*--- PBX interface functions */
+static struct ast_channel *sip_request_call(const char *type, int format, void *data, int *cause);
+static int sip_devicestate(void *data);
+static int sip_sendtext(struct ast_channel *ast, const char *text);
+static int sip_call(struct ast_channel *ast, char *dest, int timeout);
+static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen);
+static int sip_hangup(struct ast_channel *ast);
+static int sip_answer(struct ast_channel *ast);
+static struct ast_frame *sip_read(struct ast_channel *ast);
+static int sip_write(struct ast_channel *ast, struct ast_frame *frame);
+static int sip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+static int sip_transfer(struct ast_channel *ast, const char *dest);
+static int sip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static int sip_senddigit_begin(struct ast_channel *ast, char digit);
+static int sip_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration);
+
+static int handle_request_do(struct sip_request *req, struct sockaddr_in *sin);
+static int sip_standard_port(struct sip_socket s);
+static int sip_prepare_socket(struct sip_pvt *p);
+
+/*--- Transmitting responses and requests */
+static int sipsock_read(int *id, int fd, short events, void *ignore);
+static int __sip_xmit(struct sip_pvt *p, char *data, int len);
+static int __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, char *data, int len, int fatal, int sipmethod);
+static int __transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable);
+static int retrans_pkt(const void *data);
+static int transmit_sip_request(struct sip_pvt *p, struct sip_request *req);
+static int transmit_response_using_temp(ast_string_field callid, struct sockaddr_in *sin, int useglobal_nat, const int intended_method, const struct sip_request *req, const char *msg);
+static int transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req);
+static int transmit_response_reliable(struct sip_pvt *p, const char *msg, const struct sip_request *req);
+static int transmit_response_with_date(struct sip_pvt *p, const char *msg, const struct sip_request *req);
+static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp);
+static int transmit_response_with_unsupported(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *unsupported);
+static int transmit_response_with_auth(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *rand, enum xmittype reliable, const char *header, int stale);
+static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable);
+static void transmit_fake_auth_response(struct sip_pvt *p, struct sip_request *req, int reliable);
+static int transmit_request(struct sip_pvt *p, int sipmethod, int inc, enum xmittype reliable, int newbranch);
+static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch);
+static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init);
+static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp);
+static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration);
+static int transmit_info_with_vidupdate(struct sip_pvt *p);
+static int transmit_message_with_text(struct sip_pvt *p, const char *text);
+static int transmit_refer(struct sip_pvt *p, const char *dest);
+static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, char *vmexten);
+static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate);
+static int transmit_register(struct sip_registry *r, int sipmethod, const char *auth, const char *authheader);
+static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno);
+static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno);
+static void copy_request(struct sip_request *dst, const struct sip_request *src);
+static void receive_message(struct sip_pvt *p, struct sip_request *req);
+static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req);
+static int sip_send_mwi_to_peer(struct sip_peer *peer, const struct ast_event *event, int cache_only);
+
+/*--- Dialog management */
+static struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *sin,
+ int useglobal_nat, const int intended_method);
+static int __sip_autodestruct(const void *data);
+static void sip_scheddestroy(struct sip_pvt *p, int ms);
+static void sip_cancel_destroy(struct sip_pvt *p);
+static struct sip_pvt *sip_destroy(struct sip_pvt *p);
+static void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist);
+static void __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod);
+static void __sip_pretend_ack(struct sip_pvt *p);
+static int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod);
+static int auto_congest(const void *arg);
+static int update_call_counter(struct sip_pvt *fup, int event);
+static int hangup_sip2cause(int cause);
+static const char *hangup_cause2sip(int cause);
+static struct sip_pvt *find_call(struct sip_request *req, struct sockaddr_in *sin, const int intended_method);
+static void free_old_route(struct sip_route *route);
+static void list_route(struct sip_route *route);
+static void build_route(struct sip_pvt *p, struct sip_request *req, int backwards);
+static enum check_auth_result register_verify(struct sip_pvt *p, struct sockaddr_in *sin,
+ struct sip_request *req, char *uri);
+static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *totag, const char *fromtag);
+static void check_pendings(struct sip_pvt *p);
+static void *sip_park_thread(void *stuff);
+static int sip_park(struct ast_channel *chan1, struct ast_channel *chan2, struct sip_request *req, int seqno);
+static int sip_sipredirect(struct sip_pvt *p, const char *dest);
+
+/*--- Codec handling / SDP */
+static void try_suggested_sip_codec(struct sip_pvt *p);
+static const char* get_sdp_iterate(int* start, struct sip_request *req, const char *name);
+static const char *get_sdp(struct sip_request *req, const char *name);
+static int find_sdp(struct sip_request *req);
+static int process_sdp(struct sip_pvt *p, struct sip_request *req);
+static void add_codec_to_sdp(const struct sip_pvt *p, int codec, int sample_rate,
+ struct ast_str **m_buf, struct ast_str **a_buf,
+ int debug, int *min_packet_size);
+static void add_noncodec_to_sdp(const struct sip_pvt *p, int format, int sample_rate,
+ struct ast_str **m_buf, struct ast_str **a_buf,
+ int debug);
+static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp);
+static void do_setnat(struct sip_pvt *p, int natflags);
+static void stop_media_flows(struct sip_pvt *p);
+
+/*--- Authentication stuff */
+static int reply_digest(struct sip_pvt *p, struct sip_request *req, char *header, int sipmethod, char *digest, int digest_len);
+static int build_reply_digest(struct sip_pvt *p, int method, char *digest, int digest_len);
+static enum check_auth_result check_auth(struct sip_pvt *p, struct sip_request *req, const char *username,
+ const char *secret, const char *md5secret, int sipmethod,
+ char *uri, enum xmittype reliable, int ignore);
+static enum check_auth_result check_user_full(struct sip_pvt *p, struct sip_request *req,
+ int sipmethod, char *uri, enum xmittype reliable,
+ struct sockaddr_in *sin, struct sip_peer **authpeer);
+static int check_user(struct sip_pvt *p, struct sip_request *req, int sipmethod, char *uri, enum xmittype reliable, struct sockaddr_in *sin);
+
+/*--- Domain handling */
+static int check_sip_domain(const char *domain, char *context, size_t len); /* Check if domain is one of our local domains */
+static int add_sip_domain(const char *domain, const enum domain_mode mode, const char *context);
+static void clear_sip_domains(void);
+
+/*--- SIP realm authentication */
+static struct sip_auth *add_realm_authentication(struct sip_auth *authlist, const char *configuration, int lineno);
+static int clear_realm_authentication(struct sip_auth *authlist); /* Clear realm authentication list (at reload) */
+static struct sip_auth *find_realm_authentication(struct sip_auth *authlist, const char *realm);
+
+/*--- Misc functions */
+static int sip_do_reload(enum channelreloadreason reason);
+static int reload_config(enum channelreloadreason reason);
+static int expire_register(const void *data);
+static void *do_monitor(void *data);
+static int restart_monitor(void);
+static int sip_addrcmp(char *name, struct sockaddr_in *sin); /* Support for peer matching */
+static int sip_refer_allocate(struct sip_pvt *p);
+static void ast_quiet_chan(struct ast_channel *chan);
+static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target);
+
+/*--- Device monitoring and Device/extension state/event handling */
+static int cb_extensionstate(char *context, char* exten, int state, void *data);
+static int sip_devicestate(void *data);
+static int sip_poke_noanswer(const void *data);
+static int sip_poke_peer(struct sip_peer *peer);
+static void sip_poke_all_peers(void);
+static void sip_peer_hold(struct sip_pvt *p, int hold);
+static void mwi_event_cb(const struct ast_event *, void *);
+
+/*--- Applications, functions, CLI and manager command helpers */
+static const char *sip_nat_mode(const struct sip_pvt *p);
+static char *sip_show_inuse(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *transfermode2str(enum transfermodes mode) attribute_const;
+static const char *nat2str(int nat) attribute_const;
+static int peer_status(struct sip_peer *peer, char *status, int statuslen);
+static char *sip_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char * _sip_show_peers(int fd, int *total, struct mansession *s, const struct message *m, int argc, const char *argv[]);
+static char *sip_show_peers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *sip_show_objects(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static void print_group(int fd, ast_group_t group, int crlf);
+static const char *dtmfmode2str(int mode) attribute_const;
+static int str2dtmfmode(const char *str) attribute_unused;
+static const char *insecure2str(int mode) attribute_const;
+static void cleanup_stale_contexts(char *new, char *old);
+static void print_codec_to_cli(int fd, struct ast_codec_pref *pref);
+static const char *domain_mode_to_text(const enum domain_mode mode);
+static char *sip_show_domains(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct message *m, int argc, const char *argv[]);
+static char *sip_show_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *sip_show_registry(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *sip_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *sip_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static const char *subscription_type2str(enum subscriptiontype subtype) attribute_pure;
+static const struct cfsubscription_types *find_subscription_type(enum subscriptiontype subtype);
+static char *complete_sip_peer(const char *word, int state, int flags2);
+static char *complete_sip_registered_peer(const char *word, int state, int flags2);
+static char *complete_sip_show_history(const char *line, const char *word, int pos, int state);
+static char *complete_sip_show_peer(const char *line, const char *word, int pos, int state);
+static char *complete_sip_unregister(const char *line, const char *word, int pos, int state);
+static char *complete_sip_user(const char *word, int state, int flags2);
+static char *complete_sip_show_user(const char *line, const char *word, int pos, int state);
+static char *complete_sipnotify(const char *line, const char *word, int pos, int state);
+static char *sip_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *sip_show_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *sip_do_debug_ip(int fd, char *arg);
+static char *sip_do_debug_peer(int fd, char *arg);
+static char *sip_do_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *sip_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *sip_do_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *sip_no_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static int sip_dtmfmode(struct ast_channel *chan, void *data);
+static int sip_addheader(struct ast_channel *chan, void *data);
+static int sip_do_reload(enum channelreloadreason reason);
+static char *sip_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static int acf_channel_read(struct ast_channel *chan, const char *funcname, char *preparse, char *buf, size_t buflen);
+
+/*--- Debugging
+ Functions for enabling debug per IP or fully, or enabling history logging for
+ a SIP dialog
+*/
+static void sip_dump_history(struct sip_pvt *dialog); /* Dump history to debuglog at end of dialog, before destroying data */
+static inline int sip_debug_test_addr(const struct sockaddr_in *addr);
+static inline int sip_debug_test_pvt(struct sip_pvt *p);
+
+
+/*! \brief Append to SIP dialog history
+ \return Always returns 0 */
+#define append_history(p, event, fmt , args... ) append_history_full(p, "%-15s " fmt, event, ## args)
+static void append_history_full(struct sip_pvt *p, const char *fmt, ...);
+static void sip_dump_history(struct sip_pvt *dialog);
+
+/*--- Device object handling */
+static struct sip_peer *temp_peer(const char *name);
+static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime);
+static struct sip_user *build_user(const char *name, struct ast_variable *v, int realtime);
+static int update_call_counter(struct sip_pvt *fup, int event);
+static void sip_destroy_peer(struct sip_peer *peer);
+static void sip_destroy_user(struct sip_user *user);
+static int sip_poke_peer(struct sip_peer *peer);
+static void set_peer_defaults(struct sip_peer *peer);
+static struct sip_peer *temp_peer(const char *name);
+static void register_peer_exten(struct sip_peer *peer, int onoff);
+static struct sip_peer *find_peer(const char *peer, struct sockaddr_in *sin, int realtime);
+static struct sip_user *find_user(const char *name, int realtime);
+static int sip_poke_peer_s(const void *data);
+static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *p, struct sip_request *req);
+static void reg_source_db(struct sip_peer *peer);
+static void destroy_association(struct sip_peer *peer);
+static void set_insecure_flags(struct ast_flags *flags, const char *value, int lineno);
+static int handle_common_options(struct ast_flags *flags, struct ast_flags *mask, struct ast_variable *v);
+
+/* Realtime device support */
+static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, const char *username, const char *fullcontact, int expirey);
+static struct sip_user *realtime_user(const char *username);
+static void update_peer(struct sip_peer *p, int expiry);
+static struct ast_variable *get_insecure_variable_from_config(struct ast_config *config);
+static const char *get_name_from_variable(struct ast_variable *var, const char *newpeername);
+static struct sip_peer *realtime_peer(const char *peername, struct sockaddr_in *sin);
+static char *sip_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+
+/*--- Internal UA client handling (outbound registrations) */
+static void ast_sip_ouraddrfor(struct in_addr *them, struct sockaddr_in *us);
+static void sip_registry_destroy(struct sip_registry *reg);
+static int sip_register(const char *value, int lineno);
+static const char *regstate2str(enum sipregistrystate regstate) attribute_const;
+static int sip_reregister(const void *data);
+static int __sip_do_register(struct sip_registry *r);
+static int sip_reg_timeout(const void *data);
+static void sip_send_all_registers(void);
+
+/*--- Parsing SIP requests and responses */
+static void append_date(struct sip_request *req); /* Append date to SIP packet */
+static int determine_firstline_parts(struct sip_request *req);
+static const struct cfsubscription_types *find_subscription_type(enum subscriptiontype subtype);
+static const char *gettag(const struct sip_request *req, const char *header, char *tagbuf, int tagbufsize);
+static int find_sip_method(const char *msg);
+static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported);
+static void parse_request(struct sip_request *req);
+static const char *get_header(const struct sip_request *req, const char *name);
+static const char *referstatus2str(enum referstatus rstatus) attribute_pure;
+static int method_match(enum sipmethod id, const char *name);
+static void parse_copy(struct sip_request *dst, const struct sip_request *src);
+static char *get_in_brackets(char *tmp);
+static const char *find_alias(const char *name, const char *_default);
+static const char *__get_header(const struct sip_request *req, const char *name, int *start);
+static int lws2sws(char *msgbuf, int len);
+static void extract_uri(struct sip_pvt *p, struct sip_request *req);
+static char *remove_uri_parameters(char *uri);
+static int get_refer_info(struct sip_pvt *transferer, struct sip_request *outgoing_req);
+static int get_also_info(struct sip_pvt *p, struct sip_request *oreq);
+static int parse_ok_contact(struct sip_pvt *pvt, struct sip_request *req);
+static int set_address_from_contact(struct sip_pvt *pvt);
+static void check_via(struct sip_pvt *p, struct sip_request *req);
+static char *get_calleridname(const char *input, char *output, size_t outputsize);
+static int get_rpid_num(const char *input, char *output, int maxlen);
+static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq);
+static int get_destination(struct sip_pvt *p, struct sip_request *oreq);
+static int get_msg_text(char *buf, int len, struct sip_request *req);
+static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout);
+
+/*--- Constructing requests and responses */
+static void initialize_initreq(struct sip_pvt *p, struct sip_request *req);
+static int init_req(struct sip_request *req, int sipmethod, const char *recip);
+static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, int seqno, int newbranch);
+static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod);
+static int init_resp(struct sip_request *resp, const char *msg);
+static int respprep(struct sip_request *resp, struct sip_pvt *p, const char *msg, const struct sip_request *req);
+static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p);
+static void build_via(struct sip_pvt *p);
+static int create_addr_from_peer(struct sip_pvt *r, struct sip_peer *peer);
+static int create_addr(struct sip_pvt *dialog, const char *opeer);
+static char *generate_random_string(char *buf, size_t size);
+static void build_callid_pvt(struct sip_pvt *pvt);
+static void build_callid_registry(struct sip_registry *reg, struct in_addr ourip, const char *fromdomain);
+static void make_our_tag(char *tagbuf, size_t len);
+static int add_header(struct sip_request *req, const char *var, const char *value);
+static int add_header_contentLength(struct sip_request *req, int len);
+static int add_line(struct sip_request *req, const char *line);
+static int add_text(struct sip_request *req, const char *text);
+static int add_digit(struct sip_request *req, char digit, unsigned int duration, int mode);
+static int add_vidupdate(struct sip_request *req);
+static void add_route(struct sip_request *req, struct sip_route *route);
+static int copy_header(struct sip_request *req, const struct sip_request *orig, const char *field);
+static int copy_all_header(struct sip_request *req, const struct sip_request *orig, const char *field);
+static int copy_via_headers(struct sip_pvt *p, struct sip_request *req, const struct sip_request *orig, const char *field);
+static void set_destination(struct sip_pvt *p, char *uri);
+static void append_date(struct sip_request *req);
+static void build_contact(struct sip_pvt *p);
+static void build_rpid(struct sip_pvt *p);
+
+/*------Request handling functions */
+static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int *recount, int *nounlock);
+static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, struct sockaddr_in *sin, int *recount, char *e, int *nounlock);
+static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, int *nounlock);
+static int handle_request_bye(struct sip_pvt *p, struct sip_request *req);
+static int handle_request_register(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, char *e);
+static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req);
+static int handle_request_message(struct sip_pvt *p, struct sip_request *req);
+static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int seqno, char *e);
+static void handle_request_info(struct sip_pvt *p, struct sip_request *req);
+static int handle_request_options(struct sip_pvt *p, struct sip_request *req);
+static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, struct sockaddr_in *sin);
+static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int seqno, char *e);
+static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, int seqno);
+
+/*------Response handling functions */
+static void handle_response_invite(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno);
+static void handle_response_refer(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno);
+static int handle_response_register(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno);
+static void handle_response(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno);
+
+/*----- RTP interface functions */
+static int sip_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp, struct ast_rtp *vrtp, struct ast_rtp *trtp, int codecs, int nat_active);
+static enum ast_rtp_get_result sip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp **rtp);
+static enum ast_rtp_get_result sip_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp **rtp);
+static enum ast_rtp_get_result sip_get_trtp_peer(struct ast_channel *chan, struct ast_rtp **rtp);
+static int sip_get_codec(struct ast_channel *chan);
+static struct ast_frame *sip_rtp_read(struct ast_channel *ast, struct sip_pvt *p, int *faxdetect);
+
+/*------ T38 Support --------- */
+static int sip_handle_t38_reinvite(struct ast_channel *chan, struct sip_pvt *pvt, int reinvite);
+static int transmit_response_with_t38_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans);
+static struct ast_udptl *sip_get_udptl_peer(struct ast_channel *chan);
+static int sip_set_udptl_peer(struct ast_channel *chan, struct ast_udptl *udptl);
+
+/*------ Session-Timers functions --------- */
+static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp);
+static int proc_session_timer(const void *vp);
+static void stop_session_timer(struct sip_pvt *p);
+static void start_session_timer(struct sip_pvt *p);
+static void restart_session_timer(struct sip_pvt *p);
+static const char *strefresher2str(enum st_refresher r);
+static int parse_session_expires(const char *p_hdrval, int *const p_interval, enum st_refresher *const p_ref);
+static int parse_minse(const char *p_hdrval, int *const p_interval);
+static int st_get_se(struct sip_pvt *, int max);
+static enum st_refresher st_get_refresher(struct sip_pvt *);
+static enum st_mode st_get_mode(struct sip_pvt *);
+static struct sip_st_dlg* sip_st_alloc(struct sip_pvt *const p);
+
+
+/*! \brief Definition of this channel for PBX channel registration */
+static const struct ast_channel_tech sip_tech = {
+ .type = "SIP",
+ .description = "Session Initiation Protocol (SIP)",
+ .capabilities = AST_FORMAT_AUDIO_MASK, /* all audio formats */
+ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
+ .requester = sip_request_call, /* called with chan unlocked */
+ .devicestate = sip_devicestate, /* called with chan unlocked (not chan-specific) */
+ .call = sip_call, /* called with chan locked */
+ .send_html = sip_sendhtml,
+ .hangup = sip_hangup, /* called with chan locked */
+ .answer = sip_answer, /* called with chan locked */
+ .read = sip_read, /* called with chan locked */
+ .write = sip_write, /* called with chan locked */
+ .write_video = sip_write, /* called with chan locked */
+ .write_text = sip_write,
+ .indicate = sip_indicate, /* called with chan locked */
+ .transfer = sip_transfer, /* called with chan locked */
+ .fixup = sip_fixup, /* called with chan locked */
+ .send_digit_begin = sip_senddigit_begin, /* called with chan unlocked */
+ .send_digit_end = sip_senddigit_end,
+ .bridge = ast_rtp_bridge, /* XXX chan unlocked ? */
+ .early_bridge = ast_rtp_early_bridge,
+ .send_text = sip_sendtext, /* called with chan locked */
+ .func_channel_read = acf_channel_read,
+};
+
+/*! \brief This version of the sip channel tech has no send_digit_begin
+ * callback so that the core knows that the channel does not want
+ * DTMF BEGIN frames.
+ * The struct is initialized just before registering the channel driver,
+ * and is for use with channels using SIP INFO DTMF.
+ */
+static struct ast_channel_tech sip_tech_info;
+
+static void *sip_tcp_worker_fn(void *);
+
+static struct ast_tls_config sip_tls_cfg;
+static struct ast_tls_config default_tls_cfg;
+
+static struct server_args sip_tcp_desc = {
+ .accept_fd = -1,
+ .master = AST_PTHREADT_NULL,
+ .tls_cfg = NULL,
+ .poll_timeout = -1,
+ .name = "sip tcp server",
+ .accept_fn = server_root,
+ .worker_fn = sip_tcp_worker_fn,
+};
+
+static struct server_args sip_tls_desc = {
+ .accept_fd = -1,
+ .master = AST_PTHREADT_NULL,
+ .tls_cfg = &sip_tls_cfg,
+ .poll_timeout = -1,
+ .name = "sip tls server",
+ .accept_fn = server_root,
+ .worker_fn = sip_tcp_worker_fn,
+};
+
+/* wrapper macro to tell whether t points to one of the sip_tech descriptors */
+#define IS_SIP_TECH(t) ((t) == &sip_tech || (t) == &sip_tech_info)
+
+/*! \brief map from an integer value to a string.
+ * If no match is found, return errorstring
+ */
+static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring)
+{
+ const struct _map_x_s *cur;
+
+ for (cur = table; cur->s; cur++)
+ if (cur->x == x)
+ return cur->s;
+ return errorstring;
+}
+
+/*! \brief map from a string to an integer value, case insensitive.
+ * If no match is found, return errorvalue.
+ */
+static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
+{
+ const struct _map_x_s *cur;
+
+ for (cur = table; cur->s; cur++)
+ if (!strcasecmp(cur->s, s))
+ return cur->x;
+ return errorvalue;
+}
+
+
+/*! \brief Interface structure with callbacks used to connect to RTP module */
+static struct ast_rtp_protocol sip_rtp = {
+ .type = "SIP",
+ .get_rtp_info = sip_get_rtp_peer,
+ .get_vrtp_info = sip_get_vrtp_peer,
+ .get_trtp_info = sip_get_trtp_peer,
+ .set_rtp_peer = sip_set_rtp_peer,
+ .get_codec = sip_get_codec,
+};
+
+static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct server_instance *ser);
+
+static void *sip_tcp_helper_thread(void *data)
+{
+ struct sip_pvt *pvt = data;
+ struct server_instance *ser = pvt->socket.ser;
+
+ return _sip_tcp_helper_thread(pvt, ser);
+}
+
+static void *sip_tcp_worker_fn(void *data)
+{
+ struct server_instance *ser = data;
+
+ return _sip_tcp_helper_thread(NULL, ser);
+}
+
+/*! \brief SIP TCP helper function */
+static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct server_instance *ser)
+{
+ int res, cl;
+ struct sip_request req = { 0, } , reqcpy = { 0, };
+ struct sip_threadinfo *me;
+ char buf[1024];
+
+ me = ast_calloc(1, sizeof(*me));
+
+ if (!me)
+ goto cleanup2;
+
+ me->threadid = pthread_self();
+ me->ser = ser;
+ if (ser->ssl)
+ me->type = SIP_TRANSPORT_TLS;
+ else
+ me->type = SIP_TRANSPORT_TCP;
+
+ AST_LIST_LOCK(&threadl);
+ AST_LIST_INSERT_TAIL(&threadl, me, list);
+ AST_LIST_UNLOCK(&threadl);
+
+ req.socket.lock = ast_calloc(1, sizeof(*req.socket.lock));
+
+ if (!req.socket.lock)
+ goto cleanup;
+
+ ast_mutex_init(req.socket.lock);
+
+ for (;;) {
+ memset(req.data, 0, sizeof(req.data));
+ req.len = 0;
+ req.ignore = 0;
+
+ req.socket.fd = ser->fd;
+ if (ser->ssl) {
+ req.socket.type = SIP_TRANSPORT_TLS;
+ req.socket.port = htons(ourport_tls);
+ } else {
+ req.socket.type = SIP_TRANSPORT_TCP;
+ req.socket.port = htons(ourport_tcp);
+ }
+ res = ast_wait_for_input(ser->fd, -1);
+ if (res < 0) {
+ ast_log(LOG_DEBUG, "ast_wait_for_input returned %d\n", res);
+ goto cleanup;
+ }
+
+ /* Read in headers one line at a time */
+ while (req.len < 4 || strncmp((char *)&req.data + req.len - 4, "\r\n\r\n", 4)) {
+ if (req.socket.lock)
+ ast_mutex_lock(req.socket.lock);
+ if (!fgets(buf, sizeof(buf), ser->f))
+ goto cleanup;
+ if (req.socket.lock)
+ ast_mutex_unlock(req.socket.lock);
+ if (me->stop)
+ goto cleanup;
+ strncat(req.data, buf, sizeof(req.data) - req.len);
+ req.len = strlen(req.data);
+ }
+ parse_copy(&reqcpy, &req);
+ if (sscanf(get_header(&reqcpy, "Content-Length"), "%d", &cl)) {
+ while (cl > 0) {
+ if (req.socket.lock)
+ ast_mutex_lock(req.socket.lock);
+ if (!fread(buf, (cl < sizeof(buf)) ? cl : sizeof(buf), 1, ser->f))
+ goto cleanup;
+ if (req.socket.lock)
+ ast_mutex_unlock(req.socket.lock);
+ if (me->stop)
+ goto cleanup;
+ cl -= strlen(buf);
+ strncat(req.data, buf, sizeof(req.data) - req.len);
+ req.len = strlen(req.data);
+ }
+ }
+ req.socket.ser = ser;
+ handle_request_do(&req, &ser->requestor);
+ }
+
+cleanup:
+ AST_LIST_LOCK(&threadl);
+ AST_LIST_REMOVE(&threadl, me, list);
+ AST_LIST_UNLOCK(&threadl);
+ ast_free(me);
+cleanup2:
+ fclose(ser->f);
+ ast_free(ser);
+ ast_free(req.socket.lock);
+
+ return NULL;
+}
+
+#define sip_pvt_lock(x) ast_mutex_lock(&x->pvt_lock)
+#define sip_pvt_unlock(x) ast_mutex_unlock(&x->pvt_lock)
+
+/*!
+ * helper functions to unreference various types of objects.
+ * By handling them this way, we don't have to declare the
+ * destructor on each call, which removes the chance of errors.
+ */
+static void unref_peer(struct sip_peer *peer)
+{
+ ASTOBJ_UNREF(peer, sip_destroy_peer);
+}
+
+static void unref_user(struct sip_user *user)
+{
+ ASTOBJ_UNREF(user, sip_destroy_user);
+}
+
+static void *registry_unref(struct sip_registry *reg)
+{
+ ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount - 1);
+ ASTOBJ_UNREF(reg, sip_registry_destroy);
+ return NULL;
+}
+
+/*! \brief Add object reference to SIP registry */
+static struct sip_registry *registry_addref(struct sip_registry *reg)
+{
+ ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount + 1);
+ return ASTOBJ_REF(reg); /* Add pointer to registry in packet */
+}
+
+/*! \brief Interface structure with callbacks used to connect to UDPTL module*/
+static struct ast_udptl_protocol sip_udptl = {
+ type: "SIP",
+ get_udptl_info: sip_get_udptl_peer,
+ set_udptl_peer: sip_set_udptl_peer,
+};
+
+static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
+ __attribute__ ((format (printf, 2, 3)));
+
+
+/*! \brief Convert transfer status to string */
+static const char *referstatus2str(enum referstatus rstatus)
+{
+ return map_x_s(referstatusstrings, rstatus, "");
+}
+
+/*! \brief Initialize the initital request packet in the pvt structure.
+ This packet is used for creating replies and future requests in
+ a dialog */
+static void initialize_initreq(struct sip_pvt *p, struct sip_request *req)
+{
+ if (p->initreq.headers)
+ ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid);
+ else
+ ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
+ /* Use this as the basis */
+ copy_request(&p->initreq, req);
+ parse_request(&p->initreq);
+ if (req->debug)
+ ast_verbose("Initreq: %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
+}
+
+/*! \brief Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */
+static void sip_alreadygone(struct sip_pvt *dialog)
+{
+ ast_debug(3, "Setting SIP_ALREADYGONE on dialog %s\n", dialog->callid);
+ dialog->alreadygone = 1;
+}
+
+/*! Resolve DNS srv name or host name in a sip_proxy structure */
+static int proxy_update(struct sip_proxy *proxy)
+{
+ /* if it's actually an IP address and not a name,
+ there's no need for a managed lookup */
+ if (!inet_aton(proxy->name, &proxy->ip.sin_addr)) {
+ /* Ok, not an IP address, then let's check if it's a domain or host */
+ /* XXX Todo - if we have proxy port, don't do SRV */
+ if (ast_get_ip_or_srv(&proxy->ip, proxy->name, global_srvlookup ? "_sip._udp" : NULL) < 0) {
+ ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name);
+ return FALSE;
+ }
+ }
+ proxy->last_dnsupdate = time(NULL);
+ return TRUE;
+}
+
+/*! \brief Allocate and initialize sip proxy */
+static struct sip_proxy *proxy_allocate(char *name, char *port, int force)
+{
+ struct sip_proxy *proxy;
+ proxy = ast_calloc(1, sizeof(*proxy));
+ if (!proxy)
+ return NULL;
+ proxy->force = force;
+ ast_copy_string(proxy->name, name, sizeof(proxy->name));
+ proxy->ip.sin_port = htons((!ast_strlen_zero(port) ? atoi(port) : STANDARD_SIP_PORT));
+ proxy_update(proxy);
+ return proxy;
+}
+
+/*! \brief Get default outbound proxy or global proxy */
+static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer)
+{
+ if (peer && peer->outboundproxy) {
+ if (sipdebug)
+ ast_debug(1, "OBPROXY: Applying peer OBproxy to this call\n");
+ append_history(dialog, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->name);
+ return peer->outboundproxy;
+ }
+ if (global_outboundproxy.name[0]) {
+ if (sipdebug)
+ ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
+ append_history(dialog, "OBproxy", "Using global obproxy %s", global_outboundproxy.name);
+ return &global_outboundproxy;
+ }
+ if (sipdebug)
+ ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n");
+ return NULL;
+}
+
+/*! \brief returns true if 'name' (with optional trailing whitespace)
+ * matches the sip method 'id'.
+ * Strictly speaking, SIP methods are case SENSITIVE, but we do
+ * a case-insensitive comparison to be more tolerant.
+ * following Jon Postel's rule: Be gentle in what you accept, strict with what you send
+ */
+static int method_match(enum sipmethod id, const char *name)
+{
+ int len = strlen(sip_methods[id].text);
+ int l_name = name ? strlen(name) : 0;
+ /* true if the string is long enough, and ends with whitespace, and matches */
+ return (l_name >= len && name[len] < 33 &&
+ !strncasecmp(sip_methods[id].text, name, len));
+}
+
+/*! \brief find_sip_method: Find SIP method from header */
+static int find_sip_method(const char *msg)
+{
+ int i, res = 0;
+
+ if (ast_strlen_zero(msg))
+ return 0;
+ for (i = 1; i < (sizeof(sip_methods) / sizeof(sip_methods[0])) && !res; i++) {
+ if (method_match(i, msg))
+ res = sip_methods[i].id;
+ }
+ return res;
+}
+
+/*! \brief Parse supported header in incoming packet */
+static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported)
+{
+ char *next, *sep;
+ char *temp;
+ unsigned int profile = 0;
+ int i, found;
+
+ if (ast_strlen_zero(supported) )
+ return 0;
+ temp = ast_strdupa(supported);
+
+ if (sipdebug)
+ ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", supported);
+
+ for (next = temp; next; next = sep) {
+ found = FALSE;
+ if ( (sep = strchr(next, ',')) != NULL)
+ *sep++ = '\0';
+ next = ast_skip_blanks(next);
+ if (sipdebug)
+ ast_debug(3, "Found SIP option: -%s-\n", next);
+ for (i=0; i < (sizeof(sip_options) / sizeof(sip_options[0])); i++) {
+ if (!strcasecmp(next, sip_options[i].text)) {
+ profile |= sip_options[i].id;
+ found = TRUE;
+ if (sipdebug)
+ ast_debug(3, "Matched SIP option: %s\n", next);
+ break;
+ }
+ }
+
+ /* This function is used to parse both Suported: and Require: headers.
+ Let the caller of this function know that an unknown option tag was
+ encountered, so that if the UAC requires it then the request can be
+ rejected with a 420 response. */
+ if (!found)
+ profile |= SIP_OPT_UNKNOWN;
+
+ if (!found && sipdebug) {
+ if (!strncasecmp(next, "x-", 2))
+ ast_debug(3, "Found private SIP option, not supported: %s\n", next);
+ else
+ ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next);
+ }
+ }
+
+ if (pvt)
+ pvt->sipoptions = profile;
+ return profile;
+}
+
+/*! \brief See if we pass debug IP filter */
+static inline int sip_debug_test_addr(const struct sockaddr_in *addr)
+{
+ if (!sipdebug)
+ return 0;
+ if (debugaddr.sin_addr.s_addr) {
+ if (((ntohs(debugaddr.sin_port) != 0)
+ && (debugaddr.sin_port != addr->sin_port))
+ || (debugaddr.sin_addr.s_addr != addr->sin_addr.s_addr))
+ return 0;
+ }
+ return 1;
+}
+
+/*! \brief The real destination address for a write */
+static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p)
+{
+ if (p->outboundproxy)
+ return &p->outboundproxy->ip;
+
+ return ast_test_flag(&p->flags[0], SIP_NAT) & SIP_NAT_ROUTE ? &p->recv : &p->sa;
+}
+
+/*! \brief Display SIP nat mode */
+static const char *sip_nat_mode(const struct sip_pvt *p)
+{
+ return ast_test_flag(&p->flags[0], SIP_NAT) & SIP_NAT_ROUTE ? "NAT" : "no NAT";
+}
+
+/*! \brief Test PVT for debugging output */
+static inline int sip_debug_test_pvt(struct sip_pvt *p)
+{
+ if (!sipdebug)
+ return 0;
+ return sip_debug_test_addr(sip_real_dst(p));
+}
+
+static inline const char *get_transport(enum sip_transport t)
+{
+ switch (t) {
+ case SIP_TRANSPORT_UDP:
+ return "UDP";
+ case SIP_TRANSPORT_TCP:
+ return "TCP";
+ case SIP_TRANSPORT_TLS:
+ return "TLS";
+ }
+
+ return "UNKNOWN";
+}
+
+/*! \brief Transmit SIP message */
+static int __sip_xmit(struct sip_pvt *p, char *data, int len)
+{
+ int res = 0;
+ const struct sockaddr_in *dst = sip_real_dst(p);
+
+ ast_log(LOG_DEBUG, "Trying to put '%.10s' onto %s socket...\n", data, get_transport(p->socket.type));
+
+ if (sip_prepare_socket(p) < 0)
+ return XMIT_ERROR;
+
+ if (p->socket.lock)
+ ast_mutex_lock(p->socket.lock);
+
+ if (p->socket.type & SIP_TRANSPORT_UDP)
+ res = sendto(p->socket.fd, data, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
+ else {
+ if (p->socket.ser->f)
+ res = server_write(p->socket.ser, data, len);
+ else
+ ast_log(LOG_DEBUG, "No p->socket.ser->f len=%d\n", len);
+ }
+
+ if (p->socket.lock)
+ ast_mutex_unlock(p->socket.lock);
+
+ if (res == -1) {
+ switch (errno) {
+ case EBADF: /* Bad file descriptor - seems like this is generated when the host exist, but doesn't accept the UDP packet */
+ case EHOSTUNREACH: /* Host can't be reached */
+ case ENETDOWN: /* Interface down */
+ case ENETUNREACH: /* Network failure */
+ res = XMIT_ERROR; /* Don't bother with trying to transmit again */
+ }
+ }
+ if (res != len)
+ ast_log(LOG_WARNING, "sip_xmit of %p (len %d) to %s:%d returned %d: %s\n", data, len, ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), res, strerror(errno));
+
+ return res;
+}
+
+/*! \brief Build a Via header for a request */
+static void build_via(struct sip_pvt *p)
+{
+ /* Work around buggy UNIDEN UIP200 firmware */
+ const char *rport = ast_test_flag(&p->flags[0], SIP_NAT) & SIP_NAT_RFC3581 ? ";rport" : "";
+
+ /* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */
+ ast_string_field_build(p, via, "SIP/2.0/%s %s:%d;branch=z9hG4bK%08x%s",
+ get_transport(p->socket.type),
+ ast_inet_ntoa(p->ourip.sin_addr),
+ ntohs(p->ourip.sin_port), p->branch, rport);
+}
+
+/*! \brief NAT fix - decide which IP address to use for Asterisk server?
+ *
+ * Using the localaddr structure built up with localnet statements in sip.conf
+ * apply it to their address to see if we need to substitute our
+ * externip or can get away with our internal bindaddr
+ * 'us' is always overwritten.
+ */
+static void ast_sip_ouraddrfor(struct in_addr *them, struct sockaddr_in *us)
+{
+ struct sockaddr_in theirs;
+ /* Set want_remap to non-zero if we want to remap 'us' to an externally
+ * reachable IP address and port. This is done if:
+ * 1. we have a localaddr list (containing 'internal' addresses marked
+ * as 'deny', so ast_apply_ha() will return AST_SENSE_DENY on them,
+ * and AST_SENSE_ALLOW on 'external' ones);
+ * 2. either stunaddr or externip is set, so we know what to use as the
+ * externally visible address;
+ * 3. the remote address, 'them', is external;
+ * 4. the address returned by ast_ouraddrfor() is 'internal' (AST_SENSE_DENY
+ * when passed to ast_apply_ha() so it does need to be remapped.
+ * This fourth condition is checked later.
+ */
+ int want_remap;
+
+ *us = internip; /* starting guess for the internal address */
+ /* now ask the system what would it use to talk to 'them' */
+ ast_ouraddrfor(them, &us->sin_addr);
+ theirs.sin_addr = *them;
+
+ want_remap = localaddr &&
+ (externip.sin_addr.s_addr || stunaddr.sin_addr.s_addr) &&
+ ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
+
+ if (want_remap &&
+ (!global_matchexterniplocally || !ast_apply_ha(localaddr, us)) ) {
+ /* if we used externhost or stun, see if it is time to refresh the info */
+ if (externexpire && time(NULL) >= externexpire) {
+ if (stunaddr.sin_addr.s_addr) {
+ ast_stun_request(sipsock, &stunaddr, NULL, &externip);
+ } else {
+ if (ast_parse_arg(externhost, PARSE_INADDR, &externip))
+ ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
+ }
+ externexpire = time(NULL) + externrefresh;
+ }
+ if (externip.sin_addr.s_addr)
+ *us = externip;
+ else
+ ast_log(LOG_WARNING, "stun failed\n");
+ ast_debug(1, "Target address %s is not local, substituting externip\n",
+ ast_inet_ntoa(*(struct in_addr *)&them->s_addr));
+ } else if (bindaddr.sin_addr.s_addr) {
+ /* no remapping, but we bind to a specific address, so use it. */
+ *us = bindaddr;
+ }
+}
+
+/*! \brief Append to SIP dialog history with arg list */
+static void append_history_va(struct sip_pvt *p, const char *fmt, va_list ap)
+{
+ char buf[80], *c = buf; /* max history length */
+ struct sip_history *hist;
+ int l;
+
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ strsep(&c, "\r\n"); /* Trim up everything after \r or \n */
+ l = strlen(buf) + 1;
+ if (!(hist = ast_calloc(1, sizeof(*hist) + l)))
+ return;
+ if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) {
+ ast_free(hist);
+ return;
+ }
+ memcpy(hist->event, buf, l);
+ if (p->history_entries == MAX_HISTORY_ENTRIES) {
+ struct sip_history *oldest;
+ oldest = AST_LIST_REMOVE_HEAD(p->history, list);
+ p->history_entries--;
+ ast_free(oldest);
+ }
+ AST_LIST_INSERT_TAIL(p->history, hist, list);
+ p->history_entries++;
+}
+
+/*! \brief Append to SIP dialog history with arg list */
+static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!p)
+ return;
+
+ if (!p->do_history && !recordhistory && !dumphistory)
+ return;
+
+ va_start(ap, fmt);
+ append_history_va(p, fmt, ap);
+ va_end(ap);
+
+ return;
+}
+
+/*! \brief Retransmit SIP message if no answer (Called from scheduler) */
+static int retrans_pkt(const void *data)
+{
+ struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
+ int reschedule = DEFAULT_RETRANS;
+ int xmitres = 0;
+
+ /* Lock channel PVT */
+ sip_pvt_lock(pkt->owner);
+
+ if (pkt->retrans < MAX_RETRANS) {
+ pkt->retrans++;
+ if (!pkt->timer_t1) { /* Re-schedule using timer_a and timer_t1 */
+ if (sipdebug)
+ ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method);
+ } else {
+ int siptimer_a;
+
+ if (sipdebug)
+ ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method);
+ if (!pkt->timer_a)
+ pkt->timer_a = 2 ;
+ else
+ pkt->timer_a = 2 * pkt->timer_a;
+
+ /* For non-invites, a maximum of 4 secs */
+ siptimer_a = pkt->timer_t1 * pkt->timer_a; /* Double each time */
+ if (pkt->method != SIP_INVITE && siptimer_a > 4000)
+ siptimer_a = 4000;
+
+ /* Reschedule re-transmit */
+ reschedule = siptimer_a;
+ ast_debug(4, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n", pkt->retrans +1, siptimer_a, pkt->timer_t1, pkt->retransid);
+ }
+
+ if (sip_debug_test_pvt(pkt->owner)) {
+ const struct sockaddr_in *dst = sip_real_dst(pkt->owner);
+ ast_verbose("Retransmitting #%d (%s) to %s:%d:\n%s\n---\n",
+ pkt->retrans, sip_nat_mode(pkt->owner),
+ ast_inet_ntoa(dst->sin_addr),
+ ntohs(dst->sin_port), pkt->data);
+ }
+
+ append_history(pkt->owner, "ReTx", "%d %s", reschedule, pkt->data);
+ xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);
+ sip_pvt_unlock(pkt->owner);
+ if (xmitres == XMIT_ERROR)
+ ast_log(LOG_WARNING, "Network error on retransmit in dialog %s\n", pkt->owner->callid);
+ else
+ return reschedule;
+ }
+ /* Too many retries */
+ if (pkt->owner && pkt->method != SIP_OPTIONS && xmitres == 0) {
+ if (pkt->is_fatal || sipdebug) /* Tell us if it's critical or if we're debugging */
+ ast_log(LOG_WARNING, "Maximum retries exceeded on transmission %s for seqno %d (%s %s)\n",
+ pkt->owner->callid, pkt->seqno,
+ pkt->is_fatal ? "Critical" : "Non-critical", pkt->is_resp ? "Response" : "Request");
+ } else if (pkt->method == SIP_OPTIONS && sipdebug) {
+ ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) \n", pkt->owner->callid);
+
+ }
+ if (xmitres == XMIT_ERROR) {
+ ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission on Call ID %s\n", pkt->owner->callid);
+ append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+ } else
+ append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+
+ pkt->retransid = -1;
+
+ if (pkt->is_fatal) {
+ while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
+ sip_pvt_unlock(pkt->owner); /* SIP_PVT, not channel */
+ usleep(1);
+ sip_pvt_lock(pkt->owner);
+ }
+
+ if (pkt->owner->owner && !pkt->owner->owner->hangupcause)
+ pkt->owner->owner->hangupcause = AST_CAUSE_NO_USER_RESPONSE;
+
+ if (pkt->owner->owner) {
+ sip_alreadygone(pkt->owner);
+ ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet.\n", pkt->owner->callid);
+ ast_queue_hangup(pkt->owner->owner);
+ ast_channel_unlock(pkt->owner->owner);
+ } else {
+ /* If no channel owner, destroy now */
+
+ /* Let the peerpoke system expire packets when the timer expires for poke_noanswer */
+ if (pkt->method != SIP_OPTIONS && pkt->method != SIP_REGISTER) {
+ pkt->owner->needdestroy = 1;
+ sip_alreadygone(pkt->owner);
+ append_history(pkt->owner, "DialogKill", "Killing this failed dialog immediately");
+ }
+ }
+ }
+
+ if (pkt->method == SIP_BYE) {
+ /* We're not getting answers on SIP BYE's. Tear down the call anyway. */
+ if (pkt->owner->owner)
+ ast_channel_unlock(pkt->owner->owner);
+ append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway.");
+ pkt->owner->needdestroy = 1;
+ }
+
+ /* Remove the packet */
+ for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
+ if (cur == pkt) {
+ UNLINK(cur, pkt->owner->packets, prev);
+ sip_pvt_unlock(pkt->owner);
+ ast_free(pkt);
+ return 0;
+ }
+ }
+ /* error case */
+ ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
+ sip_pvt_unlock(pkt->owner);
+ return 0;
+}
+
+/*! \brief Transmit packet with retransmits
+ \return 0 on success, -1 on failure to allocate packet
+*/
+static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, char *data, int len, int fatal, int sipmethod)
+{
+ struct sip_pkt *pkt = NULL;
+ int siptimer_a = DEFAULT_RETRANS;
+ int xmitres = 0;
+
+ /* If the transport is something reliable (TCP or TLS) then don't really send this reliably */
+ /* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */
+ /* According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */
+ if (!(p->socket.type & SIP_TRANSPORT_UDP)) {
+ xmitres = __sip_xmit(dialog_ref(p), data, len); /* Send packet */
+ if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
+ append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)");
+ return AST_FAILURE;
+ } else
+ return AST_SUCCESS;
+ }
+
+ if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
+ return AST_FAILURE;
+ /* copy data, add a terminator and save length */
+ memcpy(pkt->data, data, len);
+ pkt->data[len] = '\0';
+ pkt->packetlen = len;
+ /* copy other parameters from the caller */
+ pkt->method = sipmethod;
+ pkt->seqno = seqno;
+ pkt->is_resp = resp;
+ pkt->is_fatal = fatal;
+ pkt->owner = dialog_ref(p);
+ pkt->next = p->packets;
+ p->packets = pkt;
+ pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
+ if (pkt->timer_t1)
+ siptimer_a = pkt->timer_t1 * 2;
+
+ /* Schedule retransmission */
+ pkt->retransid = ast_sched_replace_variable(pkt->retransid, sched,
+ siptimer_a, retrans_pkt, pkt, 1);
+ if (sipdebug)
+ ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", pkt->retransid);
+ if (sipmethod == SIP_INVITE) {
+ /* Note this is a pending invite */
+ p->pendinginvite = seqno;
+ }
+
+ xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen); /* Send packet */
+
+ if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
+ append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+ ast_sched_del(sched, pkt->retransid); /* No more retransmission */
+ pkt->retransid = -1;
+ return AST_FAILURE;
+ } else
+ return AST_SUCCESS;
+}
+
+/*! \brief Kill a SIP dialog (called only by the scheduler)
+ * The scheduler has a reference to this dialog when p->autokillid != -1,
+ * and we are called using that reference. So if the event is not
+ * rescheduled, we need to call dialog_unref().
+ */
+static int __sip_autodestruct(const void *data)
+{
+ struct sip_pvt *p = (struct sip_pvt *)data;
+
+ /* If this is a subscription, tell the phone that we got a timeout */
+ if (p->subscribed) {
+ transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE); /* Send last notification */
+ p->subscribed = NONE;
+ append_history(p, "Subscribestatus", "timeout");
+ ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
+ return 10000; /* Reschedule this destruction so that we know that it's gone */
+ }
+
+ /* If there are packets still waiting for delivery, delay the destruction */
+ if (p->packets) {
+ ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
+ append_history(p, "ReliableXmit", "timeout");
+ return 10000;
+ }
+
+ if (p->subscribed == MWI_NOTIFICATION)
+ if (p->relatedpeer)
+ unref_peer(p->relatedpeer); /* Remove link to peer. If it's realtime, make sure it's gone from memory) */
+
+ /* Reset schedule ID */
+ p->autokillid = -1;
+
+ if (p->owner) {
+ ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner in place (Method: %s)\n", p->callid, sip_methods[p->method].text);
+ ast_queue_hangup(p->owner);
+ dialog_unref(p);
+ } else if (p->refer) {
+ ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid);
+ transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
+ append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ dialog_unref(p);
+ } else {
+ append_history(p, "AutoDestroy", "%s", p->callid);
+ ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid);
+ sip_destroy(p); /* Go ahead and destroy dialog. All attempts to recover is done */
+ /* sip_destroy also absorbs the reference */
+ }
+ return 0;
+}
+
+/*! \brief Schedule destruction of SIP dialog */
+static void sip_scheddestroy(struct sip_pvt *p, int ms)
+{
+ if (ms < 0) {
+ if (p->timer_t1 == 0) {
+ p->timer_t1 = global_t1; /* Set timer T1 if not set (RFC 3261) */
+ p->timer_b = global_timer_b; /* Set timer B if not set (RFC 3261) */
+ }
+ ms = p->timer_t1 * 64;
+ }
+ if (sip_debug_test_pvt(p))
+ ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text);
+ sip_cancel_destroy(p);
+ if (p->do_history)
+ append_history(p, "SchedDestroy", "%d ms", ms);
+ p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p));
+
+ if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0)
+ stop_session_timer(p);
+}
+
+/*! \brief Cancel destruction of SIP dialog.
+ * Be careful as this also absorbs the reference - if you call it
+ * from within the scheduler, this might be the last reference.
+ */
+static void sip_cancel_destroy(struct sip_pvt *p)
+{
+ if (p->autokillid > -1) {
+ ast_sched_del(sched, p->autokillid);
+ append_history(p, "CancelDestroy", "");
+ p->autokillid = -1;
+ dialog_unref(p);
+ }
+}
+
+/*! \brief Acknowledges receipt of a packet and stops retransmission */
+static void __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+{
+ struct sip_pkt *cur, *prev = NULL;
+ const char *msg = "Not Found"; /* used only for debugging */
+
+ sip_pvt_lock(p);
+
+ /* If we have an outbound proxy for this dialog, then delete it now since
+ the rest of the requests in this dialog needs to follow the routing.
+ If obforcing is set, we will keep the outbound proxy during the whole
+ dialog, regardless of what the SIP rfc says
+ */
+ if (p->outboundproxy && !p->outboundproxy->force)
+ p->outboundproxy = NULL;
+
+ for (cur = p->packets; cur; prev = cur, cur = cur->next) {
+ if (cur->seqno != seqno || cur->is_resp != resp)
+ continue;
+ if (cur->is_resp || cur->method == sipmethod) {
+ msg = "Found";
+ if (!resp && (seqno == p->pendinginvite)) {
+ ast_debug(1, "Acked pending invite %d\n", p->pendinginvite);
+ p->pendinginvite = 0;
+ }
+ if (cur->retransid > -1) {
+ if (sipdebug)
+ ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
+ ast_sched_del(sched, cur->retransid);
+ cur->retransid = -1;
+ }
+ UNLINK(cur, p->packets, prev);
+ dialog_unref(cur->owner);
+ ast_free(cur);
+ break;
+ }
+ }
+ sip_pvt_unlock(p);
+ ast_debug(1, "Stopping retransmission on '%s' of %s %d: Match %s\n",
+ p->callid, resp ? "Response" : "Request", seqno, msg);
+}
+
+/*! \brief Pretend to ack all packets
+ * maybe the lock on p is not strictly necessary but there might be a race */
+static void __sip_pretend_ack(struct sip_pvt *p)
+{
+ struct sip_pkt *cur = NULL;
+
+ while (p->packets) {
+ int method;
+ if (cur == p->packets) {
+ ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
+ return;
+ }
+ cur = p->packets;
+ method = (cur->method) ? cur->method : find_sip_method(cur->data);
+ __sip_ack(p, cur->seqno, cur->is_resp, method);
+ }
+}
+
+/*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
+static int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+{
+ struct sip_pkt *cur;
+ int res = -1;
+
+ for (cur = p->packets; cur; cur = cur->next) {
+ if (cur->seqno == seqno && cur->is_resp == resp &&
+ (cur->is_resp || method_match(sipmethod, cur->data))) {
+ /* this is our baby */
+ if (cur->retransid > -1) {
+ if (sipdebug)
+ ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
+ ast_sched_del(sched, cur->retransid);
+ cur->retransid = -1;
+ }
+ res = 0;
+ break;
+ }
+ }
+ ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res ? "Not Found" : "Found");
+ return res;
+}
+
+
+/*! \brief Copy SIP request, parse it */
+static void parse_copy(struct sip_request *dst, const struct sip_request *src)
+{
+ memset(dst, 0, sizeof(*dst));
+ memcpy(dst->data, src->data, sizeof(dst->data));
+ dst->len = src->len;
+ parse_request(dst);
+}
+
+/*! \brief add a blank line if no body */
+static void add_blank(struct sip_request *req)
+{
+ if (!req->lines) {
+ /* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */
+ ast_copy_string(req->data + req->len, "\r\n", sizeof(req->data) - req->len);
+ req->len += strlen(req->data + req->len);
+ }
+}
+
+/*! \brief Transmit response on SIP request*/
+static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
+{
+ int res;
+
+ add_blank(req);
+ if (sip_debug_test_pvt(p)) {
+ const struct sockaddr_in *dst = sip_real_dst(p);
+
+ ast_verbose("\n<--- %sTransmitting (%s) to %s:%d --->\n%s\n<------------>\n",
+ reliable ? "Reliably " : "", sip_nat_mode(p),
+ ast_inet_ntoa(dst->sin_addr),
+ ntohs(dst->sin_port), req->data);
+ }
+ if (p->do_history) {
+ struct sip_request tmp;
+ parse_copy(&tmp, req);
+ append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", tmp.data, get_header(&tmp, "CSeq"),
+ (tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? tmp.rlPart2 : sip_methods[tmp.method].text);
+ }
+ res = (reliable) ?
+ __sip_reliable_xmit(p, seqno, 1, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
+ __sip_xmit(p, req->data, req->len);
+ if (res > 0)
+ return 0;
+ return res;
+}
+
+/*! \brief Send SIP Request to the other part of the dialogue */
+static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
+{
+ int res;
+
+ /* If we have an outbound proxy, reset peer address
+ Only do this once.
+ */
+ if (p->outboundproxy) {
+ p->sa = p->outboundproxy->ip;
+ }
+
+ add_blank(req);
+ if (sip_debug_test_pvt(p)) {
+ if (ast_test_flag(&p->flags[0], SIP_NAT_ROUTE))
+ ast_verbose("%sTransmitting (NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port), req->data);
+ else
+ ast_verbose("%sTransmitting (no NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->sa.sin_addr), ntohs(p->sa.sin_port), req->data);
+ }
+ if (p->do_history) {
+ struct sip_request tmp;
+ parse_copy(&tmp, req);
+ append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", tmp.data, get_header(&tmp, "CSeq"), sip_methods[tmp.method].text);
+ }
+ res = (reliable) ?
+ __sip_reliable_xmit(p, seqno, 0, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
+ __sip_xmit(p, req->data, req->len);
+ return res;
+}
+
+/*! \brief Locate closing quote in a string, skipping escaped quotes.
+ * optionally with a limit on the search.
+ * start must be past the first quote.
+ */
+static const char *find_closing_quote(const char *start, const char *lim)
+{
+ char last_char = '\0';
+ const char *s;
+ for (s = start; *s && s != lim; last_char = *s++) {
+ if (*s == '"' && last_char != '\\')
+ break;
+ }
+ return s;
+}
+
+/*! \brief Pick out text in brackets from character string
+ \return pointer to terminated stripped string
+ \param tmp input string that will be modified
+ Examples:
+\verbatim
+ "foo" <bar> valid input, returns bar
+ foo returns the whole string
+ < "foo ... > returns the string between brackets
+ < "foo... bogus (missing closing bracket), returns the whole string
+ XXX maybe should still skip the opening bracket
+\endverbatim
+ */
+static char *get_in_brackets(char *tmp)
+{
+ const char *parse = tmp;
+ char *first_bracket;
+
+ /*
+ * Skip any quoted text until we find the part in brackets.
+ * On any error give up and return the full string.
+ */
+ while ( (first_bracket = strchr(parse, '<')) ) {
+ char *first_quote = strchr(parse, '"');
+
+ if (!first_quote || first_quote > first_bracket)
+ break; /* no need to look at quoted part */
+ /* the bracket is within quotes, so ignore it */
+ parse = find_closing_quote(first_quote + 1, NULL);
+ if (!*parse) { /* not found, return full string ? */
+ /* XXX or be robust and return in-bracket part ? */
+ ast_log(LOG_WARNING, "No closing quote found in '%s'\n", tmp);
+ break;
+ }
+ parse++;
+ }
+ if (first_bracket) {
+ char *second_bracket = strchr(first_bracket + 1, '>');
+ if (second_bracket) {
+ *second_bracket = '\0';
+ tmp = first_bracket + 1;
+ } else {
+ ast_log(LOG_WARNING, "No closing bracket found in '%s'\n", tmp);
+ }
+ }
+
+ return tmp;
+}
+
+/*! \brief * parses a URI in its components.
+ *
+ * \note
+ * - If scheme is specified, drop it from the top.
+ * - If a component is not requested, do not split around it.
+ *
+ * This means that if we don't have domain, we cannot split
+ * name:pass and domain:port.
+ * It is safe to call with ret_name, pass, domain, port
+ * pointing all to the same place.
+ * Init pointers to empty string so we never get NULL dereferencing.
+ * Overwrites the string.
+ * return 0 on success, other values on error.
+ * \verbatim
+ * general form we are expecting is sip[s]:username[:password][;parameter]@host[:port][;...]
+ * \endverbatim
+ */
+static int parse_uri(char *uri, char *scheme,
+ char **ret_name, char **pass, char **domain, char **port, char **options)
+{
+ char *name = NULL;
+ int error = 0;
+
+ /* init field as required */
+ if (pass)
+ *pass = "";
+ if (port)
+ *port = "";
+ if (scheme) {
+ int l = strlen(scheme);
+ if (!strncasecmp(uri, scheme, l))
+ uri += l;
+ else {
+ ast_debug(1, "Missing scheme '%s' in '%s'\n", scheme, uri);
+ error = -1;
+ }
+ }
+ if (!domain) {
+ /* if we don't want to split around domain, keep everything as a name,
+ * so we need to do nothing here, except remember why.
+ */
+ } else {
+ /* store the result in a temp. variable to avoid it being
+ * overwritten if arguments point to the same place.
+ */
+ char *c, *dom = "";
+
+ if ((c = strchr(uri, '@')) == NULL) {
+ /* domain-only URI, according to the SIP RFC. */
+ dom = uri;
+ name = "";
+ } else {
+ *c++ = '\0';
+ dom = c;
+ name = uri;
+ }
+
+ /* Remove options in domain and name */
+ dom = strsep(&dom, ";");
+ name = strsep(&name, ";");
+
+ if (port && (c = strchr(dom, ':'))) { /* Remove :port */
+ *c++ = '\0';
+ *port = c;
+ }
+ if (pass && (c = strchr(name, ':'))) { /* user:password */
+ *c++ = '\0';
+ *pass = c;
+ }
+ *domain = dom;
+ }
+ if (ret_name) /* same as for domain, store the result only at the end */
+ *ret_name = name;
+ if (options)
+ *options = uri ? uri : "";
+
+ return error;
+}
+
+/*! \brief Send message with Access-URL header, if this is an HTML URL only! */
+static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen)
+{
+ struct sip_pvt *p = chan->tech_pvt;
+
+ if (subclass != AST_HTML_URL)
+ return -1;
+
+ ast_string_field_build(p, url, "<%s>;mode=active", data);
+
+ if (sip_debug_test_pvt(p))
+ ast_debug(1, "Send URL %s, state = %d!\n", data, chan->_state);
+
+ switch (chan->_state) {
+ case AST_STATE_RING:
+ transmit_response(p, "100 Trying", &p->initreq);
+ break;
+ case AST_STATE_RINGING:
+ transmit_response(p, "180 Ringing", &p->initreq);
+ break;
+ case AST_STATE_UP:
+ if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
+ transmit_reinvite_with_sdp(p, FALSE, FALSE);
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to send URI when state is %d!\n", chan->_state);
+ }
+
+ return 0;
+}
+
+/*! \brief Send SIP MESSAGE text within a call
+ Called from PBX core sendtext() application */
+static int sip_sendtext(struct ast_channel *ast, const char *text)
+{
+ struct sip_pvt *p = ast->tech_pvt;
+ int debug = sip_debug_test_pvt(p);
+
+ if (debug)
+ ast_verbose("Sending text %s on %s\n", text, ast->name);
+ if (!p)
+ return -1;
+ if (ast_strlen_zero(text))
+ return 0;
+ if (debug)
+ ast_verbose("Really sending text %s on %s\n", text, ast->name);
+ transmit_message_with_text(p, text);
+ return 0;
+}
+
+/*! \brief Update peer object in realtime storage
+ If the Asterisk system name is set in asterisk.conf, we will use
+ that name and store that in the "regserver" field in the sippeers
+ table to facilitate multi-server setups.
+*/
+static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, const char *defaultuser, const char *fullcontact, int expirey)
+{
+ char port[10];
+ char ipaddr[INET_ADDRSTRLEN];
+ char regseconds[20];
+ char *tablename = NULL;
+
+ const char *sysname = ast_config_AST_SYSTEM_NAME;
+ char *syslabel = NULL;
+
+ time_t nowtime = time(NULL) + expirey;
+ const char *fc = fullcontact ? "fullcontact" : NULL;
+
+ int realtimeregs = ast_check_realtime("sipregs");
+
+ tablename = realtimeregs ? "sipregs" : "sippeers";
+
+ snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime); /* Expiration time */
+ ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
+ snprintf(port, sizeof(port), "%d", ntohs(sin->sin_port));
+
+ if (ast_strlen_zero(sysname)) /* No system name, disable this */
+ sysname = NULL;
+ else if (sip_cfg.rtsave_sysname)
+ syslabel = "regserver";
+
+ if (fc)
+ ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+ "port", port, "regseconds", regseconds,
+ "defaultuser", defaultuser, fc, fullcontact, syslabel, sysname, NULL); /* note fc and syslabel _can_ be NULL */
+ else
+ ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+ "port", port, "regseconds", regseconds,
+ "defaultuser", defaultuser, syslabel, sysname, NULL); /* note syslabel _can_ be NULL */
+}
+
+/*! \brief Automatically add peer extension to dial plan */
+static void register_peer_exten(struct sip_peer *peer, int onoff)
+{
+ char multi[256];
+ char *stringp, *ext, *context;
+
+ /* XXX note that global_regcontext is both a global 'enable' flag and
+ * the name of the global regexten context, if not specified
+ * individually.
+ */
+ if (ast_strlen_zero(global_regcontext))
+ return;
+
+ ast_copy_string(multi, S_OR(peer->regexten, peer->name), sizeof(multi));
+ stringp = multi;
+ while ((ext = strsep(&stringp, "&"))) {
+ if ((context = strchr(ext, '@'))) {
+ *context++ = '\0'; /* split ext@context */
+ if (!ast_context_find(context)) {
+ ast_log(LOG_WARNING, "Context %s must exist in regcontext= in sip.conf!\n", context);
+ continue;
+ }
+ } else {
+ context = global_regcontext;
+ }
+ if (onoff)
+ ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop",
+ ast_strdup(peer->name), ast_free_ptr, "SIP");
+ else
+ ast_context_remove_extension(context, ext, 1, NULL);
+ }
+}
+
+/*! Destroy mailbox subscriptions */
+static void destroy_mailbox(struct sip_mailbox *mailbox)
+{
+ if (mailbox->mailbox)
+ ast_free(mailbox->mailbox);
+ if (mailbox->context)
+ ast_free(mailbox->context);
+ if (mailbox->event_sub)
+ ast_event_unsubscribe(mailbox->event_sub);
+ ast_free(mailbox);
+}
+
+/*! Destroy all peer-related mailbox subscriptions */
+static void clear_peer_mailboxes(struct sip_peer *peer)
+{
+ struct sip_mailbox *mailbox;
+
+ while ((mailbox = AST_LIST_REMOVE_HEAD(&peer->mailboxes, entry)))
+ destroy_mailbox(mailbox);
+}
+
+/*! \brief Destroy peer object from memory */
+static void sip_destroy_peer(struct sip_peer *peer)
+{
+ ast_debug(3, "Destroying SIP peer %s\n", peer->name);
+
+ if (peer->outboundproxy)
+ ast_free(peer->outboundproxy);
+ peer->outboundproxy = NULL;
+
+ /* Delete it, it needs to disappear */
+ if (peer->call)
+ peer->call = sip_destroy(peer->call);
+
+ if (peer->mwipvt) /* We have an active subscription, delete it */
+ peer->mwipvt = sip_destroy(peer->mwipvt);
+
+ if (peer->chanvars) {
+ ast_variables_destroy(peer->chanvars);
+ peer->chanvars = NULL;
+ }
+
+ /* If the schedule delete fails, that means the schedule is currently
+ * running, which means we should wait for that thread to complete.
+ * Otherwise, there's a crashable race condition.
+ *
+ * NOTE: once peer is refcounted, this probably is no longer necessary.
+ */
+ while (peer->expire > -1 && ast_sched_del(sched, peer->expire))
+ usleep(1);
+ while (peer->pokeexpire > -1 && ast_sched_del(sched, peer->pokeexpire))
+ usleep(1);
+
+ register_peer_exten(peer, FALSE);
+ ast_free_ha(peer->ha);
+ if (peer->selfdestruct)
+ apeerobjs--;
+ else if (peer->is_realtime) {
+ rpeerobjs--;
+ ast_debug(3,"-REALTIME- peer Destroyed. Name: %s. Realtime Peer objects: %d\n", peer->name, rpeerobjs);
+ } else
+ speerobjs--;
+ clear_realm_authentication(peer->auth);
+ peer->auth = NULL;
+ if (peer->dnsmgr)
+ ast_dnsmgr_release(peer->dnsmgr);
+ clear_peer_mailboxes(peer);
+ ast_free(peer);
+}
+
+/*! \brief Update peer data in database (if used) */
+static void update_peer(struct sip_peer *p, int expiry)
+{
+ int rtcachefriends = ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS);
+ if (sip_cfg.peer_rtupdate &&
+ (p->is_realtime || rtcachefriends)) {
+ realtime_update_peer(p->name, &p->addr, p->username, rtcachefriends ? p->fullcontact : NULL, expiry);
+ }
+}
+
+static struct ast_variable *get_insecure_variable_from_config(struct ast_config *config)
+{
+ struct ast_variable *var = NULL;
+ struct ast_flags flags = {0};
+ char *cat = NULL;
+ const char *insecure;
+ while ((cat = ast_category_browse(config, cat))) {
+ insecure = ast_variable_retrieve(config, cat, "insecure");
+ set_insecure_flags(&flags, insecure, -1);
+ if (ast_test_flag(&flags, SIP_INSECURE_PORT)) {
+ var = ast_category_root(config, cat);
+ break;
+ }
+ }
+ return var;
+}
+
+static const char *get_name_from_variable(struct ast_variable *var, const char *newpeername)
+{
+ struct ast_variable *tmp;
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!newpeername && !strcasecmp(tmp->name, "name"))
+ newpeername = tmp->value;
+ }
+ return newpeername;
+}
+
+/*! \brief realtime_peer: Get peer from realtime storage
+ * Checks the "sippeers" realtime family from extconfig.conf
+ * Checks the "sipregs" realtime family from extconfig.conf if it's configured.
+*/
+static struct sip_peer *realtime_peer(const char *newpeername, struct sockaddr_in *sin)
+{
+ struct sip_peer *peer;
+ struct ast_variable *var = NULL;
+ struct ast_variable *varregs = NULL;
+ struct ast_variable *tmp;
+ struct ast_config *peerlist = NULL;
+ char ipaddr[INET_ADDRSTRLEN];
+ char portstring[6]; /*up to 5 digits plus null terminator*/
+ char *cat = NULL;
+ unsigned short portnum;
+ int realtimeregs = ast_check_realtime("sipregs");
+
+ /* First check on peer name */
+ if (newpeername) {
+ if (realtimeregs)
+ varregs = ast_load_realtime("sipregs", "name", newpeername, NULL);
+
+ var = ast_load_realtime("sippeers", "name", newpeername, "host", "dynamic", NULL);
+ if (!var && sin)
+ var = ast_load_realtime("sippeers", "name", newpeername, "host", ast_inet_ntoa(sin->sin_addr), NULL);
+ if (!var) {
+ var = ast_load_realtime("sippeers", "name", newpeername, NULL);
+ /*!\note
+ * If this one loaded something, then we need to ensure that the host
+ * field matched. The only reason why we can't have this as a criteria
+ * is because we only have the IP address and the host field might be
+ * set as a name (and the reverse PTR might not match).
+ */
+ if (var) {
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(var->name, "host")) {
+ struct in_addr sin2 = { 0, };
+ struct ast_dnsmgr_entry *dnsmgr = NULL;
+ if ((ast_dnsmgr_lookup(tmp->value, &sin2, &dnsmgr) < 0) || (memcmp(&sin2, &sin->sin_addr, sizeof(sin2)) != 0)) {
+ /* No match */
+ ast_variables_destroy(var);
+ var = NULL;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!var && sin) { /* Then check on IP address for dynamic peers */
+ ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
+ portnum = ntohs(sin->sin_port);
+ sprintf(portstring, "%u", portnum);
+ var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, NULL); /* First check for fixed IP hosts */
+ if (var) {
+ if (realtimeregs) {
+ newpeername = get_name_from_variable(var, newpeername);
+ varregs = ast_load_realtime("sipregs", "name", newpeername, NULL);
+ }
+ } else {
+ if (realtimeregs)
+ varregs = ast_load_realtime("sipregs", "ipaddr", ipaddr, "port", portstring, NULL); /* Then check for registered hosts */
+ else
+ var = ast_load_realtime("sippeers", "ipaddr", ipaddr, "port", portstring, NULL); /* Then check for registered hosts */
+ if (varregs) {
+ newpeername = get_name_from_variable(varregs, newpeername);
+ var = ast_load_realtime("sippeers", "name", newpeername, NULL);
+ }
+ }
+ if (!var) { /*We couldn't match on ipaddress and port, so we need to check if port is insecure*/
+ peerlist = ast_load_realtime_multientry("sippeers", "host", ipaddr, NULL);
+ if (peerlist) {
+ var = get_insecure_variable_from_config(peerlist);
+ if(var) {
+ if (realtimeregs) {
+ newpeername = get_name_from_variable(var, newpeername);
+ varregs = ast_load_realtime("sipregs", "name", newpeername, NULL);
+ }
+ } else { /*var wasn't found in the list of "hosts", so try "ipaddr"*/
+ peerlist = NULL;
+ cat = NULL;
+ peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, NULL);
+ if(peerlist) {
+ var = get_insecure_variable_from_config(peerlist);
+ if(var) {
+ if (realtimeregs) {
+ newpeername = get_name_from_variable(var, newpeername);
+ varregs = ast_load_realtime("sipregs", "name", newpeername, NULL);
+ }
+ }
+ }
+ }
+ } else {
+ if (realtimeregs) {
+ peerlist = ast_load_realtime_multientry("sipregs", "ipaddr", ipaddr, NULL);
+ if (peerlist) {
+ varregs = get_insecure_variable_from_config(peerlist);
+ if (varregs) {
+ newpeername = get_name_from_variable(varregs, newpeername);
+ var = ast_load_realtime("sippeers", "name", newpeername, NULL);
+ }
+ }
+ } else {
+ peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, NULL);
+ if (peerlist) {
+ var = get_insecure_variable_from_config(peerlist);
+ if (var) {
+ newpeername = get_name_from_variable(var, newpeername);
+ varregs = ast_load_realtime("sipregs", "name", newpeername, NULL);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!var) {
+ if (peerlist)
+ ast_config_destroy(peerlist);
+ return NULL;
+ }
+
+ for (tmp = var; tmp; tmp = tmp->next) {
+ /* If this is type=user, then skip this object. */
+ if (!strcasecmp(tmp->name, "type") &&
+ !strcasecmp(tmp->value, "user")) {
+ if(peerlist)
+ ast_config_destroy(peerlist);
+ else {
+ ast_variables_destroy(var);
+ ast_variables_destroy(varregs);
+ }
+ return NULL;
+ } else if (!newpeername && !strcasecmp(tmp->name, "name")) {
+ newpeername = tmp->value;
+ }
+ }
+
+ if (!newpeername) { /* Did not find peer in realtime */
+ ast_log(LOG_WARNING, "Cannot Determine peer name ip=%s\n", ipaddr);
+ if(peerlist)
+ ast_config_destroy(peerlist);
+ else
+ ast_variables_destroy(var);
+ return NULL;
+ }
+
+
+ /* Peer found in realtime, now build it in memory */
+ peer = build_peer(newpeername, var, varregs, !ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS));
+ if (!peer) {
+ if(peerlist)
+ ast_config_destroy(peerlist);
+ else {
+ ast_variables_destroy(var);
+ ast_variables_destroy(varregs);
+ }
+ return NULL;
+ }
+
+ ast_debug(3,"-REALTIME- loading peer from database to memory. Name: %s. Peer objects: %d\n", peer->name, rpeerobjs);
+
+ if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) {
+ /* Cache peer */
+ ast_copy_flags(&peer->flags[1],&global_flags[1], SIP_PAGE2_RTAUTOCLEAR|SIP_PAGE2_RTCACHEFRIENDS);
+ if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTAUTOCLEAR)) {
+ peer->expire = ast_sched_replace(peer->expire, sched,
+ global_rtautoclear * 1000, expire_register, (void *) peer);
+ }
+ ASTOBJ_CONTAINER_LINK(&peerl,peer);
+ } else {
+ peer->is_realtime = 1;
+ }
+ if (peerlist)
+ ast_config_destroy(peerlist);
+ else {
+ ast_variables_destroy(var);
+ ast_variables_destroy(varregs);
+ }
+
+ return peer;
+}
+
+/*! \brief Support routine for find_peer */
+static int sip_addrcmp(char *name, struct sockaddr_in *sin)
+{
+ /* We know name is the first field, so we can cast */
+ struct sip_peer *p = (struct sip_peer *) name;
+ return !(!inaddrcmp(&p->addr, sin) ||
+ (ast_test_flag(&p->flags[0], SIP_INSECURE_PORT) &&
+ (p->addr.sin_addr.s_addr == sin->sin_addr.s_addr)));
+}
+
+/*! \brief Locate peer by name or ip address
+ * This is used on incoming SIP message to find matching peer on ip
+ or outgoing message to find matching peer on name
+ \note Avoid using this function in new functions if there's a way to avoid it, i
+ since it causes a database lookup or a traversal of the in-memory peer list.
+*/
+static struct sip_peer *find_peer(const char *peer, struct sockaddr_in *sin, int realtime)
+{
+ struct sip_peer *p = NULL;
+
+ if (peer)
+ p = ASTOBJ_CONTAINER_FIND(&peerl, peer);
+ else
+ p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp);
+
+ if (!p && realtime)
+ p = realtime_peer(peer, sin);
+
+ return p;
+}
+
+/*! \brief Remove user object from in-memory storage */
+static void sip_destroy_user(struct sip_user *user)
+{
+ ast_debug(3, "Destroying user object from memory: %s\n", user->name);
+
+ ast_free_ha(user->ha);
+ if (user->chanvars) {
+ ast_variables_destroy(user->chanvars);
+ user->chanvars = NULL;
+ }
+ if (user->is_realtime)
+ ruserobjs--;
+ else
+ suserobjs--;
+ ast_free(user);
+}
+
+/*! \brief Load user from realtime storage
+ * Loads user from "sipusers" category in realtime (extconfig.conf)
+ * Users are matched on From: user name (the domain in skipped) */
+static struct sip_user *realtime_user(const char *username)
+{
+ struct ast_variable *var;
+ struct ast_variable *tmp;
+ struct sip_user *user = NULL;
+
+ var = ast_load_realtime("sipusers", "name", username, NULL);
+
+ if (!var)
+ return NULL;
+
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "type") &&
+ !strcasecmp(tmp->value, "peer")) {
+ ast_variables_destroy(var);
+ return NULL;
+ }
+ }
+
+ user = build_user(username, var, !ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS));
+
+ if (!user) { /* No user found */
+ ast_variables_destroy(var);
+ return NULL;
+ }
+
+ if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) {
+ ast_set_flag(&user->flags[1], SIP_PAGE2_RTCACHEFRIENDS);
+ suserobjs++;
+ ASTOBJ_CONTAINER_LINK(&userl,user);
+ } else {
+ /* Move counter from s to r... */
+ suserobjs--;
+ ruserobjs++;
+ user->is_realtime = 1;
+ }
+ ast_variables_destroy(var);
+ return user;
+}
+
+/*! \brief Locate user by name
+ * Locates user by name (From: sip uri user name part) first
+ * from in-memory list (static configuration) then from
+ * realtime storage (defined in extconfig.conf) */
+static struct sip_user *find_user(const char *name, int realtime)
+{
+ struct sip_user *u = ASTOBJ_CONTAINER_FIND(&userl, name);
+ if (!u && realtime)
+ u = realtime_user(name);
+ return u;
+}
+
+/*! \brief Set nat mode on the various data sockets */
+static void do_setnat(struct sip_pvt *p, int natflags)
+{
+ const char *mode = natflags ? "On" : "Off";
+
+ if (p->rtp) {
+ ast_debug(1, "Setting NAT on RTP to %s\n", mode);
+ ast_rtp_setnat(p->rtp, natflags);
+ }
+ if (p->vrtp) {
+ ast_debug(1, "Setting NAT on VRTP to %s\n", mode);
+ ast_rtp_setnat(p->vrtp, natflags);
+ }
+ if (p->udptl) {
+ ast_debug(1, "Setting NAT on UDPTL to %s\n", mode);
+ ast_udptl_setnat(p->udptl, natflags);
+ }
+ if (p->trtp) {
+ ast_debug(1, "Setting NAT on TRTP to %s\n", mode);
+ ast_rtp_setnat(p->trtp, natflags);
+ }
+}
+
+/*! \brief Set the global T38 capabilities on a SIP dialog structure */
+static void set_t38_capabilities(struct sip_pvt *p)
+{
+ p->t38.capability = global_t38_capability;
+ if (p->udptl) {
+ if (ast_udptl_get_error_correction_scheme(p->udptl) == UDPTL_ERROR_CORRECTION_FEC )
+ p->t38.capability |= T38FAX_UDP_EC_FEC;
+ else if (ast_udptl_get_error_correction_scheme(p->udptl) == UDPTL_ERROR_CORRECTION_REDUNDANCY )
+ p->t38.capability |= T38FAX_UDP_EC_REDUNDANCY;
+ else if (ast_udptl_get_error_correction_scheme(p->udptl) == UDPTL_ERROR_CORRECTION_NONE )
+ p->t38.capability |= T38FAX_UDP_EC_NONE;
+ p->t38.capability |= T38FAX_RATE_MANAGEMENT_TRANSFERED_TCF;
+ }
+}
+
+/*! \brief Create address structure from peer reference.
+ * This function copies data from peer to the dialog, so we don't have to look up the peer
+ * again from memory or database during the life time of the dialog.
+ *
+ * \return -1 on error, 0 on success.
+ */
+static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
+{
+ dialog->socket = peer->socket;
+
+ if ((peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr) &&
+ (!peer->maxms || ((peer->lastms >= 0) && (peer->lastms <= peer->maxms)))) {
+ dialog->sa = (peer->addr.sin_addr.s_addr) ? peer->addr : peer->defaddr;
+ dialog->recv = dialog->sa;
+ } else
+ return -1;
+
+ ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
+ ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ dialog->capability = peer->capability;
+ if ((!ast_test_flag(&dialog->flags[1], SIP_PAGE2_VIDEOSUPPORT) || !(dialog->capability & AST_FORMAT_VIDEO_MASK)) && dialog->vrtp) {
+ ast_rtp_destroy(dialog->vrtp);
+ dialog->vrtp = NULL;
+ }
+ if (!ast_test_flag(&dialog->flags[1], SIP_PAGE2_TEXTSUPPORT) && dialog->trtp) {
+ ast_rtp_destroy(dialog->trtp);
+ dialog->trtp = NULL;
+ }
+ dialog->prefs = peer->prefs;
+ if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_T38SUPPORT)) {
+ ast_copy_flags(&dialog->t38.t38support, &peer->flags[1], SIP_PAGE2_T38SUPPORT);
+ set_t38_capabilities(dialog);
+ dialog->t38.jointcapability = dialog->t38.capability;
+ } else if (dialog->udptl) {
+ ast_udptl_destroy(dialog->udptl);
+ dialog->udptl = NULL;
+ }
+ do_setnat(dialog, ast_test_flag(&dialog->flags[0], SIP_NAT) & SIP_NAT_ROUTE);
+
+ if (dialog->rtp) { /* Audio */
+ ast_rtp_setdtmf(dialog->rtp, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
+ ast_rtp_setdtmfcompensate(dialog->rtp, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
+ ast_rtp_set_rtptimeout(dialog->rtp, peer->rtptimeout);
+ ast_rtp_set_rtpholdtimeout(dialog->rtp, peer->rtpholdtimeout);
+ ast_rtp_set_rtpkeepalive(dialog->rtp, peer->rtpkeepalive);
+ /* Set Frame packetization */
+ ast_rtp_codec_setpref(dialog->rtp, &dialog->prefs);
+ dialog->autoframing = peer->autoframing;
+ }
+ if (dialog->vrtp) { /* Video */
+ ast_rtp_setdtmf(dialog->vrtp, 0);
+ ast_rtp_setdtmfcompensate(dialog->vrtp, 0);
+ ast_rtp_set_rtptimeout(dialog->vrtp, peer->rtptimeout);
+ ast_rtp_set_rtpholdtimeout(dialog->vrtp, peer->rtpholdtimeout);
+ ast_rtp_set_rtpkeepalive(dialog->vrtp, peer->rtpkeepalive);
+ }
+ if (dialog->trtp) { /* Realtime text */
+ ast_rtp_setdtmf(dialog->trtp, 0);
+ ast_rtp_setdtmfcompensate(dialog->trtp, 0);
+ ast_rtp_set_rtptimeout(dialog->trtp, peer->rtptimeout);
+ ast_rtp_set_rtpholdtimeout(dialog->trtp, peer->rtpholdtimeout);
+ ast_rtp_set_rtpkeepalive(dialog->trtp, peer->rtpkeepalive);
+ }
+
+ ast_string_field_set(dialog, peername, peer->name);
+ ast_string_field_set(dialog, authname, peer->username);
+ ast_string_field_set(dialog, username, peer->username);
+ ast_string_field_set(dialog, peersecret, peer->secret);
+ ast_string_field_set(dialog, peermd5secret, peer->md5secret);
+ ast_string_field_set(dialog, mohsuggest, peer->mohsuggest);
+ ast_string_field_set(dialog, mohinterpret, peer->mohinterpret);
+ ast_string_field_set(dialog, tohost, peer->tohost);
+ ast_string_field_set(dialog, fullcontact, peer->fullcontact);
+ ast_string_field_set(dialog, context, peer->context);
+ dialog->outboundproxy = obproxy_get(dialog, peer);
+ dialog->callgroup = peer->callgroup;
+ dialog->pickupgroup = peer->pickupgroup;
+ dialog->allowtransfer = peer->allowtransfer;
+ dialog->jointnoncodeccapability = dialog->noncodeccapability;
+ dialog->rtptimeout = peer->rtptimeout;
+ dialog->maxcallbitrate = peer->maxcallbitrate;
+ if (ast_strlen_zero(dialog->tohost))
+ ast_string_field_set(dialog, tohost, ast_inet_ntoa(dialog->sa.sin_addr));
+ if (!ast_strlen_zero(peer->fromdomain)) {
+ ast_string_field_set(dialog, fromdomain, peer->fromdomain);
+ if (!dialog->initreq.headers) {
+ char *c;
+ char *tmpcall = ast_strdupa(dialog->callid);
+
+ c = strchr(tmpcall, '@');
+ if (c) {
+ *c = '\0';
+ ast_string_field_build(dialog, callid, "%s@%s", tmpcall, peer->fromdomain);
+ }
+ }
+ }
+ if (!ast_strlen_zero(peer->fromuser))
+ ast_string_field_set(dialog, fromuser, peer->fromuser);
+ if (!ast_strlen_zero(peer->language))
+ ast_string_field_set(dialog, language, peer->language);
+
+ /* Set timer T1 to RTT for this peer (if known by qualify=) */
+ /* Minimum is settable or default to 100 ms */
+ /* If there is a maxms and lastms from a qualify use that over a manual T1
+ value. Otherwise, use the peer's T1 value. */
+ if (peer->maxms && peer->lastms)
+ dialog->timer_t1 = peer->lastms < global_t1min ? global_t1min : peer->lastms;
+ else
+ dialog->timer_t1 = peer->timer_t1;
+
+ /* Set timer B to control transaction timeouts, the peer setting is the default and overrides
+ the known timer */
+ if (peer->timer_b)
+ dialog->timer_b = peer->timer_b;
+ else
+ dialog->timer_b = 64 * dialog->timer_t1;
+
+ if ((ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) ||
+ (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO))
+ dialog->noncodeccapability |= AST_RTP_DTMF;
+ else
+ dialog->noncodeccapability &= ~AST_RTP_DTMF;
+ if (peer->call_limit)
+ ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT);
+
+ return 0;
+}
+
+/*! \brief create address structure from peer name
+ * Or, if peer not found, find it in the global DNS
+ * returns TRUE (-1) on failure, FALSE on success */
+static int create_addr(struct sip_pvt *dialog, const char *opeer)
+{
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ struct sip_peer *peer;
+ char *port;
+ int portno;
+ char host[MAXHOSTNAMELEN], *hostn;
+ char peername[256];
+
+ ast_copy_string(peername, opeer, sizeof(peername));
+ port = strchr(peername, ':');
+ if (port)
+ *port++ = '\0';
+ dialog->sa.sin_family = AF_INET;
+ dialog->timer_t1 = global_t1; /* Default SIP retransmission timer T1 (RFC 3261) */
+ dialog->timer_b = global_timer_b; /* Default SIP transaction timer B (RFC 3261) */
+ peer = find_peer(peername, NULL, 1);
+
+ if (peer) {
+ int res = create_addr_from_peer(dialog, peer);
+ unref_peer(peer);
+ return res;
+ }
+
+ ast_string_field_set(dialog, tohost, peername);
+
+ /* Get the outbound proxy information */
+ dialog->outboundproxy = obproxy_get(dialog, NULL);
+
+ /* If we have an outbound proxy, don't bother with DNS resolution at all */
+ if (dialog->outboundproxy)
+ return 0;
+
+ /* Let's see if we can find the host in DNS. First try DNS SRV records,
+ then hostname lookup */
+
+ hostn = peername;
+ portno = port ? atoi(port) : (dialog->socket.type & SIP_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT;
+ if (global_srvlookup) {
+ char service[MAXHOSTNAMELEN];
+ int tportno;
+ int ret;
+
+ snprintf(service, sizeof(service), "_sip._%s.%s", get_transport(dialog->socket.type), peername);
+ ret = ast_get_srv(NULL, host, sizeof(host), &tportno, service);
+ if (ret > 0) {
+ hostn = host;
+ portno = tportno;
+ }
+ }
+ hp = ast_gethostbyname(hostn, &ahp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "No such host: %s\n", peername);
+ return -1;
+ }
+ memcpy(&dialog->sa.sin_addr, hp->h_addr, sizeof(dialog->sa.sin_addr));
+ dialog->sa.sin_port = htons(portno);
+ dialog->recv = dialog->sa;
+ return 0;
+}
+
+/*! \brief Scheduled congestion on a call.
+ * Only called by the scheduler, must return the reference when done.
+ */
+static int auto_congest(const void *arg)
+{
+ struct sip_pvt *p = (struct sip_pvt *)arg;
+
+ sip_pvt_lock(p);
+ p->initid = -1; /* event gone, will not be rescheduled */
+ if (p->owner) {
+ /* XXX fails on possible deadlock */
+ if (!ast_channel_trylock(p->owner)) {
+ ast_log(LOG_NOTICE, "Auto-congesting %s\n", p->owner->name);
+ append_history(p, "Cong", "Auto-congesting (timer)");
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ ast_channel_unlock(p->owner);
+ }
+ }
+ sip_pvt_unlock(p);
+ dialog_unref(p);
+ return 0;
+}
+
+
+/*! \brief Initiate SIP call from PBX
+ * used from the dial() application */
+static int sip_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ int res;
+ struct sip_pvt *p = ast->tech_pvt; /* chan is locked, so the reference cannot go away */
+ struct varshead *headp;
+ struct ast_var_t *current;
+ const char *referer = NULL; /* SIP referrer */
+
+ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "sip_call called on %s, neither down nor reserved\n", ast->name);
+ return -1;
+ }
+
+ /* Check whether there is vxml_url, distinctive ring variables */
+ headp=&ast->varshead;
+ AST_LIST_TRAVERSE(headp,current,entries) {
+ /* Check whether there is a VXML_URL variable */
+ if (!p->options->vxml_url && !strcasecmp(ast_var_name(current), "VXML_URL")) {
+ p->options->vxml_url = ast_var_value(current);
+ } else if (!p->options->uri_options && !strcasecmp(ast_var_name(current), "SIP_URI_OPTIONS")) {
+ p->options->uri_options = ast_var_value(current);
+ } else if (!p->options->addsipheaders && !strncasecmp(ast_var_name(current), "SIPADDHEADER", strlen("SIPADDHEADER"))) {
+ /* Check whether there is a variable with a name starting with SIPADDHEADER */
+ p->options->addsipheaders = 1;
+ } else if (!strcasecmp(ast_var_name(current), "SIPTRANSFER")) {
+ /* This is a transfered call */
+ p->options->transfer = 1;
+ } else if (!strcasecmp(ast_var_name(current), "SIPTRANSFER_REFERER")) {
+ /* This is the referrer */
+ referer = ast_var_value(current);
+ } else if (!strcasecmp(ast_var_name(current), "SIPTRANSFER_REPLACES")) {
+ /* We're replacing a call. */
+ p->options->replaces = ast_var_value(current);
+ } else if (!strcasecmp(ast_var_name(current), "T38CALL")) {
+ p->t38.state = T38_LOCAL_DIRECT;
+ ast_debug(1,"T38State change to %d on channel %s\n", p->t38.state, ast->name);
+ }
+
+ }
+
+ res = 0;
+ ast_set_flag(&p->flags[0], SIP_OUTGOING);
+
+ if (p->options->transfer) {
+ char buf[BUFSIZ/2];
+
+ if (referer) {
+ if (sipdebug)
+ ast_debug(3, "Call for %s transfered by %s\n", p->username, referer);
+ snprintf(buf, sizeof(buf)-1, "-> %s (via %s)", p->cid_name, referer);
+ } else
+ snprintf(buf, sizeof(buf)-1, "-> %s", p->cid_name);
+ ast_string_field_set(p, cid_name, buf);
+ }
+ ast_debug(1, "Outgoing Call for %s\n", p->username);
+
+ res = update_call_counter(p, INC_CALL_RINGING);
+
+ if (res == -1)
+ return res;
+
+ p->callingpres = ast->cid.cid_pres;
+ p->jointcapability = ast_translate_available_formats(p->capability, p->prefcodec);
+ p->jointnoncodeccapability = p->noncodeccapability;
+
+ /* If there are no audio formats left to offer, punt */
+ if (!(p->jointcapability & AST_FORMAT_AUDIO_MASK)) {
+ ast_log(LOG_WARNING, "No audio format found to offer. Cancelling call to %s\n", p->username);
+ res = -1;
+ } else {
+ int xmitres;
+
+ p->t38.jointcapability = p->t38.capability;
+ ast_debug(2,"Our T38 capability (%d), joint T38 capability (%d)\n", p->t38.capability, p->t38.jointcapability);
+
+ xmitres = transmit_invite(p, SIP_INVITE, 1, 2);
+ if (xmitres == XMIT_ERROR)
+ return -1;
+ p->invitestate = INV_CALLING;
+
+ /* Initialize auto-congest time */
+ p->initid = ast_sched_replace(p->initid, sched, p->timer_b,
+ auto_congest, dialog_ref(p));
+ }
+
+ return res;
+}
+
+/*! \brief Destroy registry object
+ Objects created with the register= statement in static configuration */
+static void sip_registry_destroy(struct sip_registry *reg)
+{
+ /* Really delete */
+ ast_debug(3, "Destroying registry entry for %s@%s\n", reg->username, reg->hostname);
+
+ if (reg->call) {
+ /* Clear registry before destroying to ensure
+ we don't get reentered trying to grab the registry lock */
+ reg->call->registry = NULL;
+ ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", reg->username, reg->hostname);
+ reg->call = sip_destroy(reg->call);
+ }
+ if (reg->expire > -1)
+ ast_sched_del(sched, reg->expire);
+ if (reg->timeout > -1)
+ ast_sched_del(sched, reg->timeout);
+ ast_string_field_free_memory(reg);
+ regobjs--;
+ ast_free(reg);
+
+}
+
+/*! \brief Execute destruction of SIP dialog structure, release memory */
+static void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
+{
+ struct sip_pvt *cur, *prev = NULL;
+ struct sip_pkt *cp;
+
+ if (sip_debug_test_pvt(p))
+ ast_verbose("Really destroying SIP dialog '%s' Method: %s\n", p->callid, sip_methods[p->method].text);
+
+ if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) {
+ update_call_counter(p, DEC_CALL_LIMIT);
+ ast_debug(2, "This call did not properly clean up call limits. Call ID %s\n", p->callid);
+ }
+
+ /* Remove link from peer to subscription of MWI */
+ if (p->relatedpeer && p->relatedpeer->mwipvt)
+ p->relatedpeer->mwipvt = dialog_unref(p->relatedpeer->mwipvt);
+
+ if (dumphistory)
+ sip_dump_history(p);
+
+ if (p->options)
+ ast_free(p->options);
+
+ if (p->stateid > -1)
+ ast_extension_state_del(p->stateid, NULL);
+ if (p->initid > -1)
+ ast_sched_del(sched, p->initid);
+ if (p->waitid > -1)
+ ast_sched_del(sched, p->waitid);
+ if (p->autokillid > -1)
+ ast_sched_del(sched, p->autokillid);
+
+ if (p->rtp)
+ ast_rtp_destroy(p->rtp);
+ if (p->vrtp)
+ ast_rtp_destroy(p->vrtp);
+ if (p->trtp)
+ ast_rtp_destroy(p->trtp);
+ if (p->udptl)
+ ast_udptl_destroy(p->udptl);
+ if (p->refer)
+ ast_free(p->refer);
+ if (p->route) {
+ free_old_route(p->route);
+ p->route = NULL;
+ }
+ if (p->registry) {
+ if (p->registry->call == p)
+ p->registry->call = NULL;
+ p->registry = registry_unref(p->registry);
+ }
+
+ /* Destroy Session-Timers if allocated */
+ if (p->stimer) {
+ if (p->stimer->st_active == TRUE && p->stimer->st_schedid > -1)
+ ast_sched_del(sched, p->stimer->st_schedid);
+ ast_free(p->stimer);
+ }
+
+ /* Unlink us from the owner if we have one */
+ if (p->owner) {
+ if (lockowner)
+ ast_channel_lock(p->owner);
+ ast_debug(1, "Detaching from %s\n", p->owner->name);
+ p->owner->tech_pvt = NULL;
+ if (lockowner)
+ ast_channel_unlock(p->owner);
+ }
+ /* Clear history */
+ if (p->history) {
+ struct sip_history *hist;
+ while ( (hist = AST_LIST_REMOVE_HEAD(p->history, list)) ) {
+ ast_free(hist);
+ p->history_entries--;
+ }
+ ast_free(p->history);
+ p->history = NULL;
+ }
+
+ /* Lock dialog list before removing ourselves from the list */
+ if (lockdialoglist)
+ dialoglist_lock();
+ for (prev = NULL, cur = dialoglist; cur; prev = cur, cur = cur->next) {
+ if (cur == p) {
+ UNLINK(cur, dialoglist, prev);
+ break;
+ }
+ }
+ if (lockdialoglist)
+ dialoglist_unlock();
+ if (!cur) {
+ ast_log(LOG_WARNING, "Trying to destroy \"%s\", not found in dialog list?!?! \n", p->callid);
+ return;
+ }
+
+ /* remove all current packets in this dialog */
+ while((cp = p->packets)) {
+ p->packets = p->packets->next;
+ if (cp->retransid > -1)
+ ast_sched_del(sched, cp->retransid);
+ dialog_unref(cp->owner);
+ ast_free(cp);
+ }
+ if (p->chanvars) {
+ ast_variables_destroy(p->chanvars);
+ p->chanvars = NULL;
+ }
+ ast_mutex_destroy(&p->pvt_lock);
+
+ ast_string_field_free_memory(p);
+
+ ast_free(p);
+}
+
+/*! \brief update_call_counter: Handle call_limit for SIP users
+ * Setting a call-limit will cause calls above the limit not to be accepted.
+ *
+ * Remember that for a type=friend, there's one limit for the user and
+ * another for the peer, not a combined call limit.
+ * This will cause unexpected behaviour in subscriptions, since a "friend"
+ * is *two* devices in Asterisk, not one.
+ *
+ * Thought: For realtime, we should probably update storage with inuse counter...
+ *
+ * \return 0 if call is ok (no call limit, below threshold)
+ * -1 on rejection of call
+ *
+ */
+static int update_call_counter(struct sip_pvt *fup, int event)
+{
+ char name[256];
+ int *inuse = NULL, *call_limit = NULL, *inringing = NULL;
+ int outgoing = fup->outgoing_call;
+ struct sip_user *u = NULL;
+ struct sip_peer *p = NULL;
+
+ ast_debug(3, "Updating call counter for %s call\n", outgoing ? "outgoing" : "incoming");
+
+
+ /* Test if we need to check call limits, in order to avoid
+ realtime lookups if we do not need it */
+ if (!ast_test_flag(&fup->flags[0], SIP_CALL_LIMIT) && !ast_test_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD))
+ return 0;
+
+ ast_copy_string(name, fup->username, sizeof(name));
+
+ /* Check the list of users only for incoming calls */
+ if (global_limitonpeers == FALSE && !outgoing && (u = find_user(name, 1))) {
+ inuse = &u->inUse;
+ call_limit = &u->call_limit;
+ inringing = NULL;
+ } else if ( (p = find_peer(ast_strlen_zero(fup->peername) ? name : fup->peername, NULL, 1) ) ) { /* Try to find peer */
+ inuse = &p->inUse;
+ call_limit = &p->call_limit;
+ inringing = &p->inRinging;
+ ast_copy_string(name, fup->peername, sizeof(name));
+ }
+ if (!p && !u) {
+ ast_debug(2, "%s is not a local device, no call limit\n", name);
+ return 0;
+ }
+
+ switch(event) {
+ /* incoming and outgoing affects the inUse counter */
+ case DEC_CALL_LIMIT:
+ /* Decrement inuse count if applicable */
+ if (inuse && ast_test_flag(&fup->flags[0], SIP_INC_COUNT)) {
+ ast_atomic_fetchadd_int(inuse, -1);
+ ast_clear_flag(&fup->flags[0], SIP_INC_COUNT);
+ } else
+ *inuse = 0;
+ /* Decrement ringing count if applicable */
+ if (inringing && ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) {
+ ast_atomic_fetchadd_int(inringing, -1);
+ ast_clear_flag(&fup->flags[0], SIP_INC_RINGING);
+ }
+ /* Decrement onhold count if applicable */
+ if (ast_test_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD) && global_notifyhold) {
+ ast_clear_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD);
+ sip_peer_hold(fup, FALSE);
+ }
+ if (sipdebug)
+ ast_debug(2, "Call %s %s '%s' removed from call limit %d\n", outgoing ? "to" : "from", u ? "user":"peer", name, *call_limit);
+ break;
+
+ case INC_CALL_RINGING:
+ case INC_CALL_LIMIT:
+ /* If call limit is active and we have reached the limit, reject the call */
+ if (*call_limit > 0 ) {
+ if (*inuse >= *call_limit) {
+ ast_log(LOG_ERROR, "Call %s %s '%s' rejected due to usage limit of %d\n", outgoing ? "to" : "from", u ? "user":"peer", name, *call_limit);
+ if (u)
+ unref_user(u);
+ else
+ unref_peer(p);
+ return -1;
+ }
+ }
+ if (inringing && (event == INC_CALL_RINGING)) {
+ if (!ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) {
+ ast_atomic_fetchadd_int(inringing, +1);
+ ast_set_flag(&fup->flags[0], SIP_INC_RINGING);
+ }
+ }
+ /* Continue */
+ ast_atomic_fetchadd_int(inuse, +1);
+ ast_set_flag(&fup->flags[0], SIP_INC_COUNT);
+ if (sipdebug) {
+ ast_debug(2, "Call %s %s '%s' is %d out of %d\n", outgoing ? "to" : "from", u ? "user":"peer", name, *inuse, *call_limit);
+ }
+ break;
+
+ case DEC_CALL_RINGING:
+ if (inringing && ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) {
+ ast_atomic_fetchadd_int(inringing, -1);
+ ast_clear_flag(&fup->flags[0], SIP_INC_RINGING);
+ }
+ break;
+
+ default:
+ ast_log(LOG_ERROR, "update_call_counter(%s, %d) called with no event!\n", name, event);
+ }
+ if (p) {
+ ast_device_state_changed("SIP/%s", p->name);
+ unref_peer(p);
+ } else /* u must be set */
+ unref_user(u);
+ return 0;
+}
+
+/*! \brief Destroy SIP call structure.
+ * Make it return NULL so the caller can do things like
+ * foo = sip_destroy(foo);
+ * and reduce the chance of bugs due to dangling pointers.
+ */
+static struct sip_pvt * sip_destroy(struct sip_pvt *p)
+{
+ ast_debug(3, "Destroying SIP dialog %s\n", p->callid);
+ __sip_destroy(p, TRUE, TRUE);
+ return NULL;
+}
+
+/*! \brief Convert SIP hangup causes to Asterisk hangup causes */
+static int hangup_sip2cause(int cause)
+{
+ /* Possible values taken from causes.h */
+
+ switch(cause) {
+ case 401: /* Unauthorized */
+ return AST_CAUSE_CALL_REJECTED;
+ case 403: /* Not found */
+ return AST_CAUSE_CALL_REJECTED;
+ case 404: /* Not found */
+ return AST_CAUSE_UNALLOCATED;
+ case 405: /* Method not allowed */
+ return AST_CAUSE_INTERWORKING;
+ case 407: /* Proxy authentication required */
+ return AST_CAUSE_CALL_REJECTED;
+ case 408: /* No reaction */
+ return AST_CAUSE_NO_USER_RESPONSE;
+ case 409: /* Conflict */
+ return AST_CAUSE_NORMAL_TEMPORARY_FAILURE;
+ case 410: /* Gone */
+ return AST_CAUSE_UNALLOCATED;
+ case 411: /* Length required */
+ return AST_CAUSE_INTERWORKING;
+ case 413: /* Request entity too large */
+ return AST_CAUSE_INTERWORKING;
+ case 414: /* Request URI too large */
+ return AST_CAUSE_INTERWORKING;
+ case 415: /* Unsupported media type */
+ return AST_CAUSE_INTERWORKING;
+ case 420: /* Bad extension */
+ return AST_CAUSE_NO_ROUTE_DESTINATION;
+ case 480: /* No answer */
+ return AST_CAUSE_NO_ANSWER;
+ case 481: /* No answer */
+ return AST_CAUSE_INTERWORKING;
+ case 482: /* Loop detected */
+ return AST_CAUSE_INTERWORKING;
+ case 483: /* Too many hops */
+ return AST_CAUSE_NO_ANSWER;
+ case 484: /* Address incomplete */
+ return AST_CAUSE_INVALID_NUMBER_FORMAT;
+ case 485: /* Ambiguous */
+ return AST_CAUSE_UNALLOCATED;
+ case 486: /* Busy everywhere */
+ return AST_CAUSE_BUSY;
+ case 487: /* Request terminated */
+ return AST_CAUSE_INTERWORKING;
+ case 488: /* No codecs approved */
+ return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
+ case 491: /* Request pending */
+ return AST_CAUSE_INTERWORKING;
+ case 493: /* Undecipherable */
+ return AST_CAUSE_INTERWORKING;
+ case 500: /* Server internal failure */
+ return AST_CAUSE_FAILURE;
+ case 501: /* Call rejected */
+ return AST_CAUSE_FACILITY_REJECTED;
+ case 502:
+ return AST_CAUSE_DESTINATION_OUT_OF_ORDER;
+ case 503: /* Service unavailable */
+ return AST_CAUSE_CONGESTION;
+ case 504: /* Gateway timeout */
+ return AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE;
+ case 505: /* SIP version not supported */
+ return AST_CAUSE_INTERWORKING;
+ case 600: /* Busy everywhere */
+ return AST_CAUSE_USER_BUSY;
+ case 603: /* Decline */
+ return AST_CAUSE_CALL_REJECTED;
+ case 604: /* Does not exist anywhere */
+ return AST_CAUSE_UNALLOCATED;
+ case 606: /* Not acceptable */
+ return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
+ default:
+ return AST_CAUSE_NORMAL;
+ }
+ /* Never reached */
+ return 0;
+}
+
+/*! \brief Convert Asterisk hangup causes to SIP codes
+\verbatim
+ Possible values from causes.h
+ AST_CAUSE_NOTDEFINED AST_CAUSE_NORMAL AST_CAUSE_BUSY
+ AST_CAUSE_FAILURE AST_CAUSE_CONGESTION AST_CAUSE_UNALLOCATED
+
+ In addition to these, a lot of PRI codes is defined in causes.h
+ ...should we take care of them too ?
+
+ Quote RFC 3398
+
+ ISUP Cause value SIP response
+ ---------------- ------------
+ 1 unallocated number 404 Not Found
+ 2 no route to network 404 Not found
+ 3 no route to destination 404 Not found
+ 16 normal call clearing --- (*)
+ 17 user busy 486 Busy here
+ 18 no user responding 408 Request Timeout
+ 19 no answer from the user 480 Temporarily unavailable
+ 20 subscriber absent 480 Temporarily unavailable
+ 21 call rejected 403 Forbidden (+)
+ 22 number changed (w/o diagnostic) 410 Gone
+ 22 number changed (w/ diagnostic) 301 Moved Permanently
+ 23 redirection to new destination 410 Gone
+ 26 non-selected user clearing 404 Not Found (=)
+ 27 destination out of order 502 Bad Gateway
+ 28 address incomplete 484 Address incomplete
+ 29 facility rejected 501 Not implemented
+ 31 normal unspecified 480 Temporarily unavailable
+\endverbatim
+*/
+static const char *hangup_cause2sip(int cause)
+{
+ switch (cause) {
+ case AST_CAUSE_UNALLOCATED: /* 1 */
+ case AST_CAUSE_NO_ROUTE_DESTINATION: /* 3 IAX2: Can't find extension in context */
+ case AST_CAUSE_NO_ROUTE_TRANSIT_NET: /* 2 */
+ return "404 Not Found";
+ case AST_CAUSE_CONGESTION: /* 34 */
+ case AST_CAUSE_SWITCH_CONGESTION: /* 42 */
+ return "503 Service Unavailable";
+ case AST_CAUSE_NO_USER_RESPONSE: /* 18 */
+ return "408 Request Timeout";
+ case AST_CAUSE_NO_ANSWER: /* 19 */
+ return "480 Temporarily unavailable";
+ case AST_CAUSE_CALL_REJECTED: /* 21 */
+ return "403 Forbidden";
+ case AST_CAUSE_NUMBER_CHANGED: /* 22 */
+ return "410 Gone";
+ case AST_CAUSE_NORMAL_UNSPECIFIED: /* 31 */
+ return "480 Temporarily unavailable";
+ case AST_CAUSE_INVALID_NUMBER_FORMAT:
+ return "484 Address incomplete";
+ case AST_CAUSE_USER_BUSY:
+ return "486 Busy here";
+ case AST_CAUSE_FAILURE:
+ return "500 Server internal failure";
+ case AST_CAUSE_FACILITY_REJECTED: /* 29 */
+ return "501 Not Implemented";
+ case AST_CAUSE_CHAN_NOT_IMPLEMENTED:
+ return "503 Service Unavailable";
+ /* Used in chan_iax2 */
+ case AST_CAUSE_DESTINATION_OUT_OF_ORDER:
+ return "502 Bad Gateway";
+ case AST_CAUSE_BEARERCAPABILITY_NOTAVAIL: /* Can't find codec to connect to host */
+ return "488 Not Acceptable Here";
+
+ case AST_CAUSE_NOTDEFINED:
+ default:
+ ast_debug(1, "AST hangup cause %d (no match found in SIP)\n", cause);
+ return NULL;
+ }
+
+ /* Never reached */
+ return 0;
+}
+
+
+/*! \brief sip_hangup: Hangup SIP call
+ * Part of PBX interface, called from ast_hangup */
+static int sip_hangup(struct ast_channel *ast)
+{
+ struct sip_pvt *p = ast->tech_pvt;
+ int needcancel = FALSE;
+ int needdestroy = 0;
+ struct ast_channel *oldowner = ast;
+
+ if (!p) {
+ ast_debug(1, "Asked to hangup channel that was not connected\n");
+ return 0;
+ }
+ if (ast_test_flag(ast, AST_FLAG_ANSWERED_ELSEWHERE)) {
+ ast_debug(1, "This call was answered elsewhere");
+ append_history(p, "Cancel", "Call answered elsewhere");
+ p->answered_elsewhere = TRUE;
+ }
+
+ if (ast_test_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER)) {
+ if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) {
+ if (sipdebug)
+ ast_debug(1, "update_call_counter(%s) - decrement call limit counter on hangup\n", p->username);
+ update_call_counter(p, DEC_CALL_LIMIT);
+ }
+ ast_debug(4, "SIP Transfer: Not hanging up right now... Rescheduling hangup for %s.\n", p->callid);
+ if (p->autokillid > -1)
+ sip_cancel_destroy(p);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Really hang up next time */
+ p->needdestroy = 0;
+ p->owner->tech_pvt = dialog_unref(p->owner->tech_pvt);
+ p->owner = NULL; /* Owner will be gone after we return, so take it away */
+ return 0;
+ }
+
+ if (ast_test_flag(ast, AST_FLAG_ZOMBIE)) {
+ if (p->refer)
+ ast_debug(1, "SIP Transfer: Hanging up Zombie channel %s after transfer ... Call-ID: %s\n", ast->name, p->callid);
+ else
+ ast_debug(1, "Hanging up zombie call. Be scared.\n");
+ } else
+ ast_debug(1, "Hangup call %s, SIP callid %s\n", ast->name, p->callid);
+
+ sip_pvt_lock(p);
+ if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) {
+ if (sipdebug)
+ ast_debug(1, "update_call_counter(%s) - decrement call limit counter on hangup\n", p->username);
+ update_call_counter(p, DEC_CALL_LIMIT);
+ }
+
+ /* Determine how to disconnect */
+ if (p->owner != ast) {
+ ast_log(LOG_WARNING, "Huh? We aren't the owner? Can't hangup call.\n");
+ sip_pvt_unlock(p);
+ return 0;
+ }
+ /* If the call is not UP, we need to send CANCEL instead of BYE */
+ /* In case of re-invites, the call might be UP even though we have an incomplete invite transaction */
+ if (p->invitestate < INV_COMPLETED && p->owner->_state != AST_STATE_UP) {
+ needcancel = TRUE;
+ ast_debug(4, "Hanging up channel in state %s (not UP)\n", ast_state2str(ast->_state));
+ }
+
+ stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
+
+ append_history(p, needcancel ? "Cancel" : "Hangup", "Cause %s", p->owner ? ast_cause2str(p->owner->hangupcause) : "Unknown");
+
+ /* Disconnect */
+ if (p->vad)
+ ast_dsp_free(p->vad);
+
+ p->owner = NULL;
+ ast->tech_pvt = dialog_unref(ast->tech_pvt);
+
+ ast_module_unref(ast_module_info->self);
+ /* Do not destroy this pvt until we have timeout or
+ get an answer to the BYE or INVITE/CANCEL
+ If we get no answer during retransmit period, drop the call anyway.
+ (Sorry, mother-in-law, you can't deny a hangup by sending
+ 603 declined to BYE...)
+ */
+ if (p->alreadygone)
+ needdestroy = 1; /* Set destroy flag at end of this function */
+ else if (p->invitestate != INV_CALLING)
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+
+ /* Start the process if it's not already started */
+ if (!p->alreadygone && !ast_strlen_zero(p->initreq.data)) {
+ if (needcancel) { /* Outgoing call, not up */
+ if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ /* stop retransmitting an INVITE that has not received a response */
+ __sip_pretend_ack(p);
+
+ /* if we can't send right now, mark it pending */
+ if (p->invitestate == INV_CALLING) {
+ /* We can't send anything in CALLING state */
+ ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
+ /* Do we need a timer here if we don't hear from them at all? */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ append_history(p, "DELAY", "Not sending cancel, waiting for timeout");
+ } else {
+ /* Send a new request: CANCEL */
+ transmit_request(p, SIP_CANCEL, p->ocseq, XMIT_RELIABLE, FALSE);
+ /* Actually don't destroy us yet, wait for the 487 on our original
+ INVITE, but do set an autodestruct just in case we never get it. */
+ needdestroy = 0;
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ p->invitestate = INV_CANCELLED;
+ }
+ if ( p->initid != -1 ) {
+ /* channel still up - reverse dec of inUse counter
+ only if the channel is not auto-congested */
+ update_call_counter(p, INC_CALL_LIMIT);
+ }
+ } else { /* Incoming call, not up */
+ const char *res;
+ if (ast->hangupcause && (res = hangup_cause2sip(ast->hangupcause)))
+ transmit_response_reliable(p, res, &p->initreq);
+ else
+ transmit_response_reliable(p, "603 Declined", &p->initreq);
+ p->invitestate = INV_TERMINATED;
+ }
+ } else { /* Call is in UP state, send BYE */
+ if (!p->pendinginvite) {
+ char *audioqos = "";
+ char *videoqos = "";
+ char *textqos = "";
+ if (p->rtp)
+ audioqos = ast_rtp_get_quality(p->rtp, NULL);
+ if (p->vrtp)
+ videoqos = ast_rtp_get_quality(p->vrtp, NULL);
+ if (p->trtp)
+ textqos = ast_rtp_get_quality(p->trtp, NULL);
+ /* Send a hangup */
+ transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
+
+ /* Get RTCP quality before end of call */
+ if (p->do_history) {
+ if (p->rtp)
+ append_history(p, "RTCPaudio", "Quality:%s", audioqos);
+ if (p->vrtp)
+ append_history(p, "RTCPvideo", "Quality:%s", videoqos);
+ if (p->trtp)
+ append_history(p, "RTCPtext", "Quality:%s", textqos);
+ }
+ if (p->rtp && oldowner)
+ pbx_builtin_setvar_helper(oldowner, "RTPAUDIOQOS", audioqos);
+ if (p->vrtp && oldowner)
+ pbx_builtin_setvar_helper(oldowner, "RTPVIDEOQOS", videoqos);
+ if (p->trtp && oldowner)
+ pbx_builtin_setvar_helper(oldowner, "RTPTEXTQOS", textqos);
+ } else {
+ /* Note we will need a BYE when this all settles out
+ but we can't send one while we have "INVITE" outstanding. */
+ ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
+ ast_clear_flag(&p->flags[0], SIP_NEEDREINVITE);
+ if (p->waitid)
+ ast_sched_del(sched, p->waitid);
+ p->waitid = -1;
+ sip_cancel_destroy(p);
+ }
+ }
+ }
+ if (needdestroy)
+ p->needdestroy = 1;
+ sip_pvt_unlock(p);
+ return 0;
+}
+
+/*! \brief Try setting codec suggested by the SIP_CODEC channel variable */
+static void try_suggested_sip_codec(struct sip_pvt *p)
+{
+ int fmt;
+ const char *codec;
+
+ codec = pbx_builtin_getvar_helper(p->owner, "SIP_CODEC");
+ if (!codec)
+ return;
+
+ fmt = ast_getformatbyname(codec);
+ if (fmt) {
+ ast_log(LOG_NOTICE, "Changing codec to '%s' for this call because of ${SIP_CODEC} variable\n", codec);
+ if (p->jointcapability & fmt) {
+ p->jointcapability &= fmt;
+ p->capability &= fmt;
+ } else
+ ast_log(LOG_NOTICE, "Ignoring ${SIP_CODEC} variable because it is not shared by both ends.\n");
+ } else
+ ast_log(LOG_NOTICE, "Ignoring ${SIP_CODEC} variable because of unrecognized/not configured codec (check allow/disallow in sip.conf): %s\n", codec);
+ return;
+}
+
+/*! \brief sip_answer: Answer SIP call , send 200 OK on Invite
+ * Part of PBX interface */
+static int sip_answer(struct ast_channel *ast)
+{
+ int res = 0;
+ struct sip_pvt *p = ast->tech_pvt;
+
+ sip_pvt_lock(p);
+ if (ast->_state != AST_STATE_UP) {
+ try_suggested_sip_codec(p);
+
+ ast_setstate(ast, AST_STATE_UP);
+ ast_debug(1, "SIP answering channel: %s\n", ast->name);
+ if (p->t38.state == T38_PEER_DIRECT) {
+ p->t38.state = T38_ENABLED;
+ ast_debug(2,"T38State change to %d on channel %s\n", p->t38.state, ast->name);
+ res = transmit_response_with_t38_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL);
+ } else
+ res = transmit_response_with_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL, FALSE);
+ }
+ sip_pvt_unlock(p);
+ return res;
+}
+
+/*! \brief Send frame to media channel (rtp) */
+static int sip_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct sip_pvt *p = ast->tech_pvt;
+ int res = 0;
+
+ switch (frame->frametype) {
+ case AST_FRAME_VOICE:
+ if (!(frame->subclass & ast->nativeformats)) {
+ char s1[512], s2[512], s3[512];
+ ast_log(LOG_WARNING, "Asked to transmit frame type %d, while native formats is %s(%d) read/write = %s(%d)/%s(%d)\n",
+ frame->subclass,
+ ast_getformatname_multiple(s1, sizeof(s1) - 1, ast->nativeformats & AST_FORMAT_AUDIO_MASK),
+ ast->nativeformats & AST_FORMAT_AUDIO_MASK,
+ ast_getformatname_multiple(s2, sizeof(s2) - 1, ast->readformat),
+ ast->readformat,
+ ast_getformatname_multiple(s3, sizeof(s3) - 1, ast->writeformat),
+ ast->writeformat);
+ return 0;
+ }
+ if (p) {
+ sip_pvt_lock(p);
+ if (p->rtp) {
+ /* If channel is not up, activate early media session */
+ if ((ast->_state != AST_STATE_UP) &&
+ !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
+ !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
+ ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
+ }
+ p->lastrtptx = time(NULL);
+ res = ast_rtp_write(p->rtp, frame);
+ }
+ sip_pvt_unlock(p);
+ }
+ break;
+ case AST_FRAME_VIDEO:
+ if (p) {
+ sip_pvt_lock(p);
+ if (p->vrtp) {
+ /* Activate video early media */
+ if ((ast->_state != AST_STATE_UP) &&
+ !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
+ !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
+ ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
+ }
+ p->lastrtptx = time(NULL);
+ res = ast_rtp_write(p->vrtp, frame);
+ }
+ sip_pvt_unlock(p);
+ }
+ break;
+ case AST_FRAME_TEXT:
+ if (p) {
+ sip_pvt_lock(p);
+ if (p->trtp) {
+ /* Activate text early media */
+ if ((ast->_state != AST_STATE_UP) &&
+ !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
+ !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
+ ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
+ }
+ p->lastrtptx = time(NULL);
+ res = ast_rtp_write(p->trtp, frame);
+ }
+ sip_pvt_unlock(p);
+ }
+ break;
+ case AST_FRAME_IMAGE:
+ return 0;
+ break;
+ case AST_FRAME_MODEM:
+ if (p) {
+ sip_pvt_lock(p);
+ /* UDPTL requires two-way communication, so early media is not needed here.
+ we simply forget the frames if we get modem frames before the bridge is up.
+ Fax will re-transmit.
+ */
+ if (p->udptl && ast->_state == AST_STATE_UP)
+ res = ast_udptl_write(p->udptl, frame);
+ sip_pvt_unlock(p);
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "Can't send %d type frames with SIP write\n", frame->frametype);
+ return 0;
+ }
+
+ return res;
+}
+
+/*! \brief sip_fixup: Fix up a channel: If a channel is consumed, this is called.
+ Basically update any ->owner links */
+static int sip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ int ret = -1;
+ struct sip_pvt *p;
+
+ if (newchan && ast_test_flag(newchan, AST_FLAG_ZOMBIE))
+ ast_debug(1, "New channel is zombie\n");
+ if (oldchan && ast_test_flag(oldchan, AST_FLAG_ZOMBIE))
+ ast_debug(1, "Old channel is zombie\n");
+
+ if (!newchan || !newchan->tech_pvt) {
+ if (!newchan)
+ ast_log(LOG_WARNING, "No new channel! Fixup of %s failed.\n", oldchan->name);
+ else
+ ast_log(LOG_WARNING, "No SIP tech_pvt! Fixup of %s failed.\n", oldchan->name);
+ return -1;
+ }
+ p = newchan->tech_pvt;
+
+ sip_pvt_lock(p);
+ append_history(p, "Masq", "Old channel: %s\n", oldchan->name);
+ append_history(p, "Masq (cont)", "...new owner: %s\n", newchan->name);
+ if (p->owner != oldchan)
+ ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, p->owner);
+ else {
+ p->owner = newchan;
+ ret = 0;
+ }
+ ast_debug(3, "SIP Fixup: New owner for dialogue %s: %s (Old parent: %s)\n", p->callid, p->owner->name, oldchan->name);
+
+ sip_pvt_unlock(p);
+ return ret;
+}
+
+static int sip_senddigit_begin(struct ast_channel *ast, char digit)
+{
+ struct sip_pvt *p = ast->tech_pvt;
+ int res = 0;
+
+ sip_pvt_lock(p);
+ switch (ast_test_flag(&p->flags[0], SIP_DTMF)) {
+ case SIP_DTMF_INBAND:
+ res = -1; /* Tell Asterisk to generate inband indications */
+ break;
+ case SIP_DTMF_RFC2833:
+ if (p->rtp)
+ ast_rtp_senddigit_begin(p->rtp, digit);
+ break;
+ default:
+ break;
+ }
+ sip_pvt_unlock(p);
+
+ return res;
+}
+
+/*! \brief Send DTMF character on SIP channel
+ within one call, we're able to transmit in many methods simultaneously */
+static int sip_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct sip_pvt *p = ast->tech_pvt;
+ int res = 0;
+
+ sip_pvt_lock(p);
+ switch (ast_test_flag(&p->flags[0], SIP_DTMF)) {
+ case SIP_DTMF_INFO:
+ case SIP_DTMF_SHORTINFO:
+ transmit_info_with_digit(p, digit, duration);
+ break;
+ case SIP_DTMF_RFC2833:
+ if (p->rtp)
+ ast_rtp_senddigit_end(p->rtp, digit);
+ break;
+ case SIP_DTMF_INBAND:
+ res = -1; /* Tell Asterisk to stop inband indications */
+ break;
+ }
+ sip_pvt_unlock(p);
+
+ return res;
+}
+
+/*! \brief Transfer SIP call */
+static int sip_transfer(struct ast_channel *ast, const char *dest)
+{
+ struct sip_pvt *p = ast->tech_pvt;
+ int res;
+
+ if (dest == NULL) /* functions below do not take a NULL */
+ dest = "";
+ sip_pvt_lock(p);
+ if (ast->_state == AST_STATE_RING)
+ res = sip_sipredirect(p, dest);
+ else
+ res = transmit_refer(p, dest);
+ sip_pvt_unlock(p);
+ return res;
+}
+
+/*! \brief Play indication to user
+ * With SIP a lot of indications is sent as messages, letting the device play
+ the indication - busy signal, congestion etc
+ \return -1 to force ast_indicate to send indication in audio, 0 if SIP can handle the indication by sending a message
+*/
+static int sip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+ struct sip_pvt *p = ast->tech_pvt;
+ int res = 0;
+
+ sip_pvt_lock(p);
+ switch(condition) {
+ case AST_CONTROL_RINGING:
+ if (ast->_state == AST_STATE_RING) {
+ p->invitestate = INV_EARLY_MEDIA;
+ if (!ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) ||
+ (ast_test_flag(&p->flags[0], SIP_PROG_INBAND) == SIP_PROG_INBAND_NEVER)) {
+ /* Send 180 ringing if out-of-band seems reasonable */
+ transmit_response(p, "180 Ringing", &p->initreq);
+ ast_set_flag(&p->flags[0], SIP_RINGING);
+ if (ast_test_flag(&p->flags[0], SIP_PROG_INBAND) != SIP_PROG_INBAND_YES)
+ break;
+ } else {
+ /* Well, if it's not reasonable, just send in-band */
+ }
+ }
+ res = -1;
+ break;
+ case AST_CONTROL_BUSY:
+ if (ast->_state != AST_STATE_UP) {
+ transmit_response(p, "486 Busy Here", &p->initreq);
+ p->invitestate = INV_COMPLETED;
+ sip_alreadygone(p);
+ ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV);
+ break;
+ }
+ res = -1;
+ break;
+ case AST_CONTROL_CONGESTION:
+ if (ast->_state != AST_STATE_UP) {
+ transmit_response(p, "503 Service Unavailable", &p->initreq);
+ p->invitestate = INV_COMPLETED;
+ sip_alreadygone(p);
+ ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV);
+ break;
+ }
+ res = -1;
+ break;
+ case AST_CONTROL_PROCEEDING:
+ if ((ast->_state != AST_STATE_UP) &&
+ !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
+ !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ transmit_response(p, "100 Trying", &p->initreq);
+ p->invitestate = INV_PROCEEDING;
+ break;
+ }
+ res = -1;
+ break;
+ case AST_CONTROL_PROGRESS:
+ if ((ast->_state != AST_STATE_UP) &&
+ !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
+ !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ p->invitestate = INV_EARLY_MEDIA;
+ transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
+ ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
+ break;
+ }
+ res = -1;
+ break;
+ case AST_CONTROL_HOLD:
+ ast_moh_start(ast, data, p->mohinterpret);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_moh_stop(ast);
+ break;
+ case AST_CONTROL_VIDUPDATE: /* Request a video frame update */
+ if (p->vrtp && !p->novideo) {
+ transmit_info_with_vidupdate(p);
+ /* ast_rtcp_send_h261fur(p->vrtp); */
+ } else
+ res = -1;
+ break;
+ case -1:
+ res = -1;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", condition);
+ res = -1;
+ break;
+ }
+ sip_pvt_unlock(p);
+ return res;
+}
+
+
+/*! \brief Initiate a call in the SIP channel
+ called from sip_request_call (calls from the pbx ) for outbound channels
+ and from handle_request_invite for inbound channels
+
+*/
+static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *title)
+{
+ struct ast_channel *tmp;
+ struct ast_variable *v = NULL;
+ int fmt;
+ int what;
+ int video;
+ int text;
+ int needvideo = 0;
+ int needtext = 0;
+ char buf[BUFSIZ];
+ char *decoded_exten;
+
+ {
+ const char *my_name; /* pick a good name */
+
+ if (title)
+ my_name = title;
+ else if ( (my_name = strchr(i->fromdomain,':')) )
+ my_name++; /* skip ':' */
+ else
+ my_name = i->fromdomain;
+
+ sip_pvt_unlock(i);
+ /* Don't hold a sip pvt lock while we allocate a channel */
+ tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, i->amaflags, "SIP/%s-%08x", my_name, (int)(long) i);
+
+ }
+ if (!tmp) {
+ ast_log(LOG_WARNING, "Unable to allocate AST channel structure for SIP channel\n");
+ return NULL;
+ }
+ sip_pvt_lock(i);
+
+ tmp->tech = ( ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_INFO || ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_SHORTINFO) ? &sip_tech_info : &sip_tech;
+
+ /* Select our native format based on codec preference until we receive
+ something from another device to the contrary. */
+ if (i->jointcapability) { /* The joint capabilities of us and peer */
+ what = i->jointcapability;
+ video = i->jointcapability & AST_FORMAT_VIDEO_MASK;
+ text = i->jointcapability & AST_FORMAT_TEXT_MASK;
+ } else if (i->capability) { /* Our configured capability for this peer */
+ what = i->capability;
+ video = i->capability & AST_FORMAT_VIDEO_MASK;
+ text = i->capability & AST_FORMAT_TEXT_MASK;
+ } else {
+ what = global_capability; /* Global codec support */
+ video = global_capability & AST_FORMAT_VIDEO_MASK;
+ text = global_capability & AST_FORMAT_TEXT_MASK;
+ }
+
+ /* Set the native formats for audio and merge in video */
+ tmp->nativeformats = ast_codec_choose(&i->prefs, what, 1) | video | text;
+ ast_debug(3, "*** Our native formats are %s \n", ast_getformatname_multiple(buf, BUFSIZ, tmp->nativeformats));
+ ast_debug(3, "*** Joint capabilities are %s \n", ast_getformatname_multiple(buf, BUFSIZ, i->jointcapability));
+ ast_debug(3, "*** Our capabilities are %s \n", ast_getformatname_multiple(buf, BUFSIZ, i->capability));
+ ast_debug(3, "*** AST_CODEC_CHOOSE formats are %s \n", ast_getformatname_multiple(buf, BUFSIZ, ast_codec_choose(&i->prefs, what, 1)));
+ if (i->prefcodec)
+ ast_debug(3, "*** Our preferred formats from the incoming channel are %s \n", ast_getformatname_multiple(buf, BUFSIZ, i->prefcodec));
+
+ /* XXX Why are we choosing a codec from the native formats?? */
+ fmt = ast_best_codec(tmp->nativeformats);
+
+ /* If we have a prefcodec setting, we have an inbound channel that set a
+ preferred format for this call. Otherwise, we check the jointcapability
+ We also check for vrtp. If it's not there, we are not allowed do any video anyway.
+ */
+ if (i->vrtp) {
+ if (i->prefcodec)
+ needvideo = i->prefcodec & AST_FORMAT_VIDEO_MASK; /* Outbound call */
+ else
+ needvideo = i->jointcapability & AST_FORMAT_VIDEO_MASK; /* Inbound call */
+ }
+
+ if (i->trtp) {
+ if (i->prefcodec)
+ needtext = i->prefcodec & AST_FORMAT_TEXT_MASK; /* Outbound call */
+ else
+ needtext = i->jointcapability & AST_FORMAT_TEXT_MASK; /* Inbound call */
+ }
+
+ if (needvideo)
+ ast_debug(3, "This channel can handle video! HOLLYWOOD next!\n");
+ else
+ ast_debug(3, "This channel will not be able to handle video.\n");
+
+
+
+ if (ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) {
+ i->vad = ast_dsp_new();
+ ast_dsp_set_features(i->vad, DSP_FEATURE_DTMF_DETECT);
+ if (global_relaxdtmf)
+ ast_dsp_digitmode(i->vad, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
+ }
+
+ /* Set file descriptors for audio, video, realtime text and UDPTL as needed */
+ if (i->rtp) {
+ ast_channel_set_fd(tmp, 0, ast_rtp_fd(i->rtp));
+ ast_channel_set_fd(tmp, 1, ast_rtcp_fd(i->rtp));
+ }
+ if (needvideo && i->vrtp) {
+ ast_channel_set_fd(tmp, 2, ast_rtp_fd(i->vrtp));
+ ast_channel_set_fd(tmp, 3, ast_rtcp_fd(i->vrtp));
+ }
+ if (needtext && i->trtp)
+ ast_channel_set_fd(tmp, 4, ast_rtp_fd(i->trtp));
+ if (i->udptl)
+ ast_channel_set_fd(tmp, 5, ast_udptl_fd(i->udptl));
+
+ if (state == AST_STATE_RING)
+ tmp->rings = 1;
+ tmp->adsicpe = AST_ADSI_UNAVAILABLE;
+ tmp->writeformat = fmt;
+ tmp->rawwriteformat = fmt;
+ tmp->readformat = fmt;
+ tmp->rawreadformat = fmt;
+ tmp->tech_pvt = dialog_ref(i);
+
+ tmp->callgroup = i->callgroup;
+ tmp->pickupgroup = i->pickupgroup;
+ tmp->cid.cid_pres = i->callingpres;
+ if (!ast_strlen_zero(i->accountcode))
+ ast_string_field_set(tmp, accountcode, i->accountcode);
+ if (i->amaflags)
+ tmp->amaflags = i->amaflags;
+ if (!ast_strlen_zero(i->language))
+ ast_string_field_set(tmp, language, i->language);
+ i->owner = tmp;
+ ast_module_ref(ast_module_info->self);
+ ast_copy_string(tmp->context, i->context, sizeof(tmp->context));
+ /*Since it is valid to have extensions in the dialplan that have unescaped characters in them
+ * we should decode the uri before storing it in the channel, but leave it encoded in the sip_pvt
+ * structure so that there aren't issues when forming URI's
+ */
+ decoded_exten = ast_strdupa(i->exten);
+ ast_uri_decode(decoded_exten);
+ ast_copy_string(tmp->exten, decoded_exten, sizeof(tmp->exten));
+
+ /* Don't use ast_set_callerid() here because it will
+ * generate an unnecessary NewCallerID event */
+ tmp->cid.cid_ani = ast_strdup(i->cid_num);
+ if (!ast_strlen_zero(i->rdnis))
+ tmp->cid.cid_rdnis = ast_strdup(i->rdnis);
+
+ if (!ast_strlen_zero(i->exten) && strcmp(i->exten, "s"))
+ tmp->cid.cid_dnid = ast_strdup(i->exten);
+
+ tmp->priority = 1;
+ if (!ast_strlen_zero(i->uri))
+ pbx_builtin_setvar_helper(tmp, "SIPURI", i->uri);
+ if (!ast_strlen_zero(i->domain))
+ pbx_builtin_setvar_helper(tmp, "SIPDOMAIN", i->domain);
+ if (!ast_strlen_zero(i->callid))
+ pbx_builtin_setvar_helper(tmp, "SIPCALLID", i->callid);
+ if (i->rtp)
+ ast_jb_configure(tmp, &global_jbconf);
+
+ /* If the INVITE contains T.38 SDP information set the proper channel variable so a created outgoing call will also have T.38 */
+ if (i->udptl && i->t38.state == T38_PEER_DIRECT)
+ pbx_builtin_setvar_helper(tmp, "_T38CALL", "1");
+
+ /* Set channel variables for this call from configuration */
+ for (v = i->chanvars ; v ; v = v->next)
+ pbx_builtin_setvar_helper(tmp, v->name, v->value);
+
+ if (state != AST_STATE_DOWN && ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ tmp->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ ast_hangup(tmp);
+ tmp = NULL;
+ }
+
+ if (i->do_history)
+ append_history(i, "NewChan", "Channel %s - from %s", tmp->name, i->callid);
+
+ /* Inform manager user about new channel and their SIP call ID */
+ if (global_callevents)
+ manager_event(EVENT_FLAG_SYSTEM, "ChannelUpdate",
+ "Channel: %s\r\nUniqueid: %s\r\nChanneltype: %s\r\nSIPcallid: %s\r\nSIPfullcontact: %s\r\n",
+ tmp->name, tmp->uniqueid, "SIP", i->callid, i->fullcontact);
+
+ return tmp;
+}
+
+/*! \brief Reads one line of SIP message body */
+static char *get_body_by_line(const char *line, const char *name, int nameLen)
+{
+ if (strncasecmp(line, name, nameLen) == 0 && line[nameLen] == '=')
+ return ast_skip_blanks(line + nameLen + 1);
+
+ return "";
+}
+
+/*! \brief Lookup 'name' in the SDP starting
+ * at the 'start' line. Returns the matching line, and 'start'
+ * is updated with the next line number.
+ */
+static const char *get_sdp_iterate(int *start, struct sip_request *req, const char *name)
+{
+ int len = strlen(name);
+
+ while (*start < req->sdp_end) {
+ const char *r = get_body_by_line(req->line[(*start)++], name, len);
+ if (r[0] != '\0')
+ return r;
+ }
+
+ return "";
+}
+
+/*! \brief Get a line from an SDP message body */
+static const char *get_sdp(struct sip_request *req, const char *name)
+{
+ int dummy = 0;
+
+ return get_sdp_iterate(&dummy, req, name);
+}
+
+/*! \brief Get a specific line from the message body */
+static char *get_body(struct sip_request *req, char *name)
+{
+ int x;
+ int len = strlen(name);
+ char *r;
+
+ for (x = 0; x < req->lines; x++) {
+ r = get_body_by_line(req->line[x], name, len);
+ if (r[0] != '\0')
+ return r;
+ }
+
+ return "";
+}
+
+/*! \brief Find compressed SIP alias */
+static const char *find_alias(const char *name, const char *_default)
+{
+ /*! \brief Structure for conversion between compressed SIP and "normal" SIP */
+ static const struct cfalias {
+ char * const fullname;
+ char * const shortname;
+ } aliases[] = {
+ { "Content-Type", "c" },
+ { "Content-Encoding", "e" },
+ { "From", "f" },
+ { "Call-ID", "i" },
+ { "Contact", "m" },
+ { "Content-Length", "l" },
+ { "Subject", "s" },
+ { "To", "t" },
+ { "Supported", "k" },
+ { "Refer-To", "r" },
+ { "Referred-By", "b" },
+ { "Allow-Events", "u" },
+ { "Event", "o" },
+ { "Via", "v" },
+ { "Accept-Contact", "a" },
+ { "Reject-Contact", "j" },
+ { "Request-Disposition", "d" },
+ { "Session-Expires", "x" },
+ { "Identity", "y" },
+ { "Identity-Info", "n" },
+ };
+ int x;
+
+ for (x=0; x<sizeof(aliases) / sizeof(aliases[0]); x++)
+ if (!strcasecmp(aliases[x].fullname, name))
+ return aliases[x].shortname;
+
+ return _default;
+}
+
+static const char *__get_header(const struct sip_request *req, const char *name, int *start)
+{
+ int pass;
+
+ /*
+ * Technically you can place arbitrary whitespace both before and after the ':' in
+ * a header, although RFC3261 clearly says you shouldn't before, and place just
+ * one afterwards. If you shouldn't do it, what absolute idiot decided it was
+ * a good idea to say you can do it, and if you can do it, why in the hell would.
+ * you say you shouldn't.
+ * Anyways, pedanticsipchecking controls whether we allow spaces before ':',
+ * and we always allow spaces after that for compatibility.
+ */
+ for (pass = 0; name && pass < 2;pass++) {
+ int x, len = strlen(name);
+ for (x=*start; x<req->headers; x++) {
+ if (!strncasecmp(req->header[x], name, len)) {
+ char *r = req->header[x] + len; /* skip name */
+ if (pedanticsipchecking)
+ r = ast_skip_blanks(r);
+
+ if (*r == ':') {
+ *start = x+1;
+ return ast_skip_blanks(r+1);
+ }
+ }
+ }
+ if (pass == 0) /* Try aliases */
+ name = find_alias(name, NULL);
+ }
+
+ /* Don't return NULL, so get_header is always a valid pointer */
+ return "";
+}
+
+/*! \brief Get header from SIP request
+ \return Always return something, so don't check for NULL because it won't happen :-)
+*/
+static const char *get_header(const struct sip_request *req, const char *name)
+{
+ int start = 0;
+ return __get_header(req, name, &start);
+}
+
+/*! \brief Read RTP from network */
+static struct ast_frame *sip_rtp_read(struct ast_channel *ast, struct sip_pvt *p, int *faxdetect)
+{
+ /* Retrieve audio/etc from channel. Assumes p->lock is already held. */
+ struct ast_frame *f;
+
+ if (!p->rtp) {
+ /* We have no RTP allocated for this channel */
+ return &ast_null_frame;
+ }
+
+ switch(ast->fdno) {
+ case 0:
+ f = ast_rtp_read(p->rtp); /* RTP Audio */
+ break;
+ case 1:
+ f = ast_rtcp_read(p->rtp); /* RTCP Control Channel */
+ break;
+ case 2:
+ f = ast_rtp_read(p->vrtp); /* RTP Video */
+ break;
+ case 3:
+ f = ast_rtcp_read(p->vrtp); /* RTCP Control Channel for video */
+ break;
+ case 4:
+ f = ast_rtp_read(p->trtp); /* RTP Text */
+ if (sipdebug_text) {
+ int i;
+ unsigned char* arr = f->data;
+ for (i=0; i < f->datalen; i++)
+ ast_verbose("%c", (arr[i] > ' ' && arr[i] < '}') ? arr[i] : '.');
+ ast_verbose(" -> ");
+ for (i=0; i < f->datalen; i++)
+ ast_verbose("%02X ", arr[i]);
+ ast_verbose("\n");
+ }
+ break;
+ case 5:
+ f = ast_udptl_read(p->udptl); /* UDPTL for T.38 */
+ break;
+ default:
+ f = &ast_null_frame;
+ }
+ /* Don't forward RFC2833 if we're not supposed to */
+ if (f && (f->frametype == AST_FRAME_DTMF) &&
+ (ast_test_flag(&p->flags[0], SIP_DTMF) != SIP_DTMF_RFC2833))
+ return &ast_null_frame;
+
+ /* We already hold the channel lock */
+ if (!p->owner || (f && f->frametype != AST_FRAME_VOICE))
+ return f;
+
+ if (f && f->subclass != (p->owner->nativeformats & AST_FORMAT_AUDIO_MASK)) {
+ if (!(f->subclass & p->jointcapability)) {
+ ast_debug(1, "Bogus frame of format '%s' received from '%s'!\n",
+ ast_getformatname(f->subclass), p->owner->name);
+ return &ast_null_frame;
+ }
+ ast_debug(1, "Oooh, format changed to %d %s\n",
+ f->subclass, ast_getformatname(f->subclass));
+ p->owner->nativeformats = (p->owner->nativeformats & (AST_FORMAT_VIDEO_MASK | AST_FORMAT_TEXT_MASK)) | f->subclass;
+ ast_set_read_format(p->owner, p->owner->readformat);
+ ast_set_write_format(p->owner, p->owner->writeformat);
+ }
+
+ if (f && (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) && p->vad) {
+ f = ast_dsp_process(p->owner, p->vad, f);
+ if (f && f->frametype == AST_FRAME_DTMF) {
+ if (ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT_UDPTL) && f->subclass == 'f') {
+ ast_debug(1, "Fax CNG detected on %s\n", ast->name);
+ *faxdetect = 1;
+ } else {
+ ast_debug(1, "* Detected inband DTMF '%c'\n", f->subclass);
+ }
+ }
+ }
+
+ return f;
+}
+
+/*! \brief Read SIP RTP from channel */
+static struct ast_frame *sip_read(struct ast_channel *ast)
+{
+ struct ast_frame *fr;
+ struct sip_pvt *p = ast->tech_pvt;
+ int faxdetected = FALSE;
+
+ sip_pvt_lock(p);
+ fr = sip_rtp_read(ast, p, &faxdetected);
+ p->lastrtprx = time(NULL);
+
+ /* If we are NOT bridged to another channel, and we have detected fax tone we issue T38 re-invite to a peer */
+ /* If we are bridged then it is the responsibility of the SIP device to issue T38 re-invite if it detects CNG or fax preamble */
+ if (faxdetected && ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT_UDPTL) && (p->t38.state == T38_DISABLED) && !(ast_bridged_channel(ast))) {
+ if (!ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
+ if (!p->pendinginvite) {
+ ast_debug(3, "Sending reinvite on SIP (%s) for T.38 negotiation.\n",ast->name);
+ p->t38.state = T38_LOCAL_REINVITE;
+ transmit_reinvite_with_sdp(p, TRUE, FALSE);
+ ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, ast->name);
+ }
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ ast_debug(3, "Deferring reinvite on SIP (%s) - it will be re-negotiated for T.38\n", ast->name);
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ }
+
+ sip_pvt_unlock(p);
+ return fr;
+}
+
+
+/*! \brief Generate 32 byte random string for callid's etc */
+static char *generate_random_string(char *buf, size_t size)
+{
+ long val[4];
+ int x;
+
+ for (x=0; x<4; x++)
+ val[x] = ast_random();
+ snprintf(buf, size, "%08lx%08lx%08lx%08lx", val[0], val[1], val[2], val[3]);
+
+ return buf;
+}
+
+/*! \brief Build SIP Call-ID value for a non-REGISTER transaction */
+static void build_callid_pvt(struct sip_pvt *pvt)
+{
+ char buf[33];
+
+ const char *host = S_OR(pvt->fromdomain, ast_inet_ntoa(pvt->ourip.sin_addr));
+
+ ast_string_field_build(pvt, callid, "%s@%s", generate_random_string(buf, sizeof(buf)), host);
+
+}
+
+/*! \brief Build SIP Call-ID value for a REGISTER transaction */
+static void build_callid_registry(struct sip_registry *reg, struct in_addr ourip, const char *fromdomain)
+{
+ char buf[33];
+
+ const char *host = S_OR(fromdomain, ast_inet_ntoa(ourip));
+
+ ast_string_field_build(reg, callid, "%s@%s", generate_random_string(buf, sizeof(buf)), host);
+}
+
+/*! \brief Make our SIP dialog tag */
+static void make_our_tag(char *tagbuf, size_t len)
+{
+ snprintf(tagbuf, len, "as%08lx", ast_random());
+}
+
+/*! \brief Allocate Session-Timers struct w/in dialog */
+static struct sip_st_dlg* sip_st_alloc(struct sip_pvt *const p)
+{
+ struct sip_st_dlg *stp;
+
+ if (p->stimer) {
+ ast_log(LOG_ERROR, "Session-Timer struct already allocated\n");
+ return p->stimer;
+ }
+
+ if (!(stp = ast_calloc(1, sizeof(struct sip_st_dlg))))
+ return NULL;
+
+ p->stimer = stp;
+
+ stp->st_schedid = -1; /* Session-Timers ast_sched scheduler id */
+
+ return p->stimer;
+}
+
+/*! \brief Allocate sip_pvt structure, set defaults and link in the container.
+ * Returns a reference to the object so whoever uses it later must
+ * remember to release the reference.
+ */
+static struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *sin,
+ int useglobal_nat, const int intended_method)
+{
+ struct sip_pvt *p;
+
+ if (!(p = ast_calloc(1, sizeof(*p))))
+ return NULL;
+
+ if (ast_string_field_init(p, 512)) {
+ ast_free(p);
+ return NULL;
+ }
+
+ ast_mutex_init(&p->pvt_lock);
+
+ p->socket.fd = -1;
+ p->socket.type = SIP_TRANSPORT_UDP;
+ p->method = intended_method;
+ p->initid = -1;
+ p->waitid = -1;
+ p->autokillid = -1;
+ p->subscribed = NONE;
+ p->stateid = -1;
+ p->sessionversion_remote = -1;
+ p->session_modify = TRUE;
+ p->stimer = NULL;
+ p->prefs = default_prefs; /* Set default codecs for this call */
+
+ if (intended_method != SIP_OPTIONS) { /* Peerpoke has it's own system */
+ p->timer_t1 = global_t1; /* Default SIP retransmission timer T1 (RFC 3261) */
+ p->timer_b = global_timer_b; /* Default SIP transaction timer B (RFC 3261) */
+ }
+
+ if (!sin)
+ p->ourip = internip;
+ else {
+ p->sa = *sin;
+ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip);
+ }
+
+ /* Copy global flags to this PVT at setup. */
+ ast_copy_flags(&p->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY);
+ ast_copy_flags(&p->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+
+ p->do_history = recordhistory;
+
+ p->branch = ast_random();
+ make_our_tag(p->tag, sizeof(p->tag));
+ p->ocseq = INITIAL_CSEQ;
+
+ if (sip_methods[intended_method].need_rtp) {
+ p->rtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
+ /* If the global videosupport flag is on, we always create a RTP interface for video */
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_VIDEOSUPPORT))
+ p->vrtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_TEXTSUPPORT))
+ p->trtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT))
+ p->udptl = ast_udptl_new_with_bindaddr(sched, io, 0, bindaddr.sin_addr);
+ if (!p->rtp|| (ast_test_flag(&p->flags[1], SIP_PAGE2_VIDEOSUPPORT) && !p->vrtp)
+ || (ast_test_flag(&p->flags[1], SIP_PAGE2_TEXTSUPPORT) && !p->trtp)) {
+ ast_log(LOG_WARNING, "Unable to create RTP audio %s%ssession: %s\n",
+ ast_test_flag(&p->flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "and video " : "",
+ ast_test_flag(&p->flags[1], SIP_PAGE2_TEXTSUPPORT) ? "and text " : "", strerror(errno));
+ ast_mutex_destroy(&p->pvt_lock);
+ if (p->chanvars) {
+ ast_variables_destroy(p->chanvars);
+ p->chanvars = NULL;
+ }
+ ast_free(p);
+ return NULL;
+ }
+ ast_rtp_setqos(p->rtp, global_tos_audio, global_cos_audio, "SIP RTP");
+ ast_rtp_setdtmf(p->rtp, ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
+ ast_rtp_setdtmfcompensate(p->rtp, ast_test_flag(&p->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
+ ast_rtp_set_rtptimeout(p->rtp, global_rtptimeout);
+ ast_rtp_set_rtpholdtimeout(p->rtp, global_rtpholdtimeout);
+ ast_rtp_set_rtpkeepalive(p->rtp, global_rtpkeepalive);
+ if (p->vrtp) {
+ ast_rtp_setqos(p->vrtp, global_tos_video, global_cos_video, "SIP VRTP");
+ ast_rtp_setdtmf(p->vrtp, 0);
+ ast_rtp_setdtmfcompensate(p->vrtp, 0);
+ ast_rtp_set_rtptimeout(p->vrtp, global_rtptimeout);
+ ast_rtp_set_rtpholdtimeout(p->vrtp, global_rtpholdtimeout);
+ ast_rtp_set_rtpkeepalive(p->vrtp, global_rtpkeepalive);
+ }
+ if (p->trtp) {
+ ast_rtp_setqos(p->trtp, global_tos_text, global_cos_text, "SIP TRTP");
+ ast_rtp_setdtmf(p->trtp, 0);
+ ast_rtp_setdtmfcompensate(p->trtp, 0);
+ }
+ if (p->udptl)
+ ast_udptl_setqos(p->udptl, global_tos_audio, global_cos_audio);
+ p->maxcallbitrate = default_maxcallbitrate;
+ }
+
+ if (useglobal_nat && sin) {
+ /* Setup NAT structure according to global settings if we have an address */
+ ast_copy_flags(&p->flags[0], &global_flags[0], SIP_NAT);
+ p->recv = *sin;
+ do_setnat(p, ast_test_flag(&p->flags[0], SIP_NAT) & SIP_NAT_ROUTE);
+ }
+
+ if (p->method != SIP_REGISTER)
+ ast_string_field_set(p, fromdomain, default_fromdomain);
+ build_via(p);
+ if (!callid)
+ build_callid_pvt(p);
+ else
+ ast_string_field_set(p, callid, callid);
+ /* Assign default music on hold class */
+ ast_string_field_set(p, mohinterpret, default_mohinterpret);
+ ast_string_field_set(p, mohsuggest, default_mohsuggest);
+ p->capability = global_capability;
+ p->allowtransfer = global_allowtransfer;
+ if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) ||
+ (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO))
+ p->noncodeccapability |= AST_RTP_DTMF;
+ if (p->udptl) {
+ ast_copy_flags(&p->t38.t38support, &p->flags[1], SIP_PAGE2_T38SUPPORT);
+ set_t38_capabilities(p);
+ p->t38.jointcapability = p->t38.capability;
+ }
+ ast_string_field_set(p, context, default_context);
+
+
+ /* Add to active dialog list */
+ dialoglist_lock();
+ p->next = dialoglist;
+ dialoglist = dialog_ref(p);
+ dialoglist_unlock();
+ ast_debug(1, "Allocating new SIP dialog for %s - %s (%s)\n", callid ? callid : "(No Call-ID)", sip_methods[intended_method].text, p->rtp ? "With RTP" : "No RTP");
+ return p;
+}
+
+/*! \brief find or create a dialog structure for an incoming SIP message.
+ * Connect incoming SIP message to current dialog or create new dialog structure
+ * Returns a reference to the sip_pvt object, remember to give it back once done.
+ * Called by handle_incoming(), sipsock_read
+ */
+static struct sip_pvt *find_call(struct sip_request *req, struct sockaddr_in *sin, const int intended_method)
+{
+ struct sip_pvt *p = NULL;
+ char *tag = ""; /* note, tag is never NULL */
+ char totag[128];
+ char fromtag[128];
+ const char *callid = get_header(req, "Call-ID");
+ const char *from = get_header(req, "From");
+ const char *to = get_header(req, "To");
+ const char *cseq = get_header(req, "Cseq");
+
+ /* Call-ID, to, from and Cseq are required by RFC 3261. (Max-forwards and via too - ignored now) */
+ /* get_header always returns non-NULL so we must use ast_strlen_zero() */
+ if (ast_strlen_zero(callid) || ast_strlen_zero(to) ||
+ ast_strlen_zero(from) || ast_strlen_zero(cseq))
+ return NULL; /* Invalid packet */
+
+ if (pedanticsipchecking) {
+ /* In principle Call-ID's uniquely identify a call, but with a forking SIP proxy
+ we need more to identify a branch - so we have to check branch, from
+ and to tags to identify a call leg.
+ For Asterisk to behave correctly, you need to turn on pedanticsipchecking
+ in sip.conf
+ */
+ if (gettag(req, "To", totag, sizeof(totag)))
+ req->has_to_tag = 1; /* Used in handle_request/response */
+ gettag(req, "From", fromtag, sizeof(fromtag));
+
+ tag = (req->method == SIP_RESPONSE) ? totag : fromtag;
+
+ ast_debug(5, "= Looking for Call ID: %s (Checking %s) --From tag %s --To-tag %s \n", callid, req->method==SIP_RESPONSE ? "To" : "From", fromtag, totag);
+
+ /* All messages must always have From: tag */
+ if (ast_strlen_zero(fromtag)) {
+ ast_debug(5, "%s request has no from tag, dropping callid: %s from: %s\n", sip_methods[req->method].text , callid, from );
+ return NULL;
+ }
+ /* reject requests that must always have a To: tag */
+ if (ast_strlen_zero(totag) && (req->method == SIP_ACK || req->method == SIP_BYE || req->method == SIP_INFO )) {
+ ast_debug(5, "%s must have a to tag. dropping callid: %s from: %s\n", sip_methods[req->method].text , callid, from );
+ return NULL;
+ }
+ }
+
+ dialoglist_lock();
+ for (p = dialoglist; p; p = p->next) {
+ /* In pedantic, we do not want packets with bad syntax to be connected to a PVT */
+ int found = FALSE;
+ if (ast_strlen_zero(p->callid))
+ continue;
+ if (req->method == SIP_REGISTER)
+ found = (!strcmp(p->callid, callid));
+ else
+ found = (!strcmp(p->callid, callid) &&
+ (!pedanticsipchecking || !tag || ast_strlen_zero(p->theirtag) || !strcmp(p->theirtag, tag))) ;
+
+ ast_debug(5, "= %s Their Call ID: %s Their Tag %s Our tag: %s\n", found ? "Found" : "No match", p->callid, p->theirtag, p->tag);
+
+ /* If we get a new request within an existing to-tag - check the to tag as well */
+ if (pedanticsipchecking && found && req->method != SIP_RESPONSE) { /* SIP Request */
+ if (p->tag[0] == '\0' && totag[0]) {
+ /* We have no to tag, but they have. Wrong dialog */
+ found = FALSE;
+ } else if (totag[0]) { /* Both have tags, compare them */
+ if (strcmp(totag, p->tag)) {
+ found = FALSE; /* This is not our packet */
+ }
+ }
+ if (!found)
+ ast_debug(5, "= Being pedantic: This is not our match on request: Call ID: %s Ourtag <null> Totag %s Method %s\n", p->callid, totag, sip_methods[req->method].text);
+ }
+
+
+ if (found) {
+ /* Found the call */
+ sip_pvt_lock(p);
+ dialoglist_unlock();
+ return p;
+ }
+ }
+ dialoglist_unlock();
+
+ /* See if the method is capable of creating a dialog */
+ if (sip_methods[intended_method].can_create == CAN_CREATE_DIALOG) {
+ if (intended_method == SIP_REFER) {
+ /* We do support REFER, but not outside of a dialog yet */
+ transmit_response_using_temp(callid, sin, 1, intended_method, req, "603 Declined (no dialog)");
+ } else if (intended_method == SIP_NOTIFY) {
+ /* We do not support out-of-dialog NOTIFY either,
+ like voicemail notification, so cancel that early */
+ transmit_response_using_temp(callid, sin, 1, intended_method, req, "489 Bad event");
+ } else {
+ /* Ok, time to create a new SIP dialog object, a pvt */
+ if ((p = sip_alloc(callid, sin, 1, intended_method))) {
+ /* Ok, we've created a dialog, let's go and process it */
+ sip_pvt_lock(p);
+ } else {
+ /* We have a memory or file/socket error (can't allocate RTP sockets or something) so we're not
+ getting a dialog from sip_alloc.
+
+ Without a dialog we can't retransmit and handle ACKs and all that, but at least
+ send an error message.
+
+ Sorry, we apologize for the inconvienience
+ */
+ transmit_response_using_temp(callid, sin, 1, intended_method, req, "500 Server internal error");
+ ast_debug(4, "Failed allocating SIP dialog, sending 500 Server internal error and giving up\n");
+ }
+ }
+ return p; /* can be NULL */
+ } else if( sip_methods[intended_method].can_create == CAN_CREATE_DIALOG_UNSUPPORTED_METHOD) {
+ /* A method we do not support, let's take it on the volley */
+ transmit_response_using_temp(callid, sin, 1, intended_method, req, "501 Method Not Implemented");
+ ast_debug(2, "Got a request with unsupported SIP method.\n");
+ } else if (intended_method != SIP_RESPONSE && intended_method != SIP_ACK) {
+ /* This is a request outside of a dialog that we don't know about */
+ transmit_response_using_temp(callid, sin, 1, intended_method, req, "481 Call leg/transaction does not exist");
+ ast_debug(2, "That's odd... Got a request in unknown dialog. Callid %s\n", callid ? callid : "<unknown>");
+ }
+ /* We do not respond to responses for dialogs that we don't know about, we just drop
+ the session quickly */
+ if (intended_method == SIP_RESPONSE)
+ ast_debug(2, "That's odd... Got a response on a call we dont know about. Callid %s\n", callid ? callid : "<unknown>");
+
+ return p;
+}
+
+/*! \brief Parse register=> line in sip.conf and add to registry */
+static int sip_register(const char *value, int lineno)
+{
+ struct sip_registry *reg;
+ int portnum = 0;
+ enum sip_transport transport = SIP_TRANSPORT_UDP;
+ char buf[256] = "";
+ char *username = NULL;
+ char *hostname=NULL, *secret=NULL, *authuser=NULL;
+ char *porta=NULL;
+ char *callback=NULL;
+ char *trans=NULL;
+
+ if (!value)
+ return -1;
+
+ ast_copy_string(buf, value, sizeof(buf));
+
+ username = strstr(buf, "://");
+
+ if (username) {
+ *username = '\0';
+ username += 3;
+
+ trans = buf;
+
+ if (!strcasecmp(trans, "udp"))
+ transport = SIP_TRANSPORT_UDP;
+ else if (!strcasecmp(trans, "tcp"))
+ transport = SIP_TRANSPORT_TCP;
+ else if (!strcasecmp(trans, "tls"))
+ transport = SIP_TRANSPORT_TLS;
+ else
+ ast_log(LOG_WARNING, "'%s' is not a valid transport value for registration '%s' at line '%d'\n", trans, value, lineno);
+ } else {
+ username = buf;
+ ast_log(LOG_DEBUG, "no trans\n");
+ }
+
+ /* First split around the last '@' then parse the two components. */
+ hostname = strrchr(username, '@'); /* allow @ in the first part */
+ if (hostname)
+ *hostname++ = '\0';
+ if (ast_strlen_zero(username) || ast_strlen_zero(hostname)) {
+ ast_log(LOG_WARNING, "Format for registration is user[:secret[:authuser]]@host[:port][/contact] at line %d\n", lineno);
+ return -1;
+ }
+ /* split user[:secret[:authuser]] */
+ secret = strchr(username, ':');
+ if (secret) {
+ *secret++ = '\0';
+ authuser = strchr(secret, ':');
+ if (authuser)
+ *authuser++ = '\0';
+ }
+ /* split host[:port][/contact] */
+ callback = strchr(hostname, '/');
+ if (callback)
+ *callback++ = '\0';
+ if (ast_strlen_zero(callback))
+ callback = "s";
+ porta = strchr(hostname, ':');
+ if (porta) {
+ *porta++ = '\0';
+ portnum = atoi(porta);
+ if (portnum == 0) {
+ ast_log(LOG_WARNING, "%s is not a valid port number at line %d\n", porta, lineno);
+ return -1;
+ }
+ }
+ if (!(reg = ast_calloc(1, sizeof(*reg)))) {
+ ast_log(LOG_ERROR, "Out of memory. Can't allocate SIP registry entry\n");
+ return -1;
+ }
+
+ if (ast_string_field_init(reg, 256)) {
+ ast_log(LOG_ERROR, "Out of memory. Can't allocate SIP registry strings\n");
+ ast_free(reg);
+ return -1;
+ }
+
+ regobjs++;
+ ASTOBJ_INIT(reg);
+ ast_string_field_set(reg, callback, callback);
+ if (!ast_strlen_zero(username))
+ ast_string_field_set(reg, username, username);
+ if (hostname)
+ ast_string_field_set(reg, hostname, hostname);
+ if (authuser)
+ ast_string_field_set(reg, authuser, authuser);
+ if (secret)
+ ast_string_field_set(reg, secret, secret);
+ reg->transport = transport;
+ reg->expire = -1;
+ reg->expiry = default_expiry;
+ reg->timeout = -1;
+ reg->refresh = default_expiry;
+ reg->portno = portnum;
+ reg->callid_valid = FALSE;
+ reg->ocseq = INITIAL_CSEQ;
+ ASTOBJ_CONTAINER_LINK(&regl, reg); /* Add the new registry entry to the list */
+ registry_unref(reg); /* release the reference given by ASTOBJ_INIT. The container has another reference */
+ return 0;
+}
+
+/*! \brief Parse multiline SIP headers into one header
+ This is enabled if pedanticsipchecking is enabled */
+static int lws2sws(char *msgbuf, int len)
+{
+ int h = 0, t = 0;
+ int lws = 0;
+
+ for (; h < len;) {
+ /* Eliminate all CRs */
+ if (msgbuf[h] == '\r') {
+ h++;
+ continue;
+ }
+ /* Check for end-of-line */
+ if (msgbuf[h] == '\n') {
+ /* Check for end-of-message */
+ if (h + 1 == len)
+ break;
+ /* Check for a continuation line */
+ if (msgbuf[h + 1] == ' ' || msgbuf[h + 1] == '\t') {
+ /* Merge continuation line */
+ h++;
+ continue;
+ }
+ /* Propagate LF and start new line */
+ msgbuf[t++] = msgbuf[h++];
+ lws = 0;
+ continue;
+ }
+ if (msgbuf[h] == ' ' || msgbuf[h] == '\t') {
+ if (lws) {
+ h++;
+ continue;
+ }
+ msgbuf[t++] = msgbuf[h++];
+ lws = 1;
+ continue;
+ }
+ msgbuf[t++] = msgbuf[h++];
+ if (lws)
+ lws = 0;
+ }
+ msgbuf[t] = '\0';
+ return t;
+}
+
+/*! \brief Parse a SIP message
+ \note this function is used both on incoming and outgoing packets
+*/
+static void parse_request(struct sip_request *req)
+{
+ char *c = req->data, **dst = req->header;
+ int i = 0, lim = SIP_MAX_HEADERS - 1;
+
+ req->header[0] = c;
+ req->headers = -1; /* mark that we are working on the header */
+ for (; *c; c++) {
+ if (*c == '\r') /* remove \r */
+ *c = '\0';
+ else if (*c == '\n') { /* end of this line */
+ *c = '\0';
+ if (sipdebug)
+ ast_debug(4, "%7s %2d [%3d]: %s\n",
+ req->headers < 0 ? "Header" : "Body",
+ i, (int)strlen(dst[i]), dst[i]);
+ if (ast_strlen_zero(dst[i]) && req->headers < 0) {
+ req->headers = i; /* record number of header lines */
+ dst = req->line; /* start working on the body */
+ i = 0;
+ lim = SIP_MAX_LINES - 1;
+ } else { /* move to next line, check for overflows */
+ if (i++ >= lim)
+ break;
+ }
+ dst[i] = c + 1; /* record start of next line */
+ }
+ }
+ /* Check for last header without CRLF. The RFC for SDP requires CRLF,
+ but since some devices send without, we'll be generous in what we accept.
+ */
+ if (!ast_strlen_zero(dst[i])) {
+ if (sipdebug)
+ ast_debug(4, "%7s %2d [%3d]: %s\n",
+ req->headers < 0 ? "Header" : "Body",
+ i, (int)strlen(dst[i]), dst[i]);
+ i++;
+ }
+ /* update count of header or body lines */
+ if (req->headers >= 0) /* we are in the body */
+ req->lines = i;
+ else { /* no body */
+ req->headers = i;
+ req->lines = 0;
+ req->line[0] = "";
+ }
+
+ if (*c)
+ ast_log(LOG_WARNING, "Too many lines, skipping <%s>\n", c);
+ /* Split up the first line parts */
+ determine_firstline_parts(req);
+}
+
+/*!
+ \brief Determine whether a SIP message contains an SDP in its body
+ \param req the SIP request to process
+ \return 1 if SDP found, 0 if not found
+
+ Also updates req->sdp_start and req->sdp_end to indicate where the SDP
+ lives in the message body.
+*/
+static int find_sdp(struct sip_request *req)
+{
+ const char *content_type;
+ const char *search;
+ char *boundary;
+ unsigned int x;
+ int boundaryisquoted = FALSE;
+ int found_application_sdp = FALSE;
+ int found_end_of_headers = FALSE;
+
+ content_type = get_header(req, "Content-Type");
+
+ /* if the body contains only SDP, this is easy */
+ if (!strcasecmp(content_type, "application/sdp")) {
+ req->sdp_start = 0;
+ req->sdp_end = req->lines;
+ return req->lines ? 1 : 0;
+ }
+
+ /* if it's not multipart/mixed, there cannot be an SDP */
+ if (strncasecmp(content_type, "multipart/mixed", 15))
+ return 0;
+
+ /* if there is no boundary marker, it's invalid */
+ if ((search = strcasestr(content_type, ";boundary=")))
+ search += 10;
+ else if ((search = strcasestr(content_type, "; boundary=")))
+ search += 11;
+ else
+ return 0;
+
+ if (ast_strlen_zero(search))
+ return 0;
+
+ /* If the boundary is quoted with ", remove quote */
+ if (*search == '\"') {
+ search++;
+ boundaryisquoted = TRUE;
+ }
+
+ /* make a duplicate of the string, with two extra characters
+ at the beginning */
+ boundary = ast_strdupa(search - 2);
+ boundary[0] = boundary[1] = '-';
+ /* Remove final quote */
+ if (boundaryisquoted)
+ boundary[strlen(boundary) - 1] = '\0';
+
+ /* search for the boundary marker, the empty line delimiting headers from
+ sdp part and the end boundry if it exists */
+
+ for (x = 0; x < (req->lines ); x++) {
+ if(!strncasecmp(req->line[x], boundary, strlen(boundary))){
+ if(found_application_sdp && found_end_of_headers){
+ req->sdp_end = x-1;
+ return 1;
+ }
+ found_application_sdp = FALSE;
+ }
+ if(!strcasecmp(req->line[x], "Content-Type: application/sdp"))
+ found_application_sdp = TRUE;
+
+ if(strlen(req->line[x]) == 0 ){
+ if(found_application_sdp && !found_end_of_headers){
+ req->sdp_start = x;
+ found_end_of_headers = TRUE;
+ }
+ }
+ }
+ if(found_application_sdp && found_end_of_headers) {
+ req->sdp_end = x;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*! \brief Process SIP SDP offer, select formats and activate RTP channels
+ If offer is rejected, we will not change any properties of the call
+ Return 0 on success, a negative value on errors.
+ Must be called after find_sdp().
+*/
+static int process_sdp(struct sip_pvt *p, struct sip_request *req)
+{
+ const char *m; /* SDP media offer */
+ const char *c;
+ const char *a;
+ const char *o; /* Pointer to o= line */
+ char *o_copy; /* Copy of o= line */
+ char *token;
+ char host[258];
+ int len = -1;
+ int portno = -1; /*!< RTP Audio port number */
+ int vportno = -1; /*!< RTP Video port number */
+ int tportno = -1; /*!< RTP Text port number */
+ int udptlportno = -1;
+ int peert38capability = 0;
+ char s[256];
+ int old = 0;
+
+ /* Peer capability is the capability in the SDP, non codec is RFC2833 DTMF (101) */
+ int peercapability = 0, peernoncodeccapability = 0;
+ int vpeercapability = 0, vpeernoncodeccapability = 0;
+ int tpeercapability = 0, tpeernoncodeccapability = 0;
+ struct sockaddr_in sin; /*!< media socket address */
+ struct sockaddr_in vsin; /*!< Video socket address */
+ struct sockaddr_in tsin; /*!< Text socket address */
+
+ const char *codecs;
+ struct hostent *hp; /*!< RTP Audio host IP */
+ struct hostent *vhp = NULL; /*!< RTP video host IP */
+ struct hostent *thp = NULL; /*!< RTP text host IP */
+ struct ast_hostent audiohp;
+ struct ast_hostent videohp;
+ struct ast_hostent texthp;
+ int codec;
+ int destiterator = 0;
+ int iterator;
+ int sendonly = -1;
+ int numberofports;
+ struct ast_rtp *newaudiortp, *newvideortp, *newtextrtp; /* Buffers for codec handling */
+ int newjointcapability; /* Negotiated capability */
+ int newpeercapability;
+ int newnoncodeccapability;
+ int numberofmediastreams = 0;
+ int debug = sip_debug_test_pvt(p);
+
+ int found_rtpmap_codecs[32];
+ int last_rtpmap_codec=0;
+
+ char buf[BUFSIZ];
+ int rua_version;
+
+ if (!p->rtp) {
+ ast_log(LOG_ERROR, "Got SDP but have no RTP session allocated.\n");
+ return -1;
+ }
+
+ /* Initialize the temporary RTP structures we use to evaluate the offer from the peer */
+ newaudiortp = alloca(ast_rtp_alloc_size());
+ memset(newaudiortp, 0, ast_rtp_alloc_size());
+ ast_rtp_new_init(newaudiortp);
+ ast_rtp_pt_clear(newaudiortp);
+
+ newvideortp = alloca(ast_rtp_alloc_size());
+ memset(newvideortp, 0, ast_rtp_alloc_size());
+ ast_rtp_new_init(newvideortp);
+ ast_rtp_pt_clear(newvideortp);
+
+ newtextrtp = alloca(ast_rtp_alloc_size());
+ memset(newtextrtp, 0, ast_rtp_alloc_size());
+ ast_rtp_new_init(newtextrtp);
+ ast_rtp_pt_clear(newtextrtp);
+
+ /* Update our last rtprx when we receive an SDP, too */
+ p->lastrtprx = p->lastrtptx = time(NULL); /* XXX why both ? */
+
+ /* Store the SDP version number of remote UA. This will allow us to
+ distinguish between session modifications and session refreshes. If
+ the remote UA does not send an incremented SDP version number in a
+ subsequent RE-INVITE then that means its not changing media session.
+ The RE-INVITE may have been sent to update connected party, remote
+ target or to refresh the session (Session-Timers). Asterisk must not
+ change media session and increment its own version number in answer
+ SDP in this case. */
+
+ o = get_sdp(req, "o");
+ if (ast_strlen_zero(o)) {
+ ast_log(LOG_WARNING, "SDP sytax error. SDP without an o= line\n");
+ return -1;
+ }
+
+ o_copy = ast_strdupa(o);
+ token = strsep(&o_copy, " "); /* Skip username */
+ if (!o_copy) {
+ ast_log(LOG_WARNING, "SDP sytax error in o= line username\n");
+ return -1;
+ }
+ token = strsep(&o_copy, " "); /* Skip session-id */
+ if (!o_copy) {
+ ast_log(LOG_WARNING, "SDP sytax error in o= line session-id\n");
+ return -1;
+ }
+ token = strsep(&o_copy, " "); /* Version */
+ if (!o_copy) {
+ ast_log(LOG_WARNING, "SDP sytax error in o= line\n");
+ return -1;
+ }
+ if (!sscanf(token, "%d", &rua_version)) {
+ ast_log(LOG_WARNING, "SDP sytax error in o= line version\n");
+ return -1;
+ }
+
+ if (p->sessionversion_remote < 0 || p->sessionversion_remote != rua_version) {
+ p->sessionversion_remote = rua_version;
+ p->session_modify = TRUE;
+ } else if (p->sessionversion_remote == rua_version) {
+ p->session_modify = FALSE;
+ ast_debug(2, "SDP version number same as previous SDP\n");
+ return 0;
+ }
+
+ /* Try to find first media stream */
+ m = get_sdp(req, "m");
+ destiterator = req->sdp_start;
+ c = get_sdp_iterate(&destiterator, req, "c");
+ if (ast_strlen_zero(m) || ast_strlen_zero(c)) {
+ ast_log(LOG_WARNING, "Insufficient information for SDP (m = '%s', c = '%s')\n", m, c);
+ return -1;
+ }
+
+ /* Check for IPv4 address (not IPv6 yet) */
+ if (sscanf(c, "IN IP4 %256s", host) != 1) {
+ ast_log(LOG_WARNING, "Invalid host in c= line, '%s'\n", c);
+ return -1;
+ }
+
+ /* XXX This could block for a long time, and block the main thread! XXX */
+ hp = ast_gethostbyname(host, &audiohp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "Unable to lookup host in c= line, '%s'\n", c);
+ return -1;
+ }
+ vhp = hp; /* Copy to video address as default too */
+ thp = hp; /* Copy to text address as default too */
+
+ iterator = req->sdp_start;
+ /* default: novideo and notext set */
+ p->novideo = TRUE;
+ p->notext = TRUE;
+
+ if (p->vrtp)
+ ast_rtp_pt_clear(newvideortp); /* Must be cleared in case no m=video line exists */
+
+ if (p->trtp)
+ ast_rtp_pt_clear(newtextrtp); /* Must be cleared in case no m=text line exists */
+
+ /* Find media streams in this SDP offer */
+ while ((m = get_sdp_iterate(&iterator, req, "m"))[0] != '\0') {
+ int x;
+ int audio = FALSE;
+ int video = FALSE;
+ int text = FALSE;
+
+ numberofports = 1;
+ if ((sscanf(m, "audio %d/%d RTP/AVP %n", &x, &numberofports, &len) == 2) ||
+ (sscanf(m, "audio %d RTP/AVP %n", &x, &len) == 1)) {
+ audio = TRUE;
+ numberofmediastreams++;
+ /* Found audio stream in this media definition */
+ portno = x;
+ /* Scan through the RTP payload types specified in a "m=" line: */
+ for (codecs = m + len; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) {
+ if (sscanf(codecs, "%d%n", &codec, &len) != 1) {
+ ast_log(LOG_WARNING, "Error in codec string '%s'\n", codecs);
+ return -1;
+ }
+ if (debug)
+ ast_verbose("Found RTP audio format %d\n", codec);
+ ast_rtp_set_m_type(newaudiortp, codec);
+ }
+ } else if ((sscanf(m, "video %d/%d RTP/AVP %n", &x, &numberofports, &len) == 2) ||
+ (sscanf(m, "video %d RTP/AVP %n", &x, &len) == 1)) {
+ video = TRUE;
+ p->novideo = FALSE;
+ numberofmediastreams++;
+ vportno = x;
+ /* Scan through the RTP payload types specified in a "m=" line: */
+ for (codecs = m + len; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) {
+ if (sscanf(codecs, "%d%n", &codec, &len) != 1) {
+ ast_log(LOG_WARNING, "Error in codec string '%s'\n", codecs);
+ return -1;
+ }
+ if (debug)
+ ast_verbose("Found RTP video format %d\n", codec);
+ ast_rtp_set_m_type(newvideortp, codec);
+ }
+ } else if ((sscanf(m, "text %d/%d RTP/AVP %n", &x, &numberofports, &len) == 2) ||
+ (sscanf(m, "text %d RTP/AVP %n", &x, &len) == 1)) {
+ text = TRUE;
+ p->notext = FALSE;
+ numberofmediastreams++;
+ tportno = x;
+ /* Scan through the RTP payload types specified in a "m=" line: */
+ for (codecs = m + len; !ast_strlen_zero(codecs); codecs = ast_skip_blanks(codecs + len)) {
+ if (sscanf(codecs, "%d%n", &codec, &len) != 1) {
+ ast_log(LOG_WARNING, "Error in codec string '%s'\n", codecs);
+ return -1;
+ }
+ if (debug)
+ ast_verbose("Found RTP text format %d\n", codec);
+ ast_rtp_set_m_type(newtextrtp, codec);
+ }
+ } else if (p->udptl && ( (sscanf(m, "image %d udptl t38%n", &x, &len) == 1) ||
+ (sscanf(m, "image %d UDPTL t38%n", &x, &len) == 1) )) {
+ if (debug)
+ ast_verbose("Got T.38 offer in SDP in dialog %s\n", p->callid);
+ udptlportno = x;
+ numberofmediastreams++;
+
+ if (p->owner && p->lastinvite) {
+ p->t38.state = T38_PEER_REINVITE; /* T38 Offered in re-invite from remote party */
+ ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>" );
+ } else {
+ p->t38.state = T38_PEER_DIRECT; /* T38 Offered directly from peer in first invite */
+ ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
+ }
+ } else
+ ast_log(LOG_WARNING, "Unsupported SDP media type in offer: %s\n", m);
+ if (numberofports > 1)
+ ast_log(LOG_WARNING, "SDP offered %d ports for media, not supported by Asterisk. Will try anyway...\n", numberofports);
+
+
+ /* Check for Media-description-level-address for audio */
+ c = get_sdp_iterate(&destiterator, req, "c");
+ if (!ast_strlen_zero(c)) {
+ if (sscanf(c, "IN IP4 %256s", host) != 1) {
+ ast_log(LOG_WARNING, "Invalid secondary host in c= line, '%s'\n", c);
+ } else {
+ /* XXX This could block for a long time, and block the main thread! XXX */
+ if (audio) {
+ if ( !(hp = ast_gethostbyname(host, &audiohp))) {
+ ast_log(LOG_WARNING, "Unable to lookup RTP Audio host in secondary c= line, '%s'\n", c);
+ return -2;
+ }
+ } else if (video) {
+ if (!(vhp = ast_gethostbyname(host, &videohp))) {
+ ast_log(LOG_WARNING, "Unable to lookup RTP video host in secondary c= line, '%s'\n", c);
+ return -2;
+ }
+ } else if (text) {
+ if (!(thp = ast_gethostbyname(host, &texthp))) {
+ ast_log(LOG_WARNING, "Unable to lookup RTP text host in secondary c= line, '%s'\n", c);
+ return -2;
+ }
+ }
+ }
+
+ }
+ }
+ if (portno == -1 && vportno == -1 && udptlportno == -1 && tportno == -1)
+ /* No acceptable offer found in SDP - we have no ports */
+ /* Do not change RTP or VRTP if this is a re-invite */
+ return -2;
+
+ if (numberofmediastreams > 3)
+ /* We have too many fax, audio and/or video and/or text media streams, fail this offer */
+ return -3;
+
+ /* RTP addresses and ports for audio and video */
+ sin.sin_family = AF_INET;
+ vsin.sin_family = AF_INET;
+ tsin.sin_family = AF_INET;
+ memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+ if (vhp)
+ memcpy(&vsin.sin_addr, vhp->h_addr, sizeof(vsin.sin_addr));
+ if (thp)
+ memcpy(&tsin.sin_addr, thp->h_addr, sizeof(tsin.sin_addr));
+
+ /* Setup UDPTL port number */
+ if (p->udptl) {
+ if (udptlportno > 0) {
+ sin.sin_port = htons(udptlportno);
+ ast_udptl_set_peer(p->udptl, &sin);
+ if (debug)
+ ast_debug(1,"Peer T.38 UDPTL is at port %s:%d\n",ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+ } else {
+ ast_udptl_stop(p->udptl);
+ if (debug)
+ ast_debug(1, "Peer doesn't provide T.38 UDPTL\n");
+ }
+ }
+
+
+ if (p->rtp) {
+ if (portno > 0) {
+ sin.sin_port = htons(portno);
+ ast_rtp_set_peer(p->rtp, &sin);
+ if (debug)
+ ast_verbose("Peer audio RTP is at port %s:%d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+ } else {
+ if (udptlportno > 0) {
+ if (debug)
+ ast_verbose("Got T.38 Re-invite without audio. Keeping RTP active during T.38 session. Callid %s\n", p->callid);
+ } else {
+ ast_rtp_stop(p->rtp);
+ if (debug)
+ ast_verbose("Peer doesn't provide audio. Callid %s\n", p->callid);
+ }
+ }
+ }
+ /* Setup video port number, assumes we have audio */
+ if (vportno != -1)
+ vsin.sin_port = htons(vportno);
+
+ /* Setup text port number, assumes we have audio */
+ if (tportno != -1)
+ tsin.sin_port = htons(tportno);
+
+ /* Next, scan through each "a=xxxx:" line, noting each
+ * specified RTP payload type (with corresponding MIME subtype):
+ */
+ /* XXX This needs to be done per media stream, since it's media stream specific */
+ iterator = req->sdp_start;
+ while ((a = get_sdp_iterate(&iterator, req, "a"))[0] != '\0') {
+ char* mimeSubtype = ast_strdupa(a); /* ensures we have enough space */
+ if (option_debug > 1) {
+ int breakout = FALSE;
+
+ /* If we're debugging, check for unsupported sdp options */
+ if (!strncasecmp(a, "rtcp:", (size_t) 5)) {
+ if (debug)
+ ast_verbose("Got unsupported a:rtcp in SDP offer \n");
+ breakout = TRUE;
+ } else if (!strncasecmp(a, "fmtp:", (size_t) 5)) {
+ /* Format parameters: Not supported */
+ /* Note: This is used for codec parameters, like bitrate for
+ G722 and video formats for H263 and H264
+ See RFC2327 for an example */
+ if (debug)
+ ast_verbose("Got unsupported a:fmtp in SDP offer \n");
+ breakout = TRUE;
+ } else if (!strncasecmp(a, "framerate:", (size_t) 10)) {
+ /* Video stuff: Not supported */
+ if (debug)
+ ast_verbose("Got unsupported a:framerate in SDP offer \n");
+ breakout = TRUE;
+ } else if (!strncasecmp(a, "maxprate:", (size_t) 9)) {
+ /* Video stuff: Not supported */
+ if (debug)
+ ast_verbose("Got unsupported a:maxprate in SDP offer \n");
+ breakout = TRUE;
+ } else if (!strncasecmp(a, "crypto:", (size_t) 7)) {
+ /* SRTP stuff, not yet supported */
+ if (debug)
+ ast_verbose("Got unsupported a:crypto in SDP offer \n");
+ breakout = TRUE;
+ }
+ if (breakout) /* We have a match, skip to next header */
+ continue;
+ }
+ if (!strcasecmp(a, "sendonly")) {
+ if (sendonly == -1)
+ sendonly = 1;
+ continue;
+ } else if (!strcasecmp(a, "inactive")) {
+ if (sendonly == -1)
+ sendonly = 2;
+ continue;
+ } else if (!strcasecmp(a, "sendrecv")) {
+ if (sendonly == -1)
+ sendonly = 0;
+ continue;
+ } else if (strlen(a) > 5 && !strncasecmp(a, "ptime", 5)) {
+ char *tmp = strrchr(a, ':');
+ long int framing = 0;
+ if (tmp) {
+ tmp++;
+ framing = strtol(tmp, NULL, 10);
+ if (framing == LONG_MIN || framing == LONG_MAX) {
+ framing = 0;
+ ast_debug(1, "Can't read framing from SDP: %s\n", a);
+ }
+ }
+ if (framing && last_rtpmap_codec) {
+ if (p->autoframing) {
+ struct ast_codec_pref *pref = ast_rtp_codec_getpref(p->rtp);
+ int codec_n;
+ int format = 0;
+ for (codec_n = 0; codec_n < last_rtpmap_codec; codec_n++) {
+ format = ast_rtp_codec_getformat(found_rtpmap_codecs[codec_n]);
+ if (!format) /* non-codec or not found */
+ continue;
+ ast_debug(1, "Setting framing for %d to %ld\n", format, framing);
+ ast_codec_pref_setsize(pref, format, framing);
+ }
+ ast_rtp_codec_setpref(p->rtp, pref);
+ }
+ }
+ memset(&found_rtpmap_codecs, 0, sizeof(found_rtpmap_codecs));
+ last_rtpmap_codec = 0;
+ continue;
+ } else if (sscanf(a, "rtpmap: %u %[^/]/", &codec, mimeSubtype) == 2) {
+ /* We have a rtpmap to handle */
+
+ /* Note: should really look at the 'freq' and '#chans' params too */
+ /* Note: This should all be done in the context of the m= above */
+ if (!strncasecmp(mimeSubtype, "H26", 3) || !strncasecmp(mimeSubtype, "MP4", 3)) { /* Video */
+ if(ast_rtp_set_rtpmap_type(newvideortp, codec, "video", mimeSubtype, 0) != -1) {
+ if (debug)
+ ast_verbose("Found video description format %s for ID %d\n", mimeSubtype, codec);
+ found_rtpmap_codecs[last_rtpmap_codec] = codec;
+ last_rtpmap_codec++;
+ } else {
+ ast_rtp_unset_m_type(newvideortp, codec);
+ if (debug)
+ ast_verbose("Found unknown media description format %s for ID %d\n", mimeSubtype, codec);
+ }
+ } else if (!strncasecmp(mimeSubtype, "T140",4)) { /* Text */
+ if (p->trtp) {
+ /* ast_verbose("Adding t140 mimeSubtype to textrtp struct\n"); */
+ ast_rtp_set_rtpmap_type(newtextrtp, codec, "text", mimeSubtype, 0);
+ }
+ } else { /* Must be audio?? */
+ if(ast_rtp_set_rtpmap_type(newaudiortp, codec, "audio", mimeSubtype,
+ ast_test_flag(&p->flags[0], SIP_G726_NONSTANDARD) ? AST_RTP_OPT_G726_NONSTANDARD : 0) != -1) {
+ if (debug)
+ ast_verbose("Found audio description format %s for ID %d\n", mimeSubtype, codec);
+ found_rtpmap_codecs[last_rtpmap_codec] = codec;
+ last_rtpmap_codec++;
+ } else {
+ ast_rtp_unset_m_type(newaudiortp, codec);
+ if (debug)
+ ast_verbose("Found unknown media description format %s for ID %d\n", mimeSubtype, codec);
+ }
+ }
+
+ }
+ }
+
+ if (udptlportno != -1) {
+ int found = 0, x;
+
+ old = 0;
+
+ /* Scan trough the a= lines for T38 attributes and set apropriate fileds */
+ iterator = req->sdp_start;
+ while ((a = get_sdp_iterate(&iterator, req, "a"))[0] != '\0') {
+ if ((sscanf(a, "T38FaxMaxBuffer:%d", &x) == 1)) {
+ found = 1;
+ ast_debug(3, "MaxBufferSize:%d\n",x);
+ } else if ((sscanf(a, "T38MaxBitRate:%d", &x) == 1)) {
+ found = 1;
+ ast_debug(3,"T38MaxBitRate: %d\n",x);
+ switch (x) {
+ case 14400:
+ peert38capability |= T38FAX_RATE_14400 | T38FAX_RATE_12000 | T38FAX_RATE_9600 | T38FAX_RATE_7200 | T38FAX_RATE_4800 | T38FAX_RATE_2400;
+ break;
+ case 12000:
+ peert38capability |= T38FAX_RATE_12000 | T38FAX_RATE_9600 | T38FAX_RATE_7200 | T38FAX_RATE_4800 | T38FAX_RATE_2400;
+ break;
+ case 9600:
+ peert38capability |= T38FAX_RATE_9600 | T38FAX_RATE_7200 | T38FAX_RATE_4800 | T38FAX_RATE_2400;
+ break;
+ case 7200:
+ peert38capability |= T38FAX_RATE_7200 | T38FAX_RATE_4800 | T38FAX_RATE_2400;
+ break;
+ case 4800:
+ peert38capability |= T38FAX_RATE_4800 | T38FAX_RATE_2400;
+ break;
+ case 2400:
+ peert38capability |= T38FAX_RATE_2400;
+ break;
+ }
+ } else if ((sscanf(a, "T38FaxVersion:%d", &x) == 1)) {
+ found = 1;
+ ast_debug(3, "FaxVersion: %d\n",x);
+ if (x == 0)
+ peert38capability |= T38FAX_VERSION_0;
+ else if (x == 1)
+ peert38capability |= T38FAX_VERSION_1;
+ } else if ((sscanf(a, "T38FaxMaxDatagram:%d", &x) == 1)) {
+ found = 1;
+ ast_debug(3, "FaxMaxDatagram: %d\n",x);
+ ast_udptl_set_far_max_datagram(p->udptl, x);
+ ast_udptl_set_local_max_datagram(p->udptl, x);
+ } else if ((sscanf(a, "T38FaxFillBitRemoval:%d", &x) == 1)) {
+ found = 1;
+ ast_debug(3, "FillBitRemoval: %d\n",x);
+ if (x == 1)
+ peert38capability |= T38FAX_FILL_BIT_REMOVAL;
+ } else if ((sscanf(a, "T38FaxTranscodingMMR:%d", &x) == 1)) {
+ found = 1;
+ ast_debug(3, "Transcoding MMR: %d\n",x);
+ if (x == 1)
+ peert38capability |= T38FAX_TRANSCODING_MMR;
+ }
+ if ((sscanf(a, "T38FaxTranscodingJBIG:%d", &x) == 1)) {
+ found = 1;
+ ast_debug(3, "Transcoding JBIG: %d\n",x);
+ if (x == 1)
+ peert38capability |= T38FAX_TRANSCODING_JBIG;
+ } else if ((sscanf(a, "T38FaxRateManagement:%255s", s) == 1)) {
+ found = 1;
+ ast_debug(3, "RateManagement: %s\n", s);
+ if (!strcasecmp(s, "localTCF"))
+ peert38capability |= T38FAX_RATE_MANAGEMENT_LOCAL_TCF;
+ else if (!strcasecmp(s, "transferredTCF"))
+ peert38capability |= T38FAX_RATE_MANAGEMENT_TRANSFERED_TCF;
+ } else if ((sscanf(a, "T38FaxUdpEC:%255s", s) == 1)) {
+ found = 1;
+ ast_debug(3, "UDP EC: %s\n", s);
+ if (!strcasecmp(s, "t38UDPRedundancy")) {
+ peert38capability |= T38FAX_UDP_EC_REDUNDANCY;
+ ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY);
+ } else if (!strcasecmp(s, "t38UDPFEC")) {
+ peert38capability |= T38FAX_UDP_EC_FEC;
+ ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_FEC);
+ } else {
+ peert38capability |= T38FAX_UDP_EC_NONE;
+ ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_NONE);
+ }
+ }
+ }
+ if (found) { /* Some cisco equipment returns nothing beside c= and m= lines in 200 OK T38 SDP */
+ p->t38.peercapability = peert38capability;
+ p->t38.jointcapability = (peert38capability & 255); /* Put everything beside supported speeds settings */
+ peert38capability &= (T38FAX_RATE_14400 | T38FAX_RATE_12000 | T38FAX_RATE_9600 | T38FAX_RATE_7200 | T38FAX_RATE_4800 | T38FAX_RATE_2400);
+ p->t38.jointcapability |= (peert38capability & p->t38.capability); /* Put the lower of our's and peer's speed */
+ }
+ if (debug)
+ ast_debug(1, "Our T38 capability = (%d), peer T38 capability (%d), joint T38 capability (%d)\n",
+ p->t38.capability,
+ p->t38.peercapability,
+ p->t38.jointcapability);
+ } else {
+ p->t38.state = T38_DISABLED;
+ ast_debug(3, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
+ }
+
+ /* Now gather all of the codecs that we are asked for: */
+ ast_rtp_get_current_formats(newaudiortp, &peercapability, &peernoncodeccapability);
+ ast_rtp_get_current_formats(newvideortp, &vpeercapability, &vpeernoncodeccapability);
+ ast_rtp_get_current_formats(newtextrtp, &tpeercapability, &tpeernoncodeccapability);
+
+ newjointcapability = p->capability & (peercapability | vpeercapability | tpeercapability);
+ newpeercapability = (peercapability | vpeercapability | tpeercapability);
+ newnoncodeccapability = p->noncodeccapability & peernoncodeccapability;
+
+
+ if (debug) {
+ /* shame on whoever coded this.... */
+ char s1[BUFSIZ], s2[BUFSIZ], s3[BUFSIZ], s4[BUFSIZ], s5[BUFSIZ];
+
+ ast_verbose("Capabilities: us - %s, peer - audio=%s/video=%s/text=%s, combined - %s\n",
+ ast_getformatname_multiple(s1, BUFSIZ, p->capability),
+ ast_getformatname_multiple(s2, BUFSIZ, peercapability),
+ ast_getformatname_multiple(s3, BUFSIZ, vpeercapability),
+ ast_getformatname_multiple(s4, BUFSIZ, tpeercapability),
+ ast_getformatname_multiple(s5, BUFSIZ, newjointcapability));
+
+ ast_verbose("Non-codec capabilities (dtmf): us - %s, peer - %s, combined - %s\n",
+ ast_rtp_lookup_mime_multiple(s1, BUFSIZ, p->noncodeccapability, 0, 0),
+ ast_rtp_lookup_mime_multiple(s2, BUFSIZ, peernoncodeccapability, 0, 0),
+ ast_rtp_lookup_mime_multiple(s3, BUFSIZ, newnoncodeccapability, 0, 0));
+ }
+ if (!newjointcapability) {
+ /* If T.38 was not negotiated either, totally bail out... */
+ if (!p->t38.jointcapability) {
+ ast_log(LOG_NOTICE, "No compatible codecs, not accepting this offer!\n");
+ /* Do NOT Change current setting */
+ return -1;
+ } else {
+ ast_debug(3, "Have T.38 but no audio codecs, accepting offer anyway\n");
+ return 0;
+ }
+ }
+
+ /* We are now ready to change the sip session and p->rtp and p->vrtp with the offered codecs, since
+ they are acceptable */
+ p->jointcapability = newjointcapability; /* Our joint codec profile for this call */
+ p->peercapability = newpeercapability; /* The other sides capability in latest offer */
+ p->jointnoncodeccapability = newnoncodeccapability; /* DTMF capabilities */
+
+ ast_rtp_pt_copy(p->rtp, newaudiortp);
+ if (p->vrtp)
+ ast_rtp_pt_copy(p->vrtp, newvideortp);
+ if (p->trtp)
+ ast_rtp_pt_copy(p->trtp, newtextrtp);
+
+ if (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) {
+ ast_clear_flag(&p->flags[0], SIP_DTMF);
+ if (newnoncodeccapability & AST_RTP_DTMF) {
+ /* XXX Would it be reasonable to drop the DSP at this point? XXX */
+ ast_set_flag(&p->flags[0], SIP_DTMF_RFC2833);
+ /* Since RFC2833 is now negotiated we need to change some properties of the RTP stream */
+ ast_rtp_setdtmf(p->rtp, 1);
+ ast_rtp_setdtmfcompensate(p->rtp, ast_test_flag(&p->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
+ } else {
+ ast_set_flag(&p->flags[0], SIP_DTMF_INBAND);
+ }
+ }
+
+ /* Setup audio port number */
+ if (p->rtp && sin.sin_port) {
+ ast_rtp_set_peer(p->rtp, &sin);
+ if (debug)
+ ast_verbose("Peer audio RTP is at port %s:%d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+ }
+
+ /* Setup video port number */
+ if (p->vrtp && vsin.sin_port) {
+ ast_rtp_set_peer(p->vrtp, &vsin);
+ if (debug)
+ ast_verbose("Peer video RTP is at port %s:%d\n", ast_inet_ntoa(vsin.sin_addr), ntohs(vsin.sin_port));
+ }
+
+ /* Setup text port number */
+ if (p->trtp && tsin.sin_port) {
+ ast_rtp_set_peer(p->trtp, &tsin);
+ if (debug)
+ ast_verbose("Peer text RTP is at port %s:%d\n", ast_inet_ntoa(tsin.sin_addr), ntohs(tsin.sin_port));
+ }
+
+ /* Ok, we're going with this offer */
+ ast_debug(2, "We're settling with these formats: %s\n", ast_getformatname_multiple(buf, BUFSIZ, p->jointcapability));
+
+ if (!p->owner) /* There's no open channel owning us so we can return here. For a re-invite or so, we proceed */
+ return 0;
+
+ ast_debug(4, "We have an owner, now see if we need to change this call\n");
+
+ if (!(p->owner->nativeformats & p->jointcapability) && (p->jointcapability & AST_FORMAT_AUDIO_MASK)) {
+ if (debug) {
+ char s1[BUFSIZ], s2[BUFSIZ];
+ ast_debug(1, "Oooh, we need to change our audio formats since our peer supports only %s and not %s\n",
+ ast_getformatname_multiple(s1, BUFSIZ, p->jointcapability),
+ ast_getformatname_multiple(s2, BUFSIZ, p->owner->nativeformats));
+ }
+ p->owner->nativeformats = ast_codec_choose(&p->prefs, p->jointcapability, 1) | (p->capability & vpeercapability) | (p->capability & tpeercapability);
+ ast_set_read_format(p->owner, p->owner->readformat);
+ ast_set_write_format(p->owner, p->owner->writeformat);
+ }
+
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) && sin.sin_addr.s_addr && (!sendonly || sendonly == -1)) {
+ ast_queue_control(p->owner, AST_CONTROL_UNHOLD);
+ /* Activate a re-invite */
+ ast_queue_frame(p->owner, &ast_null_frame);
+ /* Queue Manager Unhold event */
+ append_history(p, "Unhold", "%s", req->data);
+ if (global_callevents)
+ manager_event(EVENT_FLAG_CALL, "Hold",
+ "Status: Off\r\n"
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n",
+ p->owner->name,
+ p->owner->uniqueid);
+ if (global_notifyhold)
+ sip_peer_hold(p, FALSE);
+ ast_clear_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD); /* Clear both flags */
+ } else if (!sin.sin_addr.s_addr || (sendonly && sendonly != -1)) {
+ int already_on_hold = ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD);
+ ast_queue_control_data(p->owner, AST_CONTROL_HOLD,
+ S_OR(p->mohsuggest, NULL),
+ !ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
+ if (sendonly)
+ ast_rtp_stop(p->rtp);
+ /* RTCP needs to go ahead, even if we're on hold!!! */
+ /* Activate a re-invite */
+ ast_queue_frame(p->owner, &ast_null_frame);
+ /* Queue Manager Hold event */
+ append_history(p, "Hold", "%s", req->data);
+ if (global_callevents && !ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) {
+ manager_event(EVENT_FLAG_CALL, "Hold",
+ "Status: On\r\n"
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n",
+ p->owner->name,
+ p->owner->uniqueid);
+ }
+ if (sendonly == 1) /* One directional hold (sendonly/recvonly) */
+ ast_set_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD_ONEDIR);
+ else if (sendonly == 2) /* Inactive stream */
+ ast_set_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD_INACTIVE);
+ else
+ ast_set_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD_ACTIVE);
+ if (global_notifyhold && !already_on_hold)
+ sip_peer_hold(p, TRUE);
+ }
+
+ return 0;
+}
+
+
+/*! \brief Add header to SIP message */
+static int add_header(struct sip_request *req, const char *var, const char *value)
+{
+ int maxlen = sizeof(req->data) - 4 - req->len; /* 4 bytes are for two \r\n ? */
+
+ if (req->headers == SIP_MAX_HEADERS) {
+ ast_log(LOG_WARNING, "Out of SIP header space\n");
+ return -1;
+ }
+
+ if (req->lines) {
+ ast_log(LOG_WARNING, "Can't add more headers when lines have been added\n");
+ return -1;
+ }
+
+ if (maxlen <= 0) {
+ ast_log(LOG_WARNING, "Out of space, can't add anymore (%s:%s)\n", var, value);
+ return -1;
+ }
+
+ req->header[req->headers] = req->data + req->len;
+
+ if (compactheaders)
+ var = find_alias(var, var);
+
+ snprintf(req->header[req->headers], maxlen, "%s: %s\r\n", var, value);
+ req->len += strlen(req->header[req->headers]);
+ req->headers++;
+
+ return 0;
+}
+
+/*! \brief Add 'Content-Length' header to SIP message */
+static int add_header_contentLength(struct sip_request *req, int len)
+{
+ char clen[10];
+
+ snprintf(clen, sizeof(clen), "%d", len);
+ return add_header(req, "Content-Length", clen);
+}
+
+/*! \brief Add content (not header) to SIP message */
+static int add_line(struct sip_request *req, const char *line)
+{
+ if (req->lines == SIP_MAX_LINES) {
+ ast_log(LOG_WARNING, "Out of SIP line space\n");
+ return -1;
+ }
+ if (!req->lines) {
+ /* Add extra empty return */
+ ast_copy_string(req->data + req->len, "\r\n", sizeof(req->data) - req->len);
+ req->len += strlen(req->data + req->len);
+ }
+ if (req->len >= sizeof(req->data) - 4) {
+ ast_log(LOG_WARNING, "Out of space, can't add anymore\n");
+ return -1;
+ }
+ req->line[req->lines] = req->data + req->len;
+ snprintf(req->line[req->lines], sizeof(req->data) - req->len, "%s", line);
+ req->len += strlen(req->line[req->lines]);
+ req->lines++;
+ return 0;
+}
+
+/*! \brief Copy one header field from one request to another */
+static int copy_header(struct sip_request *req, const struct sip_request *orig, const char *field)
+{
+ const char *tmp = get_header(orig, field);
+
+ if (!ast_strlen_zero(tmp)) /* Add what we're responding to */
+ return add_header(req, field, tmp);
+ ast_log(LOG_NOTICE, "No field '%s' present to copy\n", field);
+ return -1;
+}
+
+/*! \brief Copy all headers from one request to another */
+static int copy_all_header(struct sip_request *req, const struct sip_request *orig, const char *field)
+{
+ int start = 0;
+ int copied = 0;
+ for (;;) {
+ const char *tmp = __get_header(orig, field, &start);
+
+ if (ast_strlen_zero(tmp))
+ break;
+ /* Add what we're responding to */
+ add_header(req, field, tmp);
+ copied++;
+ }
+ return copied ? 0 : -1;
+}
+
+/*! \brief Copy SIP VIA Headers from the request to the response
+\note If the client indicates that it wishes to know the port we received from,
+ it adds ;rport without an argument to the topmost via header. We need to
+ add the port number (from our point of view) to that parameter.
+\verbatim
+ We always add ;received=<ip address> to the topmost via header.
+\endverbatim
+ Received: RFC 3261, rport RFC 3581 */
+static int copy_via_headers(struct sip_pvt *p, struct sip_request *req, const struct sip_request *orig, const char *field)
+{
+ int copied = 0;
+ int start = 0;
+
+ for (;;) {
+ char new[256];
+ const char *oh = __get_header(orig, field, &start);
+
+ if (ast_strlen_zero(oh))
+ break;
+
+ if (!copied) { /* Only check for empty rport in topmost via header */
+ char leftmost[256], *others, *rport;
+
+ /* Only work on leftmost value */
+ ast_copy_string(leftmost, oh, sizeof(leftmost));
+ others = strchr(leftmost, ',');
+ if (others)
+ *others++ = '\0';
+
+ /* Find ;rport; (empty request) */
+ rport = strstr(leftmost, ";rport");
+ if (rport && *(rport+6) == '=')
+ rport = NULL; /* We already have a parameter to rport */
+
+ /* Check rport if NAT=yes or NAT=rfc3581 (which is the default setting) */
+ if (rport && ((ast_test_flag(&p->flags[0], SIP_NAT) == SIP_NAT_ALWAYS) || (ast_test_flag(&p->flags[0], SIP_NAT) == SIP_NAT_RFC3581))) {
+ /* We need to add received port - rport */
+ char *end;
+
+ rport = strstr(leftmost, ";rport");
+
+ if (rport) {
+ end = strchr(rport + 1, ';');
+ if (end)
+ memmove(rport, end, strlen(end) + 1);
+ else
+ *rport = '\0';
+ }
+
+ /* Add rport to first VIA header if requested */
+ snprintf(new, sizeof(new), "%s;received=%s;rport=%d%s%s",
+ leftmost, ast_inet_ntoa(p->recv.sin_addr),
+ ntohs(p->recv.sin_port),
+ others ? "," : "", others ? others : "");
+ } else {
+ /* We should *always* add a received to the topmost via */
+ snprintf(new, sizeof(new), "%s;received=%s%s%s",
+ leftmost, ast_inet_ntoa(p->recv.sin_addr),
+ others ? "," : "", others ? others : "");
+ }
+ oh = new; /* the header to copy */
+ } /* else add the following via headers untouched */
+ add_header(req, field, oh);
+ copied++;
+ }
+ if (!copied) {
+ ast_log(LOG_NOTICE, "No header field '%s' present to copy\n", field);
+ return -1;
+ }
+ return 0;
+}
+
+/*! \brief Add route header into request per learned route */
+static void add_route(struct sip_request *req, struct sip_route *route)
+{
+ char r[BUFSIZ*2], *p;
+ int n, rem = sizeof(r);
+
+ if (!route)
+ return;
+
+ p = r;
+ for (;route ; route = route->next) {
+ n = strlen(route->hop);
+ if (rem < n+3) /* we need room for ",<route>" */
+ break;
+ if (p != r) { /* add a separator after fist route */
+ *p++ = ',';
+ --rem;
+ }
+ *p++ = '<';
+ ast_copy_string(p, route->hop, rem); /* cannot fail */
+ p += n;
+ *p++ = '>';
+ rem -= (n+2);
+ }
+ *p = '\0';
+ add_header(req, "Route", r);
+}
+
+/*! \brief Set destination from SIP URI */
+static void set_destination(struct sip_pvt *p, char *uri)
+{
+ char *h, *maddr, hostname[256];
+ int port, hn;
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ int debug=sip_debug_test_pvt(p);
+
+ /* Parse uri to h (host) and port - uri is already just the part inside the <> */
+ /* general form we are expecting is sip[s]:username[:password][;parameter]@host[:port][;...] */
+
+ if (debug)
+ ast_verbose("set_destination: Parsing <%s> for address/port to send to\n", uri);
+
+ /* Find and parse hostname */
+ h = strchr(uri, '@');
+ if (h)
+ ++h;
+ else {
+ h = uri;
+ if (strncasecmp(h, "sip:", 4) == 0)
+ h += 4;
+ else if (strncasecmp(h, "sips:", 5) == 0)
+ h += 5;
+ }
+ hn = strcspn(h, ":;>") + 1;
+ if (hn > sizeof(hostname))
+ hn = sizeof(hostname);
+ ast_copy_string(hostname, h, hn);
+ /* XXX bug here if string has been trimmed to sizeof(hostname) */
+ h += hn - 1;
+
+ /* Is "port" present? if not default to STANDARD_SIP_PORT */
+ if (*h == ':') {
+ /* Parse port */
+ ++h;
+ port = strtol(h, &h, 10);
+ }
+ else
+ port = STANDARD_SIP_PORT;
+
+ /* Got the hostname:port - but maybe there's a "maddr=" to override address? */
+ maddr = strstr(h, "maddr=");
+ if (maddr) {
+ maddr += 6;
+ hn = strspn(maddr, "0123456789.") + 1;
+ if (hn > sizeof(hostname))
+ hn = sizeof(hostname);
+ ast_copy_string(hostname, maddr, hn);
+ }
+
+ hp = ast_gethostbyname(hostname, &ahp);
+ if (hp == NULL) {
+ ast_log(LOG_WARNING, "Can't find address for host '%s'\n", hostname);
+ return;
+ }
+ p->sa.sin_family = AF_INET;
+ memcpy(&p->sa.sin_addr, hp->h_addr, sizeof(p->sa.sin_addr));
+ p->sa.sin_port = htons(port);
+ if (debug)
+ ast_verbose("set_destination: set destination to %s, port %d\n", ast_inet_ntoa(p->sa.sin_addr), port);
+}
+
+/*! \brief Initialize SIP response, based on SIP request */
+static int init_resp(struct sip_request *resp, const char *msg)
+{
+ /* Initialize a response */
+ memset(resp, 0, sizeof(*resp));
+ resp->method = SIP_RESPONSE;
+ resp->header[0] = resp->data;
+ snprintf(resp->header[0], sizeof(resp->data), "SIP/2.0 %s\r\n", msg);
+ resp->len = strlen(resp->header[0]);
+ resp->headers++;
+ return 0;
+}
+
+/*! \brief Initialize SIP request */
+static int init_req(struct sip_request *req, int sipmethod, const char *recip)
+{
+ /* Initialize a request */
+ memset(req, 0, sizeof(*req));
+ req->method = sipmethod;
+ req->header[0] = req->data;
+ snprintf(req->header[0], sizeof(req->data), "%s %s SIP/2.0\r\n", sip_methods[sipmethod].text, recip);
+ req->len = strlen(req->header[0]);
+ req->headers++;
+ return 0;
+}
+
+
+/*! \brief Prepare SIP response packet */
+static int respprep(struct sip_request *resp, struct sip_pvt *p, const char *msg, const struct sip_request *req)
+{
+ char newto[256];
+ const char *ot;
+
+ init_resp(resp, msg);
+ copy_via_headers(p, resp, req, "Via");
+ if (msg[0] == '1' || msg[0] == '2')
+ copy_all_header(resp, req, "Record-Route");
+ copy_header(resp, req, "From");
+ ot = get_header(req, "To");
+ if (!strcasestr(ot, "tag=") && strncmp(msg, "100", 3)) {
+ /* Add the proper tag if we don't have it already. If they have specified
+ their tag, use it. Otherwise, use our own tag */
+ if (!ast_strlen_zero(p->theirtag) && ast_test_flag(&p->flags[0], SIP_OUTGOING))
+ snprintf(newto, sizeof(newto), "%s;tag=%s", ot, p->theirtag);
+ else if (p->tag && !ast_test_flag(&p->flags[0], SIP_OUTGOING))
+ snprintf(newto, sizeof(newto), "%s;tag=%s", ot, p->tag);
+ else
+ ast_copy_string(newto, ot, sizeof(newto));
+ ot = newto;
+ }
+ add_header(resp, "To", ot);
+ copy_header(resp, req, "Call-ID");
+ copy_header(resp, req, "CSeq");
+ if (!ast_strlen_zero(global_useragent))
+ add_header(resp, "User-Agent", global_useragent);
+ add_header(resp, "Allow", ALLOWED_METHODS);
+ add_header(resp, "Supported", SUPPORTED_EXTENSIONS);
+
+ /* Add Session-Timers related headers if the feature is active for this session */
+ if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_active_peer_ua == TRUE) {
+ char se_hdr[256];
+ snprintf(se_hdr, sizeof(se_hdr), "%d;refresher=%s", p->stimer->st_interval,
+ strefresher2str(p->stimer->st_ref));
+ add_header(resp, "Require", "timer");
+ add_header(resp, "Session-Expires", se_hdr);
+ }
+
+ if (msg[0] == '2' && (p->method == SIP_SUBSCRIBE || p->method == SIP_REGISTER)) {
+ /* For registration responses, we also need expiry and
+ contact info */
+ char tmp[256];
+
+ snprintf(tmp, sizeof(tmp), "%d", p->expiry);
+ add_header(resp, "Expires", tmp);
+ if (p->expiry) { /* Only add contact if we have an expiry time */
+ char contact[BUFSIZ];
+ snprintf(contact, sizeof(contact), "%s;expires=%d", p->our_contact, p->expiry);
+ add_header(resp, "Contact", contact); /* Not when we unregister */
+ }
+ } else if (msg[0] != '4' && !ast_strlen_zero(p->our_contact)) {
+ add_header(resp, "Contact", p->our_contact);
+ }
+
+ if (!ast_strlen_zero(p->url)) {
+ add_header(resp, "Access-URL", p->url);
+ ast_string_field_set(p, url, NULL);
+ }
+
+ return 0;
+}
+
+/*! \brief Initialize a SIP request message (not the initial one in a dialog) */
+static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, int seqno, int newbranch)
+{
+ struct sip_request *orig = &p->initreq;
+ char stripped[80];
+ char tmp[80];
+ char newto[256];
+ const char *c;
+ const char *ot, *of;
+ int is_strict = FALSE; /*!< Strict routing flag */
+ int is_outbound = ast_test_flag(&p->flags[0], SIP_OUTGOING); /* Session direction */
+
+ memset(req, 0, sizeof(struct sip_request));
+
+ snprintf(p->lastmsg, sizeof(p->lastmsg), "Tx: %s", sip_methods[sipmethod].text);
+
+ if (!seqno) {
+ p->ocseq++;
+ seqno = p->ocseq;
+ }
+
+ if (newbranch) {
+ p->branch ^= ast_random();
+ build_via(p);
+ }
+
+ /* Check for strict or loose router */
+ if (p->route && !ast_strlen_zero(p->route->hop) && strstr(p->route->hop,";lr") == NULL) {
+ is_strict = TRUE;
+ if (sipdebug)
+ ast_debug(1, "Strict routing enforced for session %s\n", p->callid);
+ }
+
+ if (sipmethod == SIP_CANCEL)
+ c = p->initreq.rlPart2; /* Use original URI */
+ else if (sipmethod == SIP_ACK) {
+ /* Use URI from Contact: in 200 OK (if INVITE)
+ (we only have the contacturi on INVITEs) */
+ if (!ast_strlen_zero(p->okcontacturi))
+ c = is_strict ? p->route->hop : p->okcontacturi;
+ else
+ c = p->initreq.rlPart2;
+ } else if (!ast_strlen_zero(p->okcontacturi))
+ c = is_strict ? p->route->hop : p->okcontacturi; /* Use for BYE or REINVITE */
+ else if (!ast_strlen_zero(p->uri))
+ c = p->uri;
+ else {
+ char *n;
+ /* We have no URI, use To: or From: header as URI (depending on direction) */
+ ast_copy_string(stripped, get_header(orig, is_outbound ? "To" : "From"),
+ sizeof(stripped));
+ n = get_in_brackets(stripped);
+ c = remove_uri_parameters(n);
+ }
+ init_req(req, sipmethod, c);
+
+ snprintf(tmp, sizeof(tmp), "%d %s", seqno, sip_methods[sipmethod].text);
+
+ add_header(req, "Via", p->via);
+ if (p->route) {
+ set_destination(p, p->route->hop);
+ add_route(req, is_strict ? p->route->next : p->route);
+ }
+ add_header(req, "Max-Forwards", DEFAULT_MAX_FORWARDS);
+
+ ot = get_header(orig, "To");
+ of = get_header(orig, "From");
+
+ /* Add tag *unless* this is a CANCEL, in which case we need to send it exactly
+ as our original request, including tag (or presumably lack thereof) */
+ if (!strcasestr(ot, "tag=") && sipmethod != SIP_CANCEL) {
+ /* Add the proper tag if we don't have it already. If they have specified
+ their tag, use it. Otherwise, use our own tag */
+ if (is_outbound && !ast_strlen_zero(p->theirtag))
+ snprintf(newto, sizeof(newto), "%s;tag=%s", ot, p->theirtag);
+ else if (!is_outbound)
+ snprintf(newto, sizeof(newto), "%s;tag=%s", ot, p->tag);
+ else
+ snprintf(newto, sizeof(newto), "%s", ot);
+ ot = newto;
+ }
+
+ if (is_outbound) {
+ add_header(req, "From", of);
+ add_header(req, "To", ot);
+ } else {
+ add_header(req, "From", ot);
+ add_header(req, "To", of);
+ }
+ /* Do not add Contact for MESSAGE, BYE and Cancel requests */
+ if (sipmethod != SIP_BYE && sipmethod != SIP_CANCEL && sipmethod != SIP_MESSAGE)
+ add_header(req, "Contact", p->our_contact);
+
+ copy_header(req, orig, "Call-ID");
+ add_header(req, "CSeq", tmp);
+
+ if (!ast_strlen_zero(global_useragent))
+ add_header(req, "User-Agent", global_useragent);
+
+ if (!ast_strlen_zero(p->rpid))
+ add_header(req, "Remote-Party-ID", p->rpid);
+
+ if (!ast_strlen_zero(p->url)) {
+ add_header(req, "Access-URL", p->url);
+ ast_string_field_set(p, url, NULL);
+ }
+
+ /* Add Session-Timers related headers if the feature is active for this session */
+ if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_active_peer_ua == TRUE) {
+ char se_hdr[256];
+ snprintf(se_hdr, sizeof(se_hdr), "%d;refresher=%s", p->stimer->st_interval,
+ strefresher2str(p->stimer->st_ref));
+ add_header(req, "Require", "timer");
+ add_header(req, "Session-Expires", se_hdr);
+ snprintf(se_hdr, sizeof(se_hdr), "%d", st_get_se(p, FALSE));
+ add_header(req, "Min-SE", se_hdr);
+ }
+
+ return 0;
+}
+
+/*! \brief Base transmit response function */
+static int __transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable)
+{
+ struct sip_request resp;
+ int seqno = 0;
+
+ if (reliable && (sscanf(get_header(req, "CSeq"), "%d ", &seqno) != 1)) {
+ ast_log(LOG_WARNING, "Unable to determine sequence number from '%s'\n", get_header(req, "CSeq"));
+ return -1;
+ }
+ respprep(&resp, p, msg, req);
+ add_header_contentLength(&resp, 0);
+ /* If we are cancelling an incoming invite for some reason, add information
+ about the reason why we are doing this in clear text */
+ if (p->method == SIP_INVITE && msg[0] != '1' && p->owner && p->owner->hangupcause) {
+ char buf[10];
+
+ add_header(&resp, "X-Asterisk-HangupCause", ast_cause2str(p->owner->hangupcause));
+ snprintf(buf, sizeof(buf), "%d", p->owner->hangupcause);
+ add_header(&resp, "X-Asterisk-HangupCauseCode", buf);
+ }
+ return send_response(p, &resp, reliable, seqno);
+}
+
+static int temp_pvt_init(void *data)
+{
+ struct sip_pvt *p = data;
+
+ p->do_history = 0; /* XXX do we need it ? isn't already all 0 ? */
+ return ast_string_field_init(p, 512);
+}
+
+static void temp_pvt_cleanup(void *data)
+{
+ struct sip_pvt *p = data;
+
+ ast_string_field_free_memory(p);
+
+ ast_free(data);
+}
+
+/*! \brief Transmit response, no retransmits, using a temporary pvt structure */
+static int transmit_response_using_temp(ast_string_field callid, struct sockaddr_in *sin, int useglobal_nat, const int intended_method, const struct sip_request *req, const char *msg)
+{
+ struct sip_pvt *p = NULL;
+
+ if (!(p = ast_threadstorage_get(&ts_temp_pvt, sizeof(*p)))) {
+ ast_log(LOG_NOTICE, "Failed to get temporary pvt\n");
+ return -1;
+ }
+
+ /* XXX the structure may be dirty from previous usage.
+ * Here we should state clearly how we should reinitialize it
+ * before using it.
+ * E.g. certainly the threadstorage should be left alone,
+ * but other thihngs such as flags etc. maybe need cleanup ?
+ */
+
+ /* Initialize the bare minimum */
+ p->method = intended_method;
+
+ if (!sin)
+ p->ourip = internip;
+ else {
+ p->sa = *sin;
+ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip);
+ }
+
+ p->branch = ast_random();
+ make_our_tag(p->tag, sizeof(p->tag));
+ p->ocseq = INITIAL_CSEQ;
+
+ if (useglobal_nat && sin) {
+ ast_copy_flags(&p->flags[0], &global_flags[0], SIP_NAT);
+ p->recv = *sin;
+ do_setnat(p, ast_test_flag(&p->flags[0], SIP_NAT) & SIP_NAT_ROUTE);
+ }
+
+ ast_string_field_set(p, fromdomain, default_fromdomain);
+ build_via(p);
+ ast_string_field_set(p, callid, callid);
+
+ p->socket.lock = req->socket.lock;
+ p->socket.type = req->socket.type;
+ p->socket.fd = req->socket.fd;
+ p->socket.port = req->socket.port;
+ p->socket.ser = req->socket.ser;
+
+ /* Use this temporary pvt structure to send the message */
+ __transmit_response(p, msg, req, XMIT_UNRELIABLE);
+
+ /* Free the string fields, but not the pool space */
+ ast_string_field_init(p, 0);
+
+ return 0;
+}
+
+/*! \brief Transmit response, no retransmits */
+static int transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req)
+{
+ return __transmit_response(p, msg, req, XMIT_UNRELIABLE);
+}
+
+/*! \brief Transmit response, no retransmits */
+static int transmit_response_with_unsupported(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *unsupported)
+{
+ struct sip_request resp;
+ respprep(&resp, p, msg, req);
+ append_date(&resp);
+ add_header(&resp, "Unsupported", unsupported);
+ add_header_contentLength(&resp, 0);
+ return send_response(p, &resp, XMIT_UNRELIABLE, 0);
+}
+
+/*! \brief Transmit 422 response with Min-SE header (Session-Timers) */
+static int transmit_response_with_minse(struct sip_pvt *p, const char *msg, const struct sip_request *req, int minse_int)
+{
+ struct sip_request resp;
+ char minse_str[20];
+
+ respprep(&resp, p, msg, req);
+ append_date(&resp);
+
+ snprintf(minse_str, sizeof(minse_str), "%d", minse_int);
+ add_header(&resp, "Min-SE", minse_str);
+
+ add_header_contentLength(&resp, 0);
+ return send_response(p, &resp, XMIT_UNRELIABLE, 0);
+}
+
+
+/*! \brief Transmit response, Make sure you get an ACK
+ This is only used for responses to INVITEs, where we need to make sure we get an ACK
+*/
+static int transmit_response_reliable(struct sip_pvt *p, const char *msg, const struct sip_request *req)
+{
+ return __transmit_response(p, msg, req, XMIT_CRITICAL);
+}
+
+/*! \brief Append date to SIP message */
+static void append_date(struct sip_request *req)
+{
+ char tmpdat[256];
+ struct tm tm;
+ time_t t = time(NULL);
+
+ gmtime_r(&t, &tm);
+ strftime(tmpdat, sizeof(tmpdat), "%a, %d %b %Y %T GMT", &tm);
+ add_header(req, "Date", tmpdat);
+}
+
+/*! \brief Append date and content length before transmitting response */
+static int transmit_response_with_date(struct sip_pvt *p, const char *msg, const struct sip_request *req)
+{
+ struct sip_request resp;
+ respprep(&resp, p, msg, req);
+ append_date(&resp);
+ add_header_contentLength(&resp, 0);
+ return send_response(p, &resp, XMIT_UNRELIABLE, 0);
+}
+
+/*! \brief Append Accept header, content length before transmitting response */
+static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable)
+{
+ struct sip_request resp;
+ respprep(&resp, p, msg, req);
+ add_header(&resp, "Accept", "application/sdp");
+ add_header_contentLength(&resp, 0);
+ return send_response(p, &resp, reliable, 0);
+}
+
+/*! \brief Respond with authorization request */
+static int transmit_response_with_auth(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *randdata, enum xmittype reliable, const char *header, int stale)
+{
+ struct sip_request resp;
+ char tmp[512];
+ int seqno = 0;
+
+ if (reliable && (sscanf(get_header(req, "CSeq"), "%d ", &seqno) != 1)) {
+ ast_log(LOG_WARNING, "Unable to determine sequence number from '%s'\n", get_header(req, "CSeq"));
+ return -1;
+ }
+ /* Stale means that they sent us correct authentication, but
+ based it on an old challenge (nonce) */
+ snprintf(tmp, sizeof(tmp), "Digest algorithm=MD5, realm=\"%s\", nonce=\"%s\"%s", global_realm, randdata, stale ? ", stale=true" : "");
+ respprep(&resp, p, msg, req);
+ add_header(&resp, header, tmp);
+ add_header_contentLength(&resp, 0);
+ append_history(p, "AuthChal", "Auth challenge sent for %s - nc %d", p->username, p->noncecount);
+ return send_response(p, &resp, reliable, seqno);
+}
+
+/*! \brief Add text body to SIP message */
+static int add_text(struct sip_request *req, const char *text)
+{
+ /* XXX Convert \n's to \r\n's XXX */
+ add_header(req, "Content-Type", "text/plain");
+ add_header_contentLength(req, strlen(text));
+ add_line(req, text);
+ return 0;
+}
+
+/*! \brief Add DTMF INFO tone to sip message
+ Mode = 0 for application/dtmf-relay (Cisco)
+ 1 for application/dtmf
+*/
+static int add_digit(struct sip_request *req, char digit, unsigned int duration, int mode)
+{
+ char tmp[256];
+ int event;
+ if (mode) {
+ /* Application/dtmf short version used by some implementations */
+ if (digit == '*')
+ event = 10;
+ else if (digit == '#')
+ event = 11;
+ else if ((digit >= 'A') && (digit <= 'D'))
+ event = 12 + digit - 'A';
+ else
+ event = atoi(&digit);
+ snprintf(tmp, sizeof(tmp), "%d\r\n", event);
+ add_header(req, "Content-Type", "application/dtmf");
+ add_header_contentLength(req, strlen(tmp));
+ add_line(req, tmp);
+ } else {
+ /* Application/dtmf-relay as documented by Cisco */
+ snprintf(tmp, sizeof(tmp), "Signal=%c\r\nDuration=%u\r\n", digit, duration);
+ add_header(req, "Content-Type", "application/dtmf-relay");
+ add_header_contentLength(req, strlen(tmp));
+ add_line(req, tmp);
+ }
+ return 0;
+}
+
+/*! \brief add XML encoded media control with update
+ \note XML: The only way to turn 0 bits of information into a few hundred. (markster) */
+static int add_vidupdate(struct sip_request *req)
+{
+ const char *xml_is_a_huge_waste_of_space =
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n"
+ " <media_control>\r\n"
+ " <vc_primitive>\r\n"
+ " <to_encoder>\r\n"
+ " <picture_fast_update>\r\n"
+ " </picture_fast_update>\r\n"
+ " </to_encoder>\r\n"
+ " </vc_primitive>\r\n"
+ " </media_control>\r\n";
+ add_header(req, "Content-Type", "application/media_control+xml");
+ add_header_contentLength(req, strlen(xml_is_a_huge_waste_of_space));
+ add_line(req, xml_is_a_huge_waste_of_space);
+ return 0;
+}
+
+/*! \brief Add codec offer to SDP offer/answer body in INVITE or 200 OK */
+static void add_codec_to_sdp(const struct sip_pvt *p, int codec, int sample_rate,
+ struct ast_str **m_buf, struct ast_str **a_buf,
+ int debug, int *min_packet_size)
+{
+ int rtp_code;
+ struct ast_format_list fmt;
+
+
+ if (debug)
+ ast_verbose("Adding codec 0x%x (%s) to SDP\n", codec, ast_getformatname(codec));
+ if ((rtp_code = ast_rtp_lookup_code(p->rtp, 1, codec)) == -1)
+ return;
+
+ if (p->rtp) {
+ struct ast_codec_pref *pref = ast_rtp_codec_getpref(p->rtp);
+ fmt = ast_codec_pref_getsize(pref, codec);
+ } else /* I dont see how you couldn't have p->rtp, but good to check for and error out if not there like earlier code */
+ return;
+ ast_str_append(m_buf, 0, " %d", rtp_code);
+ ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%d\r\n", rtp_code,
+ ast_rtp_lookup_mime_subtype(1, codec,
+ ast_test_flag(&p->flags[0], SIP_G726_NONSTANDARD) ? AST_RTP_OPT_G726_NONSTANDARD : 0),
+ sample_rate);
+ if (codec == AST_FORMAT_G729A) {
+ /* Indicate that we don't support VAD (G.729 annex B) */
+ ast_str_append(a_buf, 0, "a=fmtp:%d annexb=no\r\n", rtp_code);
+ } else if (codec == AST_FORMAT_G723_1) {
+ /* Indicate that we don't support VAD (G.723.1 annex A) */
+ ast_str_append(a_buf, 0, "a=fmtp:%d annexa=no\r\n", rtp_code);
+ } else if (codec == AST_FORMAT_ILBC) {
+ /* Add information about us using only 20/30 ms packetization */
+ ast_str_append(a_buf, 0, "a=fmtp:%d mode=%d\r\n", rtp_code, fmt.cur_ms);
+ }
+
+ if (fmt.cur_ms && (fmt.cur_ms < *min_packet_size))
+ *min_packet_size = fmt.cur_ms;
+
+ /* Our first codec packetization processed cannot be zero */
+ if ((*min_packet_size)==0 && fmt.cur_ms)
+ *min_packet_size = fmt.cur_ms;
+}
+
+/*! \brief Add video codec offer to SDP offer/answer body in INVITE or 200 OK */
+/* This is different to the audio one now so we can add more caps later */
+static void add_vcodec_to_sdp(const struct sip_pvt *p, int codec, int sample_rate,
+ struct ast_str **m_buf, struct ast_str **a_buf,
+ int debug, int *min_packet_size)
+{
+ int rtp_code;
+
+ if (!p->vrtp)
+ return;
+
+ if (debug)
+ ast_verbose("Adding video codec 0x%x (%s) to SDP\n", codec, ast_getformatname(codec));
+
+ if ((rtp_code = ast_rtp_lookup_code(p->vrtp, 1, codec)) == -1)
+ return;
+
+ ast_str_append(m_buf, 0, " %d", rtp_code);
+ ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%d\r\n", rtp_code,
+ ast_rtp_lookup_mime_subtype(1, codec, 0), sample_rate);
+ /* Add fmtp code here */
+}
+
+/*! \brief Add text codec offer to SDP offer/answer body in INVITE or 200 OK */
+static void add_tcodec_to_sdp(const struct sip_pvt *p, int codec, int sample_rate,
+ struct ast_str **m_buf, struct ast_str **a_buf,
+ int debug, int *min_packet_size)
+{
+ int rtp_code;
+
+ if (!p->trtp)
+ return;
+
+ if (debug)
+ ast_verbose("Adding text codec 0x%x (%s) to SDP\n", codec, ast_getformatname(codec));
+
+ if ((rtp_code = ast_rtp_lookup_code(p->trtp, 1, codec)) == -1)
+ return;
+
+ ast_str_append(m_buf, 0, " %d", rtp_code);
+ ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%d\r\n", rtp_code,
+ ast_rtp_lookup_mime_subtype(1, codec, 0), sample_rate);
+ /* Add fmtp code here */
+}
+
+
+/*! \brief Get Max T.38 Transmission rate from T38 capabilities */
+static int t38_get_rate(int t38cap)
+{
+ int maxrate = (t38cap & (T38FAX_RATE_14400 | T38FAX_RATE_12000 | T38FAX_RATE_9600 | T38FAX_RATE_7200 | T38FAX_RATE_4800 | T38FAX_RATE_2400));
+
+ if (maxrate & T38FAX_RATE_14400) {
+ ast_debug(2, "T38MaxFaxRate 14400 found\n");
+ return 14400;
+ } else if (maxrate & T38FAX_RATE_12000) {
+ ast_debug(2, "T38MaxFaxRate 12000 found\n");
+ return 12000;
+ } else if (maxrate & T38FAX_RATE_9600) {
+ ast_debug(2, "T38MaxFaxRate 9600 found\n");
+ return 9600;
+ } else if (maxrate & T38FAX_RATE_7200) {
+ ast_debug(2, "T38MaxFaxRate 7200 found\n");
+ return 7200;
+ } else if (maxrate & T38FAX_RATE_4800) {
+ ast_debug(2, "T38MaxFaxRate 4800 found\n");
+ return 4800;
+ } else if (maxrate & T38FAX_RATE_2400) {
+ ast_debug(2, "T38MaxFaxRate 2400 found\n");
+ return 2400;
+ } else {
+ ast_debug(2, "Strange, T38MaxFaxRate NOT found in peers T38 SDP.\n");
+ return 0;
+ }
+}
+
+/*! \brief Add T.38 Session Description Protocol message */
+static int add_t38_sdp(struct sip_request *resp, struct sip_pvt *p)
+{
+ int len = 0;
+ int x = 0;
+ struct sockaddr_in udptlsin;
+ struct ast_str *m_modem = ast_str_alloca(1024);
+ struct ast_str *a_modem = ast_str_alloca(1024);
+ struct sockaddr_in udptldest = { 0, };
+ int debug;
+
+ debug = sip_debug_test_pvt(p);
+ len = 0;
+ if (!p->udptl) {
+ ast_log(LOG_WARNING, "No way to add SDP without an UDPTL structure\n");
+ return -1;
+ }
+
+ if (!p->sessionid) {
+ p->sessionid = (int)ast_random();
+ p->sessionversion = p->sessionid;
+ } else
+ p->sessionversion++;
+
+ /* Our T.38 end is */
+ ast_udptl_get_us(p->udptl, &udptlsin);
+
+ /* Determine T.38 UDPTL destination */
+ if (p->udptlredirip.sin_addr.s_addr) {
+ udptldest.sin_port = p->udptlredirip.sin_port;
+ udptldest.sin_addr = p->udptlredirip.sin_addr;
+ } else {
+ udptldest.sin_addr = p->ourip.sin_addr;
+ udptldest.sin_port = udptlsin.sin_port;
+ }
+
+ if (debug)
+ ast_debug(1, "T.38 UDPTL is at %s port %d\n", ast_inet_ntoa(p->ourip.sin_addr), ntohs(udptlsin.sin_port));
+
+ /* We break with the "recommendation" and send our IP, in order that our
+ peer doesn't have to ast_gethostbyname() us */
+
+ if (debug) {
+ ast_debug(1, "Our T38 capability (%d), peer T38 capability (%d), joint capability (%d)\n",
+ p->t38.capability,
+ p->t38.peercapability,
+ p->t38.jointcapability);
+ }
+ ast_str_append(&m_modem, 0, "v=0\r\n");
+ ast_str_append(&m_modem, 0, "o=%s %d %d IN IP4 %s\r\n", ast_strlen_zero(global_sdpowner) ? "-" : global_sdpowner , p->sessionid, p->sessionversion, ast_inet_ntoa(udptldest.sin_addr));
+ ast_str_append(&m_modem, 0, "s=%s\r\n", ast_strlen_zero(global_sdpsession) ? "-" : global_sdpsession);
+ ast_str_append(&m_modem, 0, "c=IN IP4 %s\r\n", ast_inet_ntoa(udptldest.sin_addr));
+ ast_str_append(&m_modem, 0, "t=0 0\r\n");
+ ast_str_append(&m_modem, 0, "m=image %d udptl t38\r\n", ntohs(udptldest.sin_port));
+
+ if ((p->t38.jointcapability & T38FAX_VERSION) == T38FAX_VERSION_0)
+ ast_str_append(&a_modem, 0, "a=T38FaxVersion:0\r\n");
+ if ((p->t38.jointcapability & T38FAX_VERSION) == T38FAX_VERSION_1)
+ ast_str_append(&a_modem, 0, "a=T38FaxVersion:1\r\n");
+ if ((x = t38_get_rate(p->t38.jointcapability)))
+ ast_str_append(&a_modem, 0, "a=T38MaxBitRate:%d\r\n",x);
+ ast_str_append(&a_modem, 0, "a=T38FaxFillBitRemoval:%d\r\n", (p->t38.jointcapability & T38FAX_FILL_BIT_REMOVAL) ? 1 : 0);
+ ast_str_append(&a_modem, 0, "a=T38FaxTranscodingMMR:%d\r\n", (p->t38.jointcapability & T38FAX_TRANSCODING_MMR) ? 1 : 0);
+ ast_str_append(&a_modem, 0, "a=T38FaxTranscodingJBIG:%d\r\n", (p->t38.jointcapability & T38FAX_TRANSCODING_JBIG) ? 1 : 0);
+ ast_str_append(&a_modem, 0, "a=T38FaxRateManagement:%s\r\n", (p->t38.jointcapability & T38FAX_RATE_MANAGEMENT_LOCAL_TCF) ? "localTCF" : "transferredTCF");
+ x = ast_udptl_get_local_max_datagram(p->udptl);
+ ast_str_append(&a_modem, 0, "a=T38FaxMaxBuffer:%d\r\n",x);
+ ast_str_append(&a_modem, 0, "a=T38FaxMaxDatagram:%d\r\n",x);
+ if (p->t38.jointcapability != T38FAX_UDP_EC_NONE)
+ ast_str_append(&a_modem, 0, "a=T38FaxUdpEC:%s\r\n", (p->t38.jointcapability & T38FAX_UDP_EC_REDUNDANCY) ? "t38UDPRedundancy" : "t38UDPFEC");
+ len = m_modem->used + a_modem->used;
+ add_header(resp, "Content-Type", "application/sdp");
+ add_header_contentLength(resp, len);
+ add_line(resp, m_modem->str);
+ add_line(resp, a_modem->str);
+
+ /* Update lastrtprx when we send our SDP */
+ p->lastrtprx = p->lastrtptx = time(NULL);
+
+ return 0;
+}
+
+
+/*! \brief Add RFC 2833 DTMF offer to SDP */
+static void add_noncodec_to_sdp(const struct sip_pvt *p, int format, int sample_rate,
+ struct ast_str **m_buf, struct ast_str **a_buf,
+ int debug)
+{
+ int rtp_code;
+
+ if (debug)
+ ast_verbose("Adding non-codec 0x%x (%s) to SDP\n", format, ast_rtp_lookup_mime_subtype(0, format, 0));
+ if ((rtp_code = ast_rtp_lookup_code(p->rtp, 0, format)) == -1)
+ return;
+
+ ast_str_append(m_buf, 0, " %d", rtp_code);
+ ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%d\r\n", rtp_code,
+ ast_rtp_lookup_mime_subtype(0, format, 0),
+ sample_rate);
+ if (format == AST_RTP_DTMF) /* Indicate we support DTMF and FLASH... */
+ ast_str_append(a_buf, 0, "a=fmtp:%d 0-16\r\n", rtp_code);
+}
+
+/*! \brief Set all IP media addresses for this call
+ \note called from add_sdp()
+*/
+static void get_our_media_address(struct sip_pvt *p, int needvideo,
+ struct sockaddr_in *sin, struct sockaddr_in *vsin, struct sockaddr_in *tsin,
+ struct sockaddr_in *dest, struct sockaddr_in *vdest)
+{
+ /* First, get our address */
+ ast_rtp_get_us(p->rtp, sin);
+ if (p->vrtp)
+ ast_rtp_get_us(p->vrtp, vsin);
+ if (p->trtp)
+ ast_rtp_get_us(p->trtp, tsin);
+
+ /* Now, try to figure out where we want them to send data */
+ /* Is this a re-invite to move the media out, then use the original offer from caller */
+ if (p->redirip.sin_addr.s_addr) { /* If we have a redirection IP, use it */
+ dest->sin_port = p->redirip.sin_port;
+ dest->sin_addr = p->redirip.sin_addr;
+ } else {
+ dest->sin_addr = p->ourip.sin_addr;
+ dest->sin_port = sin->sin_port;
+ }
+ if (needvideo) {
+ /* Determine video destination */
+ if (p->vredirip.sin_addr.s_addr) {
+ vdest->sin_addr = p->vredirip.sin_addr;
+ vdest->sin_port = p->vredirip.sin_port;
+ } else {
+ vdest->sin_addr = p->ourip.sin_addr;
+ vdest->sin_port = vsin->sin_port;
+ }
+ }
+
+}
+
+#define SDP_SAMPLE_RATE(x) (x == AST_FORMAT_G722) ? 16000 : 8000
+
+/*! \brief Add Session Description Protocol message
+
+ If oldsdp is TRUE, then the SDP version number is not incremented. This mechanism
+ is used in Session-Timers where RE-INVITEs are used for refreshing SIP sessions
+ without modifying the media session in any way.
+*/
+static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp)
+{
+ int len = 0;
+ int alreadysent = 0;
+
+ struct sockaddr_in sin;
+ struct sockaddr_in vsin;
+ struct sockaddr_in tsin;
+ struct sockaddr_in dest;
+ struct sockaddr_in vdest = { 0, };
+ struct sockaddr_in tdest = { 0, };
+
+ /* SDP fields */
+ char *version = "v=0\r\n"; /* Protocol version */
+ char subject[256]; /* Subject of the session */
+ char owner[256]; /* Session owner/creator */
+ char connection[256]; /* Connection data */
+ char *stime = "t=0 0\r\n"; /* Time the session is active */
+ char bandwidth[256] = ""; /* Max bitrate */
+ char *hold;
+ struct ast_str *m_audio = ast_str_alloca(256); /* Media declaration line for audio */
+ struct ast_str *m_video = ast_str_alloca(256); /* Media declaration line for video */
+ struct ast_str *m_text = ast_str_alloca(256); /* Media declaration line for text */
+ struct ast_str *a_audio = ast_str_alloca(1024); /* Attributes for audio */
+ struct ast_str *a_video = ast_str_alloca(1024); /* Attributes for video */
+ struct ast_str *a_text = ast_str_alloca(1024); /* Attributes for text */
+
+ int x;
+ int capability;
+ int needvideo = FALSE;
+ int needtext = FALSE;
+ int debug = sip_debug_test_pvt(p);
+ int min_audio_packet_size = 0;
+ int min_video_packet_size = 0;
+ int min_text_packet_size = 0;
+
+ char codecbuf[BUFSIZ];
+ char buf[BUFSIZ];
+
+ /* Set the SDP session name */
+ snprintf(subject, sizeof(subject), "s=%s\r\n", ast_strlen_zero(global_sdpsession) ? "-" : global_sdpsession);
+
+ if (!p->rtp) {
+ ast_log(LOG_WARNING, "No way to add SDP without an RTP structure\n");
+ return AST_FAILURE;
+ }
+ /* XXX We should not change properties in the SIP dialog until
+ we have acceptance of the offer if this is a re-invite */
+
+ /* Set RTP Session ID and version */
+ if (!p->sessionid) {
+ p->sessionid = (int)ast_random();
+ p->sessionversion = p->sessionid;
+ } else {
+ if (oldsdp == FALSE)
+ p->sessionversion++;
+ }
+
+ capability = p->jointcapability;
+
+ /* XXX note, Video and Text are negated - 'true' means 'no' */
+ ast_debug(1, "** Our capability: %s Video flag: %s Text flag: %s\n", ast_getformatname_multiple(codecbuf, sizeof(codecbuf), capability),
+ p->novideo ? "True" : "False", p->notext ? "True" : "False");
+ ast_debug(1, "** Our prefcodec: %s \n", ast_getformatname_multiple(codecbuf, sizeof(codecbuf), p->prefcodec));
+
+#ifdef WHEN_WE_HAVE_T38_FOR_OTHER_TRANSPORTS
+ if (ast_test_flag(&p->t38.t38support, SIP_PAGE2_T38SUPPORT_RTP)) {
+ ast_str_append(&m_audio, 0, " %d", 191);
+ ast_str_append(&a_audio, 0, "a=rtpmap:%d %s/%d\r\n", 191, "t38", 8000);
+ }
+#endif
+
+ /* Check if we need video in this call */
+ if ((capability & AST_FORMAT_VIDEO_MASK) && !p->novideo) {
+ if (p->vrtp) {
+ needvideo = TRUE;
+ ast_debug(2, "This call needs video offers!\n");
+ } else
+ ast_debug(2, "This call needs video offers, but there's no video support enabled!\n");
+ }
+
+ /* Get our media addresses */
+ get_our_media_address(p, needvideo, &sin, &vsin, &tsin, &dest, &vdest);
+
+ if (debug)
+ ast_verbose("Audio is at %s port %d\n", ast_inet_ntoa(p->ourip.sin_addr), ntohs(sin.sin_port));
+
+ /* Ok, we need video. Let's add what we need for video and set codecs.
+ Video is handled differently than audio since we can not transcode. */
+ if (needvideo) {
+ ast_str_append(&m_video, 0, "m=video %d RTP/AVP", ntohs(vdest.sin_port));
+
+ /* Build max bitrate string */
+ if (p->maxcallbitrate)
+ snprintf(bandwidth, sizeof(bandwidth), "b=CT:%d\r\n", p->maxcallbitrate);
+ if (debug)
+ ast_verbose("Video is at %s port %d\n", ast_inet_ntoa(p->ourip.sin_addr), ntohs(vsin.sin_port));
+ }
+
+ /* Check if we need text in this call */
+ if((capability & AST_FORMAT_TEXT_MASK) && !p->notext) {
+ if (sipdebug_text)
+ ast_verbose("We think we can do text\n");
+ if (p->trtp) {
+ if (sipdebug_text)
+ ast_verbose("And we have a text rtp object\n");
+ needtext = TRUE;
+ ast_debug(2, "This call needs text offers! \n");
+ } else
+ ast_debug(2, "This call needs text offers, but there's no text support enabled ! \n");
+ }
+
+ /* Ok, we need text. Let's add what we need for text and set codecs.
+ Text is handled differently than audio since we can not transcode. */
+ if (needtext) {
+ if (sipdebug_text)
+ ast_verbose("Lets set up the text sdp\n");
+ /* Determine text destination */
+ if (p->tredirip.sin_addr.s_addr) {
+ tdest.sin_addr = p->tredirip.sin_addr;
+ tdest.sin_port = p->tredirip.sin_port;
+ } else {
+ tdest.sin_addr = p->ourip.sin_addr;
+ tdest.sin_port = tsin.sin_port;
+ }
+ ast_str_append(&m_text, 0, "m=text %d RTP/AVP", ntohs(tdest.sin_port));
+
+ if (debug) /* XXX should I use tdest below ? */
+ ast_verbose("Text is at %s port %d\n", ast_inet_ntoa(p->ourip.sin_addr), ntohs(tsin.sin_port));
+
+ }
+
+ /* Start building generic SDP headers */
+
+ /* We break with the "recommendation" and send our IP, in order that our
+ peer doesn't have to ast_gethostbyname() us */
+
+ snprintf(owner, sizeof(owner), "o=%s %d %d IN IP4 %s\r\n", ast_strlen_zero(global_sdpowner) ? "-" : global_sdpowner, p->sessionid, p->sessionversion, ast_inet_ntoa(dest.sin_addr));
+ snprintf(connection, sizeof(connection), "c=IN IP4 %s\r\n", ast_inet_ntoa(dest.sin_addr));
+ ast_str_append(&m_audio, 0, "m=audio %d RTP/AVP", ntohs(dest.sin_port));
+
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) == SIP_PAGE2_CALL_ONHOLD_ONEDIR)
+ hold = "a=recvonly\r\n";
+ else if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) == SIP_PAGE2_CALL_ONHOLD_INACTIVE)
+ hold = "a=inactive\r\n";
+ else
+ hold = "a=sendrecv\r\n";
+
+ /* Now, start adding audio codecs. These are added in this order:
+ - First what was requested by the calling channel
+ - Then preferences in order from sip.conf device config for this peer/user
+ - Then other codecs in capabilities, including video
+ */
+
+ /* Prefer the audio codec we were requested to use, first, no matter what
+ Note that p->prefcodec can include video codecs, so mask them out
+ */
+ if (capability & p->prefcodec) {
+ int codec = p->prefcodec & AST_FORMAT_AUDIO_MASK;
+
+ add_codec_to_sdp(p, codec, SDP_SAMPLE_RATE(codec),
+ &m_audio, &a_audio,
+ debug, &min_audio_packet_size);
+ alreadysent |= codec;
+ }
+
+ /* Start by sending our preferred audio codecs */
+ for (x = 0; x < 32; x++) {
+ int codec;
+
+ if (!(codec = ast_codec_pref_index(&p->prefs, x)))
+ break;
+
+ if (!(capability & codec))
+ continue;
+
+ if (alreadysent & codec)
+ continue;
+
+ add_codec_to_sdp(p, codec, SDP_SAMPLE_RATE(codec),
+ &m_audio, &a_audio,
+ debug, &min_audio_packet_size);
+ alreadysent |= codec;
+ }
+
+ /* Now send any other common audio and video codecs, and non-codec formats: */
+ for (x = 1; x <= (needtext ? AST_FORMAT_TEXT_MASK : (needvideo ? AST_FORMAT_VIDEO_MASK : AST_FORMAT_AUDIO_MASK)); x <<= 1) {
+ if (!(capability & x)) /* Codec not requested */
+ continue;
+
+ if (alreadysent & x) /* Already added to SDP */
+ continue;
+
+ if (x & AST_FORMAT_AUDIO_MASK)
+ add_codec_to_sdp(p, x, SDP_SAMPLE_RATE(x),
+ &m_audio, &a_audio, debug, &min_audio_packet_size);
+ else if (x & AST_FORMAT_VIDEO_MASK)
+ add_vcodec_to_sdp(p, x, 90000,
+ &m_video, &a_video, debug, &min_video_packet_size);
+ else if (x & AST_FORMAT_TEXT_MASK)
+ add_tcodec_to_sdp(p, x, 1000,
+ &m_text, &a_text, debug, &min_text_packet_size);
+ }
+
+ /* Now add DTMF RFC2833 telephony-event as a codec */
+ for (x = 1; x <= AST_RTP_MAX; x <<= 1) {
+ if (!(p->jointnoncodeccapability & x))
+ continue;
+
+ add_noncodec_to_sdp(p, x, 8000, &m_audio, &a_audio, debug);
+ }
+
+ ast_debug(3, "-- Done with adding codecs to SDP\n");
+
+ if (!p->owner || !ast_internal_timing_enabled(p->owner))
+ ast_str_append(&a_audio, 0, "a=silenceSupp:off - - - -\r\n");
+
+ if (min_audio_packet_size)
+ ast_str_append(&a_audio, 0, "a=ptime:%d\r\n", min_audio_packet_size);
+
+ /* XXX don't think you can have ptime for video */
+ if (min_video_packet_size)
+ ast_str_append(&a_video, 0, "a=ptime:%d\r\n", min_video_packet_size);
+
+ /* XXX don't think you can have ptime for text */
+ if (min_text_packet_size)
+ ast_str_append(&a_text, 0, "a=ptime:%d\r\n", min_text_packet_size);
+
+ if (m_audio->len - m_audio->used < 2 || m_video->len - m_video->used < 2 ||
+ m_text->len - m_text->used < 2 || a_text->len - a_text->used < 2 ||
+ a_audio->len - a_audio->used < 2 || a_video->len - a_video->used < 2)
+ ast_log(LOG_WARNING, "SIP SDP may be truncated due to undersized buffer!!\n");
+
+ ast_str_append(&m_audio, 0, "\r\n");
+ if (needvideo)
+ ast_str_append(&m_video, 0, "\r\n");
+ if (needtext)
+ ast_str_append(&m_text, 0, "\r\n");
+
+ len = strlen(version) + strlen(subject) + strlen(owner) +
+ strlen(connection) + strlen(stime) + m_audio->used + a_audio->used + strlen(hold);
+ if (needvideo) /* only if video response is appropriate */
+ len += m_video->used + a_video->used + strlen(bandwidth) + strlen(hold);
+ if (needtext) /* only if text response is appropriate */
+ len += m_text->used + a_text->used + strlen(hold);
+
+ add_header(resp, "Content-Type", "application/sdp");
+ add_header_contentLength(resp, len);
+ add_line(resp, version);
+ add_line(resp, owner);
+ add_line(resp, subject);
+ add_line(resp, connection);
+ if (needvideo) /* only if video response is appropriate */
+ add_line(resp, bandwidth);
+ add_line(resp, stime);
+ add_line(resp, m_audio->str);
+ add_line(resp, a_audio->str);
+ add_line(resp, hold);
+ if (needvideo) { /* only if video response is appropriate */
+ add_line(resp, m_video->str);
+ add_line(resp, a_video->str);
+ add_line(resp, hold); /* Repeat hold for the video stream */
+ }
+ if (needtext) { /* only if text response is appropriate */
+ add_line(resp, m_text->str);
+ add_line(resp, a_text->str);
+ add_line(resp, hold); /* Repeat hold for the text stream */
+ }
+
+ /* Update lastrtprx when we send our SDP */
+ p->lastrtprx = p->lastrtptx = time(NULL); /* XXX why both ? */
+
+ ast_debug(3, "Done building SDP. Settling with this capability: %s\n", ast_getformatname_multiple(buf, BUFSIZ, capability));
+
+ return AST_SUCCESS;
+}
+
+/*! \brief Used for 200 OK and 183 early media */
+static int transmit_response_with_t38_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans)
+{
+ struct sip_request resp;
+ int seqno;
+
+ if (sscanf(get_header(req, "CSeq"), "%d ", &seqno) != 1) {
+ ast_log(LOG_WARNING, "Unable to get seqno from '%s'\n", get_header(req, "CSeq"));
+ return -1;
+ }
+ respprep(&resp, p, msg, req);
+ if (p->udptl) {
+ ast_udptl_offered_from_local(p->udptl, 0);
+ add_t38_sdp(&resp, p);
+ } else
+ ast_log(LOG_ERROR, "Can't add SDP to response, since we have no UDPTL session allocated. Call-ID %s\n", p->callid);
+ if (retrans && !p->pendinginvite)
+ p->pendinginvite = seqno; /* Buggy clients sends ACK on RINGING too */
+ return send_response(p, &resp, retrans, seqno);
+}
+
+/*! \brief copy SIP request (mostly used to save request for responses) */
+static void copy_request(struct sip_request *dst, const struct sip_request *src)
+{
+ long offset;
+ int x;
+ offset = ((void *)dst) - ((void *)src);
+ /* First copy stuff */
+ memcpy(dst, src, sizeof(*dst));
+ /* Now fix pointer arithmetic */
+ for (x=0; x < src->headers; x++)
+ dst->header[x] += offset;
+ for (x=0; x < src->lines; x++)
+ dst->line[x] += offset;
+ dst->rlPart1 += offset;
+ dst->rlPart2 += offset;
+}
+
+/*! \brief Used for 200 OK and 183 early media
+ \return Will return XMIT_ERROR for network errors.
+*/
+static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp)
+{
+ struct sip_request resp;
+ int seqno;
+ if (sscanf(get_header(req, "CSeq"), "%d ", &seqno) != 1) {
+ ast_log(LOG_WARNING, "Unable to get seqno from '%s'\n", get_header(req, "CSeq"));
+ return -1;
+ }
+ respprep(&resp, p, msg, req);
+ if (p->rtp) {
+ if (!p->autoframing && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ ast_debug(1, "Setting framing from config on incoming call\n");
+ ast_rtp_codec_setpref(p->rtp, &p->prefs);
+ }
+ try_suggested_sip_codec(p);
+ add_sdp(&resp, p, oldsdp);
+ } else
+ ast_log(LOG_ERROR, "Can't add SDP to response, since we have no RTP session allocated. Call-ID %s\n", p->callid);
+ if (reliable && !p->pendinginvite)
+ p->pendinginvite = seqno; /* Buggy clients sends ACK on RINGING too */
+ return send_response(p, &resp, reliable, seqno);
+}
+
+/*! \brief Parse first line of incoming SIP request */
+static int determine_firstline_parts(struct sip_request *req)
+{
+ char *e = ast_skip_blanks(req->header[0]); /* there shouldn't be any */
+
+ if (!*e)
+ return -1;
+ req->rlPart1 = e; /* method or protocol */
+ e = ast_skip_nonblanks(e);
+ if (*e)
+ *e++ = '\0';
+ /* Get URI or status code */
+ e = ast_skip_blanks(e);
+ if ( !*e )
+ return -1;
+ ast_trim_blanks(e);
+
+ if (!strcasecmp(req->rlPart1, "SIP/2.0") ) { /* We have a response */
+ if (strlen(e) < 3) /* status code is 3 digits */
+ return -1;
+ req->rlPart2 = e;
+ } else { /* We have a request */
+ if ( *e == '<' ) { /* XXX the spec says it must not be in <> ! */
+ ast_debug(3, "Oops. Bogus uri in <> %s\n", e);
+ e++;
+ if (!*e)
+ return -1;
+ }
+ req->rlPart2 = e; /* URI */
+ e = ast_skip_nonblanks(e);
+ if (*e)
+ *e++ = '\0';
+ e = ast_skip_blanks(e);
+ if (strcasecmp(e, "SIP/2.0") ) {
+ ast_debug(3, "Skipping packet - Bad request protocol %s\n", e);
+ return -1;
+ }
+ }
+ return 1;
+}
+
+/*! \brief Transmit reinvite with SDP
+\note A re-invite is basically a new INVITE with the same CALL-ID and TAG as the
+ INVITE that opened the SIP dialogue
+ We reinvite so that the audio stream (RTP) go directly between
+ the SIP UAs. SIP Signalling stays with * in the path.
+
+ If t38version is TRUE, we send T38 SDP for re-invite from audio/video to
+ T38 UDPTL transmission on the channel
+
+ If oldsdp is TRUE then the SDP version number is not incremented. This
+ is needed for Session-Timers so we can send a re-invite to refresh the
+ SIP session without modifying the media session.
+*/
+static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp)
+{
+ struct sip_request req;
+
+ reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ? SIP_UPDATE : SIP_INVITE, 0, 1);
+
+ add_header(&req, "Allow", ALLOWED_METHODS);
+ add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
+ if (sipdebug) {
+ if (oldsdp == TRUE)
+ add_header(&req, "X-asterisk-Info", "SIP re-invite (Session-Timers)");
+ else
+ add_header(&req, "X-asterisk-Info", "SIP re-invite (External RTP bridge)");
+ }
+
+ if (p->do_history)
+ append_history(p, "ReInv", "Re-invite sent");
+ if (t38version)
+ add_t38_sdp(&req, p);
+ else
+ add_sdp(&req, p, oldsdp);
+
+ /* Use this as the basis */
+ initialize_initreq(p, &req);
+ p->lastinvite = p->ocseq;
+ ast_set_flag(&p->flags[0], SIP_OUTGOING); /* Change direction of this dialog */
+
+ return send_request(p, &req, XMIT_CRITICAL, p->ocseq);
+}
+
+/* \brief Remove URI parameters at end of URI, not in username part though */
+static char *remove_uri_parameters(char *uri)
+{
+ char *atsign;
+ atsign = strchr(uri, '@'); /* First, locate the at sign */
+ if (!atsign)
+ atsign = uri; /* Ok hostname only, let's stick with the rest */
+ atsign = strchr(atsign, ';'); /* Locate semi colon */
+ if (atsign)
+ *atsign = '\0'; /* Kill at the semi colon */
+ return uri;
+}
+
+/*! \brief Check Contact: URI of SIP message */
+static void extract_uri(struct sip_pvt *p, struct sip_request *req)
+{
+ char stripped[BUFSIZ];
+ char *c;
+
+ ast_copy_string(stripped, get_header(req, "Contact"), sizeof(stripped));
+ c = get_in_brackets(stripped);
+ /* Cut the URI at the at sign after the @, not in the username part */
+ c = remove_uri_parameters(c);
+ if (!ast_strlen_zero(c))
+ ast_string_field_set(p, uri, c);
+
+}
+
+/*! \brief Build contact header - the contact header we send out */
+static void build_contact(struct sip_pvt *p)
+{
+ /* Construct Contact: header */
+ if (p->socket.type & SIP_TRANSPORT_UDP) {
+ if (!sip_standard_port(p->socket))
+ ast_string_field_build(p, our_contact, "<sip:%s%s%s:%d>", p->exten, ast_strlen_zero(p->exten) ? "" : "@", ast_inet_ntoa(p->ourip.sin_addr), ntohs(p->socket.port));
+ else
+ ast_string_field_build(p, our_contact, "<sip:%s%s%s>", p->exten, ast_strlen_zero(p->exten) ? "" : "@", ast_inet_ntoa(p->ourip.sin_addr));
+ } else
+ ast_string_field_build(p, our_contact, "<sip:%s%s%s:%d;transport=%s>", p->exten, ast_strlen_zero(p->exten) ? "" : "@", ast_inet_ntoa(p->ourip.sin_addr), ntohs(p->socket.port), get_transport(p->socket.type));
+}
+
+/*! \brief Build the Remote Party-ID & From using callingpres options */
+static void build_rpid(struct sip_pvt *p)
+{
+ int send_pres_tags = TRUE;
+ const char *privacy=NULL;
+ const char *screen=NULL;
+ char buf[256];
+ const char *clid = default_callerid;
+ const char *clin = NULL;
+ const char *fromdomain;
+
+ if (!ast_strlen_zero(p->rpid) || !ast_strlen_zero(p->rpid_from))
+ return;
+
+ if (p->owner && p->owner->cid.cid_num)
+ clid = p->owner->cid.cid_num;
+ if (p->owner && p->owner->cid.cid_name)
+ clin = p->owner->cid.cid_name;
+ if (ast_strlen_zero(clin))
+ clin = clid;
+
+ switch (p->callingpres) {
+ case AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED:
+ privacy = "off";
+ screen = "no";
+ break;
+ case AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN:
+ privacy = "off";
+ screen = "yes";
+ break;
+ case AST_PRES_ALLOWED_USER_NUMBER_FAILED_SCREEN:
+ privacy = "off";
+ screen = "no";
+ break;
+ case AST_PRES_ALLOWED_NETWORK_NUMBER:
+ privacy = "off";
+ screen = "yes";
+ break;
+ case AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED:
+ privacy = "full";
+ screen = "no";
+ break;
+ case AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN:
+ privacy = "full";
+ screen = "yes";
+ break;
+ case AST_PRES_PROHIB_USER_NUMBER_FAILED_SCREEN:
+ privacy = "full";
+ screen = "no";
+ break;
+ case AST_PRES_PROHIB_NETWORK_NUMBER:
+ privacy = "full";
+ screen = "yes";
+ break;
+ case AST_PRES_NUMBER_NOT_AVAILABLE:
+ send_pres_tags = FALSE;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Unsupported callingpres (%d)\n", p->callingpres);
+ if ((p->callingpres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED)
+ privacy = "full";
+ else
+ privacy = "off";
+ screen = "no";
+ break;
+ }
+
+ fromdomain = S_OR(p->fromdomain, ast_inet_ntoa(p->ourip.sin_addr));
+
+ snprintf(buf, sizeof(buf), "\"%s\" <sip:%s@%s>", clin, clid, fromdomain);
+ if (send_pres_tags)
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ";privacy=%s;screen=%s", privacy, screen);
+ ast_string_field_set(p, rpid, buf);
+
+ ast_string_field_build(p, rpid_from, "\"%s\" <sip:%s@%s>;tag=%s", clin,
+ S_OR(p->fromuser, clid),
+ fromdomain, p->tag);
+}
+
+/*! \brief Initiate new SIP request to peer/user */
+static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod)
+{
+ struct ast_str *invite = ast_str_alloca(256);
+ char from[256];
+ char to[256];
+ char tmp_n[BUFSIZ/2]; /* build a local copy of 'n' if needed */
+ char tmp_l[BUFSIZ/2]; /* build a local copy of 'l' if needed */
+ const char *l = NULL; /* XXX what is this, exactly ? */
+ const char *n = NULL; /* XXX what is this, exactly ? */
+ const char *urioptions = "";
+
+ if (ast_test_flag(&p->flags[0], SIP_USEREQPHONE)) {
+ const char *s = p->username; /* being a string field, cannot be NULL */
+
+ /* Test p->username against allowed characters in AST_DIGIT_ANY
+ If it matches the allowed characters list, then sipuser = ";user=phone"
+ If not, then sipuser = ""
+ */
+ /* + is allowed in first position in a tel: uri */
+ if (*s == '+')
+ s++;
+ for (; *s; s++) {
+ if (!strchr(AST_DIGIT_ANYNUM, *s) )
+ break;
+ }
+ /* If we have only digits, add ;user=phone to the uri */
+ if (*s)
+ urioptions = ";user=phone";
+ }
+
+
+ snprintf(p->lastmsg, sizeof(p->lastmsg), "Init: %s", sip_methods[sipmethod].text);
+
+ if (p->owner) {
+ l = p->owner->cid.cid_num;
+ n = p->owner->cid.cid_name;
+ }
+ /* if we are not sending RPID and user wants his callerid restricted */
+ if (!ast_test_flag(&p->flags[0], SIP_SENDRPID) &&
+ ((p->callingpres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED)) {
+ l = CALLERID_UNKNOWN;
+ n = l;
+ }
+ if (ast_strlen_zero(l))
+ l = default_callerid;
+ if (ast_strlen_zero(n))
+ n = l;
+ /* Allow user to be overridden */
+ if (!ast_strlen_zero(p->fromuser))
+ l = p->fromuser;
+ else /* Save for any further attempts */
+ ast_string_field_set(p, fromuser, l);
+
+ /* Allow user to be overridden */
+ if (!ast_strlen_zero(p->fromname))
+ n = p->fromname;
+ else /* Save for any further attempts */
+ ast_string_field_set(p, fromname, n);
+
+ if (pedanticsipchecking) {
+ ast_uri_encode(n, tmp_n, sizeof(tmp_n), 0);
+ n = tmp_n;
+ ast_uri_encode(l, tmp_l, sizeof(tmp_l), 0);
+ l = tmp_l;
+ }
+
+ if (!sip_standard_port(p->socket) && ast_strlen_zero(p->fromdomain))
+ snprintf(from, sizeof(from), "\"%s\" <sip:%s@%s:%d>;tag=%s", n, l, S_OR(p->fromdomain, ast_inet_ntoa(p->ourip.sin_addr)), ntohs(p->socket.port), p->tag);
+ else
+ snprintf(from, sizeof(from), "\"%s\" <sip:%s@%s>;tag=%s", n, l, S_OR(p->fromdomain, ast_inet_ntoa(p->ourip.sin_addr)), p->tag);
+
+ /* If we're calling a registered SIP peer, use the fullcontact to dial to the peer */
+ if (!ast_strlen_zero(p->fullcontact)) {
+ /* If we have full contact, trust it */
+ ast_str_append(&invite, 0, "%s", p->fullcontact);
+ } else {
+ /* Otherwise, use the username while waiting for registration */
+ ast_str_append(&invite, 0, "sip:");
+ if (!ast_strlen_zero(p->username)) {
+ n = p->username;
+ if (pedanticsipchecking) {
+ ast_uri_encode(n, tmp_n, sizeof(tmp_n), 0);
+ n = tmp_n;
+ }
+ ast_str_append(&invite, 0, "%s@", n);
+ }
+ ast_str_append(&invite, 0, "%s", p->tohost);
+ if (ntohs(p->sa.sin_port) != STANDARD_SIP_PORT)
+ ast_str_append(&invite, 0, ":%d", ntohs(p->sa.sin_port));
+ ast_str_append(&invite, 0, "%s", urioptions);
+ }
+
+ /* If custom URI options have been provided, append them */
+ if (p->options && p->options->uri_options)
+ ast_str_append(&invite, 0, ";%s", p->options->uri_options);
+
+ /* This is the request URI, which is the next hop of the call
+ which may or may not be the destination of the call
+ */
+ ast_string_field_set(p, uri, invite->str);
+
+ if (!ast_strlen_zero(p->todnid)) {
+ /*! \todo Need to add back the VXML URL here at some point, possibly use build_string for all this junk */
+ if (!strchr(p->todnid, '@')) {
+ /* We have no domain in the dnid */
+ snprintf(to, sizeof(to), "<sip:%s@%s>%s%s", p->todnid, p->tohost, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag);
+ } else {
+ snprintf(to, sizeof(to), "<sip:%s>%s%s", p->todnid, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag);
+ }
+ } else {
+ if (sipmethod == SIP_NOTIFY && !ast_strlen_zero(p->theirtag)) {
+ /* If this is a NOTIFY, use the From: tag in the subscribe (RFC 3265) */
+ snprintf(to, sizeof(to), "<%s%s>;tag=%s", (strncasecmp(p->uri, "sip:", 4) ? "" : "sip:"), p->uri, p->theirtag);
+ } else if (p->options && p->options->vxml_url) {
+ /* If there is a VXML URL append it to the SIP URL */
+ snprintf(to, sizeof(to), "<%s>;%s", p->uri, p->options->vxml_url);
+ } else
+ snprintf(to, sizeof(to), "<%s>", p->uri);
+ }
+
+ init_req(req, sipmethod, p->uri);
+ /* now tmp_n is available so reuse it to build the CSeq */
+ snprintf(tmp_n, sizeof(tmp_n), "%d %s", ++p->ocseq, sip_methods[sipmethod].text);
+
+ add_header(req, "Via", p->via);
+ add_header(req, "Max-Forwards", DEFAULT_MAX_FORWARDS);
+ /* SLD: FIXME?: do Route: here too? I think not cos this is the first request.
+ * OTOH, then we won't have anything in p->route anyway */
+
+ /* Build Remote Party-ID and From */
+ if (ast_test_flag(&p->flags[0], SIP_SENDRPID) && (sipmethod == SIP_INVITE)) {
+ build_rpid(p);
+ add_header(req, "From", p->rpid_from);
+ } else
+ add_header(req, "From", from);
+ add_header(req, "To", to);
+ ast_string_field_set(p, exten, l);
+ build_contact(p);
+ add_header(req, "Contact", p->our_contact);
+ add_header(req, "Call-ID", p->callid);
+ add_header(req, "CSeq", tmp_n);
+ if (!ast_strlen_zero(global_useragent))
+ add_header(req, "User-Agent", global_useragent);
+ if (!ast_strlen_zero(p->rpid))
+ add_header(req, "Remote-Party-ID", p->rpid);
+}
+
+/*! \brief Build REFER/INVITE/OPTIONS message and transmit it
+ \param init 0 = Prepare request within dialog, 1= prepare request, new branch, 2= prepare new request and new dialog. do_proxy_auth calls this with init!=2
+ \param p sip_pvt structure
+ \param sdp unknown
+ \param sipmethod unknown
+
+*/
+static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init)
+{
+ struct sip_request req;
+
+ req.method = sipmethod;
+ if (init) {/* Bump branch even on initial requests */
+ p->branch ^= ast_random();
+ build_via(p);
+ }
+ if (init > 1)
+ initreqprep(&req, p, sipmethod);
+ else
+ reqprep(&req, p, sipmethod, 0, 1);
+
+ if (p->options && p->options->auth)
+ add_header(&req, p->options->authheader, p->options->auth);
+ append_date(&req);
+ if (sipmethod == SIP_REFER) { /* Call transfer */
+ if (p->refer) {
+ char buf[BUFSIZ];
+ if (!ast_strlen_zero(p->refer->refer_to))
+ add_header(&req, "Refer-To", p->refer->refer_to);
+ if (!ast_strlen_zero(p->refer->referred_by)) {
+ snprintf(buf, sizeof(buf), "%s <%s>", p->refer->referred_by_name, p->refer->referred_by);
+ add_header(&req, "Referred-By", buf);
+ }
+ }
+ }
+ /* This new INVITE is part of an attended transfer. Make sure that the
+ other end knows and replace the current call with this new call */
+ if (p->options && !ast_strlen_zero(p->options->replaces)) {
+ add_header(&req, "Replaces", p->options->replaces);
+ add_header(&req, "Require", "replaces");
+ }
+
+ /* Add Session-Timers related headers */
+ if (st_get_mode(p) == SESSION_TIMER_MODE_ORIGINATE) {
+ char i2astr[10];
+
+ if (!p->stimer->st_interval)
+ p->stimer->st_interval = st_get_se(p, TRUE);
+
+ p->stimer->st_active = TRUE;
+
+ snprintf(i2astr, sizeof(i2astr), "%d", p->stimer->st_interval);
+ add_header(&req, "Session-Expires", i2astr);
+ add_header(&req, "Min-SE", i2astr);
+ }
+
+ add_header(&req, "Allow", ALLOWED_METHODS);
+ add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
+ if (p->options && p->options->addsipheaders && p->owner) {
+ struct ast_channel *chan = p->owner; /* The owner channel */
+ struct varshead *headp;
+
+ ast_channel_lock(chan);
+
+ headp = &chan->varshead;
+
+ if (!headp)
+ ast_log(LOG_WARNING,"No Headp for the channel...ooops!\n");
+ else {
+ const struct ast_var_t *current;
+ AST_LIST_TRAVERSE(headp, current, entries) {
+ /* SIPADDHEADER: Add SIP header to outgoing call */
+ if (!strncasecmp(ast_var_name(current), "SIPADDHEADER", strlen("SIPADDHEADER"))) {
+ char *content, *end;
+ const char *header = ast_var_value(current);
+ char *headdup = ast_strdupa(header);
+
+ /* Strip of the starting " (if it's there) */
+ if (*headdup == '"')
+ headdup++;
+ if ((content = strchr(headdup, ':'))) {
+ *content++ = '\0';
+ content = ast_skip_blanks(content); /* Skip white space */
+ /* Strip the ending " (if it's there) */
+ end = content + strlen(content) -1;
+ if (*end == '"')
+ *end = '\0';
+
+ add_header(&req, headdup, content);
+ if (sipdebug)
+ ast_debug(1, "Adding SIP Header \"%s\" with content :%s: \n", headdup, content);
+ }
+ }
+ }
+ }
+
+ ast_channel_unlock(chan);
+ }
+ if (sdp) {
+ if (p->udptl && p->t38.state == T38_LOCAL_DIRECT) {
+ ast_udptl_offered_from_local(p->udptl, 1);
+ ast_debug(1, "T38 is in state %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
+ add_t38_sdp(&req, p);
+ } else if (p->rtp)
+ add_sdp(&req, p, FALSE);
+ } else {
+ add_header_contentLength(&req, 0);
+ }
+
+ if (!p->initreq.headers)
+ initialize_initreq(p, &req);
+ p->lastinvite = p->ocseq;
+ return send_request(p, &req, init ? XMIT_CRITICAL : XMIT_RELIABLE, p->ocseq);
+}
+
+/*! \brief Used in the SUBSCRIBE notification subsystem */
+static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout)
+{
+ struct ast_str *tmp = ast_str_alloca(4000);
+ char from[256], to[256];
+ char *c, *mfrom, *mto;
+ struct sip_request req;
+ char hint[AST_MAX_EXTENSION];
+ char *statestring = "terminated";
+ const struct cfsubscription_types *subscriptiontype;
+ enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN;
+ char *pidfstate = "--";
+ char *pidfnote= "Ready";
+
+ memset(from, 0, sizeof(from));
+ memset(to, 0, sizeof(to));
+
+ switch (state) {
+ case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE):
+ statestring = (global_notifyringing) ? "early" : "confirmed";
+ local_state = NOTIFY_INUSE;
+ pidfstate = "busy";
+ pidfnote = "Ringing";
+ break;
+ case AST_EXTENSION_RINGING:
+ statestring = "early";
+ local_state = NOTIFY_INUSE;
+ pidfstate = "busy";
+ pidfnote = "Ringing";
+ break;
+ case AST_EXTENSION_INUSE:
+ statestring = "confirmed";
+ local_state = NOTIFY_INUSE;
+ pidfstate = "busy";
+ pidfnote = "On the phone";
+ break;
+ case AST_EXTENSION_BUSY:
+ statestring = "confirmed";
+ local_state = NOTIFY_CLOSED;
+ pidfstate = "busy";
+ pidfnote = "On the phone";
+ break;
+ case AST_EXTENSION_UNAVAILABLE:
+ statestring = "terminated";
+ local_state = NOTIFY_CLOSED;
+ pidfstate = "away";
+ pidfnote = "Unavailable";
+ break;
+ case AST_EXTENSION_ONHOLD:
+ statestring = "confirmed";
+ local_state = NOTIFY_INUSE;
+ pidfstate = "busy";
+ pidfnote = "On hold";
+ break;
+ case AST_EXTENSION_NOT_INUSE:
+ default:
+ /* Default setting */
+ break;
+ }
+
+ subscriptiontype = find_subscription_type(p->subscribed);
+
+ /* Check which device/devices we are watching and if they are registered */
+ if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, p->context, p->exten)) {
+ char *hint2 = hint, *individual_hint = NULL;
+ int hint_count = 0, unavailable_count = 0;
+
+ while ((individual_hint = strsep(&hint2, "&"))) {
+ hint_count++;
+
+ if (ast_device_state(individual_hint) == AST_DEVICE_UNAVAILABLE)
+ unavailable_count++;
+ }
+
+ /* If none of the hinted devices are registered, we will
+ * override notification and show no availability.
+ */
+ if (hint_count > 0 && hint_count == unavailable_count) {
+ local_state = NOTIFY_CLOSED;
+ pidfstate = "away";
+ pidfnote = "Not online";
+ }
+ }
+
+ ast_copy_string(from, get_header(&p->initreq, "From"), sizeof(from));
+ c = get_in_brackets(from);
+ if (strncasecmp(c, "sip:", 4) && strncasecmp(c, "sips:", 5)) {
+ ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c);
+ return -1;
+ }
+
+ mfrom = remove_uri_parameters(c);
+
+ ast_copy_string(to, get_header(&p->initreq, "To"), sizeof(to));
+ c = get_in_brackets(to);
+ if (strncasecmp(c, "sip:", 4) && strncasecmp(c, "sips:", 5)) {
+ ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c);
+ return -1;
+ }
+ mto = remove_uri_parameters(c);
+
+ reqprep(&req, p, SIP_NOTIFY, 0, 1);
+
+
+ add_header(&req, "Event", subscriptiontype->event);
+ add_header(&req, "Content-Type", subscriptiontype->mediatype);
+ switch(state) {
+ case AST_EXTENSION_DEACTIVATED:
+ if (timeout)
+ add_header(&req, "Subscription-State", "terminated;reason=timeout");
+ else {
+ add_header(&req, "Subscription-State", "terminated;reason=probation");
+ add_header(&req, "Retry-After", "60");
+ }
+ break;
+ case AST_EXTENSION_REMOVED:
+ add_header(&req, "Subscription-State", "terminated;reason=noresource");
+ break;
+ default:
+ if (p->expiry)
+ add_header(&req, "Subscription-State", "active");
+ else /* Expired */
+ add_header(&req, "Subscription-State", "terminated;reason=timeout");
+ }
+ switch (p->subscribed) {
+ case XPIDF_XML:
+ case CPIM_PIDF_XML:
+ ast_str_append(&tmp, 0,
+ "<?xml version=\"1.0\"?>\n"
+ "<!DOCTYPE presence PUBLIC \"-//IETF//DTD RFCxxxx XPIDF 1.0//EN\" \"xpidf.dtd\">\n"
+ "<presence>\n");
+ ast_str_append(&tmp, 0, "<presentity uri=\"%s;method=SUBSCRIBE\" />\n", mfrom);
+ ast_str_append(&tmp, 0, "<atom id=\"%s\">\n", p->exten);
+ ast_str_append(&tmp, 0, "<address uri=\"%s;user=ip\" priority=\"0.800000\">\n", mto);
+ ast_str_append(&tmp, 0, "<status status=\"%s\" />\n", (local_state == NOTIFY_OPEN) ? "open" : (local_state == NOTIFY_INUSE) ? "inuse" : "closed");
+ ast_str_append(&tmp, 0, "<msnsubstatus substatus=\"%s\" />\n", (local_state == NOTIFY_OPEN) ? "online" : (local_state == NOTIFY_INUSE) ? "onthephone" : "offline");
+ ast_str_append(&tmp, 0, "</address>\n</atom>\n</presence>\n");
+ break;
+ case PIDF_XML: /* Eyebeam supports this format */
+ ast_str_append(&tmp, 0,
+ "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
+ "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" \nxmlns:pp=\"urn:ietf:params:xml:ns:pidf:person\"\nxmlns:es=\"urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status\"\nxmlns:ep=\"urn:ietf:params:xml:ns:pidf:rpid:rpid-person\"\nentity=\"%s\">\n", mfrom);
+ ast_str_append(&tmp, 0, "<pp:person><status>\n");
+ if (pidfstate[0] != '-')
+ ast_str_append(&tmp, 0, "<ep:activities><ep:%s/></ep:activities>\n", pidfstate);
+ ast_str_append(&tmp, 0, "</status></pp:person>\n");
+ ast_str_append(&tmp, 0, "<note>%s</note>\n", pidfnote); /* Note */
+ ast_str_append(&tmp, 0, "<tuple id=\"%s\">\n", p->exten); /* Tuple start */
+ ast_str_append(&tmp, 0, "<contact priority=\"1\">%s</contact>\n", mto);
+ if (pidfstate[0] == 'b') /* Busy? Still open ... */
+ ast_str_append(&tmp, 0, "<status><basic>open</basic></status>\n");
+ else
+ ast_str_append(&tmp, 0, "<status><basic>%s</basic></status>\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed");
+ ast_str_append(&tmp, 0, "</tuple>\n</presence>\n");
+ break;
+ case DIALOG_INFO_XML: /* SNOM subscribes in this format */
+ ast_str_append(&tmp, 0, "<?xml version=\"1.0\"?>\n");
+ ast_str_append(&tmp, 0, "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"%d\" state=\"%s\" entity=\"%s\">\n", p->dialogver++, full ? "full":"partial", mto);
+ if ((state & AST_EXTENSION_RINGING) && global_notifyringing)
+ ast_str_append(&tmp, 0, "<dialog id=\"%s\" direction=\"recipient\">\n", p->exten);
+ else
+ ast_str_append(&tmp, 0, "<dialog id=\"%s\">\n", p->exten);
+ ast_str_append(&tmp, 0, "<state>%s</state>\n", statestring);
+ if (state == AST_EXTENSION_ONHOLD) {
+ ast_str_append(&tmp, 0, "<local>\n<target uri=\"%s\">\n"
+ "<param pname=\"+sip.rendering\" pvalue=\"no\">\n"
+ "</target>\n</local>\n", mto);
+ }
+ ast_str_append(&tmp, 0, "</dialog>\n</dialog-info>\n");
+ break;
+ case NONE:
+ default:
+ break;
+ }
+
+ add_header_contentLength(&req, tmp->used);
+ add_line(&req, tmp->str);
+
+ return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+}
+
+/*! \brief Notify user of messages waiting in voicemail
+\note - Notification only works for registered peers with mailbox= definitions
+ in sip.conf
+ - We use the SIP Event package message-summary
+ MIME type defaults to "application/simple-message-summary";
+ */
+static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, char *vmexten)
+{
+ struct sip_request req;
+ struct ast_str *out = ast_str_alloca(500);
+
+ initreqprep(&req, p, SIP_NOTIFY);
+ add_header(&req, "Event", "message-summary");
+ add_header(&req, "Content-Type", default_notifymime);
+
+ ast_str_append(&out, 0, "Messages-Waiting: %s\r\n", newmsgs ? "yes" : "no");
+ ast_str_append(&out, 0, "Message-Account: sip:%s@%s\r\n",
+ S_OR(vmexten, default_vmexten), S_OR(p->fromdomain, ast_inet_ntoa(p->ourip.sin_addr)));
+ /* Cisco has a bug in the SIP stack where it can't accept the
+ (0/0) notification. This can temporarily be disabled in
+ sip.conf with the "buggymwi" option */
+ ast_str_append(&out, 0, "Voice-Message: %d/%d%s\r\n",
+ newmsgs, oldmsgs, (ast_test_flag(&p->flags[1], SIP_PAGE2_BUGGY_MWI) ? "" : " (0/0)"));
+
+ if (p->subscribed) {
+ if (p->expiry)
+ add_header(&req, "Subscription-State", "active");
+ else /* Expired */
+ add_header(&req, "Subscription-State", "terminated;reason=timeout");
+ }
+
+ add_header_contentLength(&req, out->used);
+ add_line(&req, out->str);
+
+ if (!p->initreq.headers)
+ initialize_initreq(p, &req);
+ return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+}
+
+/*! \brief Transmit SIP request unreliably (only used in sip_notify subsystem) */
+static int transmit_sip_request(struct sip_pvt *p, struct sip_request *req)
+{
+ if (!p->initreq.headers) /* Initialize first request before sending */
+ initialize_initreq(p, req);
+ return send_request(p, req, XMIT_UNRELIABLE, p->ocseq);
+}
+
+/*! \brief Notify a transferring party of the status of transfer */
+static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate)
+{
+ struct sip_request req;
+ char tmp[BUFSIZ/2];
+
+ reqprep(&req, p, SIP_NOTIFY, 0, 1);
+ snprintf(tmp, sizeof(tmp), "refer;id=%d", cseq);
+ add_header(&req, "Event", tmp);
+ add_header(&req, "Subscription-state", terminate ? "terminated;reason=noresource" : "active");
+ add_header(&req, "Content-Type", "message/sipfrag;version=2.0");
+ add_header(&req, "Allow", ALLOWED_METHODS);
+ add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
+
+ snprintf(tmp, sizeof(tmp), "SIP/2.0 %s\r\n", message);
+ add_header_contentLength(&req, strlen(tmp));
+ add_line(&req, tmp);
+
+ if (!p->initreq.headers)
+ initialize_initreq(p, &req);
+
+ p->lastnoninvite = p->ocseq;
+
+ return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+}
+
+static const struct _map_x_s regstatestrings[] = {
+ { REG_STATE_FAILED, "Failed" },
+ { REG_STATE_UNREGISTERED, "Unregistered"},
+ { REG_STATE_REGSENT, "Request Sent"},
+ { REG_STATE_AUTHSENT, "Auth. Sent"},
+ { REG_STATE_REGISTERED, "Registered"},
+ { REG_STATE_REJECTED, "Rejected"},
+ { REG_STATE_TIMEOUT, "Timeout"},
+ { REG_STATE_NOAUTH, "No Authentication"},
+ { -1, NULL } /* terminator */
+};
+
+/*! \brief Convert registration state status to string */
+static const char *regstate2str(enum sipregistrystate regstate)
+{
+ return map_x_s(regstatestrings, regstate, "Unknown");
+}
+
+/*! \brief Update registration with SIP Proxy.
+ * Called from the scheduler when the previous registration expires,
+ * so we don't have to cancel the pending event.
+ * We assume the reference so the sip_registry is valid, since it
+ * is stored in the scheduled event anyways.
+ */
+static int sip_reregister(const void *data)
+{
+ /* if we are here, we know that we need to reregister. */
+ struct sip_registry *r= registry_addref((struct sip_registry *) data);
+
+ /* if we couldn't get a reference to the registry object, punt */
+ if (!r)
+ return 0;
+
+ if (r->call && r->call->do_history)
+ append_history(r->call, "RegistryRenew", "Account: %s@%s", r->username, r->hostname);
+ /* Since registry's are only added/removed by the the monitor thread, this
+ may be overkill to reference/dereference at all here */
+ if (sipdebug)
+ ast_log(LOG_NOTICE, " -- Re-registration for %s@%s\n", r->username, r->hostname);
+
+ r->expire = -1;
+ __sip_do_register(r);
+ registry_unref(r);
+ return 0;
+}
+
+/*! \brief Register with SIP proxy */
+static int __sip_do_register(struct sip_registry *r)
+{
+ int res;
+
+ res = transmit_register(r, SIP_REGISTER, NULL, NULL);
+ return res;
+}
+
+/*! \brief Registration timeout, register again
+ * Registered as a timeout handler during transmit_register(),
+ * to retransmit the packet if a reply does not come back.
+ * This is called by the scheduler so the event is not pending anymore when
+ * we are called.
+ */
+static int sip_reg_timeout(const void *data)
+{
+
+ /* if we are here, our registration timed out, so we'll just do it over */
+ struct sip_registry *r = registry_addref((struct sip_registry *) data);
+ struct sip_pvt *p;
+ int res;
+
+ /* if we couldn't get a reference to the registry object, punt */
+ if (!r)
+ return 0;
+
+ ast_log(LOG_NOTICE, " -- Registration for '%s@%s' timed out, trying again (Attempt #%d)\n", r->username, r->hostname, r->regattempts);
+ /* If the initial tranmission failed, we may not have an existing dialog,
+ * so it is possible that r->call == NULL.
+ * Otherwise destroy it, as we have a timeout so we don't want it.
+ */
+ if (r->call) {
+ /* Unlink us, destroy old call. Locking is not relevant here because all this happens
+ in the single SIP manager thread. */
+ p = r->call;
+ p->needdestroy = 1;
+ /* Pretend to ACK anything just in case */
+ __sip_pretend_ack(p); /* XXX we need p locked, not sure we have */
+
+ /* decouple the two objects */
+ /* p->registry == r, so r has 2 refs, and the unref won't take the object away */
+ if (p->registry)
+ p->registry = registry_unref(p->registry);
+ r->call = dialog_unref(r->call);
+ }
+ /* If we have a limit, stop registration and give up */
+ if (global_regattempts_max && r->regattempts > global_regattempts_max) {
+ /* Ok, enough is enough. Don't try any more */
+ /* We could add an external notification here...
+ steal it from app_voicemail :-) */
+ ast_log(LOG_NOTICE, " -- Giving up forever trying to register '%s@%s'\n", r->username, r->hostname);
+ r->regstate = REG_STATE_FAILED;
+ } else {
+ r->regstate = REG_STATE_UNREGISTERED;
+ r->timeout = -1;
+ res=transmit_register(r, SIP_REGISTER, NULL, NULL);
+ }
+ manager_event(EVENT_FLAG_SYSTEM, "Registry", "ChannelType: SIP\r\nUsername: %s\r\nDomain: %s\r\nStatus: %s\r\n", r->username, r->hostname, regstate2str(r->regstate));
+ registry_unref(r);
+ return 0;
+}
+
+/*! \brief Transmit register to SIP proxy or UA
+ * auth = NULL on the initial registration (from sip_reregister())
+ */
+static int transmit_register(struct sip_registry *r, int sipmethod, const char *auth, const char *authheader)
+{
+ struct sip_request req;
+ char from[256];
+ char to[256];
+ char tmp[80];
+ char addr[80];
+ struct sip_pvt *p;
+
+ /* exit if we are already in process with this registrar ?*/
+ if ( r == NULL || ((auth==NULL) && (r->regstate==REG_STATE_REGSENT || r->regstate==REG_STATE_AUTHSENT))) {
+ ast_log(LOG_NOTICE, "Strange, trying to register %s@%s when registration already pending\n", r->username, r->hostname);
+ return 0;
+ }
+
+ if (r->call) { /* We have a registration */
+ if (!auth) {
+ ast_log(LOG_WARNING, "Already have a REGISTER going on to %s@%s?? \n", r->username, r->hostname);
+ return 0;
+ } else {
+ p = r->call;
+ make_our_tag(p->tag, sizeof(p->tag)); /* create a new local tag for every register attempt */
+ ast_string_field_set(p, theirtag, NULL); /* forget their old tag, so we don't match tags when getting response */
+ }
+ } else {
+ /* Build callid for registration if we haven't registered before */
+ if (!r->callid_valid) {
+ build_callid_registry(r, internip.sin_addr, default_fromdomain);
+ r->callid_valid = TRUE;
+ }
+ /* Allocate SIP dialog for registration */
+ if (!(p = sip_alloc( r->callid, NULL, 0, SIP_REGISTER))) {
+ ast_log(LOG_WARNING, "Unable to allocate registration transaction (memory or socket error)\n");
+ return 0;
+ }
+
+ if (p->do_history)
+ append_history(p, "RegistryInit", "Account: %s@%s", r->username, r->hostname);
+
+ p->outboundproxy = obproxy_get(p, NULL);
+
+ /* Find address to hostname */
+ if (create_addr(p, r->hostname)) {
+ /* we have what we hope is a temporary network error,
+ * probably DNS. We need to reschedule a registration try */
+ sip_destroy(p);
+ if (r->timeout > -1) {
+ r->timeout = ast_sched_replace(r->timeout, sched,
+ global_reg_timeout * 1000, sip_reg_timeout, r);
+ ast_log(LOG_WARNING, "Still have a registration timeout for %s@%s (create_addr() error), %d\n", r->username, r->hostname, r->timeout);
+ } else {
+ r->timeout = ast_sched_add(sched, global_reg_timeout*1000, sip_reg_timeout, r);
+ ast_log(LOG_WARNING, "Probably a DNS error for registration to %s@%s, trying REGISTER again (after %d seconds)\n", r->username, r->hostname, global_reg_timeout);
+ }
+ r->regattempts++;
+ return 0;
+ }
+ /* Copy back Call-ID in case create_addr changed it */
+ ast_string_field_set(r, callid, p->callid);
+ if (r->portno) {
+ p->sa.sin_port = htons(r->portno);
+ p->recv.sin_port = htons(r->portno);
+ } else /* Set registry port to the port set from the peer definition/srv or default */
+ r->portno = ntohs(p->sa.sin_port);
+ ast_set_flag(&p->flags[0], SIP_OUTGOING); /* Registration is outgoing call */
+ r->call = dialog_ref(p); /* Save pointer to SIP dialog */
+ p->registry = registry_addref(r); /* Add pointer to registry in packet */
+ if (!ast_strlen_zero(r->secret)) /* Secret (password) */
+ ast_string_field_set(p, peersecret, r->secret);
+ if (!ast_strlen_zero(r->md5secret))
+ ast_string_field_set(p, peermd5secret, r->md5secret);
+ /* User name in this realm
+ - if authuser is set, use that, otherwise use username */
+ if (!ast_strlen_zero(r->authuser)) {
+ ast_string_field_set(p, peername, r->authuser);
+ ast_string_field_set(p, authname, r->authuser);
+ } else if (!ast_strlen_zero(r->username)) {
+ ast_string_field_set(p, peername, r->username);
+ ast_string_field_set(p, authname, r->username);
+ ast_string_field_set(p, fromuser, r->username);
+ }
+ if (!ast_strlen_zero(r->username))
+ ast_string_field_set(p, username, r->username);
+ /* Save extension in packet */
+ if (!ast_strlen_zero(r->callback))
+ ast_string_field_set(p, exten, r->callback);
+
+ /* Set transport and port so the correct contact is built */
+ p->socket.type = r->transport;
+ p->socket.port = htons(r->portno);
+ /*
+ check which address we should use in our contact header
+ based on whether the remote host is on the external or
+ internal network so we can register through nat
+ */
+ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip);
+ build_contact(p);
+ }
+
+ /* set up a timeout */
+ if (auth == NULL) {
+ if (r->timeout > -1)
+ ast_log(LOG_WARNING, "Still have a registration timeout, #%d - deleting it\n", r->timeout);
+ r->timeout = ast_sched_replace(r->timeout, sched, global_reg_timeout * 1000, sip_reg_timeout, r);
+ ast_debug(1, "Scheduled a registration timeout for %s id #%d \n", r->hostname, r->timeout);
+ }
+
+ if (strchr(r->username, '@')) {
+ snprintf(from, sizeof(from), "<sip:%s>;tag=%s", r->username, p->tag);
+ if (!ast_strlen_zero(p->theirtag))
+ snprintf(to, sizeof(to), "<sip:%s>;tag=%s", r->username, p->theirtag);
+ else
+ snprintf(to, sizeof(to), "<sip:%s>", r->username);
+ } else {
+ snprintf(from, sizeof(from), "<sip:%s@%s>;tag=%s", r->username, p->tohost, p->tag);
+ if (!ast_strlen_zero(p->theirtag))
+ snprintf(to, sizeof(to), "<sip:%s@%s>;tag=%s", r->username, p->tohost, p->theirtag);
+ else
+ snprintf(to, sizeof(to), "<sip:%s@%s>", r->username, p->tohost);
+ }
+
+ /* Fromdomain is what we are registering to, regardless of actual
+ host name from SRV */
+ if (!ast_strlen_zero(p->fromdomain)) {
+ if (r->portno && r->portno != STANDARD_SIP_PORT)
+ snprintf(addr, sizeof(addr), "sip:%s:%d", p->fromdomain, r->portno);
+ else
+ snprintf(addr, sizeof(addr), "sip:%s", p->fromdomain);
+ } else {
+ if (r->portno && r->portno != STANDARD_SIP_PORT)
+ snprintf(addr, sizeof(addr), "sip:%s:%d", r->hostname, r->portno);
+ else
+ snprintf(addr, sizeof(addr), "sip:%s", r->hostname);
+ }
+ ast_string_field_set(p, uri, addr);
+
+ p->branch ^= ast_random();
+
+ init_req(&req, sipmethod, addr);
+
+ /* Add to CSEQ */
+ snprintf(tmp, sizeof(tmp), "%u %s", ++r->ocseq, sip_methods[sipmethod].text);
+ p->ocseq = r->ocseq;
+
+ build_via(p);
+ add_header(&req, "Via", p->via);
+ add_header(&req, "Max-Forwards", DEFAULT_MAX_FORWARDS);
+ add_header(&req, "From", from);
+ add_header(&req, "To", to);
+ add_header(&req, "Call-ID", p->callid);
+ add_header(&req, "CSeq", tmp);
+ if (!ast_strlen_zero(global_useragent))
+ add_header(&req, "User-Agent", global_useragent);
+
+
+ if (auth) /* Add auth header */
+ add_header(&req, authheader, auth);
+ else if (!ast_strlen_zero(r->nonce)) {
+ char digest[1024];
+
+ /* We have auth data to reuse, build a digest header.
+ * Note, this is not always useful because some parties do not
+ * like nonces to be reused (for good reasons!) so they will
+ * challenge us anyways.
+ */
+ if (sipdebug)
+ ast_debug(1, " >>> Re-using Auth data for %s@%s\n", r->username, r->hostname);
+ ast_string_field_set(p, realm, r->realm);
+ ast_string_field_set(p, nonce, r->nonce);
+ ast_string_field_set(p, domain, r->domain);
+ ast_string_field_set(p, opaque, r->opaque);
+ ast_string_field_set(p, qop, r->qop);
+ p->noncecount = ++r->noncecount;
+
+ memset(digest,0,sizeof(digest));
+ if(!build_reply_digest(p, sipmethod, digest, sizeof(digest)))
+ add_header(&req, "Authorization", digest);
+ else
+ ast_log(LOG_NOTICE, "No authorization available for authentication of registration to %s@%s\n", r->username, r->hostname);
+
+ }
+
+ snprintf(tmp, sizeof(tmp), "%d", r->expiry);
+ add_header(&req, "Expires", tmp);
+ add_header(&req, "Contact", p->our_contact);
+ add_header(&req, "Event", "registration");
+ add_header_contentLength(&req, 0);
+
+ initialize_initreq(p, &req);
+ if (sip_debug_test_pvt(p)) {
+ ast_verbose("REGISTER %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
+ }
+ r->regstate = auth ? REG_STATE_AUTHSENT : REG_STATE_REGSENT;
+ r->regattempts++; /* Another attempt */
+ ast_debug(4, "REGISTER attempt %d to %s@%s\n", r->regattempts, r->username, r->hostname);
+
+ return send_request(p, &req, XMIT_CRITICAL, p->ocseq);
+}
+
+/*! \brief Transmit text with SIP MESSAGE method */
+static int transmit_message_with_text(struct sip_pvt *p, const char *text)
+{
+ struct sip_request req;
+
+ reqprep(&req, p, SIP_MESSAGE, 0, 1);
+ add_text(&req, text);
+ return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+}
+
+/*! \brief Allocate SIP refer structure */
+static int sip_refer_allocate(struct sip_pvt *p)
+{
+ p->refer = ast_calloc(1, sizeof(struct sip_refer));
+ return p->refer ? 1 : 0;
+}
+
+/*! \brief Transmit SIP REFER message (initiated by the transfer() dialplan application
+ \note this is currently broken as we have no way of telling the dialplan
+ engine whether a transfer succeeds or fails.
+ \todo Fix the transfer() dialplan function so that a transfer may fail
+*/
+static int transmit_refer(struct sip_pvt *p, const char *dest)
+{
+ struct sip_request req = {
+ .headers = 0,
+ };
+ char from[256];
+ const char *of;
+ char *c;
+ char referto[256];
+ char *ttag, *ftag;
+ char *theirtag = ast_strdupa(p->theirtag);
+
+ if (sipdebug)
+ ast_debug(1, "SIP transfer of %s to %s\n", p->callid, dest);
+
+ /* Are we transfering an inbound or outbound call ? */
+ if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ of = get_header(&p->initreq, "To");
+ ttag = theirtag;
+ ftag = p->tag;
+ } else {
+ of = get_header(&p->initreq, "From");
+ ftag = theirtag;
+ ttag = p->tag;
+ }
+
+ ast_copy_string(from, of, sizeof(from));
+ of = get_in_brackets(from);
+ ast_string_field_set(p, from, of);
+ if (!strncasecmp(of, "sip:", 4))
+ of += 4;
+ else if (!strncasecmp(of, "sips:", 5))
+ of += 5;
+ else
+ ast_log(LOG_NOTICE, "From address missing 'sip(s):', using it anyway\n");
+ /* Get just the username part */
+ if ((c = strchr(dest, '@')))
+ c = NULL;
+ else if ((c = strchr(of, '@')))
+ *c++ = '\0';
+ if (c)
+ snprintf(referto, sizeof(referto), "<sip:%s@%s>", dest, c);
+ else
+ snprintf(referto, sizeof(referto), "<sip:%s>", dest);
+
+ /* save in case we get 407 challenge */
+ sip_refer_allocate(p);
+ ast_copy_string(p->refer->refer_to, referto, sizeof(p->refer->refer_to));
+ ast_copy_string(p->refer->referred_by, p->our_contact, sizeof(p->refer->referred_by));
+ p->refer->status = REFER_SENT; /* Set refer status */
+
+ reqprep(&req, p, SIP_REFER, 0, 1);
+ add_header(&req, "Max-Forwards", DEFAULT_MAX_FORWARDS);
+
+ add_header(&req, "Refer-To", referto);
+ add_header(&req, "Allow", ALLOWED_METHODS);
+ add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
+ if (!ast_strlen_zero(p->our_contact))
+ add_header(&req, "Referred-By", p->our_contact);
+
+ return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+ /* We should propably wait for a NOTIFY here until we ack the transfer */
+ /* Maybe fork a new thread and wait for a STATUS of REFER_200OK on the refer status before returning to app_transfer */
+
+ /*! \todo In theory, we should hang around and wait for a reply, before
+ returning to the dial plan here. Don't know really how that would
+ affect the transfer() app or the pbx, but, well, to make this
+ useful we should have a STATUS code on transfer().
+ */
+}
+
+
+/*! \brief Send SIP INFO dtmf message, see Cisco documentation on cisco.com */
+static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration)
+{
+ struct sip_request req;
+
+ reqprep(&req, p, SIP_INFO, 0, 1);
+ add_digit(&req, digit, duration, (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_SHORTINFO));
+ return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+}
+
+/*! \brief Send SIP INFO with video update request */
+static int transmit_info_with_vidupdate(struct sip_pvt *p)
+{
+ struct sip_request req;
+
+ reqprep(&req, p, SIP_INFO, 0, 1);
+ add_vidupdate(&req);
+ return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+}
+
+/*! \brief Transmit generic SIP request
+ returns XMIT_ERROR if transmit failed with a critical error (don't retry)
+*/
+static int transmit_request(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch)
+{
+ struct sip_request resp;
+
+ if (sipmethod == SIP_ACK)
+ p->invitestate = INV_CONFIRMED;
+
+ reqprep(&resp, p, sipmethod, seqno, newbranch);
+ if (sipmethod == SIP_CANCEL && p->answered_elsewhere)
+ add_header(&resp, "Reason:", "SIP;cause=200;text=\"Call completed elsewhere\"");
+
+ add_header_contentLength(&resp, 0);
+ return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq);
+}
+
+/*! \brief return the request and response heade for a 401 or 407 code */
+static void auth_headers(enum sip_auth_type code, char **header, char **respheader)
+{
+ if (code == WWW_AUTH) { /* 401 */
+ *header = "WWW-Authenticate";
+ *respheader = "Authorization";
+ } else if (code == PROXY_AUTH) { /* 407 */
+ *header = "Proxy-Authenticate";
+ *respheader = "Proxy-Authorization";
+ } else {
+ ast_verbose("-- wrong response code %d\n", code);
+ *header = *respheader = "Invalid";
+ }
+}
+
+/*! \brief Transmit SIP request, auth added */
+static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch)
+{
+ struct sip_request resp;
+
+ reqprep(&resp, p, sipmethod, seqno, newbranch);
+ if (!ast_strlen_zero(p->realm)) {
+ char digest[1024];
+
+ memset(digest, 0, sizeof(digest));
+ if(!build_reply_digest(p, sipmethod, digest, sizeof(digest))) {
+ char *dummy, *response;
+ enum sip_auth_type code = p->options ? p->options->auth_type : PROXY_AUTH; /* XXX force 407 if unknown */
+ auth_headers(code, &dummy, &response);
+ add_header(&resp, response, digest);
+ } else
+ ast_log(LOG_WARNING, "No authentication available for call %s\n", p->callid);
+ }
+ /* If we are hanging up and know a cause for that, send it in clear text to make
+ debugging easier. */
+ if (sipmethod == SIP_BYE && p->owner && p->owner->hangupcause) {
+ char buf[10];
+
+ add_header(&resp, "X-Asterisk-HangupCause", ast_cause2str(p->owner->hangupcause));
+ snprintf(buf, sizeof(buf), "%d", p->owner->hangupcause);
+ add_header(&resp, "X-Asterisk-HangupCauseCode", buf);
+ }
+
+ add_header_contentLength(&resp, 0);
+ return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq);
+}
+
+/*! \brief Remove registration data from realtime database or AST/DB when registration expires */
+static void destroy_association(struct sip_peer *peer)
+{
+ int realtimeregs = ast_check_realtime("sipregs");
+ char *tablename = (realtimeregs) ? "sipregs" : "sippeers";
+
+ if (!sip_cfg.ignore_regexpire) {
+ if (peer->rt_fromcontact)
+ ast_update_realtime(tablename, "name", peer->name, "fullcontact", "", "ipaddr", "", "port", "", "regseconds", "0", "defaultuser", "", "regserver", "", NULL);
+ else
+ ast_db_del("SIP/Registry", peer->name);
+ }
+}
+
+/*! \brief Expire registration of SIP peer */
+static int expire_register(const void *data)
+{
+ struct sip_peer *peer = (struct sip_peer *)data;
+
+ if (!peer) /* Hmmm. We have no peer. Weird. */
+ return 0;
+
+ memset(&peer->addr, 0, sizeof(peer->addr));
+
+ destroy_association(peer); /* remove registration data from storage */
+
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name);
+ register_peer_exten(peer, FALSE); /* Remove regexten */
+ peer->expire = -1;
+ ast_device_state_changed("SIP/%s", peer->name);
+
+ /* Do we need to release this peer from memory?
+ Only for realtime peers and autocreated peers
+ */
+ if (peer->is_realtime)
+ ast_debug(3,"-REALTIME- peer expired registration. Name: %s. Realtime peer objects now %d\n", peer->name, rpeerobjs);
+
+ if (peer->selfdestruct ||
+ ast_test_flag(&peer->flags[1], SIP_PAGE2_RTAUTOCLEAR)) {
+ peer = ASTOBJ_CONTAINER_UNLINK(&peerl, peer); /* Remove from peer list */
+ unref_peer(peer); /* Remove from memory */
+ }
+
+ return 0;
+}
+
+/*! \brief Poke peer (send qualify to check if peer is alive and well) */
+static int sip_poke_peer_s(const void *data)
+{
+ struct sip_peer *peer = (struct sip_peer *)data;
+
+ peer->pokeexpire = -1;
+ sip_poke_peer(peer);
+ return 0;
+}
+
+/*! \brief Get registration details from Asterisk DB */
+static void reg_source_db(struct sip_peer *peer)
+{
+ char data[256];
+ struct in_addr in;
+ int expiry;
+ int port;
+ char *scan, *addr, *port_str, *expiry_str, *username, *contact;
+
+ if (peer->rt_fromcontact)
+ return;
+ if (ast_db_get("SIP/Registry", peer->name, data, sizeof(data)))
+ return;
+
+ scan = data;
+ addr = strsep(&scan, ":");
+ port_str = strsep(&scan, ":");
+ expiry_str = strsep(&scan, ":");
+ username = strsep(&scan, ":");
+ contact = scan; /* Contact include sip: and has to be the last part of the database entry as long as we use : as a separator */
+
+ if (!inet_aton(addr, &in))
+ return;
+
+ if (port_str)
+ port = atoi(port_str);
+ else
+ return;
+
+ if (expiry_str)
+ expiry = atoi(expiry_str);
+ else
+ return;
+
+ if (username)
+ ast_copy_string(peer->username, username, sizeof(peer->username));
+ if (contact)
+ ast_copy_string(peer->fullcontact, contact, sizeof(peer->fullcontact));
+
+ ast_debug(2, "SIP Seeding peer from astdb: '%s' at %s@%s:%d for %d\n",
+ peer->name, peer->username, ast_inet_ntoa(in), port, expiry);
+
+ memset(&peer->addr, 0, sizeof(peer->addr));
+ peer->addr.sin_family = AF_INET;
+ peer->addr.sin_addr = in;
+ peer->addr.sin_port = htons(port);
+ if (sipsock < 0) {
+ /* SIP isn't up yet, so schedule a poke only, pretty soon */
+ peer->pokeexpire = ast_sched_replace(peer->pokeexpire, sched,
+ ast_random() % 5000 + 1, sip_poke_peer_s, peer);
+ } else
+ sip_poke_peer(peer);
+ peer->expire = ast_sched_replace(peer->expire, sched,
+ (expiry + 10) * 1000, expire_register, peer);
+ register_peer_exten(peer, TRUE);
+}
+
+/*! \brief Save contact header for 200 OK on INVITE */
+static int parse_ok_contact(struct sip_pvt *pvt, struct sip_request *req)
+{
+ char contact[BUFSIZ];
+ char *c;
+
+ /* Look for brackets */
+ ast_copy_string(contact, get_header(req, "Contact"), sizeof(contact));
+ c = get_in_brackets(contact);
+
+ /* Save full contact to call pvt for later bye or re-invite */
+ ast_string_field_set(pvt, fullcontact, c);
+
+ /* Save URI for later ACKs, BYE or RE-invites */
+ ast_string_field_set(pvt, okcontacturi, c);
+
+ /* We should return false for URI:s we can't handle,
+ like tel:, mailto:,ldap: etc */
+ return TRUE;
+}
+
+/*! \brief Change the other partys IP address based on given contact */
+static int set_address_from_contact(struct sip_pvt *pvt)
+{
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ int port;
+ char *host, *pt;
+ char *contact, *contact2;
+
+
+ if (ast_test_flag(&pvt->flags[0], SIP_NAT_ROUTE)) {
+ /* NAT: Don't trust the contact field. Just use what they came to us
+ with. */
+ pvt->sa = pvt->recv;
+ return 0;
+ }
+
+
+ /* Work on a copy */
+ contact = ast_strdupa(pvt->fullcontact);
+ contact2 = ast_strdupa(pvt->fullcontact);
+ /* We have only the part in <brackets> here so we just need to parse a SIP URI.*/
+
+ if (pvt->socket.type == SIP_TRANSPORT_TLS) {
+ if (parse_uri(contact, "sips:", &contact, NULL, &host, &pt, NULL)) {
+ if (parse_uri(contact2, "sip:", &contact, NULL, &host, &pt, NULL))
+ ast_log(LOG_NOTICE, "'%s' is not a valid SIP contact (missing sip:) trying to use anyway\n", contact);
+ }
+ port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_TLS_PORT;
+ } else {
+ if (parse_uri(contact, "sip:", &contact, NULL, &host, &pt, NULL))
+ ast_log(LOG_NOTICE, "'%s' is not a valid SIP contact (missing sip:) trying to use anyway\n", contact);
+ port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_SIP_PORT;
+ }
+
+ ast_verbose("--- set_address_from_contact host '%s'\n", host);
+
+ /* XXX This could block for a long time XXX */
+ /* We should only do this if it's a name, not an IP */
+ hp = ast_gethostbyname(host, &ahp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "Invalid host name in Contact: (can't resolve in DNS) : '%s'\n", host);
+ return -1;
+ }
+ pvt->sa.sin_family = AF_INET;
+ memcpy(&pvt->sa.sin_addr, hp->h_addr, sizeof(pvt->sa.sin_addr));
+ pvt->sa.sin_port = htons(port);
+
+ return 0;
+}
+
+
+/*! \brief Parse contact header and save registration (peer registration) */
+static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req)
+{
+ char contact[BUFSIZ];
+ char data[BUFSIZ];
+ const char *expires = get_header(req, "Expires");
+ int expiry = atoi(expires);
+ char *curi, *host, *pt, *curi2;
+ int port;
+ const char *useragent;
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ struct sockaddr_in oldsin;
+
+ ast_copy_string(contact, get_header(req, "Contact"), sizeof(contact));
+
+ if (ast_strlen_zero(expires)) { /* No expires header, try look in Contact: */
+ char *s = strcasestr(contact, ";expires=");
+ if (s) {
+ expires = strsep(&s, ";"); /* trim ; and beyond */
+ if (sscanf(expires + 9, "%d", &expiry) != 1)
+ expiry = default_expiry;
+ } else {
+ /* Nothing has been specified */
+ expiry = default_expiry;
+ }
+ }
+
+ pvt->socket = peer->socket = req->socket;
+
+ /* Look for brackets */
+ curi = contact;
+ if (strchr(contact, '<') == NULL) /* No <, check for ; and strip it */
+ strsep(&curi, ";"); /* This is Header options, not URI options */
+ curi = get_in_brackets(contact);
+ curi2 = ast_strdupa(curi);
+
+ /* if they did not specify Contact: or Expires:, they are querying
+ what we currently have stored as their contact address, so return
+ it
+ */
+ if (ast_strlen_zero(curi) && ast_strlen_zero(expires)) {
+ /* If we have an active registration, tell them when the registration is going to expire */
+ if (peer->expire > -1 && !ast_strlen_zero(peer->fullcontact))
+ pvt->expiry = ast_sched_when(sched, peer->expire);
+ return PARSE_REGISTER_QUERY;
+ } else if (!strcasecmp(curi, "*") || !expiry) { /* Unregister this peer */
+ /* This means remove all registrations and return OK */
+ memset(&peer->addr, 0, sizeof(peer->addr));
+ if (peer->expire > -1)
+ ast_sched_del(sched, peer->expire);
+ peer->expire = -1;
+
+ destroy_association(peer);
+
+ register_peer_exten(peer, FALSE); /* Remove extension from regexten= setting in sip.conf */
+ peer->fullcontact[0] = '\0';
+ peer->useragent[0] = '\0';
+ peer->sipoptions = 0;
+ peer->lastms = 0;
+
+ ast_verb(3, "Unregistered SIP '%s'\n", peer->name);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: SIP/%s\r\nPeerStatus: Unregistered\r\n", peer->name);
+ return PARSE_REGISTER_UPDATE;
+ }
+
+ /* Store whatever we got as a contact from the client */
+ ast_copy_string(peer->fullcontact, curi, sizeof(peer->fullcontact));
+
+ /* For the 200 OK, we should use the received contact */
+ ast_string_field_build(pvt, our_contact, "<%s>", curi);
+
+ /* Make sure it's a SIP URL */
+ if (pvt->socket.type == SIP_TRANSPORT_TLS) {
+ if (parse_uri(curi, "sips:", &curi, NULL, &host, &pt, NULL)) {
+ if (parse_uri(curi2, "sip:", &curi, NULL, &host, &pt, NULL))
+ ast_log(LOG_NOTICE, "Not a valid SIP contact (missing sip:) trying to use anyway\n");
+ }
+ port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_TLS_PORT;
+ } else {
+ if (parse_uri(curi, "sip:", &curi, NULL, &host, &pt, NULL))
+ ast_log(LOG_NOTICE, "Not a valid SIP contact (missing sip:) trying to use anyway\n");
+ port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_SIP_PORT;
+ }
+
+ oldsin = peer->addr;
+ if (!ast_test_flag(&peer->flags[0], SIP_NAT_ROUTE)) {
+ /* XXX This could block for a long time XXX */
+ hp = ast_gethostbyname(host, &ahp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "Invalid host '%s'\n", host);
+ return PARSE_REGISTER_FAILED;
+ }
+ peer->addr.sin_family = AF_INET;
+ memcpy(&peer->addr.sin_addr, hp->h_addr, sizeof(peer->addr.sin_addr));
+ peer->addr.sin_port = htons(port);
+ } else {
+ /* Don't trust the contact field. Just use what they came to us
+ with */
+ peer->addr = pvt->recv;
+ }
+
+ /* Save SIP options profile */
+ peer->sipoptions = pvt->sipoptions;
+
+ if (!ast_strlen_zero(curi) && ast_strlen_zero(peer->username))
+ ast_copy_string(peer->username, curi, sizeof(peer->username));
+
+ if (peer->expire > -1) {
+ ast_sched_del(sched, peer->expire);
+ peer->expire = -1;
+ }
+ if (expiry > max_expiry)
+ expiry = max_expiry;
+ if (expiry < min_expiry)
+ expiry = min_expiry;
+ peer->expire = peer->is_realtime ? -1 :
+ ast_sched_add(sched, (expiry + 10) * 1000, expire_register, peer);
+ pvt->expiry = expiry;
+ snprintf(data, sizeof(data), "%s:%d:%d:%s:%s", ast_inet_ntoa(peer->addr.sin_addr), ntohs(peer->addr.sin_port), expiry, peer->username, peer->fullcontact);
+ /* Saving TCP connections is useless, we won't be able to reconnect */
+ if (!peer->rt_fromcontact && (peer->socket.type & SIP_TRANSPORT_UDP))
+ ast_db_put("SIP/Registry", peer->name, data);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Registered\r\n", peer->name);
+
+ /* Is this a new IP address for us? */
+ if (inaddrcmp(&peer->addr, &oldsin)) {
+ sip_poke_peer(peer);
+ ast_verb(3, "Registered SIP '%s' at %s port %d expires %d\n", peer->name, ast_inet_ntoa(peer->addr.sin_addr), ntohs(peer->addr.sin_port), expiry);
+ register_peer_exten(peer, TRUE);
+ }
+
+ /* Save User agent */
+ useragent = get_header(req, "User-Agent");
+ if (strcasecmp(useragent, peer->useragent)) { /* XXX copy if they are different ? */
+ ast_copy_string(peer->useragent, useragent, sizeof(peer->useragent));
+ ast_verb(4, "Saved useragent \"%s\" for peer %s\n", peer->useragent, peer->name);
+ }
+ return PARSE_REGISTER_UPDATE;
+}
+
+/*! \brief Remove route from route list */
+static void free_old_route(struct sip_route *route)
+{
+ struct sip_route *next;
+
+ while (route) {
+ next = route->next;
+ ast_free(route);
+ route = next;
+ }
+}
+
+/*! \brief List all routes - mostly for debugging */
+static void list_route(struct sip_route *route)
+{
+ if (!route)
+ ast_verbose("list_route: no route\n");
+ else {
+ for (;route; route = route->next)
+ ast_verbose("list_route: hop: <%s>\n", route->hop);
+ }
+}
+
+/*! \brief Build route list from Record-Route header */
+static void build_route(struct sip_pvt *p, struct sip_request *req, int backwards)
+{
+ struct sip_route *thishop, *head, *tail;
+ int start = 0;
+ int len;
+ const char *rr, *contact, *c;
+
+ /* Once a persistant route is set, don't fool with it */
+ if (p->route && p->route_persistant) {
+ ast_debug(1, "build_route: Retaining previous route: <%s>\n", p->route->hop);
+ return;
+ }
+
+ if (p->route) {
+ free_old_route(p->route);
+ p->route = NULL;
+ }
+
+ p->route_persistant = backwards;
+
+ /* Build a tailq, then assign it to p->route when done.
+ * If backwards, we add entries from the head so they end up
+ * in reverse order. However, we do need to maintain a correct
+ * tail pointer because the contact is always at the end.
+ */
+ head = NULL;
+ tail = head;
+ /* 1st we pass through all the hops in any Record-Route headers */
+ for (;;) {
+ /* Each Record-Route header */
+ rr = __get_header(req, "Record-Route", &start);
+ if (*rr == '\0')
+ break;
+ for (; (rr = strchr(rr, '<')) ; rr += len) { /* Each route entry */
+ ++rr;
+ len = strcspn(rr, ">") + 1;
+ /* Make a struct route */
+ if ((thishop = ast_malloc(sizeof(*thishop) + len))) {
+ /* ast_calloc is not needed because all fields are initialized in this block */
+ ast_copy_string(thishop->hop, rr, len);
+ ast_debug(2, "build_route: Record-Route hop: <%s>\n", thishop->hop);
+ /* Link in */
+ if (backwards) {
+ /* Link in at head so they end up in reverse order */
+ thishop->next = head;
+ head = thishop;
+ /* If this was the first then it'll be the tail */
+ if (!tail)
+ tail = thishop;
+ } else {
+ thishop->next = NULL;
+ /* Link in at the end */
+ if (tail)
+ tail->next = thishop;
+ else
+ head = thishop;
+ tail = thishop;
+ }
+ }
+ }
+ }
+
+ /* Only append the contact if we are dealing with a strict router */
+ if (!head || (!ast_strlen_zero(head->hop) && strstr(head->hop,";lr") == NULL) ) {
+ /* 2nd append the Contact: if there is one */
+ /* Can be multiple Contact headers, comma separated values - we just take the first */
+ contact = get_header(req, "Contact");
+ if (!ast_strlen_zero(contact)) {
+ ast_debug(2, "build_route: Contact hop: %s\n", contact);
+ /* Look for <: delimited address */
+ c = strchr(contact, '<');
+ if (c) {
+ /* Take to > */
+ ++c;
+ len = strcspn(c, ">") + 1;
+ } else {
+ /* No <> - just take the lot */
+ c = contact;
+ len = strlen(contact) + 1;
+ }
+ if ((thishop = ast_malloc(sizeof(*thishop) + len))) {
+ /* ast_calloc is not needed because all fields are initialized in this block */
+ ast_copy_string(thishop->hop, c, len);
+ thishop->next = NULL;
+ /* Goes at the end */
+ if (tail)
+ tail->next = thishop;
+ else
+ head = thishop;
+ }
+ }
+ }
+
+ /* Store as new route */
+ p->route = head;
+
+ /* For debugging dump what we ended up with */
+ if (sip_debug_test_pvt(p))
+ list_route(p->route);
+}
+
+AST_THREADSTORAGE(check_auth_buf);
+#define CHECK_AUTH_BUF_INITLEN 256
+
+/*! \brief Check user authorization from peer definition
+ Some actions, like REGISTER and INVITEs from peers require
+ authentication (if peer have secret set)
+ \return 0 on success, non-zero on error
+*/
+static enum check_auth_result check_auth(struct sip_pvt *p, struct sip_request *req, const char *username,
+ const char *secret, const char *md5secret, int sipmethod,
+ char *uri, enum xmittype reliable, int ignore)
+{
+ const char *response;
+ char *reqheader, *respheader;
+ const char *authtoken;
+ char a1_hash[256];
+ char resp_hash[256]="";
+ char *c;
+ int wrongnonce = FALSE;
+ int good_response;
+ const char *usednonce = p->randdata;
+ struct ast_str *buf;
+ int res;
+
+ /* table of recognised keywords, and their value in the digest */
+ enum keys { K_RESP, K_URI, K_USER, K_NONCE, K_LAST };
+ struct x {
+ const char *key;
+ const char *s;
+ } *i, keys[] = {
+ [K_RESP] = { "response=", "" },
+ [K_URI] = { "uri=", "" },
+ [K_USER] = { "username=", "" },
+ [K_NONCE] = { "nonce=", "" },
+ [K_LAST] = { NULL, NULL}
+ };
+
+ /* Always OK if no secret */
+ if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret))
+ return AUTH_SUCCESSFUL;
+
+ /* Always auth with WWW-auth since we're NOT a proxy */
+ /* Using proxy-auth in a B2BUA may block proxy authorization in the same transaction */
+ response = "401 Unauthorized";
+
+ /*
+ * Note the apparent swap of arguments below, compared to other
+ * usages of auth_headers().
+ */
+ auth_headers(WWW_AUTH, &respheader, &reqheader);
+
+ authtoken = get_header(req, reqheader);
+ if (ignore && !ast_strlen_zero(p->randdata) && ast_strlen_zero(authtoken)) {
+ /* This is a retransmitted invite/register/etc, don't reconstruct authentication
+ information */
+ if (!reliable) {
+ /* Resend message if this was NOT a reliable delivery. Otherwise the
+ retransmission should get it */
+ transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 0);
+ /* Schedule auto destroy in 32 seconds (according to RFC 3261) */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return AUTH_CHALLENGE_SENT;
+ } else if (ast_strlen_zero(p->randdata) || ast_strlen_zero(authtoken)) {
+ /* We have no auth, so issue challenge and request authentication */
+ ast_string_field_build(p, randdata, "%08lx", ast_random()); /* Create nonce for challenge */
+ transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 0);
+ /* Schedule auto destroy in 32 seconds */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return AUTH_CHALLENGE_SENT;
+ }
+
+ /* --- We have auth, so check it */
+
+ /* Whoever came up with the authentication section of SIP can suck my %&#$&* for not putting
+ an example in the spec of just what it is you're doing a hash on. */
+
+ if (!(buf = ast_str_thread_get(&check_auth_buf, CHECK_AUTH_BUF_INITLEN)))
+ return AUTH_SECRET_FAILED; /*! XXX \todo need a better return code here */
+
+ /* Make a copy of the response and parse it */
+ res = ast_str_set(&buf, 0, "%s", authtoken);
+
+ if (res == AST_DYNSTR_BUILD_FAILED)
+ return AUTH_SECRET_FAILED; /*! XXX \todo need a better return code here */
+
+ c = buf->str;
+
+ while(c && *(c = ast_skip_blanks(c)) ) { /* lookup for keys */
+ for (i = keys; i->key != NULL; i++) {
+ const char *separator = ","; /* default */
+
+ if (strncasecmp(c, i->key, strlen(i->key)) != 0)
+ continue;
+ /* Found. Skip keyword, take text in quotes or up to the separator. */
+ c += strlen(i->key);
+ if (*c == '"') { /* in quotes. Skip first and look for last */
+ c++;
+ separator = "\"";
+ }
+ i->s = c;
+ strsep(&c, separator);
+ break;
+ }
+ if (i->key == NULL) /* not found, jump after space or comma */
+ strsep(&c, " ,");
+ }
+
+ /* Verify that digest username matches the username we auth as */
+ if (strcmp(username, keys[K_USER].s)) {
+ ast_log(LOG_WARNING, "username mismatch, have <%s>, digest has <%s>\n",
+ username, keys[K_USER].s);
+ /* Oops, we're trying something here */
+ return AUTH_USERNAME_MISMATCH;
+ }
+
+ /* Verify nonce from request matches our nonce. If not, send 401 with new nonce */
+ if (strcasecmp(p->randdata, keys[K_NONCE].s)) { /* XXX it was 'n'casecmp ? */
+ wrongnonce = TRUE;
+ usednonce = keys[K_NONCE].s;
+ }
+
+ if (!ast_strlen_zero(md5secret))
+ ast_copy_string(a1_hash, md5secret, sizeof(a1_hash));
+ else {
+ char a1[256];
+ snprintf(a1, sizeof(a1), "%s:%s:%s", username, global_realm, secret);
+ ast_md5_hash(a1_hash, a1);
+ }
+
+ /* compute the expected response to compare with what we received */
+ {
+ char a2[256];
+ char a2_hash[256];
+ char resp[256];
+
+ snprintf(a2, sizeof(a2), "%s:%s", sip_methods[sipmethod].text,
+ S_OR(keys[K_URI].s, uri));
+ ast_md5_hash(a2_hash, a2);
+ snprintf(resp, sizeof(resp), "%s:%s:%s", a1_hash, usednonce, a2_hash);
+ ast_md5_hash(resp_hash, resp);
+ }
+
+ good_response = keys[K_RESP].s &&
+ !strncasecmp(keys[K_RESP].s, resp_hash, strlen(resp_hash));
+ if (wrongnonce) {
+ ast_string_field_build(p, randdata, "%08lx", ast_random());
+ if (good_response) {
+ if (sipdebug)
+ ast_log(LOG_NOTICE, "Correct auth, but based on stale nonce received from '%s'\n", get_header(req, "To"));
+ /* We got working auth token, based on stale nonce . */
+ transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, TRUE);
+ } else {
+ /* Everything was wrong, so give the device one more try with a new challenge */
+ if (sipdebug)
+ ast_log(LOG_NOTICE, "Bad authentication received from '%s'\n", get_header(req, "To"));
+ transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, FALSE);
+ }
+
+ /* Schedule auto destroy in 32 seconds */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return AUTH_CHALLENGE_SENT;
+ }
+ if (good_response) {
+ append_history(p, "AuthOK", "Auth challenge succesful for %s", username);
+ return AUTH_SUCCESSFUL;
+ }
+
+ /* Ok, we have a bad username/secret pair */
+ /* Tell the UAS not to re-send this authentication data, because
+ it will continue to fail
+ */
+
+ return AUTH_SECRET_FAILED;
+}
+
+/*! \brief Change onhold state of a peer using a pvt structure */
+static void sip_peer_hold(struct sip_pvt *p, int hold)
+{
+ struct sip_peer *peer = find_peer(p->peername, NULL, 1);
+
+ if (!peer)
+ return;
+
+ /* If they put someone on hold, increment the value... otherwise decrement it */
+ ast_atomic_fetchadd_int(&peer->onHold, (hold ? +1 : -1));
+
+ /* Request device state update */
+ ast_device_state_changed("SIP/%s", peer->name);
+
+ return;
+}
+
+/*! \brief Receive MWI events that we have subscribed to */
+static void mwi_event_cb(const struct ast_event *event, void *userdata)
+{
+ struct sip_peer *peer = userdata;
+
+ ASTOBJ_RDLOCK(peer);
+ sip_send_mwi_to_peer(peer, event, 0);
+ ASTOBJ_UNLOCK(peer);
+}
+
+/*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem
+\note If you add an "hint" priority to the extension in the dial plan,
+ you will get notifications on device state changes */
+static int cb_extensionstate(char *context, char* exten, int state, void *data)
+{
+ struct sip_pvt *p = data;
+
+ sip_pvt_lock(p);
+
+ switch(state) {
+ case AST_EXTENSION_DEACTIVATED: /* Retry after a while */
+ case AST_EXTENSION_REMOVED: /* Extension is gone */
+ if (p->autokillid > -1)
+ sip_cancel_destroy(p); /* Remove subscription expiry for renewals */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); /* Delete subscription in 32 secs */
+ ast_verb(2, "Extension state: Watcher for hint %s %s. Notify User %s\n", exten, state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", p->username);
+ p->stateid = -1;
+ p->subscribed = NONE;
+ append_history(p, "Subscribestatus", "%s", state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated");
+ break;
+ default: /* Tell user */
+ p->laststate = state;
+ break;
+ }
+ if (p->subscribed != NONE) /* Only send state NOTIFY if we know the format */
+ transmit_state_notify(p, state, 1, FALSE);
+
+ ast_verb(2, "Extension Changed %s new state %s for Notify User %s\n", exten, ast_extension_state2str(state), p->username);
+
+ sip_pvt_unlock(p);
+
+ return 0;
+}
+
+/*! \brief Send a fake 401 Unauthorized response when the administrator
+ wants to hide the names of local users/peers from fishers
+ */
+static void transmit_fake_auth_response(struct sip_pvt *p, struct sip_request *req, int reliable)
+{
+ ast_string_field_build(p, randdata, "%08lx", ast_random()); /* Create nonce for challenge */
+ transmit_response_with_auth(p, "401 Unauthorized", req, p->randdata, reliable, "WWW-Authenticate", 0);
+}
+
+/*!
+ * Terminate the uri at the first ';' or space.
+ * Technically we should ignore escaped space per RFC3261 (19.1.1 etc)
+ * but don't do it for the time being. Remember the uri format is:
+ *\verbatim
+ *
+ * sip:user:password@host:port;uri-parameters?headers
+ * sips:user:password@host:port;uri-parameters?headers
+ *
+ *\endverbatim
+ */
+static char *terminate_uri(char *uri)
+{
+ char *t = uri;
+ while (*t && *t > ' ' && *t != ';')
+ t++;
+ *t = '\0';
+ return uri;
+}
+
+/*! \brief Verify registration of user
+ - Registration is done in several steps, first a REGISTER without auth
+ to get a challenge (nonce) then a second one with auth
+ - Registration requests are only matched with peers that are marked as "dynamic"
+ */
+static enum check_auth_result register_verify(struct sip_pvt *p, struct sockaddr_in *sin,
+ struct sip_request *req, char *uri)
+{
+ enum check_auth_result res = AUTH_NOT_FOUND;
+ struct sip_peer *peer;
+ char tmp[256];
+ char *name, *c;
+ char *domain;
+
+ terminate_uri(uri); /* warning, overwrite the string */
+
+ ast_copy_string(tmp, get_header(req, "To"), sizeof(tmp));
+ if (pedanticsipchecking)
+ ast_uri_decode(tmp);
+
+ c = get_in_brackets(tmp);
+ c = remove_uri_parameters(c);
+
+ if (!strncasecmp(c, "sip:", 4)) {
+ name = c + 4;
+ } else if (!strncasecmp(c, "sips:", 5)) {
+ name = c + 5;
+ } else {
+ name = c;
+ ast_log(LOG_NOTICE, "Invalid to address: '%s' from %s (missing sip:) trying to use anyway...\n", c, ast_inet_ntoa(sin->sin_addr));
+ }
+
+ /* XXX here too we interpret a missing @domain as a name-only
+ * URI, whereas the RFC says this is a domain-only uri.
+ */
+ /* Strip off the domain name */
+ if ((c = strchr(name, '@'))) {
+ *c++ = '\0';
+ domain = c;
+ if ((c = strchr(domain, ':'))) /* Remove :port */
+ *c = '\0';
+ if (!AST_LIST_EMPTY(&domain_list)) {
+ if (!check_sip_domain(domain, NULL, 0)) {
+ transmit_response(p, "404 Not found (unknown domain)", &p->initreq);
+ return AUTH_UNKNOWN_DOMAIN;
+ }
+ }
+ }
+ c = strchr(name, ';'); /* Remove any Username parameters */
+ if (c)
+ *c = '\0';
+
+ ast_string_field_set(p, exten, name);
+ build_contact(p);
+ peer = find_peer(name, NULL, 1);
+ if (!(peer && ast_apply_ha(peer->ha, sin))) {
+ /* Peer fails ACL check */
+ if (peer) {
+ unref_peer(peer);
+ peer = NULL;
+ res = AUTH_ACL_FAILED;
+ } else
+ res = AUTH_NOT_FOUND;
+ }
+
+ if (peer) {
+ /* Set Frame packetization */
+ if (p->rtp) {
+ ast_rtp_codec_setpref(p->rtp, &peer->prefs);
+ p->autoframing = peer->autoframing;
+ }
+ if (!peer->host_dynamic) {
+ ast_log(LOG_ERROR, "Peer '%s' is trying to register, but not configured as host=dynamic\n", peer->name);
+ res = AUTH_PEER_NOT_DYNAMIC;
+ } else {
+ ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_NAT);
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_REGISTERTRYING))
+ transmit_response(p, "100 Trying", req);
+ if (!(res = check_auth(p, req, peer->name, peer->secret, peer->md5secret, SIP_REGISTER, uri, XMIT_UNRELIABLE, req->ignore))) {
+ sip_cancel_destroy(p);
+
+ /* We have a successful registration attempt with proper authentication,
+ now, update the peer */
+ switch (parse_register_contact(p, peer, req)) {
+ case PARSE_REGISTER_FAILED:
+ ast_log(LOG_WARNING, "Failed to parse contact info\n");
+ transmit_response_with_date(p, "400 Bad Request", req);
+ peer->lastmsgssent = -1;
+ res = 0;
+ break;
+ case PARSE_REGISTER_QUERY:
+ transmit_response_with_date(p, "200 OK", req);
+ peer->lastmsgssent = -1;
+ res = 0;
+ break;
+ case PARSE_REGISTER_UPDATE:
+ update_peer(peer, p->expiry);
+ /* Say OK and ask subsystem to retransmit msg counter */
+ transmit_response_with_date(p, "200 OK", req);
+ if (!ast_test_flag((&peer->flags[1]), SIP_PAGE2_SUBSCRIBEMWIONLY))
+ peer->lastmsgssent = -1;
+ res = 0;
+ break;
+ }
+ }
+ }
+ }
+ if (!peer && autocreatepeer) {
+ /* Create peer if we have autocreate mode enabled */
+ peer = temp_peer(name);
+ if (peer) {
+ ASTOBJ_CONTAINER_LINK(&peerl, peer);
+ sip_cancel_destroy(p);
+ switch (parse_register_contact(p, peer, req)) {
+ case PARSE_REGISTER_FAILED:
+ ast_log(LOG_WARNING, "Failed to parse contact info\n");
+ transmit_response_with_date(p, "400 Bad Request", req);
+ peer->lastmsgssent = -1;
+ res = 0;
+ break;
+ case PARSE_REGISTER_QUERY:
+ transmit_response_with_date(p, "200 OK", req);
+ peer->lastmsgssent = -1;
+ res = 0;
+ break;
+ case PARSE_REGISTER_UPDATE:
+ /* Say OK and ask subsystem to retransmit msg counter */
+ transmit_response_with_date(p, "200 OK", req);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Registered\r\n", peer->name);
+ peer->lastmsgssent = -1;
+ res = 0;
+ break;
+ }
+ }
+ }
+ if (!res) {
+ ast_device_state_changed("SIP/%s", peer->name);
+ }
+ if (res < 0) {
+ switch (res) {
+ case AUTH_SECRET_FAILED:
+ /* Wrong password in authentication. Go away, don't try again until you fixed it */
+ transmit_response(p, "403 Forbidden (Bad auth)", &p->initreq);
+ break;
+ case AUTH_USERNAME_MISMATCH:
+ /* Username and digest username does not match.
+ Asterisk uses the From: username for authentication. We need the
+ users to use the same authentication user name until we support
+ proper authentication by digest auth name */
+ transmit_response(p, "403 Authentication user name does not match account name", &p->initreq);
+ break;
+ case AUTH_NOT_FOUND:
+ case AUTH_PEER_NOT_DYNAMIC:
+ case AUTH_ACL_FAILED:
+ if (global_alwaysauthreject) {
+ transmit_fake_auth_response(p, &p->initreq, 1);
+ } else {
+ /* URI not found */
+ if (res == AUTH_PEER_NOT_DYNAMIC)
+ transmit_response(p, "403 Forbidden", &p->initreq);
+ else
+ transmit_response(p, "404 Not found", &p->initreq);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (peer)
+ unref_peer(peer);
+
+ return res;
+}
+
+/*! \brief Translate referring cause */
+static void sip_set_redirstr(struct sip_pvt *p, char *reason) {
+
+ if (strcmp(reason, "unknown")==0) {
+ ast_string_field_set(p, redircause, "UNKNOWN");
+ } else if (strcmp(reason, "user-busy")==0) {
+ ast_string_field_set(p, redircause, "BUSY");
+ } else if (strcmp(reason, "no-answer")==0) {
+ ast_string_field_set(p, redircause, "NOANSWER");
+ } else if (strcmp(reason, "unavailable")==0) {
+ ast_string_field_set(p, redircause, "UNREACHABLE");
+ } else if (strcmp(reason, "unconditional")==0) {
+ ast_string_field_set(p, redircause, "UNCONDITIONAL");
+ } else if (strcmp(reason, "time-of-day")==0) {
+ ast_string_field_set(p, redircause, "UNKNOWN");
+ } else if (strcmp(reason, "do-not-disturb")==0) {
+ ast_string_field_set(p, redircause, "UNKNOWN");
+ } else if (strcmp(reason, "deflection")==0) {
+ ast_string_field_set(p, redircause, "UNKNOWN");
+ } else if (strcmp(reason, "follow-me")==0) {
+ ast_string_field_set(p, redircause, "UNKNOWN");
+ } else if (strcmp(reason, "out-of-service")==0) {
+ ast_string_field_set(p, redircause, "UNREACHABLE");
+ } else if (strcmp(reason, "away")==0) {
+ ast_string_field_set(p, redircause, "UNREACHABLE");
+ } else {
+ ast_string_field_set(p, redircause, "UNKNOWN");
+ }
+}
+
+/*! \brief Get referring dnis */
+static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq)
+{
+ char tmp[256], *exten, *rexten, *rdomain;
+ char *params, *reason = NULL;
+ struct sip_request *req;
+
+ req = oreq ? oreq : &p->initreq;
+
+ ast_copy_string(tmp, get_header(req, "Diversion"), sizeof(tmp));
+ if (ast_strlen_zero(tmp))
+ return 0;
+
+ exten = get_in_brackets(tmp);
+ if (!strncasecmp(exten, "sip:", 4)) {
+ exten += 4;
+ } else if (!strncasecmp(exten, "sips:", 5)) {
+ exten += 5;
+ } else {
+ ast_log(LOG_WARNING, "Huh? Not an RDNIS SIP header (%s)?\n", exten);
+ return -1;
+ }
+
+ /* Get diversion-reason param if present */
+ if ((params = strchr(tmp, ';'))) {
+ *params = '\0'; /* Cut off parameters */
+ params++;
+ while (*params == ';' || *params == ' ')
+ params++;
+ /* Check if we have a reason parameter */
+ if ((reason = strcasestr(params, "reason="))) {
+ reason+=7;
+ /* Remove enclosing double-quotes */
+ if (*reason == '"')
+ ast_strip_quoted(reason, "\"", "\"");
+ if (!ast_strlen_zero(reason)) {
+ sip_set_redirstr(p, reason);
+ if (p->owner) {
+ pbx_builtin_setvar_helper(p->owner, "__PRIREDIRECTREASON", p->redircause);
+ pbx_builtin_setvar_helper(p->owner, "__SIPREDIRECTREASON", reason);
+ }
+ }
+ }
+ }
+
+ rdomain = exten;
+ rexten = strsep(&rdomain, "@"); /* trim anything after @ */
+ if (p->owner)
+ pbx_builtin_setvar_helper(p->owner, "__SIPRDNISDOMAIN", rdomain);
+
+ if (sip_debug_test_pvt(p))
+ ast_verbose("RDNIS for this call is is %s (reason %s)\n", exten, reason ? reason : "");
+
+ ast_string_field_set(p, rdnis, rexten);
+
+ return 0;
+}
+
+/*! \brief Find out who the call is for.
+ We use the request uri as a destination.
+ This code assumes authentication has been done, so that the
+ device (peer/user) context is already set.
+ \return 0 on success (found a matching extension),
+ 1 for pickup extension or overlap dialling support (if we support it),
+ -1 on error.
+*/
+static int get_destination(struct sip_pvt *p, struct sip_request *oreq)
+{
+ char tmp[256] = "", *uri, *a;
+ char tmpf[256] = "", *from = NULL;
+ struct sip_request *req;
+ char *colon;
+
+ req = oreq;
+ if (!req)
+ req = &p->initreq;
+
+ /* Find the request URI */
+ if (req->rlPart2)
+ ast_copy_string(tmp, req->rlPart2, sizeof(tmp));
+
+ if (pedanticsipchecking)
+ ast_uri_decode(tmp);
+
+ uri = get_in_brackets(tmp);
+
+ if (!strncasecmp(uri, "sip:", 4)) {
+ uri += 4;
+ } else if (!strncasecmp(uri, "sips:", 5)) {
+ uri += 5;
+ } else {
+ ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", uri);
+ return -1;
+ }
+
+ /* Now find the From: caller ID and name */
+ /* XXX Why is this done in get_destination? Isn't it already done?
+ Needs to be checked
+ */
+ ast_copy_string(tmpf, get_header(req, "From"), sizeof(tmpf));
+ if (!ast_strlen_zero(tmpf)) {
+ if (pedanticsipchecking)
+ ast_uri_decode(tmpf);
+ from = get_in_brackets(tmpf);
+ }
+
+ if (!ast_strlen_zero(from)) {
+ if (!strncasecmp(from, "sip:", 4)) {
+ from += 4;
+ } else if (!strncasecmp(from, "sips:", 5)) {
+ from += 5;
+ } else {
+ ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", from);
+ return -1;
+ }
+ if ((a = strchr(from, '@')))
+ *a++ = '\0';
+ else
+ a = from; /* just a domain */
+ from = strsep(&from, ";"); /* Remove userinfo options */
+ a = strsep(&a, ";"); /* Remove URI options */
+ ast_string_field_set(p, fromdomain, a);
+ }
+
+ /* Skip any options and find the domain */
+
+ /* Get the target domain */
+ if ((a = strchr(uri, '@'))) {
+ *a++ = '\0';
+ } else { /* No username part */
+ a = uri;
+ uri = "s"; /* Set extension to "s" */
+ }
+ colon = strchr(a, ':'); /* Remove :port */
+ if (colon)
+ *colon = '\0';
+
+ uri = strsep(&uri, ";"); /* Remove userinfo options */
+ a = strsep(&a, ";"); /* Remove URI options */
+
+ ast_string_field_set(p, domain, a);
+
+ if (!AST_LIST_EMPTY(&domain_list)) {
+ char domain_context[AST_MAX_EXTENSION];
+
+ domain_context[0] = '\0';
+ if (!check_sip_domain(p->domain, domain_context, sizeof(domain_context))) {
+ if (!allow_external_domains && (req->method == SIP_INVITE || req->method == SIP_REFER)) {
+ ast_debug(1, "Got SIP %s to non-local domain '%s'; refusing request.\n", sip_methods[req->method].text, p->domain);
+ return -2;
+ }
+ }
+ /* If we have a context defined, overwrite the original context */
+ if (!ast_strlen_zero(domain_context))
+ ast_string_field_set(p, context, domain_context);
+ }
+
+ /* If the request coming in is a subscription and subscribecontext has been specified use it */
+ if (req->method == SIP_SUBSCRIBE && !ast_strlen_zero(p->subscribecontext))
+ ast_string_field_set(p, context, p->subscribecontext);
+
+ if (sip_debug_test_pvt(p))
+ ast_verbose("Looking for %s in %s (domain %s)\n", uri, p->context, p->domain);
+
+ /* If this is a subscription we actually just need to see if a hint exists for the extension */
+ if (req->method == SIP_SUBSCRIBE) {
+ char hint[AST_MAX_EXTENSION];
+ return (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, p->context, p->exten) ? 0 : -1);
+ } else {
+ /* Check the dialplan for the username part of the request URI,
+ the domain will be stored in the SIPDOMAIN variable
+ Since extensions.conf can have unescaped characters, try matching a decoded
+ uri in addition to the non-decoded uri
+ Return 0 if we have a matching extension */
+ char *decoded_uri = ast_strdupa(uri);
+ ast_uri_decode(decoded_uri);
+ if (ast_exists_extension(NULL, p->context, uri, 1, S_OR(p->cid_num, from)) || ast_exists_extension(NULL, p->context, decoded_uri, 1, S_OR(p->cid_num, from)) ||
+ !strcmp(uri, ast_pickup_ext())) {
+ if (!oreq)
+ ast_string_field_set(p, exten, uri);
+ return 0;
+ }
+ }
+
+ /* Return 1 for pickup extension or overlap dialling support (if we support it) */
+ if((ast_test_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP) &&
+ ast_canmatch_extension(NULL, p->context, uri, 1, S_OR(p->cid_num, from))) ||
+ !strncmp(uri, ast_pickup_ext(), strlen(uri))) {
+ return 1;
+ }
+
+ return -1;
+}
+
+/*! \brief Lock dialog lock and find matching pvt lock
+ - Their tag is fromtag, our tag is to-tag
+ - This means that in some transactions, totag needs to be their tag :-)
+ depending upon the direction
+ Returns a reference, remember to release it when done XXX
+*/
+static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *totag, const char *fromtag)
+{
+ struct sip_pvt *sip_pvt_ptr;
+
+
+ if (totag)
+ ast_debug(4, "Looking for callid %s (fromtag %s totag %s)\n", callid, fromtag ? fromtag : "<no fromtag>", totag ? totag : "<no totag>");
+
+ /* Search dialogs and find the match */
+ dialoglist_lock();
+ for (sip_pvt_ptr = dialoglist; sip_pvt_ptr; sip_pvt_ptr = sip_pvt_ptr->next) {
+ if (!strcmp(sip_pvt_ptr->callid, callid)) {
+ int match = 1;
+ char *ourtag = sip_pvt_ptr->tag;
+
+ /* Go ahead and lock it (and its owner) before returning */
+ sip_pvt_lock(sip_pvt_ptr);
+
+ /* Check if tags match. If not, this is not the call we want
+ (With a forking SIP proxy, several call legs share the
+ call id, but have different tags)
+ */
+ if (pedanticsipchecking && (strcmp(fromtag, sip_pvt_ptr->theirtag) || (!ast_strlen_zero(totag) && strcmp(totag, ourtag))))
+ match = 0;
+
+ if (!match) {
+ sip_pvt_unlock(sip_pvt_ptr);
+ continue;
+ }
+
+ if (totag)
+ ast_debug(4, "Matched %s call - their tag is %s Our tag is %s\n",
+ ast_test_flag(&sip_pvt_ptr->flags[0], SIP_OUTGOING) ? "OUTGOING": "INCOMING",
+ sip_pvt_ptr->theirtag, sip_pvt_ptr->tag);
+
+ /* deadlock avoidance... */
+ while (sip_pvt_ptr->owner && ast_channel_trylock(sip_pvt_ptr->owner)) {
+ sip_pvt_unlock(sip_pvt_ptr);
+ usleep(1);
+ sip_pvt_lock(sip_pvt_ptr);
+ }
+ break;
+ }
+ }
+ dialoglist_unlock();
+ if (!sip_pvt_ptr)
+ ast_debug(4, "Found no match for callid %s to-tag %s from-tag %s\n", callid, totag, fromtag);
+ return sip_pvt_ptr;
+}
+
+/*! \brief Call transfer support (the REFER method)
+ * Extracts Refer headers into pvt dialog structure */
+static int get_refer_info(struct sip_pvt *transferer, struct sip_request *outgoing_req)
+{
+
+ const char *p_referred_by = NULL;
+ char *h_refer_to = NULL;
+ char *h_referred_by = NULL;
+ char *refer_to;
+ const char *p_refer_to;
+ char *referred_by_uri = NULL;
+ char *ptr;
+ struct sip_request *req = NULL;
+ const char *transfer_context = NULL;
+ struct sip_refer *referdata;
+
+
+ req = outgoing_req;
+ referdata = transferer->refer;
+
+ if (!req)
+ req = &transferer->initreq;
+
+ p_refer_to = get_header(req, "Refer-To");
+ if (ast_strlen_zero(p_refer_to)) {
+ ast_log(LOG_WARNING, "Refer-To Header missing. Skipping transfer.\n");
+ return -2; /* Syntax error */
+ }
+ h_refer_to = ast_strdupa(p_refer_to);
+ refer_to = get_in_brackets(h_refer_to);
+ if (pedanticsipchecking)
+ ast_uri_decode(refer_to);
+
+ if (!strncasecmp(refer_to, "sip:", 4)) {
+ refer_to += 4; /* Skip sip: */
+ } else if (!strncasecmp(refer_to, "sips:", 5)) {
+ refer_to += 5;
+ } else {
+ ast_log(LOG_WARNING, "Can't transfer to non-sip: URI. (Refer-to: %s)?\n", refer_to);
+ return -3;
+ }
+
+ /* Get referred by header if it exists */
+ p_referred_by = get_header(req, "Referred-By");
+
+ /* Give useful transfer information to the dialplan */
+ if (transferer->owner) {
+ struct ast_channel *peer = ast_bridged_channel(transferer->owner);
+ if (peer) {
+ pbx_builtin_setvar_helper(peer, "SIPREFERRINGCONTEXT", transferer->context);
+ pbx_builtin_setvar_helper(peer, "SIPREFERREDBYHDR", p_referred_by);
+ }
+ }
+
+ if (!ast_strlen_zero(p_referred_by)) {
+ char *lessthan;
+ h_referred_by = ast_strdupa(p_referred_by);
+ if (pedanticsipchecking)
+ ast_uri_decode(h_referred_by);
+
+ /* Store referrer's caller ID name */
+ ast_copy_string(referdata->referred_by_name, h_referred_by, sizeof(referdata->referred_by_name));
+ if ((lessthan = strchr(referdata->referred_by_name, '<'))) {
+ *(lessthan - 1) = '\0'; /* Space */
+ }
+
+ referred_by_uri = get_in_brackets(h_referred_by);
+ if (!strncasecmp(referred_by_uri, "sip:", 4)) {
+ referred_by_uri += 4; /* Skip sip: */
+ } else if (!strncasecmp(referred_by_uri, "sips:", 5)) {
+ referred_by_uri += 5; /* Skip sips: */
+ } else {
+ ast_log(LOG_WARNING, "Huh? Not a sip: header (Referred-by: %s). Skipping.\n", referred_by_uri);
+ referred_by_uri = NULL;
+ }
+ }
+
+ /* Check for arguments in the refer_to header */
+ if ((ptr = strchr(refer_to, '?'))) { /* Search for arguments */
+ *ptr++ = '\0';
+ if (!strncasecmp(ptr, "REPLACES=", 9)) {
+ char *to = NULL, *from = NULL;
+
+ /* This is an attended transfer */
+ referdata->attendedtransfer = 1;
+ ast_copy_string(referdata->replaces_callid, ptr+9, sizeof(referdata->replaces_callid));
+ ast_uri_decode(referdata->replaces_callid);
+ if ((ptr = strchr(referdata->replaces_callid, ';'))) /* Find options */ {
+ *ptr++ = '\0';
+ }
+
+ if (ptr) {
+ /* Find the different tags before we destroy the string */
+ to = strcasestr(ptr, "to-tag=");
+ from = strcasestr(ptr, "from-tag=");
+ }
+
+ /* Grab the to header */
+ if (to) {
+ ptr = to + 7;
+ if ((to = strchr(ptr, '&')))
+ *to = '\0';
+ if ((to = strchr(ptr, ';')))
+ *to = '\0';
+ ast_copy_string(referdata->replaces_callid_totag, ptr, sizeof(referdata->replaces_callid_totag));
+ }
+
+ if (from) {
+ ptr = from + 9;
+ if ((to = strchr(ptr, '&')))
+ *to = '\0';
+ if ((to = strchr(ptr, ';')))
+ *to = '\0';
+ ast_copy_string(referdata->replaces_callid_fromtag, ptr, sizeof(referdata->replaces_callid_fromtag));
+ }
+
+ if (!pedanticsipchecking)
+ ast_debug(2,"Attended transfer: Will use Replace-Call-ID : %s (No check of from/to tags)\n", referdata->replaces_callid );
+ else
+ ast_debug(2,"Attended transfer: Will use Replace-Call-ID : %s F-tag: %s T-tag: %s\n", referdata->replaces_callid, referdata->replaces_callid_fromtag ? referdata->replaces_callid_fromtag : "<none>", referdata->replaces_callid_totag ? referdata->replaces_callid_totag : "<none>" );
+ }
+ }
+
+ if ((ptr = strchr(refer_to, '@'))) { /* Separate domain */
+ char *urioption = NULL, *domain;
+ *ptr++ = '\0';
+
+ if ((urioption = strchr(ptr, ';'))) /* Separate urioptions */
+ *urioption++ = '\0';
+
+ domain = ptr;
+ if ((ptr = strchr(domain, ':'))) /* Remove :port */
+ *ptr = '\0';
+
+ /* Save the domain for the dial plan */
+ ast_copy_string(referdata->refer_to_domain, domain, sizeof(referdata->refer_to_domain));
+ if (urioption)
+ ast_copy_string(referdata->refer_to_urioption, urioption, sizeof(referdata->refer_to_urioption));
+ }
+
+ if ((ptr = strchr(refer_to, ';'))) /* Remove options */
+ *ptr = '\0';
+ ast_copy_string(referdata->refer_to, refer_to, sizeof(referdata->refer_to));
+
+ if (referred_by_uri) {
+ if ((ptr = strchr(referred_by_uri, ';'))) /* Remove options */
+ *ptr = '\0';
+ ast_copy_string(referdata->referred_by, referred_by_uri, sizeof(referdata->referred_by));
+ } else {
+ referdata->referred_by[0] = '\0';
+ }
+
+ /* Determine transfer context */
+ if (transferer->owner) /* Mimic behaviour in res_features.c */
+ transfer_context = pbx_builtin_getvar_helper(transferer->owner, "TRANSFER_CONTEXT");
+
+ /* By default, use the context in the channel sending the REFER */
+ if (ast_strlen_zero(transfer_context)) {
+ transfer_context = S_OR(transferer->owner->macrocontext,
+ S_OR(transferer->context, default_context));
+ }
+
+ ast_copy_string(referdata->refer_to_context, transfer_context, sizeof(referdata->refer_to_context));
+
+ /* Either an existing extension or the parking extension */
+ if (ast_exists_extension(NULL, transfer_context, refer_to, 1, NULL) ) {
+ if (sip_debug_test_pvt(transferer)) {
+ ast_verbose("SIP transfer to extension %s@%s by %s\n", refer_to, transfer_context, referred_by_uri);
+ }
+ /* We are ready to transfer to the extension */
+ return 0;
+ }
+ if (sip_debug_test_pvt(transferer))
+ ast_verbose("Failed SIP Transfer to non-existing extension %s in context %s\n n", refer_to, transfer_context);
+
+ /* Failure, we can't find this extension */
+ return -1;
+}
+
+
+/*! \brief Call transfer support (old way, deprecated by the IETF)--*/
+static int get_also_info(struct sip_pvt *p, struct sip_request *oreq)
+{
+ char tmp[256] = "", *c, *a;
+ struct sip_request *req = oreq ? oreq : &p->initreq;
+ struct sip_refer *referdata = NULL;
+ const char *transfer_context = NULL;
+
+ if (!p->refer && !sip_refer_allocate(p))
+ return -1;
+
+ referdata = p->refer;
+
+ ast_copy_string(tmp, get_header(req, "Also"), sizeof(tmp));
+ c = get_in_brackets(tmp);
+
+ if (pedanticsipchecking)
+ ast_uri_decode(c);
+
+ if (!strncasecmp(c, "sip:", 4)) {
+ c += 4;
+ } else if (!strncasecmp(c, "sips:", 5)) {
+ c += 5;
+ } else {
+ ast_log(LOG_WARNING, "Huh? Not a SIP header in Also: transfer (%s)?\n", c);
+ return -1;
+ }
+
+ if ((a = strchr(c, ';'))) /* Remove arguments */
+ *a = '\0';
+
+ if ((a = strchr(c, '@'))) { /* Separate Domain */
+ *a++ = '\0';
+ ast_copy_string(referdata->refer_to_domain, a, sizeof(referdata->refer_to_domain));
+ }
+
+ if (sip_debug_test_pvt(p))
+ ast_verbose("Looking for %s in %s\n", c, p->context);
+
+ if (p->owner) /* Mimic behaviour in res_features.c */
+ transfer_context = pbx_builtin_getvar_helper(p->owner, "TRANSFER_CONTEXT");
+
+ /* By default, use the context in the channel sending the REFER */
+ if (ast_strlen_zero(transfer_context)) {
+ transfer_context = S_OR(p->owner->macrocontext,
+ S_OR(p->context, default_context));
+ }
+ if (ast_exists_extension(NULL, transfer_context, c, 1, NULL)) {
+ /* This is a blind transfer */
+ ast_debug(1,"SIP Bye-also transfer to Extension %s@%s \n", c, transfer_context);
+ ast_copy_string(referdata->refer_to, c, sizeof(referdata->refer_to));
+ ast_copy_string(referdata->referred_by, "", sizeof(referdata->referred_by));
+ ast_copy_string(referdata->refer_contact, "", sizeof(referdata->refer_contact));
+ referdata->refer_call = dialog_unref(referdata->refer_call);
+ /* Set new context */
+ ast_string_field_set(p, context, transfer_context);
+ return 0;
+ } else if (ast_canmatch_extension(NULL, p->context, c, 1, NULL)) {
+ return 1;
+ }
+
+ return -1;
+}
+
+/*! \brief check received= and rport= in a SIP response.
+ * If we get a response with received= and/or rport= in the Via:
+ * line, use them as 'p->ourip' (see RFC 3581 for rport,
+ * and RFC 3261 for received).
+ * Using these two fields SIP can produce the correct
+ * address and port in the SIP headers without the need for STUN.
+ * The address part is also reused for the media sessions.
+ * Note that ast_sip_ouraddrfor() still rewrites p->ourip
+ * if you specify externip/seternaddr/stunaddr.
+ */
+static attribute_unused void check_via_response(struct sip_pvt *p, struct sip_request *req)
+{
+ char via[256];
+ char *cur, *opts;
+
+ ast_copy_string(via, get_header(req, "Via"), sizeof(via));
+
+ /* Work on the leftmost value of the topmost Via header */
+ opts = strchr(via, ',');
+ if (opts)
+ *opts = '\0';
+
+ /* parse all relevant options */
+ opts = strchr(via, ';');
+ if (!opts)
+ return; /* no options to parse */
+ *opts++ = '\0';
+ while ( (cur = strsep(&opts, ";")) ) {
+ if (!strncmp(cur, "rport=", 6)) {
+ int port = strtol(cur+6, NULL, 10);
+ /* XXX add error checking */
+ p->ourip.sin_port = ntohs(port);
+ } else if (!strncmp(cur, "received=", 9)) {
+ if (ast_parse_arg(cur+9, PARSE_INADDR, &p->ourip))
+ ; /* XXX add error checking */
+ }
+ }
+}
+
+/*! \brief check Via: header for hostname, port and rport request/answer */
+static void check_via(struct sip_pvt *p, struct sip_request *req)
+{
+ char via[256];
+ char *c, *pt;
+ struct hostent *hp;
+ struct ast_hostent ahp;
+
+ ast_copy_string(via, get_header(req, "Via"), sizeof(via));
+
+ /* Work on the leftmost value of the topmost Via header */
+ c = strchr(via, ',');
+ if (c)
+ *c = '\0';
+
+ /* Check for rport */
+ c = strstr(via, ";rport");
+ if (c && (c[6] != '=')) /* rport query, not answer */
+ ast_set_flag(&p->flags[0], SIP_NAT_ROUTE);
+
+ c = strchr(via, ';');
+ if (c)
+ *c = '\0';
+
+ c = strchr(via, ' ');
+ if (c) {
+ *c = '\0';
+ c = ast_skip_blanks(c+1);
+ if (strcasecmp(via, "SIP/2.0/UDP") && strcasecmp(via, "SIP/2.0/TCP") && strcasecmp(via, "SIP/2.0/TLS")) {
+ ast_log(LOG_WARNING, "Don't know how to respond via '%s'\n", via);
+ return;
+ }
+ pt = strchr(c, ':');
+ if (pt)
+ *pt++ = '\0'; /* remember port pointer */
+ hp = ast_gethostbyname(c, &ahp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "'%s' is not a valid host\n", c);
+ return;
+ }
+ memset(&p->sa, 0, sizeof(p->sa));
+ p->sa.sin_family = AF_INET;
+ memcpy(&p->sa.sin_addr, hp->h_addr, sizeof(p->sa.sin_addr));
+ p->sa.sin_port = htons(pt ? atoi(pt) : STANDARD_SIP_PORT);
+
+ if (sip_debug_test_pvt(p)) {
+ const struct sockaddr_in *dst = sip_real_dst(p);
+ ast_verbose("Sending to %s : %d (%s)\n", ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), sip_nat_mode(p));
+ }
+ }
+}
+
+/*! \brief Get caller id name from SIP headers */
+static char *get_calleridname(const char *input, char *output, size_t outputsize)
+{
+ const char *end = strchr(input,'<'); /* first_bracket */
+ const char *tmp = strchr(input,'"'); /* first quote */
+ int bytes = 0;
+ int maxbytes = outputsize - 1;
+
+ if (!end || end == input) /* we require a part in brackets */
+ return NULL;
+
+ end--; /* move just before "<" */
+
+ if (tmp && tmp <= end) {
+ /* The quote (tmp) precedes the bracket (end+1).
+ * Find the matching quote and return the content.
+ */
+ end = strchr(tmp+1, '"');
+ if (!end)
+ return NULL;
+ bytes = (int) (end - tmp);
+ /* protect the output buffer */
+ if (bytes > maxbytes)
+ bytes = maxbytes;
+ ast_copy_string(output, tmp + 1, bytes);
+ } else {
+ /* No quoted string, or it is inside brackets. */
+ /* clear the empty characters in the begining*/
+ input = ast_skip_blanks(input);
+ /* clear the empty characters in the end */
+ while(*end && *end < 33 && end > input)
+ end--;
+ if (end >= input) {
+ bytes = (int) (end - input) + 2;
+ /* protect the output buffer */
+ if (bytes > maxbytes)
+ bytes = maxbytes;
+ ast_copy_string(output, input, bytes);
+ } else
+ return NULL;
+ }
+ return output;
+}
+
+/*! \brief Get caller id number from Remote-Party-ID header field
+ * Returns true if number should be restricted (privacy setting found)
+ * output is set to NULL if no number found
+ */
+static int get_rpid_num(const char *input, char *output, int maxlen)
+{
+ char *start;
+ char *end;
+
+ start = strchr(input,':');
+ if (!start) {
+ output[0] = '\0';
+ return 0;
+ }
+ start++;
+
+ /* we found "number" */
+ ast_copy_string(output,start,maxlen);
+ output[maxlen-1] = '\0';
+
+ end = strchr(output,'@');
+ if (end)
+ *end = '\0';
+ else
+ output[0] = '\0';
+ if (strstr(input,"privacy=full") || strstr(input,"privacy=uri"))
+ return AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
+
+ return 0;
+}
+
+/*!
+ * duplicate a list of channel variables, \return the copy.
+ */
+static struct ast_variable *copy_vars(struct ast_variable *src)
+{
+ struct ast_variable *res = NULL, *tmp, *v = NULL;
+
+ for (v = src ; v ; v = v->next) {
+ if ((tmp = ast_variable_new(v->name, v->value, v->file))) {
+ tmp->next = res;
+ res = tmp;
+ }
+ }
+ return res;
+}
+
+/*! \brief helper function for check_{user|peer}_ok() */
+static void replace_cid(struct sip_pvt *p, const char *rpid_num, const char *calleridname)
+{
+ /* replace callerid if rpid found, and not restricted */
+ if (!ast_strlen_zero(rpid_num) && ast_test_flag(&p->flags[0], SIP_TRUSTRPID)) {
+ char *tmp = ast_strdupa(rpid_num); /* XXX the copy can be done later */
+ if (!ast_strlen_zero(calleridname))
+ ast_string_field_set(p, cid_name, calleridname);
+ if (ast_is_shrinkable_phonenumber(tmp))
+ ast_shrink_phone_number(tmp);
+ ast_string_field_set(p, cid_num, tmp);
+ }
+}
+
+/*! \brief Validate user authentication */
+static enum check_auth_result check_user_ok(struct sip_pvt *p, char *of,
+ struct sip_request *req, int sipmethod, struct sockaddr_in *sin,
+ enum xmittype reliable,
+ char *rpid_num, char *calleridname, char *uri2)
+{
+ enum check_auth_result res;
+ struct sip_user *user = find_user(of, 1);
+ int debug=sip_debug_test_addr(sin);
+
+ /* Find user based on user name in the from header */
+ if (!user) {
+ if (debug)
+ ast_verbose("No user '%s' in SIP users list\n", of);
+ return AUTH_DONT_KNOW;
+ }
+ if (!ast_apply_ha(user->ha, sin)) {
+ if (debug)
+ ast_verbose("Found user '%s' for '%s', but fails host access\n",
+ user->name, of);
+ unref_user(user);
+ return AUTH_DONT_KNOW;
+ }
+ if (debug)
+ ast_verbose("Found user '%s' for '%s'\n", user->name, of);
+
+ ast_copy_flags(&p->flags[0], &user->flags[0], SIP_FLAGS_TO_COPY);
+ ast_copy_flags(&p->flags[1], &user->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ /* copy channel vars */
+ p->chanvars = copy_vars(user->chanvars);
+ p->prefs = user->prefs;
+ /* Set Frame packetization */
+ if (p->rtp) {
+ ast_rtp_codec_setpref(p->rtp, &p->prefs);
+ p->autoframing = user->autoframing;
+ }
+
+ replace_cid(p, rpid_num, calleridname);
+ do_setnat(p, ast_test_flag(&p->flags[0], SIP_NAT_ROUTE) );
+
+ if (!(res = check_auth(p, req, user->name, user->secret, user->md5secret, sipmethod, uri2, reliable, req->ignore))) {
+ sip_cancel_destroy(p);
+ ast_copy_flags(&p->flags[0], &user->flags[0], SIP_FLAGS_TO_COPY);
+ ast_copy_flags(&p->flags[1], &user->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ /* Copy SIP extensions profile from INVITE */
+ if (p->sipoptions)
+ user->sipoptions = p->sipoptions;
+
+ /* If we have a call limit, set flag */
+ if (user->call_limit)
+ ast_set_flag(&p->flags[0], SIP_CALL_LIMIT);
+ if (!ast_strlen_zero(user->context))
+ ast_string_field_set(p, context, user->context);
+ if (!ast_strlen_zero(user->cid_num) && !ast_strlen_zero(p->cid_num)) {
+ char *tmp = ast_strdupa(user->cid_num);
+ if (ast_is_shrinkable_phonenumber(tmp))
+ ast_shrink_phone_number(tmp);
+ ast_string_field_set(p, cid_num, tmp);
+ }
+ if (!ast_strlen_zero(user->cid_name) && !ast_strlen_zero(p->cid_num))
+ ast_string_field_set(p, cid_name, user->cid_name);
+ ast_string_field_set(p, username, user->name);
+ ast_string_field_set(p, peername, user->name);
+ ast_string_field_set(p, peersecret, user->secret);
+ ast_string_field_set(p, peermd5secret, user->md5secret);
+ ast_string_field_set(p, subscribecontext, user->subscribecontext);
+ ast_string_field_set(p, accountcode, user->accountcode);
+ ast_string_field_set(p, language, user->language);
+ ast_string_field_set(p, mohsuggest, user->mohsuggest);
+ ast_string_field_set(p, mohinterpret, user->mohinterpret);
+ p->allowtransfer = user->allowtransfer;
+ p->amaflags = user->amaflags;
+ p->callgroup = user->callgroup;
+ p->pickupgroup = user->pickupgroup;
+ if (user->callingpres) /* User callingpres setting will override RPID header */
+ p->callingpres = user->callingpres;
+
+ /* Set default codec settings for this call */
+ p->capability = user->capability; /* User codec choice */
+ p->jointcapability = user->capability; /* Our codecs */
+ if (p->peercapability) /* AND with peer's codecs */
+ p->jointcapability &= p->peercapability;
+ if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) ||
+ (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO))
+ p->noncodeccapability |= AST_RTP_DTMF;
+ else
+ p->noncodeccapability &= ~AST_RTP_DTMF;
+ p->jointnoncodeccapability = p->noncodeccapability;
+ if (p->t38.peercapability)
+ p->t38.jointcapability &= p->t38.peercapability;
+ p->maxcallbitrate = user->maxcallbitrate;
+ /* If we do not support video, remove video from call structure */
+ if ((!ast_test_flag(&p->flags[1], SIP_PAGE2_VIDEOSUPPORT) || !(p->capability & AST_FORMAT_VIDEO_MASK)) && p->vrtp) {
+ ast_rtp_destroy(p->vrtp);
+ p->vrtp = NULL;
+ }
+ /* If we do not support text, remove text from call structure */
+ if (!ast_test_flag(&p->flags[1], SIP_PAGE2_TEXTSUPPORT) && p->trtp) {
+ ast_rtp_destroy(p->trtp);
+ p->trtp = NULL;
+ }
+ }
+ unref_user(user);
+ return res;
+}
+
+/*! \brief Validate peer authentication */
+static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of,
+ struct sip_request *req, int sipmethod, struct sockaddr_in *sin,
+ struct sip_peer **authpeer,
+ enum xmittype reliable,
+ char *rpid_num, char *calleridname, char *uri2)
+{
+ enum check_auth_result res;
+ int debug=sip_debug_test_addr(sin);
+ struct sip_peer *peer;
+
+ /* For subscribes, match on peer name only; for other methods,
+ * match on IP address-port of the incoming request.
+ */
+ peer = (sipmethod == SIP_SUBSCRIBE) ? find_peer(of, NULL, 1) : find_peer(NULL, &p->recv, 1);
+
+ if (!peer) {
+ if (debug)
+ ast_verbose("No matching peer for '%s' from '%s:%d'\n",
+ of, ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port));
+ return AUTH_DONT_KNOW;
+ }
+
+ if (debug)
+ ast_verbose("Found peer '%s' for '%s' from %s:%d\n",
+ peer->name, of, ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port));
+
+ /* XXX what about p->prefs = peer->prefs; ? */
+ /* Set Frame packetization */
+ if (p->rtp) {
+ ast_rtp_codec_setpref(p->rtp, &peer->prefs);
+ p->autoframing = peer->autoframing;
+ }
+
+ /* Take the peer */
+ ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
+ ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+
+ /* Copy SIP extensions profile to peer */
+ /* XXX is this correct before a successful auth ? */
+ if (p->sipoptions)
+ peer->sipoptions = p->sipoptions;
+
+ replace_cid(p, rpid_num, calleridname);
+ do_setnat(p, ast_test_flag(&p->flags[0], SIP_NAT_ROUTE));
+
+ ast_string_field_set(p, peersecret, peer->secret);
+ ast_string_field_set(p, peermd5secret, peer->md5secret);
+ ast_string_field_set(p, subscribecontext, peer->subscribecontext);
+ ast_string_field_set(p, mohinterpret, peer->mohinterpret);
+ ast_string_field_set(p, mohsuggest, peer->mohsuggest);
+ if (peer->callingpres) /* Peer calling pres setting will override RPID */
+ p->callingpres = peer->callingpres;
+ if (peer->maxms && peer->lastms)
+ p->timer_t1 = peer->lastms < global_t1min ? global_t1min : peer->lastms;
+ else
+ p->timer_t1 = peer->timer_t1;
+
+ /* Set timer B to control transaction timeouts */
+ if (peer->timer_b)
+ p->timer_b = peer->timer_b;
+ else
+ p->timer_b = 64 * p->timer_t1;
+
+ if (ast_test_flag(&peer->flags[0], SIP_INSECURE_INVITE)) {
+ /* Pretend there is no required authentication */
+ ast_string_field_set(p, peersecret, NULL);
+ ast_string_field_set(p, peermd5secret, NULL);
+ }
+ if (!(res = check_auth(p, req, peer->name, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable, req->ignore))) {
+ ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
+ ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ /* If we have a call limit, set flag */
+ if (peer->call_limit)
+ ast_set_flag(&p->flags[0], SIP_CALL_LIMIT);
+ ast_string_field_set(p, peername, peer->name);
+ ast_string_field_set(p, authname, peer->name);
+
+ /* copy channel vars */
+ p->chanvars = copy_vars(peer->chanvars);
+ if (authpeer) {
+ (*authpeer) = ASTOBJ_REF(peer); /* Add a ref to the object here, to keep it in memory a bit longer if it is realtime */
+ }
+
+ if (!ast_strlen_zero(peer->username)) {
+ ast_string_field_set(p, username, peer->username);
+ /* Use the default username for authentication on outbound calls */
+ /* XXX this takes the name from the caller... can we override ? */
+ ast_string_field_set(p, authname, peer->username);
+ }
+ if (!ast_strlen_zero(peer->cid_num) && !ast_strlen_zero(p->cid_num)) {
+ char *tmp = ast_strdupa(peer->cid_num);
+ if (ast_is_shrinkable_phonenumber(tmp))
+ ast_shrink_phone_number(tmp);
+ ast_string_field_set(p, cid_num, tmp);
+ }
+ if (!ast_strlen_zero(peer->cid_name) && !ast_strlen_zero(p->cid_name))
+ ast_string_field_set(p, cid_name, peer->cid_name);
+ ast_string_field_set(p, fullcontact, peer->fullcontact);
+ if (!ast_strlen_zero(peer->context))
+ ast_string_field_set(p, context, peer->context);
+ ast_string_field_set(p, peersecret, peer->secret);
+ ast_string_field_set(p, peermd5secret, peer->md5secret);
+ ast_string_field_set(p, language, peer->language);
+ ast_string_field_set(p, accountcode, peer->accountcode);
+ p->amaflags = peer->amaflags;
+ p->callgroup = peer->callgroup;
+ p->pickupgroup = peer->pickupgroup;
+ p->capability = peer->capability;
+ p->prefs = peer->prefs;
+ p->jointcapability = peer->capability;
+ if (p->peercapability)
+ p->jointcapability &= p->peercapability;
+ p->maxcallbitrate = peer->maxcallbitrate;
+ if ((!ast_test_flag(&p->flags[1], SIP_PAGE2_VIDEOSUPPORT) || !(p->capability & AST_FORMAT_VIDEO_MASK)) && p->vrtp) {
+ ast_rtp_destroy(p->vrtp);
+ p->vrtp = NULL;
+ }
+ if ((!ast_test_flag(&p->flags[1], SIP_PAGE2_TEXTSUPPORT) || !(p->capability & AST_FORMAT_TEXT_MASK)) && p->trtp) {
+ ast_rtp_destroy(p->trtp);
+ p->trtp = NULL;
+ }
+ if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) ||
+ (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO))
+ p->noncodeccapability |= AST_RTP_DTMF;
+ else
+ p->noncodeccapability &= ~AST_RTP_DTMF;
+ p->jointnoncodeccapability = p->noncodeccapability;
+ if (p->t38.peercapability)
+ p->t38.jointcapability &= p->t38.peercapability;
+ }
+ unref_peer(peer);
+ return res;
+}
+
+
+/*! \brief Check if matching user or peer is defined
+ Match user on From: user name and peer on IP/port
+ This is used on first invite (not re-invites) and subscribe requests
+ \return 0 on success, non-zero on failure
+*/
+static enum check_auth_result check_user_full(struct sip_pvt *p, struct sip_request *req,
+ int sipmethod, char *uri, enum xmittype reliable,
+ struct sockaddr_in *sin, struct sip_peer **authpeer)
+{
+ char from[256];
+ char *dummy; /* dummy return value for parse_uri */
+ char *domain; /* dummy return value for parse_uri */
+ char *of, *of2;
+ char rpid_num[50];
+ const char *rpid;
+ enum check_auth_result res;
+ char calleridname[50];
+ char *uri2 = ast_strdupa(uri);
+
+ terminate_uri(uri2); /* trim extra stuff */
+
+ ast_copy_string(from, get_header(req, "From"), sizeof(from));
+ if (pedanticsipchecking)
+ ast_uri_decode(from);
+ /* XXX here tries to map the username for invite things */
+ memset(calleridname, 0, sizeof(calleridname));
+ get_calleridname(from, calleridname, sizeof(calleridname));
+ if (calleridname[0])
+ ast_string_field_set(p, cid_name, calleridname);
+
+ rpid = get_header(req, "Remote-Party-ID");
+ memset(rpid_num, 0, sizeof(rpid_num));
+ if (!ast_strlen_zero(rpid))
+ p->callingpres = get_rpid_num(rpid, rpid_num, sizeof(rpid_num));
+
+ of = get_in_brackets(from);
+ if (ast_strlen_zero(p->exten)) {
+ char *t = uri2;
+ if (!strncasecmp(t, "sip:", 4))
+ t+= 4;
+ else if (!strncasecmp(t, "sips:", 5))
+ t += 5;
+ ast_string_field_set(p, exten, t);
+ t = strchr(p->exten, '@');
+ if (t)
+ *t = '\0';
+ if (ast_strlen_zero(p->our_contact))
+ build_contact(p);
+ }
+ /* save the URI part of the From header */
+ ast_string_field_set(p, from, of);
+
+ of2 = ast_strdupa(of);
+
+ /* ignore all fields but name */
+ if (p->socket.type == SIP_TRANSPORT_TLS) {
+ if (parse_uri(of, "sips:", &of, &dummy, &domain, &dummy, &dummy)) {
+ if (parse_uri(of2, "sip:", &of, &dummy, &domain, &dummy, &dummy))
+ ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n");
+ }
+ } else {
+ if (parse_uri(of, "sip:", &of, &dummy, &domain, &dummy, &dummy))
+ ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n");
+ }
+
+ if (ast_strlen_zero(of)) {
+ /* XXX note: the original code considered a missing @host
+ * as a username-only URI. The SIP RFC (19.1.1) says that
+ * this is wrong, and it should be considered as a domain-only URI.
+ * For backward compatibility, we keep this block, but it is
+ * really a mistake and should go away.
+ */
+ of = domain;
+ }
+ {
+ char *tmp = ast_strdupa(of);
+ /* We need to be able to handle auth-headers looking like
+ <sip:8164444422;phone-context=+1@1.2.3.4:5060;user=phone;tag=SDadkoa01-gK0c3bdb43>
+ */
+ tmp = strsep(&tmp, ";");
+ if (ast_is_shrinkable_phonenumber(tmp))
+ ast_shrink_phone_number(tmp);
+ ast_string_field_set(p, cid_num, tmp);
+ }
+ if (ast_strlen_zero(of))
+ return AUTH_SUCCESSFUL;
+
+ if (global_match_auth_username) {
+ /*
+ * XXX This is experimental code to grab the search key from the
+ * Auth header's username instead of the 'From' name, if available.
+ * Do not enable this block unless you understand the side effects (if any!)
+ * Note, the search for "username" should be done in a more robust way.
+ * Note2, at the moment we check both fields, though maybe we should
+ * pick one or another depending on the request ? XXX
+ */
+ const char *hdr = get_header(req, "Authorization");
+ if (ast_strlen_zero(hdr))
+ hdr = get_header(req, "Proxy-Authorization");
+
+ if ( !ast_strlen_zero(hdr) && (hdr = strstr(hdr, "username=\"")) ) {
+ ast_copy_string(from, hdr + strlen("username=\""), sizeof(from));
+ of = from;
+ of = strsep(&of, "\"");
+ }
+ }
+
+ if (!authpeer) {
+ /* If we are looking for a peer, don't check the
+ user objects (or realtime) */
+ res = check_user_ok(p, of, req, sipmethod, sin,
+ reliable, rpid_num, calleridname, uri2);
+ if (res != AUTH_DONT_KNOW)
+ return res;
+ }
+
+ res = check_peer_ok(p, of, req, sipmethod, sin,
+ authpeer, reliable, rpid_num, calleridname, uri2);
+ if (res != AUTH_DONT_KNOW)
+ return res;
+
+ /* Finally, apply the guest policy */
+ if (global_allowguest) {
+ replace_cid(p, rpid_num, calleridname);
+ res = AUTH_SUCCESSFUL;
+ } else if (global_alwaysauthreject)
+ res = AUTH_FAKE_AUTH; /* reject with fake authorization request */
+ else
+ res = AUTH_SECRET_FAILED; /* we don't want any guests, authentication will fail */
+
+ return res;
+}
+
+/*! \brief Find user
+ If we get a match, this will add a reference pointer to the user object in ASTOBJ, that needs to be unreferenced
+*/
+static int check_user(struct sip_pvt *p, struct sip_request *req, int sipmethod, char *uri, enum xmittype reliable, struct sockaddr_in *sin)
+{
+ return check_user_full(p, req, sipmethod, uri, reliable, sin, NULL);
+}
+
+/*! \brief Get text out of a SIP MESSAGE packet */
+static int get_msg_text(char *buf, int len, struct sip_request *req)
+{
+ int x;
+ int y;
+
+ buf[0] = '\0';
+ y = len - strlen(buf) - 5;
+ if (y < 0)
+ y = 0;
+ for (x=0;x<req->lines;x++) {
+ strncat(buf, req->line[x], y); /* safe */
+ y -= strlen(req->line[x]) + 1;
+ if (y < 0)
+ y = 0;
+ if (y != 0)
+ strcat(buf, "\n"); /* safe */
+ }
+ return 0;
+}
+
+
+/*! \brief Receive SIP MESSAGE method messages
+\note We only handle messages within current calls currently
+ Reference: RFC 3428 */
+static void receive_message(struct sip_pvt *p, struct sip_request *req)
+{
+ char buf[1024];
+ struct ast_frame f;
+ const char *content_type = get_header(req, "Content-Type");
+
+ if (strcmp(content_type, "text/plain")) { /* No text/plain attachment */
+ transmit_response(p, "415 Unsupported Media Type", req); /* Good enough, or? */
+ if (!p->owner)
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return;
+ }
+
+ if (get_msg_text(buf, sizeof(buf), req)) {
+ ast_log(LOG_WARNING, "Unable to retrieve text from %s\n", p->callid);
+ transmit_response(p, "202 Accepted", req);
+ if (!p->owner)
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return;
+ }
+
+ if (p->owner) {
+ if (sip_debug_test_pvt(p))
+ ast_verbose("Message received: '%s'\n", buf);
+ memset(&f, 0, sizeof(f));
+ f.frametype = AST_FRAME_TEXT;
+ f.subclass = 0;
+ f.offset = 0;
+ f.data = buf;
+ f.datalen = strlen(buf);
+ ast_queue_frame(p->owner, &f);
+ transmit_response(p, "202 Accepted", req); /* We respond 202 accepted, since we relay the message */
+ } else { /* Message outside of a call, we do not support that */
+ ast_log(LOG_WARNING,"Received message to %s from %s, dropped it...\n Content-Type:%s\n Message: %s\n", get_header(req,"To"), get_header(req,"From"), content_type, buf);
+ transmit_response(p, "405 Method Not Allowed", req); /* Good enough, or? */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return;
+}
+
+/*! \brief CLI Command to show calls within limits set by call_limit */
+static char *sip_show_inuse(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT "%-25.25s %-15.15s %-15.15s \n"
+#define FORMAT2 "%-25.25s %-15.15s %-15.15s \n"
+ char ilimits[40];
+ char iused[40];
+ int showall = FALSE;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show inuse";
+ e->usage =
+ "Usage: sip show inuse [all]\n"
+ " List all SIP users and peers usage counters and limits.\n"
+ " Add option \"all\" to show all devices, not only those with a limit.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc < 3)
+ return CLI_SHOWUSAGE;
+
+ if (a->argc == 4 && !strcmp(a->argv[3],"all"))
+ showall = TRUE;
+
+ ast_cli(a->fd, FORMAT, "* User name", "In use", "Limit");
+ ASTOBJ_CONTAINER_TRAVERSE(&userl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ if (iterator->call_limit)
+ snprintf(ilimits, sizeof(ilimits), "%d", iterator->call_limit);
+ else
+ ast_copy_string(ilimits, "N/A", sizeof(ilimits));
+ snprintf(iused, sizeof(iused), "%d", iterator->inUse);
+ if (showall || iterator->call_limit)
+ ast_cli(a->fd, FORMAT2, iterator->name, iused, ilimits);
+ ASTOBJ_UNLOCK(iterator);
+ } while (0) );
+
+ ast_cli(a->fd, FORMAT, "* Peer name", "In use", "Limit");
+
+ ASTOBJ_CONTAINER_TRAVERSE(&peerl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ if (iterator->call_limit)
+ snprintf(ilimits, sizeof(ilimits), "%d", iterator->call_limit);
+ else
+ ast_copy_string(ilimits, "N/A", sizeof(ilimits));
+ snprintf(iused, sizeof(iused), "%d/%d/%d", iterator->inUse, iterator->inRinging, iterator->onHold);
+ if (showall || iterator->call_limit)
+ ast_cli(a->fd, FORMAT2, iterator->name, iused, ilimits);
+ ASTOBJ_UNLOCK(iterator);
+ } while (0) );
+
+ return CLI_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+
+/*! \brief Convert transfer mode to text string */
+static char *transfermode2str(enum transfermodes mode)
+{
+ if (mode == TRANSFER_OPENFORALL)
+ return "open";
+ else if (mode == TRANSFER_CLOSED)
+ return "closed";
+ return "strict";
+}
+
+static struct _map_x_s natmodes[] = {
+ { SIP_NAT_NEVER, "No"},
+ { SIP_NAT_ROUTE, "Route"},
+ { SIP_NAT_ALWAYS, "Always"},
+ { SIP_NAT_RFC3581, "RFC3581"},
+ { -1, NULL}, /* terminator */
+};
+
+/*! \brief Convert NAT setting to text string */
+static const char *nat2str(int nat)
+{
+ return map_x_s(natmodes, nat, "Unknown");
+}
+
+/*! \brief Report Peer status in character string
+ * \return 0 if peer is unreachable, 1 if peer is online, -1 if unmonitored
+ */
+
+
+/* Session-Timer Modes */
+static struct _map_x_s stmodes[] = {
+ { SESSION_TIMER_MODE_ACCEPT, "Accept"},
+ { SESSION_TIMER_MODE_ORIGINATE, "Originate"},
+ { SESSION_TIMER_MODE_REFUSE, "Refuse"},
+ { -1, NULL},
+};
+
+static const char *stmode2str(enum st_mode m)
+{
+ return map_x_s(stmodes, m, "Unknown");
+}
+
+static enum st_mode str2stmode(const char *s)
+{
+ return map_s_x(stmodes, s, -1);
+}
+
+/* Session-Timer Refreshers */
+static struct _map_x_s strefreshers[] = {
+ { SESSION_TIMER_REFRESHER_AUTO, "auto"},
+ { SESSION_TIMER_REFRESHER_UAC, "uac"},
+ { SESSION_TIMER_REFRESHER_UAS, "uas"},
+ { -1, NULL},
+};
+
+static const char *strefresher2str(enum st_refresher r)
+{
+ return map_x_s(strefreshers, r, "Unknown");
+}
+
+static enum st_refresher str2strefresher(const char *s)
+{
+ return map_s_x(strefreshers, s, -1);
+}
+
+
+static int peer_status(struct sip_peer *peer, char *status, int statuslen)
+{
+ int res = 0;
+ if (peer->maxms) {
+ if (peer->lastms < 0) {
+ ast_copy_string(status, "UNREACHABLE", statuslen);
+ } else if (peer->lastms > peer->maxms) {
+ snprintf(status, statuslen, "LAGGED (%d ms)", peer->lastms);
+ res = 1;
+ } else if (peer->lastms) {
+ snprintf(status, statuslen, "OK (%d ms)", peer->lastms);
+ res = 1;
+ } else {
+ ast_copy_string(status, "UNKNOWN", statuslen);
+ }
+ } else {
+ ast_copy_string(status, "Unmonitored", statuslen);
+ /* Checking if port is 0 */
+ res = -1;
+ }
+ return res;
+}
+
+/*! \brief return Yes or No depending on the argument.
+ * This is used in many places in CLI command, having a function to generate
+ * this helps maintaining a consistent output (and possibly emitting the
+ * output in other languages, at some point).
+ */
+static const char *cli_yesno(int x)
+{
+ return x ? "Yes" : "No";
+}
+
+/*! \brief Show active TCP connections */
+static char *sip_show_tcp(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct sip_threadinfo *th;
+
+#define FORMAT2 "%-30.30s %3.6s %9.9s %6.6s\n"
+#define FORMAT "%-30.30s %-6d %-9.9s %-6.6s\n"
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show tcp";
+ e->usage =
+ "Usage: sip show tcp\n"
+ " Lists all active TCP/TLS sessions.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ ast_cli(a->fd, FORMAT2, "Host", "Port", "Transport", "Type");
+ AST_LIST_LOCK(&threadl);
+ AST_LIST_TRAVERSE(&threadl, th, list) {
+ ast_cli(a->fd, FORMAT, ast_inet_ntoa(th->ser->requestor.sin_addr),
+ ntohs(th->ser->requestor.sin_port),
+ get_transport(th->type),
+ (th->ser->client ? "Client" : "Server"));
+
+ }
+ AST_LIST_UNLOCK(&threadl);
+ return CLI_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+/*! \brief CLI Command 'SIP Show Users' */
+static char *sip_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ regex_t regexbuf;
+ int havepattern = FALSE;
+
+#define FORMAT "%-25.25s %-15.15s %-15.15s %-15.15s %-5.5s%-10.10s\n"
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show users";
+ e->usage =
+ "Usage: sip show users [like <pattern>]\n"
+ " Lists all known SIP users.\n"
+ " Optional regular expression pattern is used to filter the user list.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ switch (a->argc) {
+ case 5:
+ if (!strcasecmp(a->argv[3], "like")) {
+ if (regcomp(&regexbuf, a->argv[4], REG_EXTENDED | REG_NOSUB))
+ return CLI_SHOWUSAGE;
+ havepattern = TRUE;
+ } else
+ return CLI_SHOWUSAGE;
+ case 3:
+ break;
+ default:
+ return CLI_SHOWUSAGE;
+ }
+
+ ast_cli(a->fd, FORMAT, "Username", "Secret", "Accountcode", "Def.Context", "ACL", "NAT");
+ ASTOBJ_CONTAINER_TRAVERSE(&userl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+
+ if (havepattern && regexec(&regexbuf, iterator->name, 0, NULL, 0)) {
+ ASTOBJ_UNLOCK(iterator);
+ continue;
+ }
+
+ ast_cli(a->fd, FORMAT, iterator->name,
+ iterator->secret,
+ iterator->accountcode,
+ iterator->context,
+ cli_yesno(iterator->ha != NULL),
+ nat2str(ast_test_flag(&iterator->flags[0], SIP_NAT)));
+ ASTOBJ_UNLOCK(iterator);
+ } while (0)
+ );
+
+ if (havepattern)
+ regfree(&regexbuf);
+
+ return CLI_SUCCESS;
+#undef FORMAT
+}
+
+/*! \brief Manager Action SIPShowRegistry description */
+static char mandescr_show_registry[] =
+"Description: Lists all registration requests and status\n"
+"Registrations will follow as separate events. followed by a final event called\n"
+"RegistrationsComplete.\n"
+"Variables: \n"
+" ActionID: <id> Action ID for this transaction. Will be returned.\n";
+
+/*! \brief Show SIP registrations in the manager API */
+static int manager_show_registry(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m, "ActionID");
+ char idtext[256] = "";
+ char tmpdat[256] = "";
+ int total = 0;
+ struct ast_tm tm;
+
+ if (!ast_strlen_zero(id))
+ snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id);
+
+ astman_send_listack(s, m, "Registrations will follow", "start");
+
+ ASTOBJ_CONTAINER_TRAVERSE(&regl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ if (iterator->regtime.tv_sec) {
+ ast_localtime(&iterator->regtime, &tm, NULL);
+ ast_strftime(tmpdat, sizeof(tmpdat), "%a, %d %b %Y %T", &tm);
+ } else
+ tmpdat[0] = '\0';
+ astman_append(s,
+ "Event: RegistryEntry\r\n"
+ "Host: %s\r\n"
+ "Port: %d\r\n"
+ "Username: %s\r\n"
+ "Refresh: %d\r\n"
+ "State: %s\r\n"
+ "Reg.Time: %s\r\n"
+ "\r\n", iterator->hostname, iterator->portno ? iterator->portno : STANDARD_SIP_PORT,
+ iterator->username, iterator->refresh, regstate2str(iterator->regstate), tmpdat);
+ ASTOBJ_UNLOCK(iterator);
+ total++;
+ } while(0));
+
+ astman_append(s,
+ "Event: RegistrationsComplete\r\n"
+ "EventList: Complete\r\n"
+ "ListItems: %d\r\n"
+ "%s"
+ "\r\n", total, idtext);
+
+ return 0;
+}
+
+static char mandescr_show_peers[] =
+"Description: Lists SIP peers in text format with details on current status.\n"
+"Peerlist will follow as separate events, followed by a final event called\n"
+"PeerlistComplete.\n"
+"Variables: \n"
+" ActionID: <id> Action ID for this transaction. Will be returned.\n";
+
+/*! \brief Show SIP peers in the manager API */
+/* Inspired from chan_iax2 */
+static int manager_sip_show_peers(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m,"ActionID");
+ const char *a[] = {"sip", "show", "peers"};
+ char idtext[256] = "";
+ int total = 0;
+
+ if (!ast_strlen_zero(id))
+ snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id);
+
+ astman_send_listack(s, m, "Peer status list will follow", "start");
+ /* List the peers in separate manager events */
+ _sip_show_peers(-1, &total, s, m, 3, a);
+ /* Send final confirmation */
+ astman_append(s,
+ "Event: PeerlistComplete\r\n"
+ "EventList: Complete\r\n"
+ "ListItems: %d\r\n"
+ "%s"
+ "\r\n", total, idtext);
+ return 0;
+}
+
+/*! \brief CLI Show Peers command */
+static char *sip_show_peers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show peers";
+ e->usage =
+ "Usage: sip show peers [like <pattern>]\n"
+ " Lists all known SIP peers.\n"
+ " Optional regular expression pattern is used to filter the peer list.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ return _sip_show_peers(a->fd, NULL, NULL, NULL, a->argc, (const char **) a->argv);
+}
+
+/*! \brief _sip_show_peers: Execute sip show peers command */
+static char *_sip_show_peers(int fd, int *total, struct mansession *s, const struct message *m, int argc, const char *argv[])
+{
+ regex_t regexbuf;
+ int havepattern = FALSE;
+
+/* the last argument is left-aligned, so we don't need a size anyways */
+#define FORMAT2 "%-25.25s %-15.15s %-3.3s %-3.3s %-3.3s %-8s %-10s %s\n"
+#define FORMAT "%-25.25s %-15.15s %-3.3s %-3.3s %-3.3s %-8d %-10s %s\n"
+
+ char name[256];
+ int total_peers = 0;
+ int peers_mon_online = 0;
+ int peers_mon_offline = 0;
+ int peers_unmon_offline = 0;
+ int peers_unmon_online = 0;
+ const char *id;
+ char idtext[256] = "";
+ int realtimepeers;
+
+ realtimepeers = ast_check_realtime("sippeers");
+
+ if (s) { /* Manager - get ActionID */
+ id = astman_get_header(m,"ActionID");
+ if (!ast_strlen_zero(id))
+ snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id);
+ }
+
+ switch (argc) {
+ case 5:
+ if (!strcasecmp(argv[3], "like")) {
+ if (regcomp(&regexbuf, argv[4], REG_EXTENDED | REG_NOSUB))
+ return CLI_SHOWUSAGE;
+ havepattern = TRUE;
+ } else
+ return CLI_SHOWUSAGE;
+ case 3:
+ break;
+ default:
+ return CLI_SHOWUSAGE;
+ }
+
+ if (!s) /* Normal list */
+ ast_cli(fd, FORMAT2, "Name/username", "Host", "Dyn", "Nat", "ACL", "Port", "Status", (realtimepeers ? "Realtime" : ""));
+
+ ASTOBJ_CONTAINER_TRAVERSE(&peerl, 1, do {
+ char status[20] = "";
+ char srch[2000];
+ char pstatus;
+
+ ASTOBJ_RDLOCK(iterator);
+
+ if (havepattern && regexec(&regexbuf, iterator->name, 0, NULL, 0)) {
+ ASTOBJ_UNLOCK(iterator);
+ continue;
+ }
+
+ if (!ast_strlen_zero(iterator->username) && !s)
+ snprintf(name, sizeof(name), "%s/%s", iterator->name, iterator->username);
+ else
+ ast_copy_string(name, iterator->name, sizeof(name));
+
+ pstatus = peer_status(iterator, status, sizeof(status));
+ if (pstatus == 1)
+ peers_mon_online++;
+ else if (pstatus == 0)
+ peers_mon_offline++;
+ else {
+ if (iterator->addr.sin_port == 0)
+ peers_unmon_offline++;
+ else
+ peers_unmon_online++;
+ }
+
+ snprintf(srch, sizeof(srch), FORMAT, name,
+ iterator->addr.sin_addr.s_addr ? ast_inet_ntoa(iterator->addr.sin_addr) : "(Unspecified)",
+ iterator->host_dynamic ? " D " : " ", /* Dynamic or not? */
+ ast_test_flag(&iterator->flags[0], SIP_NAT_ROUTE) ? " N " : " ", /* NAT=yes? */
+ iterator->ha ? " A " : " ", /* permit/deny */
+ ntohs(iterator->addr.sin_port), status,
+ realtimepeers ? (iterator->is_realtime ? "Cached RT":"") : "");
+
+ if (!s) {/* Normal CLI list */
+ ast_cli(fd, FORMAT, name,
+ iterator->addr.sin_addr.s_addr ? ast_inet_ntoa(iterator->addr.sin_addr) : "(Unspecified)",
+ iterator->host_dynamic ? " D " : " ", /* Dynamic or not? */
+ ast_test_flag(&iterator->flags[0], SIP_NAT_ROUTE) ? " N " : " ", /* NAT=yes? */
+ iterator->ha ? " A " : " ", /* permit/deny */
+
+ ntohs(iterator->addr.sin_port), status,
+ realtimepeers ? (iterator->is_realtime ? "Cached RT":"") : "");
+ } else { /* Manager format */
+ /* The names here need to be the same as other channels */
+ astman_append(s,
+ "Event: PeerEntry\r\n%s"
+ "Channeltype: SIP\r\n"
+ "ObjectName: %s\r\n"
+ "ChanObjectType: peer\r\n" /* "peer" or "user" */
+ "IPaddress: %s\r\n"
+ "IPport: %d\r\n"
+ "Dynamic: %s\r\n"
+ "Natsupport: %s\r\n"
+ "VideoSupport: %s\r\n"
+ "TextSupport: %s\r\n"
+ "ACL: %s\r\n"
+ "Status: %s\r\n"
+ "RealtimeDevice: %s\r\n\r\n",
+ idtext,
+ iterator->name,
+ iterator->addr.sin_addr.s_addr ? ast_inet_ntoa(iterator->addr.sin_addr) : "-none-",
+ ntohs(iterator->addr.sin_port),
+ iterator->host_dynamic ? "yes" : "no", /* Dynamic or not? */
+ ast_test_flag(&iterator->flags[0], SIP_NAT_ROUTE) ? "yes" : "no", /* NAT=yes? */
+ ast_test_flag(&iterator->flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "yes" : "no", /* VIDEOSUPPORT=yes? */
+ ast_test_flag(&iterator->flags[1], SIP_PAGE2_TEXTSUPPORT) ? "yes" : "no", /* TEXTSUPPORT=yes? */
+ iterator->ha ? "yes" : "no", /* permit/deny */
+ status,
+ realtimepeers ? (iterator->is_realtime ? "yes":"no") : "no");
+ }
+
+ ASTOBJ_UNLOCK(iterator);
+
+ total_peers++;
+ } while(0) );
+
+ if (!s)
+ ast_cli(fd, "%d sip peers [Monitored: %d online, %d offline Unmonitored: %d online, %d offline]\n",
+ total_peers, peers_mon_online, peers_mon_offline, peers_unmon_online, peers_unmon_offline);
+
+ if (havepattern)
+ regfree(&regexbuf);
+
+ if (total)
+ *total = total_peers;
+
+
+ return CLI_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+/*! \brief List all allocated SIP Objects (realtime or static) */
+static char *sip_show_objects(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char tmp[256];
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show objects";
+ e->usage =
+ "Usage: sip show objects\n"
+ " Lists status of known SIP objects\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ ast_cli(a->fd, "-= User objects: %d static, %d realtime =-\n\n", suserobjs, ruserobjs);
+ ASTOBJ_CONTAINER_DUMP(a->fd, tmp, sizeof(tmp), &userl);
+ ast_cli(a->fd, "-= Peer objects: %d static, %d realtime, %d autocreate =-\n\n", speerobjs, rpeerobjs, apeerobjs);
+ ASTOBJ_CONTAINER_DUMP(a->fd, tmp, sizeof(tmp), &peerl);
+ ast_cli(a->fd, "-= Registry objects: %d =-\n\n", regobjs);
+ ASTOBJ_CONTAINER_DUMP(a->fd, tmp, sizeof(tmp), &regl);
+ return CLI_SUCCESS;
+}
+/*! \brief Print call group and pickup group */
+static void print_group(int fd, ast_group_t group, int crlf)
+{
+ char buf[256];
+ ast_cli(fd, crlf ? "%s\r\n" : "%s\n", ast_print_group(buf, sizeof(buf), group) );
+}
+
+/*! \brief mapping between dtmf flags and strings */
+static struct _map_x_s dtmfstr[] = {
+ { SIP_DTMF_RFC2833, "rfc2833" },
+ { SIP_DTMF_INFO, "info" },
+ { SIP_DTMF_SHORTINFO, "shortinfo" },
+ { SIP_DTMF_INBAND, "inband" },
+ { SIP_DTMF_AUTO, "auto" },
+ { -1, NULL }, /* terminator */
+};
+
+/*! \brief Convert DTMF mode to printable string */
+static const char *dtmfmode2str(int mode)
+{
+ return map_x_s(dtmfstr, mode, "<error>");
+}
+
+/*! \brief maps a string to dtmfmode, returns -1 on error */
+static int str2dtmfmode(const char *str)
+{
+ return map_s_x(dtmfstr, str, -1);
+}
+
+static struct _map_x_s insecurestr[] = {
+ { SIP_INSECURE_PORT, "port" },
+ { SIP_INSECURE_INVITE, "invite" },
+ { SIP_INSECURE_PORT | SIP_INSECURE_INVITE, "port,invite" },
+ { 0, "no" },
+ { -1, NULL }, /* terminator */
+};
+
+/*! \brief Convert Insecure setting to printable string */
+static const char *insecure2str(int mode)
+{
+ return map_x_s(insecurestr, mode, "<error>");
+}
+
+/*! \brief Destroy disused contexts between reloads
+ Only used in reload_config so the code for regcontext doesn't get ugly
+*/
+static void cleanup_stale_contexts(char *new, char *old)
+{
+ char *oldcontext, *newcontext, *stalecontext, *stringp, newlist[AST_MAX_CONTEXT];
+
+ while ((oldcontext = strsep(&old, "&"))) {
+ stalecontext = '\0';
+ ast_copy_string(newlist, new, sizeof(newlist));
+ stringp = newlist;
+ while ((newcontext = strsep(&stringp, "&"))) {
+ if (strcmp(newcontext, oldcontext) == 0) {
+ /* This is not the context you're looking for */
+ stalecontext = '\0';
+ break;
+ } else if (strcmp(newcontext, oldcontext)) {
+ stalecontext = oldcontext;
+ }
+
+ }
+ if (stalecontext)
+ ast_context_destroy(ast_context_find(stalecontext), "SIP");
+ }
+}
+
+/*! \brief Remove temporary realtime objects from memory (CLI) */
+static char *sip_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct sip_peer *peer;
+ struct sip_user *user;
+ int pruneuser = FALSE;
+ int prunepeer = FALSE;
+ int multi = FALSE;
+ char *name = NULL;
+ regex_t regexbuf;
+
+ if (cmd == CLI_INIT) {
+ e->command = "sip prune realtime [peer|user|all] [all|like]";
+ e->usage =
+ "Usage: sip prune realtime [peer|user] [<name>|all|like <pattern>]\n"
+ " Prunes object(s) from the cache.\n"
+ " Optional regular expression pattern is used to filter the objects.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE) {
+ if (a->pos == 4) {
+ if (strcasestr(a->line, "realtime peer"))
+ return complete_sip_peer(a->word, a->n, SIP_PAGE2_RTCACHEFRIENDS);
+ else if (strcasestr(a->line, "realtime user"))
+ return complete_sip_user(a->word, a->n, SIP_PAGE2_RTCACHEFRIENDS);
+ }
+ return NULL;
+ }
+ switch (a->argc) {
+ case 4:
+ name = a->argv[3];
+ /* we accept a name in position 3, but keywords are not good. */
+ if (!strcasecmp(name, "user") || !strcasecmp(name, "peer") ||
+ !strcasecmp(name, "like"))
+ return CLI_SHOWUSAGE;
+ pruneuser = prunepeer = TRUE;
+ if (!strcasecmp(name, "all")) {
+ multi = TRUE;
+ name = NULL;
+ }
+ /* else a single name, already set */
+ break;
+ case 5:
+ /* sip prune realtime {user|peer|like} name */
+ name = a->argv[4];
+ if (!strcasecmp(a->argv[3], "user"))
+ pruneuser = TRUE;
+ else if (!strcasecmp(a->argv[3], "peer"))
+ prunepeer = TRUE;
+ else if (!strcasecmp(a->argv[3], "like")) {
+ pruneuser = prunepeer = TRUE;
+ multi = TRUE;
+ } else
+ return CLI_SHOWUSAGE;
+ if (!strcasecmp(a->argv[4], "like"))
+ return CLI_SHOWUSAGE;
+ if (!multi && !strcasecmp(a->argv[4], "all")) {
+ multi = TRUE;
+ name = NULL;
+ }
+ break;
+ case 6:
+ name = a->argv[5];
+ multi = TRUE;
+ /* sip prune realtime {user|peer} like name */
+ if (strcasecmp(a->argv[4], "like"))
+ return CLI_SHOWUSAGE;
+ if (!strcasecmp(a->argv[3], "user")) {
+ pruneuser = TRUE;
+ } else if (!strcasecmp(a->argv[3], "peer")) {
+ prunepeer = TRUE;
+ } else
+ return CLI_SHOWUSAGE;
+ break;
+ default:
+ return CLI_SHOWUSAGE;
+ }
+
+ if (multi && name) {
+ if (regcomp(&regexbuf, name, REG_EXTENDED | REG_NOSUB))
+ return CLI_SHOWUSAGE;
+ }
+
+ if (multi) {
+ if (prunepeer) {
+ int pruned = 0;
+
+ ASTOBJ_CONTAINER_WRLOCK(&peerl);
+ ASTOBJ_CONTAINER_TRAVERSE(&peerl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ if (name && regexec(&regexbuf, iterator->name, 0, NULL, 0)) {
+ ASTOBJ_UNLOCK(iterator);
+ continue;
+ };
+ if (ast_test_flag(&iterator->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) {
+ ASTOBJ_MARK(iterator);
+ pruned++;
+ }
+ ASTOBJ_UNLOCK(iterator);
+ } while (0) );
+ if (pruned) {
+ ASTOBJ_CONTAINER_PRUNE_MARKED(&peerl, sip_destroy_peer);
+ ast_cli(a->fd, "%d peers pruned.\n", pruned);
+ } else
+ ast_cli(a->fd, "No peers found to prune.\n");
+ ASTOBJ_CONTAINER_UNLOCK(&peerl);
+ }
+ if (pruneuser) {
+ int pruned = 0;
+
+ ASTOBJ_CONTAINER_WRLOCK(&userl);
+ ASTOBJ_CONTAINER_TRAVERSE(&userl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ if (name && regexec(&regexbuf, iterator->name, 0, NULL, 0)) {
+ ASTOBJ_UNLOCK(iterator);
+ continue;
+ };
+ if (ast_test_flag(&iterator->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) {
+ ASTOBJ_MARK(iterator);
+ pruned++;
+ }
+ ASTOBJ_UNLOCK(iterator);
+ } while (0) );
+ if (pruned) {
+ ASTOBJ_CONTAINER_PRUNE_MARKED(&userl, sip_destroy_user);
+ ast_cli(a->fd, "%d users pruned.\n", pruned);
+ } else
+ ast_cli(a->fd, "No users found to prune.\n");
+ ASTOBJ_CONTAINER_UNLOCK(&userl);
+ }
+ } else {
+ if (prunepeer) {
+ if ((peer = ASTOBJ_CONTAINER_FIND_UNLINK(&peerl, name))) {
+ if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) {
+ ast_cli(a->fd, "Peer '%s' is not a Realtime peer, cannot be pruned.\n", name);
+ ASTOBJ_CONTAINER_LINK(&peerl, peer);
+ } else
+ ast_cli(a->fd, "Peer '%s' pruned.\n", name);
+ unref_peer(peer);
+ } else
+ ast_cli(a->fd, "Peer '%s' not found.\n", name);
+ }
+ if (pruneuser) {
+ if ((user = ASTOBJ_CONTAINER_FIND_UNLINK(&userl, name))) {
+ if (!ast_test_flag(&user->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) {
+ ast_cli(a->fd, "User '%s' is not a Realtime user, cannot be pruned.\n", name);
+ ASTOBJ_CONTAINER_LINK(&userl, user);
+ } else
+ ast_cli(a->fd, "User '%s' pruned.\n", name);
+ unref_user(user);
+ } else
+ ast_cli(a->fd, "User '%s' not found.\n", name);
+ }
+ }
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief Print codec list from preference to CLI/manager */
+static void print_codec_to_cli(int fd, struct ast_codec_pref *pref)
+{
+ int x, codec;
+
+ for(x = 0; x < 32 ; x++) {
+ codec = ast_codec_pref_index(pref, x);
+ if (!codec)
+ break;
+ ast_cli(fd, "%s", ast_getformatname(codec));
+ ast_cli(fd, ":%d", pref->framing[x]);
+ if (x < 31 && ast_codec_pref_index(pref, x + 1))
+ ast_cli(fd, ",");
+ }
+ if (!x)
+ ast_cli(fd, "none");
+}
+
+/*! \brief Print domain mode to cli */
+static const char *domain_mode_to_text(const enum domain_mode mode)
+{
+ switch (mode) {
+ case SIP_DOMAIN_AUTO:
+ return "[Automatic]";
+ case SIP_DOMAIN_CONFIG:
+ return "[Configured]";
+ }
+
+ return "";
+}
+
+/*! \brief CLI command to list local domains */
+static char *sip_show_domains(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct domain *d;
+#define FORMAT "%-40.40s %-20.20s %-16.16s\n"
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show domains";
+ e->usage =
+ "Usage: sip show domains\n"
+ " Lists all configured SIP local domains.\n"
+ " Asterisk only responds to SIP messages to local domains.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (AST_LIST_EMPTY(&domain_list)) {
+ ast_cli(a->fd, "SIP Domain support not enabled.\n\n");
+ return CLI_SUCCESS;
+ } else {
+ ast_cli(a->fd, FORMAT, "Our local SIP domains:", "Context", "Set by");
+ AST_LIST_LOCK(&domain_list);
+ AST_LIST_TRAVERSE(&domain_list, d, list)
+ ast_cli(a->fd, FORMAT, d->domain, S_OR(d->context, "(default)"),
+ domain_mode_to_text(d->mode));
+ AST_LIST_UNLOCK(&domain_list);
+ ast_cli(a->fd, "\n");
+ return CLI_SUCCESS;
+ }
+}
+#undef FORMAT
+
+static char mandescr_show_peer[] =
+"Description: Show one SIP peer with details on current status.\n"
+"Variables: \n"
+" Peer: <name> The peer name you want to check.\n"
+" ActionID: <id> Optional action ID for this AMI transaction.\n";
+
+/*! \brief Show SIP peers in the manager API */
+static int manager_sip_show_peer(struct mansession *s, const struct message *m)
+{
+ const char *a[4];
+ const char *peer;
+
+ peer = astman_get_header(m,"Peer");
+ if (ast_strlen_zero(peer)) {
+ astman_send_error(s, m, "Peer: <name> missing.\n");
+ return 0;
+ }
+ a[0] = "sip";
+ a[1] = "show";
+ a[2] = "peer";
+ a[3] = peer;
+
+ _sip_show_peer(1, -1, s, m, 4, a);
+ astman_append(s, "\r\n\r\n" );
+ return 0;
+}
+
+/*! \brief Show one peer in detail */
+static char *sip_show_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show peer";
+ e->usage =
+ "Usage: sip show peer <name> [load]\n"
+ " Shows all details on one SIP peer and the current status.\n"
+ " Option \"load\" forces lookup of peer in realtime storage.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_sip_show_peer(a->line, a->word, a->pos, a->n);
+ }
+ return _sip_show_peer(0, a->fd, NULL, NULL, a->argc, (const char **) a->argv);
+}
+
+static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer)
+{
+ struct sip_mailbox *mailbox;
+
+ AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) {
+ ast_str_append(mailbox_str, 0, "%s%s%s%s",
+ mailbox->mailbox,
+ ast_strlen_zero(mailbox->context) ? "" : "@",
+ S_OR(mailbox->context, ""),
+ AST_LIST_NEXT(mailbox, entry) ? "," : "");
+ }
+}
+
+/*! \brief Show one peer in detail (main function) */
+static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct message *m, int argc, const char *argv[])
+{
+ char status[30] = "";
+ char cbuf[256];
+ struct sip_peer *peer;
+ char codec_buf[512];
+ struct ast_codec_pref *pref;
+ struct ast_variable *v;
+ struct sip_auth *auth;
+ int x = 0, codec = 0, load_realtime;
+ int realtimepeers;
+
+ realtimepeers = ast_check_realtime("sippeers");
+
+ if (argc < 4)
+ return CLI_SHOWUSAGE;
+
+ load_realtime = (argc == 5 && !strcmp(argv[4], "load")) ? TRUE : FALSE;
+ peer = find_peer(argv[3], NULL, load_realtime);
+ if (s) { /* Manager */
+ if (peer) {
+ const char *id = astman_get_header(m,"ActionID");
+
+ astman_append(s, "Response: Success\r\n");
+ if (!ast_strlen_zero(id))
+ astman_append(s, "ActionID: %s\r\n",id);
+ } else {
+ snprintf (cbuf, sizeof(cbuf), "Peer %s not found.\n", argv[3]);
+ astman_send_error(s, m, cbuf);
+ return CLI_SUCCESS;
+ }
+ }
+ if (peer && type==0 ) { /* Normal listing */
+ struct ast_str *mailbox_str = ast_str_alloca(512);
+ ast_cli(fd,"\n\n");
+ ast_cli(fd, " * Name : %s\n", peer->name);
+ if (realtimepeers) { /* Realtime is enabled */
+ ast_cli(fd, " Realtime peer: %s\n", peer->is_realtime ? "Yes, cached" : "No");
+ }
+ ast_cli(fd, " Secret : %s\n", ast_strlen_zero(peer->secret)?"<Not set>":"<Set>");
+ ast_cli(fd, " MD5Secret : %s\n", ast_strlen_zero(peer->md5secret)?"<Not set>":"<Set>");
+ for (auth = peer->auth; auth; auth = auth->next) {
+ ast_cli(fd, " Realm-auth : Realm %-15.15s User %-10.20s ", auth->realm, auth->username);
+ ast_cli(fd, "%s\n", !ast_strlen_zero(auth->secret)?"<Secret set>":(!ast_strlen_zero(auth->md5secret)?"<MD5secret set>" : "<Not set>"));
+ }
+ ast_cli(fd, " Context : %s\n", peer->context);
+ ast_cli(fd, " Subscr.Cont. : %s\n", S_OR(peer->subscribecontext, "<Not set>") );
+ ast_cli(fd, " Language : %s\n", peer->language);
+ if (!ast_strlen_zero(peer->accountcode))
+ ast_cli(fd, " Accountcode : %s\n", peer->accountcode);
+ ast_cli(fd, " AMA flags : %s\n", ast_cdr_flags2str(peer->amaflags));
+ ast_cli(fd, " Transfer mode: %s\n", transfermode2str(peer->allowtransfer));
+ ast_cli(fd, " CallingPres : %s\n", ast_describe_caller_presentation(peer->callingpres));
+ if (!ast_strlen_zero(peer->fromuser))
+ ast_cli(fd, " FromUser : %s\n", peer->fromuser);
+ if (!ast_strlen_zero(peer->fromdomain))
+ ast_cli(fd, " FromDomain : %s\n", peer->fromdomain);
+ ast_cli(fd, " Callgroup : ");
+ print_group(fd, peer->callgroup, 0);
+ ast_cli(fd, " Pickupgroup : ");
+ print_group(fd, peer->pickupgroup, 0);
+ peer_mailboxes_to_str(&mailbox_str, peer);
+ ast_cli(fd, " Mailbox : %s\n", mailbox_str->str);
+ ast_cli(fd, " VM Extension : %s\n", peer->vmexten);
+ ast_cli(fd, " LastMsgsSent : %d/%d\n", (peer->lastmsgssent & 0x7fff0000) >> 16, peer->lastmsgssent & 0xffff);
+ ast_cli(fd, " Call limit : %d\n", peer->call_limit);
+ if (peer->busy_level)
+ ast_cli(fd, " Busy level : %d\n", peer->busy_level);
+ ast_cli(fd, " Dynamic : %s\n", cli_yesno(peer->host_dynamic));
+ ast_cli(fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, "<unspecified>"));
+ ast_cli(fd, " MaxCallBR : %d kbps\n", peer->maxcallbitrate);
+ ast_cli(fd, " Expire : %ld\n", ast_sched_when(sched, peer->expire));
+ ast_cli(fd, " Insecure : %s\n", insecure2str(ast_test_flag(&peer->flags[0], SIP_INSECURE)));
+ ast_cli(fd, " Nat : %s\n", nat2str(ast_test_flag(&peer->flags[0], SIP_NAT)));
+ ast_cli(fd, " ACL : %s\n", cli_yesno(peer->ha != NULL));
+ ast_cli(fd, " T38 pt UDPTL : %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT_UDPTL)));
+#ifdef WHEN_WE_HAVE_T38_FOR_OTHER_TRANSPORTS
+ ast_cli(fd, " T38 pt RTP : %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT_RTP)));
+ ast_cli(fd, " T38 pt TCP : %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT_TCP)));
+#endif
+ ast_cli(fd, " CanReinvite : %s\n", cli_yesno(ast_test_flag(&peer->flags[0], SIP_CAN_REINVITE)));
+ ast_cli(fd, " PromiscRedir : %s\n", cli_yesno(ast_test_flag(&peer->flags[0], SIP_PROMISCREDIR)));
+ ast_cli(fd, " User=Phone : %s\n", cli_yesno(ast_test_flag(&peer->flags[0], SIP_USEREQPHONE)));
+ ast_cli(fd, " Video Support: %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT)));
+ ast_cli(fd, " Text Support : %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_TEXTSUPPORT)));
+ ast_cli(fd, " Trust RPID : %s\n", cli_yesno(ast_test_flag(&peer->flags[0], SIP_TRUSTRPID)));
+ ast_cli(fd, " Send RPID : %s\n", cli_yesno(ast_test_flag(&peer->flags[0], SIP_SENDRPID)));
+ ast_cli(fd, " Subscriptions: %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE)));
+ ast_cli(fd, " Overlap dial : %s\n", cli_yesno(ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWOVERLAP)));
+ if (peer->outboundproxy)
+ ast_cli(fd, " Outb. proxy : %s %s\n", ast_strlen_zero(peer->outboundproxy->name) ? "<not set>" : peer->outboundproxy->name,
+ peer->outboundproxy->force ? "(forced)" : "");
+
+ /* - is enumerated */
+ ast_cli(fd, " DTMFmode : %s\n", dtmfmode2str(ast_test_flag(&peer->flags[0], SIP_DTMF)));
+ ast_cli(fd, " Timer T1 : %d\n", peer->timer_t1);
+ ast_cli(fd, " Timer B : %d\n", peer->timer_b);
+ ast_cli(fd, " ToHost : %s\n", peer->tohost);
+ ast_cli(fd, " Addr->IP : %s Port %d\n", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "(Unspecified)", ntohs(peer->addr.sin_port));
+ ast_cli(fd, " Defaddr->IP : %s Port %d\n", ast_inet_ntoa(peer->defaddr.sin_addr), ntohs(peer->defaddr.sin_port));
+ ast_cli(fd, " Transport : %s\n", get_transport(peer->socket.type));
+ if (!ast_strlen_zero(global_regcontext))
+ ast_cli(fd, " Reg. exten : %s\n", peer->regexten);
+ ast_cli(fd, " Def. Username: %s\n", peer->username);
+ ast_cli(fd, " SIP Options : ");
+ if (peer->sipoptions) {
+ int lastoption = -1;
+ for (x=0 ; (x < (sizeof(sip_options) / sizeof(sip_options[0]))); x++) {
+ if (sip_options[x].id != lastoption) {
+ if (peer->sipoptions & sip_options[x].id)
+ ast_cli(fd, "%s ", sip_options[x].text);
+ lastoption = x;
+ }
+ }
+ } else
+ ast_cli(fd, "(none)");
+
+ ast_cli(fd, "\n");
+ ast_cli(fd, " Codecs : ");
+ ast_getformatname_multiple(codec_buf, sizeof(codec_buf) -1, peer->capability);
+ ast_cli(fd, "%s\n", codec_buf);
+ ast_cli(fd, " Codec Order : (");
+ print_codec_to_cli(fd, &peer->prefs);
+ ast_cli(fd, ")\n");
+
+ ast_cli(fd, " Auto-Framing : %s \n", cli_yesno(peer->autoframing));
+ ast_cli(fd, " 100 on REG : %s\n", ast_test_flag(&peer->flags[1], SIP_PAGE2_REGISTERTRYING) ? "Yes" : "No");
+ ast_cli(fd, " Status : ");
+ peer_status(peer, status, sizeof(status));
+ ast_cli(fd, "%s\n",status);
+ ast_cli(fd, " Useragent : %s\n", peer->useragent);
+ ast_cli(fd, " Reg. Contact : %s\n", peer->fullcontact);
+ ast_cli(fd, " Qualify Freq : %d ms\n", peer->qualifyfreq);
+ if (peer->chanvars) {
+ ast_cli(fd, " Variables :\n");
+ for (v = peer->chanvars ; v ; v = v->next)
+ ast_cli(fd, " %s = %s\n", v->name, v->value);
+ }
+
+ ast_cli(fd, " Sess-Timers : %s\n", stmode2str(peer->stimer.st_mode_oper));
+ ast_cli(fd, " Sess-Refresh : %s\n", strefresher2str(peer->stimer.st_ref));
+ ast_cli(fd, " Sess-Expires : %d secs\n", peer->stimer.st_max_se);
+ ast_cli(fd, " Min-Sess : %d secs\n", peer->stimer.st_min_se);
+ ast_cli(fd,"\n");
+ unref_peer(peer);
+ } else if (peer && type == 1) { /* manager listing */
+ char buf[256];
+ struct ast_str *mailbox_str = ast_str_alloca(512);
+ astman_append(s, "Channeltype: SIP\r\n");
+ astman_append(s, "ObjectName: %s\r\n", peer->name);
+ astman_append(s, "ChanObjectType: peer\r\n");
+ astman_append(s, "SecretExist: %s\r\n", ast_strlen_zero(peer->secret)?"N":"Y");
+ astman_append(s, "MD5SecretExist: %s\r\n", ast_strlen_zero(peer->md5secret)?"N":"Y");
+ astman_append(s, "Context: %s\r\n", peer->context);
+ astman_append(s, "Language: %s\r\n", peer->language);
+ if (!ast_strlen_zero(peer->accountcode))
+ astman_append(s, "Accountcode: %s\r\n", peer->accountcode);
+ astman_append(s, "AMAflags: %s\r\n", ast_cdr_flags2str(peer->amaflags));
+ astman_append(s, "CID-CallingPres: %s\r\n", ast_describe_caller_presentation(peer->callingpres));
+ if (!ast_strlen_zero(peer->fromuser))
+ astman_append(s, "SIP-FromUser: %s\r\n", peer->fromuser);
+ if (!ast_strlen_zero(peer->fromdomain))
+ astman_append(s, "SIP-FromDomain: %s\r\n", peer->fromdomain);
+ astman_append(s, "Callgroup: ");
+ astman_append(s, "%s\r\n", ast_print_group(buf, sizeof(buf), peer->callgroup));
+ astman_append(s, "Pickupgroup: ");
+ astman_append(s, "%s\r\n", ast_print_group(buf, sizeof(buf), peer->pickupgroup));
+ peer_mailboxes_to_str(&mailbox_str, peer);
+ astman_append(s, "VoiceMailbox: %s\r\n", mailbox_str->str);
+ astman_append(s, "TransferMode: %s\r\n", transfermode2str(peer->allowtransfer));
+ astman_append(s, "LastMsgsSent: %d\r\n", peer->lastmsgssent);
+ astman_append(s, "Call-limit: %d\r\n", peer->call_limit);
+ astman_append(s, "Busy-level: %d\r\n", peer->busy_level);
+ astman_append(s, "MaxCallBR: %d kbps\r\n", peer->maxcallbitrate);
+ astman_append(s, "Dynamic: %s\r\n", peer->host_dynamic?"Y":"N");
+ astman_append(s, "Callerid: %s\r\n", ast_callerid_merge(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, ""));
+ astman_append(s, "RegExpire: %ld seconds\r\n", ast_sched_when(sched,peer->expire));
+ astman_append(s, "SIP-AuthInsecure: %s\r\n", insecure2str(ast_test_flag(&peer->flags[0], SIP_INSECURE)));
+ astman_append(s, "SIP-NatSupport: %s\r\n", nat2str(ast_test_flag(&peer->flags[0], SIP_NAT)));
+ astman_append(s, "ACL: %s\r\n", (peer->ha?"Y":"N"));
+ astman_append(s, "SIP-CanReinvite: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_CAN_REINVITE)?"Y":"N"));
+ astman_append(s, "SIP-PromiscRedir: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_PROMISCREDIR)?"Y":"N"));
+ astman_append(s, "SIP-UserPhone: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_USEREQPHONE)?"Y":"N"));
+ astman_append(s, "SIP-VideoSupport: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT)?"Y":"N"));
+ astman_append(s, "SIP-TextSupport: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_TEXTSUPPORT)?"Y":"N"));
+ astman_append(s, "SIP-Sess-Timers: %s\r\n", stmode2str(peer->stimer.st_mode_oper));
+ astman_append(s, "SIP-Sess-Refresh: %s\r\n", strefresher2str(peer->stimer.st_ref));
+ astman_append(s, "SIP-Sess-Expires: %d\r\n", peer->stimer.st_max_se);
+ astman_append(s, "SIP-Sess-Min: %d\r\n", peer->stimer.st_min_se);
+
+ /* - is enumerated */
+ astman_append(s, "SIP-DTMFmode: %s\r\n", dtmfmode2str(ast_test_flag(&peer->flags[0], SIP_DTMF)));
+ astman_append(s, "ToHost: %s\r\n", peer->tohost);
+ astman_append(s, "Address-IP: %s\r\nAddress-Port: %d\r\n", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "", ntohs(peer->addr.sin_port));
+ astman_append(s, "Default-addr-IP: %s\r\nDefault-addr-port: %d\r\n", ast_inet_ntoa(peer->defaddr.sin_addr), ntohs(peer->defaddr.sin_port));
+ astman_append(s, "Default-Username: %s\r\n", peer->username);
+ if (!ast_strlen_zero(global_regcontext))
+ astman_append(s, "RegExtension: %s\r\n", peer->regexten);
+ astman_append(s, "Codecs: ");
+ ast_getformatname_multiple(codec_buf, sizeof(codec_buf) -1, peer->capability);
+ astman_append(s, "%s\r\n", codec_buf);
+ astman_append(s, "CodecOrder: ");
+ pref = &peer->prefs;
+ for(x = 0; x < 32 ; x++) {
+ codec = ast_codec_pref_index(pref,x);
+ if (!codec)
+ break;
+ astman_append(s, "%s", ast_getformatname(codec));
+ if (x < 31 && ast_codec_pref_index(pref,x+1))
+ astman_append(s, ",");
+ }
+
+ astman_append(s, "\r\n");
+ astman_append(s, "Status: ");
+ peer_status(peer, status, sizeof(status));
+ astman_append(s, "%s\r\n", status);
+ astman_append(s, "SIP-Useragent: %s\r\n", peer->useragent);
+ astman_append(s, "Reg-Contact : %s\r\n", peer->fullcontact);
+ astman_append(s, "Qualify Freq : %d ms\n", peer->qualifyfreq);
+ if (peer->chanvars) {
+ for (v = peer->chanvars ; v ; v = v->next) {
+ astman_append(s, "ChanVariable:\n");
+ astman_append(s, " %s,%s\r\n", v->name, v->value);
+ }
+ }
+
+ unref_peer(peer);
+
+ } else {
+ ast_cli(fd,"Peer %s not found.\n", argv[3]);
+ ast_cli(fd,"\n");
+ }
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief Show one user in detail */
+static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ char cbuf[256];
+ struct sip_user *user;
+ struct ast_variable *v;
+ int load_realtime;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show user";
+ e->usage =
+ "Usage: sip show user <name> [load]\n"
+ " Shows all details on one SIP user and the current status.\n"
+ " Option \"load\" forces lookup of peer in realtime storage.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_sip_show_user(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc < 4)
+ return CLI_SHOWUSAGE;
+
+ /* Load from realtime storage? */
+ load_realtime = (a->argc == 5 && !strcmp(a->argv[4], "load")) ? TRUE : FALSE;
+
+ user = find_user(a->argv[3], load_realtime);
+ if (user) {
+ ast_cli(a->fd,"\n\n");
+ ast_cli(a->fd, " * Name : %s\n", user->name);
+ ast_cli(a->fd, " Secret : %s\n", ast_strlen_zero(user->secret)?"<Not set>":"<Set>");
+ ast_cli(a->fd, " MD5Secret : %s\n", ast_strlen_zero(user->md5secret)?"<Not set>":"<Set>");
+ ast_cli(a->fd, " Context : %s\n", user->context);
+ ast_cli(a->fd, " Language : %s\n", user->language);
+ if (!ast_strlen_zero(user->accountcode))
+ ast_cli(a->fd, " Accountcode : %s\n", user->accountcode);
+ ast_cli(a->fd, " AMA flags : %s\n", ast_cdr_flags2str(user->amaflags));
+ ast_cli(a->fd, " Transfer mode: %s\n", transfermode2str(user->allowtransfer));
+ ast_cli(a->fd, " MaxCallBR : %d kbps\n", user->maxcallbitrate);
+ ast_cli(a->fd, " CallingPres : %s\n", ast_describe_caller_presentation(user->callingpres));
+ ast_cli(a->fd, " Call limit : %d\n", user->call_limit);
+ ast_cli(a->fd, " Callgroup : ");
+ print_group(a->fd, user->callgroup, 0);
+ ast_cli(a->fd, " Pickupgroup : ");
+ print_group(a->fd, user->pickupgroup, 0);
+ ast_cli(a->fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), user->cid_name, user->cid_num, "<unspecified>"));
+ ast_cli(a->fd, " ACL : %s\n", cli_yesno(user->ha != NULL));
+ ast_cli(a->fd, " Sess-Timers : %s\n", stmode2str(user->stimer.st_mode_oper));
+ ast_cli(a->fd, " Sess-Refresh : %s\n", strefresher2str(user->stimer.st_ref));
+ ast_cli(a->fd, " Sess-Expires : %d secs\n", user->stimer.st_max_se);
+ ast_cli(a->fd, " Sess-Min-SE : %d secs\n", user->stimer.st_min_se);
+
+ ast_cli(a->fd, " Codec Order : (");
+ print_codec_to_cli(a->fd, &user->prefs);
+ ast_cli(a->fd, ")\n");
+
+ ast_cli(a->fd, " Auto-Framing: %s \n", cli_yesno(user->autoframing));
+ if (user->chanvars) {
+ ast_cli(a->fd, " Variables :\n");
+ for (v = user->chanvars ; v ; v = v->next)
+ ast_cli(a->fd, " %s = %s\n", v->name, v->value);
+ }
+
+ ast_cli(a->fd,"\n");
+
+ unref_user(user);
+ } else {
+ ast_cli(a->fd,"User %s not found.\n", a->argv[3]);
+ ast_cli(a->fd,"\n");
+ }
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief Show SIP Registry (registrations with other SIP proxies */
+static char *sip_show_registry(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT2 "%-30.30s %-12.12s %8.8s %-20.20s %-25.25s\n"
+#define FORMAT "%-30.30s %-12.12s %8d %-20.20s %-25.25s\n"
+ char host[80];
+ char tmpdat[256];
+ struct ast_tm tm;
+ int counter = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show registry";
+ e->usage =
+ "Usage: sip show registry\n"
+ " Lists all registration requests and status.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ ast_cli(a->fd, FORMAT2, "Host", "Username", "Refresh", "State", "Reg.Time");
+ ASTOBJ_CONTAINER_TRAVERSE(&regl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ snprintf(host, sizeof(host), "%s:%d", iterator->hostname, iterator->portno ? iterator->portno : STANDARD_SIP_PORT);
+ if (iterator->regtime.tv_sec) {
+ ast_localtime(&iterator->regtime, &tm, NULL);
+ ast_strftime(tmpdat, sizeof(tmpdat), "%a, %d %b %Y %T", &tm);
+ } else
+ tmpdat[0] = '\0';
+ ast_cli(a->fd, FORMAT, host, iterator->username, iterator->refresh, regstate2str(iterator->regstate), tmpdat);
+ ASTOBJ_UNLOCK(iterator);
+ counter++;
+ } while(0));
+ ast_cli(a->fd, "%d SIP registrations.\n", counter);
+ return CLI_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+/*! \brief Unregister (force expiration) a SIP peer in the registry via CLI
+ \note This function does not tell the SIP device what's going on,
+ so use it with great care.
+*/
+static char *sip_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct sip_peer *peer;
+ int load_realtime = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip unregister";
+ e->usage =
+ "Usage: sip unregister <peer>\n"
+ " Unregister (force expiration) a SIP peer from the registry\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_sip_unregister(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ if ((peer = find_peer(a->argv[2], NULL, load_realtime))) {
+ if (peer->expire > 0) {
+ expire_register(peer);
+ ast_cli(a->fd, "Unregistered peer \'%s\'\n\n", a->argv[2]);
+ } else {
+ ast_cli(a->fd, "Peer %s not registered\n", a->argv[2]);
+ }
+ } else {
+ ast_cli(a->fd, "Peer unknown: \'%s\'. Not unregistered.\n", a->argv[2]);
+ }
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief List global settings for the SIP channel */
+static char *sip_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int realtimepeers;
+ int realtimeusers;
+ int realtimeregs;
+ char codec_buf[BUFSIZ];
+ const char *msg; /* temporary msg pointer */
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show settings";
+ e->usage =
+ "Usage: sip show settings\n"
+ " Provides detailed list of the configuration of the SIP channel.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+
+ realtimepeers = ast_check_realtime("sippeers");
+ realtimeusers = ast_check_realtime("sipusers");
+ realtimeregs = ast_check_realtime("sipregs");
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+ ast_cli(a->fd, "\n\nGlobal Settings:\n");
+ ast_cli(a->fd, "----------------\n");
+ ast_cli(a->fd, " SIP Port: %d\n", ntohs(bindaddr.sin_port));
+ ast_cli(a->fd, " Bindaddress: %s\n", ast_inet_ntoa(bindaddr.sin_addr));
+ ast_cli(a->fd, " Videosupport: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_VIDEOSUPPORT)));
+ ast_cli(a->fd, " Textsupport: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_TEXTSUPPORT)));
+ ast_cli(a->fd, " AutoCreatePeer: %s\n", cli_yesno(autocreatepeer));
+ ast_cli(a->fd, " MatchAuthUsername: %s\n", cli_yesno(global_match_auth_username));
+ ast_cli(a->fd, " Allow unknown access: %s\n", cli_yesno(global_allowguest));
+ ast_cli(a->fd, " Allow subscriptions: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_ALLOWSUBSCRIBE)));
+ ast_cli(a->fd, " Enable call counters: %s\n", cli_yesno(global_callcounter));
+ ast_cli(a->fd, " Allow overlap dialing: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP)));
+ ast_cli(a->fd, " Promsic. redir: %s\n", cli_yesno(ast_test_flag(&global_flags[0], SIP_PROMISCREDIR)));
+ ast_cli(a->fd, " SIP domain support: %s\n", cli_yesno(!AST_LIST_EMPTY(&domain_list)));
+ ast_cli(a->fd, " Call to non-local dom.: %s\n", cli_yesno(allow_external_domains));
+ ast_cli(a->fd, " URI user is phone no: %s\n", cli_yesno(ast_test_flag(&global_flags[0], SIP_USEREQPHONE)));
+ ast_cli(a->fd, " Our auth realm %s\n", global_realm);
+ ast_cli(a->fd, " Realm. auth: %s\n", cli_yesno(authl != NULL));
+ ast_cli(a->fd, " Always auth rejects: %s\n", cli_yesno(global_alwaysauthreject));
+ ast_cli(a->fd, " Call limit peers only: %s\n", cli_yesno(global_limitonpeers));
+ ast_cli(a->fd, " Direct RTP setup: %s\n", cli_yesno(global_directrtpsetup));
+ ast_cli(a->fd, " User Agent: %s\n", global_useragent);
+ ast_cli(a->fd, " SDP Session Name: %s\n", ast_strlen_zero(global_sdpsession) ? "-" : global_sdpsession);
+ ast_cli(a->fd, " SDP Owner Name: %s\n", ast_strlen_zero(global_sdpowner) ? "-" : global_sdpowner);
+ ast_cli(a->fd, " Reg. context: %s\n", S_OR(global_regcontext, "(not set)"));
+ ast_cli(a->fd, " Regexten on Qualify: %s\n", cli_yesno(global_regextenonqualify));
+ ast_cli(a->fd, " Caller ID: %s\n", default_callerid);
+ ast_cli(a->fd, " From: Domain: %s\n", default_fromdomain);
+ ast_cli(a->fd, " Record SIP history: %s\n", recordhistory ? "On" : "Off");
+ ast_cli(a->fd, " Call Events: %s\n", global_callevents ? "On" : "Off");
+ ast_cli(a->fd, " IP ToS SIP: %s\n", ast_tos2str(global_tos_sip));
+ ast_cli(a->fd, " IP ToS RTP audio: %s\n", ast_tos2str(global_tos_audio));
+ ast_cli(a->fd, " IP ToS RTP video: %s\n", ast_tos2str(global_tos_video));
+ ast_cli(a->fd, " IP ToS RTP text: %s\n", ast_tos2str(global_tos_text));
+ ast_cli(a->fd, " 802.1p CoS SIP: %d\n", global_cos_sip);
+ ast_cli(a->fd, " 802.1p CoS RTP audio: %d\n", global_cos_audio);
+ ast_cli(a->fd, " 802.1p CoS RTP video: %d\n", global_cos_video);
+ ast_cli(a->fd, " 802.1p CoS RTP text: %d\n", global_cos_text);
+
+ ast_cli(a->fd, " T38 fax pt UDPTL: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT_UDPTL)));
+#ifdef WHEN_WE_HAVE_T38_FOR_OTHER_TRANSPORTS
+ ast_cli(a->fd, " T38 fax pt RTP: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT_RTP)));
+ ast_cli(a->fd, " T38 fax pt TCP: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT_TCP)));
+#endif
+ ast_cli(a->fd, " RFC2833 Compensation: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_RFC2833_COMPENSATE)));
+ ast_cli(a->fd, " Jitterbuffer enabled: %s\n", cli_yesno(ast_test_flag(&global_jbconf, AST_JB_ENABLED)));
+ ast_cli(a->fd, " Jitterbuffer forced: %s\n", cli_yesno(ast_test_flag(&global_jbconf, AST_JB_FORCED)));
+ ast_cli(a->fd, " Jitterbuffer max size: %ld\n", global_jbconf.max_size);
+ ast_cli(a->fd, " Jitterbuffer resync: %ld\n", global_jbconf.resync_threshold);
+ ast_cli(a->fd, " Jitterbuffer impl: %s\n", global_jbconf.impl);
+ ast_cli(a->fd, " Jitterbuffer log: %s\n", cli_yesno(ast_test_flag(&global_jbconf, AST_JB_LOG)));
+ if (!realtimepeers && !realtimeusers && !realtimeregs)
+ ast_cli(a->fd, " SIP realtime: Disabled\n" );
+ else
+ ast_cli(a->fd, " SIP realtime: Enabled\n" );
+ ast_cli(a->fd, " Qualify Freq : %d ms\n", global_qualifyfreq);
+
+ ast_cli(a->fd, "\nNetwork Settings:\n");
+ ast_cli(a->fd, "---------------------------\n");
+ /* determine if/how SIP address can be remapped */
+ if (localaddr == NULL)
+ msg = "Disabled, no localnet list";
+ else if (externip.sin_addr.s_addr == 0)
+ msg = "Disabled, externip is 0.0.0.0";
+ else if (stunaddr.sin_addr.s_addr != 0)
+ msg = "Enabled using STUN";
+ else if (!ast_strlen_zero(externhost))
+ msg = "Enabled using externhost";
+ else
+ msg = "Enabled using externip";
+ ast_cli(a->fd, " SIP address remapping: %s\n", msg);
+ ast_cli(a->fd, " Externhost: %s\n", S_OR(externhost, "<none>"));
+ ast_cli(a->fd, " Externip: %s:%d\n", ast_inet_ntoa(externip.sin_addr), ntohs(externip.sin_port));
+ ast_cli(a->fd, " Externrefresh: %d\n", externrefresh);
+ ast_cli(a->fd, " Internal IP: %s:%d\n", ast_inet_ntoa(internip.sin_addr), ntohs(internip.sin_port));
+ {
+ struct ast_ha *d;
+ const char *prefix = "Localnet:";
+ char buf[INET_ADDRSTRLEN]; /* need to print two addresses */
+
+ for (d = localaddr; d ; prefix = "", d = d->next) {
+ ast_cli(a->fd, " %-24s%s/%s\n",
+ prefix, ast_inet_ntoa(d->netaddr),
+ inet_ntop(AF_INET, &d->netmask, buf, sizeof(buf)) );
+ }
+ }
+ ast_cli(a->fd, " STUN server: %s:%d\n", ast_inet_ntoa(stunaddr.sin_addr), ntohs(stunaddr.sin_port));
+
+ ast_cli(a->fd, "\nGlobal Signalling Settings:\n");
+ ast_cli(a->fd, "---------------------------\n");
+ ast_cli(a->fd, " Codecs: ");
+ ast_getformatname_multiple(codec_buf, sizeof(codec_buf) -1, global_capability);
+ ast_cli(a->fd, "%s\n", codec_buf);
+ ast_cli(a->fd, " Codec Order: ");
+ print_codec_to_cli(a->fd, &default_prefs);
+ ast_cli(a->fd, "\n");
+ ast_cli(a->fd, " Relax DTMF: %s\n", cli_yesno(global_relaxdtmf));
+ ast_cli(a->fd, " Compact SIP headers: %s\n", cli_yesno(compactheaders));
+ ast_cli(a->fd, " RTP Keepalive: %d %s\n", global_rtpkeepalive, global_rtpkeepalive ? "" : "(Disabled)" );
+ ast_cli(a->fd, " RTP Timeout: %d %s\n", global_rtptimeout, global_rtptimeout ? "" : "(Disabled)" );
+ ast_cli(a->fd, " RTP Hold Timeout: %d %s\n", global_rtpholdtimeout, global_rtpholdtimeout ? "" : "(Disabled)");
+ ast_cli(a->fd, " MWI NOTIFY mime type: %s\n", default_notifymime);
+ ast_cli(a->fd, " DNS SRV lookup: %s\n", cli_yesno(global_srvlookup));
+ ast_cli(a->fd, " Pedantic SIP support: %s\n", cli_yesno(pedanticsipchecking));
+ ast_cli(a->fd, " Reg. min duration %d secs\n", min_expiry);
+ ast_cli(a->fd, " Reg. max duration: %d secs\n", max_expiry);
+ ast_cli(a->fd, " Reg. default duration: %d secs\n", default_expiry);
+ ast_cli(a->fd, " Outbound reg. timeout: %d secs\n", global_reg_timeout);
+ ast_cli(a->fd, " Outbound reg. attempts: %d\n", global_regattempts_max);
+ ast_cli(a->fd, " Notify ringing state: %s\n", cli_yesno(global_notifyringing));
+ ast_cli(a->fd, " Notify hold state: %s\n", cli_yesno(global_notifyhold));
+ ast_cli(a->fd, " SIP Transfer mode: %s\n", transfermode2str(global_allowtransfer));
+ ast_cli(a->fd, " Max Call Bitrate: %d kbps\n", default_maxcallbitrate);
+ ast_cli(a->fd, " Auto-Framing: %s\n", cli_yesno(global_autoframing));
+ ast_cli(a->fd, " Outb. proxy: %s %s\n", ast_strlen_zero(global_outboundproxy.name) ? "<not set>" : global_outboundproxy.name,
+ global_outboundproxy.force ? "(forced)" : "");
+ ast_cli(a->fd, " Session Timers: %s\n", stmode2str(global_st_mode));
+ ast_cli(a->fd, " Session Refresher: %s\n", strefresher2str (global_st_refresher));
+ ast_cli(a->fd, " Session Expires: %d secs\n", global_max_se);
+ ast_cli(a->fd, " Session Min-SE: %d secs\n", global_min_se);
+ ast_cli(a->fd, " Timer T1: %d\n", global_t1);
+ ast_cli(a->fd, " Timer T1 minimum: %d\n", global_t1min);
+ ast_cli(a->fd, " Timer B: %d\n", global_timer_b);
+
+ ast_cli(a->fd, "\nDefault Settings:\n");
+ ast_cli(a->fd, "-----------------\n");
+ ast_cli(a->fd, " Context: %s\n", default_context);
+ ast_cli(a->fd, " Nat: %s\n", nat2str(ast_test_flag(&global_flags[0], SIP_NAT)));
+ ast_cli(a->fd, " DTMF: %s\n", dtmfmode2str(ast_test_flag(&global_flags[0], SIP_DTMF)));
+ ast_cli(a->fd, " Qualify: %d\n", default_qualify);
+ ast_cli(a->fd, " Use ClientCode: %s\n", cli_yesno(ast_test_flag(&global_flags[0], SIP_USECLIENTCODE)));
+ ast_cli(a->fd, " Progress inband: %s\n", (ast_test_flag(&global_flags[0], SIP_PROG_INBAND) == SIP_PROG_INBAND_NEVER) ? "Never" : (ast_test_flag(&global_flags[0], SIP_PROG_INBAND) == SIP_PROG_INBAND_NO) ? "No" : "Yes" );
+ ast_cli(a->fd, " Language: %s\n", default_language);
+ ast_cli(a->fd, " MOH Interpret: %s\n", default_mohinterpret);
+ ast_cli(a->fd, " MOH Suggest: %s\n", default_mohsuggest);
+ ast_cli(a->fd, " Voice Mail Extension: %s\n", default_vmexten);
+
+
+ if (realtimepeers || realtimeusers || realtimeregs) {
+ ast_cli(a->fd, "\nRealtime SIP Settings:\n");
+ ast_cli(a->fd, "----------------------\n");
+ ast_cli(a->fd, " Realtime Peers: %s\n", cli_yesno(realtimepeers));
+ ast_cli(a->fd, " Realtime Users: %s\n", cli_yesno(realtimeusers));
+ ast_cli(a->fd, " Realtime Regs: %s\n", cli_yesno(realtimeregs));
+ ast_cli(a->fd, " Cache Friends: %s\n", cli_yesno(ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)));
+ ast_cli(a->fd, " Update: %s\n", cli_yesno(sip_cfg.peer_rtupdate));
+ ast_cli(a->fd, " Ignore Reg. Expire: %s\n", cli_yesno(sip_cfg.ignore_regexpire));
+ ast_cli(a->fd, " Save sys. name: %s\n", cli_yesno(sip_cfg.rtsave_sysname));
+ ast_cli(a->fd, " Auto Clear: %d\n", global_rtautoclear);
+ }
+ ast_cli(a->fd, "\n----\n");
+ return CLI_SUCCESS;
+}
+
+/*! \brief Show subscription type in string format */
+static const char *subscription_type2str(enum subscriptiontype subtype)
+{
+ int i;
+
+ for (i = 1; (i < (sizeof(subscription_types) / sizeof(subscription_types[0]))); i++) {
+ if (subscription_types[i].type == subtype) {
+ return subscription_types[i].text;
+ }
+ }
+ return subscription_types[0].text;
+}
+
+/*! \brief Find subscription type in array */
+static const struct cfsubscription_types *find_subscription_type(enum subscriptiontype subtype)
+{
+ int i;
+
+ for (i = 1; (i < (sizeof(subscription_types) / sizeof(subscription_types[0]))); i++) {
+ if (subscription_types[i].type == subtype) {
+ return &subscription_types[i];
+ }
+ }
+ return &subscription_types[0];
+}
+
+/*
+ * We try to structure all functions that loop on data structures as
+ * a handler for individual entries, and a mainloop that iterates
+ * on the main data structure. This way, moving the code to containers
+ * that support iteration through callbacks will be a lot easier.
+ */
+
+/*! \brief argument for the 'show channels|subscriptions' callback. */
+struct __show_chan_arg {
+ int fd;
+ int subscriptions;
+ int numchans; /* return value */
+};
+
+#define FORMAT3 "%-15.15s %-10.10s %-15.15s %-15.15s %-13.13s %-15.15s %-10.10s\n"
+#define FORMAT2 "%-15.15s %-10.10s %-15.15s %-15.15s %-7.7s %-15.15s\n"
+#define FORMAT "%-15.15s %-10.10s %-15.15s %-15.15s %-3.3s %-3.3s %-15.15s %-10.10s\n"
+
+/*! \brief callback for show channel|subscription */
+static int show_channels_cb(void *__cur, void *__arg, int flags)
+{
+ struct sip_pvt *cur = __cur;
+ struct __show_chan_arg *arg = __arg;
+ const struct sockaddr_in *dst = sip_real_dst(cur);
+
+ /* XXX indentation preserved to reduce diff. Will be fixed later */
+ if (cur->subscribed == NONE && !arg->subscriptions) {
+ /* set if SIP transfer in progress */
+ const char *referstatus = cur->refer ? referstatus2str(cur->refer->status) : "";
+ char formatbuf[BUFSIZ/2];
+
+ ast_cli(arg->fd, FORMAT, ast_inet_ntoa(dst->sin_addr),
+ S_OR(cur->username, S_OR(cur->cid_num, "(None)")),
+ cur->callid,
+ ast_getformatname_multiple(formatbuf, sizeof(formatbuf), cur->owner ? cur->owner->nativeformats : 0),
+ cli_yesno(ast_test_flag(&cur->flags[1], SIP_PAGE2_CALL_ONHOLD)),
+ cur->needdestroy ? "(d)" : "",
+ cur->lastmsg ,
+ referstatus
+ );
+ arg->numchans++;
+ }
+ if (cur->subscribed != NONE && arg->subscriptions) {
+ struct ast_str *mailbox_str = ast_str_alloca(512);
+ if (cur->subscribed == MWI_NOTIFICATION && cur->relatedpeer)
+ peer_mailboxes_to_str(&mailbox_str, cur->relatedpeer);
+ ast_cli(arg->fd, FORMAT3, ast_inet_ntoa(dst->sin_addr),
+ S_OR(cur->username, S_OR(cur->cid_num, "(None)")),
+ cur->callid,
+ /* the 'complete' exten/context is hidden in the refer_to field for subscriptions */
+ cur->subscribed == MWI_NOTIFICATION ? "--" : cur->subscribeuri,
+ cur->subscribed == MWI_NOTIFICATION ? "<none>" : ast_extension_state2str(cur->laststate),
+ subscription_type2str(cur->subscribed),
+ cur->subscribed == MWI_NOTIFICATION ? S_OR(mailbox_str->str, "<none>") : "<none>"
+);
+ arg->numchans++;
+ }
+
+ return 0; /* don't care, we scan all channels */
+}
+
+/*! \brief CLI for show channels or subscriptions.
+ * This is a new-style CLI handler so a single function contains
+ * the prototype for the function, the 'generator' to produce multiple
+ * entries in case it is required, and the actual handler for the command.
+ */
+static char *sip_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct sip_pvt *cur;
+ struct __show_chan_arg arg = { .fd = a->fd, .numchans = 0 };
+
+ if (cmd == CLI_INIT) {
+ e->command = "sip show {channels|subscriptions}";
+ e->usage =
+ "Usage: sip show channels\n"
+ " Lists all currently active SIP calls (dialogs).\n"
+ "Usage: sip show subscriptions\n"
+ " Lists active SIP subscriptions.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE)
+ return NULL;
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+ arg.subscriptions = !strcasecmp(a->argv[e->args - 1], "subscriptions");
+ if (!arg.subscriptions)
+ ast_cli(arg.fd, FORMAT2, "Peer", "User/ANR", "Call ID", "Format", "Hold", "Last Message");
+ else
+ ast_cli(arg.fd, FORMAT3, "Peer", "User", "Call ID", "Extension", "Last state", "Type", "Mailbox");
+
+ /* iterate on the container and invoke the callback on each item */
+ dialoglist_lock();
+ for (cur = dialoglist; cur; cur = cur->next) {
+ show_channels_cb(cur, &arg, 0);
+ }
+ dialoglist_unlock();
+
+ /* print summary information */
+ ast_cli(arg.fd, "%d active SIP %s%s\n", arg.numchans,
+ (arg.subscriptions ? "subscription" : "dialog"),
+ ESS(arg.numchans)); /* ESS(n) returns an "s" if n>1 */
+ return CLI_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+#undef FORMAT3
+}
+
+/*! \brief Support routine for 'sip show channel' and 'sip show history' CLI
+ * This is in charge of generating all strings that match a prefix in the
+ * given position. As many functions of this kind, each invokation has
+ * O(state) time complexity so be careful in using it.
+ */
+static char *complete_sipch(const char *line, const char *word, int pos, int state)
+{
+ int which=0;
+ struct sip_pvt *cur;
+ char *c = NULL;
+ int wordlen = strlen(word);
+
+ dialoglist_lock();
+ for (cur = dialoglist; cur; cur = cur->next) {
+ if (!strncasecmp(word, cur->callid, wordlen) && ++which > state) {
+ c = ast_strdup(cur->callid);
+ break;
+ }
+ }
+ dialoglist_unlock();
+ return c;
+}
+
+/*! \brief Do completion on peer name */
+static char *complete_sip_peer(const char *word, int state, int flags2)
+{
+ char *result = NULL;
+ int wordlen = strlen(word);
+ int which = 0;
+
+ ASTOBJ_CONTAINER_TRAVERSE(&peerl, !result, do {
+ /* locking of the object is not required because only the name and flags are being compared */
+ if (!strncasecmp(word, iterator->name, wordlen) &&
+ (!flags2 || ast_test_flag(&iterator->flags[1], flags2)) &&
+ ++which > state)
+ result = ast_strdup(iterator->name);
+ } while(0) );
+ return result;
+}
+
+/*! \brief Do completion on registered peer name */
+static char *complete_sip_registered_peer(const char *word, int state, int flags2)
+{
+ char *result = NULL;
+ int wordlen = strlen(word);
+ int which = 0;
+
+ ASTOBJ_CONTAINER_TRAVERSE(&peerl, !result, do {
+ ASTOBJ_WRLOCK(iterator);
+ if (!strncasecmp(word, iterator->name, wordlen) &&
+ (!flags2 || ast_test_flag(&iterator->flags[1], flags2)) &&
+ ++which > state && iterator->expire > 0)
+ result = ast_strdup(iterator->name);
+ ASTOBJ_UNLOCK(iterator);
+ } while(0) );
+ return result;
+}
+
+/*! \brief Support routine for 'sip show history' CLI */
+static char *complete_sip_show_history(const char *line, const char *word, int pos, int state)
+{
+ if (pos == 3)
+ return complete_sipch(line, word, pos, state);
+
+ return NULL;
+}
+
+/*! \brief Support routine for 'sip show peer' CLI */
+static char *complete_sip_show_peer(const char *line, const char *word, int pos, int state)
+{
+ if (pos == 3)
+ return complete_sip_peer(word, state, 0);
+
+ return NULL;
+}
+
+/*! \brief Support routine for 'sip unregister' CLI */
+static char *complete_sip_unregister(const char *line, const char *word, int pos, int state)
+{
+ if (pos == 2)
+ return complete_sip_registered_peer(word, state, 0);
+
+ return NULL;
+}
+
+/*! \brief Do completion on user name */
+static char *complete_sip_user(const char *word, int state, int flags2)
+{
+ char *result = NULL;
+ int wordlen = strlen(word);
+ int which = 0;
+
+ ASTOBJ_CONTAINER_TRAVERSE(&userl, !result, do {
+ /* locking of the object is not required because only the name and flags are being compared */
+ if (!strncasecmp(word, iterator->name, wordlen)) {
+ if (flags2 && !ast_test_flag(&iterator->flags[1], flags2))
+ continue;
+ if (++which > state) {
+ result = ast_strdup(iterator->name);
+ }
+ }
+ } while(0) );
+ return result;
+}
+
+/*! \brief Support routine for 'sip show user' CLI */
+static char *complete_sip_show_user(const char *line, const char *word, int pos, int state)
+{
+ if (pos == 3)
+ return complete_sip_user(word, state, 0);
+
+ return NULL;
+}
+
+/*! \brief Support routine for 'sip notify' CLI */
+static char *complete_sipnotify(const char *line, const char *word, int pos, int state)
+{
+ char *c = NULL;
+
+ if (pos == 2) {
+ int which = 0;
+ char *cat = NULL;
+ int wordlen = strlen(word);
+
+ /* do completion for notify type */
+
+ if (!notify_types)
+ return NULL;
+
+ while ( (cat = ast_category_browse(notify_types, cat)) ) {
+ if (!strncasecmp(word, cat, wordlen) && ++which > state) {
+ c = ast_strdup(cat);
+ break;
+ }
+ }
+ return c;
+ }
+
+ if (pos > 2)
+ return complete_sip_peer(word, state, 0);
+
+ return NULL;
+}
+
+/*! \brief Show details of one active dialog */
+static char *sip_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct sip_pvt *cur;
+ size_t len;
+ int found = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show channel";
+ e->usage =
+ "Usage: sip show channel <call-id>\n"
+ " Provides detailed status on a given SIP dialog (identified by SIP call-id).\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_sipch(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+ len = strlen(a->argv[3]);
+ dialoglist_lock();
+ for (cur = dialoglist; cur; cur = cur->next) {
+ if (!strncasecmp(cur->callid, a->argv[3], len)) {
+ char formatbuf[BUFSIZ/2];
+ ast_cli(a->fd,"\n");
+ if (cur->subscribed != NONE)
+ ast_cli(a->fd, " * Subscription (type: %s)\n", subscription_type2str(cur->subscribed));
+ else
+ ast_cli(a->fd, " * SIP Call\n");
+ ast_cli(a->fd, " Curr. trans. direction: %s\n", ast_test_flag(&cur->flags[0], SIP_OUTGOING) ? "Outgoing" : "Incoming");
+ ast_cli(a->fd, " Call-ID: %s\n", cur->callid);
+ ast_cli(a->fd, " Owner channel ID: %s\n", cur->owner ? cur->owner->name : "<none>");
+ ast_cli(a->fd, " Our Codec Capability: %d\n", cur->capability);
+ ast_cli(a->fd, " Non-Codec Capability (DTMF): %d\n", cur->noncodeccapability);
+ ast_cli(a->fd, " Their Codec Capability: %d\n", cur->peercapability);
+ ast_cli(a->fd, " Joint Codec Capability: %d\n", cur->jointcapability);
+ ast_cli(a->fd, " Format: %s\n", ast_getformatname_multiple(formatbuf, sizeof(formatbuf), cur->owner ? cur->owner->nativeformats : 0) );
+ ast_cli(a->fd, " T.38 support %s\n", cli_yesno(cur->udptl != NULL));
+ ast_cli(a->fd, " Video support %s\n", cli_yesno(cur->vrtp != NULL));
+ ast_cli(a->fd, " MaxCallBR: %d kbps\n", cur->maxcallbitrate);
+ ast_cli(a->fd, " Theoretical Address: %s:%d\n", ast_inet_ntoa(cur->sa.sin_addr), ntohs(cur->sa.sin_port));
+ ast_cli(a->fd, " Received Address: %s:%d\n", ast_inet_ntoa(cur->recv.sin_addr), ntohs(cur->recv.sin_port));
+ ast_cli(a->fd, " SIP Transfer mode: %s\n", transfermode2str(cur->allowtransfer));
+ ast_cli(a->fd, " NAT Support: %s\n", nat2str(ast_test_flag(&cur->flags[0], SIP_NAT)));
+ ast_cli(a->fd, " Audio IP: %s %s\n", ast_inet_ntoa(cur->redirip.sin_addr.s_addr ? cur->redirip.sin_addr : cur->ourip.sin_addr), cur->redirip.sin_addr.s_addr ? "(Outside bridge)" : "(local)" );
+ ast_cli(a->fd, " Our Tag: %s\n", cur->tag);
+ ast_cli(a->fd, " Their Tag: %s\n", cur->theirtag);
+ ast_cli(a->fd, " SIP User agent: %s\n", cur->useragent);
+ if (!ast_strlen_zero(cur->username))
+ ast_cli(a->fd, " Username: %s\n", cur->username);
+ if (!ast_strlen_zero(cur->peername))
+ ast_cli(a->fd, " Peername: %s\n", cur->peername);
+ if (!ast_strlen_zero(cur->uri))
+ ast_cli(a->fd, " Original uri: %s\n", cur->uri);
+ if (!ast_strlen_zero(cur->cid_num))
+ ast_cli(a->fd, " Caller-ID: %s\n", cur->cid_num);
+ ast_cli(a->fd, " Need Destroy: %s\n", cli_yesno(cur->needdestroy));
+ ast_cli(a->fd, " Last Message: %s\n", cur->lastmsg);
+ ast_cli(a->fd, " Promiscuous Redir: %s\n", cli_yesno(ast_test_flag(&cur->flags[0], SIP_PROMISCREDIR)));
+ ast_cli(a->fd, " Route: %s\n", cur->route ? cur->route->hop : "N/A");
+ ast_cli(a->fd, " DTMF Mode: %s\n", dtmfmode2str(ast_test_flag(&cur->flags[0], SIP_DTMF)));
+ ast_cli(a->fd, " SIP Options: ");
+ if (cur->sipoptions) {
+ int x;
+ for (x=0 ; (x < (sizeof(sip_options) / sizeof(sip_options[0]))); x++) {
+ if (cur->sipoptions & sip_options[x].id)
+ ast_cli(a->fd, "%s ", sip_options[x].text);
+ }
+ ast_cli(a->fd, "\n");
+ } else
+ ast_cli(a->fd, "(none)\n");
+
+ if (!cur->stimer)
+ ast_cli(a->fd, " Session-Timer: Uninitiallized\n");
+ else {
+ ast_cli(a->fd, " Session-Timer: %s\n", cur->stimer->st_active ? "Active" : "Inactive");
+ if (cur->stimer->st_active == TRUE) {
+ ast_cli(a->fd, " S-Timer Interval: %d\n", cur->stimer->st_interval);
+ ast_cli(a->fd, " S-Timer Refresher: %s\n", strefresher2str(cur->stimer->st_ref));
+ ast_cli(a->fd, " S-Timer Expirys: %d\n", cur->stimer->st_expirys);
+ ast_cli(a->fd, " S-Timer Sched Id: %d\n", cur->stimer->st_schedid);
+ ast_cli(a->fd, " S-Timer Peer Sts: %s\n", cur->stimer->st_active_peer_ua ? "Active" : "Inactive");
+ ast_cli(a->fd, " S-Timer Cached Min-SE: %d\n", cur->stimer->st_cached_min_se);
+ ast_cli(a->fd, " S-Timer Cached SE: %d\n", cur->stimer->st_cached_max_se);
+ ast_cli(a->fd, " S-Timer Cached Ref: %s\n", strefresher2str(cur->stimer->st_cached_ref));
+ ast_cli(a->fd, " S-Timer Cached Mode: %s\n", stmode2str(cur->stimer->st_cached_mode));
+ }
+ }
+
+ ast_cli(a->fd, "\n\n");
+
+ found++;
+ }
+ }
+ dialoglist_unlock();
+ if (!found)
+ ast_cli(a->fd, "No such SIP Call ID starting with '%s'\n", a->argv[3]);
+ return CLI_SUCCESS;
+}
+
+/*! \brief Show history details of one dialog */
+static char *sip_show_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct sip_pvt *cur;
+ size_t len;
+ int found = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip show history";
+ e->usage =
+ "Usage: sip show history <call-id>\n"
+ " Provides detailed dialog history on a given SIP call (specified by call-id).\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_sip_show_history(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+ if (!recordhistory)
+ ast_cli(a->fd, "\n***Note: History recording is currently DISABLED. Use 'sip history' to ENABLE.\n");
+ len = strlen(a->argv[3]);
+ dialoglist_lock();
+ for (cur = dialoglist; cur; cur = cur->next) {
+ if (!strncasecmp(cur->callid, a->argv[3], len)) {
+ struct sip_history *hist;
+ int x = 0;
+
+ ast_cli(a->fd,"\n");
+ if (cur->subscribed != NONE)
+ ast_cli(a->fd, " * Subscription\n");
+ else
+ ast_cli(a->fd, " * SIP Call\n");
+ if (cur->history)
+ AST_LIST_TRAVERSE(cur->history, hist, list)
+ ast_cli(a->fd, "%d. %s\n", ++x, hist->event);
+ if (x == 0)
+ ast_cli(a->fd, "Call '%s' has no history\n", cur->callid);
+ found++;
+ }
+ }
+ dialoglist_unlock();
+ if (!found)
+ ast_cli(a->fd, "No such SIP Call ID starting with '%s'\n", a->argv[3]);
+ return CLI_SUCCESS;
+}
+
+/*! \brief Dump SIP history to debug log file at end of lifespan for SIP dialog */
+static void sip_dump_history(struct sip_pvt *dialog)
+{
+ int x = 0;
+ struct sip_history *hist;
+ static int errmsg = 0;
+
+ if (!dialog)
+ return;
+
+ if (!option_debug && !sipdebug) {
+ if (!errmsg) {
+ ast_log(LOG_NOTICE, "You must have debugging enabled (SIP or Asterisk) in order to dump SIP history.\n");
+ errmsg = 1;
+ }
+ return;
+ }
+
+ ast_debug(1, "\n---------- SIP HISTORY for '%s' \n", dialog->callid);
+ if (dialog->subscribed)
+ ast_debug(1, " * Subscription\n");
+ else
+ ast_debug(1, " * SIP Call\n");
+ if (dialog->history)
+ AST_LIST_TRAVERSE(dialog->history, hist, list)
+ ast_debug(1, " %-3.3d. %s\n", ++x, hist->event);
+ if (!x)
+ ast_debug(1, "Call '%s' has no history\n", dialog->callid);
+ ast_debug(1, "\n---------- END SIP HISTORY for '%s' \n", dialog->callid);
+}
+
+
+/*! \brief Receive SIP INFO Message
+\note Doesn't read the duration of the DTMF signal */
+static void handle_request_info(struct sip_pvt *p, struct sip_request *req)
+{
+ char buf[1024];
+ unsigned int event;
+ const char *c = get_header(req, "Content-Type");
+
+ /* Need to check the media/type */
+ if (!strcasecmp(c, "application/dtmf-relay") ||
+ !strcasecmp(c, "application/vnd.nortelnetworks.digits")) {
+ unsigned int duration = 0;
+
+ if (!p->owner) { /* not a PBX call */
+ transmit_response(p, "481 Call leg/transaction does not exist", req);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return;
+ }
+
+ /* Try getting the "signal=" part */
+ if (ast_strlen_zero(c = get_body(req, "Signal")) && ast_strlen_zero(c = get_body(req, "d"))) {
+ ast_log(LOG_WARNING, "Unable to retrieve DTMF signal from INFO message from %s\n", p->callid);
+ transmit_response(p, "200 OK", req); /* Should return error */
+ return;
+ } else {
+ ast_copy_string(buf, c, sizeof(buf));
+ }
+
+ if (!ast_strlen_zero((c = get_body(req, "Duration"))))
+ duration = atoi(c);
+ if (!duration)
+ duration = 100; /* 100 ms */
+
+
+ if (ast_strlen_zero(buf)) {
+ transmit_response(p, "200 OK", req);
+ return;
+ }
+
+ if (buf[0] == '*')
+ event = 10;
+ else if (buf[0] == '#')
+ event = 11;
+ else if ((buf[0] >= 'A') && (buf[0] <= 'D'))
+ event = 12 + buf[0] - 'A';
+ else if (buf[0] == '!')
+ event = 16;
+ else
+ event = atoi(buf);
+ if (event == 16) {
+ /* send a FLASH event */
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_FLASH, };
+ ast_queue_frame(p->owner, &f);
+ if (sipdebug)
+ ast_verbose("* DTMF-relay event received: FLASH\n");
+ } else {
+ /* send a DTMF event */
+ struct ast_frame f = { AST_FRAME_DTMF, };
+ if (event < 10) {
+ f.subclass = '0' + event;
+ } else if (event < 11) {
+ f.subclass = '*';
+ } else if (event < 12) {
+ f.subclass = '#';
+ } else if (event < 16) {
+ f.subclass = 'A' + (event - 12);
+ }
+ f.len = duration;
+ ast_queue_frame(p->owner, &f);
+ if (sipdebug)
+ ast_verbose("* DTMF-relay event received: %c\n", f.subclass);
+ }
+ transmit_response(p, "200 OK", req);
+ return;
+ } else if (!strcasecmp(c, "application/dtmf")) {
+ unsigned int duration = 0;
+
+ if (!p->owner) { /* not a PBX call */
+ transmit_response(p, "481 Call leg/transaction does not exist", req);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return;
+ }
+
+
+
+ get_msg_text(buf, sizeof(buf), req);
+ duration = 100; /* 100 ms */
+
+ if (ast_strlen_zero(buf)) {
+ transmit_response(p, "200 OK", req);
+ return;
+ }
+ event = atoi(buf);
+ if (event == 16) {
+ /* send a FLASH event */
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_FLASH, };
+ ast_queue_frame(p->owner, &f);
+ if (sipdebug)
+ ast_verbose("* DTMF-relay event received: FLASH\n");
+ } else {
+ /* send a DTMF event */
+ struct ast_frame f = { AST_FRAME_DTMF, };
+ if (event < 10) {
+ f.subclass = '0' + event;
+ } else if (event < 11) {
+ f.subclass = '*';
+ } else if (event < 12) {
+ f.subclass = '#';
+ } else if (event < 16) {
+ f.subclass = 'A' + (event - 12);
+ }
+ f.len = duration;
+ ast_queue_frame(p->owner, &f);
+ if (sipdebug)
+ ast_verbose("* DTMF-relay event received: %c\n", f.subclass);
+ }
+ transmit_response(p, "200 OK", req);
+ return;
+
+ } else if (!strcasecmp(c, "application/media_control+xml")) {
+ /* Eh, we'll just assume it's a fast picture update for now */
+ if (p->owner)
+ ast_queue_control(p->owner, AST_CONTROL_VIDUPDATE);
+ transmit_response(p, "200 OK", req);
+ return;
+ } else if (!ast_strlen_zero(c = get_header(req, "X-ClientCode"))) {
+ /* Client code (from SNOM phone) */
+ if (ast_test_flag(&p->flags[0], SIP_USECLIENTCODE)) {
+ if (p->owner && p->owner->cdr)
+ ast_cdr_setuserfield(p->owner, c);
+ if (p->owner && ast_bridged_channel(p->owner) && ast_bridged_channel(p->owner)->cdr)
+ ast_cdr_setuserfield(ast_bridged_channel(p->owner), c);
+ transmit_response(p, "200 OK", req);
+ } else {
+ transmit_response(p, "403 Forbidden", req);
+ }
+ return;
+ } else if (!ast_strlen_zero(c = get_header(req, "Record"))) {
+ /* INFO messages generated by some phones to start/stop recording
+ on phone calls.
+ OEJ: I think this should be something that is enabled/disabled
+ per device. I don't want incoming callers to record calls in my
+ pbx.
+ */
+ /* first, get the feature string, if it exists */
+ struct ast_call_feature *feat;
+ int j;
+ struct ast_frame f = { AST_FRAME_DTMF, };
+
+ ast_rdlock_call_features();
+ feat = ast_find_call_feature("automon");
+ if (!feat || ast_strlen_zero(feat->exten)) {
+ ast_log(LOG_WARNING,"Recording requested, but no One Touch Monitor registered. (See features.conf)\n");
+ /* 403 means that we don't support this feature, so don't request it again */
+ transmit_response(p, "403 Forbidden", req);
+ ast_unlock_call_features();
+ return;
+ }
+ /* Send the feature code to the PBX as DTMF, just like the handset had sent it */
+ f.len = 100;
+ for (j=0; j < strlen(feat->exten); j++) {
+ f.subclass = feat->exten[j];
+ ast_queue_frame(p->owner, &f);
+ if (sipdebug)
+ ast_verbose("* DTMF-relay event faked: %c\n", f.subclass);
+ }
+ ast_unlock_call_features();
+
+ ast_debug(1, "Got a Request to Record the channel, state %s\n", c);
+ transmit_response(p, "200 OK", req);
+ return;
+ } else if (ast_strlen_zero(c = get_header(req, "Content-Length")) || !strcasecmp(c, "0")) {
+ /* This is probably just a packet making sure the signalling is still up, just send back a 200 OK */
+ transmit_response(p, "200 OK", req);
+ return;
+ }
+
+ /* Other type of INFO message, not really understood by Asterisk */
+ /* if (get_msg_text(buf, sizeof(buf), req)) { */
+
+ ast_log(LOG_WARNING, "Unable to parse INFO message from %s. Content %s\n", p->callid, buf);
+ transmit_response(p, "415 Unsupported media type", req);
+ return;
+}
+
+/*! \brief Enable SIP Debugging for a single IP */
+static char *sip_do_debug_ip(int fd, char *arg)
+{
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ int port = 0;
+ char *p;
+
+ p = arg;
+ strsep(&p, ":");
+ if (p)
+ port = atoi(p);
+ hp = ast_gethostbyname(arg, &ahp);
+ if (hp == NULL)
+ return CLI_SHOWUSAGE;
+
+ debugaddr.sin_family = AF_INET;
+ memcpy(&debugaddr.sin_addr, hp->h_addr, sizeof(debugaddr.sin_addr));
+ debugaddr.sin_port = htons(port);
+ if (port == 0)
+ ast_cli(fd, "SIP Debugging Enabled for IP: %s\n", ast_inet_ntoa(debugaddr.sin_addr));
+ else
+ ast_cli(fd, "SIP Debugging Enabled for IP: %s:%d\n", ast_inet_ntoa(debugaddr.sin_addr), port);
+
+ sipdebug |= sip_debug_console;
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief sip_do_debug_peer: Turn on SIP debugging for a given peer */
+static char *sip_do_debug_peer(int fd, char *arg)
+{
+ struct sip_peer *peer = find_peer(arg, NULL, 1);
+ if (!peer)
+ ast_cli(fd, "No such peer '%s'\n", arg);
+ else if (peer->addr.sin_addr.s_addr == 0)
+ ast_cli(fd, "Unable to get IP address of peer '%s'\n", arg);
+ else {
+ debugaddr.sin_family = AF_INET;
+ debugaddr.sin_addr = peer->addr.sin_addr;
+ debugaddr.sin_port = peer->addr.sin_port;
+ ast_cli(fd, "SIP Debugging Enabled for IP: %s:%d\n",
+ ast_inet_ntoa(debugaddr.sin_addr), ntohs(debugaddr.sin_port));
+ sipdebug |= sip_debug_console;
+ }
+ if (peer)
+ unref_peer(peer);
+ return CLI_SUCCESS;
+}
+
+/*! \brief Turn on SIP debugging (CLI command) */
+static char *sip_do_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int oldsipdebug = sipdebug & sip_debug_console;
+ char *what;
+
+ if (cmd == CLI_INIT) {
+ e->command = "sip set debug {on|off|ip|peer}";
+ e->usage =
+ "Usage: sip set debug {off|on|ip addr[:port]|peer peername}\n"
+ " Globally disables dumping of SIP packets,\n"
+ " or enables it either globally or for a (single)\n"
+ " IP address or registered peer.\n";
+ return NULL;
+ } else if (cmd == CLI_GENERATE) {
+ if (a->pos == 4 && strcasestr(a->line, " peer")) /* XXX should check on argv too */
+ return complete_sip_peer(a->word, a->n, 0);
+ return NULL;
+ }
+
+ what = a->argv[e->args-1]; /* guaranteed to exist */
+ if (a->argc == e->args) { /* on/off */
+ if (!strcasecmp(what, "on")) {
+ sipdebug |= sip_debug_console;
+ sipdebug_text = 1; /*! \note this can be a special debug command - "sip debug text" or something */
+ memset(&debugaddr, 0, sizeof(debugaddr));
+ ast_cli(a->fd, "SIP Debugging %senabled\n", oldsipdebug ? "re-" : "");
+ return CLI_SUCCESS;
+ } else if (!strcasecmp(what, "off")) {
+ sipdebug &= ~sip_debug_console;
+ sipdebug_text = 0;
+ ast_cli(a->fd, "SIP Debugging Disabled\n");
+ return CLI_SUCCESS;
+ }
+ } else if (a->argc == e->args +1) {/* ip/peer */
+ if (!strcasecmp(what, "ip"))
+ return sip_do_debug_ip(a->fd, a->argv[e->args]);
+ else if (!strcasecmp(what, "peer"))
+ return sip_do_debug_peer(a->fd, a->argv[e->args]);
+ }
+ return CLI_SHOWUSAGE; /* default, failure */
+}
+
+/*! \brief Cli command to send SIP notify to peer */
+static char *sip_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_variable *varlist;
+ int i;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip notify";
+ e->usage =
+ "Usage: sip notify <type> <peer> [<peer>...]\n"
+ " Send a NOTIFY message to a SIP peer or peers\n"
+ " Message types are defined in sip_notify.conf\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_sipnotify(a->line, a->word, a->pos, a->n);
+ }
+
+
+ if (a->argc < 4)
+ return CLI_SHOWUSAGE;
+
+ if (!notify_types) {
+ ast_cli(a->fd, "No %s file found, or no types listed there\n", notify_config);
+ return CLI_FAILURE;
+ }
+
+ varlist = ast_variable_browse(notify_types, a->argv[2]);
+
+ if (!varlist) {
+ ast_cli(a->fd, "Unable to find notify type '%s'\n", a->argv[2]);
+ return CLI_FAILURE;
+ }
+
+ for (i = 3; i < a->argc; i++) {
+ struct sip_pvt *p;
+ struct sip_request req;
+ struct ast_variable *var;
+
+ if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY))) {
+ ast_log(LOG_WARNING, "Unable to build sip pvt data for notify (memory/socket error)\n");
+ return CLI_FAILURE;
+ }
+
+ if (create_addr(p, a->argv[i])) {
+ /* Maybe they're not registered, etc. */
+ sip_destroy(p);
+ ast_cli(a->fd, "Could not create address for '%s'\n", a->argv[i]);
+ continue;
+ }
+
+ initreqprep(&req, p, SIP_NOTIFY);
+
+ for (var = varlist; var; var = var->next) {
+ char buf[512];
+ ast_copy_string(buf, var->value, sizeof(buf));
+ add_header(&req, var->name, ast_unescape_semicolon(buf));
+ }
+
+ /* Recalculate our side, and recalculate Call ID */
+ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip);
+ build_via(p);
+ build_callid_pvt(p);
+ ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n", a->argv[2], a->argv[i]);
+ transmit_sip_request(p, &req);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ dialog_unref(p);
+ }
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief Enable SIP History logging (CLI) */
+static char *sip_do_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip history";
+ e->usage =
+ "Usage: sip history\n"
+ " Enables recording of SIP dialog history for debugging purposes.\n"
+ " Use 'sip show history' to view the history of a call number.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 2) {
+ return CLI_SHOWUSAGE;
+ }
+ recordhistory = TRUE;
+ ast_cli(a->fd, "SIP History Recording Enabled (use 'sip show history')\n");
+ return CLI_SUCCESS;
+}
+
+/*! \brief Disable SIP History logging (CLI) */
+static char *sip_no_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip history off";
+ e->usage =
+ "Usage: sip history off\n"
+ " Disables recording of SIP dialog history for debugging purposes\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+ recordhistory = FALSE;
+ ast_cli(a->fd, "SIP History Recording Disabled\n");
+ return CLI_SUCCESS;
+}
+
+/*! \brief Authenticate for outbound registration */
+static int do_register_auth(struct sip_pvt *p, struct sip_request *req, enum sip_auth_type code)
+{
+ char *header, *respheader;
+ char digest[1024];
+
+ p->authtries++;
+ auth_headers(code, &header, &respheader);
+ memset(digest,0,sizeof(digest));
+ if (reply_digest(p, req, header, SIP_REGISTER, digest, sizeof(digest))) {
+ /* There's nothing to use for authentication */
+ /* No digest challenge in request */
+ if (sip_debug_test_pvt(p) && p->registry)
+ ast_verbose("No authentication challenge, sending blank registration to domain/host name %s\n", p->registry->hostname);
+ /* No old challenge */
+ return -1;
+ }
+ if (p->do_history)
+ append_history(p, "RegistryAuth", "Try: %d", p->authtries);
+ if (sip_debug_test_pvt(p) && p->registry)
+ ast_verbose("Responding to challenge, registration to domain/host name %s\n", p->registry->hostname);
+ return transmit_register(p->registry, SIP_REGISTER, digest, respheader);
+}
+
+/*! \brief Add authentication on outbound SIP packet */
+static int do_proxy_auth(struct sip_pvt *p, struct sip_request *req, enum sip_auth_type code, int sipmethod, int init)
+{
+ char *header, *respheader;
+ char digest[1024];
+
+ if (!p->options && !(p->options = ast_calloc(1, sizeof(*p->options))))
+ return -2;
+
+ p->authtries++;
+ auth_headers(code, &header, &respheader);
+ ast_debug(2, "Auth attempt %d on %s\n", p->authtries, sip_methods[sipmethod].text);
+ memset(digest, 0, sizeof(digest));
+ if (reply_digest(p, req, header, sipmethod, digest, sizeof(digest) )) {
+ /* No way to authenticate */
+ return -1;
+ }
+ /* Now we have a reply digest */
+ p->options->auth = digest;
+ p->options->authheader = respheader;
+ return transmit_invite(p, sipmethod, sipmethod == SIP_INVITE, init);
+}
+
+/*! \brief reply to authentication for outbound registrations
+\return Returns -1 if we have no auth
+\note This is used for register= servers in sip.conf, SIP proxies we register
+ with for receiving calls from. */
+static int reply_digest(struct sip_pvt *p, struct sip_request *req, char *header, int sipmethod, char *digest, int digest_len)
+{
+ char tmp[512];
+ char *c;
+ char oldnonce[256];
+
+ /* table of recognised keywords, and places where they should be copied */
+ const struct x {
+ const char *key;
+ const ast_string_field *field;
+ } *i, keys[] = {
+ { "realm=", &p->realm },
+ { "nonce=", &p->nonce },
+ { "opaque=", &p->opaque },
+ { "qop=", &p->qop },
+ { "domain=", &p->domain },
+ { NULL, 0 },
+ };
+
+ ast_copy_string(tmp, get_header(req, header), sizeof(tmp));
+ if (ast_strlen_zero(tmp))
+ return -1;
+ if (strncasecmp(tmp, "Digest ", strlen("Digest "))) {
+ ast_log(LOG_WARNING, "missing Digest.\n");
+ return -1;
+ }
+ c = tmp + strlen("Digest ");
+ ast_copy_string(oldnonce, p->nonce, sizeof(oldnonce));
+ while (c && *(c = ast_skip_blanks(c))) { /* lookup for keys */
+ for (i = keys; i->key != NULL; i++) {
+ char *src, *separator;
+ if (strncasecmp(c, i->key, strlen(i->key)) != 0)
+ continue;
+ /* Found. Skip keyword, take text in quotes or up to the separator. */
+ c += strlen(i->key);
+ if (*c == '"') {
+ src = ++c;
+ separator = "\"";
+ } else {
+ src = c;
+ separator = ",";
+ }
+ strsep(&c, separator); /* clear separator and move ptr */
+ ast_string_field_ptr_set(p, i->field, src);
+ break;
+ }
+ if (i->key == NULL) /* not found, try ',' */
+ strsep(&c, ",");
+ }
+ /* Reset nonce count */
+ if (strcmp(p->nonce, oldnonce))
+ p->noncecount = 0;
+
+ /* Save auth data for following registrations */
+ if (p->registry) {
+ struct sip_registry *r = p->registry;
+
+ if (strcmp(r->nonce, p->nonce)) {
+ ast_string_field_set(r, realm, p->realm);
+ ast_string_field_set(r, nonce, p->nonce);
+ ast_string_field_set(r, domain, p->domain);
+ ast_string_field_set(r, opaque, p->opaque);
+ ast_string_field_set(r, qop, p->qop);
+ r->noncecount = 0;
+ }
+ }
+ return build_reply_digest(p, sipmethod, digest, digest_len);
+}
+
+/*! \brief Build reply digest
+\return Returns -1 if we have no auth
+\note Build digest challenge for authentication of peers (for registration)
+ and users (for calls). Also used for authentication of CANCEL and BYE
+*/
+static int build_reply_digest(struct sip_pvt *p, int method, char* digest, int digest_len)
+{
+ char a1[256];
+ char a2[256];
+ char a1_hash[256];
+ char a2_hash[256];
+ char resp[256];
+ char resp_hash[256];
+ char uri[256];
+ char cnonce[80];
+ const char *username;
+ const char *secret;
+ const char *md5secret;
+ struct sip_auth *auth = NULL; /* Realm authentication */
+
+ if (!ast_strlen_zero(p->domain))
+ ast_copy_string(uri, p->domain, sizeof(uri));
+ else if (!ast_strlen_zero(p->uri))
+ ast_copy_string(uri, p->uri, sizeof(uri));
+ else
+ snprintf(uri, sizeof(uri), "sip:%s@%s",p->username, ast_inet_ntoa(p->sa.sin_addr));
+
+ snprintf(cnonce, sizeof(cnonce), "%08lx", ast_random());
+
+ /* Check if we have separate auth credentials */
+ if ((auth = find_realm_authentication(authl, p->realm))) {
+ ast_log(LOG_WARNING, "use realm [%s] from peer [%s][%s]\n",
+ auth->username, p->peername, p->username);
+ username = auth->username;
+ secret = auth->secret;
+ md5secret = auth->md5secret;
+ if (sipdebug)
+ ast_debug(1,"Using realm %s authentication for call %s\n", p->realm, p->callid);
+ } else {
+ /* No authentication, use peer or register= config */
+ username = p->authname;
+ secret = p->peersecret;
+ md5secret = p->peermd5secret;
+ }
+ if (ast_strlen_zero(username)) /* We have no authentication */
+ return -1;
+
+ /* Calculate SIP digest response */
+ snprintf(a1,sizeof(a1),"%s:%s:%s", username, p->realm, secret);
+ snprintf(a2,sizeof(a2),"%s:%s", sip_methods[method].text, uri);
+ if (!ast_strlen_zero(md5secret))
+ ast_copy_string(a1_hash, md5secret, sizeof(a1_hash));
+ else
+ ast_md5_hash(a1_hash,a1);
+ ast_md5_hash(a2_hash,a2);
+
+ p->noncecount++;
+ if (!ast_strlen_zero(p->qop))
+ snprintf(resp,sizeof(resp),"%s:%s:%08x:%s:%s:%s", a1_hash, p->nonce, p->noncecount, cnonce, "auth", a2_hash);
+ else
+ snprintf(resp,sizeof(resp),"%s:%s:%s", a1_hash, p->nonce, a2_hash);
+ ast_md5_hash(resp_hash, resp);
+ /* XXX We hard code our qop to "auth" for now. XXX */
+ if (!ast_strlen_zero(p->qop))
+ snprintf(digest, digest_len, "Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\", opaque=\"%s\", qop=auth, cnonce=\"%s\", nc=%08x", username, p->realm, uri, p->nonce, resp_hash, p->opaque, cnonce, p->noncecount);
+ else
+ snprintf(digest, digest_len, "Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\", opaque=\"%s\"", username, p->realm, uri, p->nonce, resp_hash, p->opaque);
+
+ append_history(p, "AuthResp", "Auth response sent for %s in realm %s - nc %d", username, p->realm, p->noncecount);
+
+ return 0;
+}
+
+/*! \brief Read SIP header (dialplan function) */
+static int func_header_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len)
+{
+ struct sip_pvt *p;
+ const char *content = NULL;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(header);
+ AST_APP_ARG(number);
+ );
+ int i, number, start = 0;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "This function requires a header name.\n");
+ return -1;
+ }
+
+ ast_channel_lock(chan);
+ if (!IS_SIP_TECH(chan->tech)) {
+ ast_log(LOG_WARNING, "This function can only be used on SIP channels.\n");
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ AST_STANDARD_APP_ARGS(args, data);
+ if (!args.number) {
+ number = 1;
+ } else {
+ sscanf(args.number, "%d", &number);
+ if (number < 1)
+ number = 1;
+ }
+
+ p = chan->tech_pvt;
+
+ /* If there is no private structure, this channel is no longer alive */
+ if (!p) {
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ for (i = 0; i < number; i++)
+ content = __get_header(&p->initreq, args.header, &start);
+
+ if (ast_strlen_zero(content)) {
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ ast_copy_string(buf, content, len);
+ ast_channel_unlock(chan);
+
+ return 0;
+}
+
+static struct ast_custom_function sip_header_function = {
+ .name = "SIP_HEADER",
+ .synopsis = "Gets the specified SIP header",
+ .syntax = "SIP_HEADER(<name>[,<number>])",
+ .desc = "Since there are several headers (such as Via) which can occur multiple\n"
+ "times, SIP_HEADER takes an optional second argument to specify which header with\n"
+ "that name to retrieve. Headers start at offset 1.\n",
+ .read = func_header_read,
+};
+
+/*! \brief Dial plan function to check if domain is local */
+static int func_check_sipdomain(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "CHECKSIPDOMAIN requires an argument - A domain name\n");
+ return -1;
+ }
+ if (check_sip_domain(data, NULL, 0))
+ ast_copy_string(buf, data, len);
+ else
+ buf[0] = '\0';
+ return 0;
+}
+
+static struct ast_custom_function checksipdomain_function = {
+ .name = "CHECKSIPDOMAIN",
+ .synopsis = "Checks if domain is a local domain",
+ .syntax = "CHECKSIPDOMAIN(<domain|IP>)",
+ .read = func_check_sipdomain,
+ .desc = "This function checks if the domain in the argument is configured\n"
+ "as a local SIP domain that this Asterisk server is configured to handle.\n"
+ "Returns the domain name if it is locally handled, otherwise an empty string.\n"
+ "Check the domain= configuration in sip.conf\n",
+};
+
+/*! \brief ${SIPPEER()} Dialplan function - reads peer data */
+static int function_sippeer(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ struct sip_peer *peer;
+ char *colname;
+
+ if ((colname = strchr(data, ':'))) { /*! \todo Will be deprecated after 1.4 */
+ static int deprecation_warning = 0;
+ *colname++ = '\0';
+ if (deprecation_warning++ % 10 == 0)
+ ast_log(LOG_WARNING, "SIPPEER(): usage of ':' to separate arguments is deprecated. Please use ',' instead.\n");
+ } else if ((colname = strchr(data, ',')))
+ *colname++ = '\0';
+ else
+ colname = "ip";
+
+ if (!(peer = find_peer(data, NULL, 1)))
+ return -1;
+
+ if (!strcasecmp(colname, "ip")) {
+ ast_copy_string(buf, peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "", len);
+ } else if (!strcasecmp(colname, "port")) {
+ snprintf(buf, len, "%d", ntohs(peer->addr.sin_port));
+ } else if (!strcasecmp(colname, "status")) {
+ peer_status(peer, buf, len);
+ } else if (!strcasecmp(colname, "language")) {
+ ast_copy_string(buf, peer->language, len);
+ } else if (!strcasecmp(colname, "regexten")) {
+ ast_copy_string(buf, peer->regexten, len);
+ } else if (!strcasecmp(colname, "limit")) {
+ snprintf(buf, len, "%d", peer->call_limit);
+ } else if (!strcasecmp(colname, "busylevel")) {
+ snprintf(buf, len, "%d", peer->busy_level);
+ } else if (!strcasecmp(colname, "curcalls")) {
+ snprintf(buf, len, "%d", peer->inUse);
+ } else if (!strcasecmp(colname, "accountcode")) {
+ ast_copy_string(buf, peer->accountcode, len);
+ } else if (!strcasecmp(colname, "callgroup")) {
+ ast_print_group(buf, len, peer->callgroup);
+ } else if (!strcasecmp(colname, "pickupgroup")) {
+ ast_print_group(buf, len, peer->pickupgroup);
+ } else if (!strcasecmp(colname, "useragent")) {
+ ast_copy_string(buf, peer->useragent, len);
+ } else if (!strcasecmp(colname, "mailbox")) {
+ struct ast_str *mailbox_str = ast_str_alloca(512);
+ peer_mailboxes_to_str(&mailbox_str, peer);
+ ast_copy_string(buf, mailbox_str->str, len);
+ } else if (!strcasecmp(colname, "context")) {
+ ast_copy_string(buf, peer->context, len);
+ } else if (!strcasecmp(colname, "expire")) {
+ snprintf(buf, len, "%d", peer->expire);
+ } else if (!strcasecmp(colname, "dynamic")) {
+ ast_copy_string(buf, peer->host_dynamic ? "yes" : "no", len);
+ } else if (!strcasecmp(colname, "callerid_name")) {
+ ast_copy_string(buf, peer->cid_name, len);
+ } else if (!strcasecmp(colname, "callerid_num")) {
+ ast_copy_string(buf, peer->cid_num, len);
+ } else if (!strcasecmp(colname, "codecs")) {
+ ast_getformatname_multiple(buf, len -1, peer->capability);
+ } else if (!strncasecmp(colname, "codec[", 6)) {
+ char *codecnum;
+ int index = 0, codec = 0;
+
+ codecnum = colname + 6; /* move past the '[' */
+ codecnum = strsep(&codecnum, "]"); /* trim trailing ']' if any */
+ index = atoi(codecnum);
+ if((codec = ast_codec_pref_index(&peer->prefs, index))) {
+ ast_copy_string(buf, ast_getformatname(codec), len);
+ }
+ }
+
+ unref_peer(peer);
+
+ return 0;
+}
+
+/*! \brief Structure to declare a dialplan function: SIPPEER */
+struct ast_custom_function sippeer_function = {
+ .name = "SIPPEER",
+ .synopsis = "Gets SIP peer information",
+ .syntax = "SIPPEER(<peername>[,item])",
+ .read = function_sippeer,
+ .desc = "Valid items are:\n"
+ "- ip (default) The IP address.\n"
+ "- port The port number\n"
+ "- mailbox The configured mailbox.\n"
+ "- context The configured context.\n"
+ "- expire The epoch time of the next expire.\n"
+ "- dynamic Is it dynamic? (yes/no).\n"
+ "- callerid_name The configured Caller ID name.\n"
+ "- callerid_num The configured Caller ID number.\n"
+ "- callgroup The configured Callgroup.\n"
+ "- pickupgroup The configured Pickupgroup.\n"
+ "- codecs The configured codecs.\n"
+ "- status Status (if qualify=yes).\n"
+ "- regexten Registration extension\n"
+ "- limit Call limit (call-limit)\n"
+ "- busylevel Configured call level for signalling busy\n"
+ "- curcalls Current amount of calls \n"
+ " Only available if call-limit is set\n"
+ "- language Default language for peer\n"
+ "- accountcode Account code for this peer\n"
+ "- useragent Current user agent id for peer\n"
+ "- codec[x] Preferred codec index number 'x' (beginning with zero).\n"
+ "\n"
+};
+
+/*! \brief ${SIPCHANINFO()} Dialplan function - reads sip channel data */
+static int function_sipchaninfo_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ struct sip_pvt *p;
+
+ *buf = 0;
+
+ if (!data) {
+ ast_log(LOG_WARNING, "This function requires a parameter name.\n");
+ return -1;
+ }
+
+ ast_channel_lock(chan);
+ if (!IS_SIP_TECH(chan->tech)) {
+ ast_log(LOG_WARNING, "This function can only be used on SIP channels.\n");
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ p = chan->tech_pvt;
+
+ /* If there is no private structure, this channel is no longer alive */
+ if (!p) {
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ if (!strcasecmp(data, "peerip")) {
+ ast_copy_string(buf, p->sa.sin_addr.s_addr ? ast_inet_ntoa(p->sa.sin_addr) : "", len);
+ } else if (!strcasecmp(data, "recvip")) {
+ ast_copy_string(buf, p->recv.sin_addr.s_addr ? ast_inet_ntoa(p->recv.sin_addr) : "", len);
+ } else if (!strcasecmp(data, "from")) {
+ ast_copy_string(buf, p->from, len);
+ } else if (!strcasecmp(data, "uri")) {
+ ast_copy_string(buf, p->uri, len);
+ } else if (!strcasecmp(data, "useragent")) {
+ ast_copy_string(buf, p->useragent, len);
+ } else if (!strcasecmp(data, "peername")) {
+ ast_copy_string(buf, p->peername, len);
+ } else if (!strcasecmp(data, "t38passthrough")) {
+ if (p->t38.state == T38_DISABLED)
+ ast_copy_string(buf, "0", sizeof("0"));
+ else /* T38 is offered or enabled in this call */
+ ast_copy_string(buf, "1", sizeof("1"));
+ } else {
+ ast_channel_unlock(chan);
+ return -1;
+ }
+ ast_channel_unlock(chan);
+
+ return 0;
+}
+
+/*! \brief Structure to declare a dialplan function: SIPCHANINFO */
+static struct ast_custom_function sipchaninfo_function = {
+ .name = "SIPCHANINFO",
+ .synopsis = "Gets the specified SIP parameter from the current channel",
+ .syntax = "SIPCHANINFO(item)",
+ .read = function_sipchaninfo_read,
+ .desc = "Valid items are:\n"
+ "- peerip The IP address of the peer.\n"
+ "- recvip The source IP address of the peer.\n"
+ "- from The URI from the From: header.\n"
+ "- uri The URI from the Contact: header.\n"
+ "- useragent The useragent.\n"
+ "- peername The name of the peer.\n"
+ "- t38passthrough 1 if T38 is offered or enabled in this channel, otherwise 0\n"
+};
+
+/*! \brief Parse 302 Moved temporalily response */
+static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req)
+{
+ char tmp[BUFSIZ];
+ char *s, *e, *t;
+ char *domain;
+
+ ast_copy_string(tmp, get_header(req, "Contact"), sizeof(tmp));
+ if ((t = strchr(tmp, ',')))
+ *t = '\0';
+ s = remove_uri_parameters(get_in_brackets(tmp));
+ if (ast_test_flag(&p->flags[0], SIP_PROMISCREDIR)) {
+ if (!strncasecmp(s, "sip:", 4))
+ s += 4;
+ else if (!strncasecmp(s, "sips:", 5))
+ s += 5;
+ e = strchr(s, '/');
+ if (e)
+ *e = '\0';
+ ast_debug(2, "Found promiscuous redirection to 'SIP/%s'\n", s);
+ if (p->owner)
+ ast_string_field_build(p->owner, call_forward, "SIP/%s", s);
+ } else {
+ e = strchr(tmp, '@');
+ if (e) {
+ *e++ = '\0';
+ domain = e;
+ } else {
+ /* No username part */
+ domain = tmp;
+ }
+ e = strchr(tmp, '/'); /* WHEN do we hae a forward slash in the URI? */
+ if (e)
+ *e = '\0';
+
+ if (!strncasecmp(s, "sip:", 4))
+ s += 4;
+ else if (!strncasecmp(s, "sips:", 5))
+ s += 5;
+ e = strchr(s, ';'); /* And username ; parameters? */
+ if (e)
+ *e = '\0';
+ ast_debug(2, "Received 302 Redirect to extension '%s' (domain %s)\n", s, domain);
+ if (p->owner) {
+ pbx_builtin_setvar_helper(p->owner, "SIPDOMAIN", domain);
+ ast_string_field_set(p->owner, call_forward, s);
+ }
+ }
+}
+
+/*! \brief Check pending actions on SIP call */
+static void check_pendings(struct sip_pvt *p)
+{
+ if (ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ /* if we can't BYE, then this is really a pending CANCEL */
+ if (p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA)
+ transmit_request(p, SIP_CANCEL, p->ocseq, XMIT_RELIABLE, FALSE);
+ /* Actually don't destroy us yet, wait for the 487 on our original
+ INVITE, but do set an autodestruct just in case we never get it. */
+ else {
+ /* We have a pending outbound invite, don't send someting
+ new in-transaction */
+ if (p->pendinginvite)
+ return;
+
+ /* Perhaps there is an SD change INVITE outstanding */
+ transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, TRUE);
+ }
+ ast_clear_flag(&p->flags[0], SIP_PENDINGBYE);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ } else if (ast_test_flag(&p->flags[0], SIP_NEEDREINVITE)) {
+ /* if we can't REINVITE, hold it for later */
+ if (p->pendinginvite || p->invitestate == INV_CALLING || p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA || p->waitid > 0) {
+ ast_debug(2, "NOT Sending pending reinvite (yet) on '%s'\n", p->callid);
+ } else {
+ ast_debug(2, "Sending pending reinvite on '%s'\n", p->callid);
+ /* Didn't get to reinvite yet, so do it now */
+ transmit_reinvite_with_sdp(p, FALSE, FALSE);
+ ast_clear_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ }
+}
+
+/*! \brief Reset the NEEDREINVITE flag after waiting when we get 491 on a Re-invite
+ to avoid race conditions between asterisk servers.
+ Called from the scheduler.
+*/
+static int sip_reinvite_retry(const void *data)
+{
+ struct sip_pvt *p = (struct sip_pvt *) data;
+
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ p->waitid = -1;
+ return 0;
+}
+
+
+/*! \brief Handle SIP response to INVITE dialogue */
+static void handle_response_invite(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno)
+{
+ int outgoing = ast_test_flag(&p->flags[0], SIP_OUTGOING);
+ int res = 0;
+ int xmitres = 0;
+ int reinvite = (p->owner && p->owner->_state == AST_STATE_UP);
+ struct ast_channel *bridgepeer = NULL;
+ char *p_hdrval;
+ int rtn;
+
+ if (reinvite)
+ ast_debug(4, "SIP response %d to RE-invite on %s call %s\n", resp, outgoing ? "outgoing" : "incoming", p->callid);
+ else
+ ast_debug(4, "SIP response %d to standard invite\n", resp);
+
+ if (p->alreadygone) { /* This call is already gone */
+ ast_debug(1, "Got response on call that is already terminated: %s (ignoring)\n", p->callid);
+ return;
+ }
+
+ /* Acknowledge sequence number - This only happens on INVITE from SIP-call */
+ if (p->initid > -1) {
+ /* Don't auto congest anymore since we've gotten something useful back */
+ ast_sched_del(sched, p->initid);
+ p->initid = -1;
+ }
+
+ /* RFC3261 says we must treat every 1xx response (but not 100)
+ that we don't recognize as if it was 183.
+ */
+ if (resp > 100 && resp < 200 && resp!=101 && resp != 180 && resp != 182 && resp != 183)
+ resp = 183;
+
+ /* Any response between 100 and 199 is PROCEEDING */
+ if (resp >= 100 && resp < 200 && p->invitestate == INV_CALLING)
+ p->invitestate = INV_PROCEEDING;
+
+ /* Final response, not 200 ? */
+ if (resp >= 300 && (p->invitestate == INV_CALLING || p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA ))
+ p->invitestate = INV_COMPLETED;
+
+
+ switch (resp) {
+ case 100: /* Trying */
+ case 101: /* Dialog establishment */
+ if (!req->ignore)
+ sip_cancel_destroy(p);
+ check_pendings(p);
+ break;
+
+ case 180: /* 180 Ringing */
+ case 182: /* 182 Queued */
+ if (!req->ignore)
+ sip_cancel_destroy(p);
+ if (!req->ignore && p->owner) {
+ ast_queue_control(p->owner, AST_CONTROL_RINGING);
+ if (p->owner->_state != AST_STATE_UP) {
+ ast_setstate(p->owner, AST_STATE_RINGING);
+ }
+ }
+ if (find_sdp(req)) {
+ p->invitestate = INV_EARLY_MEDIA;
+ res = process_sdp(p, req);
+ if (!req->ignore && p->owner) {
+ /* Queue a progress frame only if we have SDP in 180 or 182 */
+ ast_queue_control(p->owner, AST_CONTROL_PROGRESS);
+ }
+ }
+ check_pendings(p);
+ break;
+
+ case 183: /* Session progress */
+ if (!req->ignore)
+ sip_cancel_destroy(p);
+ /* Ignore 183 Session progress without SDP */
+ if (find_sdp(req)) {
+ p->invitestate = INV_EARLY_MEDIA;
+ res = process_sdp(p, req);
+ if (!req->ignore && p->owner) {
+ /* Queue a progress frame */
+ ast_queue_control(p->owner, AST_CONTROL_PROGRESS);
+ }
+ }
+ check_pendings(p);
+ break;
+
+ case 200: /* 200 OK on invite - someone's answering our call */
+ if (!req->ignore)
+ sip_cancel_destroy(p);
+ p->authtries = 0;
+ if (find_sdp(req)) {
+ if ((res = process_sdp(p, req)) && !req->ignore)
+ if (!reinvite)
+ /* This 200 OK's SDP is not acceptable, so we need to ack, then hangup */
+ /* For re-invites, we try to recover */
+ ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
+ }
+
+ /* Parse contact header for continued conversation */
+ /* When we get 200 OK, we know which device (and IP) to contact for this call */
+ /* This is important when we have a SIP proxy between us and the phone */
+ if (outgoing) {
+ update_call_counter(p, DEC_CALL_RINGING);
+ parse_ok_contact(p, req);
+ if(set_address_from_contact(p)) {
+ /* Bad contact - we don't know how to reach this device */
+ /* We need to ACK, but then send a bye */
+ /* OEJ: Possible issue that may need a check:
+ If we have a proxy route between us and the device,
+ should we care about resolving the contact
+ or should we just send it?
+ */
+ if (!req->ignore)
+ ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
+ }
+
+ /* Save Record-Route for any later requests we make on this dialogue */
+ if (!reinvite)
+ build_route(p, req, 1);
+ }
+
+ if (p->owner && (p->owner->_state == AST_STATE_UP) && (bridgepeer = ast_bridged_channel(p->owner))) { /* if this is a re-invite */
+ struct sip_pvt *bridgepvt = NULL;
+
+ if (!bridgepeer->tech) {
+ ast_log(LOG_WARNING, "Ooooh.. no tech! That's REALLY bad\n");
+ break;
+ }
+ if (IS_SIP_TECH(bridgepeer->tech)) {
+ bridgepvt = (struct sip_pvt*)(bridgepeer->tech_pvt);
+ if (bridgepvt->udptl) {
+ if (p->t38.state == T38_PEER_REINVITE) {
+ sip_handle_t38_reinvite(bridgepeer, p, 0);
+ ast_rtp_set_rtptimers_onhold(p->rtp);
+ if (p->vrtp)
+ ast_rtp_set_rtptimers_onhold(p->vrtp); /* Turn off RTP timers while we send fax */
+ } else if (p->t38.state == T38_DISABLED && bridgepeer && (bridgepvt->t38.state == T38_ENABLED)) {
+ ast_log(LOG_WARNING, "RTP re-invite after T38 session not handled yet !\n");
+ /* Insted of this we should somehow re-invite the other side of the bridge to RTP */
+ /* XXXX Should we really destroy this session here, without any response at all??? */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ } else {
+ ast_debug(2, "Strange... The other side of the bridge does not have a udptl struct\n");
+ sip_pvt_lock(bridgepvt);
+ bridgepvt->t38.state = T38_DISABLED;
+ sip_pvt_unlock(bridgepvt);
+ ast_debug(1,"T38 state changed to %d on channel %s\n", bridgepvt->t38.state, bridgepeer->tech->type);
+ p->t38.state = T38_DISABLED;
+ ast_debug(2,"T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
+ }
+ } else {
+ /* Other side is not a SIP channel */
+ ast_debug(2, "Strange... The other side of the bridge is not a SIP channel\n");
+ p->t38.state = T38_DISABLED;
+ ast_debug(2,"T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
+ }
+ }
+ if ((p->t38.state == T38_LOCAL_REINVITE) || (p->t38.state == T38_LOCAL_DIRECT)) {
+ /* If there was T38 reinvite and we are supposed to answer with 200 OK than this should set us to T38 negotiated mode */
+ p->t38.state = T38_ENABLED;
+ ast_debug(1, "T38 changed state to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
+ }
+
+ if (!req->ignore && p->owner) {
+ if (!reinvite) {
+ ast_queue_control(p->owner, AST_CONTROL_ANSWER);
+ if (global_callevents)
+ manager_event(EVENT_FLAG_SYSTEM, "ChannelUpdate",
+ "Channel: %s\r\nChanneltype: %s\r\nUniqueid: %s\r\nSIPcallid: %s\r\nSIPfullcontact: %s\r\nPeername: %s\r\n",
+ p->owner->name, p->owner->uniqueid, "SIP", p->callid, p->fullcontact, p->peername);
+ } else { /* RE-invite */
+ ast_queue_frame(p->owner, &ast_null_frame);
+ }
+ } else {
+ /* It's possible we're getting an 200 OK after we've tried to disconnect
+ by sending CANCEL */
+ /* First send ACK, then send bye */
+ if (!req->ignore)
+ ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
+ }
+
+ /* Check for Session-Timers related headers */
+ if (st_get_mode(p) != SESSION_TIMER_MODE_REFUSE && p->outgoing_call == TRUE && !reinvite) {
+ p_hdrval = (char*)get_header(req, "Session-Expires");
+ if (!ast_strlen_zero(p_hdrval)) {
+ /* UAS supports Session-Timers */
+ enum st_refresher tmp_st_ref = SESSION_TIMER_REFRESHER_AUTO;
+ int tmp_st_interval = 0;
+ rtn = parse_session_expires(p_hdrval, &tmp_st_interval, &tmp_st_ref);
+ if (rtn != 0) {
+ ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
+ }
+ if (tmp_st_ref == SESSION_TIMER_REFRESHER_UAC ||
+ tmp_st_ref == SESSION_TIMER_REFRESHER_UAS) {
+ p->stimer->st_ref = tmp_st_ref;
+ }
+ if (tmp_st_interval) {
+ p->stimer->st_interval = tmp_st_interval;
+ }
+ p->stimer->st_active = TRUE;
+ p->stimer->st_active_peer_ua = TRUE;
+ start_session_timer(p);
+ } else {
+ /* UAS doesn't support Session-Timers */
+ if (st_get_mode(p) == SESSION_TIMER_MODE_ORIGINATE) {
+ p->stimer->st_ref = SESSION_TIMER_REFRESHER_UAC;
+ p->stimer->st_active_peer_ua = FALSE;
+ start_session_timer(p);
+ }
+ }
+ }
+
+
+ /* If I understand this right, the branch is different for a non-200 ACK only */
+ p->invitestate = INV_TERMINATED;
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, TRUE);
+ check_pendings(p);
+ break;
+
+ case 407: /* Proxy authentication */
+ case 401: /* Www auth */
+ /* First we ACK */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ if (p->options)
+ p->options->auth_type = resp;
+
+ /* Then we AUTH */
+ ast_string_field_set(p, theirtag, NULL); /* forget their old tag, so we don't match tags when getting response */
+ if (!req->ignore) {
+ if (p->authtries < MAX_AUTHTRIES)
+ p->invitestate = INV_CALLING;
+ if (p->authtries == MAX_AUTHTRIES || do_proxy_auth(p, req, resp, SIP_INVITE, 1)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on INVITE to '%s'\n", get_header(&p->initreq, "From"));
+ p->needdestroy = 1;
+ sip_alreadygone(p);
+ if (p->owner)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ }
+ }
+ break;
+
+ case 403: /* Forbidden */
+ /* First we ACK */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ ast_log(LOG_WARNING, "Received response: \"Forbidden\" from '%s'\n", get_header(&p->initreq, "From"));
+ if (!req->ignore && p->owner)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ p->needdestroy = 1;
+ sip_alreadygone(p);
+ break;
+
+ case 404: /* Not found */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ if (p->owner && !req->ignore)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ sip_alreadygone(p);
+ break;
+
+ case 408: /* Request timeout */
+ case 481: /* Call leg does not exist */
+ /* Could be REFER caused INVITE with replaces */
+ ast_log(LOG_WARNING, "Re-invite to non-existing call leg on other UA. SIP dialog '%s'. Giving up.\n", p->callid);
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ if (p->owner)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ break;
+
+ case 422: /* Session-Timers: Session interval too small */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ ast_string_field_set(p, theirtag, NULL);
+ proc_422_rsp(p, req);
+ break;
+
+ case 487: /* Cancelled transaction */
+ /* We have sent CANCEL on an outbound INVITE
+ This transaction is already scheduled to be killed by sip_hangup().
+ */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ if (p->owner && !req->ignore) {
+ ast_queue_hangup(p->owner);
+ append_history(p, "Hangup", "Got 487 on CANCEL request from us. Queued AST hangup request");
+ } else if (!req->ignore) {
+ update_call_counter(p, DEC_CALL_LIMIT);
+ append_history(p, "Hangup", "Got 487 on CANCEL request from us on call without owner. Killing this dialog.");
+ p->needdestroy = 1;
+ sip_alreadygone(p);
+ }
+ break;
+ case 488: /* Not acceptable here */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ if (reinvite && p->udptl) {
+ /* If this is a T.38 call, we should go back to
+ audio. If this is an audio call - something went
+ terribly wrong since we don't renegotiate codecs,
+ only IP/port .
+ */
+ p->t38.state = T38_DISABLED;
+ /* Try to reset RTP timers */
+ ast_rtp_set_rtptimers_onhold(p->rtp);
+ ast_log(LOG_ERROR, "Got error on T.38 re-invite. Bad configuration. Peer needs to have T.38 disabled.\n");
+
+ /*! \bug Is there any way we can go back to the audio call on both
+ sides here?
+ */
+ /* While figuring that out, hangup the call */
+ if (p->owner && !req->ignore)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ p->needdestroy = 1;
+ } else if (p->udptl && p->t38.state == T38_LOCAL_DIRECT) {
+ /* We tried to send T.38 out in an initial INVITE and the remote side rejected it,
+ right now we can't fall back to audio so totally abort.
+ */
+ p->t38.state = T38_DISABLED;
+ /* Try to reset RTP timers */
+ ast_rtp_set_rtptimers_onhold(p->rtp);
+ ast_log(LOG_ERROR, "Got error on T.38 initial invite. Bailing out.\n");
+
+ /* The dialog is now terminated */
+ if (p->owner && !req->ignore)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ p->needdestroy = 1;
+ sip_alreadygone(p);
+ } else {
+ /* We can't set up this call, so give up */
+ if (p->owner && !req->ignore)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ p->needdestroy = 1;
+ }
+ break;
+ case 491: /* Pending */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ if (p->owner && !req->ignore) {
+ if (p->owner->_state != AST_STATE_UP) {
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ p->needdestroy = 1;
+ } else {
+ /* This is a re-invite that failed. */
+ /* Reset the flag after a while
+ */
+ int wait = 3 + ast_random() % 5;
+ p->waitid = ast_sched_add(sched, wait, sip_reinvite_retry, p);
+ ast_debug(2, "Reinvite race. Waiting %d secs before retry\n", wait);
+ }
+ }
+ break;
+
+ case 501: /* Not implemented */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ if (p->owner)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ break;
+ }
+ if (xmitres == XMIT_ERROR)
+ ast_log(LOG_WARNING, "Could not transmit message in dialog %s\n", p->callid);
+}
+
+/* \brief Handle SIP response in REFER transaction
+ We've sent a REFER, now handle responses to it
+ */
+static void handle_response_refer(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno)
+{
+ /* If no refer structure exists, then do nothing */
+ if (!p->refer)
+ return;
+
+ switch (resp) {
+ case 202: /* Transfer accepted */
+ /* We need to do something here */
+ /* The transferee is now sending INVITE to target */
+ p->refer->status = REFER_ACCEPTED;
+ /* Now wait for next message */
+ ast_debug(3, "Got 202 accepted on transfer\n");
+ /* We should hang along, waiting for NOTIFY's here */
+ break;
+
+ case 401: /* Not www-authorized on SIP method */
+ case 407: /* Proxy auth */
+ if (ast_strlen_zero(p->authname)) {
+ ast_log(LOG_WARNING, "Asked to authenticate REFER to %s:%d but we have no matching peer or realm auth!\n",
+ ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port));
+ p->needdestroy = 1;
+ }
+ if (p->authtries > 1 || do_proxy_auth(p, req, resp, SIP_REFER, 0)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on REFER to '%s'\n", get_header(&p->initreq, "From"));
+ p->refer->status = REFER_NOAUTH;
+ p->needdestroy = 1;
+ }
+ break;
+ case 481: /* Call leg does not exist */
+
+ /* A transfer with Replaces did not work */
+ /* OEJ: We should Set flag, cancel the REFER, go back
+ to original call - but right now we can't */
+ ast_log(LOG_WARNING, "Remote host can't match REFER request to call '%s'. Giving up.\n", p->callid);
+ if (p->owner)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ p->needdestroy = 1;
+ break;
+
+ case 500: /* Server error */
+ case 501: /* Method not implemented */
+ /* Return to the current call onhold */
+ /* Status flag needed to be reset */
+ ast_log(LOG_NOTICE, "SIP transfer to %s failed, call miserably fails. \n", p->refer->refer_to);
+ p->needdestroy = 1;
+ p->refer->status = REFER_FAILED;
+ break;
+ case 603: /* Transfer declined */
+ ast_log(LOG_NOTICE, "SIP transfer to %s declined, call miserably fails. \n", p->refer->refer_to);
+ p->refer->status = REFER_FAILED;
+ p->needdestroy = 1;
+ break;
+ }
+}
+
+/*! \brief Handle responses on REGISTER to services */
+static int handle_response_register(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno)
+{
+ int expires, expires_ms;
+ struct sip_registry *r;
+ r=p->registry;
+
+ switch (resp) {
+ case 401: /* Unauthorized */
+ if (p->authtries == MAX_AUTHTRIES || do_register_auth(p, req, resp)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on REGISTER to '%s@%s' (Tries %d)\n", p->registry->username, p->registry->hostname, p->authtries);
+ p->needdestroy = 1;
+ }
+ break;
+ case 403: /* Forbidden */
+ ast_log(LOG_WARNING, "Forbidden - wrong password on authentication for REGISTER for '%s' to '%s'\n", p->registry->username, p->registry->hostname);
+ ast_sched_del(sched, r->timeout);
+ r->timeout = -1;
+ r->regstate = REG_STATE_NOAUTH;
+ p->needdestroy = 1;
+ break;
+ case 404: /* Not found */
+ ast_log(LOG_WARNING, "Got 404 Not found on SIP register to service %s@%s, giving up\n", p->registry->username,p->registry->hostname);
+ p->needdestroy = 1;
+ r->call = NULL;
+ r->regstate = REG_STATE_REJECTED;
+ ast_sched_del(sched, r->timeout);
+ r->timeout = -1;
+ break;
+ case 407: /* Proxy auth */
+ if (p->authtries == MAX_AUTHTRIES || do_register_auth(p, req, resp)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on REGISTER to '%s' (tries '%d')\n", get_header(&p->initreq, "From"), p->authtries);
+ p->needdestroy = 1;
+ }
+ break;
+ case 408: /* Request timeout */
+ if (global_regattempts_max)
+ p->registry->regattempts = global_regattempts_max+1;
+ p->needdestroy = 1;
+ r->call = NULL;
+ ast_sched_del(sched, r->timeout);
+ r->timeout = -1;
+ break;
+ case 423: /* Interval too brief */
+ r->expiry = atoi(get_header(req, "Min-Expires"));
+ ast_log(LOG_WARNING, "Got 423 Interval too brief for service %s@%s, minimum is %d seconds\n", p->registry->username, p->registry->hostname, r->expiry);
+ ast_sched_del(sched, r->timeout);
+ r->timeout = -1;
+ if (r->call) {
+ r->call = NULL;
+ p->needdestroy = 1;
+ }
+ if (r->expiry > max_expiry) {
+ ast_log(LOG_WARNING, "Required expiration time from %s@%s is too high, giving up\n", p->registry->username, p->registry->hostname);
+ r->expiry = default_expiry;
+ r->regstate = REG_STATE_REJECTED;
+ } else {
+ r->regstate = REG_STATE_UNREGISTERED;
+ transmit_register(r, SIP_REGISTER, NULL, NULL);
+ }
+ manager_event(EVENT_FLAG_SYSTEM, "Registry", "ChannelType: SIP\r\nUsername: %s\r\nDomain: %s\r\nStatus: %s\r\n", r->username, r->hostname, regstate2str(r->regstate));
+ break;
+ case 479: /* SER: Not able to process the URI - address is wrong in register*/
+ ast_log(LOG_WARNING, "Got error 479 on register to %s@%s, giving up (check config)\n", p->registry->username,p->registry->hostname);
+ p->needdestroy = 1;
+ r->call = NULL;
+ r->regstate = REG_STATE_REJECTED;
+ ast_sched_del(sched, r->timeout);
+ r->timeout = -1;
+ break;
+ case 200: /* 200 OK */
+ if (!r) {
+ ast_log(LOG_WARNING, "Got 200 OK on REGISTER that isn't a register\n");
+ p->needdestroy = 1;
+ return 0;
+ }
+
+ r->regstate = REG_STATE_REGISTERED;
+ r->regtime = ast_tvnow(); /* Reset time of last succesful registration */
+ manager_event(EVENT_FLAG_SYSTEM, "Registry", "ChannelType: SIP\r\nDomain: %s\r\nStatus: %s\r\n", r->hostname, regstate2str(r->regstate));
+ r->regattempts = 0;
+ ast_debug(1, "Registration successful\n");
+ if (r->timeout > -1) {
+ ast_debug(1, "Cancelling timeout %d\n", r->timeout);
+ ast_sched_del(sched, r->timeout);
+ }
+ r->timeout=-1;
+ r->call = NULL;
+ p->registry = NULL;
+ /* Let this one hang around until we have all the responses */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ /* p->needdestroy = 1; */
+
+ /* set us up for re-registering */
+ /* figure out how long we got registered for */
+ if (r->expire > -1)
+ ast_sched_del(sched, r->expire);
+ /* according to section 6.13 of RFC, contact headers override
+ expires headers, so check those first */
+ expires = 0;
+
+ /* XXX todo: try to save the extra call */
+ if (!ast_strlen_zero(get_header(req, "Contact"))) {
+ const char *contact = NULL;
+ const char *tmptmp = NULL;
+ int start = 0;
+ for(;;) {
+ contact = __get_header(req, "Contact", &start);
+ /* this loop ensures we get a contact header about our register request */
+ if(!ast_strlen_zero(contact)) {
+ if( (tmptmp=strstr(contact, p->our_contact))) {
+ contact=tmptmp;
+ break;
+ }
+ } else
+ break;
+ }
+ tmptmp = strcasestr(contact, "expires=");
+ if (tmptmp) {
+ if (sscanf(tmptmp + 8, "%d;", &expires) != 1)
+ expires = 0;
+ }
+
+ }
+ if (!expires)
+ expires=atoi(get_header(req, "expires"));
+ if (!expires)
+ expires=default_expiry;
+
+ expires_ms = expires * 1000;
+ if (expires <= EXPIRY_GUARD_LIMIT)
+ expires_ms -= MAX((expires_ms * EXPIRY_GUARD_PCT),EXPIRY_GUARD_MIN);
+ else
+ expires_ms -= EXPIRY_GUARD_SECS * 1000;
+ if (sipdebug)
+ ast_log(LOG_NOTICE, "Outbound Registration: Expiry for %s is %d sec (Scheduling reregistration in %d s)\n", r->hostname, expires, expires_ms/1000);
+
+ r->refresh= (int) expires_ms / 1000;
+
+ /* Schedule re-registration before we expire */
+ r->expire = ast_sched_replace(r->expire, sched, expires_ms, sip_reregister, r);
+ registry_unref(r);
+ }
+ return 1;
+}
+
+/*! \brief Handle qualification responses (OPTIONS) */
+static void handle_response_peerpoke(struct sip_pvt *p, int resp, struct sip_request *req)
+{
+ struct sip_peer *peer = p->relatedpeer;
+ int statechanged, is_reachable, was_reachable;
+ int pingtime = ast_tvdiff_ms(ast_tvnow(), peer->ps);
+
+ /*
+ * Compute the response time to a ping (goes in peer->lastms.)
+ * -1 means did not respond, 0 means unknown,
+ * 1..maxms is a valid response, >maxms means late response.
+ */
+ if (pingtime < 1) /* zero = unknown, so round up to 1 */
+ pingtime = 1;
+
+ /* Now determine new state and whether it has changed.
+ * Use some helper variables to simplify the writing
+ * of the expressions.
+ */
+ was_reachable = peer->lastms > 0 && peer->lastms <= peer->maxms;
+ is_reachable = pingtime <= peer->maxms;
+ statechanged = peer->lastms == 0 /* yes, unknown before */
+ || was_reachable != is_reachable;
+
+ peer->lastms = pingtime;
+ peer->call = dialog_unref(peer->call);
+ if (statechanged) {
+ const char *s = is_reachable ? "Reachable" : "Lagged";
+
+ ast_log(LOG_NOTICE, "Peer '%s' is now %s. (%dms / %dms)\n",
+ peer->name, s, pingtime, peer->maxms);
+ ast_device_state_changed("SIP/%s", peer->name);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus",
+ "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: %s\r\nTime: %d\r\n",
+ peer->name, s, pingtime);
+ if (is_reachable && global_regextenonqualify)
+ register_peer_exten(peer, TRUE);
+ }
+
+ p->needdestroy = 1;
+
+ /* Try again eventually */
+ peer->pokeexpire = ast_sched_replace(peer->pokeexpire, sched,
+ is_reachable ? peer->qualifyfreq : DEFAULT_FREQ_NOTOK,
+ sip_poke_peer_s, peer);
+}
+
+/*! \brief Immediately stop RTP, VRTP and UDPTL as applicable */
+static void stop_media_flows(struct sip_pvt *p)
+{
+ /* Immediately stop RTP, VRTP and UDPTL as applicable */
+ if (p->rtp)
+ ast_rtp_stop(p->rtp);
+ if (p->vrtp)
+ ast_rtp_stop(p->vrtp);
+ if (p->trtp)
+ ast_rtp_stop(p->trtp);
+ if (p->udptl)
+ ast_udptl_stop(p->udptl);
+}
+
+/*! \brief Handle SIP response in dialogue */
+/* XXX only called by handle_incoming */
+static void handle_response(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno)
+{
+ struct ast_channel *owner;
+ int sipmethod;
+ int res = 1;
+ const char *c = get_header(req, "Cseq");
+ const char *msg = strchr(c, ' ');
+
+ if (!msg)
+ msg = "";
+ else
+ msg++;
+ sipmethod = find_sip_method(msg);
+
+ owner = p->owner;
+ if (owner)
+ owner->hangupcause = hangup_sip2cause(resp);
+
+ /* Acknowledge whatever it is destined for */
+ if ((resp >= 100) && (resp <= 199))
+ __sip_semi_ack(p, seqno, 0, sipmethod);
+ else
+ __sip_ack(p, seqno, 0, sipmethod);
+
+ /* Get their tag if we haven't already */
+ if (ast_strlen_zero(p->theirtag) || (resp >= 200)) {
+ char tag[128];
+
+ gettag(req, "To", tag, sizeof(tag));
+ ast_string_field_set(p, theirtag, tag);
+ }
+ /* This needs to be configurable on a channel/peer/user level,
+ not mandatory for all communication. Sadly enough, NAT implementations
+ are not so stable so we can always rely on these headers.
+ Temporarily disabled, while waiting for fix.
+ Fix assigned to Rizzo :-)
+ */
+ /* check_via_response(p, req); */
+ if (p->relatedpeer && p->method == SIP_OPTIONS) {
+ /* We don't really care what the response is, just that it replied back.
+ Well, as long as it's not a 100 response... since we might
+ need to hang around for something more "definitive" */
+ if (resp != 100)
+ handle_response_peerpoke(p, resp, req);
+ } else if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ switch(resp) {
+ case 100: /* 100 Trying */
+ case 101: /* 101 Dialog establishment */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ break;
+ case 183: /* 183 Session Progress */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ break;
+ case 180: /* 180 Ringing */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ break;
+ case 182: /* 182 Queued */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ break;
+ case 200: /* 200 OK */
+ p->authtries = 0; /* Reset authentication counter */
+ if (sipmethod == SIP_MESSAGE || sipmethod == SIP_INFO) {
+ /* We successfully transmitted a message
+ or a video update request in INFO */
+ /* Nothing happens here - the message is inside a dialog */
+ } else if (sipmethod == SIP_INVITE) {
+ handle_response_invite(p, resp, rest, req, seqno);
+ } else if (sipmethod == SIP_NOTIFY) {
+ /* They got the notify, this is the end */
+ if (p->owner) {
+ if (!p->refer) {
+ ast_log(LOG_WARNING, "Notify answer on an owned channel? - %s\n", p->owner->name);
+ ast_queue_hangup(p->owner);
+ } else
+ ast_debug(4, "Got OK on REFER Notify message\n");
+ } else {
+ if (p->subscribed == NONE)
+ p->needdestroy = 1;
+ }
+ } else if (sipmethod == SIP_REGISTER)
+ res = handle_response_register(p, resp, rest, req, seqno);
+ else if (sipmethod == SIP_BYE) /* Ok, we're ready to go */
+ p->needdestroy = 1;
+ break;
+ case 202: /* Transfer accepted */
+ if (sipmethod == SIP_REFER)
+ handle_response_refer(p, resp, rest, req, seqno);
+ break;
+ case 401: /* Not www-authorized on SIP method */
+ case 407: /* Proxy auth required */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ else if (sipmethod == SIP_REFER)
+ handle_response_refer(p, resp, rest, req, seqno);
+ else if (p->registry && sipmethod == SIP_REGISTER)
+ res = handle_response_register(p, resp, rest, req, seqno);
+ else if (sipmethod == SIP_BYE) {
+ if (p->options)
+ p->options->auth_type = resp;
+ if (ast_strlen_zero(p->authname)) {
+ ast_log(LOG_WARNING, "Asked to authenticate %s, to %s:%d but we have no matching peer!\n",
+ msg, ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port));
+ p->needdestroy = 1;
+ } else if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, resp, sipmethod, 0)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on %s to '%s'\n", msg, get_header(&p->initreq, "From"));
+ p->needdestroy = 1;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Got authentication request (%d) on %s to '%s'\n", resp, sip_methods[sipmethod].text, get_header(req, "To"));
+ p->needdestroy = 1;
+ }
+ break;
+ case 403: /* Forbidden - we failed authentication */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ else if (p->registry && sipmethod == SIP_REGISTER)
+ res = handle_response_register(p, resp, rest, req, seqno);
+ else {
+ ast_log(LOG_WARNING, "Forbidden - maybe wrong password on authentication for %s\n", msg);
+ p->needdestroy = 1;
+ }
+ break;
+ case 404: /* Not found */
+ if (p->registry && sipmethod == SIP_REGISTER)
+ res = handle_response_register(p, resp, rest, req, seqno);
+ else if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ else if (owner)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ break;
+ case 423: /* Interval too brief */
+ if (sipmethod == SIP_REGISTER)
+ res = handle_response_register(p, resp, rest, req, seqno);
+ break;
+ case 408: /* Request timeout - terminate dialog */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ else if (sipmethod == SIP_REGISTER)
+ res = handle_response_register(p, resp, rest, req, seqno);
+ else if (sipmethod == SIP_BYE) {
+ p->needdestroy = 1;
+ ast_debug(4, "Got timeout on bye. Thanks for the answer. Now, kill this call\n");
+ } else {
+ if (owner)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ p->needdestroy = 1;
+ }
+ break;
+
+ case 422: /* Session-Timers: Session Interval Too Small */
+ if (sipmethod == SIP_INVITE) {
+ handle_response_invite(p, resp, rest, req, seqno);
+ }
+ break;
+
+ case 481: /* Call leg does not exist */
+ if (sipmethod == SIP_INVITE) {
+ handle_response_invite(p, resp, rest, req, seqno);
+ } else if (sipmethod == SIP_REFER) {
+ handle_response_refer(p, resp, rest, req, seqno);
+ } else if (sipmethod == SIP_BYE) {
+ /* The other side has no transaction to bye,
+ just assume it's all right then */
+ ast_log(LOG_WARNING, "Remote host can't match request %s to call '%s'. Giving up.\n", sip_methods[sipmethod].text, p->callid);
+ } else if (sipmethod == SIP_CANCEL) {
+ /* The other side has no transaction to cancel,
+ just assume it's all right then */
+ ast_log(LOG_WARNING, "Remote host can't match request %s to call '%s'. Giving up.\n", sip_methods[sipmethod].text, p->callid);
+ } else {
+ ast_log(LOG_WARNING, "Remote host can't match request %s to call '%s'. Giving up.\n", sip_methods[sipmethod].text, p->callid);
+ /* Guessing that this is not an important request */
+ }
+ break;
+ case 487:
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ break;
+ case 488: /* Not acceptable here - codec error */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ break;
+ case 491: /* Pending */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ else {
+ ast_debug(1, "Got 491 on %s, unspported. Call ID %s\n", sip_methods[sipmethod].text, p->callid);
+ p->needdestroy = 1;
+ }
+ break;
+ case 501: /* Not Implemented */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ else if (sipmethod == SIP_REFER)
+ handle_response_refer(p, resp, rest, req, seqno);
+ else
+ ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", ast_inet_ntoa(p->sa.sin_addr), msg);
+ break;
+ case 603: /* Declined transfer */
+ if (sipmethod == SIP_REFER) {
+ handle_response_refer(p, resp, rest, req, seqno);
+ break;
+ }
+ /* Fallthrough */
+ default:
+ if ((resp >= 300) && (resp < 700)) {
+ /* Fatal response */
+ if ((resp != 487))
+ ast_verb(3, "Got SIP response %d \"%s\" back from %s\n", resp, rest, ast_inet_ntoa(p->sa.sin_addr));
+
+ if (sipmethod == SIP_INVITE)
+ stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
+
+ /* XXX Locking issues?? XXX */
+ switch(resp) {
+ case 300: /* Multiple Choices */
+ case 301: /* Moved permanently */
+ case 302: /* Moved temporarily */
+ case 305: /* Use Proxy */
+ parse_moved_contact(p, req);
+ /* Fall through */
+ case 486: /* Busy here */
+ case 600: /* Busy everywhere */
+ case 603: /* Decline */
+ if (p->owner)
+ ast_queue_control(p->owner, AST_CONTROL_BUSY);
+ break;
+ case 482: /*!
+ \note SIP is incapable of performing a hairpin call, which
+ is yet another failure of not having a layer 2 (again, YAY
+ IETF for thinking ahead). So we treat this as a call
+ forward and hope we end up at the right place... */
+ ast_debug(1, "Hairpin detected, setting up call forward for what it's worth\n");
+ if (p->owner)
+ ast_string_field_build(p->owner, call_forward,
+ "Local/%s@%s", p->username, p->context);
+ /* Fall through */
+ case 480: /* Temporarily Unavailable */
+ case 404: /* Not Found */
+ case 410: /* Gone */
+ case 400: /* Bad Request */
+ case 500: /* Server error */
+ if (sipmethod == SIP_REFER) {
+ handle_response_refer(p, resp, rest, req, seqno);
+ break;
+ }
+ /* Fall through */
+ case 503: /* Service Unavailable */
+ case 504: /* Server Timeout */
+ if (owner)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ break;
+ default:
+ /* Send hangup */
+ if (owner && sipmethod != SIP_MESSAGE && sipmethod != SIP_INFO && sipmethod != SIP_BYE)
+ ast_queue_hangup(p->owner);
+ break;
+ }
+ /* ACK on invite */
+ if (sipmethod == SIP_INVITE)
+ transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ if (sipmethod != SIP_MESSAGE && sipmethod != SIP_INFO)
+ sip_alreadygone(p);
+ if (!p->owner)
+ p->needdestroy = 1;
+ } else if ((resp >= 100) && (resp < 200)) {
+ if (sipmethod == SIP_INVITE) {
+ if (!req->ignore)
+ sip_cancel_destroy(p);
+ if (find_sdp(req))
+ process_sdp(p, req);
+ if (p->owner) {
+ /* Queue a progress frame */
+ ast_queue_control(p->owner, AST_CONTROL_PROGRESS);
+ }
+ }
+ } else
+ ast_log(LOG_NOTICE, "Dont know how to handle a %d %s response from %s\n", resp, rest, p->owner ? p->owner->name : ast_inet_ntoa(p->sa.sin_addr));
+ }
+ } else {
+ /* Responses to OUTGOING SIP requests on INCOMING calls
+ get handled here. As well as out-of-call message responses */
+ if (req->debug)
+ ast_verbose("SIP Response message for INCOMING dialog %s arrived\n", msg);
+
+ if (sipmethod == SIP_INVITE && resp == 200) {
+ /* Tags in early session is replaced by the tag in 200 OK, which is
+ the final reply to our INVITE */
+ char tag[128];
+
+ gettag(req, "To", tag, sizeof(tag));
+ ast_string_field_set(p, theirtag, tag);
+ }
+
+ switch(resp) {
+ case 200:
+ if (sipmethod == SIP_INVITE) {
+ handle_response_invite(p, resp, rest, req, seqno);
+ } else if (sipmethod == SIP_CANCEL) {
+ ast_debug(1, "Got 200 OK on CANCEL\n");
+
+ /* Wait for 487, then destroy */
+ } else if (sipmethod == SIP_NOTIFY) {
+ /* They got the notify, this is the end */
+ if (p->owner) {
+ if (p->refer) {
+ ast_debug(1, "Got 200 OK on NOTIFY for transfer\n");
+ } else
+ ast_log(LOG_WARNING, "Notify answer on an owned channel?\n");
+ /* ast_queue_hangup(p->owner); Disabled */
+ } else {
+ if (!p->subscribed && !p->refer)
+ p->needdestroy = 1;
+ }
+ } else if (sipmethod == SIP_BYE)
+ p->needdestroy = 1;
+ else if (sipmethod == SIP_MESSAGE || sipmethod == SIP_INFO)
+ /* We successfully transmitted a message or
+ a video update request in INFO */
+ ;
+ else if (sipmethod == SIP_BYE)
+ /* Ok, we're ready to go */
+ p->needdestroy = 1;
+ break;
+ case 202: /* Transfer accepted */
+ if (sipmethod == SIP_REFER)
+ handle_response_refer(p, resp, rest, req, seqno);
+ break;
+ case 401: /* www-auth */
+ case 407:
+ if (sipmethod == SIP_REFER)
+ handle_response_refer(p, resp, rest, req, seqno);
+ else if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ else if (sipmethod == SIP_BYE) {
+ if (p->authtries == MAX_AUTHTRIES || do_proxy_auth(p, req, resp, sipmethod, 0)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on %s to '%s'\n", msg, get_header(&p->initreq, "From"));
+ p->needdestroy = 1;
+ }
+ }
+ break;
+ case 481: /* Call leg does not exist */
+ if (sipmethod == SIP_INVITE) {
+ /* Re-invite failed */
+ handle_response_invite(p, resp, rest, req, seqno);
+ } else if (sipmethod == SIP_BYE) {
+ p->needdestroy = 1;
+ } else if (sipdebug) {
+ ast_debug(1, "Remote host can't match request %s to call '%s'. Giving up\n", sip_methods[sipmethod].text, p->callid);
+ }
+ break;
+ case 501: /* Not Implemented */
+ if (sipmethod == SIP_INVITE)
+ handle_response_invite(p, resp, rest, req, seqno);
+ else if (sipmethod == SIP_REFER)
+ handle_response_refer(p, resp, rest, req, seqno);
+ break;
+ case 603: /* Declined transfer */
+ if (sipmethod == SIP_REFER) {
+ handle_response_refer(p, resp, rest, req, seqno);
+ break;
+ }
+ /* Fallthrough */
+ default: /* Errors without handlers */
+ if ((resp >= 100) && (resp < 200)) {
+ if (sipmethod == SIP_INVITE) { /* re-invite */
+ if (!req->ignore)
+ sip_cancel_destroy(p);
+ }
+ }
+ if ((resp >= 300) && (resp < 700)) {
+ if ((resp != 487))
+ ast_verb(3, "Incoming call: Got SIP response %d \"%s\" back from %s\n", resp, rest, ast_inet_ntoa(p->sa.sin_addr));
+ switch(resp) {
+ case 488: /* Not acceptable here - codec error */
+ case 603: /* Decline */
+ case 500: /* Server error */
+ case 503: /* Service Unavailable */
+ case 504: /* Server timeout */
+
+ if (sipmethod == SIP_INVITE) { /* re-invite failed */
+ sip_cancel_destroy(p);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+}
+
+
+/*! \brief Park SIP call support function
+ Starts in a new thread, then parks the call
+ XXX Should we add a wait period after streaming audio and before hangup?? Sometimes the
+ audio can't be heard before hangup
+*/
+static void *sip_park_thread(void *stuff)
+{
+ struct ast_channel *transferee, *transferer; /* Chan1: The transferee, Chan2: The transferer */
+ struct sip_dual *d;
+ struct sip_request req;
+ int ext;
+ int res;
+
+ d = stuff;
+ transferee = d->chan1;
+ transferer = d->chan2;
+ copy_request(&req, &d->req);
+ ast_free(d);
+
+ if (!transferee || !transferer) {
+ ast_log(LOG_ERROR, "Missing channels for parking! Transferer %s Transferee %s\n", transferer ? "<available>" : "<missing>", transferee ? "<available>" : "<missing>" );
+ return NULL;
+ }
+ ast_debug(4, "SIP Park: Transferer channel %s, Transferee %s\n", transferer->name, transferee->name);
+
+ ast_channel_lock(transferee);
+ if (ast_do_masquerade(transferee)) {
+ ast_log(LOG_WARNING, "Masquerade failed.\n");
+ transmit_response(transferer->tech_pvt, "503 Internal error", &req);
+ ast_channel_unlock(transferee);
+ return NULL;
+ }
+ ast_channel_unlock(transferee);
+
+ res = ast_park_call(transferee, transferer, 0, &ext);
+
+
+#ifdef WHEN_WE_KNOW_THAT_THE_CLIENT_SUPPORTS_MESSAGE
+ if (!res) {
+ transmit_message_with_text(transferer->tech_pvt, "Unable to park call.\n");
+ } else {
+ /* Then tell the transferer what happened */
+ sprintf(buf, "Call parked on extension '%d'", ext);
+ transmit_message_with_text(transferer->tech_pvt, buf);
+ }
+#endif
+
+ /* Any way back to the current call??? */
+ /* Transmit response to the REFER request */
+ transmit_response(transferer->tech_pvt, "202 Accepted", &req);
+ if (!res) {
+ /* Transfer succeeded */
+ append_history(transferer->tech_pvt, "SIPpark","Parked call on %d", ext);
+ transmit_notify_with_sipfrag(transferer->tech_pvt, d->seqno, "200 OK", TRUE);
+ transferer->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ ast_hangup(transferer); /* This will cause a BYE */
+ ast_debug(1, "SIP Call parked on extension '%d'\n", ext);
+ } else {
+ transmit_notify_with_sipfrag(transferer->tech_pvt, d->seqno, "503 Service Unavailable", TRUE);
+ append_history(transferer->tech_pvt, "SIPpark","Parking failed\n");
+ ast_debug(1, "SIP Call parked failed \n");
+ /* Do not hangup call */
+ }
+ return NULL;
+}
+
+/*! \brief Park a call using the subsystem in res_features.c
+ This is executed in a separate thread
+*/
+static int sip_park(struct ast_channel *chan1, struct ast_channel *chan2, struct sip_request *req, int seqno)
+{
+ struct sip_dual *d;
+ struct ast_channel *transferee, *transferer;
+ /* Chan2m: The transferer, chan1m: The transferee */
+ pthread_t th;
+
+ transferee = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, chan1->accountcode, chan1->exten, chan1->context, chan1->amaflags, "Parking/%s", chan1->name);
+ transferer = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, chan2->accountcode, chan2->exten, chan2->context, chan2->amaflags, "SIPPeer/%s", chan2->name);
+ if ((!transferer) || (!transferee)) {
+ if (transferee) {
+ transferee->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ ast_hangup(transferee);
+ }
+ if (transferer) {
+ transferer->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ ast_hangup(transferer);
+ }
+ return -1;
+ }
+
+ /* Make formats okay */
+ transferee->readformat = chan1->readformat;
+ transferee->writeformat = chan1->writeformat;
+
+ /* Prepare for taking over the channel */
+ ast_channel_masquerade(transferee, chan1);
+
+ /* Setup the extensions and such */
+ ast_copy_string(transferee->context, chan1->context, sizeof(transferee->context));
+ ast_copy_string(transferee->exten, chan1->exten, sizeof(transferee->exten));
+ transferee->priority = chan1->priority;
+
+ /* We make a clone of the peer channel too, so we can play
+ back the announcement */
+
+ /* Make formats okay */
+ transferer->readformat = chan2->readformat;
+ transferer->writeformat = chan2->writeformat;
+
+ /* Prepare for taking over the channel. Go ahead and grab this channel
+ * lock here to avoid a deadlock with callbacks into the channel driver
+ * that hold the channel lock and want the pvt lock. */
+ while (ast_channel_trylock(chan2)) {
+ struct sip_pvt *pvt = chan2->tech_pvt;
+ sip_pvt_unlock(pvt);
+ usleep(1);
+ sip_pvt_lock(pvt);
+ }
+ ast_channel_masquerade(transferer, chan2);
+ ast_channel_unlock(chan2);
+
+ /* Setup the extensions and such */
+ ast_copy_string(transferer->context, chan2->context, sizeof(transferer->context));
+ ast_copy_string(transferer->exten, chan2->exten, sizeof(transferer->exten));
+ transferer->priority = chan2->priority;
+
+ ast_channel_lock(transferer);
+ if (ast_do_masquerade(transferer)) {
+ ast_log(LOG_WARNING, "Masquerade failed :(\n");
+ ast_channel_unlock(transferer);
+ transferer->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ ast_hangup(transferer);
+ return -1;
+ }
+ ast_channel_unlock(transferer);
+ if (!transferer || !transferee) {
+ if (!transferer) {
+ ast_debug(1, "No transferer channel, giving up parking\n");
+ }
+ if (!transferee) {
+ ast_debug(1, "No transferee channel, giving up parking\n");
+ }
+ return -1;
+ }
+ if ((d = ast_calloc(1, sizeof(*d)))) {
+
+ /* Save original request for followup */
+ copy_request(&d->req, req);
+ d->chan1 = transferee; /* Transferee */
+ d->chan2 = transferer; /* Transferer */
+ d->seqno = seqno;
+ if (ast_pthread_create_detached_background(&th, NULL, sip_park_thread, d) < 0) {
+ /* Could not start thread */
+ ast_free(d); /* We don't need it anymore. If thread is created, d will be free'd
+ by sip_park_thread() */
+ return 0;
+ }
+ }
+ return -1;
+}
+
+/*! \brief Turn off generator data
+ XXX Does this function belong in the SIP channel?
+*/
+static void ast_quiet_chan(struct ast_channel *chan)
+{
+ if (chan && chan->_state == AST_STATE_UP) {
+ if (chan->generatordata)
+ ast_deactivate_generator(chan);
+ }
+}
+
+/*! \brief Attempt transfer of SIP call
+ This fix for attended transfers on a local PBX */
+static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target)
+{
+ int res = 0;
+ struct ast_channel *peera = NULL,
+ *peerb = NULL,
+ *peerc = NULL,
+ *peerd = NULL;
+
+
+ /* We will try to connect the transferee with the target and hangup
+ all channels to the transferer */
+ ast_debug(4, "Sip transfer:--------------------\n");
+ if (transferer->chan1)
+ ast_debug(4, "-- Transferer to PBX channel: %s State %s\n", transferer->chan1->name, ast_state2str(transferer->chan1->_state));
+ else
+ ast_debug(4, "-- No transferer first channel - odd??? \n");
+ if (target->chan1)
+ ast_debug(4, "-- Transferer to PBX second channel (target): %s State %s\n", target->chan1->name, ast_state2str(target->chan1->_state));
+ else
+ ast_debug(4, "-- No target first channel ---\n");
+ if (transferer->chan2)
+ ast_debug(4, "-- Bridged call to transferee: %s State %s\n", transferer->chan2->name, ast_state2str(transferer->chan2->_state));
+ else
+ ast_debug(4, "-- No bridged call to transferee\n");
+ if (target->chan2)
+ ast_debug(4, "-- Bridged call to transfer target: %s State %s\n", target->chan2 ? target->chan2->name : "<none>", target->chan2 ? ast_state2str(target->chan2->_state) : "(none)");
+ else
+ ast_debug(4, "-- No target second channel ---\n");
+ ast_debug(4, "-- END Sip transfer:--------------------\n");
+ if (transferer->chan2) { /* We have a bridge on the transferer's channel */
+ peera = transferer->chan1; /* Transferer - PBX -> transferee channel * the one we hangup */
+ peerb = target->chan1; /* Transferer - PBX -> target channel - This will get lost in masq */
+ peerc = transferer->chan2; /* Asterisk to Transferee */
+ peerd = target->chan2; /* Asterisk to Target */
+ ast_debug(3, "SIP transfer: Four channels to handle\n");
+ } else if (target->chan2) { /* Transferer has no bridge (IVR), but transferee */
+ peera = target->chan1; /* Transferer to PBX -> target channel */
+ peerb = transferer->chan1; /* Transferer to IVR*/
+ peerc = target->chan2; /* Asterisk to Target */
+ peerd = transferer->chan2; /* Nothing */
+ ast_debug(3, "SIP transfer: Three channels to handle\n");
+ }
+
+ if (peera && peerb && peerc && (peerb != peerc)) {
+ ast_quiet_chan(peera); /* Stop generators */
+ ast_quiet_chan(peerb);
+ ast_quiet_chan(peerc);
+ if (peerd)
+ ast_quiet_chan(peerd);
+
+ /* Fix CDRs so they're attached to the remaining channel */
+ if (peera->cdr && peerb->cdr)
+ peerb->cdr = ast_cdr_append(peerb->cdr, peera->cdr);
+ else if (peera->cdr)
+ peerb->cdr = peera->cdr;
+ peera->cdr = NULL;
+
+ if (peerb->cdr && peerc->cdr)
+ peerb->cdr = ast_cdr_append(peerb->cdr, peerc->cdr);
+ else if (peerc->cdr)
+ peerb->cdr = peerc->cdr;
+ peerc->cdr = NULL;
+
+ ast_debug(4, "SIP transfer: trying to masquerade %s into %s\n", peerc->name, peerb->name);
+ if (ast_channel_masquerade(peerb, peerc)) {
+ ast_log(LOG_WARNING, "Failed to masquerade %s into %s\n", peerb->name, peerc->name);
+ res = -1;
+ } else
+ ast_debug(4, "SIP transfer: Succeeded to masquerade channels.\n");
+ return res;
+ } else {
+ ast_log(LOG_NOTICE, "SIP Transfer attempted with no appropriate bridged calls to transfer\n");
+ if (transferer->chan1)
+ ast_softhangup_nolock(transferer->chan1, AST_SOFTHANGUP_DEV);
+ if (target->chan1)
+ ast_softhangup_nolock(target->chan1, AST_SOFTHANGUP_DEV);
+ return -1;
+ }
+ return 0;
+}
+
+/*! \brief Get tag from packet
+ *
+ * \return Returns the pointer to the provided tag buffer,
+ * or NULL if the tag was not found.
+ */
+static const char *gettag(const struct sip_request *req, const char *header, char *tagbuf, int tagbufsize)
+{
+ const char *thetag;
+
+ if (!tagbuf)
+ return NULL;
+ tagbuf[0] = '\0'; /* reset the buffer */
+ thetag = get_header(req, header);
+ thetag = strcasestr(thetag, ";tag=");
+ if (thetag) {
+ thetag += 5;
+ ast_copy_string(tagbuf, thetag, tagbufsize);
+ return strsep(&tagbuf, ";");
+ }
+ return NULL;
+}
+
+/*! \brief Handle incoming notifications */
+static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int seqno, char *e)
+{
+ /* This is mostly a skeleton for future improvements */
+ /* Mostly created to return proper answers on notifications on outbound REFER's */
+ int res = 0;
+ const char *event = get_header(req, "Event");
+ char *eventid = NULL;
+ char *sep;
+
+ if( (sep = strchr(event, ';')) ) { /* XXX bug here - overwriting string ? */
+ *sep++ = '\0';
+ eventid = sep;
+ }
+
+ if (sipdebug)
+ ast_debug(2, "Got NOTIFY Event: %s\n", event);
+
+ if (strcmp(event, "refer")) {
+ /* We don't understand this event. */
+ /* Here's room to implement incoming voicemail notifications :-) */
+ transmit_response(p, "489 Bad event", req);
+ res = -1;
+ } else {
+ /* Save nesting depth for now, since there might be other events we will
+ support in the future */
+
+ /* Handle REFER notifications */
+
+ char buf[1024];
+ char *cmd, *code;
+ int respcode;
+ int success = TRUE;
+
+ /* EventID for each transfer... EventID is basically the REFER cseq
+
+ We are getting notifications on a call that we transfered
+ We should hangup when we are getting a 200 OK in a sipfrag
+ Check if we have an owner of this event */
+
+ /* Check the content type */
+ if (strncasecmp(get_header(req, "Content-Type"), "message/sipfrag", strlen("message/sipfrag"))) {
+ /* We need a sipfrag */
+ transmit_response(p, "400 Bad request", req);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return -1;
+ }
+
+ /* Get the text of the attachment */
+ if (get_msg_text(buf, sizeof(buf), req)) {
+ ast_log(LOG_WARNING, "Unable to retrieve attachment from NOTIFY %s\n", p->callid);
+ transmit_response(p, "400 Bad request", req);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return -1;
+ }
+
+ /*
+ From the RFC...
+ A minimal, but complete, implementation can respond with a single
+ NOTIFY containing either the body:
+ SIP/2.0 100 Trying
+
+ if the subscription is pending, the body:
+ SIP/2.0 200 OK
+ if the reference was successful, the body:
+ SIP/2.0 503 Service Unavailable
+ if the reference failed, or the body:
+ SIP/2.0 603 Declined
+
+ if the REFER request was accepted before approval to follow the
+ reference could be obtained and that approval was subsequently denied
+ (see Section 2.4.7).
+
+ If there are several REFERs in the same dialog, we need to
+ match the ID of the event header...
+ */
+ ast_debug(3, "* SIP Transfer NOTIFY Attachment: \n---%s\n---\n", buf);
+ cmd = ast_skip_blanks(buf);
+ code = cmd;
+ /* We are at SIP/2.0 */
+ while(*code && (*code > 32)) { /* Search white space */
+ code++;
+ }
+ *code++ = '\0';
+ code = ast_skip_blanks(code);
+ sep = code;
+ sep++;
+ while(*sep && (*sep > 32)) { /* Search white space */
+ sep++;
+ }
+ *sep++ = '\0'; /* Response string */
+ respcode = atoi(code);
+ switch (respcode) {
+ case 100: /* Trying: */
+ case 101: /* dialog establishment */
+ /* Don't do anything yet */
+ break;
+ case 183: /* Ringing: */
+ /* Don't do anything yet */
+ break;
+ case 200: /* OK: The new call is up, hangup this call */
+ /* Hangup the call that we are replacing */
+ break;
+ case 301: /* Moved permenantly */
+ case 302: /* Moved temporarily */
+ /* Do we get the header in the packet in this case? */
+ success = FALSE;
+ break;
+ case 503: /* Service Unavailable: The new call failed */
+ /* Cancel transfer, continue the call */
+ success = FALSE;
+ break;
+ case 603: /* Declined: Not accepted */
+ /* Cancel transfer, continue the current call */
+ success = FALSE;
+ break;
+ }
+ if (!success) {
+ ast_log(LOG_NOTICE, "Transfer failed. Sorry. Nothing further to do with this call\n");
+ }
+
+ /* Confirm that we received this packet */
+ transmit_response(p, "200 OK", req);
+ };
+
+ if (!p->lastinvite)
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+
+ return res;
+}
+
+/*! \brief Handle incoming OPTIONS request
+ An OPTIONS request should be answered like an INVITE from the same UA, including SDP
+*/
+static int handle_request_options(struct sip_pvt *p, struct sip_request *req)
+{
+ int res;
+
+ /*! XXX get_destination assumes we're already authenticated. This means that a request from
+ a known device (peer/user) will end up in the wrong context if this is out-of-dialog.
+ However, we want to handle OPTIONS as light as possible, so we might want to have
+ a configuration option whether we care or not. Some devices use this for testing
+ capabilities, which means that we need to match device to answer with proper
+ capabilities (including SDP).
+ \todo Fix handle_request_options device handling with optional authentication
+ (this needs to be fixed in 1.4 as well)
+ */
+ res = get_destination(p, req);
+ build_contact(p);
+
+ if (ast_strlen_zero(p->context))
+ ast_string_field_set(p, context, default_context);
+
+ if (ast_shutting_down())
+ transmit_response_with_allow(p, "503 Unavailable", req, 0);
+ else if (res < 0)
+ transmit_response_with_allow(p, "404 Not Found", req, 0);
+ else
+ transmit_response_with_allow(p, "200 OK", req, 0);
+
+ /* Destroy if this OPTIONS was the opening request, but not if
+ it's in the middle of a normal call flow. */
+ if (!p->lastinvite)
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+
+ return res;
+}
+
+/*! \brief Handle the transfer part of INVITE with a replaces: header,
+ meaning a target pickup or an attended transfer.
+ Used only once.
+ XXX 'ignore' is unused.
+ */
+static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, struct sockaddr_in *sin)
+{
+ struct ast_frame *f;
+ int earlyreplace = 0;
+ int oneleggedreplace = 0; /* Call with no bridge, propably IVR or voice message */
+ struct ast_channel *c = p->owner; /* Our incoming call */
+ struct ast_channel *replacecall = p->refer->refer_call->owner; /* The channel we're about to take over */
+ struct ast_channel *targetcall; /* The bridge to the take-over target */
+
+ struct ast_channel *test;
+
+ /* Check if we're in ring state */
+ if (replacecall->_state == AST_STATE_RING)
+ earlyreplace = 1;
+
+ /* Check if we have a bridge */
+ if (!(targetcall = ast_bridged_channel(replacecall))) {
+ /* We have no bridge */
+ if (!earlyreplace) {
+ ast_debug(2, " Attended transfer attempted to replace call with no bridge (maybe ringing). Channel %s!\n", replacecall->name);
+ oneleggedreplace = 1;
+ }
+ }
+ if (targetcall && targetcall->_state == AST_STATE_RINGING)
+ ast_debug(4, "SIP transfer: Target channel is in ringing state\n");
+
+ if (targetcall)
+ ast_debug(4, "SIP transfer: Invite Replace incoming channel should bridge to channel %s while hanging up channel %s\n", targetcall->name, replacecall->name);
+ else
+ ast_debug(4, "SIP transfer: Invite Replace incoming channel should replace and hang up channel %s (one call leg)\n", replacecall->name);
+
+ if (req->ignore) {
+ ast_log(LOG_NOTICE, "Ignoring this INVITE with replaces in a stupid way.\n");
+ /* We should answer something here. If we are here, the
+ call we are replacing exists, so an accepted
+ can't harm */
+ transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE, FALSE);
+ /* Do something more clever here */
+ ast_channel_unlock(c);
+ sip_pvt_unlock(p->refer->refer_call);
+ return 1;
+ }
+ if (!c) {
+ /* What to do if no channel ??? */
+ ast_log(LOG_ERROR, "Unable to create new channel. Invite/replace failed.\n");
+ transmit_response_reliable(p, "503 Service Unavailable", req);
+ append_history(p, "Xfer", "INVITE/Replace Failed. No new channel.");
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ sip_pvt_unlock(p->refer->refer_call);
+ return 1;
+ }
+ append_history(p, "Xfer", "INVITE/Replace received");
+ /* We have three channels to play with
+ channel c: New incoming call
+ targetcall: Call from PBX to target
+ p->refer->refer_call: SIP pvt dialog from transferer to pbx.
+ replacecall: The owner of the previous
+ We need to masq C into refer_call to connect to
+ targetcall;
+ If we are talking to internal audio stream, target call is null.
+ */
+
+ /* Fake call progress */
+ transmit_response(p, "100 Trying", req);
+ ast_setstate(c, AST_STATE_RING);
+
+ /* Masquerade the new call into the referred call to connect to target call
+ Targetcall is not touched by the masq */
+
+ /* Answer the incoming call and set channel to UP state */
+ transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE, FALSE);
+
+ ast_setstate(c, AST_STATE_UP);
+
+ /* Stop music on hold and other generators */
+ ast_quiet_chan(replacecall);
+ ast_quiet_chan(targetcall);
+ ast_debug(4, "Invite/Replaces: preparing to masquerade %s into %s\n", c->name, replacecall->name);
+ /* Unlock clone, but not original (replacecall) */
+ if (!oneleggedreplace)
+ ast_channel_unlock(c);
+
+ /* Unlock PVT */
+ sip_pvt_unlock(p->refer->refer_call);
+
+ /* Make sure that the masq does not free our PVT for the old call */
+ if (! earlyreplace && ! oneleggedreplace )
+ ast_set_flag(&p->refer->refer_call->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */
+
+ /* Prepare the masquerade - if this does not happen, we will be gone */
+ if(ast_channel_masquerade(replacecall, c))
+ ast_log(LOG_ERROR, "Failed to masquerade C into Replacecall\n");
+ else
+ ast_debug(4, "Invite/Replaces: Going to masquerade %s into %s\n", c->name, replacecall->name);
+
+ /* The masquerade will happen as soon as someone reads a frame from the channel */
+
+ /* C should now be in place of replacecall */
+ /* ast_read needs to lock channel */
+ ast_channel_unlock(c);
+
+ if (earlyreplace || oneleggedreplace ) {
+ /* Force the masq to happen */
+ if ((f = ast_read(replacecall))) { /* Force the masq to happen */
+ ast_frfree(f);
+ f = NULL;
+ ast_debug(4, "Invite/Replace: Could successfully read frame from RING channel!\n");
+ } else {
+ ast_log(LOG_WARNING, "Invite/Replace: Could not read frame from RING channel \n");
+ }
+ c->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ if (!oneleggedreplace)
+ ast_channel_unlock(replacecall);
+ } else { /* Bridged call, UP channel */
+ if ((f = ast_read(replacecall))) { /* Force the masq to happen */
+ /* Masq ok */
+ ast_frfree(f);
+ f = NULL;
+ ast_debug(3, "Invite/Replace: Could successfully read frame from channel! Masq done.\n");
+ } else {
+ ast_log(LOG_WARNING, "Invite/Replace: Could not read frame from channel. Transfer failed\n");
+ }
+ ast_channel_unlock(replacecall);
+ }
+ sip_pvt_unlock(p->refer->refer_call);
+
+ ast_setstate(c, AST_STATE_DOWN);
+ ast_debug(4, "After transfer:----------------------------\n");
+ ast_debug(4, " -- C: %s State %s\n", c->name, ast_state2str(c->_state));
+ if (replacecall)
+ ast_debug(4, " -- replacecall: %s State %s\n", replacecall->name, ast_state2str(replacecall->_state));
+ if (p->owner) {
+ ast_debug(4, " -- P->owner: %s State %s\n", p->owner->name, ast_state2str(p->owner->_state));
+ test = ast_bridged_channel(p->owner);
+ if (test)
+ ast_debug(4, " -- Call bridged to P->owner: %s State %s\n", test->name, ast_state2str(test->_state));
+ else
+ ast_debug(4, " -- No call bridged to C->owner \n");
+ } else
+ ast_debug(4, " -- No channel yet \n");
+ ast_debug(4, "End After transfer:----------------------------\n");
+
+ ast_channel_unlock(p->owner); /* Unlock new owner */
+ if (!oneleggedreplace)
+ sip_pvt_unlock(p); /* Unlock SIP structure */
+
+ /* The call should be down with no ast_channel, so hang it up */
+ c->tech_pvt = dialog_unref(c->tech_pvt);
+ ast_hangup(c);
+ return 0;
+}
+
+
+/*! \brief Handle incoming INVITE request
+\note If the INVITE has a Replaces header, it is part of an
+ * attended transfer. If so, we do not go through the dial
+ * plan but tries to find the active call and masquerade
+ * into it
+ */
+static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, struct sockaddr_in *sin, int *recount, char *e, int *nounlock)
+{
+ int res = 1;
+ int gotdest;
+ const char *p_replaces;
+ char *replace_id = NULL;
+ const char *required;
+ unsigned int required_profile = 0;
+ struct ast_channel *c = NULL; /* New channel */
+ int reinvite = 0;
+ int rtn;
+
+ const char *p_uac_se_hdr; /* UAC's Session-Expires header string */
+ const char *p_uac_min_se; /* UAC's requested Min-SE interval (char string) */
+ int uac_max_se = -1; /* UAC's Session-Expires in integer format */
+ int uac_min_se = -1; /* UAC's Min-SE in integer format */
+ int st_active = FALSE; /* Session-Timer on/off boolean */
+ int st_interval = 0; /* Session-Timer negotiated refresh interval */
+ enum st_refresher st_ref; /* Session-Timer session refresher */
+ int dlg_min_se = -1;
+ st_ref = SESSION_TIMER_REFRESHER_AUTO;
+
+ /* Find out what they support */
+ if (!p->sipoptions) {
+ const char *supported = get_header(req, "Supported");
+ if (!ast_strlen_zero(supported))
+ parse_sip_options(p, supported);
+ }
+
+ /* Find out what they require */
+ required = get_header(req, "Require");
+ if (!ast_strlen_zero(required)) {
+ required_profile = parse_sip_options(NULL, required);
+ if (required_profile && required_profile != SIP_OPT_REPLACES && required_profile != SIP_OPT_TIMER) {
+ /* At this point we only support REPLACES and Session-Timer */
+ transmit_response_with_unsupported(p, "420 Bad extension (unsupported)", req, required);
+ ast_log(LOG_WARNING,"Received SIP INVITE with unsupported required extension: %s\n", required);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite)
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return -1;
+ }
+ }
+
+ /* The option tags may be present in Supported: or Require: headers.
+ Include the Require: option tags for further processing as well */
+ p->sipoptions |= required_profile;
+ p->reqsipoptions = required_profile;
+
+ /* Check if this is a loop */
+ if (ast_test_flag(&p->flags[0], SIP_OUTGOING) && p->owner && (p->owner->_state != AST_STATE_UP)) {
+ /* This is a call to ourself. Send ourselves an error code and stop
+ processing immediately, as SIP really has no good mechanism for
+ being able to call yourself */
+ /* If pedantic is on, we need to check the tags. If they're different, this is
+ in fact a forked call through a SIP proxy somewhere. */
+ transmit_response(p, "482 Loop Detected", req);
+ p->invitestate = INV_COMPLETED;
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return 0;
+ }
+
+ if (!req->ignore && p->pendinginvite) {
+ /* We already have a pending invite. Sorry. You are on hold. */
+ transmit_response(p, "491 Request Pending", req);
+ ast_debug(1, "Got INVITE on call where we already have pending INVITE, deferring that - %s\n", p->callid);
+ /* Don't destroy dialog here */
+ return 0;
+ }
+
+ p_replaces = get_header(req, "Replaces");
+ if (!ast_strlen_zero(p_replaces)) {
+ /* We have a replaces header */
+ char *ptr;
+ char *fromtag = NULL;
+ char *totag = NULL;
+ char *start, *to;
+ int error = 0;
+
+ if (p->owner) {
+ ast_debug(3, "INVITE w Replaces on existing call? Refusing action. [%s]\n", p->callid);
+ transmit_response(p, "400 Bad request", req); /* The best way to not not accept the transfer */
+ /* Do not destroy existing call */
+ return -1;
+ }
+
+ if (sipdebug)
+ ast_debug(3, "INVITE part of call transfer. Replaces [%s]\n", p_replaces);
+ /* Create a buffer we can manipulate */
+ replace_id = ast_strdupa(p_replaces);
+ ast_uri_decode(replace_id);
+
+ if (!p->refer && !sip_refer_allocate(p)) {
+ transmit_response(p, "500 Server Internal Error", req);
+ append_history(p, "Xfer", "INVITE/Replace Failed. Out of memory.");
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ p->invitestate = INV_COMPLETED;
+ return -1;
+ }
+
+ /* Todo: (When we find phones that support this)
+ if the replaces header contains ";early-only"
+ we can only replace the call in early
+ stage, not after it's up.
+
+ If it's not in early mode, 486 Busy.
+ */
+
+ /* Skip leading whitespace */
+ replace_id = ast_skip_blanks(replace_id);
+
+ start = replace_id;
+ while ( (ptr = strsep(&start, ";")) ) {
+ ptr = ast_skip_blanks(ptr); /* XXX maybe unnecessary ? */
+ if ( (to = strcasestr(ptr, "to-tag=") ) )
+ totag = to + 7; /* skip the keyword */
+ else if ( (to = strcasestr(ptr, "from-tag=") ) ) {
+ fromtag = to + 9; /* skip the keyword */
+ fromtag = strsep(&fromtag, "&"); /* trim what ? */
+ }
+ }
+
+ if (sipdebug)
+ ast_debug(4,"Invite/replaces: Will use Replace-Call-ID : %s Fromtag: %s Totag: %s\n", replace_id, fromtag ? fromtag : "<no from tag>", totag ? totag : "<no to tag>");
+
+
+ /* Try to find call that we are replacing
+ If we have a Replaces header, we need to cancel that call if we succeed with this call
+ */
+ if ((p->refer->refer_call = get_sip_pvt_byid_locked(replace_id, totag, fromtag)) == NULL) {
+ ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existent call id (%s)!\n", replace_id);
+ transmit_response(p, "481 Call Leg Does Not Exist (Replaces)", req);
+ error = 1;
+ }
+
+ /* At this point, bot the pvt and the owner of the call to be replaced is locked */
+
+ /* The matched call is the call from the transferer to Asterisk .
+ We want to bridge the bridged part of the call to the
+ incoming invite, thus taking over the refered call */
+
+ if (p->refer->refer_call == p) {
+ ast_log(LOG_NOTICE, "INVITE with replaces into it's own call id (%s == %s)!\n", replace_id, p->callid);
+ p->refer->refer_call = dialog_unref(p->refer->refer_call);
+ transmit_response(p, "400 Bad request", req); /* The best way to not not accept the transfer */
+ error = 1;
+ }
+
+ if (!error && !p->refer->refer_call->owner) {
+ /* Oops, someting wrong anyway, no owner, no call */
+ ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existing call id (%s)!\n", replace_id);
+ /* Check for better return code */
+ transmit_response(p, "481 Call Leg Does Not Exist (Replace)", req);
+ error = 1;
+ }
+
+ if (!error && p->refer->refer_call->owner->_state != AST_STATE_RINGING && p->refer->refer_call->owner->_state != AST_STATE_RING && p->refer->refer_call->owner->_state != AST_STATE_UP ) {
+ ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-ringing or active call id (%s)!\n", replace_id);
+ transmit_response(p, "603 Declined (Replaces)", req);
+ error = 1;
+ }
+
+ if (error) { /* Give up this dialog */
+ append_history(p, "Xfer", "INVITE/Replace Failed.");
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ sip_pvt_unlock(p);
+ if (p->refer->refer_call) {
+ sip_pvt_unlock(p->refer->refer_call);
+ ast_channel_unlock(p->refer->refer_call->owner);
+ }
+ p->invitestate = INV_COMPLETED;
+ return -1;
+ }
+ }
+
+ /* Check if this is an INVITE that sets up a new dialog or
+ a re-invite in an existing dialog */
+
+ if (!req->ignore) {
+ int newcall = (p->initreq.headers ? TRUE : FALSE);
+
+ sip_cancel_destroy(p);
+ /* This also counts as a pending invite */
+ p->pendinginvite = seqno;
+ check_via(p, req);
+
+ copy_request(&p->initreq, req); /* Save this INVITE as the transaction basis */
+ if (sipdebug)
+ ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
+ if (!p->owner) { /* Not a re-invite */
+ if (debug)
+ ast_verbose("Using INVITE request as basis request - %s\n", p->callid);
+ if (newcall)
+ append_history(p, "Invite", "New call: %s", p->callid);
+ parse_ok_contact(p, req);
+ } else { /* Re-invite on existing call */
+ ast_clear_flag(&p->flags[0], SIP_OUTGOING); /* This is now an inbound dialog */
+ /* Handle SDP here if we already have an owner */
+ if (find_sdp(req)) {
+ if (process_sdp(p, req)) {
+ transmit_response(p, "488 Not acceptable here", req);
+ if (!p->lastinvite)
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return -1;
+ }
+ } else {
+ p->jointcapability = p->capability;
+ ast_debug(1, "Hm.... No sdp for the moment\n");
+ }
+ if (p->do_history) /* This is a response, note what it was for */
+ append_history(p, "ReInv", "Re-invite received");
+ }
+ } else if (debug)
+ ast_verbose("Ignoring this INVITE request\n");
+
+
+ if (!p->lastinvite && !req->ignore && !p->owner) {
+ /* This is a new invite */
+ /* Handle authentication if this is our first invite */
+ res = check_user(p, req, SIP_INVITE, e, XMIT_RELIABLE, sin);
+ if (res == AUTH_CHALLENGE_SENT) {
+ p->invitestate = INV_COMPLETED; /* Needs to restart in another INVITE transaction */
+ return 0;
+ }
+ if (res < 0) { /* Something failed in authentication */
+ if (res == AUTH_FAKE_AUTH) {
+ ast_log(LOG_NOTICE, "Sending fake auth rejection for user %s\n", get_header(req, "From"));
+ transmit_fake_auth_response(p, req, 1);
+ } else {
+ ast_log(LOG_NOTICE, "Failed to authenticate user %s\n", get_header(req, "From"));
+ transmit_response_reliable(p, "403 Forbidden", req);
+ }
+ p->invitestate = INV_COMPLETED;
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ ast_string_field_set(p, theirtag, NULL);
+ return 0;
+ }
+
+ /* We have a succesful authentication, process the SDP portion if there is one */
+ if (find_sdp(req)) {
+ if (process_sdp(p, req)) {
+ /* Unacceptable codecs */
+ transmit_response_reliable(p, "488 Not acceptable here", req);
+ p->invitestate = INV_COMPLETED;
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ ast_debug(1, "No compatible codecs for this SIP call.\n");
+ return -1;
+ }
+ } else { /* No SDP in invite, call control session */
+ p->jointcapability = p->capability;
+ ast_debug(2, "No SDP in Invite, third party call control\n");
+ }
+
+ /* Queue NULL frame to prod ast_rtp_bridge if appropriate */
+ /* This seems redundant ... see !p-owner above */
+ if (p->owner)
+ ast_queue_frame(p->owner, &ast_null_frame);
+
+
+ /* Initialize the context if it hasn't been already */
+ if (ast_strlen_zero(p->context))
+ ast_string_field_set(p, context, default_context);
+
+
+ /* Check number of concurrent calls -vs- incoming limit HERE */
+ ast_debug(1, "Checking SIP call limits for device %s\n", p->username);
+ if ((res = update_call_counter(p, INC_CALL_LIMIT))) {
+ if (res < 0) {
+ ast_log(LOG_NOTICE, "Failed to place call for user %s, too many calls\n", p->username);
+ transmit_response_reliable(p, "480 Temporarily Unavailable (Call limit) ", req);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ p->invitestate = INV_COMPLETED;
+ }
+ return 0;
+ }
+ gotdest = get_destination(p, NULL); /* Get destination right away */
+ get_rdnis(p, NULL); /* Get redirect information */
+ extract_uri(p, req); /* Get the Contact URI */
+ build_contact(p); /* Build our contact header */
+
+ if (p->rtp) {
+ ast_rtp_setdtmf(p->rtp, ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
+ ast_rtp_setdtmfcompensate(p->rtp, ast_test_flag(&p->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
+ }
+
+ if (!replace_id && gotdest) { /* No matching extension found */
+ if (gotdest == 1 && ast_test_flag(&p->flags[1], SIP_PAGE2_ALLOWOVERLAP))
+ transmit_response_reliable(p, "484 Address Incomplete", req);
+ else {
+ transmit_response_reliable(p, "404 Not Found", req);
+ ast_log(LOG_NOTICE, "Call from '%s' to extension"
+ " '%s' rejected because extension not found.\n",
+ S_OR(p->username, p->peername), p->exten);
+ }
+ p->invitestate = INV_COMPLETED;
+ update_call_counter(p, DEC_CALL_LIMIT);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return 0;
+ } else {
+
+ /* If no extension was specified, use the s one */
+ /* Basically for calling to IP/Host name only */
+ if (ast_strlen_zero(p->exten))
+ ast_string_field_set(p, exten, "s");
+ /* Initialize our tag */
+
+ make_our_tag(p->tag, sizeof(p->tag));
+ /* First invitation - create the channel */
+ c = sip_new(p, AST_STATE_DOWN, S_OR(p->username, NULL));
+ *recount = 1;
+
+ /* Save Record-Route for any later requests we make on this dialogue */
+ build_route(p, req, 0);
+
+ if (c) {
+ /* Pre-lock the call */
+ ast_channel_lock(c);
+ }
+ }
+ } else {
+ if (sipdebug) {
+ if (!req->ignore)
+ ast_debug(2, "Got a SIP re-invite for call %s\n", p->callid);
+ else
+ ast_debug(2, "Got a SIP re-transmit of INVITE for call %s\n", p->callid);
+ }
+
+ reinvite = 1;
+ c = p->owner;
+ }
+
+ /* Session-Timers */
+ if (p->sipoptions == SIP_OPT_TIMER) {
+ /* The UAC has requested session-timers for this session. Negotiate
+ the session refresh interval and who will be the refresher */
+ ast_debug(2, "Incoming INVITE with 'timer' option enabled\n");
+
+ /* Allocate Session-Timers struct w/in the dialog */
+ if (!p->stimer)
+ sip_st_alloc(p);
+
+ /* Parse the Session-Expires header */
+ p_uac_se_hdr = get_header(req, "Session-Expires");
+ if (!ast_strlen_zero(p_uac_se_hdr)) {
+ rtn = parse_session_expires(p_uac_se_hdr, &uac_max_se, &st_ref);
+ if (rtn != 0) {
+ transmit_response(p, "400 Session-Expires Invalid Syntax", req);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite) {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return -1;
+ }
+ }
+
+ /* Parse the Min-SE header */
+ p_uac_min_se = get_header(req, "Min-SE");
+ if (!ast_strlen_zero(p_uac_min_se)) {
+ rtn = parse_minse(p_uac_min_se, &uac_min_se);
+ if (rtn != 0) {
+ transmit_response(p, "400 Min-SE Invalid Syntax", req);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite) {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return -1;
+ }
+ }
+
+ dlg_min_se = st_get_se(p, FALSE);
+ switch (st_get_mode(p)) {
+ case SESSION_TIMER_MODE_ACCEPT:
+ case SESSION_TIMER_MODE_ORIGINATE:
+ if (uac_max_se > 0 && uac_max_se < dlg_min_se) {
+ transmit_response_with_minse(p, "422 Session Interval Too Small", req, dlg_min_se);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite) {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return -1;
+ }
+
+ p->stimer->st_active_peer_ua = TRUE;
+ st_active = TRUE;
+ if (st_ref == SESSION_TIMER_REFRESHER_AUTO) {
+ st_ref = st_get_refresher(p);
+ }
+
+ if (uac_max_se > 0) {
+ int dlg_max_se = st_get_se(p, TRUE);
+ if (dlg_max_se >= uac_min_se) {
+ st_interval = (uac_max_se < dlg_max_se) ? uac_max_se : dlg_max_se;
+ } else {
+ st_interval = uac_max_se;
+ }
+ } else {
+ st_interval = uac_min_se;
+ }
+ break;
+
+ case SESSION_TIMER_MODE_REFUSE:
+ if (p->reqsipoptions == SIP_OPT_TIMER) {
+ transmit_response_with_unsupported(p, "420 Option Disabled", req, required);
+ ast_log(LOG_WARNING,"Received SIP INVITE with supported but disabled option: %s\n", required);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite) {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return -1;
+ }
+ break;
+
+ default:
+ ast_log(LOG_ERROR, "Internal Error %d at %s:%d\n", st_get_mode(p), __FILE__, __LINE__);
+ break;
+ }
+ } else {
+ /* The UAC did not request session-timers. Asterisk (UAS), will now decide
+ (based on session-timer-mode in sip.conf) whether to run session-timers for
+ this session or not. */
+ switch (st_get_mode(p)) {
+ case SESSION_TIMER_MODE_ORIGINATE:
+ st_active = TRUE;
+ st_interval = st_get_se(p, TRUE);
+ st_ref = SESSION_TIMER_REFRESHER_UAS;
+ p->stimer->st_active_peer_ua = FALSE;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (reinvite == 0) {
+ /* Session-Timers: Start session refresh timer based on negotiation/config */
+ if (st_active == TRUE) {
+ p->stimer->st_active = TRUE;
+ p->stimer->st_interval = st_interval;
+ p->stimer->st_ref = st_ref;
+ start_session_timer(p);
+ }
+ } else {
+ if (p->stimer->st_active == TRUE) {
+ /* Session-Timers: A re-invite request sent within a dialog will serve as
+ a refresh request, no matter whether the re-invite was sent for refreshing
+ the session or modifying it.*/
+ ast_debug (2, "Restarting session-timers on a refresh - %s\n", p->callid);
+
+ /* The UAC may be adjusting the session-timers mid-session */
+ if (st_interval > 0) {
+ p->stimer->st_interval = st_interval;
+ p->stimer->st_ref = st_ref;
+ }
+
+ restart_session_timer(p);
+ if (p->stimer->st_expirys > 0) {
+ p->stimer->st_expirys--;
+ }
+ }
+ }
+
+ if (!req->ignore && p)
+ p->lastinvite = seqno;
+
+ if (replace_id) { /* Attended transfer or call pickup - we're the target */
+ /* Go and take over the target call */
+ if (sipdebug)
+ ast_debug(4, "Sending this call to the invite/replcaes handler %s\n", p->callid);
+ return handle_invite_replaces(p, req, debug, seqno, sin);
+ }
+
+
+ if (c) { /* We have a call -either a new call or an old one (RE-INVITE) */
+ switch(c->_state) {
+ case AST_STATE_DOWN:
+ ast_debug(2, "%s: New call is still down.... Trying... \n", c->name);
+ transmit_response(p, "100 Trying", req);
+ p->invitestate = INV_PROCEEDING;
+ ast_setstate(c, AST_STATE_RING);
+ if (strcmp(p->exten, ast_pickup_ext())) { /* Call to extension -start pbx on this call */
+ enum ast_pbx_result res;
+
+ res = ast_pbx_start(c);
+
+ switch(res) {
+ case AST_PBX_FAILED:
+ ast_log(LOG_WARNING, "Failed to start PBX :(\n");
+ p->invitestate = INV_COMPLETED;
+ if (req->ignore)
+ transmit_response(p, "503 Unavailable", req);
+ else
+ transmit_response_reliable(p, "503 Unavailable", req);
+ break;
+ case AST_PBX_CALL_LIMIT:
+ ast_log(LOG_WARNING, "Failed to start PBX (call limit reached) \n");
+ p->invitestate = INV_COMPLETED;
+ if (req->ignore)
+ transmit_response(p, "480 Temporarily Unavailable", req);
+ else
+ transmit_response_reliable(p, "480 Temporarily Unavailable", req);
+ break;
+ case AST_PBX_SUCCESS:
+ /* nothing to do */
+ break;
+ }
+
+ if (res) {
+
+ /* Unlock locks so ast_hangup can do its magic */
+ ast_channel_unlock(c);
+ sip_pvt_unlock(p);
+ ast_hangup(c);
+ sip_pvt_lock(p);
+ c = NULL;
+ }
+ } else { /* Pickup call in call group */
+ ast_channel_unlock(c);
+ *nounlock = 1;
+ if (ast_pickup_call(c)) {
+ ast_log(LOG_NOTICE, "Nothing to pick up for %s\n", p->callid);
+ if (req->ignore)
+ transmit_response(p, "503 Unavailable", req); /* OEJ - Right answer? */
+ else
+ transmit_response_reliable(p, "503 Unavailable", req);
+ sip_alreadygone(p);
+ /* Unlock locks so ast_hangup can do its magic */
+ sip_pvt_unlock(p);
+ c->hangupcause = AST_CAUSE_CALL_REJECTED;
+ } else {
+ sip_pvt_unlock(p);
+ ast_setstate(c, AST_STATE_DOWN);
+ c->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ }
+ p->invitestate = INV_COMPLETED;
+ ast_hangup(c);
+ sip_pvt_lock(p);
+ c = NULL;
+ }
+ break;
+ case AST_STATE_RING:
+ transmit_response(p, "100 Trying", req);
+ p->invitestate = INV_PROCEEDING;
+ break;
+ case AST_STATE_RINGING:
+ transmit_response(p, "180 Ringing", req);
+ p->invitestate = INV_PROCEEDING;
+ break;
+ case AST_STATE_UP:
+ ast_debug(2, "%s: This call is UP.... \n", c->name);
+
+ transmit_response(p, "100 Trying", req);
+
+ if (p->t38.state == T38_PEER_REINVITE) {
+ struct ast_channel *bridgepeer = NULL;
+ struct sip_pvt *bridgepvt = NULL;
+
+ if ((bridgepeer = ast_bridged_channel(p->owner))) {
+ /* We have a bridge, and this is re-invite to switchover to T38 so we send re-invite with T38 SDP, to other side of bridge*/
+ /*! XXX: we should also check here does the other side supports t38 at all !!! XXX */
+ if (IS_SIP_TECH(bridgepeer->tech)) {
+ bridgepvt = (struct sip_pvt*)bridgepeer->tech_pvt;
+ if (bridgepvt->t38.state == T38_DISABLED) {
+ if (bridgepvt->udptl) { /* If everything is OK with other side's udptl struct */
+ /* Send re-invite to the bridged channel */
+ sip_handle_t38_reinvite(bridgepeer, p, 1);
+ } else { /* Something is wrong with peers udptl struct */
+ ast_log(LOG_WARNING, "Strange... The other side of the bridge don't have udptl struct\n");
+ sip_pvt_lock(bridgepvt);
+ bridgepvt->t38.state = T38_DISABLED;
+ sip_pvt_unlock(bridgepvt);
+ ast_debug(2,"T38 state changed to %d on channel %s\n", bridgepvt->t38.state, bridgepeer->name);
+ if (req->ignore)
+ transmit_response(p, "488 Not acceptable here", req);
+ else
+ transmit_response_reliable(p, "488 Not acceptable here", req);
+
+ }
+ } else {
+ /* The other side is already setup for T.38 most likely so we need to acknowledge this too */
+ transmit_response_with_t38_sdp(p, "200 OK", req, XMIT_CRITICAL);
+ p->t38.state = T38_ENABLED;
+ ast_debug(1, "T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
+ }
+ } else {
+ /* Other side is not a SIP channel */
+ if (req->ignore)
+ transmit_response(p, "488 Not acceptable here", req);
+ else
+ transmit_response_reliable(p, "488 Not acceptable here", req);
+ p->t38.state = T38_DISABLED;
+ ast_debug(2,"T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
+
+ if (!p->lastinvite) /* Only destroy if this is *not* a re-invite */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ } else {
+ /* we are not bridged in a call */
+ transmit_response_with_t38_sdp(p, "200 OK", req, XMIT_CRITICAL);
+ p->t38.state = T38_ENABLED;
+ ast_debug(1,"T38 state changed to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
+ }
+ } else if (p->t38.state == T38_DISABLED) { /* Channel doesn't have T38 offered or enabled */
+ int sendok = TRUE;
+
+ /* If we are bridged to a channel that has T38 enabled than this is a case of RTP re-invite after T38 session */
+ /* so handle it here (re-invite other party to RTP) */
+ struct ast_channel *bridgepeer = NULL;
+ struct sip_pvt *bridgepvt = NULL;
+ if ((bridgepeer = ast_bridged_channel(p->owner))) {
+ if (IS_SIP_TECH(bridgepeer->tech)) {
+ bridgepvt = (struct sip_pvt*)bridgepeer->tech_pvt;
+ /* Does the bridged peer have T38 ? */
+ if (bridgepvt->t38.state == T38_ENABLED) {
+ ast_log(LOG_WARNING, "RTP re-invite after T38 session not handled yet !\n");
+ /* Insted of this we should somehow re-invite the other side of the bridge to RTP */
+ if (req->ignore)
+ transmit_response(p, "488 Not Acceptable Here (unsupported)", req);
+ else
+ transmit_response_reliable(p, "488 Not Acceptable Here (unsupported)", req);
+ sendok = FALSE;
+ }
+ /* No bridged peer with T38 enabled*/
+ }
+ }
+ /* Respond to normal re-invite */
+ if (sendok) {
+ /* If this is not a re-invite or something to ignore - it's critical */
+ transmit_response_with_sdp(p, "200 OK", req, (reinvite || req->ignore) ? XMIT_UNRELIABLE : XMIT_CRITICAL, p->session_modify == TRUE ? FALSE:TRUE);
+ }
+ }
+ p->invitestate = INV_TERMINATED;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to handle INVITE in state %d\n", c->_state);
+ transmit_response(p, "100 Trying", req);
+ break;
+ }
+ } else {
+ if (p && (p->autokillid == -1)) {
+ const char *msg;
+
+ if (!p->jointcapability)
+ msg = "488 Not Acceptable Here (codec error)";
+ else {
+ ast_log(LOG_NOTICE, "Unable to create/find SIP channel for this INVITE\n");
+ msg = "503 Unavailable";
+ }
+ if (req->ignore)
+ transmit_response(p, msg, req);
+ else
+ transmit_response_reliable(p, msg, req);
+ p->invitestate = INV_COMPLETED;
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ }
+ return res;
+}
+
+/*! \brief Find all call legs and bridge transferee with target
+ * called from handle_request_refer */
+static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, int seqno)
+{
+ struct sip_dual target; /* Chan 1: Call from tranferer to Asterisk */
+ /* Chan 2: Call from Asterisk to target */
+ int res = 0;
+ struct sip_pvt *targetcall_pvt;
+
+ /* Check if the call ID of the replaces header does exist locally */
+ if (!(targetcall_pvt = get_sip_pvt_byid_locked(transferer->refer->replaces_callid, transferer->refer->replaces_callid_totag,
+ transferer->refer->replaces_callid_fromtag))) {
+ if (transferer->refer->localtransfer) {
+ /* We did not find the refered call. Sorry, can't accept then */
+ transmit_response(transferer, "202 Accepted", req);
+ /* Let's fake a response from someone else in order
+ to follow the standard */
+ transmit_notify_with_sipfrag(transferer, seqno, "481 Call leg/transaction does not exist", TRUE);
+ append_history(transferer, "Xfer", "Refer failed");
+ ast_clear_flag(&transferer->flags[0], SIP_GOTREFER);
+ transferer->refer->status = REFER_FAILED;
+ return -1;
+ }
+ /* Fall through for remote transfers that we did not find locally */
+ ast_debug(3, "SIP attended transfer: Not our call - generating INVITE with replaces\n");
+ return 0;
+ }
+
+ /* Ok, we can accept this transfer */
+ transmit_response(transferer, "202 Accepted", req);
+ append_history(transferer, "Xfer", "Refer accepted");
+ if (!targetcall_pvt->owner) { /* No active channel */
+ ast_debug(4, "SIP attended transfer: Error: No owner of target call\n");
+ /* Cancel transfer */
+ transmit_notify_with_sipfrag(transferer, seqno, "503 Service Unavailable", TRUE);
+ append_history(transferer, "Xfer", "Refer failed");
+ ast_clear_flag(&transferer->flags[0], SIP_GOTREFER);
+ transferer->refer->status = REFER_FAILED;
+ sip_pvt_unlock(targetcall_pvt);
+ ast_channel_unlock(current->chan1);
+ return -1;
+ }
+
+ /* We have a channel, find the bridge */
+ target.chan1 = targetcall_pvt->owner; /* Transferer to Asterisk */
+ target.chan2 = ast_bridged_channel(targetcall_pvt->owner); /* Asterisk to target */
+
+ if (!target.chan2 || !(target.chan2->_state == AST_STATE_UP || target.chan2->_state == AST_STATE_RINGING) ) {
+ /* Wrong state of new channel */
+ if (target.chan2)
+ ast_debug(4, "SIP attended transfer: Error: Wrong state of target call: %s\n", ast_state2str(target.chan2->_state));
+ else if (target.chan1->_state != AST_STATE_RING)
+ ast_debug(4, "SIP attended transfer: Error: No target channel\n");
+ else
+ ast_debug(4, "SIP attended transfer: Attempting transfer in ringing state\n");
+ }
+
+ /* Transfer */
+ if (sipdebug) {
+ if (current->chan2) /* We have two bridges */
+ ast_debug(4, "SIP attended transfer: trying to bridge %s and %s\n", target.chan1->name, current->chan2->name);
+ else /* One bridge, propably transfer of IVR/voicemail etc */
+ ast_debug(4, "SIP attended transfer: trying to make %s take over (masq) %s\n", target.chan1->name, current->chan1->name);
+ }
+
+ ast_set_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */
+
+ /* Perform the transfer */
+ manager_event(EVENT_FLAG_CALL, "Transfer", "TransferMethod: SIP\r\nTransferType: Attended\r\nChannel: %s\r\nUniqueid: %s\r\nSIP-Callid: %s\r\nTargetChannel: %s\r\nTargetUniqueid: %s\r\n",
+ transferer->owner->name,
+ transferer->owner->uniqueid,
+ transferer->callid,
+ target.chan1->name,
+ target.chan1->uniqueid);
+ res = attempt_transfer(current, &target);
+ sip_pvt_unlock(targetcall_pvt);
+ if (res) {
+ /* Failed transfer */
+ transmit_notify_with_sipfrag(transferer, seqno, "486 Busy Here", TRUE);
+ append_history(transferer, "Xfer", "Refer failed");
+ if (targetcall_pvt->owner)
+ ast_channel_unlock(targetcall_pvt->owner);
+ /* Right now, we have to hangup, sorry. Bridge is destroyed */
+ if (res != -2)
+ ast_hangup(transferer->owner);
+ else
+ ast_clear_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER);
+ } else {
+ /* Transfer succeeded! */
+
+ /* Tell transferer that we're done. */
+ transmit_notify_with_sipfrag(transferer, seqno, "200 OK", TRUE);
+ append_history(transferer, "Xfer", "Refer succeeded");
+ transferer->refer->status = REFER_200OK;
+ if (targetcall_pvt->owner) {
+ ast_debug(1, "SIP attended transfer: Unlocking channel %s\n", targetcall_pvt->owner->name);
+ ast_channel_unlock(targetcall_pvt->owner);
+ }
+ }
+ return 1;
+}
+
+
+/*! \brief Handle incoming REFER request */
+/*! \page SIP_REFER SIP transfer Support (REFER)
+
+ REFER is used for call transfer in SIP. We get a REFER
+ to place a new call with an INVITE somwhere and then
+ keep the transferor up-to-date of the transfer. If the
+ transfer fails, get back on line with the orginal call.
+
+ - REFER can be sent outside or inside of a dialog.
+ Asterisk only accepts REFER inside of a dialog.
+
+ - If we get a replaces header, it is an attended transfer
+
+ \par Blind transfers
+ The transferor provides the transferee
+ with the transfer targets contact. The signalling between
+ transferer or transferee should not be cancelled, so the
+ call is recoverable if the transfer target can not be reached
+ by the transferee.
+
+ In this case, Asterisk receives a TRANSFER from
+ the transferor, thus is the transferee. We should
+ try to set up a call to the contact provided
+ and if that fails, re-connect the current session.
+ If the new call is set up, we issue a hangup.
+ In this scenario, we are following section 5.2
+ in the SIP CC Transfer draft. (Transfer without
+ a GRUU)
+
+ \par Transfer with consultation hold
+ In this case, the transferor
+ talks to the transfer target before the transfer takes place.
+ This is implemented with SIP hold and transfer.
+ Note: The invite From: string could indicate a transfer.
+ (Section 6. Transfer with consultation hold)
+ The transferor places the transferee on hold, starts a call
+ with the transfer target to alert them to the impending
+ transfer, terminates the connection with the target, then
+ proceeds with the transfer (as in Blind transfer above)
+
+ \par Attended transfer
+ The transferor places the transferee
+ on hold, calls the transfer target to alert them,
+ places the target on hold, then proceeds with the transfer
+ using a Replaces header field in the Refer-to header. This
+ will force the transfee to send an Invite to the target,
+ with a replaces header that instructs the target to
+ hangup the call between the transferor and the target.
+ In this case, the Refer/to: uses the AOR address. (The same
+ URI that the transferee used to establish the session with
+ the transfer target (To: ). The Require: replaces header should
+ be in the INVITE to avoid the wrong UA in a forked SIP proxy
+ scenario to answer and have no call to replace with.
+
+ The referred-by header is *NOT* required, but if we get it,
+ can be copied into the INVITE to the transfer target to
+ inform the target about the transferor
+
+ "Any REFER request has to be appropriately authenticated.".
+
+ We can't destroy dialogs, since we want the call to continue.
+
+ */
+static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, int *nounlock)
+{
+ struct sip_dual current; /* Chan1: Call between asterisk and transferer */
+ /* Chan2: Call between asterisk and transferee */
+
+ int res = 0;
+
+ if (req->debug)
+ ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n", p->callid, ast_test_flag(&p->flags[0], SIP_OUTGOING) ? "callee" : "caller");
+
+ if (!p->owner) {
+ /* This is a REFER outside of an existing SIP dialog */
+ /* We can't handle that, so decline it */
+ ast_debug(3, "Call %s: Declined REFER, outside of dialog...\n", p->callid);
+ transmit_response(p, "603 Declined (No dialog)", req);
+ if (!req->ignore) {
+ append_history(p, "Xfer", "Refer failed. Outside of dialog.");
+ sip_alreadygone(p);
+ p->needdestroy = 1;
+ }
+ return 0;
+ }
+
+
+ /* Check if transfer is allowed from this device */
+ if (p->allowtransfer == TRANSFER_CLOSED ) {
+ /* Transfer not allowed, decline */
+ transmit_response(p, "603 Declined (policy)", req);
+ append_history(p, "Xfer", "Refer failed. Allowtransfer == closed.");
+ /* Do not destroy SIP session */
+ return 0;
+ }
+
+ if (!req->ignore && ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
+ /* Already have a pending REFER */
+ transmit_response(p, "491 Request pending", req);
+ append_history(p, "Xfer", "Refer failed. Request pending.");
+ return 0;
+ }
+
+ /* Allocate memory for call transfer data */
+ if (!p->refer && !sip_refer_allocate(p)) {
+ transmit_response(p, "500 Internal Server Error", req);
+ append_history(p, "Xfer", "Refer failed. Memory allocation error.");
+ return -3;
+ }
+
+ res = get_refer_info(p, req); /* Extract headers */
+
+ p->refer->status = REFER_SENT;
+
+ if (res != 0) {
+ switch (res) {
+ case -2: /* Syntax error */
+ transmit_response(p, "400 Bad Request (Refer-to missing)", req);
+ append_history(p, "Xfer", "Refer failed. Refer-to missing.");
+ if (req->debug)
+ ast_debug(1, "SIP transfer to black hole can't be handled (no refer-to: )\n");
+ break;
+ case -3:
+ transmit_response(p, "603 Declined (Non sip: uri)", req);
+ append_history(p, "Xfer", "Refer failed. Non SIP uri");
+ if (req->debug)
+ ast_debug(1, "SIP transfer to non-SIP uri denied\n");
+ break;
+ default:
+ /* Refer-to extension not found, fake a failed transfer */
+ transmit_response(p, "202 Accepted", req);
+ append_history(p, "Xfer", "Refer failed. Bad extension.");
+ transmit_notify_with_sipfrag(p, seqno, "404 Not found", TRUE);
+ ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ if (req->debug)
+ ast_debug(1, "SIP transfer to bad extension: %s\n", p->refer->refer_to);
+ break;
+ }
+ return 0;
+ }
+ if (ast_strlen_zero(p->context))
+ ast_string_field_set(p, context, default_context);
+
+ /* If we do not support SIP domains, all transfers are local */
+ if (allow_external_domains && check_sip_domain(p->refer->refer_to_domain, NULL, 0)) {
+ p->refer->localtransfer = 1;
+ if (sipdebug)
+ ast_debug(3, "This SIP transfer is local : %s\n", p->refer->refer_to_domain);
+ } else if (AST_LIST_EMPTY(&domain_list) || check_sip_domain(p->refer->refer_to_domain, NULL, 0)) {
+ /* This PBX doesn't bother with SIP domains or domain is local, so this transfer is local */
+ p->refer->localtransfer = 1;
+ } else if (sipdebug)
+ ast_debug(3, "This SIP transfer is to a remote SIP extension (remote domain %s)\n", p->refer->refer_to_domain);
+
+ /* Is this a repeat of a current request? Ignore it */
+ /* Don't know what else to do right now. */
+ if (req->ignore)
+ return res;
+
+ /* If this is a blind transfer, we have the following
+ channels to work with:
+ - chan1, chan2: The current call between transferer and transferee (2 channels)
+ - target_channel: A new call from the transferee to the target (1 channel)
+ We need to stay tuned to what happens in order to be able
+ to bring back the call to the transferer */
+
+ /* If this is a attended transfer, we should have all call legs within reach:
+ - chan1, chan2: The call between the transferer and transferee (2 channels)
+ - target_channel, targetcall_pvt: The call between the transferer and the target (2 channels)
+ We want to bridge chan2 with targetcall_pvt!
+
+ The replaces call id in the refer message points
+ to the call leg between Asterisk and the transferer.
+ So we need to connect the target and the transferee channel
+ and hangup the two other channels silently
+
+ If the target is non-local, the call ID could be on a remote
+ machine and we need to send an INVITE with replaces to the
+ target. We basically handle this as a blind transfer
+ and let the sip_call function catch that we need replaces
+ header in the INVITE.
+ */
+
+
+ /* Get the transferer's channel */
+ current.chan1 = p->owner;
+
+ /* Find the other part of the bridge (2) - transferee */
+ current.chan2 = ast_bridged_channel(current.chan1);
+
+ if (sipdebug)
+ ast_debug(3, "SIP %s transfer: Transferer channel %s, transferee channel %s\n", p->refer->attendedtransfer ? "attended" : "blind", current.chan1->name, current.chan2 ? current.chan2->name : "<none>");
+
+ if (!current.chan2 && !p->refer->attendedtransfer) {
+ /* No bridged channel, propably IVR or echo or similar... */
+ /* Guess we should masquerade or something here */
+ /* Until we figure it out, refuse transfer of such calls */
+ if (sipdebug)
+ ast_debug(3,"Refused SIP transfer on non-bridged channel.\n");
+ p->refer->status = REFER_FAILED;
+ append_history(p, "Xfer", "Refer failed. Non-bridged channel.");
+ transmit_response(p, "603 Declined", req);
+ return -1;
+ }
+
+ if (current.chan2) {
+ if (sipdebug)
+ ast_debug(4, "Got SIP transfer, applying to bridged peer '%s'\n", current.chan2->name);
+
+ ast_queue_control(current.chan1, AST_CONTROL_UNHOLD);
+ }
+
+ ast_set_flag(&p->flags[0], SIP_GOTREFER);
+
+ /* Attended transfer: Find all call legs and bridge transferee with target*/
+ if (p->refer->attendedtransfer) {
+ if ((res = local_attended_transfer(p, &current, req, seqno)))
+ return res; /* We're done with the transfer */
+ /* Fall through for remote transfers that we did not find locally */
+ if (sipdebug)
+ ast_debug(4, "SIP attended transfer: Still not our call - generating INVITE with replaces\n");
+ /* Fallthrough if we can't find the call leg internally */
+ }
+
+
+ /* Parking a call */
+ if (p->refer->localtransfer && !strcmp(p->refer->refer_to, ast_parking_ext())) {
+ /* Must release c's lock now, because it will not longer be accessible after the transfer! */
+ *nounlock = 1;
+ ast_channel_unlock(current.chan1);
+ copy_request(&current.req, req);
+ ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ p->refer->status = REFER_200OK;
+ append_history(p, "Xfer", "REFER to call parking.");
+ manager_event(EVENT_FLAG_CALL, "Transfer", "TransferMethod: SIP\r\nTransferType: Blind\r\nChannel: %s\r\nUniqueid: %s\r\nSIP-Callid: %s\r\nTargetChannel: %s\r\nTargetUniqueid: %s\r\nTransferExten: %s\r\nTransfer2Parking: Yes\r\n",
+ current.chan1->name,
+ current.chan1->uniqueid,
+ p->callid,
+ current.chan2->name,
+ current.chan2->uniqueid,
+ p->refer->refer_to);
+ if (sipdebug)
+ ast_debug(4, "SIP transfer to parking: trying to park %s. Parked by %s\n", current.chan2->name, current.chan1->name);
+ sip_park(current.chan2, current.chan1, req, seqno);
+ return res;
+ }
+
+ /* Blind transfers and remote attended xfers */
+ transmit_response(p, "202 Accepted", req);
+
+ if (current.chan1 && current.chan2) {
+ ast_debug(3, "chan1->name: %s\n", current.chan1->name);
+ pbx_builtin_setvar_helper(current.chan1, "BLINDTRANSFER", current.chan2->name);
+ }
+ if (current.chan2) {
+ pbx_builtin_setvar_helper(current.chan2, "BLINDTRANSFER", current.chan1->name);
+ pbx_builtin_setvar_helper(current.chan2, "SIPDOMAIN", p->refer->refer_to_domain);
+ pbx_builtin_setvar_helper(current.chan2, "SIPTRANSFER", "yes");
+ /* One for the new channel */
+ pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER", "yes");
+ /* Attended transfer to remote host, prepare headers for the INVITE */
+ if (p->refer->referred_by)
+ pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER_REFERER", p->refer->referred_by);
+ }
+ /* Generate a Replaces string to be used in the INVITE during attended transfer */
+ if (!ast_strlen_zero(p->refer->replaces_callid)) {
+ char tempheader[BUFSIZ];
+ snprintf(tempheader, sizeof(tempheader), "%s%s%s%s%s", p->refer->replaces_callid,
+ p->refer->replaces_callid_totag ? ";to-tag=" : "",
+ p->refer->replaces_callid_totag,
+ p->refer->replaces_callid_fromtag ? ";from-tag=" : "",
+ p->refer->replaces_callid_fromtag);
+ if (current.chan2)
+ pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER_REPLACES", tempheader);
+ }
+ /* Must release lock now, because it will not longer
+ be accessible after the transfer! */
+ *nounlock = 1;
+ ast_channel_unlock(current.chan1);
+
+ /* Connect the call */
+
+ /* FAKE ringing if not attended transfer */
+ if (!p->refer->attendedtransfer)
+ transmit_notify_with_sipfrag(p, seqno, "183 Ringing", FALSE);
+
+ /* For blind transfer, this will lead to a new call */
+ /* For attended transfer to remote host, this will lead to
+ a new SIP call with a replaces header, if the dial plan allows it
+ */
+ if (!current.chan2) {
+ /* We have no bridge, so we're talking with Asterisk somehow */
+ /* We need to masquerade this call */
+ /* What to do to fix this situation:
+ * Set up the new call in a new channel
+ * Let the new channel masq into this channel
+ Please add that code here :-)
+ */
+ p->refer->status = REFER_FAILED;
+ transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable (can't handle one-legged xfers)", TRUE);
+ ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ append_history(p, "Xfer", "Refer failed (only bridged calls).");
+ return -1;
+ }
+ ast_set_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */
+
+
+ /* For blind transfers, move the call to the new extensions. For attended transfers on multiple
+ servers - generate an INVITE with Replaces. Either way, let the dial plan decided */
+ res = ast_async_goto(current.chan2, p->refer->refer_to_context, p->refer->refer_to, 1);
+
+ if (!res) {
+ manager_event(EVENT_FLAG_CALL, "Transfer", "TransferMethod: SIP\r\nTransferType: Blind\r\nChannel: %s\r\nUniqueid: %s\r\nSIP-Callid: %s\r\nTargetChannel: %s\r\nTargetUniqueid: %s\r\nTransferExten: %s\r\nTransferContext: %s\r\n",
+ current.chan1->name,
+ current.chan1->uniqueid,
+ p->callid,
+ current.chan2->name,
+ current.chan2->uniqueid,
+ p->refer->refer_to, p->refer->refer_to_context);
+ /* Success - we have a new channel */
+ ast_debug(3, "%s transfer succeeded. Telling transferer.\n", p->refer->attendedtransfer? "Attended" : "Blind");
+ transmit_notify_with_sipfrag(p, seqno, "200 Ok", TRUE);
+ if (p->refer->localtransfer)
+ p->refer->status = REFER_200OK;
+ if (p->owner)
+ p->owner->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ append_history(p, "Xfer", "Refer succeeded.");
+ ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ /* Do not hangup call, the other side do that when we say 200 OK */
+ /* We could possibly implement a timer here, auto congestion */
+ res = 0;
+ } else {
+ ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Don't delay hangup */
+ ast_debug(3, "%s transfer failed. Resuming original call.\n", p->refer->attendedtransfer? "Attended" : "Blind");
+ append_history(p, "Xfer", "Refer failed.");
+ /* Failure of some kind */
+ p->refer->status = REFER_FAILED;
+ transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable", TRUE);
+ ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ res = -1;
+ }
+ return res;
+}
+
+/*! \brief Handle incoming CANCEL request */
+static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req)
+{
+
+ check_via(p, req);
+ sip_alreadygone(p);
+
+ /* At this point, we could have cancelled the invite at the same time
+ as the other side sends a CANCEL. Our final reply with error code
+ might not have been received by the other side before the CANCEL
+ was sent, so let's just give up retransmissions and waiting for
+ ACK on our error code. The call is hanging up any way. */
+ if (p->invitestate == INV_TERMINATED)
+ __sip_pretend_ack(p);
+ else
+ p->invitestate = INV_CANCELLED;
+
+ if (p->owner && p->owner->_state == AST_STATE_UP) {
+ /* This call is up, cancel is ignored, we need a bye */
+ transmit_response(p, "200 OK", req);
+ ast_debug(1, "Got CANCEL on an answered call. Ignoring... \n");
+ return 0;
+ }
+
+ if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD))
+ update_call_counter(p, DEC_CALL_LIMIT);
+
+ stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
+
+ if (p->owner)
+ ast_queue_hangup(p->owner);
+ else
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ if (p->initreq.len > 0) {
+ transmit_response_reliable(p, "487 Request Terminated", &p->initreq);
+ transmit_response(p, "200 OK", req);
+ return 1;
+ } else {
+ transmit_response(p, "481 Call Leg Does Not Exist", req);
+ return 0;
+ }
+}
+
+static int acf_channel_read(struct ast_channel *chan, const char *funcname, char *preparse, char *buf, size_t buflen)
+{
+ struct sip_pvt *p = chan->tech_pvt;
+ char *all = "", *parse = ast_strdupa(preparse);
+ int res = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(param);
+ AST_APP_ARG(type);
+ AST_APP_ARG(field);
+ );
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ /* Sanity check */
+ if (!IS_SIP_TECH(chan->tech)) {
+ ast_log(LOG_ERROR, "Cannot call %s on a non-SIP channel\n", funcname);
+ return 0;
+ }
+
+ memset(buf, 0, buflen);
+
+ if (!strcasecmp(args.param, "rtpdest")) {
+ struct sockaddr_in sin;
+
+ if (ast_strlen_zero(args.type))
+ args.type = "audio";
+
+ if (!strcasecmp(args.type, "audio"))
+ ast_rtp_get_peer(p->rtp, &sin);
+ else if (!strcasecmp(args.type, "video"))
+ ast_rtp_get_peer(p->vrtp, &sin);
+ else if (!strcasecmp(args.type, "text"))
+ ast_rtp_get_peer(p->trtp, &sin);
+
+ snprintf(buf, buflen, "%s:%d", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+ } else if (!strcasecmp(args.param, "rtpqos")) {
+ struct ast_rtp_quality qos;
+
+ memset(&qos, 0, sizeof(qos));
+
+ if (ast_strlen_zero(args.type))
+ args.type = "audio";
+ if (ast_strlen_zero(args.field))
+ args.field = "all";
+
+ if (strcasecmp(args.type, "AUDIO") == 0) {
+ all = ast_rtp_get_quality(p->rtp, &qos);
+ } else if (strcasecmp(args.type, "VIDEO") == 0) {
+ all = ast_rtp_get_quality(p->vrtp, &qos);
+ } else if (strcasecmp(args.type, "TEXT") == 0) {
+ all = ast_rtp_get_quality(p->trtp, &qos);
+ }
+
+ if (strcasecmp(args.field, "local_ssrc") == 0)
+ snprintf(buf, buflen, "%u", qos.local_ssrc);
+ else if (strcasecmp(args.field, "local_lostpackets") == 0)
+ snprintf(buf, buflen, "%u", qos.local_lostpackets);
+ else if (strcasecmp(args.field, "local_jitter") == 0)
+ snprintf(buf, buflen, "%.0f", qos.local_jitter * 1000.0);
+ else if (strcasecmp(args.field, "local_count") == 0)
+ snprintf(buf, buflen, "%u", qos.local_count);
+ else if (strcasecmp(args.field, "remote_ssrc") == 0)
+ snprintf(buf, buflen, "%u", qos.remote_ssrc);
+ else if (strcasecmp(args.field, "remote_lostpackets") == 0)
+ snprintf(buf, buflen, "%u", qos.remote_lostpackets);
+ else if (strcasecmp(args.field, "remote_jitter") == 0)
+ snprintf(buf, buflen, "%.0f", qos.remote_jitter * 1000.0);
+ else if (strcasecmp(args.field, "remote_count") == 0)
+ snprintf(buf, buflen, "%u", qos.remote_count);
+ else if (strcasecmp(args.field, "rtt") == 0)
+ snprintf(buf, buflen, "%.0f", qos.rtt * 1000.0);
+ else if (strcasecmp(args.field, "all") == 0)
+ ast_copy_string(buf, all, buflen);
+ else {
+ ast_log(LOG_WARNING, "Unrecognized argument '%s' to %s\n", preparse, funcname);
+ return -1;
+ }
+ } else {
+ res = -1;
+ }
+ return res;
+}
+
+/*! \brief Handle incoming BYE request */
+static int handle_request_bye(struct sip_pvt *p, struct sip_request *req)
+{
+ struct ast_channel *c=NULL;
+ int res;
+ struct ast_channel *bridged_to;
+
+ /* If we have an INCOMING invite that we haven't answered, terminate that transaction */
+ if (p->pendinginvite && !ast_test_flag(&p->flags[0], SIP_OUTGOING) && !req->ignore && !p->owner)
+ transmit_response_reliable(p, "487 Request Terminated", &p->initreq);
+
+ p->invitestate = INV_TERMINATED;
+
+ copy_request(&p->initreq, req);
+ if (sipdebug)
+ ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
+ check_via(p, req);
+ sip_alreadygone(p);
+
+ /* Get RTCP quality before end of call */
+ if (p->do_history || p->owner) {
+ char *audioqos, *videoqos, *textqos;
+ if (p->rtp) {
+ audioqos = ast_rtp_get_quality(p->rtp, NULL);
+ if (p->do_history)
+ append_history(p, "RTCPaudio", "Quality:%s", audioqos);
+ if (p->owner)
+ pbx_builtin_setvar_helper(p->owner, "RTPAUDIOQOS", audioqos);
+ }
+ if (p->vrtp) {
+ videoqos = ast_rtp_get_quality(p->vrtp, NULL);
+ if (p->do_history)
+ append_history(p, "RTCPvideo", "Quality:%s", videoqos);
+ if (p->owner)
+ pbx_builtin_setvar_helper(p->owner, "RTPVIDEOQOS", videoqos);
+ }
+ if (p->trtp) {
+ textqos = ast_rtp_get_quality(p->trtp, NULL);
+ if (p->do_history)
+ append_history(p, "RTCPtext", "Quality:%s", textqos);
+ if (p->owner)
+ pbx_builtin_setvar_helper(p->owner, "RTPTEXTQOS", textqos);
+ }
+ }
+
+ stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
+ stop_session_timer(p); /* Stop Session-Timer */
+
+ if (!ast_strlen_zero(get_header(req, "Also"))) {
+ ast_log(LOG_NOTICE, "Client '%s' using deprecated BYE/Also transfer method. Ask vendor to support REFER instead\n",
+ ast_inet_ntoa(p->recv.sin_addr));
+ if (ast_strlen_zero(p->context))
+ ast_string_field_set(p, context, default_context);
+ res = get_also_info(p, req);
+ if (!res) {
+ c = p->owner;
+ if (c) {
+ bridged_to = ast_bridged_channel(c);
+ if (bridged_to) {
+ /* Don't actually hangup here... */
+ ast_queue_control(c, AST_CONTROL_UNHOLD);
+ ast_async_goto(bridged_to, p->context, p->refer->refer_to,1);
+ } else
+ ast_queue_hangup(p->owner);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Invalid transfer information from '%s'\n", ast_inet_ntoa(p->recv.sin_addr));
+ if (p->owner)
+ ast_queue_hangup(p->owner);
+ }
+ } else if (p->owner) {
+ ast_queue_hangup(p->owner);
+ ast_debug(3, "Received bye, issuing owner hangup\n");
+ } else {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ ast_debug(3, "Received bye, no owner, selfdestruct soon.\n");
+ }
+ transmit_response(p, "200 OK", req);
+
+ return 1;
+}
+
+/*! \brief Handle incoming MESSAGE request */
+static int handle_request_message(struct sip_pvt *p, struct sip_request *req)
+{
+ if (!req->ignore) {
+ if (req->debug)
+ ast_verbose("Receiving message!\n");
+ receive_message(p, req);
+ } else
+ transmit_response(p, "202 Accepted", req);
+ return 1;
+}
+
+static void add_peer_mwi_subs(struct sip_peer *peer)
+{
+ struct sip_mailbox *mailbox;
+
+ AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) {
+ mailbox->event_sub = ast_event_subscribe(AST_EVENT_MWI, mwi_event_cb, peer,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox->mailbox,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, S_OR(mailbox->context, "default"),
+ AST_EVENT_IE_END);
+ }
+}
+
+/*! \brief Handle incoming SUBSCRIBE request */
+static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int seqno, char *e)
+{
+ int gotdest;
+ int res = 0;
+ int firststate = AST_EXTENSION_REMOVED;
+ struct sip_peer *authpeer = NULL;
+ const char *eventheader = get_header(req, "Event"); /* Get Event package name */
+ const char *accept = get_header(req, "Accept");
+ int resubscribe = (p->subscribed != NONE);
+ char *temp, *event;
+
+ if (p->initreq.headers) {
+ /* We already have a dialog */
+ if (p->initreq.method != SIP_SUBSCRIBE) {
+ /* This is a SUBSCRIBE within another SIP dialog, which we do not support */
+ /* For transfers, this could happen, but since we haven't seen it happening, let us just refuse this */
+ transmit_response(p, "403 Forbidden (within dialog)", req);
+ /* Do not destroy session, since we will break the call if we do */
+ ast_debug(1, "Got a subscription within the context of another call, can't handle that - %s (Method %s)\n", p->callid, sip_methods[p->initreq.method].text);
+ return 0;
+ } else if (req->debug) {
+ if (resubscribe)
+ ast_debug(1, "Got a re-subscribe on existing subscription %s\n", p->callid);
+ else
+ ast_debug(1, "Got a new subscription %s (possibly with auth)\n", p->callid);
+ }
+ }
+
+ /* Check if we have a global disallow setting on subscriptions.
+ if so, we don't have to check peer/user settings after auth, which saves a lot of processing
+ */
+ if (!global_allowsubscribe) {
+ transmit_response(p, "403 Forbidden (policy)", req);
+ p->needdestroy = 1;
+ return 0;
+ }
+
+ if (!req->ignore && !resubscribe) { /* Set up dialog, new subscription */
+ /* Use this as the basis */
+ if (req->debug)
+ ast_verbose("Creating new subscription\n");
+
+ copy_request(&p->initreq, req);
+ if (sipdebug)
+ ast_debug(4, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
+ check_via(p, req);
+ } else if (req->debug && req->ignore)
+ ast_verbose("Ignoring this SUBSCRIBE request\n");
+
+ /* Find parameters to Event: header value and remove them for now */
+ if (ast_strlen_zero(eventheader)) {
+ transmit_response(p, "489 Bad Event", req);
+ ast_debug(2, "Received SIP subscribe for unknown event package: <none>\n");
+ p->needdestroy = 1;
+ return 0;
+ }
+
+ if ( (strchr(eventheader, ';'))) {
+ event = ast_strdupa(eventheader); /* Since eventheader is a const, we can't change it */
+ temp = strchr(event, ';');
+ *temp = '\0'; /* Remove any options for now */
+ /* We might need to use them later :-) */
+ } else
+ event = (char *) eventheader; /* XXX is this legal ? */
+
+ /* Handle authentication */
+ res = check_user_full(p, req, SIP_SUBSCRIBE, e, 0, sin, &authpeer);
+ /* if an authentication response was sent, we are done here */
+ if (res == AUTH_CHALLENGE_SENT) /* authpeer = NULL here */
+ return 0;
+ if (res < 0) {
+ if (res == AUTH_FAKE_AUTH) {
+ ast_log(LOG_NOTICE, "Sending fake auth rejection for user %s\n", get_header(req, "From"));
+ transmit_fake_auth_response(p, req, 1);
+ } else {
+ ast_log(LOG_NOTICE, "Failed to authenticate user %s for SUBSCRIBE\n", get_header(req, "From"));
+ transmit_response_reliable(p, "403 Forbidden", req);
+ }
+ p->needdestroy = 1;
+ return 0;
+ }
+
+ /* At this point, authpeer cannot be NULL. Remember we hold a reference,
+ * so we must release it when done.
+ * XXX must remove all the checks for authpeer == NULL.
+ */
+
+ /* Check if this user/peer is allowed to subscribe at all */
+ if (!ast_test_flag(&p->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE)) {
+ transmit_response(p, "403 Forbidden (policy)", req);
+ p->needdestroy = 1;
+ if (authpeer)
+ unref_peer(authpeer);
+ return 0;
+ }
+
+ /* Get destination right away */
+ gotdest = get_destination(p, NULL);
+
+ /* Get full contact header - this needs to be used as a request URI in NOTIFY's */
+ parse_ok_contact(p, req);
+
+ build_contact(p);
+ if (gotdest) {
+ transmit_response(p, "404 Not Found", req);
+ p->needdestroy = 1;
+ if (authpeer)
+ unref_peer(authpeer);
+ return 0;
+ }
+
+ /* Initialize tag for new subscriptions */
+ if (ast_strlen_zero(p->tag))
+ make_our_tag(p->tag, sizeof(p->tag));
+
+ if (!strcmp(event, "presence") || !strcmp(event, "dialog")) { /* Presence, RFC 3842 */
+ if (authpeer) /* We do not need the authpeer any more */
+ unref_peer(authpeer);
+
+ /* Header from Xten Eye-beam Accept: multipart/related, application/rlmi+xml, application/pidf+xml, application/xpidf+xml */
+ /* Polycom phones only handle xpidf+xml, even if they say they can
+ handle pidf+xml as well
+ */
+ if (strstr(p->useragent, "Polycom")) {
+ p->subscribed = XPIDF_XML;
+ } else if (strstr(accept, "application/pidf+xml")) {
+ p->subscribed = PIDF_XML; /* RFC 3863 format */
+ } else if (strstr(accept, "application/dialog-info+xml")) {
+ p->subscribed = DIALOG_INFO_XML;
+ /* IETF draft: draft-ietf-sipping-dialog-package-05.txt */
+ } else if (strstr(accept, "application/cpim-pidf+xml")) {
+ p->subscribed = CPIM_PIDF_XML; /* RFC 3863 format */
+ } else if (strstr(accept, "application/xpidf+xml")) {
+ p->subscribed = XPIDF_XML; /* Early pre-RFC 3863 format with MSN additions (Microsoft Messenger) */
+ } else if (ast_strlen_zero(accept)) {
+ if (p->subscribed == NONE) { /* if the subscribed field is not already set, and there is no accept header... */
+ transmit_response(p, "489 Bad Event", req);
+
+ ast_log(LOG_WARNING,"SUBSCRIBE failure: no Accept header: pvt: stateid: %d, laststate: %d, dialogver: %d, subscribecont: '%s', subscribeuri: '%s'\n",
+ p->stateid, p->laststate, p->dialogver, p->subscribecontext, p->subscribeuri);
+ p->needdestroy = 1;
+ return 0;
+ }
+ /* if p->subscribed is non-zero, then accept is not obligatory; according to rfc 3265 section 3.1.3, at least.
+ so, we'll just let it ride, keeping the value from a previous subscription, and not abort the subscription */
+ } else {
+ /* Can't find a format for events that we know about */
+ char mybuf[200];
+ snprintf(mybuf,sizeof(mybuf),"489 Bad Event (format %s)", accept);
+ transmit_response(p, mybuf, req);
+
+ ast_log(LOG_WARNING,"SUBSCRIBE failure: unrecognized format: '%s' pvt: subscribed: %d, stateid: %d, laststate: %d, dialogver: %d, subscribecont: '%s', subscribeuri: '%s'\n",
+ accept, (int)p->subscribed, p->stateid, p->laststate, p->dialogver, p->subscribecontext, p->subscribeuri);
+ p->needdestroy = 1;
+ return 0;
+ }
+ } else if (!strcmp(event, "message-summary")) {
+ if (!ast_strlen_zero(accept) && strcmp(accept, "application/simple-message-summary")) {
+ /* Format requested that we do not support */
+ transmit_response(p, "406 Not Acceptable", req);
+ ast_debug(2, "Received SIP mailbox subscription for unknown format: %s\n", accept);
+ p->needdestroy = 1;
+ if (authpeer)
+ unref_peer(authpeer);
+ return 0;
+ }
+ /* Looks like they actually want a mailbox status
+ This version of Asterisk supports mailbox subscriptions
+ The subscribed URI needs to exist in the dial plan
+ In most devices, this is configurable to the voicemailmain extension you use
+ */
+ if (!authpeer || AST_LIST_EMPTY(&authpeer->mailboxes)) {
+ transmit_response(p, "404 Not found (no mailbox)", req);
+ p->needdestroy = 1;
+ ast_log(LOG_NOTICE, "Received SIP subscribe for peer without mailbox: %s\n", authpeer->name);
+ if (authpeer)
+ unref_peer(authpeer);
+ return 0;
+ }
+
+ p->subscribed = MWI_NOTIFICATION;
+ if (ast_test_flag(&authpeer->flags[1], SIP_PAGE2_SUBSCRIBEMWIONLY)) {
+ add_peer_mwi_subs(authpeer);
+ }
+ if (authpeer->mwipvt && authpeer->mwipvt != p) /* Destroy old PVT if this is a new one */
+ /* We only allow one subscription per peer */
+ sip_destroy(authpeer->mwipvt);
+ authpeer->mwipvt = p; /* Link from peer to pvt */
+ p->relatedpeer = authpeer; /* Link from pvt to peer */
+ /* Do not release authpeer here */
+ } else { /* At this point, Asterisk does not understand the specified event */
+ transmit_response(p, "489 Bad Event", req);
+ ast_debug(2, "Received SIP subscribe for unknown event package: %s\n", event);
+ p->needdestroy = 1;
+ if (authpeer)
+ unref_peer(authpeer);
+ return 0;
+ }
+
+ /* Add subscription for extension state from the PBX core */
+ if (p->subscribed != MWI_NOTIFICATION && !resubscribe) {
+ if (p->stateid > -1)
+ ast_extension_state_del(p->stateid, cb_extensionstate);
+ p->stateid = ast_extension_state_add(p->context, p->exten, cb_extensionstate, p);
+ }
+
+ if (!req->ignore && p)
+ p->lastinvite = seqno;
+ if (p && !p->needdestroy) {
+ p->expiry = atoi(get_header(req, "Expires"));
+
+ /* check if the requested expiry-time is within the approved limits from sip.conf */
+ if (p->expiry > max_expiry)
+ p->expiry = max_expiry;
+ if (p->expiry < min_expiry && p->expiry > 0)
+ p->expiry = min_expiry;
+
+ if (sipdebug) {
+ if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer)
+ ast_debug(2, "Adding subscription for mailbox notification - peer %s\n", p->relatedpeer->name);
+ else
+ ast_debug(2, "Adding subscription for extension %s context %s for peer %s\n", p->exten, p->context, p->username);
+ }
+ if (p->autokillid > -1)
+ sip_cancel_destroy(p); /* Remove subscription expiry for renewals */
+ if (p->expiry > 0)
+ sip_scheddestroy(p, (p->expiry + 10) * 1000); /* Set timer for destruction of call at expiration */
+
+ if (p->subscribed == MWI_NOTIFICATION) {
+ transmit_response(p, "200 OK", req);
+ if (p->relatedpeer) { /* Send first notification */
+ ASTOBJ_WRLOCK(p->relatedpeer);
+ sip_send_mwi_to_peer(p->relatedpeer, NULL, 0);
+ ASTOBJ_UNLOCK(p->relatedpeer);
+ }
+ } else {
+ struct sip_pvt *p_old;
+
+ if ((firststate = ast_extension_state(NULL, p->context, p->exten)) < 0) {
+
+ ast_log(LOG_NOTICE, "Got SUBSCRIBE for extension %s@%s from %s, but there is no hint for that extension.\n", p->exten, p->context, ast_inet_ntoa(p->sa.sin_addr));
+ transmit_response(p, "404 Not found", req);
+ p->needdestroy = 1;
+ return 0;
+ }
+
+ transmit_response(p, "200 OK", req);
+ transmit_state_notify(p, firststate, 1, FALSE); /* Send first notification */
+ append_history(p, "Subscribestatus", "%s", ast_extension_state2str(firststate));
+ /* hide the 'complete' exten/context in the refer_to field for later display */
+ ast_string_field_build(p, subscribeuri, "%s@%s", p->exten, p->context);
+
+ /* remove any old subscription from this peer for the same exten/context,
+ as the peer has obviously forgotten about it and it's wasteful to wait
+ for it to expire and send NOTIFY messages to the peer only to have them
+ ignored (or generate errors)
+ */
+ dialoglist_lock();
+ for (p_old = dialoglist; p_old; p_old = p_old->next) {
+ if (p_old == p)
+ continue;
+ if (p_old->initreq.method != SIP_SUBSCRIBE)
+ continue;
+ if (p_old->subscribed == NONE)
+ continue;
+ sip_pvt_lock(p_old);
+ if (!strcmp(p_old->username, p->username)) {
+ if (!strcmp(p_old->exten, p->exten) &&
+ !strcmp(p_old->context, p->context)) {
+ p_old->needdestroy = 1;
+ sip_pvt_unlock(p_old);
+ break;
+ }
+ }
+ sip_pvt_unlock(p_old);
+ }
+ dialoglist_unlock();
+ }
+ if (!p->expiry)
+ p->needdestroy = 1;
+ }
+ return 1;
+}
+
+/*! \brief Handle incoming REGISTER request */
+static int handle_request_register(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, char *e)
+{
+ enum check_auth_result res;
+
+ /* Use this as the basis */
+ copy_request(&p->initreq, req);
+ if (sipdebug)
+ ast_debug(4, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
+ check_via(p, req);
+ if ((res = register_verify(p, sin, req, e)) < 0) {
+ const char *reason;
+
+ switch (res) {
+ case AUTH_SECRET_FAILED:
+ reason = "Wrong password";
+ break;
+ case AUTH_USERNAME_MISMATCH:
+ reason = "Username/auth name mismatch";
+ break;
+ case AUTH_NOT_FOUND:
+ reason = "No matching peer found";
+ break;
+ case AUTH_UNKNOWN_DOMAIN:
+ reason = "Not a local domain";
+ break;
+ case AUTH_PEER_NOT_DYNAMIC:
+ reason = "Peer is not supposed to register";
+ break;
+ case AUTH_ACL_FAILED:
+ reason = "Device does not match ACL";
+ break;
+ default:
+ reason = "Unknown failure";
+ break;
+ }
+ ast_log(LOG_NOTICE, "Registration from '%s' failed for '%s' - %s\n",
+ get_header(req, "To"), ast_inet_ntoa(sin->sin_addr),
+ reason);
+ append_history(p, "RegRequest", "Failed : Account %s : %s", get_header(req, "To"), reason);
+ } else
+ append_history(p, "RegRequest", "Succeeded : Account %s", get_header(req, "To"));
+
+ if (res < 1) {
+ /* Destroy the session, but keep us around for just a bit in case they don't
+ get our 200 OK */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return res;
+}
+
+/*! \brief Handle incoming SIP requests (methods)
+\note This is where all incoming requests go first */
+/* called with p and p->owner locked */
+static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int *recount, int *nounlock)
+{
+ /* Called with p->lock held, as well as p->owner->lock if appropriate, keeping things
+ relatively static */
+ const char *cmd;
+ const char *cseq;
+ const char *useragent;
+ int seqno;
+ int len;
+ int respid;
+ int res = 0;
+ int debug = sip_debug_test_pvt(p);
+ char *e;
+ int error = 0;
+
+ /* Get Method and Cseq */
+ cseq = get_header(req, "Cseq");
+ cmd = req->header[0];
+
+ /* Must have Cseq */
+ if (ast_strlen_zero(cmd) || ast_strlen_zero(cseq)) {
+ ast_log(LOG_ERROR, "Missing Cseq. Dropping this SIP message, it's incomplete.\n");
+ error = 1;
+ }
+ if (!error && sscanf(cseq, "%d%n", &seqno, &len) != 1) {
+ ast_log(LOG_ERROR, "No seqno in '%s'. Dropping incomplete message.\n", cmd);
+ error = 1;
+ }
+ if (error) {
+ if (!p->initreq.headers) /* New call */
+ p->needdestroy = 1; /* Make sure we destroy this dialog */
+ return -1;
+ }
+ /* Get the command XXX */
+
+ cmd = req->rlPart1;
+ e = req->rlPart2;
+
+ /* Save useragent of the client */
+ useragent = get_header(req, "User-Agent");
+ if (!ast_strlen_zero(useragent))
+ ast_string_field_set(p, useragent, useragent);
+
+ /* Find out SIP method for incoming request */
+ if (req->method == SIP_RESPONSE) { /* Response to our request */
+ /* When we get here, we know this is a SIP dialog where we've sent
+ * a request and have a response, or at least get a response
+ * within an existing dialog. Do some sanity checks, then
+ * possibly process the request. In all cases, there function
+ * terminates at the end of this block
+ */
+ int ret = 0;
+
+ if (p->ocseq < seqno && seqno != p->lastnoninvite) {
+ ast_debug(1, "Ignoring out of order response %d (expecting %d)\n", seqno, p->ocseq);
+ ret = -1;
+ } else if (p->ocseq != seqno && seqno != p->lastnoninvite) {
+ /* ignore means "don't do anything with it" but still have to
+ * respond appropriately.
+ * But in this case this is a response already, so we really
+ * have nothing to do with this message, and even setting the
+ * ignore flag is pointless.
+ */
+ req->ignore = 1;
+ append_history(p, "Ignore", "Ignoring this retransmit\n");
+ } else if (e) {
+ e = ast_skip_blanks(e);
+ if (sscanf(e, "%d %n", &respid, &len) != 1) {
+ ast_log(LOG_WARNING, "Invalid response: '%s'\n", e);
+ /* XXX maybe should do ret = -1; */
+ } else if (respid <= 0) {
+ ast_log(LOG_WARNING, "Invalid SIP response code: '%d'\n", respid);
+ /* XXX maybe should do ret = -1; */
+ } else { /* finally, something worth processing */
+ /* More SIP ridiculousness, we have to ignore bogus contacts in 100 etc responses */
+ if ((respid == 200) || ((respid >= 300) && (respid <= 399)))
+ extract_uri(p, req);
+ handle_response(p, respid, e + len, req, seqno);
+ }
+ }
+ return 0;
+ }
+
+ /* New SIP request coming in
+ (could be new request in existing SIP dialog as well...)
+ */
+
+ p->method = req->method; /* Find out which SIP method they are using */
+ ast_debug(4, "**** Received %s (%d) - Command in SIP %s\n", sip_methods[p->method].text, sip_methods[p->method].id, cmd);
+
+ if (p->icseq && (p->icseq > seqno)) {
+ ast_debug(1, "Ignoring too old SIP packet packet %d (expecting >= %d)\n", seqno, p->icseq);
+ if (req->method != SIP_ACK)
+ transmit_response(p, "503 Server error", req); /* We must respond according to RFC 3261 sec 12.2 */
+ return -1;
+ } else if (p->icseq &&
+ p->icseq == seqno &&
+ req->method != SIP_ACK &&
+ (p->method != SIP_CANCEL || p->alreadygone)) {
+ /* ignore means "don't do anything with it" but still have to
+ respond appropriately. We do this if we receive a repeat of
+ the last sequence number */
+ req->ignore = 1;
+ ast_debug(3, "Ignoring SIP message because of retransmit (%s Seqno %d, ours %d)\n", sip_methods[p->method].text, p->icseq, seqno);
+ }
+
+ if (seqno >= p->icseq)
+ /* Next should follow monotonically (but not necessarily
+ incrementally -- thanks again to the genius authors of SIP --
+ increasing */
+ p->icseq = seqno;
+
+ /* Find their tag if we haven't got it */
+ if (ast_strlen_zero(p->theirtag)) {
+ char tag[128];
+
+ gettag(req, "From", tag, sizeof(tag));
+ ast_string_field_set(p, theirtag, tag);
+ }
+ snprintf(p->lastmsg, sizeof(p->lastmsg), "Rx: %s", cmd);
+
+ if (pedanticsipchecking) {
+ /* If this is a request packet without a from tag, it's not
+ correct according to RFC 3261 */
+ /* Check if this a new request in a new dialog with a totag already attached to it,
+ RFC 3261 - section 12.2 - and we don't want to mess with recovery */
+ if (!p->initreq.headers && req->has_to_tag) {
+ /* If this is a first request and it got a to-tag, it is not for us */
+ if (!req->ignore && req->method == SIP_INVITE) {
+ transmit_response_reliable(p, "481 Call/Transaction Does Not Exist", req);
+ /* Will cease to exist after ACK */
+ } else if (req->method != SIP_ACK) {
+ transmit_response(p, "481 Call/Transaction Does Not Exist", req);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ } else {
+ ast_debug(1, "Got ACK for unknown dialog... strange.\n");
+ }
+ return res;
+ }
+ }
+
+ if (!e && (p->method == SIP_INVITE || p->method == SIP_SUBSCRIBE || p->method == SIP_REGISTER || p->method == SIP_NOTIFY)) {
+ transmit_response(p, "400 Bad request", req);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return -1;
+ }
+
+ /* Handle various incoming SIP methods in requests */
+ switch (p->method) {
+ case SIP_OPTIONS:
+ res = handle_request_options(p, req);
+ break;
+ case SIP_INVITE:
+ res = handle_request_invite(p, req, debug, seqno, sin, recount, e, nounlock);
+ break;
+ case SIP_REFER:
+ res = handle_request_refer(p, req, debug, seqno, nounlock);
+ break;
+ case SIP_CANCEL:
+ res = handle_request_cancel(p, req);
+ break;
+ case SIP_BYE:
+ res = handle_request_bye(p, req);
+ break;
+ case SIP_MESSAGE:
+ res = handle_request_message(p, req);
+ break;
+ case SIP_SUBSCRIBE:
+ res = handle_request_subscribe(p, req, sin, seqno, e);
+ break;
+ case SIP_REGISTER:
+ res = handle_request_register(p, req, sin, e);
+ break;
+ case SIP_INFO:
+ if (req->debug)
+ ast_verbose("Receiving INFO!\n");
+ if (!req->ignore)
+ handle_request_info(p, req);
+ else /* if ignoring, transmit response */
+ transmit_response(p, "200 OK", req);
+ break;
+ case SIP_NOTIFY:
+ res = handle_request_notify(p, req, sin, seqno, e);
+ break;
+ case SIP_ACK:
+ /* Make sure we don't ignore this */
+ if (seqno == p->pendinginvite) {
+ p->invitestate = INV_TERMINATED;
+ p->pendinginvite = 0;
+ __sip_ack(p, seqno, 1 /* response */, 0);
+ if (find_sdp(req)) {
+ if (process_sdp(p, req))
+ return -1;
+ }
+ check_pendings(p);
+ }
+ /* Got an ACK that we did not match. Ignore silently */
+ if (!p->lastinvite && ast_strlen_zero(p->randdata))
+ p->needdestroy = 1;
+ break;
+ default:
+ transmit_response_with_allow(p, "501 Method Not Implemented", req, 0);
+ ast_log(LOG_NOTICE, "Unknown SIP command '%s' from '%s'\n",
+ cmd, ast_inet_ntoa(p->sa.sin_addr));
+ /* If this is some new method, and we don't have a call, destroy it now */
+ if (!p->initreq.headers)
+ p->needdestroy = 1;
+ break;
+ }
+ return res;
+}
+
+/*! \brief Read data from SIP socket
+\note sipsock_read locks the owner channel while we are processing the SIP message
+\return 1 on error, 0 on success
+\note Successful messages is connected to SIP call and forwarded to handle_incoming()
+*/
+static int sipsock_read(int *id, int fd, short events, void *ignore)
+{
+ struct sip_request req;
+ struct sockaddr_in sin = { 0, };
+ int res;
+ socklen_t len = sizeof(sin);
+
+ memset(&req, 0, sizeof(req));
+ res = recvfrom(fd, req.data, sizeof(req.data) - 1, 0, (struct sockaddr *)&sin, &len);
+ if (res < 0) {
+#if !defined(__FreeBSD__)
+ if (errno == EAGAIN)
+ ast_log(LOG_NOTICE, "SIP: Received packet with bad UDP checksum\n");
+ else
+#endif
+ if (errno != ECONNREFUSED)
+ ast_log(LOG_WARNING, "Recv error: %s\n", strerror(errno));
+ return 1;
+ }
+ if (res == sizeof(req.data)) {
+ ast_debug(1, "Received packet exceeds buffer. Data is possibly lost\n");
+ req.data[sizeof(req.data) - 1] = '\0';
+ } else
+ req.data[res] = '\0';
+ req.len = res;
+
+ req.socket.fd = sipsock;
+ req.socket.type = SIP_TRANSPORT_UDP;
+ req.socket.ser = NULL;
+ req.socket.port = htons(bindaddr.sin_port);
+ req.socket.lock = NULL;
+
+ handle_request_do(&req, &sin);
+
+ return 1;
+}
+
+static int handle_request_do(struct sip_request *req, struct sockaddr_in *sin)
+{
+ struct sip_pvt *p;
+ int recount = 0;
+ int nounlock = 0;
+ int lockretry;
+
+ if (sip_debug_test_addr(sin)) /* Set the debug flag early on packet level */
+ req->debug = 1;
+ if (pedanticsipchecking)
+ req->len = lws2sws(req->data, req->len); /* Fix multiline headers */
+ if (req->debug) {
+ ast_verbose("\n<--- SIP read from %s://%s:%d --->\n%s\n<------------->\n",
+ get_transport(req->socket.type), ast_inet_ntoa(sin->sin_addr),
+ ntohs(sin->sin_port), req->data);
+ }
+
+ parse_request(req);
+ req->method = find_sip_method(req->rlPart1);
+
+ if (req->debug)
+ ast_verbose("--- (%d headers %d lines)%s ---\n", req->headers, req->lines, (req->headers + req->lines == 0) ? " Nat keepalive" : "");
+
+ if (req->headers < 2) /* Must have at least two headers */
+ return 1;
+
+ /* Process request, with netlock held, and with usual deadlock avoidance */
+ for (lockretry = 100; lockretry > 0; lockretry--) {
+ ast_mutex_lock(&netlock);
+
+ /* Find the active SIP dialog or create a new one */
+ p = find_call(req, sin, req->method); /* returns p locked */
+ if (p == NULL) {
+ ast_debug(1, "Invalid SIP message - rejected , no callid, len %d\n", req->len);
+ ast_mutex_unlock(&netlock);
+ return 1;
+ }
+
+ p->socket = req->socket;
+
+ /* Go ahead and lock the owner if it has one -- we may need it */
+ /* becaues this is deadlock-prone, we need to try and unlock if failed */
+ if (!p->owner || !ast_channel_trylock(p->owner))
+ break; /* locking succeeded */
+ ast_debug(1, "Failed to grab owner channel lock, trying again. (SIP call %s)\n", p->callid);
+ sip_pvt_unlock(p);
+ ast_mutex_unlock(&netlock);
+ /* Sleep for a very short amount of time */
+ usleep(1);
+ }
+ p->recv = *sin;
+
+ if (p->do_history) /* This is a request or response, note what it was for */
+ append_history(p, "Rx", "%s / %s / %s", req->data, get_header(req, "CSeq"), req->rlPart2);
+
+ if (!lockretry) {
+ if (p->owner)
+ ast_log(LOG_ERROR, "We could NOT get the channel lock for %s! \n", S_OR(p->owner->name, "- no channel name ??? - "));
+ ast_log(LOG_ERROR, "SIP transaction failed: %s \n", p->callid);
+ if (req->method != SIP_ACK)
+ transmit_response(p, "503 Server error", req); /* We must respond according to RFC 3261 sec 12.2 */
+ /* XXX We could add retry-after to make sure they come back */
+ append_history(p, "LockFail", "Owner lock failed, transaction failed.");
+ return 1;
+ }
+
+ nounlock = 0;
+ if (handle_incoming(p, req, sin, &recount, &nounlock) == -1) {
+ /* Request failed */
+ ast_debug(1, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : "<no callid>");
+ }
+
+ if (recount)
+ ast_update_use_count();
+
+ if (p->owner && !nounlock)
+ ast_channel_unlock(p->owner);
+ sip_pvt_unlock(p);
+ ast_mutex_unlock(&netlock);
+
+ return 1;
+}
+
+static int sip_standard_port(struct sip_socket s)
+{
+ if (s.type & SIP_TRANSPORT_TLS)
+ return s.port == STANDARD_TLS_PORT;
+ else
+ return s.port == STANDARD_SIP_PORT;
+}
+
+static struct server_instance *sip_tcp_locate(struct sockaddr_in *s)
+{
+ struct sip_threadinfo *th;
+
+ AST_LIST_LOCK(&threadl);
+ AST_LIST_TRAVERSE(&threadl, th, list) {
+ if ((s->sin_family == th->ser->requestor.sin_family) &&
+ (s->sin_addr.s_addr == th->ser->requestor.sin_addr.s_addr) &&
+ (s->sin_port == th->ser->requestor.sin_port))
+ return th->ser;
+ }
+ AST_LIST_UNLOCK(&threadl);
+ return NULL;
+}
+
+static int sip_prepare_socket(struct sip_pvt *p)
+{
+ struct sip_socket *s = &p->socket;
+ static const char name[] = "SIP socket";
+ struct server_instance *ser;
+ struct server_args ca = {
+ .name = name,
+ .accept_fd = -1,
+ };
+
+ if (s->fd != -1)
+ return s->fd;
+
+ if (s->type & SIP_TRANSPORT_UDP) {
+ s->fd = sipsock;
+ return s->fd;
+ }
+
+ ca.sin = *(sip_real_dst(p));
+
+ if ((ser = sip_tcp_locate(&ca.sin))) {
+ s->fd = ser->fd;
+ s->ser = ser;
+ return s->fd;
+ }
+
+ if (s->ser && s->ser->parent->tls_cfg)
+ ca.tls_cfg = s->ser->parent->tls_cfg;
+ else {
+ if (s->type & SIP_TRANSPORT_TLS) {
+ ca.tls_cfg = ast_calloc(1, sizeof(*ca.tls_cfg));
+ if (!ca.tls_cfg)
+ return -1;
+ memcpy(ca.tls_cfg, &default_tls_cfg, sizeof(*ca.tls_cfg));
+ if (!ast_strlen_zero(p->tohost))
+ ast_copy_string(ca.hostname, p->tohost, sizeof(ca.hostname));
+ }
+ }
+ s->ser = (!s->ser) ? client_start(&ca) : s->ser;
+
+ if (!s->ser) {
+ if (ca.tls_cfg)
+ ast_free(ca.tls_cfg);
+ return -1;
+ }
+
+ s->fd = ca.accept_fd;
+
+ if (ast_pthread_create_background(&ca.master, NULL, sip_tcp_helper_thread, p)) {
+ ast_log(LOG_DEBUG, "Unable to launch '%s'.", ca.name);
+ close(ca.accept_fd);
+ s->fd = ca.accept_fd = -1;
+ }
+
+ return s->fd;
+}
+
+/*!
+ * \brief Get cached MWI info
+ * \retval 0 At least one message is waiting
+ * \retval 1 no messages waiting
+ */
+static int get_cached_mwi(struct sip_peer *peer, int *new, int *old)
+{
+ struct sip_mailbox *mailbox;
+
+ AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) {
+ struct ast_event *event;
+ event = ast_event_get_cached(AST_EVENT_MWI,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox->mailbox,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, S_OR(mailbox->context, "default"),
+ AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_EXISTS,
+ AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_EXISTS,
+ AST_EVENT_IE_END);
+ if (!event)
+ continue;
+ *new += ast_event_get_ie_uint(event, AST_EVENT_IE_NEWMSGS);
+ *old += ast_event_get_ie_uint(event, AST_EVENT_IE_OLDMSGS);
+ ast_event_destroy(event);
+ }
+
+ return (*new || *old) ? 0 : 1;
+}
+
+/*! \brief Send message waiting indication to alert peer that they've got voicemail */
+static int sip_send_mwi_to_peer(struct sip_peer *peer, const struct ast_event *event, int cache_only)
+{
+ /* Called with peerl lock, but releases it */
+ struct sip_pvt *p;
+ int newmsgs = 0, oldmsgs = 0;
+
+ if (ast_test_flag((&peer->flags[1]), SIP_PAGE2_SUBSCRIBEMWIONLY) && !peer->mwipvt)
+ return 0;
+
+ /* Do we have an IP address? If not, skip this peer */
+ if (!peer->addr.sin_addr.s_addr && !peer->defaddr.sin_addr.s_addr)
+ return 0;
+
+ if (event) {
+ newmsgs = ast_event_get_ie_uint(event, AST_EVENT_IE_NEWMSGS);
+ oldmsgs = ast_event_get_ie_uint(event, AST_EVENT_IE_OLDMSGS);
+ } else if (!get_cached_mwi(peer, &newmsgs, &oldmsgs)) {
+ /* got it! Don't keep looking. */
+ } else if (cache_only) {
+ return 0;
+ } else { /* Fall back to manually checking the mailbox */
+ struct ast_str *mailbox_str = ast_str_alloca(512);
+ peer_mailboxes_to_str(&mailbox_str, peer);
+ ast_app_inboxcount(mailbox_str->str, &newmsgs, &oldmsgs);
+ }
+
+ if (peer->mwipvt) {
+ /* Base message on subscription */
+ p = dialog_ref(peer->mwipvt);
+ } else {
+ /* Build temporary dialog for this message */
+ if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY)))
+ return -1;
+ if (create_addr_from_peer(p, peer)) {
+ /* Maybe they're not registered, etc. */
+ sip_destroy(p);
+ return 0;
+ }
+ /* Recalculate our side, and recalculate Call ID */
+ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip);
+ build_via(p);
+ build_callid_pvt(p);
+ /* Destroy this session after 32 secs */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+
+ /* Send MWI */
+ ast_set_flag(&p->flags[0], SIP_OUTGOING);
+ transmit_notify_with_mwi(p, newmsgs, oldmsgs, peer->vmexten);
+
+ return 0;
+}
+
+/*! \brief helper function for the monitoring thread */
+static void check_rtp_timeout(struct sip_pvt *dialog, time_t t)
+{
+ /* If we have no RTP or no active owner, no need to check timers */
+ if (!dialog->rtp || !dialog->owner)
+ return;
+ /* If the call is not in UP state or redirected outside Asterisk, no need to check timers */
+ if (dialog->owner->_state != AST_STATE_UP || dialog->redirip.sin_addr.s_addr)
+ return;
+
+ /* If the call is involved in a T38 fax session do not check RTP timeout */
+ if (dialog->t38.state == T38_ENABLED)
+ return;
+
+ /* If we have no timers set, return now */
+ if ((ast_rtp_get_rtpkeepalive(dialog->rtp) == 0) && (ast_rtp_get_rtptimeout(dialog->rtp) == 0) && (ast_rtp_get_rtpholdtimeout(dialog->rtp) == 0))
+ return;
+
+ /* Check AUDIO RTP keepalives */
+ if (dialog->lastrtptx && ast_rtp_get_rtpkeepalive(dialog->rtp) &&
+ (t > dialog->lastrtptx + ast_rtp_get_rtpkeepalive(dialog->rtp))) {
+ /* Need to send an empty RTP packet */
+ dialog->lastrtptx = time(NULL);
+ ast_rtp_sendcng(dialog->rtp, 0);
+ }
+
+ /*! \todo Check video RTP keepalives
+
+ Do we need to move the lastrtptx to the RTP structure to have one for audio and one
+ for video? It really does belong to the RTP structure.
+ */
+
+ /* Check AUDIO RTP timers */
+ if (dialog->lastrtprx && (ast_rtp_get_rtptimeout(dialog->rtp) || ast_rtp_get_rtpholdtimeout(dialog->rtp)) &&
+ (t > dialog->lastrtprx + ast_rtp_get_rtptimeout(dialog->rtp))) {
+
+ /* Might be a timeout now -- see if we're on hold */
+ struct sockaddr_in sin;
+ ast_rtp_get_peer(dialog->rtp, &sin);
+ if (sin.sin_addr.s_addr || (ast_rtp_get_rtpholdtimeout(dialog->rtp) &&
+ (t > dialog->lastrtprx + ast_rtp_get_rtpholdtimeout(dialog->rtp)))) {
+ /* Needs a hangup */
+ if (ast_rtp_get_rtptimeout(dialog->rtp)) {
+ while (dialog->owner && ast_channel_trylock(dialog->owner)) {
+ sip_pvt_unlock(dialog);
+ usleep(1);
+ sip_pvt_lock(dialog);
+ }
+ ast_log(LOG_NOTICE, "Disconnecting call '%s' for lack of RTP activity in %ld seconds\n",
+ dialog->owner->name, (long) (t - dialog->lastrtprx));
+ /* Issue a softhangup */
+ ast_softhangup_nolock(dialog->owner, AST_SOFTHANGUP_DEV);
+ ast_channel_unlock(dialog->owner);
+ /* forget the timeouts for this call, since a hangup
+ has already been requested and we don't want to
+ repeatedly request hangups
+ */
+ ast_rtp_set_rtptimeout(dialog->rtp, 0);
+ ast_rtp_set_rtpholdtimeout(dialog->rtp, 0);
+ if (dialog->vrtp) {
+ ast_rtp_set_rtptimeout(dialog->vrtp, 0);
+ ast_rtp_set_rtpholdtimeout(dialog->vrtp, 0);
+ }
+ }
+ }
+ }
+}
+
+/*! \brief The SIP monitoring thread
+\note This thread monitors all the SIP sessions and peers that needs notification of mwi
+ (and thus do not have a separate thread) indefinitely
+*/
+static void *do_monitor(void *data)
+{
+ int res;
+ struct sip_pvt *dialog;
+ time_t t;
+ int reloading;
+
+ /* Add an I/O event to our SIP UDP socket */
+ if (sipsock > -1)
+ sipsock_read_id = ast_io_add(io, sipsock, sipsock_read, AST_IO_IN, NULL);
+
+ /* From here on out, we die whenever asked */
+ for(;;) {
+ /* Check for a reload request */
+ ast_mutex_lock(&sip_reload_lock);
+ reloading = sip_reloading;
+ sip_reloading = FALSE;
+ ast_mutex_unlock(&sip_reload_lock);
+ if (reloading) {
+ ast_verb(1, "Reloading SIP\n");
+ sip_do_reload(sip_reloadreason);
+
+ /* Change the I/O fd of our UDP socket */
+ if (sipsock > -1) {
+ if (sipsock_read_id)
+ sipsock_read_id = ast_io_change(io, sipsock_read_id, sipsock, NULL, 0, NULL);
+ else
+ sipsock_read_id = ast_io_add(io, sipsock, sipsock_read, AST_IO_IN, NULL);
+ } else if (sipsock_read_id) {
+ ast_io_remove(io, sipsock_read_id);
+ sipsock_read_id = NULL;
+ }
+ }
+
+ /* Check for dialogs needing to be killed */
+ dialoglist_lock();
+restartsearch:
+ t = time(NULL);
+ /* don't scan the dialogs list if it hasn't been a reasonable period
+ of time since the last time we did it (when MWI is being sent, we can
+ get back to this point every millisecond or less)
+ */
+ for (dialog = dialoglist; dialog; dialog = dialog->next) {
+ sip_pvt_lock(dialog);
+ /* Check RTP timeouts and kill calls if we have a timeout set and do not get RTP */
+ check_rtp_timeout(dialog, t);
+ /* If we have sessions that needs to be destroyed, do it now */
+ /* Check if we have outstanding requests not responsed to or an active call
+ - if that's the case, wait with destruction */
+ if (dialog->needdestroy && !dialog->packets && !dialog->owner) {
+ sip_pvt_unlock(dialog);
+ __sip_destroy(dialog, TRUE, FALSE);
+ goto restartsearch;
+ }
+ sip_pvt_unlock(dialog);
+ }
+ dialoglist_unlock();
+
+ pthread_testcancel();
+ /* Wait for sched or io */
+ res = ast_sched_wait(sched);
+ if ((res < 0) || (res > 1000))
+ res = 1000;
+ res = ast_io_wait(io, res);
+ if (res > 20)
+ ast_debug(1, "chan_sip: ast_io_wait ran %d all at once\n", res);
+ ast_mutex_lock(&monlock);
+ if (res >= 0) {
+ res = ast_sched_runq(sched);
+ if (res >= 20)
+ ast_debug(1, "chan_sip: ast_sched_runq ran %d all at once\n", res);
+ }
+ ast_mutex_unlock(&monlock);
+ }
+
+ /* Never reached */
+ return NULL;
+}
+
+/*! \brief Start the channel monitor thread */
+static int restart_monitor(void)
+{
+ /* If we're supposed to be stopped -- stay stopped */
+ if (monitor_thread == AST_PTHREADT_STOP)
+ return 0;
+ ast_mutex_lock(&monlock);
+ if (monitor_thread == pthread_self()) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_WARNING, "Cannot kill myself\n");
+ return -1;
+ }
+ if (monitor_thread != AST_PTHREADT_NULL) {
+ /* Wake up the thread */
+ pthread_kill(monitor_thread, SIGURG);
+ } else {
+ /* Start a new monitor */
+ if (ast_pthread_create_background(&monitor_thread, NULL, do_monitor, NULL) < 0) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
+ return -1;
+ }
+ }
+ ast_mutex_unlock(&monlock);
+ return 0;
+}
+
+
+/*! \brief Session-Timers: Restart session timer */
+static void restart_session_timer(struct sip_pvt *p)
+{
+ if (!p->stimer) {
+ ast_log(LOG_WARNING, "Null stimer in restart_session_timer - %s\n", p->callid);
+ return;
+ }
+
+ if (p->stimer->st_active == TRUE) {
+ if (ast_sched_del(sched, p->stimer->st_schedid) != 0) {
+ ast_log(LOG_WARNING, "ast_sched_del failed: %d - %s\n", p->stimer->st_schedid, p->callid);
+ }
+
+ ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid);
+ start_session_timer(p);
+ }
+}
+
+
+/*! \brief Session-Timers: Stop session timer */
+static void stop_session_timer(struct sip_pvt *p)
+{
+ if (!p->stimer) {
+ ast_log(LOG_WARNING, "Null stimer in stop_session_timer - %s\n", p->callid);
+ return;
+ }
+
+ if (p->stimer->st_active == TRUE) {
+ p->stimer->st_active = FALSE;
+ ast_sched_del(sched, p->stimer->st_schedid);
+ ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid);
+ }
+}
+
+
+/*! \brief Session-Timers: Start session timer */
+static void start_session_timer(struct sip_pvt *p)
+{
+ if (!p->stimer) {
+ ast_log(LOG_WARNING, "Null stimer in start_session_timer - %s\n", p->callid);
+ return;
+ }
+
+ p->stimer->st_schedid = ast_sched_add(sched, p->stimer->st_interval * 1000 / 2, proc_session_timer, p);
+ if (p->stimer->st_schedid < 0) {
+ ast_log(LOG_ERROR, "ast_sched_add failed.\n");
+ }
+ ast_debug(2, "Session timer started: %d - %s\n", p->stimer->st_schedid, p->callid);
+}
+
+
+/*! \brief Session-Timers: Process session refresh timeout event */
+static int proc_session_timer(const void *vp)
+{
+ struct sip_pvt *p = (struct sip_pvt *) vp;
+ int sendreinv = FALSE;
+
+ if (!p->stimer) {
+ ast_log(LOG_WARNING, "Null stimer in proc_session_timer - %s\n", p->callid);
+ return 0;
+ }
+
+ ast_debug(2, "Session timer expired: %d - %s\n", p->stimer->st_schedid, p->callid);
+
+ if (!p->owner) {
+ if (p->stimer->st_active == TRUE) {
+ stop_session_timer(p);
+ }
+ return 0;
+ }
+
+ if ((p->stimer->st_active != TRUE) || (p->owner->_state != AST_STATE_UP)) {
+ return 0;
+ }
+
+ switch (p->stimer->st_ref) {
+ case SESSION_TIMER_REFRESHER_UAC:
+ if (p->outgoing_call == TRUE) {
+ sendreinv = TRUE;
+ }
+ break;
+ case SESSION_TIMER_REFRESHER_UAS:
+ if (p->outgoing_call != TRUE) {
+ sendreinv = TRUE;
+ }
+ break;
+ default:
+ ast_log(LOG_ERROR, "Unknown session refresher %d\n", p->stimer->st_ref);
+ return -1;
+ }
+
+ if (sendreinv == TRUE) {
+ transmit_reinvite_with_sdp(p, FALSE, TRUE);
+ } else {
+ p->stimer->st_expirys++;
+ if (p->stimer->st_expirys >= 2) {
+ ast_log(LOG_WARNING, "Session-Timer expired - %s\n", p->callid);
+ stop_session_timer(p);
+
+ while (p->owner && ast_channel_trylock(p->owner)) {
+ sip_pvt_unlock(p);
+ usleep(1);
+ sip_pvt_lock(p);
+ }
+
+ ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV);
+ ast_channel_unlock(p->owner);
+ }
+ }
+ return 1;
+}
+
+
+/* Session-Timers: Function for parsing Min-SE header */
+int parse_minse (const char *p_hdrval, int *const p_interval)
+{
+ if (ast_strlen_zero(p_hdrval)) {
+ ast_log(LOG_WARNING, "Null Min-SE header\n");
+ return -1;
+ }
+
+ *p_interval = 0;
+ p_hdrval = ast_skip_blanks(p_hdrval);
+ if (!sscanf(p_hdrval, "%d", p_interval)) {
+ ast_log(LOG_WARNING, "Parsing of Min-SE header failed %s\n", p_hdrval);
+ return -1;
+ }
+
+ ast_debug(2, "Received Min-SE: %d\n", *p_interval);
+ return 0;
+}
+
+
+/* Session-Timers: Function for parsing Session-Expires header */
+int parse_session_expires(const char *p_hdrval, int *const p_interval, enum st_refresher *const p_ref)
+{
+ char *p_token;
+ int ref_idx;
+ char *p_se_hdr;
+
+ if (ast_strlen_zero(p_hdrval)) {
+ ast_log(LOG_WARNING, "Null Session-Expires header\n");
+ return -1;
+ }
+
+ *p_ref = SESSION_TIMER_REFRESHER_AUTO;
+ *p_interval = 0;
+
+ p_se_hdr = ast_strdupa(p_hdrval);
+ p_se_hdr = ast_skip_blanks(p_se_hdr);
+
+ while ((p_token = strsep(&p_se_hdr, ";"))) {
+ p_token = ast_skip_blanks(p_token);
+ if (!sscanf(p_token, "%d", p_interval)) {
+ ast_log(LOG_WARNING, "Parsing of Session-Expires failed\n");
+ return -1;
+ }
+
+ ast_debug(2, "Session-Expires: %d\n", *p_interval);
+
+ if (!p_se_hdr)
+ continue;
+
+ ref_idx = strlen("refresher=");
+ if (!strncasecmp(p_se_hdr, "refresher=", ref_idx)) {
+ p_se_hdr += ref_idx;
+ p_se_hdr = ast_skip_blanks(p_se_hdr);
+
+ if (!strncasecmp(p_se_hdr, "uac", strlen("uac"))) {
+ *p_ref = SESSION_TIMER_REFRESHER_UAC;
+ ast_debug(2, "Refresher: UAC\n");
+ } else if (!strncasecmp(p_se_hdr, "uas", strlen("uas"))) {
+ *p_ref = SESSION_TIMER_REFRESHER_UAS;
+ ast_debug(2, "Refresher: UAS\n");
+ } else {
+ ast_log(LOG_WARNING, "Invalid refresher value %s\n", p_se_hdr);
+ return -1;
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+
+/*! \brief Handle 422 response to INVITE with session-timer requested
+
+ Session-Timers: An INVITE originated by Asterisk that asks for session-timers support
+ from the UAS can result into a 422 response. This is how a UAS or an intermediary proxy
+ server tells Asterisk that the session refresh interval offered by Asterisk is too low
+ for them. The proc_422_rsp() function handles a 422 response. It extracts the Min-SE
+ header that comes back in 422 and sends a new INVITE accordingly. */
+static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp)
+{
+ int rtn;
+ const char *p_hdrval;
+ int minse;
+
+ p_hdrval = get_header(rsp, "Min-SE");
+ if (ast_strlen_zero(p_hdrval)) {
+ ast_log(LOG_WARNING, "422 response without a Min-SE header %s\n", p_hdrval);
+ return;
+ }
+ rtn = parse_minse(p_hdrval, &minse);
+ if (rtn != 0) {
+ ast_log(LOG_WARNING, "Parsing of Min-SE header failed %s\n", p_hdrval);
+ return;
+ }
+ p->stimer->st_interval = minse;
+ transmit_invite(p, SIP_INVITE, 1, 2);
+}
+
+
+/*! \brief Get Max or Min SE (session timer expiry)
+ \param max if true, get max se, otherwise min se
+*/
+int st_get_se(struct sip_pvt *p, int max)
+{
+ if (max == TRUE) {
+ if (p->stimer->st_cached_max_se) {
+ return p->stimer->st_cached_max_se;
+ } else {
+ if (p->username) {
+ struct sip_user *up = find_user(p->username, 1);
+ if (up) {
+ p->stimer->st_cached_max_se = up->stimer.st_max_se;
+ return (p->stimer->st_cached_max_se);
+ }
+ }
+ if (p->peername) {
+ struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+ if (pp) {
+ p->stimer->st_cached_max_se = pp->stimer.st_max_se;
+ return (p->stimer->st_cached_max_se);
+ }
+ }
+ }
+ p->stimer->st_cached_max_se = global_max_se;
+ return (p->stimer->st_cached_max_se);
+ } else {
+ if (p->stimer->st_cached_min_se) {
+ return p->stimer->st_cached_min_se;
+ } else {
+ if (p->username) {
+ struct sip_user *up = find_user(p->username, 1);
+ if (up) {
+ p->stimer->st_cached_min_se = up->stimer.st_min_se;
+ return (p->stimer->st_cached_min_se);
+ }
+ }
+ if (p->peername) {
+ struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+ if (pp) {
+ p->stimer->st_cached_min_se = pp->stimer.st_min_se;
+ return (p->stimer->st_cached_min_se);
+ }
+ }
+ }
+ p->stimer->st_cached_min_se = global_min_se;
+ return (p->stimer->st_cached_min_se);
+ }
+}
+
+
+/*! \brief Get the entity (UAC or UAS) that's acting as the session-timer refresher
+ \param sip_pvt pointer to the SIP dialog
+*/
+enum st_refresher st_get_refresher(struct sip_pvt *p)
+{
+ if (p->stimer->st_cached_ref != SESSION_TIMER_REFRESHER_AUTO)
+ return p->stimer->st_cached_ref;
+
+ if (p->username) {
+ struct sip_user *up = find_user(p->username, 1);
+ if (up) {
+ p->stimer->st_cached_ref = up->stimer.st_ref;
+ return up->stimer.st_ref;
+ }
+ }
+
+ if (p->peername) {
+ struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+ if (pp) {
+ p->stimer->st_cached_ref = pp->stimer.st_ref;
+ return pp->stimer.st_ref;
+ }
+ }
+
+ p->stimer->st_cached_ref = global_st_refresher;
+ return global_st_refresher;
+}
+
+
+/*! \brief Get the session-timer mode
+ \param sip_pvt pointer to the SIP dialog
+*/
+enum st_mode st_get_mode(struct sip_pvt *p)
+{
+ if (!p->stimer)
+ sip_st_alloc(p);
+
+ if (p->stimer->st_cached_mode != SESSION_TIMER_MODE_INVALID)
+ return p->stimer->st_cached_mode;
+
+ if (p->username) {
+ struct sip_user *up = find_user(p->username, 1);
+ if (up) {
+ p->stimer->st_cached_mode = up->stimer.st_mode_oper;
+ return up->stimer.st_mode_oper;
+ }
+ }
+ if (p->peername) {
+ struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+ if (pp) {
+ p->stimer->st_cached_mode = pp->stimer.st_mode_oper;
+ return pp->stimer.st_mode_oper;
+ }
+ }
+
+ p->stimer->st_cached_mode = global_st_mode;
+ return global_st_mode;
+}
+
+
+/*! \brief React to lack of answer to Qualify poke */
+static int sip_poke_noanswer(const void *data)
+{
+ struct sip_peer *peer = (struct sip_peer *)data;
+
+ peer->pokeexpire = -1;
+ if (peer->lastms > -1) {
+ ast_log(LOG_NOTICE, "Peer '%s' is now UNREACHABLE! Last qualify: %d\n", peer->name, peer->lastms);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unreachable\r\nTime: %d\r\n", peer->name, -1);
+ if (global_regextenonqualify)
+ register_peer_exten(peer, FALSE);
+ }
+ if (peer->call)
+ peer->call = sip_destroy(peer->call);
+ peer->lastms = -1;
+ ast_device_state_changed("SIP/%s", peer->name);
+ /* Try again quickly */
+ peer->pokeexpire = ast_sched_replace(peer->pokeexpire, sched,
+ DEFAULT_FREQ_NOTOK, sip_poke_peer_s, peer);
+ return 0;
+}
+
+/*! \brief Check availability of peer, also keep NAT open
+\note This is done with the interval in qualify= configuration option
+ Default is 2 seconds */
+static int sip_poke_peer(struct sip_peer *peer)
+{
+ struct sip_pvt *p;
+ int xmitres = 0;
+
+ if (!peer->maxms || !peer->addr.sin_addr.s_addr) {
+ /* IF we have no IP, or this isn't to be monitored, return
+ immediately after clearing things out */
+ if (peer->pokeexpire > -1)
+ ast_sched_del(sched, peer->pokeexpire);
+ peer->lastms = 0;
+ peer->pokeexpire = -1;
+ peer->call = NULL;
+ return 0;
+ }
+ if (peer->call) {
+ if (sipdebug)
+ ast_log(LOG_NOTICE, "Still have a QUALIFY dialog active, deleting\n");
+ peer->call = sip_destroy(peer->call);
+ }
+ if (!(p = peer->call = sip_alloc(NULL, NULL, 0, SIP_OPTIONS)))
+ return -1;
+
+ p->sa = peer->addr;
+ p->recv = peer->addr;
+ p->socket = peer->socket;
+ ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
+ ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+
+ /* Send OPTIONs to peer's fullcontact */
+ if (!ast_strlen_zero(peer->fullcontact))
+ ast_string_field_set(p, fullcontact, peer->fullcontact);
+
+ if (!ast_strlen_zero(peer->tohost))
+ ast_string_field_set(p, tohost, peer->tohost);
+ else
+ ast_string_field_set(p, tohost, ast_inet_ntoa(peer->addr.sin_addr));
+
+ /* Recalculate our side, and recalculate Call ID */
+ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip);
+ build_via(p);
+ build_callid_pvt(p);
+
+ if (peer->pokeexpire > -1)
+ ast_sched_del(sched, peer->pokeexpire);
+ p->relatedpeer = peer;
+ ast_set_flag(&p->flags[0], SIP_OUTGOING);
+#ifdef VOCAL_DATA_HACK
+ ast_copy_string(p->username, "__VOCAL_DATA_SHOULD_READ_THE_SIP_SPEC__", sizeof(p->username));
+ xmitres = transmit_invite(p, SIP_INVITE, 0, 2);
+#else
+ xmitres = transmit_invite(p, SIP_OPTIONS, 0, 2);
+#endif
+ peer->ps = ast_tvnow();
+ if (xmitres == XMIT_ERROR)
+ sip_poke_noanswer(peer); /* Immediately unreachable, network problems */
+ else {
+ peer->pokeexpire = ast_sched_replace(peer->pokeexpire, sched,
+ peer->maxms * 2, sip_poke_noanswer, peer);
+ }
+
+ return 0;
+}
+
+/*! \brief Part of PBX channel interface
+\note
+\par Return values:---
+
+ If we have qualify on and the device is not reachable, regardless of registration
+ state we return AST_DEVICE_UNAVAILABLE
+
+ For peers with call limit:
+ - not registered AST_DEVICE_UNAVAILABLE
+ - registered, no call AST_DEVICE_NOT_INUSE
+ - registered, active calls AST_DEVICE_INUSE
+ - registered, call limit reached AST_DEVICE_BUSY
+ - registered, onhold AST_DEVICE_ONHOLD
+ - registered, ringing AST_DEVICE_RINGING
+
+ For peers without call limit:
+ - not registered AST_DEVICE_UNAVAILABLE
+ - registered AST_DEVICE_NOT_INUSE
+ - fixed IP (!dynamic) AST_DEVICE_NOT_INUSE
+
+ Peers that does not have a known call and can't be reached by OPTIONS
+ - unreachable AST_DEVICE_UNAVAILABLE
+
+ If we return AST_DEVICE_UNKNOWN, the device state engine will try to find
+ out a state by walking the channel list.
+
+ The queue system (\ref app_queue.c) treats a member as "active"
+ if devicestate is != AST_DEVICE_UNAVAILBALE && != AST_DEVICE_INVALID
+
+ When placing a call to the queue member, queue system sets a member to busy if
+ != AST_DEVICE_NOT_INUSE and != AST_DEVICE_UNKNOWN
+
+*/
+static int sip_devicestate(void *data)
+{
+ char *host;
+ char *tmp;
+ struct sip_peer *p;
+
+ int res = AST_DEVICE_INVALID;
+
+ /* make sure data is not null. Maybe unnecessary, but better be safe */
+ host = ast_strdupa(data ? data : "");
+ if ((tmp = strchr(host, '@')))
+ host = tmp + 1;
+
+ ast_debug(3, "Checking device state for peer %s\n", host);
+
+ if ((p = find_peer(host, NULL, 1))) {
+ if (p->addr.sin_addr.s_addr || p->defaddr.sin_addr.s_addr) {
+ /* we have an address for the peer */
+
+ /* Check status in this order
+ - Hold
+ - Ringing
+ - Busy (enforced only by call limit)
+ - Inuse (we have a call)
+ - Unreachable (qualify)
+ If we don't find any of these state, report AST_DEVICE_NOT_INUSE
+ for registered devices */
+
+ if (p->onHold)
+ /* First check for hold or ring states */
+ res = AST_DEVICE_ONHOLD;
+ else if (p->inRinging) {
+ if (p->inRinging == p->inUse)
+ res = AST_DEVICE_RINGING;
+ else
+ res = AST_DEVICE_RINGINUSE;
+ } else if (p->call_limit && (p->inUse == p->call_limit))
+ /* check call limit */
+ res = AST_DEVICE_BUSY;
+ else if (p->call_limit && p->busy_level && p->inUse >= p->busy_level)
+ /* We're forcing busy before we've reached the call limit */
+ res = AST_DEVICE_BUSY;
+ else if (p->call_limit && p->inUse)
+ /* Not busy, but we do have a call */
+ res = AST_DEVICE_INUSE;
+ else if (p->maxms && ((p->lastms > p->maxms) || (p->lastms < 0)))
+ /* We don't have a call. Are we reachable at all? Requires qualify= */
+ res = AST_DEVICE_UNAVAILABLE;
+ else /* Default reply if we're registered and have no other data */
+ res = AST_DEVICE_NOT_INUSE;
+ } else {
+ /* there is no address, it's unavailable */
+ res = AST_DEVICE_UNAVAILABLE;
+ }
+ unref_peer(p);
+ } else {
+ res = AST_DEVICE_UNKNOWN;
+ }
+
+ return res;
+}
+
+/*! \brief PBX interface function -build SIP pvt structure
+ SIP calls initiated by the PBX arrive here
+
+ SIP Dial string syntax
+ SIP/exten@host!dnid
+ or SIP/host/exten!dnid
+ or SIP/host!dnid
+*/
+static struct ast_channel *sip_request_call(const char *type, int format, void *data, int *cause)
+{
+ struct sip_pvt *p;
+ struct ast_channel *tmpc = NULL;
+ char *ext, *host;
+ char tmp[256];
+ char *dest = data;
+ char *dnid;
+ int oldformat = format;
+
+ /* mask request with some set of allowed formats.
+ * XXX this needs to be fixed.
+ * The original code uses AST_FORMAT_AUDIO_MASK, but it is
+ * unclear what to use here. We have global_capabilities, which is
+ * configured from sip.conf, and sip_tech.capabilities, which is
+ * hardwired to all audio formats.
+ */
+ format &= AST_FORMAT_AUDIO_MASK;
+ if (!format) {
+ ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format %s while capability is %s\n", ast_getformatname(oldformat), ast_getformatname(global_capability));
+ *cause = AST_CAUSE_BEARERCAPABILITY_NOTAVAIL; /* Can't find codec to connect to host */
+ return NULL;
+ }
+ ast_debug(1, "Asked to create a SIP channel with formats: %s\n", ast_getformatname_multiple(tmp, sizeof(tmp), oldformat));
+
+ if (!(p = sip_alloc(NULL, NULL, 0, SIP_INVITE))) {
+ ast_log(LOG_ERROR, "Unable to build sip pvt data for '%s' (Out of memory or socket error)\n", dest);
+ *cause = AST_CAUSE_SWITCH_CONGESTION;
+ return NULL;
+ }
+
+ p->outgoing_call = TRUE;
+
+ if (!(p->options = ast_calloc(1, sizeof(*p->options)))) {
+ sip_destroy(p);
+ ast_log(LOG_ERROR, "Unable to build option SIP data structure - Out of memory\n");
+ *cause = AST_CAUSE_SWITCH_CONGESTION;
+ return NULL;
+ }
+
+ /* Save the destination, the SIP dial string */
+ ast_copy_string(tmp, dest, sizeof(tmp));
+
+
+ /* Find DNID and take it away */
+ dnid = strchr(tmp, '!');
+ if (dnid != NULL) {
+ *dnid++ = '\0';
+ ast_string_field_set(p, todnid, dnid);
+ }
+
+ /* Find at sign - @ */
+ host = strchr(tmp, '@');
+ if (host) {
+ *host++ = '\0';
+ ext = tmp;
+ } else {
+ ext = strchr(tmp, '/');
+ if (ext)
+ *ext++ = '\0';
+ host = tmp;
+ }
+
+ /* We now have
+ host = peer name, DNS host name or DNS domain (for SRV)
+ ext = extension (user part of URI)
+ dnid = destination of the call (applies to the To: header)
+ */
+ if (create_addr(p, host)) {
+ *cause = AST_CAUSE_UNREGISTERED;
+ ast_debug(3, "Cant create SIP call - target device not registred\n");
+ sip_destroy(p);
+ return NULL;
+ }
+ if (ast_strlen_zero(p->peername) && ext)
+ ast_string_field_set(p, peername, ext);
+ /* Recalculate our side, and recalculate Call ID */
+ ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip);
+ build_via(p);
+ build_callid_pvt(p);
+
+ /* We have an extension to call, don't use the full contact here */
+ /* This to enable dialing registered peers with extension dialling,
+ like SIP/peername/extension
+ SIP/peername will still use the full contact
+ */
+ if (ext) {
+ ast_string_field_set(p, username, ext);
+ ast_string_field_set(p, fullcontact, NULL);
+ }
+#if 0
+ printf("Setting up to call extension '%s' at '%s'\n", ext ? ext : "<none>", host);
+#endif
+ p->prefcodec = oldformat; /* Format for this call */
+ p->jointcapability = oldformat;
+ sip_pvt_lock(p);
+ tmpc = sip_new(p, AST_STATE_DOWN, host); /* Place the call */
+ if (global_callevents)
+ manager_event(EVENT_FLAG_SYSTEM, "ChannelUpdate",
+ "Channel: %s\r\nChanneltype: %s\r\nSIPcallid: %s\r\nSIPfullcontact: %s\r\nPeername: %s\r\n",
+ p->owner? p->owner->name : "", "SIP", p->callid, p->fullcontact, p->peername);
+ sip_pvt_unlock(p);
+ if (!tmpc)
+ sip_destroy(p);
+ ast_update_use_count();
+ restart_monitor();
+ return tmpc;
+}
+
+/*! Parse insecure= setting in sip.conf and set flags according to setting */
+static void set_insecure_flags (struct ast_flags *flags, const char *value, int lineno)
+{
+ if (ast_strlen_zero(value))
+ return;
+
+ if (!ast_false(value)) {
+ char buf[64];
+ char *word, *next;
+
+ ast_copy_string(buf, value, sizeof(buf));
+ next = buf;
+ while ((word = strsep(&next, ","))) {
+ if (!strcasecmp(word, "port"))
+ ast_set_flag(&flags[0], SIP_INSECURE_PORT);
+ else if (!strcasecmp(word, "invite"))
+ ast_set_flag(&flags[0], SIP_INSECURE_INVITE);
+ else
+ ast_log(LOG_WARNING, "Unknown insecure mode '%s' on line %d\n", value, lineno);
+ }
+ }
+}
+
+/*!
+ \brief Handle flag-type options common to configuration of devices - users and peers
+ \param flags array of two struct ast_flags
+ \param mask array of two struct ast_flags
+ \param v linked list of config variables to process
+ \returns non-zero if any config options were handled, zero otherwise
+*/
+static int handle_common_options(struct ast_flags *flags, struct ast_flags *mask, struct ast_variable *v)
+{
+ int res = 1;
+
+ if (!strcasecmp(v->name, "trustrpid")) {
+ ast_set_flag(&mask[0], SIP_TRUSTRPID);
+ ast_set2_flag(&flags[0], ast_true(v->value), SIP_TRUSTRPID);
+ } else if (!strcasecmp(v->name, "sendrpid")) {
+ ast_set_flag(&mask[0], SIP_SENDRPID);
+ ast_set2_flag(&flags[0], ast_true(v->value), SIP_SENDRPID);
+ } else if (!strcasecmp(v->name, "g726nonstandard")) {
+ ast_set_flag(&mask[0], SIP_G726_NONSTANDARD);
+ ast_set2_flag(&flags[0], ast_true(v->value), SIP_G726_NONSTANDARD);
+ } else if (!strcasecmp(v->name, "useclientcode")) {
+ ast_set_flag(&mask[0], SIP_USECLIENTCODE);
+ ast_set2_flag(&flags[0], ast_true(v->value), SIP_USECLIENTCODE);
+ } else if (!strcasecmp(v->name, "dtmfmode")) {
+ ast_set_flag(&mask[0], SIP_DTMF);
+ ast_clear_flag(&flags[0], SIP_DTMF);
+ if (!strcasecmp(v->value, "inband"))
+ ast_set_flag(&flags[0], SIP_DTMF_INBAND);
+ else if (!strcasecmp(v->value, "rfc2833"))
+ ast_set_flag(&flags[0], SIP_DTMF_RFC2833);
+ else if (!strcasecmp(v->value, "info"))
+ ast_set_flag(&flags[0], SIP_DTMF_INFO);
+ else if (!strcasecmp(v->value, "shortinfo"))
+ ast_set_flag(&flags[0], SIP_DTMF_SHORTINFO);
+ else if (!strcasecmp(v->value, "auto"))
+ ast_set_flag(&flags[0], SIP_DTMF_AUTO);
+ else {
+ ast_log(LOG_WARNING, "Unknown dtmf mode '%s' on line %d, using rfc2833\n", v->value, v->lineno);
+ ast_set_flag(&flags[0], SIP_DTMF_RFC2833);
+ }
+ } else if (!strcasecmp(v->name, "nat")) {
+ ast_set_flag(&mask[0], SIP_NAT);
+ ast_clear_flag(&flags[0], SIP_NAT);
+ if (!strcasecmp(v->value, "never"))
+ ast_set_flag(&flags[0], SIP_NAT_NEVER);
+ else if (!strcasecmp(v->value, "route"))
+ ast_set_flag(&flags[0], SIP_NAT_ROUTE);
+ else if (ast_true(v->value))
+ ast_set_flag(&flags[0], SIP_NAT_ALWAYS);
+ else
+ ast_set_flag(&flags[0], SIP_NAT_RFC3581);
+ } else if (!strcasecmp(v->name, "canreinvite")) {
+ ast_set_flag(&mask[0], SIP_REINVITE);
+ ast_clear_flag(&flags[0], SIP_REINVITE);
+ if (ast_true(v->value)) {
+ ast_set_flag(&flags[0], SIP_CAN_REINVITE | SIP_CAN_REINVITE_NAT);
+ } else if (!ast_false(v->value)) {
+ char buf[64];
+ char *word, *next = buf;
+
+ ast_copy_string(buf, v->value, sizeof(buf));
+ while ((word = strsep(&next, ","))) {
+ if (!strcasecmp(word, "update")) {
+ ast_set_flag(&flags[0], SIP_REINVITE_UPDATE | SIP_CAN_REINVITE);
+ } else if (!strcasecmp(word, "nonat")) {
+ ast_set_flag(&flags[0], SIP_CAN_REINVITE);
+ ast_clear_flag(&flags[0], SIP_CAN_REINVITE_NAT);
+ } else {
+ ast_log(LOG_WARNING, "Unknown canreinvite mode '%s' on line %d\n", v->value, v->lineno);
+ }
+ }
+ }
+ } else if (!strcasecmp(v->name, "insecure")) {
+ ast_set_flag(&mask[0], SIP_INSECURE);
+ ast_clear_flag(&flags[0], SIP_INSECURE);
+ set_insecure_flags(&flags[0], v->value, v->lineno);
+ } else if (!strcasecmp(v->name, "progressinband")) {
+ ast_set_flag(&mask[0], SIP_PROG_INBAND);
+ ast_clear_flag(&flags[0], SIP_PROG_INBAND);
+ if (ast_true(v->value))
+ ast_set_flag(&flags[0], SIP_PROG_INBAND_YES);
+ else if (strcasecmp(v->value, "never"))
+ ast_set_flag(&flags[0], SIP_PROG_INBAND_NO);
+ } else if (!strcasecmp(v->name, "promiscredir")) {
+ ast_set_flag(&mask[0], SIP_PROMISCREDIR);
+ ast_set2_flag(&flags[0], ast_true(v->value), SIP_PROMISCREDIR);
+ } else if (!strcasecmp(v->name, "videosupport")) {
+ ast_set_flag(&mask[1], SIP_PAGE2_VIDEOSUPPORT);
+ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_VIDEOSUPPORT);
+ } else if (!strcasecmp(v->name, "textsupport")) {
+ ast_set_flag(&mask[1], SIP_PAGE2_TEXTSUPPORT);
+ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_TEXTSUPPORT);
+ res = 1;
+ } else if (!strcasecmp(v->name, "allowoverlap")) {
+ ast_set_flag(&mask[1], SIP_PAGE2_ALLOWOVERLAP);
+ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_ALLOWOVERLAP);
+ } else if (!strcasecmp(v->name, "allowsubscribe")) {
+ ast_set_flag(&mask[1], SIP_PAGE2_ALLOWSUBSCRIBE);
+ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_ALLOWSUBSCRIBE);
+ } else if (!strcasecmp(v->name, "t38pt_udptl")) {
+ ast_set_flag(&mask[1], SIP_PAGE2_T38SUPPORT_UDPTL);
+ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_T38SUPPORT_UDPTL);
+#ifdef WHEN_WE_HAVE_T38_FOR_OTHER_TRANSPORTS
+ } else if (!strcasecmp(v->name, "t38pt_rtp")) {
+ ast_set_flag(&mask[1], SIP_PAGE2_T38SUPPORT_RTP);
+ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_T38SUPPORT_RTP);
+ } else if (!strcasecmp(v->name, "t38pt_tcp")) {
+ ast_set_flag(&mask[1], SIP_PAGE2_T38SUPPORT_TCP);
+ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_T38SUPPORT_TCP);
+#endif
+ } else if (!strcasecmp(v->name, "rfc2833compensate")) {
+ ast_set_flag(&mask[1], SIP_PAGE2_RFC2833_COMPENSATE);
+ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_RFC2833_COMPENSATE);
+ } else if (!strcasecmp(v->name, "buggymwi")) {
+ ast_set_flag(&mask[1], SIP_PAGE2_BUGGY_MWI);
+ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_BUGGY_MWI);
+ } else
+ res = 0;
+
+ return res;
+}
+
+/*! \brief Add SIP domain to list of domains we are responsible for */
+static int add_sip_domain(const char *domain, const enum domain_mode mode, const char *context)
+{
+ struct domain *d;
+
+ if (ast_strlen_zero(domain)) {
+ ast_log(LOG_WARNING, "Zero length domain.\n");
+ return 1;
+ }
+
+ if (!(d = ast_calloc(1, sizeof(*d))))
+ return 0;
+
+ ast_copy_string(d->domain, domain, sizeof(d->domain));
+
+ if (!ast_strlen_zero(context))
+ ast_copy_string(d->context, context, sizeof(d->context));
+
+ d->mode = mode;
+
+ AST_LIST_LOCK(&domain_list);
+ AST_LIST_INSERT_TAIL(&domain_list, d, list);
+ AST_LIST_UNLOCK(&domain_list);
+
+ if (sipdebug)
+ ast_debug(1, "Added local SIP domain '%s'\n", domain);
+
+ return 1;
+}
+
+/*! \brief check_sip_domain: Check if domain part of uri is local to our server */
+static int check_sip_domain(const char *domain, char *context, size_t len)
+{
+ struct domain *d;
+ int result = 0;
+
+ AST_LIST_LOCK(&domain_list);
+ AST_LIST_TRAVERSE(&domain_list, d, list) {
+ if (strcasecmp(d->domain, domain))
+ continue;
+
+ if (len && !ast_strlen_zero(d->context))
+ ast_copy_string(context, d->context, len);
+
+ result = 1;
+ break;
+ }
+ AST_LIST_UNLOCK(&domain_list);
+
+ return result;
+}
+
+/*! \brief Clear our domain list (at reload) */
+static void clear_sip_domains(void)
+{
+ struct domain *d;
+
+ AST_LIST_LOCK(&domain_list);
+ while ((d = AST_LIST_REMOVE_HEAD(&domain_list, list)))
+ ast_free(d);
+ AST_LIST_UNLOCK(&domain_list);
+}
+
+
+/*! \brief Add realm authentication in list */
+static struct sip_auth *add_realm_authentication(struct sip_auth *authlist, const char *configuration, int lineno)
+{
+ char authcopy[256];
+ char *username=NULL, *realm=NULL, *secret=NULL, *md5secret=NULL;
+ char *stringp;
+ struct sip_auth *a, *b, *auth;
+
+ if (ast_strlen_zero(configuration))
+ return authlist;
+
+ ast_debug(1, "Auth config :: %s\n", configuration);
+
+ ast_copy_string(authcopy, configuration, sizeof(authcopy));
+ stringp = authcopy;
+
+ username = stringp;
+ realm = strrchr(stringp, '@');
+ if (realm)
+ *realm++ = '\0';
+ if (ast_strlen_zero(username) || ast_strlen_zero(realm)) {
+ ast_log(LOG_WARNING, "Format for authentication entry is user[:secret]@realm at line %d\n", lineno);
+ return authlist;
+ }
+ stringp = username;
+ username = strsep(&stringp, ":");
+ if (username) {
+ secret = strsep(&stringp, ":");
+ if (!secret) {
+ stringp = username;
+ md5secret = strsep(&stringp,"#");
+ }
+ }
+ if (!(auth = ast_calloc(1, sizeof(*auth))))
+ return authlist;
+
+ ast_copy_string(auth->realm, realm, sizeof(auth->realm));
+ ast_copy_string(auth->username, username, sizeof(auth->username));
+ if (secret)
+ ast_copy_string(auth->secret, secret, sizeof(auth->secret));
+ if (md5secret)
+ ast_copy_string(auth->md5secret, md5secret, sizeof(auth->md5secret));
+
+ /* find the end of the list */
+ for (b = NULL, a = authlist; a ; b = a, a = a->next)
+ ;
+ if (b)
+ b->next = auth; /* Add structure add end of list */
+ else
+ authlist = auth;
+
+ ast_verb(3, "Added authentication for realm %s\n", realm);
+
+ return authlist;
+
+}
+
+/*! \brief Clear realm authentication list (at reload) */
+static int clear_realm_authentication(struct sip_auth *authlist)
+{
+ struct sip_auth *a = authlist;
+ struct sip_auth *b;
+
+ while (a) {
+ b = a;
+ a = a->next;
+ ast_free(b);
+ }
+
+ return 1;
+}
+
+/*! \brief Find authentication for a specific realm */
+static struct sip_auth *find_realm_authentication(struct sip_auth *authlist, const char *realm)
+{
+ struct sip_auth *a;
+
+ for (a = authlist; a; a = a->next) {
+ if (!strcasecmp(a->realm, realm))
+ break;
+ }
+
+ return a;
+}
+
+/*!
+ * implement the servar config line
+ */
+static struct ast_variable *add_var(const char *buf, struct ast_variable *list)
+{
+ struct ast_variable *tmpvar = NULL;
+ char *varname = ast_strdupa(buf), *varval = NULL;
+
+ if ((varval = strchr(varname,'='))) {
+ *varval++ = '\0';
+ if ((tmpvar = ast_variable_new(varname, varval, ""))) {
+ tmpvar->next = list;
+ list = tmpvar;
+ }
+ }
+ return list;
+}
+
+/*! \brief Initiate a SIP user structure from configuration (configuration or realtime) */
+static struct sip_user *build_user(const char *name, struct ast_variable *v, int realtime)
+{
+ struct sip_user *user;
+ int format;
+ struct ast_ha *oldha = NULL;
+ struct ast_flags userflags[2] = {{(0)}};
+ struct ast_flags mask[2] = {{(0)}};
+
+
+ if (!(user = ast_calloc(1, sizeof(*user))))
+ return NULL;
+
+ suserobjs++;
+ ASTOBJ_INIT(user);
+ ast_copy_string(user->name, name, sizeof(user->name));
+ oldha = user->ha;
+ user->ha = NULL;
+ ast_copy_flags(&user->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY);
+ ast_copy_flags(&user->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ user->capability = global_capability;
+ user->allowtransfer = global_allowtransfer;
+ user->maxcallbitrate = default_maxcallbitrate;
+ user->autoframing = global_autoframing;
+ if (global_callcounter)
+ user->call_limit=999;
+ user->prefs = default_prefs;
+ user->stimer.st_mode_oper = global_st_mode; /* Session-Timers */
+ user->stimer.st_ref = global_st_refresher;
+ user->stimer.st_min_se = global_min_se;
+ user->stimer.st_max_se = global_max_se;
+
+ /* set default context */
+ strcpy(user->context, default_context);
+ strcpy(user->language, default_language);
+ strcpy(user->mohinterpret, default_mohinterpret);
+ strcpy(user->mohsuggest, default_mohsuggest);
+ for (; v; v = v->next) {
+ if (handle_common_options(&userflags[0], &mask[0], v))
+ continue;
+ if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(user->context, v->value, sizeof(user->context));
+ } else if (!strcasecmp(v->name, "subscribecontext")) {
+ ast_copy_string(user->subscribecontext, v->value, sizeof(user->subscribecontext));
+ } else if (!strcasecmp(v->name, "setvar")) {
+ user->chanvars = add_var(v->value, user->chanvars);
+ } else if (!strcasecmp(v->name, "permit") ||
+ !strcasecmp(v->name, "deny")) {
+ int ha_error = 0;
+
+ user->ha = ast_append_ha(v->name, v->value, user->ha, &ha_error);
+ if (ha_error)
+ ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value);
+ } else if (!strcasecmp(v->name, "allowtransfer")) {
+ user->allowtransfer = ast_true(v->value) ? TRANSFER_OPENFORALL : TRANSFER_CLOSED;
+ } else if (!strcasecmp(v->name, "secret")) {
+ ast_copy_string(user->secret, v->value, sizeof(user->secret));
+ } else if (!strcasecmp(v->name, "md5secret")) {
+ ast_copy_string(user->md5secret, v->value, sizeof(user->md5secret));
+ } else if (!strcasecmp(v->name, "callerid")) {
+ ast_callerid_split(v->value, user->cid_name, sizeof(user->cid_name), user->cid_num, sizeof(user->cid_num));
+ } else if (!strcasecmp(v->name, "fullname")) {
+ ast_copy_string(user->cid_name, v->value, sizeof(user->cid_name));
+ } else if (!strcasecmp(v->name, "cid_number")) {
+ ast_copy_string(user->cid_num, v->value, sizeof(user->cid_num));
+ } else if (!strcasecmp(v->name, "callgroup")) {
+ user->callgroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "pickupgroup")) {
+ user->pickupgroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "language")) {
+ ast_copy_string(user->language, v->value, sizeof(user->language));
+ } else if (!strcasecmp(v->name, "mohinterpret")) {
+ ast_copy_string(user->mohinterpret, v->value, sizeof(user->mohinterpret));
+ } else if (!strcasecmp(v->name, "mohsuggest")) {
+ ast_copy_string(user->mohsuggest, v->value, sizeof(user->mohsuggest));
+ } else if (!strcasecmp(v->name, "accountcode")) {
+ ast_copy_string(user->accountcode, v->value, sizeof(user->accountcode));
+ } else if (!strcasecmp(v->name, "callcounter")) {
+ user->call_limit = ast_true(v->value) ? 999 : 0;
+ } else if (!strcasecmp(v->name, "call-limit")) {
+ user->call_limit = atoi(v->value);
+ if (user->call_limit < 0)
+ user->call_limit = 0;
+ } else if (!strcasecmp(v->name, "amaflags")) {
+ format = ast_cdr_amaflags2int(v->value);
+ if (format < 0) {
+ ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
+ } else {
+ user->amaflags = format;
+ }
+ } else if (!strcasecmp(v->name, "allow")) {
+ int error = ast_parse_allow_disallow(&user->prefs, &user->capability, v->value, TRUE);
+ if (error)
+ ast_log(LOG_WARNING, "Codec configuration errors found in line %d : %s = %s\n", v->lineno, v->name, v->value);
+ } else if (!strcasecmp(v->name, "disallow")) {
+ int error = ast_parse_allow_disallow(&user->prefs, &user->capability, v->value, FALSE);
+ if (error)
+ ast_log(LOG_WARNING, "Codec configuration errors found in line %d : %s = %s\n", v->lineno, v->name, v->value);
+ } else if (!strcasecmp(v->name, "autoframing")) {
+ user->autoframing = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callingpres")) {
+ user->callingpres = ast_parse_caller_presentation(v->value);
+ if (user->callingpres == -1)
+ user->callingpres = atoi(v->value);
+ } else if (!strcasecmp(v->name, "maxcallbitrate")) {
+ user->maxcallbitrate = atoi(v->value);
+ if (user->maxcallbitrate < 0)
+ user->maxcallbitrate = default_maxcallbitrate;
+ } else if (!strcasecmp(v->name, "session-timers")) {
+ int i = (int) str2stmode(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config);
+ user->stimer.st_mode_oper = global_st_mode;
+ } else {
+ user->stimer.st_mode_oper = i;
+ }
+ } else if (!strcasecmp(v->name, "session-expires")) {
+ if (sscanf(v->value, "%d", &user->stimer.st_max_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config);
+ user->stimer.st_max_se = global_max_se;
+ }
+ } else if (!strcasecmp(v->name, "session-minse")) {
+ if (sscanf(v->value, "%d", &user->stimer.st_min_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config);
+ user->stimer.st_min_se = global_min_se;
+ }
+ if (user->stimer.st_min_se < 90) {
+ ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < 90 secs\n", v->value, v->lineno, config);
+ user->stimer.st_min_se = global_min_se;
+ }
+ } else if (!strcasecmp(v->name, "session-refresher")) {
+ int i = (int) str2strefresher(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config);
+ user->stimer.st_ref = global_st_refresher;
+ } else {
+ user->stimer.st_ref = i;
+ }
+ }
+
+ /* We can't just report unknown options here because this may be a
+ * type=friend entry. All user options are valid for a peer, but not
+ * the other way around. */
+ }
+ ast_copy_flags(&user->flags[0], &userflags[0], mask[0].flags);
+ ast_copy_flags(&user->flags[1], &userflags[1], mask[1].flags);
+ if (ast_test_flag(&user->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE))
+ global_allowsubscribe = TRUE; /* No global ban any more */
+ ast_free_ha(oldha);
+ return user;
+}
+
+/*! \brief Set peer defaults before configuring specific configurations */
+static void set_peer_defaults(struct sip_peer *peer)
+{
+ if (peer->expire == 0) {
+ /* Don't reset expire or port time during reload
+ if we have an active registration
+ */
+ peer->expire = -1;
+ peer->pokeexpire = -1;
+ peer->addr.sin_port = htons(STANDARD_SIP_PORT);
+ }
+ ast_copy_flags(&peer->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY);
+ ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ strcpy(peer->context, default_context);
+ strcpy(peer->subscribecontext, default_subscribecontext);
+ strcpy(peer->language, default_language);
+ strcpy(peer->mohinterpret, default_mohinterpret);
+ strcpy(peer->mohsuggest, default_mohsuggest);
+ peer->addr.sin_family = AF_INET;
+ peer->defaddr.sin_family = AF_INET;
+ peer->capability = global_capability;
+ peer->maxcallbitrate = default_maxcallbitrate;
+ peer->rtptimeout = global_rtptimeout;
+ peer->rtpholdtimeout = global_rtpholdtimeout;
+ peer->rtpkeepalive = global_rtpkeepalive;
+ peer->allowtransfer = global_allowtransfer;
+ peer->autoframing = global_autoframing;
+ peer->qualifyfreq = global_qualifyfreq;
+ if (global_callcounter)
+ peer->call_limit=999;
+ strcpy(peer->vmexten, default_vmexten);
+ peer->secret[0] = '\0';
+ peer->md5secret[0] = '\0';
+ peer->cid_num[0] = '\0';
+ peer->cid_name[0] = '\0';
+ peer->fromdomain[0] = '\0';
+ peer->fromuser[0] = '\0';
+ peer->regexten[0] = '\0';
+ peer->callgroup = 0;
+ peer->pickupgroup = 0;
+ peer->maxms = default_qualify;
+ peer->prefs = default_prefs;
+ peer->socket.type = SIP_TRANSPORT_UDP;
+ peer->socket.fd = -1;
+ peer->stimer.st_mode_oper = global_st_mode; /* Session-Timers */
+ peer->stimer.st_ref = global_st_refresher;
+ peer->stimer.st_min_se = global_min_se;
+ peer->stimer.st_max_se = global_max_se;
+ peer->timer_t1 = global_t1;
+ peer->timer_b = global_timer_b;
+ clear_peer_mailboxes(peer);
+}
+
+/*! \brief Create temporary peer (used in autocreatepeer mode) */
+static struct sip_peer *temp_peer(const char *name)
+{
+ struct sip_peer *peer;
+
+ if (!(peer = ast_calloc(1, sizeof(*peer))))
+ return NULL;
+
+ apeerobjs++;
+ ASTOBJ_INIT(peer);
+ set_peer_defaults(peer);
+
+ ast_copy_string(peer->name, name, sizeof(peer->name));
+
+ peer->selfdestruct = TRUE;
+ peer->host_dynamic = TRUE;
+ peer->prefs = default_prefs;
+ reg_source_db(peer);
+
+ return peer;
+}
+
+static void add_peer_mailboxes(struct sip_peer *peer, const char *value)
+{
+ char *next, *mbox, *context;
+
+ next = ast_strdupa(value);
+
+ while ((mbox = context = strsep(&next, ","))) {
+ struct sip_mailbox *mailbox;
+
+ if (!(mailbox = ast_calloc(1, sizeof(*mailbox))))
+ continue;
+
+ strsep(&context, "@");
+ if (ast_strlen_zero(mbox)) {
+ ast_free(mailbox);
+ continue;
+ }
+ mailbox->mailbox = ast_strdup(mbox);
+ mailbox->context = ast_strdup(context);
+
+ AST_LIST_INSERT_TAIL(&peer->mailboxes, mailbox, entry);
+ }
+}
+
+/*! \brief Build peer from configuration (file or realtime static/dynamic) */
+static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime)
+{
+ struct sip_peer *peer = NULL;
+ struct ast_ha *oldha = NULL;
+ int found=0;
+ int firstpass=1;
+ int format=0; /* Ama flags */
+ time_t regseconds = 0;
+ struct ast_flags peerflags[2] = {{(0)}};
+ struct ast_flags mask[2] = {{(0)}};
+ char callback[256] = "";
+ const char *srvlookup = NULL;
+
+ if (!realtime)
+ /* Note we do NOT use find_peer here, to avoid realtime recursion */
+ /* We also use a case-sensitive comparison (unlike find_peer) so
+ that case changes made to the peer name will be properly handled
+ during reload
+ */
+ peer = ASTOBJ_CONTAINER_FIND_UNLINK_FULL(&peerl, name, name, 0, 0, strcmp);
+
+ if (peer) {
+ /* Already in the list, remove it and it will be added back (or FREE'd) */
+ found++;
+ if (!(peer->objflags & ASTOBJ_FLAG_MARKED))
+ firstpass = 0;
+ } else {
+ if (!(peer = ast_calloc(1, sizeof(*peer))))
+ return NULL;
+
+ if (realtime) {
+ rpeerobjs++;
+ ast_debug(3,"-REALTIME- peer built. Name: %s. Peer objects: %d\n", name, rpeerobjs);
+ } else
+ speerobjs++;
+ ASTOBJ_INIT(peer);
+ }
+ /* Note that our peer HAS had its reference count incrased */
+ if (firstpass) {
+ peer->lastmsgssent = -1;
+ oldha = peer->ha;
+ peer->ha = NULL;
+ set_peer_defaults(peer); /* Set peer defaults */
+ }
+ if (!found && name)
+ ast_copy_string(peer->name, name, sizeof(peer->name));
+
+ /* If we have channel variables, remove them (reload) */
+ if (peer->chanvars) {
+ ast_variables_destroy(peer->chanvars);
+ peer->chanvars = NULL;
+ /* XXX should unregister ? */
+ }
+
+ /* If we have realm authentication information, remove them (reload) */
+ clear_realm_authentication(peer->auth);
+ peer->auth = NULL;
+
+ for (; v || ((v = alt) && !(alt=NULL)); v = v->next) {
+ if (handle_common_options(&peerflags[0], &mask[0], v))
+ continue;
+ if (!strcasecmp(v->name, "transport")) {
+ if (!strcasecmp(v->value, "udp"))
+ peer->socket.type = SIP_TRANSPORT_UDP;
+ else if (!strcasecmp(v->value, "tcp"))
+ peer->socket.type = SIP_TRANSPORT_TCP;
+ else if (!strcasecmp(v->value, "tls"))
+ peer->socket.type = SIP_TRANSPORT_TLS;
+ } else if (realtime && !strcasecmp(v->name, "regseconds")) {
+ ast_get_time_t(v->value, &regseconds, 0, NULL);
+ } else if (realtime && !strcasecmp(v->name, "ipaddr") && !ast_strlen_zero(v->value) ) {
+ inet_aton(v->value, &(peer->addr.sin_addr));
+ } else if (realtime && !strcasecmp(v->name, "name"))
+ ast_copy_string(peer->name, v->value, sizeof(peer->name));
+ else if (realtime && !strcasecmp(v->name, "fullcontact")) {
+ ast_copy_string(peer->fullcontact, v->value, sizeof(peer->fullcontact));
+ peer->rt_fromcontact = TRUE;
+ } else if (!strcasecmp(v->name, "secret"))
+ ast_copy_string(peer->secret, v->value, sizeof(peer->secret));
+ else if (!strcasecmp(v->name, "md5secret"))
+ ast_copy_string(peer->md5secret, v->value, sizeof(peer->md5secret));
+ else if (!strcasecmp(v->name, "auth"))
+ peer->auth = add_realm_authentication(peer->auth, v->value, v->lineno);
+ else if (!strcasecmp(v->name, "callerid")) {
+ ast_callerid_split(v->value, peer->cid_name, sizeof(peer->cid_name), peer->cid_num, sizeof(peer->cid_num));
+ } else if (!strcasecmp(v->name, "fullname")) {
+ ast_copy_string(peer->cid_name, v->value, sizeof(peer->cid_name));
+ } else if (!strcasecmp(v->name, "cid_number")) {
+ ast_copy_string(peer->cid_num, v->value, sizeof(peer->cid_num));
+ } else if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(peer->context, v->value, sizeof(peer->context));
+ } else if (!strcasecmp(v->name, "subscribecontext")) {
+ ast_copy_string(peer->subscribecontext, v->value, sizeof(peer->subscribecontext));
+ } else if (!strcasecmp(v->name, "fromdomain")) {
+ ast_copy_string(peer->fromdomain, v->value, sizeof(peer->fromdomain));
+ } else if (!strcasecmp(v->name, "usereqphone")) {
+ ast_set2_flag(&peer->flags[0], ast_true(v->value), SIP_USEREQPHONE);
+ } else if (!strcasecmp(v->name, "fromuser")) {
+ ast_copy_string(peer->fromuser, v->value, sizeof(peer->fromuser));
+ } else if (!strcasecmp(v->name, "outboundproxy")) {
+ char *port, *next, *force, *proxyname;
+ int forceopt = FALSE;
+ /* Set peer channel variable */
+ next = proxyname = ast_strdupa(v->value);
+ if ((port = strchr(proxyname, ':'))) {
+ *port++ = '\0';
+ next = port;
+ }
+ if ((force = strchr(next, ','))) {
+ *force++ = '\0';
+ forceopt = strcmp(force, "force");
+ }
+ /* Allocate proxy object */
+ peer->outboundproxy = proxy_allocate(proxyname, port, forceopt);
+ } else if (!strcasecmp(v->name, "host")) {
+ if (!strcasecmp(v->value, "dynamic")) {
+ /* They'll register with us */
+ if (!found || !peer->host_dynamic) {
+ /* Initialize stuff if this is a new peer, or if it used to
+ * not be dynamic before the reload. */
+ memset(&peer->addr.sin_addr, 0, 4);
+ if (peer->addr.sin_port) {
+ /* If we've already got a port, make it the default rather than absolute */
+ peer->defaddr.sin_port = peer->addr.sin_port;
+ peer->addr.sin_port = 0;
+ }
+ }
+ peer->host_dynamic = TRUE;
+ } else {
+ /* Non-dynamic. Make sure we become that way if we're not */
+ if (peer->expire > -1)
+ ast_sched_del(sched, peer->expire);
+ peer->expire = -1;
+ peer->host_dynamic = FALSE;
+ srvlookup = v->value;
+ }
+ } else if (!strcasecmp(v->name, "defaultip")) {
+ if (ast_get_ip(&peer->defaddr, v->value)) {
+ unref_peer(peer);
+ return NULL;
+ }
+ } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) {
+ int ha_error = 0;
+
+ peer->ha = ast_append_ha(v->name, v->value, peer->ha, &ha_error);
+ if (ha_error)
+ ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value);
+ } else if (!strcasecmp(v->name, "port")) {
+ if (!realtime && peer->host_dynamic)
+ peer->defaddr.sin_port = htons(atoi(v->value));
+ else
+ peer->addr.sin_port = htons(atoi(v->value));
+ } else if (!strcasecmp(v->name, "callingpres")) {
+ peer->callingpres = ast_parse_caller_presentation(v->value);
+ if (peer->callingpres == -1)
+ peer->callingpres = atoi(v->value);
+ } else if (!strcasecmp(v->name, "username") | !strcmp(v->name, "defaultuser")) { /* "username" is deprecated */
+ ast_copy_string(peer->username, v->value, sizeof(peer->username));
+ } else if (!strcasecmp(v->name, "language")) {
+ ast_copy_string(peer->language, v->value, sizeof(peer->language));
+ } else if (!strcasecmp(v->name, "regexten")) {
+ ast_copy_string(peer->regexten, v->value, sizeof(peer->regexten));
+ } else if (!strcasecmp(v->name, "callbackextension")) {
+ ast_copy_string(callback, v->value, sizeof(callback));
+ } else if (!strcasecmp(v->name, "callcounter")) {
+ peer->call_limit = ast_true(v->value) ? 999 : 0;
+ } else if (!strcasecmp(v->name, "call-limit")) {
+ peer->call_limit = atoi(v->value);
+ if (peer->call_limit < 0)
+ peer->call_limit = 0;
+ } else if (!strcasecmp(v->name, "busylevel")) {
+ peer->busy_level = atoi(v->value);
+ if (peer->busy_level < 0)
+ peer->busy_level = 0;
+ } else if (!strcasecmp(v->name, "amaflags")) {
+ format = ast_cdr_amaflags2int(v->value);
+ if (format < 0) {
+ ast_log(LOG_WARNING, "Invalid AMA Flags for peer: %s at line %d\n", v->value, v->lineno);
+ } else {
+ peer->amaflags = format;
+ }
+ } else if (!strcasecmp(v->name, "accountcode")) {
+ ast_copy_string(peer->accountcode, v->value, sizeof(peer->accountcode));
+ } else if (!strcasecmp(v->name, "mohinterpret")) {
+ ast_copy_string(peer->mohinterpret, v->value, sizeof(peer->mohinterpret));
+ } else if (!strcasecmp(v->name, "mohsuggest")) {
+ ast_copy_string(peer->mohsuggest, v->value, sizeof(peer->mohsuggest));
+ } else if (!strcasecmp(v->name, "mailbox")) {
+ add_peer_mailboxes(peer, v->value);
+ } else if (!strcasecmp(v->name, "subscribemwi")) {
+ ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_SUBSCRIBEMWIONLY);
+ } else if (!strcasecmp(v->name, "vmexten")) {
+ ast_copy_string(peer->vmexten, v->value, sizeof(peer->vmexten));
+ } else if (!strcasecmp(v->name, "callgroup")) {
+ peer->callgroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "allowtransfer")) {
+ peer->allowtransfer = ast_true(v->value) ? TRANSFER_OPENFORALL : TRANSFER_CLOSED;
+ } else if (!strcasecmp(v->name, "pickupgroup")) {
+ peer->pickupgroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "allow")) {
+ int error = ast_parse_allow_disallow(&peer->prefs, &peer->capability, v->value, TRUE);
+ if (error)
+ ast_log(LOG_WARNING, "Codec configuration errors found in line %d : %s = %s\n", v->lineno, v->name, v->value);
+ } else if (!strcasecmp(v->name, "disallow")) {
+ int error = ast_parse_allow_disallow(&peer->prefs, &peer->capability, v->value, FALSE);
+ if (error)
+ ast_log(LOG_WARNING, "Codec configuration errors found in line %d : %s = %s\n", v->lineno, v->name, v->value);
+ } else if (!strcasecmp(v->name, "registertrying")) {
+ ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_REGISTERTRYING);
+ } else if (!strcasecmp(v->name, "autoframing")) {
+ peer->autoframing = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "rtptimeout")) {
+ if ((sscanf(v->value, "%d", &peer->rtptimeout) != 1) || (peer->rtptimeout < 0)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid RTP hold time at line %d. Using default.\n", v->value, v->lineno);
+ peer->rtptimeout = global_rtptimeout;
+ }
+ } else if (!strcasecmp(v->name, "rtpholdtimeout")) {
+ if ((sscanf(v->value, "%d", &peer->rtpholdtimeout) != 1) || (peer->rtpholdtimeout < 0)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid RTP hold time at line %d. Using default.\n", v->value, v->lineno);
+ peer->rtpholdtimeout = global_rtpholdtimeout;
+ }
+ } else if (!strcasecmp(v->name, "rtpkeepalive")) {
+ if ((sscanf(v->value, "%d", &peer->rtpkeepalive) != 1) || (peer->rtpkeepalive < 0)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid RTP keepalive time at line %d. Using default.\n", v->value, v->lineno);
+ peer->rtpkeepalive = global_rtpkeepalive;
+ }
+ } else if (!strcasecmp(v->name, "timert1")) {
+ if ((sscanf(v->value, "%d", &peer->timer_t1) != 1) || (peer->timer_t1 < 0)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid T1 time at line %d. Using default.\n", v->value, v->lineno);
+ peer->timer_t1 = global_t1;
+ }
+ } else if (!strcasecmp(v->name, "timerb")) {
+ if ((sscanf(v->value, "%d", &peer->timer_b) != 1) || (peer->timer_b < 0)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid Timer B time at line %d. Using default.\n", v->value, v->lineno);
+ peer->timer_b = global_timer_b;
+ }
+ } else if (!strcasecmp(v->name, "setvar")) {
+ peer->chanvars = add_var(v->value, peer->chanvars);
+ } else if (!strcasecmp(v->name, "qualify")) {
+ if (!strcasecmp(v->value, "no")) {
+ peer->maxms = 0;
+ } else if (!strcasecmp(v->value, "yes")) {
+ peer->maxms = default_qualify ? default_qualify : DEFAULT_MAXMS;
+ } else if (sscanf(v->value, "%d", &peer->maxms) != 1) {
+ ast_log(LOG_WARNING, "Qualification of peer '%s' should be 'yes', 'no', or a number of milliseconds at line %d of sip.conf\n", peer->name, v->lineno);
+ peer->maxms = 0;
+ }
+ } else if (!strcasecmp(v->name, "qualifyfreq")) {
+ int i;
+ if (sscanf(v->value, "%d", &i) == 1)
+ peer->qualifyfreq = i * 1000;
+ else {
+ ast_log(LOG_WARNING, "Invalid qualifyfreq number '%s' at line %d of %s\n",v->value, v->lineno, config);
+ peer->qualifyfreq = global_qualifyfreq;
+ }
+ } else if (!strcasecmp(v->name, "maxcallbitrate")) {
+ peer->maxcallbitrate = atoi(v->value);
+ if (peer->maxcallbitrate < 0)
+ peer->maxcallbitrate = default_maxcallbitrate;
+ } else if (!strcasecmp(v->name, "session-timers")) {
+ int i = (int) str2stmode(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config);
+ peer->stimer.st_mode_oper = global_st_mode;
+ } else {
+ peer->stimer.st_mode_oper = i;
+ }
+ } else if (!strcasecmp(v->name, "session-expires")) {
+ if (sscanf(v->value, "%d", &peer->stimer.st_max_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config);
+ peer->stimer.st_max_se = global_max_se;
+ }
+ } else if (!strcasecmp(v->name, "session-minse")) {
+ if (sscanf(v->value, "%d", &peer->stimer.st_min_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config);
+ peer->stimer.st_min_se = global_min_se;
+ }
+ if (peer->stimer.st_min_se < 90) {
+ ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < 90 secs\n", v->value, v->lineno, config);
+ peer->stimer.st_min_se = global_min_se;
+ }
+ } else if (!strcasecmp(v->name, "session-refresher")) {
+ int i = (int) str2strefresher(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config);
+ peer->stimer.st_ref = global_st_refresher;
+ } else {
+ peer->stimer.st_ref = i;
+ }
+ }
+ }
+
+ if (srvlookup) {
+ if (ast_get_ip_or_srv(&peer->addr, srvlookup,
+ global_srvlookup ?
+ ((peer->socket.type & SIP_TRANSPORT_UDP) ? "_sip._udp" :
+ (peer->socket.type & SIP_TRANSPORT_TCP) ? "_sip._tcp" :
+ "_sip._tls")
+ : NULL)) {
+ unref_peer(peer);
+ return NULL;
+ }
+
+ ast_copy_string(peer->tohost, srvlookup, sizeof(peer->tohost));
+ }
+
+ if (!peer->addr.sin_port)
+ peer->addr.sin_port = htons(((peer->socket.type & SIP_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT));
+
+ if (!peer->socket.port)
+ peer->socket.port = htons(((peer->socket.type & SIP_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT));
+
+ if (!sip_cfg.ignore_regexpire && peer->host_dynamic && realtime) {
+ time_t nowtime = time(NULL);
+
+ if ((nowtime - regseconds) > 0) {
+ destroy_association(peer);
+ memset(&peer->addr, 0, sizeof(peer->addr));
+ ast_debug(1, "Bah, we're expired (%d/%d/%d)!\n", (int)(nowtime - regseconds), (int)regseconds, (int)nowtime);
+ }
+ }
+ ast_copy_flags(&peer->flags[0], &peerflags[0], mask[0].flags);
+ ast_copy_flags(&peer->flags[1], &peerflags[1], mask[1].flags);
+ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE))
+ global_allowsubscribe = TRUE; /* No global ban any more */
+ if (!found && peer->host_dynamic && !peer->is_realtime)
+ reg_source_db(peer);
+
+ /* If they didn't request that MWI is sent *only* on subscribe, go ahead and
+ * subscribe to it now. */
+ if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_SUBSCRIBEMWIONLY) &&
+ !AST_LIST_EMPTY(&peer->mailboxes)) {
+ add_peer_mwi_subs(peer);
+ /* Send MWI from the event cache only. This is so we can send initial
+ * MWI if app_voicemail got loaded before chan_sip. If it is the other
+ * way, then we will get events when app_voicemail gets loaded. */
+ sip_send_mwi_to_peer(peer, NULL, 1);
+ }
+
+ ASTOBJ_UNMARK(peer);
+
+ ast_free_ha(oldha);
+ if (!ast_strlen_zero(callback)) { /* build string from peer info */
+ char *reg_string;
+
+ asprintf(&reg_string, "%s:%s@%s/%s", peer->username, peer->secret, peer->tohost, callback);
+ if (reg_string) {
+ sip_register(reg_string, 0); /* XXX TODO: count in registry_count */
+ ast_free(reg_string);
+ }
+ }
+ return peer;
+}
+
+/*! \brief Re-read SIP.conf config file
+\note This function reloads all config data, except for
+ active peers (with registrations). They will only
+ change configuration data at restart, not at reload.
+ SIP debug and recordhistory state will not change
+ */
+static int reload_config(enum channelreloadreason reason)
+{
+ struct ast_config *cfg, *ucfg;
+ struct ast_variable *v;
+ struct sip_peer *peer;
+ struct sip_user *user;
+ char *cat, *stringp, *context, *oldregcontext;
+ char newcontexts[AST_MAX_CONTEXT], oldcontexts[AST_MAX_CONTEXT];
+ struct ast_flags dummy[2];
+ struct ast_flags config_flags = { reason == CHANNEL_MODULE_LOAD ? 0 : CONFIG_FLAG_FILEUNCHANGED };
+ int auto_sip_domains = FALSE;
+ struct sockaddr_in old_bindaddr = bindaddr;
+ int registry_count = 0, peer_count = 0, user_count = 0;
+
+ cfg = ast_config_load(config, config_flags);
+
+ /* We *must* have a config file otherwise stop immediately */
+ if (!cfg) {
+ ast_log(LOG_NOTICE, "Unable to load config %s\n", config);
+ return -1;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
+ ucfg = ast_config_load("users.conf", config_flags);
+ if (ucfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 1;
+ /* Must reread both files, because one changed */
+ ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
+ cfg = ast_config_load(config, config_flags);
+ } else {
+ ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
+ ucfg = ast_config_load("users.conf", config_flags);
+ }
+
+ /* Initialize tcp sockets */
+ memset(&sip_tcp_desc.sin, 0, sizeof(sip_tcp_desc.sin));
+ memset(&sip_tls_desc.sin, 0, sizeof(sip_tls_desc.sin));
+
+ sip_tcp_desc.sin.sin_family = AF_INET;
+
+ sip_tcp_desc.sin.sin_port = htons(STANDARD_SIP_PORT);
+ sip_tls_desc.sin.sin_port = htons(STANDARD_TLS_PORT);
+
+ if (reason != CHANNEL_MODULE_LOAD) {
+ ast_debug(4, "--------------- SIP reload started\n");
+
+ clear_realm_authentication(authl);
+ clear_sip_domains();
+ authl = NULL;
+
+ /* First, destroy all outstanding registry calls */
+ /* This is needed, since otherwise active registry entries will not be destroyed */
+ ASTOBJ_CONTAINER_TRAVERSE(&regl, 1, do {
+ ASTOBJ_RDLOCK(iterator);
+ if (iterator->call) {
+ ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", iterator->username, iterator->hostname);
+ /* This will also remove references to the registry */
+ iterator->call = sip_destroy(iterator->call);
+ }
+ ASTOBJ_UNLOCK(iterator);
+
+ } while(0));
+
+ /* Then, actually destroy users and registry */
+ ASTOBJ_CONTAINER_DESTROYALL(&userl, sip_destroy_user);
+ ast_debug(4, "--------------- Done destroying user list\n");
+ ASTOBJ_CONTAINER_DESTROYALL(&regl, sip_registry_destroy);
+ ast_debug(4, "--------------- Done destroying registry list\n");
+ ASTOBJ_CONTAINER_MARKALL(&peerl);
+ }
+
+ default_tls_cfg.certfile = ast_strdup(AST_CERTFILE); /*XXX Not sure if this is useful */
+ default_tls_cfg.cipher = ast_strdup("");
+ default_tls_cfg.cafile = ast_strdup("");
+ default_tls_cfg.capath = ast_strdup("");
+
+ /* Initialize copy of current global_regcontext for later use in removing stale contexts */
+ ast_copy_string(oldcontexts, global_regcontext, sizeof(oldcontexts));
+ oldregcontext = oldcontexts;
+
+ /* Clear all flags before setting default values */
+ /* Preserve debugging settings for console */
+ sipdebug &= sip_debug_console;
+ ast_clear_flag(&global_flags[0], AST_FLAGS_ALL);
+ ast_clear_flag(&global_flags[1], AST_FLAGS_ALL);
+
+ /* Reset IP addresses */
+ memset(&bindaddr, 0, sizeof(bindaddr));
+ memset(&stunaddr, 0, sizeof(stunaddr));
+ memset(&internip, 0, sizeof(internip));
+ /* Free memory for local network address mask */
+ ast_free_ha(localaddr);
+ memset(&localaddr, 0, sizeof(localaddr));
+ memset(&externip, 0, sizeof(externip));
+ memset(&default_prefs, 0 , sizeof(default_prefs));
+ memset(&global_outboundproxy, 0, sizeof(struct sip_proxy));
+ global_outboundproxy.ip.sin_port = htons(STANDARD_SIP_PORT);
+ global_outboundproxy.ip.sin_family = AF_INET; /* Type of address: IPv4 */
+ ourport_tcp = STANDARD_SIP_PORT;
+ ourport_tls = STANDARD_TLS_PORT;
+ bindaddr.sin_port = htons(STANDARD_SIP_PORT);
+ externip.sin_port = htons(STANDARD_SIP_PORT);
+ global_srvlookup = DEFAULT_SRVLOOKUP;
+ global_tos_sip = DEFAULT_TOS_SIP;
+ global_tos_audio = DEFAULT_TOS_AUDIO;
+ global_tos_video = DEFAULT_TOS_VIDEO;
+ global_tos_text = DEFAULT_TOS_TEXT;
+ global_cos_sip = DEFAULT_COS_SIP;
+ global_cos_audio = DEFAULT_COS_AUDIO;
+ global_cos_video = DEFAULT_COS_VIDEO;
+ global_cos_text = DEFAULT_COS_TEXT;
+
+ externhost[0] = '\0'; /* External host name (for behind NAT DynDNS support) */
+ externexpire = 0; /* Expiration for DNS re-issuing */
+ externrefresh = 10;
+
+ /* Reset channel settings to default before re-configuring */
+ allow_external_domains = DEFAULT_ALLOW_EXT_DOM; /* Allow external invites */
+ global_regcontext[0] = '\0';
+ global_regextenonqualify = DEFAULT_REGEXTENONQUALIFY;
+ expiry = DEFAULT_EXPIRY;
+ global_notifyringing = DEFAULT_NOTIFYRINGING;
+ global_limitonpeers = FALSE; /*!< Match call limit on peers only */
+ global_notifyhold = FALSE; /*!< Keep track of hold status for a peer */
+ global_directrtpsetup = FALSE; /* Experimental feature, disabled by default */
+ global_alwaysauthreject = 0;
+ global_allowsubscribe = FALSE;
+ snprintf(global_useragent, sizeof(global_useragent), "%s %s", DEFAULT_USERAGENT, ast_get_version());
+ snprintf(global_sdpsession, sizeof(global_sdpsession), "%s %s", DEFAULT_SDPSESSION, ast_get_version());
+ snprintf(global_sdpowner, sizeof(global_sdpowner), "%s", DEFAULT_SDPOWNER);
+ ast_copy_string(default_notifymime, DEFAULT_NOTIFYMIME, sizeof(default_notifymime));
+ ast_copy_string(global_realm, S_OR(ast_config_AST_SYSTEM_NAME, DEFAULT_REALM), sizeof(global_realm));
+ ast_copy_string(default_callerid, DEFAULT_CALLERID, sizeof(default_callerid));
+ compactheaders = DEFAULT_COMPACTHEADERS;
+ global_reg_timeout = DEFAULT_REGISTRATION_TIMEOUT;
+ global_regattempts_max = 0;
+ pedanticsipchecking = DEFAULT_PEDANTIC;
+ autocreatepeer = DEFAULT_AUTOCREATEPEER;
+ global_autoframing = 0;
+ global_allowguest = DEFAULT_ALLOWGUEST;
+ global_callcounter = DEFAULT_CALLCOUNTER;
+ global_match_auth_username = FALSE; /*!< Match auth username if available instead of From: Default off. */
+ global_rtptimeout = 0;
+ global_rtpholdtimeout = 0;
+ global_rtpkeepalive = 0;
+ global_allowtransfer = TRANSFER_OPENFORALL; /* Merrily accept all transfers by default */
+ global_rtautoclear = 120;
+ ast_set_flag(&global_flags[1], SIP_PAGE2_ALLOWSUBSCRIBE); /* Default for peers, users: TRUE */
+ ast_set_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP); /* Default for peers, users: TRUE */
+ sip_cfg.peer_rtupdate = TRUE;
+
+ global_st_mode = SESSION_TIMER_MODE_ACCEPT; /* Session-Timers */
+ global_st_refresher = SESSION_TIMER_REFRESHER_UAS;
+ global_min_se = DEFAULT_MIN_SE;
+ global_max_se = DEFAULT_MAX_SE;
+
+ /* Initialize some reasonable defaults at SIP reload (used both for channel and as default for peers and users */
+ ast_copy_string(default_context, DEFAULT_CONTEXT, sizeof(default_context));
+ default_subscribecontext[0] = '\0';
+ default_language[0] = '\0';
+ default_fromdomain[0] = '\0';
+ default_qualify = DEFAULT_QUALIFY;
+ default_maxcallbitrate = DEFAULT_MAX_CALL_BITRATE;
+ ast_copy_string(default_mohinterpret, DEFAULT_MOHINTERPRET, sizeof(default_mohinterpret));
+ ast_copy_string(default_mohsuggest, DEFAULT_MOHSUGGEST, sizeof(default_mohsuggest));
+ ast_copy_string(default_vmexten, DEFAULT_VMEXTEN, sizeof(default_vmexten));
+ ast_set_flag(&global_flags[0], SIP_DTMF_RFC2833); /*!< Default DTMF setting: RFC2833 */
+ ast_set_flag(&global_flags[0], SIP_NAT_RFC3581); /*!< NAT support if requested by device with rport */
+ ast_set_flag(&global_flags[0], SIP_CAN_REINVITE); /*!< Allow re-invites */
+
+ /* Debugging settings, always default to off */
+ dumphistory = FALSE;
+ recordhistory = FALSE;
+ sipdebug &= ~sip_debug_config;
+
+ /* Misc settings for the channel */
+ global_relaxdtmf = FALSE;
+ global_callevents = FALSE;
+ global_t1 = SIP_TIMER_T1;
+ global_timer_b = 64 * SIP_TIMER_T1;
+ global_t1min = DEFAULT_T1MIN;
+ global_qualifyfreq = DEFAULT_QUALIFYFREQ;
+
+ global_matchexterniplocally = FALSE;
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ ast_clear_flag(&global_flags[1], SIP_PAGE2_VIDEOSUPPORT);
+ ast_clear_flag(&global_flags[1], SIP_PAGE2_TEXTSUPPORT);
+
+ /* Read the [general] config section of sip.conf (or from realtime config) */
+ for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
+ if (handle_common_options(&global_flags[0], &dummy[0], v))
+ continue;
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
+ continue;
+
+ /* Create the dialogs list */
+ if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(default_context, v->value, sizeof(default_context));
+ } else if (!strcasecmp(v->name, "subscribecontext")) {
+ ast_copy_string(default_subscribecontext, v->value, sizeof(default_subscribecontext));
+ } else if (!strcasecmp(v->name, "callcounter")) {
+ global_callcounter = ast_true(v->value) ? 1 : 0;
+ } else if (!strcasecmp(v->name, "allowguest")) {
+ global_allowguest = ast_true(v->value) ? 1 : 0;
+ } else if (!strcasecmp(v->name, "realm")) {
+ ast_copy_string(global_realm, v->value, sizeof(global_realm));
+ } else if (!strcasecmp(v->name, "useragent")) {
+ ast_copy_string(global_useragent, v->value, sizeof(global_useragent));
+ ast_debug(1, "Setting SIP channel User-Agent Name to %s\n", global_useragent);
+ } else if (!strcasecmp(v->name, "sdpsession")) {
+ ast_copy_string(global_sdpsession, v->value, sizeof(global_sdpsession));
+ } else if (!strcasecmp(v->name, "sdpowner")) {
+ /* Field cannot contain spaces */
+ if (!strstr(v->value, " "))
+ ast_copy_string(global_sdpowner, v->value, sizeof(global_sdpowner));
+ else
+ ast_log(LOG_WARNING, "'%s' must not contain spaces at line %d. Using default.\n", v->value, v->lineno);
+ } else if (!strcasecmp(v->name, "allowtransfer")) {
+ global_allowtransfer = ast_true(v->value) ? TRANSFER_OPENFORALL : TRANSFER_CLOSED;
+ } else if (!strcasecmp(v->name, "rtcachefriends")) {
+ ast_set2_flag(&global_flags[1], ast_true(v->value), SIP_PAGE2_RTCACHEFRIENDS);
+ } else if (!strcasecmp(v->name, "rtsavesysname")) {
+ sip_cfg.rtsave_sysname = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "rtupdate")) {
+ sip_cfg.peer_rtupdate = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "ignoreregexpire")) {
+ sip_cfg.ignore_regexpire = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "t1min")) {
+ global_t1min = atoi(v->value);
+ } else if (!strcasecmp(v->name, "tcpenable")) {
+ sip_tcp_desc.sin.sin_family = ast_false(v->value) ? 0 : AF_INET;
+ } else if (!strcasecmp(v->name, "tcpbindaddr")) {
+ if (ast_parse_arg(v->value, PARSE_INADDR, &sip_tcp_desc.sin))
+ ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of %s\n", v->name, v->value, v->lineno, config);
+ } else if (!strcasecmp(v->name, "tlsenable")) {
+ default_tls_cfg.enabled = ast_true(v->value) ? TRUE : FALSE;
+ sip_tls_desc.sin.sin_family = AF_INET;
+ } else if (!strcasecmp(v->name, "tlscertfile")) {
+ ast_free(default_tls_cfg.certfile);
+ default_tls_cfg.certfile = ast_strdup(v->value);
+ } else if (!strcasecmp(v->name, "tlscipher")) {
+ ast_free(default_tls_cfg.cipher);
+ default_tls_cfg.cipher = ast_strdup(v->value);
+ } else if (!strcasecmp(v->name, "tlscafile")) {
+ ast_free(default_tls_cfg.cafile);
+ default_tls_cfg.cafile = ast_strdup(v->value);
+ } else if (!strcasecmp(v->name, "tlscapath")) {
+ ast_free(default_tls_cfg.capath);
+ default_tls_cfg.capath = ast_strdup(v->value);
+ } else if (!strcasecmp(v->name, "tlsverifyclient")) {
+ ast_set2_flag(&default_tls_cfg.flags, ast_true(v->value), AST_SSL_VERIFY_CLIENT);
+ } else if (!strcasecmp(v->name, "tlsdontverifyserver")) {
+ ast_set2_flag(&default_tls_cfg.flags, ast_true(v->value), AST_SSL_DONT_VERIFY_SERVER);
+ } else if (!strcasecmp(v->name, "tlsbindaddr")) {
+ if (ast_parse_arg(v->value, PARSE_INADDR, &sip_tls_desc.sin))
+ ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of %s\n", v->name, v->value, v->lineno, config);
+ } else if (!strcasecmp(v->name, "rtautoclear")) {
+ int i = atoi(v->value);
+ if (i > 0)
+ global_rtautoclear = i;
+ else
+ i = 0;
+ ast_set2_flag(&global_flags[1], i || ast_true(v->value), SIP_PAGE2_RTAUTOCLEAR);
+ } else if (!strcasecmp(v->name, "usereqphone")) {
+ ast_set2_flag(&global_flags[0], ast_true(v->value), SIP_USEREQPHONE);
+ } else if (!strcasecmp(v->name, "relaxdtmf")) {
+ global_relaxdtmf = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "vmexten")) {
+ ast_copy_string(default_vmexten, v->value, sizeof(default_vmexten));
+ } else if (!strcasecmp(v->name, "rtptimeout")) {
+ if ((sscanf(v->value, "%d", &global_rtptimeout) != 1) || (global_rtptimeout < 0)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid RTP hold time at line %d. Using default.\n", v->value, v->lineno);
+ global_rtptimeout = 0;
+ }
+ } else if (!strcasecmp(v->name, "rtpholdtimeout")) {
+ if ((sscanf(v->value, "%d", &global_rtpholdtimeout) != 1) || (global_rtpholdtimeout < 0)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid RTP hold time at line %d. Using default.\n", v->value, v->lineno);
+ global_rtpholdtimeout = 0;
+ }
+ } else if (!strcasecmp(v->name, "rtpkeepalive")) {
+ if ((sscanf(v->value, "%d", &global_rtpkeepalive) != 1) || (global_rtpkeepalive < 0)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid RTP keepalive time at line %d. Using default.\n", v->value, v->lineno);
+ global_rtpkeepalive = 0;
+ }
+ } else if (!strcasecmp(v->name, "compactheaders")) {
+ compactheaders = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "notifymimetype")) {
+ ast_copy_string(default_notifymime, v->value, sizeof(default_notifymime));
+ } else if (!strncasecmp(v->name, "limitonpeer", 11) || !strcasecmp(v->name, "counteronpeer")) {
+ global_limitonpeers = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "directrtpsetup")) {
+ global_directrtpsetup = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "notifyringing")) {
+ global_notifyringing = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "notifyhold")) {
+ global_notifyhold = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "alwaysauthreject")) {
+ global_alwaysauthreject = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "mohinterpret")) {
+ ast_copy_string(default_mohinterpret, v->value, sizeof(default_mohinterpret));
+ } else if (!strcasecmp(v->name, "mohsuggest")) {
+ ast_copy_string(default_mohsuggest, v->value, sizeof(default_mohsuggest));
+ } else if (!strcasecmp(v->name, "language")) {
+ ast_copy_string(default_language, v->value, sizeof(default_language));
+ } else if (!strcasecmp(v->name, "regcontext")) {
+ ast_copy_string(newcontexts, v->value, sizeof(newcontexts));
+ stringp = newcontexts;
+ /* Let's remove any contexts that are no longer defined in regcontext */
+ cleanup_stale_contexts(stringp, oldregcontext);
+ /* Create contexts if they don't exist already */
+ while ((context = strsep(&stringp, "&"))) {
+ ast_copy_string(used_context, context, sizeof(used_context));
+ if (!ast_context_find(context))
+ ast_context_create(NULL, context,"SIP");
+ }
+ ast_copy_string(global_regcontext, v->value, sizeof(global_regcontext));
+ } else if (!strcasecmp(v->name, "regextenonqualify")) {
+ global_regextenonqualify = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callerid")) {
+ ast_copy_string(default_callerid, v->value, sizeof(default_callerid));
+ } else if (!strcasecmp(v->name, "fromdomain")) {
+ ast_copy_string(default_fromdomain, v->value, sizeof(default_fromdomain));
+ } else if (!strcasecmp(v->name, "outboundproxy")) {
+ char *name, *port = NULL, *force;
+
+ name = ast_strdupa(v->value);
+ if ((port = strchr(name, ':'))) {
+ *port++ = '\0';
+ global_outboundproxy.ip.sin_port = htons(atoi(port));
+ }
+
+ if ((force = strchr(port ? port : name, ','))) {
+ *force++ = '\0';
+ global_outboundproxy.force = (!strcasecmp(force, "force"));
+ }
+ ast_copy_string(global_outboundproxy.name, name, sizeof(global_outboundproxy.name));
+ proxy_update(&global_outboundproxy);
+
+ } else if (!strcasecmp(v->name, "autocreatepeer")) {
+ autocreatepeer = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "match_auth_username")) {
+ global_match_auth_username = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "srvlookup")) {
+ global_srvlookup = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "pedantic")) {
+ pedanticsipchecking = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "maxexpirey") || !strcasecmp(v->name, "maxexpiry")) {
+ max_expiry = atoi(v->value);
+ if (max_expiry < 1)
+ max_expiry = DEFAULT_MAX_EXPIRY;
+ } else if (!strcasecmp(v->name, "minexpirey") || !strcasecmp(v->name, "minexpiry")) {
+ min_expiry = atoi(v->value);
+ if (min_expiry < 1)
+ min_expiry = DEFAULT_MIN_EXPIRY;
+ } else if (!strcasecmp(v->name, "defaultexpiry") || !strcasecmp(v->name, "defaultexpirey")) {
+ default_expiry = atoi(v->value);
+ if (default_expiry < 1)
+ default_expiry = DEFAULT_DEFAULT_EXPIRY;
+ } else if (!strcasecmp(v->name, "sipdebug")) {
+ if (ast_true(v->value))
+ sipdebug |= sip_debug_config;
+ } else if (!strcasecmp(v->name, "dumphistory")) {
+ dumphistory = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "recordhistory")) {
+ recordhistory = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "registertimeout")) {
+ global_reg_timeout = atoi(v->value);
+ if (global_reg_timeout < 1)
+ global_reg_timeout = DEFAULT_REGISTRATION_TIMEOUT;
+ } else if (!strcasecmp(v->name, "registerattempts")) {
+ global_regattempts_max = atoi(v->value);
+ } else if (!strcasecmp(v->name, "stunaddr")) {
+ stunaddr.sin_port = htons(3478);
+ if (ast_parse_arg(v->value, PARSE_INADDR, &stunaddr))
+ ast_log(LOG_WARNING, "Invalid STUN server address: %s\n", v->value);
+ externexpire = time(NULL);
+ } else if (!strcasecmp(v->name, "bindaddr")) {
+ if (ast_parse_arg(v->value, PARSE_INADDR, &bindaddr))
+ ast_log(LOG_WARNING, "Invalid address: %s\n", v->value);
+ } else if (!strcasecmp(v->name, "localnet")) {
+ struct ast_ha *na;
+ int ha_error = 0;
+
+ if (!(na = ast_append_ha("d", v->value, localaddr, &ha_error)))
+ ast_log(LOG_WARNING, "Invalid localnet value: %s\n", v->value);
+ else
+ localaddr = na;
+ if (ha_error)
+ ast_log(LOG_ERROR, "Bad localnet configuration value line %d : %s\n", v->lineno, v->value);
+ } else if (!strcasecmp(v->name, "externip")) {
+ if (ast_parse_arg(v->value, PARSE_INADDR, &externip))
+ ast_log(LOG_WARNING, "Invalid address for externip keyword: %s\n", v->value);
+ externexpire = 0;
+ } else if (!strcasecmp(v->name, "externhost")) {
+ ast_copy_string(externhost, v->value, sizeof(externhost));
+ if (ast_parse_arg(externhost, PARSE_INADDR, &externip))
+ ast_log(LOG_WARNING, "Invalid address for externhost keyword: %s\n", externhost);
+ externexpire = time(NULL);
+ } else if (!strcasecmp(v->name, "externrefresh")) {
+ if (sscanf(v->value, "%d", &externrefresh) != 1) {
+ ast_log(LOG_WARNING, "Invalid externrefresh value '%s', must be an integer >0 at line %d\n", v->value, v->lineno);
+ externrefresh = 10;
+ }
+ } else if (!strcasecmp(v->name, "allow")) {
+ int error = ast_parse_allow_disallow(&default_prefs, &global_capability, v->value, TRUE);
+ if (error)
+ ast_log(LOG_WARNING, "Codec configuration errors found in line %d : %s = %s\n", v->lineno, v->name, v->value);
+ } else if (!strcasecmp(v->name, "disallow")) {
+ int error = ast_parse_allow_disallow(&default_prefs, &global_capability, v->value, FALSE);
+ if (error)
+ ast_log(LOG_WARNING, "Codec configuration errors found in line %d : %s = %s\n", v->lineno, v->name, v->value);
+ } else if (!strcasecmp(v->name, "autoframing")) {
+ global_autoframing = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "allowexternaldomains")) {
+ allow_external_domains = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "autodomain")) {
+ auto_sip_domains = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "domain")) {
+ char *domain = ast_strdupa(v->value);
+ char *context = strchr(domain, ',');
+
+ if (context)
+ *context++ = '\0';
+
+ if (ast_strlen_zero(context))
+ ast_debug(1, "No context specified at line %d for domain '%s'\n", v->lineno, domain);
+ if (ast_strlen_zero(domain))
+ ast_log(LOG_WARNING, "Empty domain specified at line %d\n", v->lineno);
+ else
+ add_sip_domain(ast_strip(domain), SIP_DOMAIN_CONFIG, context ? ast_strip(context) : "");
+ } else if (!strcasecmp(v->name, "register")) {
+ if (sip_register(v->value, v->lineno) == 0)
+ registry_count++;
+ } else if (!strcasecmp(v->name, "tos_sip")) {
+ if (ast_str2tos(v->value, &global_tos_sip))
+ ast_log(LOG_WARNING, "Invalid tos_sip value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "tos_audio")) {
+ if (ast_str2tos(v->value, &global_tos_audio))
+ ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "tos_video")) {
+ if (ast_str2tos(v->value, &global_tos_video))
+ ast_log(LOG_WARNING, "Invalid tos_video value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "tos_text")) {
+ if (ast_str2tos(v->value, &global_tos_text))
+ ast_log(LOG_WARNING, "Invalid tos_text value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos_sip")) {
+ if (ast_str2cos(v->value, &global_cos_sip))
+ ast_log(LOG_WARNING, "Invalid cos_sip value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos_audio")) {
+ if (ast_str2cos(v->value, &global_cos_audio))
+ ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos_video")) {
+ if (ast_str2cos(v->value, &global_cos_video))
+ ast_log(LOG_WARNING, "Invalid cos_video value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos_text")) {
+ if (ast_str2cos(v->value, &global_cos_text))
+ ast_log(LOG_WARNING, "Invalid cos_text value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "bindport")) {
+ int i;
+ if (sscanf(v->value, "%d", &i) == 1) {
+ bindaddr.sin_port = htons(i);
+ } else {
+ ast_log(LOG_WARNING, "Invalid port number '%s' at line %d of %s\n", v->value, v->lineno, config);
+ }
+ } else if (!strcasecmp(v->name, "qualify")) {
+ if (!strcasecmp(v->value, "no")) {
+ default_qualify = 0;
+ } else if (!strcasecmp(v->value, "yes")) {
+ default_qualify = DEFAULT_MAXMS;
+ } else if (sscanf(v->value, "%d", &default_qualify) != 1) {
+ ast_log(LOG_WARNING, "Qualification default should be 'yes', 'no', or a number of milliseconds at line %d of sip.conf\n", v->lineno);
+ default_qualify = 0;
+ }
+ } else if (!strcasecmp(v->name, "qualifyfreq")) {
+ int i;
+ if (sscanf(v->value, "%d", &i) == 1)
+ global_qualifyfreq = i * 1000;
+ else {
+ ast_log(LOG_WARNING, "Invalid qualifyfreq number '%s' at line %d of %s\n", v->value, v->lineno, config);
+ global_qualifyfreq = DEFAULT_QUALIFYFREQ;
+ }
+ } else if (!strcasecmp(v->name, "callevents")) {
+ global_callevents = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "maxcallbitrate")) {
+ default_maxcallbitrate = atoi(v->value);
+ if (default_maxcallbitrate < 0)
+ default_maxcallbitrate = DEFAULT_MAX_CALL_BITRATE;
+ } else if (!strcasecmp(v->name, "matchexterniplocally")) {
+ global_matchexterniplocally = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "session-timers")) {
+ int i = (int) str2stmode(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config);
+ global_st_mode = SESSION_TIMER_MODE_ACCEPT;
+ } else {
+ global_st_mode = i;
+ }
+ } else if (!strcasecmp(v->name, "session-expires")) {
+ if (sscanf(v->value, "%d", &global_max_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config);
+ global_max_se = DEFAULT_MAX_SE;
+ }
+ } else if (!strcasecmp(v->name, "session-minse")) {
+ if (sscanf(v->value, "%d", &global_min_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config);
+ global_min_se = DEFAULT_MIN_SE;
+ }
+ if (global_min_se < 90) {
+ ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < 90 secs\n", v->value, v->lineno, config);
+ global_min_se = DEFAULT_MIN_SE;
+ }
+ } else if (!strcasecmp(v->name, "session-refresher")) {
+ int i = (int) str2strefresher(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config);
+ global_st_refresher = SESSION_TIMER_REFRESHER_UAS;
+ } else {
+ global_st_refresher = i;
+ }
+ }
+ }
+
+ if (!allow_external_domains && AST_LIST_EMPTY(&domain_list)) {
+ ast_log(LOG_WARNING, "To disallow external domains, you need to configure local SIP domains.\n");
+ allow_external_domains = 1;
+ }
+
+ /* Build list of authentication to various SIP realms, i.e. service providers */
+ for (v = ast_variable_browse(cfg, "authentication"); v ; v = v->next) {
+ /* Format for authentication is auth = username:password@realm */
+ if (!strcasecmp(v->name, "auth"))
+ authl = add_realm_authentication(authl, v->value, v->lineno);
+ }
+
+ if (ucfg) {
+ struct ast_variable *gen;
+ int genhassip, genregistersip;
+ const char *hassip, *registersip;
+
+ genhassip = ast_true(ast_variable_retrieve(ucfg, "general", "hassip"));
+ genregistersip = ast_true(ast_variable_retrieve(ucfg, "general", "registersip"));
+ gen = ast_variable_browse(ucfg, "general");
+ cat = ast_category_browse(ucfg, NULL);
+ while (cat) {
+ if (strcasecmp(cat, "general")) {
+ hassip = ast_variable_retrieve(ucfg, cat, "hassip");
+ registersip = ast_variable_retrieve(ucfg, cat, "registersip");
+ if (ast_true(hassip) || (!hassip && genhassip)) {
+ peer = build_peer(cat, gen, ast_variable_browse(ucfg, cat), 0);
+ if (peer) {
+ ast_device_state_changed("SIP/%s", peer->name);
+ ASTOBJ_CONTAINER_LINK(&peerl,peer);
+ unref_peer(peer);
+ peer_count++;
+ }
+ }
+ if (ast_true(registersip) || (!registersip && genregistersip)) {
+ char tmp[256];
+ const char *host = ast_variable_retrieve(ucfg, cat, "host");
+ const char *username = ast_variable_retrieve(ucfg, cat, "username");
+ const char *secret = ast_variable_retrieve(ucfg, cat, "secret");
+ const char *contact = ast_variable_retrieve(ucfg, cat, "contact");
+ if (!host)
+ host = ast_variable_retrieve(ucfg, "general", "host");
+ if (!username)
+ username = ast_variable_retrieve(ucfg, "general", "username");
+ if (!secret)
+ secret = ast_variable_retrieve(ucfg, "general", "secret");
+ if (!contact)
+ contact = "s";
+ if (!ast_strlen_zero(username) && !ast_strlen_zero(host)) {
+ if (!ast_strlen_zero(secret))
+ snprintf(tmp, sizeof(tmp), "%s:%s@%s/%s", username, secret, host, contact);
+ else
+ snprintf(tmp, sizeof(tmp), "%s@%s/%s", username, host, contact);
+ if (sip_register(tmp, 0) == 0)
+ registry_count++;
+ }
+ }
+ }
+ cat = ast_category_browse(ucfg, cat);
+ }
+ ast_config_destroy(ucfg);
+ }
+
+
+ /* Load peers, users and friends */
+ cat = NULL;
+ while ( (cat = ast_category_browse(cfg, cat)) ) {
+ const char *utype;
+ if (!strcasecmp(cat, "general") || !strcasecmp(cat, "authentication"))
+ continue;
+ utype = ast_variable_retrieve(cfg, cat, "type");
+ if (!utype) {
+ ast_log(LOG_WARNING, "Section '%s' lacks type\n", cat);
+ continue;
+ } else {
+ int is_user = 0, is_peer = 0;
+ if (!strcasecmp(utype, "user"))
+ is_user = 1;
+ else if (!strcasecmp(utype, "friend"))
+ is_user = is_peer = 1;
+ else if (!strcasecmp(utype, "peer"))
+ is_peer = 1;
+ else {
+ ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", utype, cat, "sip.conf");
+ continue;
+ }
+ if (is_user) {
+ user = build_user(cat, ast_variable_browse(cfg, cat), 0);
+ if (user) {
+ ASTOBJ_CONTAINER_LINK(&userl,user);
+ unref_user(user);
+ user_count++;
+ }
+ }
+ if (is_peer) {
+ peer = build_peer(cat, ast_variable_browse(cfg, cat), NULL, 0);
+ if (peer) {
+ ASTOBJ_CONTAINER_LINK(&peerl,peer);
+ unref_peer(peer);
+ peer_count++;
+ }
+ }
+ }
+ }
+ bindaddr.sin_family = AF_INET;
+ internip = bindaddr;
+ if (ast_find_ourip(&internip.sin_addr, bindaddr)) {
+ ast_log(LOG_WARNING, "Unable to get own IP address, SIP disabled\n");
+ ast_config_destroy(cfg);
+ return 0;
+ }
+ ast_mutex_lock(&netlock);
+ if ((sipsock > -1) && (memcmp(&old_bindaddr, &bindaddr, sizeof(struct sockaddr_in)))) {
+ close(sipsock);
+ sipsock = -1;
+ }
+ if (sipsock < 0) {
+ sipsock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sipsock < 0) {
+ ast_log(LOG_WARNING, "Unable to create SIP socket: %s\n", strerror(errno));
+ ast_config_destroy(cfg);
+ return -1;
+ } else {
+ /* Allow SIP clients on the same host to access us: */
+ const int reuseFlag = 1;
+
+ setsockopt(sipsock, SOL_SOCKET, SO_REUSEADDR,
+ (const char*)&reuseFlag,
+ sizeof reuseFlag);
+
+ ast_enable_packet_fragmentation(sipsock);
+
+ if (bind(sipsock, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) {
+ ast_log(LOG_WARNING, "Failed to bind to %s:%d: %s\n",
+ ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port),
+ strerror(errno));
+ close(sipsock);
+ sipsock = -1;
+ } else {
+ ast_verb(2, "SIP Listening on %s:%d\n",
+ ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port));
+ ast_netsock_set_qos(sipsock, global_tos_sip, global_cos_sip, "SIP");
+ }
+ }
+ }
+ if (stunaddr.sin_addr.s_addr != 0) {
+ ast_debug(1, "stun to %s:%d\n",
+ ast_inet_ntoa(stunaddr.sin_addr) , ntohs(stunaddr.sin_port));
+ ast_stun_request(sipsock, &stunaddr,
+ NULL, &externip);
+ ast_debug(1, "STUN sees us at %s:%d\n",
+ ast_inet_ntoa(externip.sin_addr) , ntohs(externip.sin_port));
+ }
+ ast_mutex_unlock(&netlock);
+
+ /* Add default domains - host name, IP address and IP:port */
+ /* Only do this if user added any sip domain with "localdomains" */
+ /* In order to *not* break backwards compatibility */
+ /* Some phones address us at IP only, some with additional port number */
+ if (auto_sip_domains) {
+ char temp[MAXHOSTNAMELEN];
+
+ /* First our default IP address */
+ if (bindaddr.sin_addr.s_addr)
+ add_sip_domain(ast_inet_ntoa(bindaddr.sin_addr), SIP_DOMAIN_AUTO, NULL);
+ else
+ ast_log(LOG_NOTICE, "Can't add wildcard IP address to domain list, please add IP address to domain manually.\n");
+
+ /* Our extern IP address, if configured */
+ if (externip.sin_addr.s_addr)
+ add_sip_domain(ast_inet_ntoa(externip.sin_addr), SIP_DOMAIN_AUTO, NULL);
+
+ /* Extern host name (NAT traversal support) */
+ if (!ast_strlen_zero(externhost))
+ add_sip_domain(externhost, SIP_DOMAIN_AUTO, NULL);
+
+ /* Our host name */
+ if (!gethostname(temp, sizeof(temp)))
+ add_sip_domain(temp, SIP_DOMAIN_AUTO, NULL);
+ }
+
+ /* Release configuration from memory */
+ ast_config_destroy(cfg);
+
+ /* Load the list of manual NOTIFY types to support */
+ if (notify_types)
+ ast_config_destroy(notify_types);
+ notify_types = ast_config_load(notify_config, config_flags);
+
+ memcpy(sip_tls_desc.tls_cfg, &default_tls_cfg, sizeof(default_tls_cfg));
+ server_start(&sip_tcp_desc);
+
+ if (ssl_setup(sip_tls_desc.tls_cfg))
+ server_start(&sip_tls_desc);
+ else if (sip_tls_desc.tls_cfg->enabled) {
+ sip_tls_desc.tls_cfg = NULL;
+ ast_log(LOG_WARNING, "SIP TLS server did not load because of errors.\n");
+ }
+
+ /* Done, tell the manager */
+ manager_event(EVENT_FLAG_SYSTEM, "ChannelReload", "ChannelType: SIP\r\nReloadReason: %s\r\nRegistry_Count: %d\r\nPeer_Count: %d\r\nUser_Count: %d\r\n", channelreloadreason2txt(reason), registry_count, peer_count, user_count);
+
+ return 0;
+}
+
+static struct ast_udptl *sip_get_udptl_peer(struct ast_channel *chan)
+{
+ struct sip_pvt *p;
+ struct ast_udptl *udptl = NULL;
+
+ p = chan->tech_pvt;
+ if (!p)
+ return NULL;
+
+ sip_pvt_lock(p);
+ if (p->udptl && ast_test_flag(&p->flags[0], SIP_CAN_REINVITE))
+ udptl = p->udptl;
+ sip_pvt_unlock(p);
+ return udptl;
+}
+
+static int sip_set_udptl_peer(struct ast_channel *chan, struct ast_udptl *udptl)
+{
+ struct sip_pvt *p;
+
+ p = chan->tech_pvt;
+ if (!p)
+ return -1;
+ sip_pvt_lock(p);
+ if (udptl)
+ ast_udptl_get_peer(udptl, &p->udptlredirip);
+ else
+ memset(&p->udptlredirip, 0, sizeof(p->udptlredirip));
+ if (!ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
+ if (!p->pendinginvite) {
+ ast_debug(3, "Sending reinvite on SIP '%s' - It's UDPTL soon redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(udptl ? p->udptlredirip.sin_addr : p->ourip.sin_addr), udptl ? ntohs(p->udptlredirip.sin_port) : 0);
+ transmit_reinvite_with_sdp(p, TRUE, FALSE);
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ ast_debug(3, "Deferring reinvite on SIP '%s' - It's UDPTL will be redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(udptl ? p->udptlredirip.sin_addr : p->ourip.sin_addr), udptl ? ntohs(p->udptlredirip.sin_port) : 0);
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ }
+ /* Reset lastrtprx timer */
+ p->lastrtprx = p->lastrtptx = time(NULL);
+ sip_pvt_unlock(p);
+ return 0;
+}
+
+/*! \brief Handle T38 reinvite
+ \todo Make sure we don't destroy the call if we can't handle the re-invite.
+ Nothing should be changed until we have processed the SDP and know that we
+ can handle it.
+*/
+static int sip_handle_t38_reinvite(struct ast_channel *chan, struct sip_pvt *pvt, int reinvite)
+{
+ struct sip_pvt *p;
+ int flag = 0;
+
+ p = chan->tech_pvt;
+ if (!p || !pvt->udptl)
+ return -1;
+
+ /* Setup everything on the other side like offered/responded from first side */
+ sip_pvt_lock(p);
+
+ /*! \todo check if this is not set earlier when setting up the PVT. If not
+ maybe it should move there. */
+ p->t38.jointcapability = p->t38.peercapability = pvt->t38.jointcapability;
+
+ ast_udptl_set_far_max_datagram(p->udptl, ast_udptl_get_local_max_datagram(pvt->udptl));
+ ast_udptl_set_local_max_datagram(p->udptl, ast_udptl_get_local_max_datagram(pvt->udptl));
+ ast_udptl_set_error_correction_scheme(p->udptl, ast_udptl_get_error_correction_scheme(pvt->udptl));
+
+ if (reinvite) { /* If we are handling sending re-invite to the other side of the bridge */
+ /*! \note The SIP_CAN_REINVITE flag is for RTP media redirects,
+ not really T38 re-invites which are different. In this
+ case it's used properly, to see if we can reinvite over
+ NAT
+ */
+ if (ast_test_flag(&p->flags[0], SIP_CAN_REINVITE) && ast_test_flag(&pvt->flags[0], SIP_CAN_REINVITE)) {
+ ast_udptl_get_peer(pvt->udptl, &p->udptlredirip);
+ flag =1;
+ } else {
+ memset(&p->udptlredirip, 0, sizeof(p->udptlredirip));
+ }
+ if (!ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
+ if (!p->pendinginvite) {
+ if (flag)
+ ast_debug(3, "Sending reinvite on SIP '%s' - It's UDPTL soon redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(p->udptlredirip.sin_addr), ntohs(p->udptlredirip.sin_port));
+ else
+ ast_debug(3, "Sending reinvite on SIP '%s' - It's UDPTL soon redirected to us (IP %s)\n", p->callid, ast_inet_ntoa(p->ourip.sin_addr));
+ transmit_reinvite_with_sdp(p, TRUE, FALSE);
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ if (flag)
+ ast_debug(3, "Deferring reinvite on SIP '%s' - It's UDPTL will be redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(p->udptlredirip.sin_addr), ntohs(p->udptlredirip.sin_port));
+ else
+ ast_debug(3, "Deferring reinvite on SIP '%s' - It's UDPTL will be redirected to us (IP %s)\n", p->callid, ast_inet_ntoa(p->ourip.sin_addr));
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ }
+ /* Reset lastrtprx timer */
+ p->lastrtprx = p->lastrtptx = time(NULL);
+ sip_pvt_unlock(p);
+ return 0;
+ } else { /* If we are handling sending 200 OK to the other side of the bridge */
+ if (ast_test_flag(&p->flags[0], SIP_CAN_REINVITE) && ast_test_flag(&pvt->flags[0], SIP_CAN_REINVITE)) {
+ ast_udptl_get_peer(pvt->udptl, &p->udptlredirip);
+ flag = 1;
+ } else {
+ memset(&p->udptlredirip, 0, sizeof(p->udptlredirip));
+ }
+ if (flag)
+ ast_debug(3, "Responding 200 OK on SIP '%s' - It's UDPTL soon redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(p->udptlredirip.sin_addr), ntohs(p->udptlredirip.sin_port));
+ else
+ ast_debug(3, "Responding 200 OK on SIP '%s' - It's UDPTL soon redirected to us (IP %s)\n", p->callid, ast_inet_ntoa(p->ourip.sin_addr));
+ pvt->t38.state = T38_ENABLED;
+ p->t38.state = T38_ENABLED;
+ ast_debug(2, "T38 changed state to %d on channel %s\n", pvt->t38.state, pvt->owner ? pvt->owner->name : "<none>");
+ ast_debug(2, "T38 changed state to %d on channel %s\n", p->t38.state, chan ? chan->name : "<none>");
+ transmit_response_with_t38_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL);
+ p->lastrtprx = p->lastrtptx = time(NULL);
+ sip_pvt_unlock(p);
+ return 0;
+ }
+}
+
+
+/*! \brief Returns null if we can't reinvite audio (part of RTP interface) */
+static enum ast_rtp_get_result sip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp **rtp)
+{
+ struct sip_pvt *p = NULL;
+ enum ast_rtp_get_result res = AST_RTP_TRY_PARTIAL;
+
+ if (!(p = chan->tech_pvt))
+ return AST_RTP_GET_FAILED;
+
+ sip_pvt_lock(p);
+ if (!(p->rtp)) {
+ sip_pvt_unlock(p);
+ return AST_RTP_GET_FAILED;
+ }
+
+ *rtp = p->rtp;
+
+ if (ast_rtp_getnat(*rtp) && !ast_test_flag(&p->flags[0], SIP_CAN_REINVITE_NAT))
+ res = AST_RTP_TRY_PARTIAL;
+ else if (ast_test_flag(&p->flags[0], SIP_CAN_REINVITE))
+ res = AST_RTP_TRY_NATIVE;
+ else if (ast_test_flag(&global_jbconf, AST_JB_FORCED))
+ res = AST_RTP_GET_FAILED;
+
+ sip_pvt_unlock(p);
+
+ return res;
+}
+
+/*! \brief Returns null if we can't reinvite video (part of RTP interface) */
+static enum ast_rtp_get_result sip_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp **rtp)
+{
+ struct sip_pvt *p = NULL;
+ enum ast_rtp_get_result res = AST_RTP_TRY_PARTIAL;
+
+ if (!(p = chan->tech_pvt))
+ return AST_RTP_GET_FAILED;
+
+ sip_pvt_lock(p);
+ if (!(p->vrtp)) {
+ sip_pvt_unlock(p);
+ return AST_RTP_GET_FAILED;
+ }
+
+ *rtp = p->vrtp;
+
+ if (ast_test_flag(&p->flags[0], SIP_CAN_REINVITE))
+ res = AST_RTP_TRY_NATIVE;
+
+ sip_pvt_unlock(p);
+
+ return res;
+}
+
+/*! \brief Returns null if we can't reinvite text (part of RTP interface) */
+static enum ast_rtp_get_result sip_get_trtp_peer(struct ast_channel *chan, struct ast_rtp **rtp)
+{
+ struct sip_pvt *p = NULL;
+ enum ast_rtp_get_result res = AST_RTP_TRY_PARTIAL;
+
+ if (!(p = chan->tech_pvt))
+ return AST_RTP_GET_FAILED;
+
+ sip_pvt_lock(p);
+ if (!(p->trtp)) {
+ sip_pvt_unlock(p);
+ return AST_RTP_GET_FAILED;
+ }
+
+ *rtp = p->trtp;
+
+ if (ast_test_flag(&p->flags[0], SIP_CAN_REINVITE))
+ res = AST_RTP_TRY_NATIVE;
+
+ sip_pvt_unlock(p);
+
+ return res;
+}
+
+/*! \brief Set the RTP peer for this call */
+static int sip_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp, struct ast_rtp *vrtp, struct ast_rtp *trtp, int codecs, int nat_active)
+{
+ struct sip_pvt *p;
+ int changed = 0;
+
+ p = chan->tech_pvt;
+ if (!p)
+ return -1;
+
+ /* Disable early RTP bridge */
+ if (chan->_state != AST_STATE_UP && !global_directrtpsetup) /* We are in early state */
+ return 0;
+
+ sip_pvt_lock(p);
+ if (p->alreadygone) {
+ /* If we're destroyed, don't bother */
+ sip_pvt_unlock(p);
+ return 0;
+ }
+
+ /* if this peer cannot handle reinvites of the media stream to devices
+ that are known to be behind a NAT, then stop the process now
+ */
+ if (nat_active && !ast_test_flag(&p->flags[0], SIP_CAN_REINVITE_NAT)) {
+ sip_pvt_unlock(p);
+ return 0;
+ }
+
+ if (rtp) {
+ changed |= ast_rtp_get_peer(rtp, &p->redirip);
+ } else if (p->redirip.sin_addr.s_addr || ntohs(p->redirip.sin_port) != 0) {
+ memset(&p->redirip, 0, sizeof(p->redirip));
+ changed = 1;
+ }
+ if (vrtp) {
+ changed |= ast_rtp_get_peer(vrtp, &p->vredirip);
+ } else if (p->vredirip.sin_addr.s_addr || ntohs(p->vredirip.sin_port) != 0) {
+ memset(&p->vredirip, 0, sizeof(p->vredirip));
+ changed = 1;
+ }
+ if (trtp) {
+ changed |= ast_rtp_get_peer(trtp, &p->tredirip);
+ } else if (p->tredirip.sin_addr.s_addr || ntohs(p->tredirip.sin_port) != 0) {
+ memset(&p->tredirip, 0, sizeof(p->tredirip));
+ changed = 1;
+ }
+ if (codecs && (p->redircodecs != codecs)) {
+ p->redircodecs = codecs;
+ changed = 1;
+ }
+ if (changed && !ast_test_flag(&p->flags[0], SIP_GOTREFER) && !ast_test_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER)) {
+ if (chan->_state != AST_STATE_UP) { /* We are in early state */
+ if (p->do_history)
+ append_history(p, "ExtInv", "Initial invite sent with remote bridge proposal.");
+ ast_debug(1, "Early remote bridge setting SIP '%s' - Sending media to %s\n", p->callid, ast_inet_ntoa(rtp ? p->redirip.sin_addr : p->ourip.sin_addr));
+ } else if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
+ ast_debug(3, "Sending reinvite on SIP '%s' - It's audio soon redirected to IP %s\n", p->callid, ast_inet_ntoa(rtp ? p->redirip.sin_addr : p->ourip.sin_addr));
+ transmit_reinvite_with_sdp(p, FALSE, FALSE);
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ ast_debug(3, "Deferring reinvite on SIP '%s' - It's audio will be redirected to IP %s\n", p->callid, ast_inet_ntoa(rtp ? p->redirip.sin_addr : p->ourip.sin_addr));
+ /* We have a pending Invite. Send re-invite when we're done with the invite */
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ }
+ /* Reset lastrtprx timer */
+ p->lastrtprx = p->lastrtptx = time(NULL);
+ sip_pvt_unlock(p);
+ return 0;
+}
+
+static char *synopsis_dtmfmode = "Change the dtmfmode for a SIP call";
+static char *descrip_dtmfmode = " SIPDtmfMode(inband|info|rfc2833): Changes the dtmfmode for a SIP call\n";
+static char *app_dtmfmode = "SIPDtmfMode";
+
+static char *app_sipaddheader = "SIPAddHeader";
+static char *synopsis_sipaddheader = "Add a SIP header to the outbound call";
+
+static char *descrip_sipaddheader = ""
+" SIPAddHeader(Header: Content):\n"
+"Adds a header to a SIP call placed with DIAL.\n"
+"Remember to user the X-header if you are adding non-standard SIP\n"
+"headers, like \"X-Asterisk-Accountcode:\". Use this with care.\n"
+"Adding the wrong headers may jeopardize the SIP dialog.\n"
+"Always returns 0\n";
+
+
+/*! \brief Set the DTMFmode for an outbound SIP call (application) */
+static int sip_dtmfmode(struct ast_channel *chan, void *data)
+{
+ struct sip_pvt *p;
+ char *mode = data;
+
+ if (!data) {
+ ast_log(LOG_WARNING, "This application requires the argument: info, inband, rfc2833\n");
+ return 0;
+ }
+ ast_channel_lock(chan);
+ if (!IS_SIP_TECH(chan->tech)) {
+ ast_log(LOG_WARNING, "Call this application only on SIP incoming calls\n");
+ ast_channel_unlock(chan);
+ return 0;
+ }
+ p = chan->tech_pvt;
+ if (!p) {
+ ast_channel_unlock(chan);
+ return 0;
+ }
+ sip_pvt_lock(p);
+ if (!strcasecmp(mode,"info")) {
+ ast_clear_flag(&p->flags[0], SIP_DTMF);
+ ast_set_flag(&p->flags[0], SIP_DTMF_INFO);
+ p->jointnoncodeccapability &= ~AST_RTP_DTMF;
+ } else if (!strcasecmp(mode,"shortinfo")) {
+ ast_clear_flag(&p->flags[0], SIP_DTMF);
+ ast_set_flag(&p->flags[0], SIP_DTMF_SHORTINFO);
+ p->jointnoncodeccapability &= ~AST_RTP_DTMF;
+ } else if (!strcasecmp(mode,"rfc2833")) {
+ ast_clear_flag(&p->flags[0], SIP_DTMF);
+ ast_set_flag(&p->flags[0], SIP_DTMF_RFC2833);
+ p->jointnoncodeccapability |= AST_RTP_DTMF;
+ } else if (!strcasecmp(mode,"inband")) {
+ ast_clear_flag(&p->flags[0], SIP_DTMF);
+ ast_set_flag(&p->flags[0], SIP_DTMF_INBAND);
+ p->jointnoncodeccapability &= ~AST_RTP_DTMF;
+ } else
+ ast_log(LOG_WARNING, "I don't know about this dtmf mode: %s\n",mode);
+ if (p->rtp)
+ ast_rtp_setdtmf(p->rtp, ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
+ if (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) {
+ if (!p->vad) {
+ p->vad = ast_dsp_new();
+ ast_dsp_set_features(p->vad, DSP_FEATURE_DTMF_DETECT);
+ }
+ } else {
+ if (p->vad) {
+ ast_dsp_free(p->vad);
+ p->vad = NULL;
+ }
+ }
+ sip_pvt_unlock(p);
+ ast_channel_unlock(chan);
+ return 0;
+}
+
+/*! \brief Add a SIP header to an outbound INVITE */
+static int sip_addheader(struct ast_channel *chan, void *data)
+{
+ int no = 0;
+ int ok = FALSE;
+ char varbuf[30];
+ char *inbuf = data;
+
+ if (ast_strlen_zero(inbuf)) {
+ ast_log(LOG_WARNING, "This application requires the argument: Header\n");
+ return 0;
+ }
+ ast_channel_lock(chan);
+
+ /* Check for headers */
+ while (!ok && no <= 50) {
+ no++;
+ snprintf(varbuf, sizeof(varbuf), "_SIPADDHEADER%.2d", no);
+
+ /* Compare without the leading underscore */
+ if( (pbx_builtin_getvar_helper(chan, (const char *) varbuf + 1) == (const char *) NULL) )
+ ok = TRUE;
+ }
+ if (ok) {
+ pbx_builtin_setvar_helper (chan, varbuf, inbuf);
+ if (sipdebug)
+ ast_debug(1,"SIP Header added \"%s\" as %s\n", inbuf, varbuf);
+ } else {
+ ast_log(LOG_WARNING, "Too many SIP headers added, max 50\n");
+ }
+ ast_channel_unlock(chan);
+ return 0;
+}
+
+/*! \brief Transfer call before connect with a 302 redirect
+\note Called by the transfer() dialplan application through the sip_transfer()
+ pbx interface function if the call is in ringing state
+\todo Fix this function so that we wait for reply to the REFER and
+ react to errors, denials or other issues the other end might have.
+ */
+static int sip_sipredirect(struct sip_pvt *p, const char *dest)
+{
+ char *cdest;
+ char *extension, *host, *port;
+ char tmp[80];
+
+ cdest = ast_strdupa(dest);
+
+ extension = strsep(&cdest, "@");
+ host = strsep(&cdest, ":");
+ port = strsep(&cdest, ":");
+ if (ast_strlen_zero(extension)) {
+ ast_log(LOG_ERROR, "Missing mandatory argument: extension\n");
+ return 0;
+ }
+
+ /* we'll issue the redirect message here */
+ if (!host) {
+ char *localtmp;
+
+ ast_copy_string(tmp, get_header(&p->initreq, "To"), sizeof(tmp));
+ if (ast_strlen_zero(tmp)) {
+ ast_log(LOG_ERROR, "Cannot retrieve the 'To' header from the original SIP request!\n");
+ return 0;
+ }
+ if ( ( (localtmp = strcasestr(tmp, "sip:")) || (localtmp = strcasestr(tmp, "sips:")) )
+ && (localtmp = strchr(localtmp, '@'))) {
+ char lhost[80], lport[80];
+
+ memset(lhost, 0, sizeof(lhost));
+ memset(lport, 0, sizeof(lport));
+ localtmp++;
+ /* This is okey because lhost and lport are as big as tmp */
+ sscanf(localtmp, "%[^<>:; ]:%[^<>:; ]", lhost, lport);
+ if (ast_strlen_zero(lhost)) {
+ ast_log(LOG_ERROR, "Can't find the host address\n");
+ return 0;
+ }
+ host = ast_strdupa(lhost);
+ if (!ast_strlen_zero(lport)) {
+ port = ast_strdupa(lport);
+ }
+ }
+ }
+
+ ast_string_field_build(p, our_contact, "Transfer <sip:%s@%s%s%s>", extension, host, port ? ":" : "", port ? port : "");
+ transmit_response_reliable(p, "302 Moved Temporarily", &p->initreq);
+
+ sip_scheddestroy(p, SIP_TRANS_TIMEOUT); /* Make sure we stop send this reply. */
+ sip_alreadygone(p);
+ /* hangup here */
+ return 0;
+}
+
+/*! \brief Return SIP UA's codec (part of the RTP interface) */
+static int sip_get_codec(struct ast_channel *chan)
+{
+ struct sip_pvt *p = chan->tech_pvt;
+ return p->peercapability ? p->peercapability : p->capability;
+}
+
+/*! \brief Send a poke to all known peers
+ Space them out 100 ms apart
+ XXX We might have a cool algorithm for this or use random - any suggestions?
+*/
+static void sip_poke_all_peers(void)
+{
+ int ms = 0;
+
+ if (!speerobjs) /* No peers, just give up */
+ return;
+
+ ASTOBJ_CONTAINER_TRAVERSE(&peerl, 1, do {
+ ASTOBJ_WRLOCK(iterator);
+ ms += 100;
+ iterator->pokeexpire = ast_sched_replace(iterator->pokeexpire,
+ sched, ms, sip_poke_peer_s, iterator);
+ ASTOBJ_UNLOCK(iterator);
+ } while (0)
+ );
+}
+
+/*! \brief Send all known registrations */
+static void sip_send_all_registers(void)
+{
+ int ms;
+ int regspacing;
+ if (!regobjs)
+ return;
+ regspacing = default_expiry * 1000/regobjs;
+ if (regspacing > 100)
+ regspacing = 100;
+ ms = regspacing;
+ ASTOBJ_CONTAINER_TRAVERSE(&regl, 1, do {
+ ASTOBJ_WRLOCK(iterator);
+ ms += regspacing;
+ iterator->expire = ast_sched_replace(iterator->expire,
+ sched, ms, sip_reregister, iterator);
+ ASTOBJ_UNLOCK(iterator);
+ } while (0)
+ );
+}
+
+/*! \brief Reload module */
+static int sip_do_reload(enum channelreloadreason reason)
+{
+ reload_config(reason);
+
+ /* Prune peers who still are supposed to be deleted */
+ ASTOBJ_CONTAINER_PRUNE_MARKED(&peerl, sip_destroy_peer);
+ ast_debug(4, "--------------- Done destroying pruned peers\n");
+
+ /* Send qualify (OPTIONS) to all peers */
+ sip_poke_all_peers();
+
+ /* Register with all services */
+ sip_send_all_registers();
+
+ ast_debug(4, "--------------- SIP reload done\n");
+
+ return 0;
+}
+
+/*! \brief Force reload of module from cli */
+static char *sip_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "sip reload";
+ e->usage =
+ "Usage: sip reload\n"
+ " Reloads SIP configuration from sip.conf\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ ast_mutex_lock(&sip_reload_lock);
+ if (sip_reloading)
+ ast_verbose("Previous SIP reload not yet done\n");
+ else {
+ sip_reloading = TRUE;
+ sip_reloadreason = (a && a->fd) ? CHANNEL_CLI_RELOAD : CHANNEL_MODULE_RELOAD;
+ }
+ ast_mutex_unlock(&sip_reload_lock);
+ restart_monitor();
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief Part of Asterisk module interface */
+static int reload(void)
+{
+ if (sip_reload(0, 0, NULL))
+ return 0;
+ return 1;
+}
+
+/*! \brief SIP Cli commands definition */
+static struct ast_cli_entry cli_sip[] = {
+ AST_CLI_DEFINE(sip_show_channels, "List active SIP channels/subscriptions"),
+ AST_CLI_DEFINE(sip_show_domains, "List our local SIP domains."),
+ AST_CLI_DEFINE(sip_show_inuse, "List all inuse/limits"),
+ AST_CLI_DEFINE(sip_show_objects, "List all SIP object allocations"),
+ AST_CLI_DEFINE(sip_show_peers, "List defined SIP peers"),
+ AST_CLI_DEFINE(sip_show_registry, "List SIP registration status"),
+ AST_CLI_DEFINE(sip_unregister, "Unregister (force expiration) a SIP peer from the registery\n"),
+ AST_CLI_DEFINE(sip_show_settings, "Show SIP global settings"),
+ AST_CLI_DEFINE(sip_notify, "Send a notify packet to a SIP peer"),
+ AST_CLI_DEFINE(sip_show_channel, "Show detailed SIP channel info"),
+ AST_CLI_DEFINE(sip_show_history, "Show SIP dialog history"),
+ AST_CLI_DEFINE(sip_show_peer, "Show details on specific SIP peer"),
+ AST_CLI_DEFINE(sip_show_users, "List defined SIP users"),
+ AST_CLI_DEFINE(sip_show_user, "Show details on specific SIP user"),
+ AST_CLI_DEFINE(sip_prune_realtime, "Prune cached Realtime users/peers"),
+ AST_CLI_DEFINE(sip_do_debug, "Enable/Disable SIP debugging"),
+ AST_CLI_DEFINE(sip_do_history, "Enable SIP history"),
+ AST_CLI_DEFINE(sip_no_history, "Disable SIP history"),
+ AST_CLI_DEFINE(sip_reload, "Reload SIP configuration"),
+ AST_CLI_DEFINE(sip_show_tcp, "List TCP Connections")
+};
+
+/*! \brief PBX load module - initialization */
+static int load_module(void)
+{
+ ast_verbose("SIP channel loading...\n");
+ ASTOBJ_CONTAINER_INIT(&userl); /* User object list */
+ ASTOBJ_CONTAINER_INIT(&peerl); /* Peer object list */
+ ASTOBJ_CONTAINER_INIT(&regl); /* Registry object list */
+
+ if (!(sched = sched_context_create())) {
+ ast_log(LOG_ERROR, "Unable to create scheduler context\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ if (!(io = io_context_create())) {
+ ast_log(LOG_ERROR, "Unable to create I/O context\n");
+ sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ sip_reloadreason = CHANNEL_MODULE_LOAD;
+
+ if(reload_config(sip_reloadreason)) /* Load the configuration from sip.conf */
+ return AST_MODULE_LOAD_DECLINE;
+
+ /* Prepare the version that does not require DTMF BEGIN frames.
+ * We need to use tricks such as memcpy and casts because the variable
+ * has const fields.
+ */
+ memcpy(&sip_tech_info, &sip_tech, sizeof(sip_tech));
+ memset((void *) &sip_tech_info.send_digit_begin, 0, sizeof(sip_tech_info.send_digit_begin));
+
+ /* Make sure we can register our sip channel type */
+ if (ast_channel_register(&sip_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel type 'SIP'\n");
+ io_context_destroy(io);
+ sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ /* Register all CLI functions for SIP */
+ ast_cli_register_multiple(cli_sip, sizeof(cli_sip)/ sizeof(struct ast_cli_entry));
+
+ /* Tell the RTP subdriver that we're here */
+ ast_rtp_proto_register(&sip_rtp);
+
+ /* Tell the UDPTL subdriver that we're here */
+ ast_udptl_proto_register(&sip_udptl);
+
+ /* Register dialplan applications */
+ ast_register_application(app_dtmfmode, sip_dtmfmode, synopsis_dtmfmode, descrip_dtmfmode);
+ ast_register_application(app_sipaddheader, sip_addheader, synopsis_sipaddheader, descrip_sipaddheader);
+
+ /* Register dialplan functions */
+ ast_custom_function_register(&sip_header_function);
+ ast_custom_function_register(&sippeer_function);
+ ast_custom_function_register(&sipchaninfo_function);
+ ast_custom_function_register(&checksipdomain_function);
+
+ /* Register manager commands */
+ ast_manager_register2("SIPpeers", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_sip_show_peers,
+ "List SIP peers (text format)", mandescr_show_peers);
+ ast_manager_register2("SIPshowpeer", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_sip_show_peer,
+ "Show SIP peer (text format)", mandescr_show_peer);
+ ast_manager_register2("SIPshowregistry", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_show_registry,
+ "Show SIP registrations (text format)", mandescr_show_registry);
+ sip_poke_all_peers();
+ sip_send_all_registers();
+
+ /* And start the monitor for the first time */
+ restart_monitor();
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+/*! \brief PBX unload module API */
+static int unload_module(void)
+{
+ struct sip_pvt *p, *pl;
+ struct sip_threadinfo *th;
+ struct ast_context *con;
+
+ /* First, take us out of the channel type list */
+ ast_channel_unregister(&sip_tech);
+
+ /* Unregister dial plan functions */
+ ast_custom_function_unregister(&sipchaninfo_function);
+ ast_custom_function_unregister(&sippeer_function);
+ ast_custom_function_unregister(&sip_header_function);
+ ast_custom_function_unregister(&checksipdomain_function);
+
+ /* Unregister dial plan applications */
+ ast_unregister_application(app_dtmfmode);
+ ast_unregister_application(app_sipaddheader);
+
+ /* Unregister CLI commands */
+ ast_cli_unregister_multiple(cli_sip, sizeof(cli_sip) / sizeof(struct ast_cli_entry));
+
+ /* Disconnect from the RTP subsystem */
+ ast_rtp_proto_unregister(&sip_rtp);
+
+ /* Disconnect from UDPTL */
+ ast_udptl_proto_unregister(&sip_udptl);
+
+ /* Unregister AMI actions */
+ ast_manager_unregister("SIPpeers");
+ ast_manager_unregister("SIPshowpeer");
+ ast_manager_unregister("SIPshowregistry");
+
+ /* Kill TCP/TLS server threads */
+ if (sip_tcp_desc.master)
+ server_stop(&sip_tcp_desc);
+ if (sip_tls_desc.master)
+ server_stop(&sip_tls_desc);
+
+ /* Kill all existing TCP/TLS threads */
+ AST_LIST_LOCK(&threadl);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&threadl, th, list) {
+ pthread_t thread = th->threadid;
+ th->stop = 1;
+ AST_LIST_UNLOCK(&threadl);
+ pthread_kill(thread, SIGURG);
+ pthread_join(thread, NULL);
+ AST_LIST_LOCK(&threadl);
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&threadl);
+
+ dialoglist_lock();
+ /* Hangup all dialogs if they have an owner */
+ for (p = dialoglist; p ; p = p->next) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ }
+ dialoglist_unlock();
+
+ ast_mutex_lock(&monlock);
+ if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) {
+ pthread_cancel(monitor_thread);
+ pthread_kill(monitor_thread, SIGURG);
+ pthread_join(monitor_thread, NULL);
+ }
+ monitor_thread = AST_PTHREADT_STOP;
+ ast_mutex_unlock(&monlock);
+
+ dialoglist_lock();
+ /* Destroy all the dialogs and free their memory */
+ p = dialoglist;
+ while (p) {
+ pl = p;
+ p = p->next;
+ __sip_destroy(pl, TRUE, TRUE);
+ }
+ dialoglist = NULL;
+ dialoglist_unlock();
+
+ /* Free memory for local network address mask */
+ ast_free_ha(localaddr);
+
+ if (default_tls_cfg.certfile)
+ ast_free(default_tls_cfg.certfile);
+ if (default_tls_cfg.cipher)
+ ast_free(default_tls_cfg.cipher);
+ if (default_tls_cfg.cafile)
+ ast_free(default_tls_cfg.cafile);
+ if (default_tls_cfg.capath)
+ ast_free(default_tls_cfg.capath);
+
+ ASTOBJ_CONTAINER_DESTROYALL(&userl, sip_destroy_user);
+ ASTOBJ_CONTAINER_DESTROY(&userl);
+ ASTOBJ_CONTAINER_DESTROYALL(&peerl, sip_destroy_peer);
+ ASTOBJ_CONTAINER_DESTROY(&peerl);
+ ASTOBJ_CONTAINER_DESTROYALL(&regl, sip_registry_destroy);
+ ASTOBJ_CONTAINER_DESTROY(&regl);
+
+ clear_realm_authentication(authl);
+ clear_sip_domains();
+ close(sipsock);
+ sched_context_destroy(sched);
+ con = ast_context_find(used_context);
+ if (con)
+ ast_context_destroy(con, "SIP");
+
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Session Initiation Protocol (SIP)",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/trunk/channels/chan_skinny.c b/trunk/channels/chan_skinny.c
new file mode 100644
index 000000000..719258178
--- /dev/null
+++ b/trunk/channels/chan_skinny.c
@@ -0,0 +1,6066 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * chan_skinny was developed by Jeremy McNamara & Florian Overkamp
+ * chan_skinny was heavily modified/fixed by North Antara
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Implementation of the Skinny protocol
+ *
+ * \author Jeremy McNamara & Florian Overkamp & North Antara
+ * \ingroup channel_drivers
+ */
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <sys/signal.h>
+#include <signal.h>
+#include <ctype.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/rtp.h"
+#include "asterisk/netsock.h"
+#include "asterisk/acl.h"
+#include "asterisk/callerid.h"
+#include "asterisk/cli.h"
+#include "asterisk/say.h"
+#include "asterisk/cdr.h"
+#include "asterisk/astdb.h"
+#include "asterisk/features.h"
+#include "asterisk/app.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/utils.h"
+#include "asterisk/dsp.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/astobj.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/threadstorage.h"
+#include "asterisk/devicestate.h"
+
+/*************************************
+ * Skinny/Asterisk Protocol Settings *
+ *************************************/
+static const char tdesc[] = "Skinny Client Control Protocol (Skinny)";
+static const char config[] = "skinny.conf";
+
+static int default_capability = AST_FORMAT_ULAW | AST_FORMAT_ALAW;
+static struct ast_codec_pref default_prefs;
+
+enum skinny_codecs {
+ SKINNY_CODEC_ALAW = 2,
+ SKINNY_CODEC_ULAW = 4,
+ SKINNY_CODEC_G723_1 = 9,
+ SKINNY_CODEC_G729A = 12,
+ SKINNY_CODEC_G726_32 = 82, /* XXX Which packing order does this translate to? */
+ SKINNY_CODEC_H261 = 100,
+ SKINNY_CODEC_H263 = 101
+};
+
+#define DEFAULT_SKINNY_PORT 2000
+#define DEFAULT_SKINNY_BACKLOG 2
+#define SKINNY_MAX_PACKET 1000
+
+static unsigned int tos = 0;
+static unsigned int tos_audio = 0;
+static unsigned int tos_video = 0;
+static unsigned int cos = 0;
+static unsigned int cos_audio = 0;
+static unsigned int cos_video = 0;
+
+static int keep_alive = 120;
+static char vmexten[AST_MAX_EXTENSION]; /* Voicemail pilot number */
+static char used_context[AST_MAX_EXTENSION]; /* Voicemail pilot number */
+static char regcontext[AST_MAX_CONTEXT]; /* Context for auto-extension */
+static char date_format[6] = "D-M-Y";
+static char version_id[16] = "P002F202";
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define letohl(x) (x)
+#define letohs(x) (x)
+#define htolel(x) (x)
+#define htoles(x) (x)
+#else
+#if defined(HAVE_BYTESWAP_H)
+#include <byteswap.h>
+#define letohl(x) bswap_32(x)
+#define letohs(x) bswap_16(x)
+#define htolel(x) bswap_32(x)
+#define htoles(x) bswap_16(x)
+#elif defined(HAVE_SYS_ENDIAN_SWAP16)
+#include <sys/endian.h>
+#define letohl(x) __swap32(x)
+#define letohs(x) __swap16(x)
+#define htolel(x) __swap32(x)
+#define htoles(x) __swap16(x)
+#elif defined(HAVE_SYS_ENDIAN_BSWAP16)
+#include <sys/endian.h>
+#define letohl(x) bswap32(x)
+#define letohs(x) bswap16(x)
+#define htolel(x) bswap32(x)
+#define htoles(x) bswap16(x)
+#else
+#define __bswap_16(x) \
+ ((((x) & 0xff00) >> 8) | \
+ (((x) & 0x00ff) << 8))
+#define __bswap_32(x) \
+ ((((x) & 0xff000000) >> 24) | \
+ (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | \
+ (((x) & 0x000000ff) << 24))
+#define letohl(x) __bswap_32(x)
+#define letohs(x) __bswap_16(x)
+#define htolel(x) __bswap_32(x)
+#define htoles(x) __bswap_16(x)
+#endif
+#endif
+
+/*! Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = ""
+};
+static struct ast_jb_conf global_jbconf;
+
+AST_THREADSTORAGE(device2str_threadbuf);
+#define DEVICE2STR_BUFSIZE 15
+
+AST_THREADSTORAGE(control2str_threadbuf);
+#define CONTROL2STR_BUFSIZE 100
+
+/*********************
+ * Protocol Messages *
+ *********************/
+/* message types */
+#define KEEP_ALIVE_MESSAGE 0x0000
+/* no additional struct */
+
+#define REGISTER_MESSAGE 0x0001
+struct register_message {
+ char name[16];
+ uint32_t userId;
+ uint32_t instance;
+ uint32_t ip;
+ uint32_t type;
+ uint32_t maxStreams;
+};
+
+#define IP_PORT_MESSAGE 0x0002
+
+#define KEYPAD_BUTTON_MESSAGE 0x0003
+struct keypad_button_message {
+ uint32_t button;
+ uint32_t lineInstance;
+ uint32_t callReference;
+};
+
+
+#define ENBLOC_CALL_MESSAGE 0x0004
+struct enbloc_call_message {
+ char calledParty[24];
+};
+
+#define STIMULUS_MESSAGE 0x0005
+struct stimulus_message {
+ uint32_t stimulus;
+ uint32_t stimulusInstance;
+ uint32_t callreference;
+};
+
+#define OFFHOOK_MESSAGE 0x0006
+struct offhook_message {
+ uint32_t unknown1;
+ uint32_t unknown2;
+};
+
+#define ONHOOK_MESSAGE 0x0007
+struct onhook_message {
+ uint32_t unknown1;
+ uint32_t unknown2;
+};
+
+#define CAPABILITIES_RES_MESSAGE 0x0010
+struct station_capabilities {
+ uint32_t codec;
+ uint32_t frames;
+ union {
+ char res[8];
+ uint32_t rate;
+ } payloads;
+};
+
+#define SKINNY_MAX_CAPABILITIES 18
+
+struct capabilities_res_message {
+ uint32_t count;
+ struct station_capabilities caps[SKINNY_MAX_CAPABILITIES];
+};
+
+#define SPEED_DIAL_STAT_REQ_MESSAGE 0x000A
+struct speed_dial_stat_req_message {
+ uint32_t speedDialNumber;
+};
+
+#define LINE_STATE_REQ_MESSAGE 0x000B
+struct line_state_req_message {
+ uint32_t lineNumber;
+};
+
+#define TIME_DATE_REQ_MESSAGE 0x000D
+#define BUTTON_TEMPLATE_REQ_MESSAGE 0x000E
+#define VERSION_REQ_MESSAGE 0x000F
+#define SERVER_REQUEST_MESSAGE 0x0012
+
+#define ALARM_MESSAGE 0x0020
+struct alarm_message {
+ uint32_t alarmSeverity;
+ char displayMessage[80];
+ uint32_t alarmParam1;
+ uint32_t alarmParam2;
+};
+
+#define OPEN_RECEIVE_CHANNEL_ACK_MESSAGE 0x0022
+struct open_receive_channel_ack_message {
+ uint32_t status;
+ uint32_t ipAddr;
+ uint32_t port;
+ uint32_t passThruId;
+};
+
+#define SOFT_KEY_SET_REQ_MESSAGE 0x0025
+
+#define SOFT_KEY_EVENT_MESSAGE 0x0026
+struct soft_key_event_message {
+ uint32_t softKeyEvent;
+ uint32_t instance;
+ uint32_t callreference;
+};
+
+#define UNREGISTER_MESSAGE 0x0027
+#define SOFT_KEY_TEMPLATE_REQ_MESSAGE 0x0028
+#define HEADSET_STATUS_MESSAGE 0x002B
+#define REGISTER_AVAILABLE_LINES_MESSAGE 0x002D
+
+#define REGISTER_ACK_MESSAGE 0x0081
+struct register_ack_message {
+ uint32_t keepAlive;
+ char dateTemplate[6];
+ char res[2];
+ uint32_t secondaryKeepAlive;
+ char res2[4];
+};
+
+#define START_TONE_MESSAGE 0x0082
+struct start_tone_message {
+ uint32_t tone;
+ uint32_t space;
+ uint32_t instance;
+ uint32_t reference;
+};
+
+#define STOP_TONE_MESSAGE 0x0083
+struct stop_tone_message {
+ uint32_t instance;
+ uint32_t reference;
+};
+
+#define SET_RINGER_MESSAGE 0x0085
+struct set_ringer_message {
+ uint32_t ringerMode;
+ uint32_t unknown1; /* See notes in transmit_ringer_mode */
+ uint32_t unknown2;
+ uint32_t space[2];
+};
+
+#define SET_LAMP_MESSAGE 0x0086
+struct set_lamp_message {
+ uint32_t stimulus;
+ uint32_t stimulusInstance;
+ uint32_t deviceStimulus;
+};
+
+#define SET_SPEAKER_MESSAGE 0x0088
+struct set_speaker_message {
+ uint32_t mode;
+};
+
+/* XXX When do we need to use this? */
+#define SET_MICROPHONE_MESSAGE 0x0089
+struct set_microphone_message {
+ uint32_t mode;
+};
+
+#define START_MEDIA_TRANSMISSION_MESSAGE 0x008A
+struct media_qualifier {
+ uint32_t precedence;
+ uint32_t vad;
+ uint16_t packets;
+ uint32_t bitRate;
+};
+
+struct start_media_transmission_message {
+ uint32_t conferenceId;
+ uint32_t passThruPartyId;
+ uint32_t remoteIp;
+ uint32_t remotePort;
+ uint32_t packetSize;
+ uint32_t payloadType;
+ struct media_qualifier qualifier;
+ uint32_t space[16];
+};
+
+#define STOP_MEDIA_TRANSMISSION_MESSAGE 0x008B
+struct stop_media_transmission_message {
+ uint32_t conferenceId;
+ uint32_t passThruPartyId;
+ uint32_t space[3];
+};
+
+#define CALL_INFO_MESSAGE 0x008F
+struct call_info_message {
+ char callingPartyName[40];
+ char callingParty[24];
+ char calledPartyName[40];
+ char calledParty[24];
+ uint32_t instance;
+ uint32_t reference;
+ uint32_t type;
+ char originalCalledPartyName[40];
+ char originalCalledParty[24];
+ char lastRedirectingPartyName[40];
+ char lastRedirectingParty[24];
+ uint32_t originalCalledPartyRedirectReason;
+ uint32_t lastRedirectingReason;
+ char callingPartyVoiceMailbox[24];
+ char calledPartyVoiceMailbox[24];
+ char originalCalledPartyVoiceMailbox[24];
+ char lastRedirectingVoiceMailbox[24];
+ uint32_t space[3];
+};
+
+#define FORWARD_STAT_MESSAGE 0x0090
+struct forward_stat_message {
+ uint32_t activeforward;
+ uint32_t lineNumber;
+ uint32_t fwdall;
+ char fwdallnum[24];
+ uint32_t fwdbusy;
+ char fwdbusynum[24];
+ uint32_t fwdnoanswer;
+ char fwdnoanswernum[24];
+};
+
+#define SPEED_DIAL_STAT_RES_MESSAGE 0x0091
+struct speed_dial_stat_res_message {
+ uint32_t speedDialNumber;
+ char speedDialDirNumber[24];
+ char speedDialDisplayName[40];
+};
+
+#define LINE_STAT_RES_MESSAGE 0x0092
+struct line_stat_res_message {
+ uint32_t lineNumber;
+ char lineDirNumber[24];
+ char lineDisplayName[24];
+ uint32_t space[15];
+};
+
+#define DEFINETIMEDATE_MESSAGE 0x0094
+struct definetimedate_message {
+ uint32_t year; /* since 1900 */
+ uint32_t month;
+ uint32_t dayofweek; /* monday = 1 */
+ uint32_t day;
+ uint32_t hour;
+ uint32_t minute;
+ uint32_t seconds;
+ uint32_t milliseconds;
+ uint32_t timestamp;
+};
+
+#define BUTTON_TEMPLATE_RES_MESSAGE 0x0097
+struct button_definition {
+ uint8_t instanceNumber;
+ uint8_t buttonDefinition;
+};
+
+struct button_definition_template {
+ uint8_t buttonDefinition;
+ /* for now, anything between 0xB0 and 0xCF is custom */
+ /*int custom;*/
+};
+
+#define STIMULUS_REDIAL 0x01
+#define STIMULUS_SPEEDDIAL 0x02
+#define STIMULUS_HOLD 0x03
+#define STIMULUS_TRANSFER 0x04
+#define STIMULUS_FORWARDALL 0x05
+#define STIMULUS_FORWARDBUSY 0x06
+#define STIMULUS_FORWARDNOANSWER 0x07
+#define STIMULUS_DISPLAY 0x08
+#define STIMULUS_LINE 0x09
+#define STIMULUS_VOICEMAIL 0x0F
+#define STIMULUS_AUTOANSWER 0x11
+#define STIMULUS_DND 0x3F
+#define STIMULUS_CONFERENCE 0x7D
+#define STIMULUS_CALLPARK 0x7E
+#define STIMULUS_CALLPICKUP 0x7F
+#define STIMULUS_NONE 0xFF
+
+/* Button types */
+#define BT_REDIAL STIMULUS_REDIAL
+#define BT_SPEEDDIAL STIMULUS_SPEEDDIAL
+#define BT_HOLD STIMULUS_HOLD
+#define BT_TRANSFER STIMULUS_TRANSFER
+#define BT_FORWARDALL STIMULUS_FORWARDALL
+#define BT_FORWARDBUSY STIMULUS_FORWARDBUSY
+#define BT_FORWARDNOANSWER STIMULUS_FORWARDNOANSWER
+#define BT_DISPLAY STIMULUS_DISPLAY
+#define BT_LINE STIMULUS_LINE
+#define BT_VOICEMAIL STIMULUS_VOICEMAIL
+#define BT_AUTOANSWER STIMULUS_AUTOANSWER
+#define BT_DND STIMULUS_DND
+#define BT_CONFERENCE STIMULUS_CONFERENCE
+#define BT_CALLPARK STIMULUS_CALLPARK
+#define BT_CALLPICKUP STIMULUS_CALLPICKUP
+#define BT_NONE 0x00
+
+/* Custom button types - add our own between 0xB0 and 0xCF.
+ This may need to be revised in the future,
+ if stimuluses are ever added in this range. */
+#define BT_CUST_LINESPEEDDIAL 0xB0 /* line or speeddial with/without hint */
+#define BT_CUST_LINE 0xB1 /* line or speeddial with hint only */
+
+struct button_template_res_message {
+ uint32_t buttonOffset;
+ uint32_t buttonCount;
+ uint32_t totalButtonCount;
+ struct button_definition definition[42];
+};
+
+#define VERSION_RES_MESSAGE 0x0098
+struct version_res_message {
+ char version[16];
+};
+
+#define DISPLAYTEXT_MESSAGE 0x0099
+struct displaytext_message {
+ char text[40];
+};
+
+#define CLEAR_NOTIFY_MESSAGE 0x0115
+#define CLEAR_DISPLAY_MESSAGE 0x009A
+
+#define CAPABILITIES_REQ_MESSAGE 0x009B
+
+#define REGISTER_REJ_MESSAGE 0x009D
+struct register_rej_message {
+ char errMsg[33];
+};
+
+#define SERVER_RES_MESSAGE 0x009E
+struct server_identifier {
+ char serverName[48];
+};
+
+struct server_res_message {
+ struct server_identifier server[5];
+ uint32_t serverListenPort[5];
+ uint32_t serverIpAddr[5];
+};
+
+#define RESET_MESSAGE 0x009F
+struct reset_message {
+ uint32_t resetType;
+};
+
+#define KEEP_ALIVE_ACK_MESSAGE 0x0100
+
+#define OPEN_RECEIVE_CHANNEL_MESSAGE 0x0105
+struct open_receive_channel_message {
+ uint32_t conferenceId;
+ uint32_t partyId;
+ uint32_t packets;
+ uint32_t capability;
+ uint32_t echo;
+ uint32_t bitrate;
+ uint32_t space[16];
+};
+
+#define CLOSE_RECEIVE_CHANNEL_MESSAGE 0x0106
+struct close_receive_channel_message {
+ uint32_t conferenceId;
+ uint32_t partyId;
+ uint32_t space[2];
+};
+
+#define SOFT_KEY_TEMPLATE_RES_MESSAGE 0x0108
+
+struct soft_key_template_definition {
+ char softKeyLabel[16];
+ uint32_t softKeyEvent;
+};
+
+#define KEYDEF_ONHOOK 0
+#define KEYDEF_CONNECTED 1
+#define KEYDEF_ONHOLD 2
+#define KEYDEF_RINGIN 3
+#define KEYDEF_OFFHOOK 4
+#define KEYDEF_CONNWITHTRANS 5
+#define KEYDEF_DADFD 6 /* Digits After Dialing First Digit */
+#define KEYDEF_CONNWITHCONF 7
+#define KEYDEF_RINGOUT 8
+#define KEYDEF_OFFHOOKWITHFEAT 9
+#define KEYDEF_UNKNOWN 10
+
+#define SOFTKEY_NONE 0x00
+#define SOFTKEY_REDIAL 0x01
+#define SOFTKEY_NEWCALL 0x02
+#define SOFTKEY_HOLD 0x03
+#define SOFTKEY_TRNSFER 0x04
+#define SOFTKEY_CFWDALL 0x05
+#define SOFTKEY_CFWDBUSY 0x06
+#define SOFTKEY_CFWDNOANSWER 0x07
+#define SOFTKEY_BKSPC 0x08
+#define SOFTKEY_ENDCALL 0x09
+#define SOFTKEY_RESUME 0x0A
+#define SOFTKEY_ANSWER 0x0B
+#define SOFTKEY_INFO 0x0C
+#define SOFTKEY_CONFRN 0x0D
+#define SOFTKEY_PARK 0x0E
+#define SOFTKEY_JOIN 0x0F
+#define SOFTKEY_MEETME 0x10
+#define SOFTKEY_PICKUP 0x11
+#define SOFTKEY_GPICKUP 0x12
+#define SOFTKEY_DND 0x13
+#define SOFTKEY_IDIVERT 0x14
+
+struct soft_key_template_definition soft_key_template_default[] = {
+ { "\200\001", SOFTKEY_REDIAL },
+ { "\200\002", SOFTKEY_NEWCALL },
+ { "\200\003", SOFTKEY_HOLD },
+ { "\200\004", SOFTKEY_TRNSFER },
+ { "\200\005", SOFTKEY_CFWDALL },
+ { "\200\006", SOFTKEY_CFWDBUSY },
+ { "\200\007", SOFTKEY_CFWDNOANSWER },
+ { "\200\010", SOFTKEY_BKSPC },
+ { "\200\011", SOFTKEY_ENDCALL },
+ { "\200\012", SOFTKEY_RESUME },
+ { "\200\013", SOFTKEY_ANSWER },
+ { "\200\014", SOFTKEY_INFO },
+ { "\200\015", SOFTKEY_CONFRN },
+ { "\200\016", SOFTKEY_PARK },
+ { "\200\017", SOFTKEY_JOIN },
+ { "\200\020", SOFTKEY_MEETME },
+ { "\200\021", SOFTKEY_PICKUP },
+ { "\200\022", SOFTKEY_GPICKUP },
+ { "\200\077", SOFTKEY_DND },
+ { "\200\120", SOFTKEY_IDIVERT },
+};
+
+/* Localized message "codes" (in octal)
+ Below is en_US (taken from a 7970)
+
+ \200\xxx
+ \000: ???
+ \001: Redial
+ \002: New Call
+ \003: Hold
+ \004: Transfer
+ \005: CFwdALL
+ \006: CFwdBusy
+ \007: CFwdNoAnswer
+ \010: <<
+ \011: EndCall
+ \012: Resume
+ \013: Answer
+ \014: Info
+ \015: Confrn
+ \016: Park
+ \017: Join
+ \020: MeetMe
+ \021: PickUp
+ \022: GPickUp
+ \023: Your current options
+ \024: Off Hook
+ \025: On Hook
+ \026: Ring out
+ \027: From
+ \030: Connected
+ \031: Busy
+ \032: Line In Use
+ \033: Call Waiting
+ \034: Call Transfer
+ \035: Call Park
+ \036: Call Proceed
+ \037: In Use Remote
+ \040: Enter number
+ \041: Call park At
+ \042: Primary Only
+ \043: Temp Fail
+ \044: You Have VoiceMail
+ \045: Forwarded to
+ \046: Can Not Complete Conference
+ \047: No Conference Bridge
+ \050: Can Not Hold Primary Control
+ \051: Invalid Conference Participant
+ \052: In Conference Already
+ \053: No Participant Info
+ \054: Exceed Maximum Parties
+ \055: Key Is Not Active
+ \056: Error No License
+ \057: Error DBConfig
+ \060: Error Database
+ \061: Error Pass Limit
+ \062: Error Unknown
+ \063: Error Mismatch
+ \064: Conference
+ \065: Park Number
+ \066: Private
+ \067: Not Enough Bandwidth
+ \070: Unknown Number
+ \071: RmLstC
+ \072: Voicemail
+ \073: ImmDiv
+ \074: Intrcpt
+ \075: SetWtch
+ \076: TrnsfVM
+ \077: DND
+ \100: DivAll
+ \101: CallBack
+ \102: Network congestion,rerouting
+ \103: Barge
+ \104: Failed to setup Barge
+ \105: Another Barge exists
+ \106: Incompatible device type
+ \107: No Park Number Available
+ \110: CallPark Reversion
+ \111: Service is not Active
+ \112: High Traffic Try Again Later
+ \113: QRT
+ \114: MCID
+ \115: DirTrfr
+ \116: Select
+ \117: ConfList
+ \120: iDivert
+ \121: cBarge
+ \122: Can Not Complete Transfer
+ \123: Can Not Join Calls
+ \124: Mcid Successful
+ \125: Number Not Configured
+ \126: Security Error
+ \127: Video Bandwidth Unavailable
+ \130: VidMode
+ \131: Max Call Duration Timeout
+ \132: Max Hold Duration Timeout
+ \133: OPickUp
+ \134: ???
+ \135: ???
+ \136: ???
+ \137: ???
+ \140: ???
+ \141: External Transfer Restricted
+ \142: ???
+ \143: ???
+ \144: ???
+ \145: Mac Address
+ \146: Host Name
+ \147: Domain Name
+ \150: IP Address
+ \151: Subnet Mask
+ \152: TFTP Server 1
+ \153: Default Router 1
+ \154: Default Router 2
+ \155: Default Router 3
+ \156: Default Router 4
+ \157: Default Router 5
+ \160: DNS Server 1
+ \161: DNS Server 2
+ \162: DNS Server 3
+ \163: DNS Server 4
+ \164: DNS Server 5
+ \165: Operational VLAN Id
+ \166: Admin. VLAN Id
+ \167: CallManager 1
+ \170: CallManager 2
+ \171: CallManager 3
+ \172: CallManager 4
+ \173: CallManager 5
+ \174: Information URL
+ \175: Directories URL
+ \176: Messages URL
+ \177: Services URL
+ */
+
+struct soft_key_definitions {
+ const uint8_t mode;
+ const uint8_t *defaults;
+ const int count;
+};
+
+static const uint8_t soft_key_default_onhook[] = {
+ SOFTKEY_REDIAL,
+ SOFTKEY_NEWCALL,
+ SOFTKEY_CFWDALL,
+ SOFTKEY_CFWDBUSY,
+ SOFTKEY_DND,
+ SOFTKEY_GPICKUP,
+ SOFTKEY_CONFRN,
+};
+
+static const uint8_t soft_key_default_connected[] = {
+ SOFTKEY_HOLD,
+ SOFTKEY_ENDCALL,
+ SOFTKEY_TRNSFER,
+ SOFTKEY_PARK,
+ SOFTKEY_CFWDALL,
+ SOFTKEY_CFWDBUSY,
+};
+
+static const uint8_t soft_key_default_onhold[] = {
+ SOFTKEY_RESUME,
+ SOFTKEY_NEWCALL,
+ SOFTKEY_ENDCALL,
+ SOFTKEY_TRNSFER,
+};
+
+static const uint8_t soft_key_default_ringin[] = {
+ SOFTKEY_ANSWER,
+ SOFTKEY_ENDCALL,
+ SOFTKEY_TRNSFER,
+};
+
+static const uint8_t soft_key_default_offhook[] = {
+ SOFTKEY_REDIAL,
+ SOFTKEY_ENDCALL,
+ SOFTKEY_CFWDALL,
+ SOFTKEY_CFWDBUSY,
+ SOFTKEY_GPICKUP,
+};
+
+static const uint8_t soft_key_default_connwithtrans[] = {
+ SOFTKEY_HOLD,
+ SOFTKEY_ENDCALL,
+ SOFTKEY_TRNSFER,
+ SOFTKEY_PARK,
+ SOFTKEY_CFWDALL,
+ SOFTKEY_CFWDBUSY,
+};
+
+static const uint8_t soft_key_default_dadfd[] = {
+ SOFTKEY_BKSPC,
+ SOFTKEY_ENDCALL,
+};
+
+static const uint8_t soft_key_default_connwithconf[] = {
+ SOFTKEY_NONE,
+};
+
+static const uint8_t soft_key_default_ringout[] = {
+ SOFTKEY_NONE,
+ SOFTKEY_ENDCALL,
+};
+
+static const uint8_t soft_key_default_offhookwithfeat[] = {
+ SOFTKEY_REDIAL,
+ SOFTKEY_ENDCALL,
+};
+
+static const uint8_t soft_key_default_unknown[] = {
+ SOFTKEY_NONE,
+};
+
+static const struct soft_key_definitions soft_key_default_definitions[] = {
+ {KEYDEF_ONHOOK, soft_key_default_onhook, sizeof(soft_key_default_onhook) / sizeof(uint8_t)},
+ {KEYDEF_CONNECTED, soft_key_default_connected, sizeof(soft_key_default_connected) / sizeof(uint8_t)},
+ {KEYDEF_ONHOLD, soft_key_default_onhold, sizeof(soft_key_default_onhold) / sizeof(uint8_t)},
+ {KEYDEF_RINGIN, soft_key_default_ringin, sizeof(soft_key_default_ringin) / sizeof(uint8_t)},
+ {KEYDEF_OFFHOOK, soft_key_default_offhook, sizeof(soft_key_default_offhook) / sizeof(uint8_t)},
+ {KEYDEF_CONNWITHTRANS, soft_key_default_connwithtrans, sizeof(soft_key_default_connwithtrans) / sizeof(uint8_t)},
+ {KEYDEF_DADFD, soft_key_default_dadfd, sizeof(soft_key_default_dadfd) / sizeof(uint8_t)},
+ {KEYDEF_CONNWITHCONF, soft_key_default_connwithconf, sizeof(soft_key_default_connwithconf) / sizeof(uint8_t)},
+ {KEYDEF_RINGOUT, soft_key_default_ringout, sizeof(soft_key_default_ringout) / sizeof(uint8_t)},
+ {KEYDEF_OFFHOOKWITHFEAT, soft_key_default_offhookwithfeat, sizeof(soft_key_default_offhookwithfeat) / sizeof(uint8_t)},
+ {KEYDEF_UNKNOWN, soft_key_default_unknown, sizeof(soft_key_default_unknown) / sizeof(uint8_t)}
+};
+
+struct soft_key_template_res_message {
+ uint32_t softKeyOffset;
+ uint32_t softKeyCount;
+ uint32_t totalSoftKeyCount;
+ struct soft_key_template_definition softKeyTemplateDefinition[32];
+};
+
+#define SOFT_KEY_SET_RES_MESSAGE 0x0109
+
+struct soft_key_set_definition {
+ uint8_t softKeyTemplateIndex[16];
+ uint16_t softKeyInfoIndex[16];
+};
+
+struct soft_key_set_res_message {
+ uint32_t softKeySetOffset;
+ uint32_t softKeySetCount;
+ uint32_t totalSoftKeySetCount;
+ struct soft_key_set_definition softKeySetDefinition[16];
+ uint32_t res;
+};
+
+#define SELECT_SOFT_KEYS_MESSAGE 0x0110
+struct select_soft_keys_message {
+ uint32_t instance;
+ uint32_t reference;
+ uint32_t softKeySetIndex;
+ uint32_t validKeyMask;
+};
+
+#define CALL_STATE_MESSAGE 0x0111
+struct call_state_message {
+ uint32_t callState;
+ uint32_t lineInstance;
+ uint32_t callReference;
+ uint32_t space[3];
+};
+
+#define DISPLAY_PROMPT_STATUS_MESSAGE 0x0112
+struct display_prompt_status_message {
+ uint32_t messageTimeout;
+ char promptMessage[32];
+ uint32_t lineInstance;
+ uint32_t callReference;
+};
+
+#define CLEAR_PROMPT_MESSAGE 0x0113
+struct clear_prompt_message {
+ uint32_t lineInstance;
+ uint32_t callReference;
+};
+
+#define DISPLAY_NOTIFY_MESSAGE 0x0114
+struct display_notify_message {
+ uint32_t displayTimeout;
+ char displayMessage[100];
+};
+
+#define ACTIVATE_CALL_PLANE_MESSAGE 0x0116
+struct activate_call_plane_message {
+ uint32_t lineInstance;
+};
+
+#define DIALED_NUMBER_MESSAGE 0x011D
+struct dialed_number_message {
+ char dialedNumber[24];
+ uint32_t lineInstance;
+ uint32_t callReference;
+};
+
+union skinny_data {
+ struct alarm_message alarm;
+ struct speed_dial_stat_req_message speeddialreq;
+ struct register_message reg;
+ struct register_ack_message regack;
+ struct register_rej_message regrej;
+ struct capabilities_res_message caps;
+ struct version_res_message version;
+ struct button_template_res_message buttontemplate;
+ struct displaytext_message displaytext;
+ struct display_prompt_status_message displaypromptstatus;
+ struct clear_prompt_message clearpromptstatus;
+ struct definetimedate_message definetimedate;
+ struct start_tone_message starttone;
+ struct stop_tone_message stoptone;
+ struct speed_dial_stat_res_message speeddial;
+ struct line_state_req_message line;
+ struct line_stat_res_message linestat;
+ struct soft_key_set_res_message softkeysets;
+ struct soft_key_template_res_message softkeytemplate;
+ struct server_res_message serverres;
+ struct reset_message reset;
+ struct set_lamp_message setlamp;
+ struct set_ringer_message setringer;
+ struct call_state_message callstate;
+ struct keypad_button_message keypad;
+ struct select_soft_keys_message selectsoftkey;
+ struct activate_call_plane_message activatecallplane;
+ struct stimulus_message stimulus;
+ struct offhook_message offhook;
+ struct onhook_message onhook;
+ struct set_speaker_message setspeaker;
+ struct set_microphone_message setmicrophone;
+ struct call_info_message callinfo;
+ struct start_media_transmission_message startmedia;
+ struct stop_media_transmission_message stopmedia;
+ struct open_receive_channel_message openreceivechannel;
+ struct open_receive_channel_ack_message openreceivechannelack;
+ struct close_receive_channel_message closereceivechannel;
+ struct display_notify_message displaynotify;
+ struct dialed_number_message dialednumber;
+ struct soft_key_event_message softkeyeventmessage;
+ struct enbloc_call_message enbloccallmessage;
+ struct forward_stat_message forwardstat;
+};
+
+/* packet composition */
+struct skinny_req {
+ int len;
+ int res;
+ int e;
+ union skinny_data data;
+};
+
+/* XXX This is the combined size of the variables above. (len, res, e)
+ If more are added, this MUST change.
+ (sizeof(skinny_req) - sizeof(skinny_data)) DOES NOT WORK on all systems (amd64?). */
+int skinny_header_size = 12;
+
+/*****************************
+ * Asterisk specific globals *
+ *****************************/
+
+static int skinnydebug = 0;
+
+/* a hostname, portnumber, socket and such is usefull for VoIP protocols */
+static struct sockaddr_in bindaddr;
+static char ourhost[256];
+static int ourport;
+static struct in_addr __ourip;
+struct ast_hostent ahp;
+struct hostent *hp;
+static int skinnysock = -1;
+static pthread_t accept_t;
+static char context[AST_MAX_CONTEXT] = "default";
+static char language[MAX_LANGUAGE] = "";
+static char mohinterpret[MAX_MUSICCLASS] = "default";
+static char mohsuggest[MAX_MUSICCLASS] = "";
+static char cid_num[AST_MAX_EXTENSION] = "";
+static char cid_name[AST_MAX_EXTENSION] = "";
+static char linelabel[AST_MAX_EXTENSION] ="";
+static int nat = 0;
+static ast_group_t cur_callergroup = 0;
+static ast_group_t cur_pickupgroup = 0;
+static int immediate = 0;
+static int callwaiting = 0;
+static int callreturn = 0;
+static int threewaycalling = 0;
+static int mwiblink = 0;
+/* This is for flashhook transfers */
+static int transfer = 0;
+static int cancallforward = 0;
+/* static int busycount = 3;*/
+static char accountcode[AST_MAX_ACCOUNT_CODE] = "";
+static char mailbox[AST_MAX_EXTENSION];
+static char regexten[AST_MAX_EXTENSION];
+static int amaflags = 0;
+static int callnums = 1;
+static int canreinvite = 0;
+
+#define SKINNY_DEVICE_UNKNOWN -1
+#define SKINNY_DEVICE_NONE 0
+#define SKINNY_DEVICE_30SPPLUS 1
+#define SKINNY_DEVICE_12SPPLUS 2
+#define SKINNY_DEVICE_12SP 3
+#define SKINNY_DEVICE_12 4
+#define SKINNY_DEVICE_30VIP 5
+#define SKINNY_DEVICE_7910 6
+#define SKINNY_DEVICE_7960 7
+#define SKINNY_DEVICE_7940 8
+#define SKINNY_DEVICE_7935 9
+#define SKINNY_DEVICE_ATA186 12 /* Cisco ATA-186 */
+#define SKINNY_DEVICE_7941 115
+#define SKINNY_DEVICE_7971 119
+#define SKINNY_DEVICE_7985 302
+#define SKINNY_DEVICE_7911 307
+#define SKINNY_DEVICE_7961GE 308
+#define SKINNY_DEVICE_7941GE 309
+#define SKINNY_DEVICE_7921 365
+#define SKINNY_DEVICE_7905 20000
+#define SKINNY_DEVICE_7920 30002
+#define SKINNY_DEVICE_7970 30006
+#define SKINNY_DEVICE_7912 30007
+#define SKINNY_DEVICE_7902 30008
+#define SKINNY_DEVICE_CIPC 30016 /* Cisco IP Communicator */
+#define SKINNY_DEVICE_7961 30018
+#define SKINNY_DEVICE_7936 30019
+#define SKINNY_DEVICE_SCCPGATEWAY_AN 30027 /* ??? */
+#define SKINNY_DEVICE_SCCPGATEWAY_BRI 30028 /* ??? */
+
+#define SKINNY_SPEAKERON 1
+#define SKINNY_SPEAKEROFF 2
+
+#define SKINNY_MICON 1
+#define SKINNY_MICOFF 2
+
+#define SKINNY_OFFHOOK 1
+#define SKINNY_ONHOOK 2
+#define SKINNY_RINGOUT 3
+#define SKINNY_RINGIN 4
+#define SKINNY_CONNECTED 5
+#define SKINNY_BUSY 6
+#define SKINNY_CONGESTION 7
+#define SKINNY_HOLD 8
+#define SKINNY_CALLWAIT 9
+#define SKINNY_TRANSFER 10
+#define SKINNY_PARK 11
+#define SKINNY_PROGRESS 12
+#define SKINNY_CALLREMOTEMULTILINE 13
+#define SKINNY_INVALID 14
+
+#define SKINNY_SILENCE 0x00
+#define SKINNY_DIALTONE 0x21
+#define SKINNY_BUSYTONE 0x23
+#define SKINNY_ALERT 0x24
+#define SKINNY_REORDER 0x25
+#define SKINNY_CALLWAITTONE 0x2D
+#define SKINNY_NOTONE 0x7F
+
+#define SKINNY_LAMP_OFF 1
+#define SKINNY_LAMP_ON 2
+#define SKINNY_LAMP_WINK 3
+#define SKINNY_LAMP_FLASH 4
+#define SKINNY_LAMP_BLINK 5
+
+#define SKINNY_RING_OFF 1
+#define SKINNY_RING_INSIDE 2
+#define SKINNY_RING_OUTSIDE 3
+#define SKINNY_RING_FEATURE 4
+
+#define SKINNY_CFWD_ALL (1 << 0)
+#define SKINNY_CFWD_BUSY (1 << 1)
+#define SKINNY_CFWD_NOANSWER (1 << 2)
+
+#define TYPE_TRUNK 1
+#define TYPE_LINE 2
+
+/* Skinny rtp stream modes. Do we really need this? */
+#define SKINNY_CX_SENDONLY 0
+#define SKINNY_CX_RECVONLY 1
+#define SKINNY_CX_SENDRECV 2
+#define SKINNY_CX_CONF 3
+#define SKINNY_CX_CONFERENCE 3
+#define SKINNY_CX_MUTE 4
+#define SKINNY_CX_INACTIVE 4
+
+#if 0
+static char *skinny_cxmodes[] = {
+ "sendonly",
+ "recvonly",
+ "sendrecv",
+ "confrnce",
+ "inactive"
+};
+#endif
+
+/* driver scheduler */
+static struct sched_context *sched = NULL;
+static struct io_context *io;
+
+/* Protect the monitoring thread, so only one process can kill or start it, and not
+ when it's doing something critical. */
+AST_MUTEX_DEFINE_STATIC(monlock);
+/* Protect the network socket */
+AST_MUTEX_DEFINE_STATIC(netlock);
+/* Protect the session list */
+AST_MUTEX_DEFINE_STATIC(sessionlock);
+/* Protect the device list */
+AST_MUTEX_DEFINE_STATIC(devicelock);
+#if 0
+/* Protect the paging device list */
+AST_MUTEX_DEFINE_STATIC(pagingdevicelock);
+#endif
+
+/* This is the thread for the monitor which checks for input on the channels
+ which are not currently in use. */
+static pthread_t monitor_thread = AST_PTHREADT_NULL;
+
+/* Wait up to 16 seconds for first digit */
+static int firstdigittimeout = 16000;
+
+/* How long to wait for following digits */
+static int gendigittimeout = 8000;
+
+/* How long to wait for an extra digit, if there is an ambiguous match */
+static int matchdigittimeout = 3000;
+
+struct skinny_subchannel {
+ ast_mutex_t lock;
+ struct ast_channel *owner;
+ struct ast_rtp *rtp;
+ struct ast_rtp *vrtp;
+ unsigned int callid;
+ /* time_t lastouttime; */ /* Unused */
+ int progress;
+ int ringing;
+ int onhold;
+ /* int lastout; */ /* Unused */
+ int cxmode;
+ int nat;
+ int outgoing;
+ int alreadygone;
+
+ struct skinny_subchannel *next;
+ struct skinny_line *parent;
+};
+
+struct skinny_line {
+ ast_mutex_t lock;
+ char name[80];
+ char label[24]; /* Label that shows next to the line buttons */
+ char accountcode[AST_MAX_ACCOUNT_CODE];
+ char exten[AST_MAX_EXTENSION]; /* Extension where to start */
+ char context[AST_MAX_CONTEXT];
+ char language[MAX_LANGUAGE];
+ char cid_num[AST_MAX_EXTENSION]; /* Caller*ID */
+ char cid_name[AST_MAX_EXTENSION]; /* Caller*ID */
+ char lastcallerid[AST_MAX_EXTENSION]; /* Last Caller*ID */
+ int cfwdtype;
+ char call_forward_all[AST_MAX_EXTENSION];
+ char call_forward_busy[AST_MAX_EXTENSION];
+ char call_forward_noanswer[AST_MAX_EXTENSION];
+ char mailbox[AST_MAX_EXTENSION];
+ char vmexten[AST_MAX_EXTENSION];
+ char regexten[AST_MAX_EXTENSION]; /* Extension for auto-extensions */
+ char regcontext[AST_MAX_CONTEXT]; /* Context for auto-extensions */
+ char mohinterpret[MAX_MUSICCLASS];
+ char mohsuggest[MAX_MUSICCLASS];
+ char lastnumberdialed[AST_MAX_EXTENSION]; /* Last number that was dialed - used for redial */
+ int curtone; /* Current tone being played */
+ ast_group_t callgroup;
+ ast_group_t pickupgroup;
+ int callwaiting;
+ int transfer;
+ int threewaycalling;
+ int mwiblink;
+ int cancallforward;
+ int getforward;
+ int callreturn;
+ int dnd; /* How does this affect callwait? Do we just deny a skinny_request if we're dnd? */
+ int hascallerid;
+ int hidecallerid;
+ int amaflags;
+ int type;
+ int instance;
+ int group;
+ int needdestroy;
+ int capability;
+ int nonCodecCapability;
+ int onhooktime;
+ int msgstate; /* voicemail message state */
+ int immediate;
+ int hookstate;
+ int nat;
+ int canreinvite;
+
+ struct ast_codec_pref prefs;
+ struct skinny_subchannel *sub;
+ struct skinny_line *next;
+ struct skinny_device *parent;
+ struct ast_variable *chanvars; /*!< Channel variables to set for inbound call */
+};
+
+struct skinny_speeddial {
+ ast_mutex_t lock;
+ char label[42];
+ char context[AST_MAX_CONTEXT];
+ char exten[AST_MAX_EXTENSION];
+ int instance;
+ int stateid;
+ int laststate;
+ int isHint;
+
+ struct skinny_speeddial *next;
+ struct skinny_device *parent;
+};
+
+struct skinny_addon {
+ ast_mutex_t lock;
+ char type[10];
+
+ struct skinny_addon *next;
+ struct skinny_device *parent;
+};
+
+static struct skinny_device {
+ /* A device containing one or more lines */
+ char name[80];
+ char id[16];
+ char version_id[16];
+ char exten[AST_MAX_EXTENSION]; /* Cruddy variable name, pick a better one */
+ int type;
+ int registered;
+ int lastlineinstance;
+ int lastcallreference;
+ int capability;
+ struct sockaddr_in addr;
+ struct in_addr ourip;
+ struct skinny_line *lines;
+ struct skinny_speeddial *speeddials;
+ struct skinny_addon *addons;
+ struct ast_codec_pref prefs;
+ struct ast_ha *ha;
+ struct skinnysession *session;
+ struct skinny_device *next;
+} *devices = NULL;
+
+struct skinny_paging_device {
+ char name[80];
+ char id[16];
+ struct skinny_device ** devices;
+ struct skinny_paging_device *next;
+};
+
+static struct skinnysession {
+ pthread_t t;
+ ast_mutex_t lock;
+ struct sockaddr_in sin;
+ int fd;
+ char inbuf[SKINNY_MAX_PACKET];
+ char outbuf[SKINNY_MAX_PACKET];
+ struct skinny_device *device;
+ struct skinnysession *next;
+} *sessions = NULL;
+
+static struct ast_channel *skinny_request(const char *type, int format, void *data, int *cause);
+static int skinny_devicestate(void *data);
+static int skinny_call(struct ast_channel *ast, char *dest, int timeout);
+static int skinny_hangup(struct ast_channel *ast);
+static int skinny_answer(struct ast_channel *ast);
+static struct ast_frame *skinny_read(struct ast_channel *ast);
+static int skinny_write(struct ast_channel *ast, struct ast_frame *frame);
+static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen);
+static int skinny_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static int skinny_senddigit_begin(struct ast_channel *ast, char digit);
+static int skinny_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int handle_time_date_req_message(struct skinny_req *req, struct skinnysession *s);
+
+static const struct ast_channel_tech skinny_tech = {
+ .type = "Skinny",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_AUDIO_MASK,
+ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
+ .requester = skinny_request,
+ .devicestate = skinny_devicestate,
+ .call = skinny_call,
+ .hangup = skinny_hangup,
+ .answer = skinny_answer,
+ .read = skinny_read,
+ .write = skinny_write,
+ .indicate = skinny_indicate,
+ .fixup = skinny_fixup,
+ .send_digit_begin = skinny_senddigit_begin,
+ .send_digit_end = skinny_senddigit_end,
+ .bridge = ast_rtp_bridge,
+};
+
+static int skinny_extensionstate_cb(char *context, char* exten, int state, void *data);
+
+static void *get_button_template(struct skinnysession *s, struct button_definition_template *btn)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_addon *a = d->addons;
+ int i;
+
+ switch (d->type) {
+ case SKINNY_DEVICE_30SPPLUS:
+ case SKINNY_DEVICE_30VIP:
+ /* 13 rows, 2 columns */
+ for (i = 0; i < 4; i++)
+ (btn++)->buttonDefinition = BT_CUST_LINE;
+ (btn++)->buttonDefinition = BT_REDIAL;
+ (btn++)->buttonDefinition = BT_VOICEMAIL;
+ (btn++)->buttonDefinition = BT_CALLPARK;
+ (btn++)->buttonDefinition = BT_FORWARDALL;
+ (btn++)->buttonDefinition = BT_CONFERENCE;
+ for (i = 0; i < 4; i++)
+ (btn++)->buttonDefinition = BT_NONE;
+ for (i = 0; i < 13; i++)
+ (btn++)->buttonDefinition = BT_SPEEDDIAL;
+
+ break;
+ case SKINNY_DEVICE_12SPPLUS:
+ case SKINNY_DEVICE_12SP:
+ case SKINNY_DEVICE_12:
+ /* 6 rows, 2 columns */
+ for (i = 0; i < 2; i++)
+ (btn++)->buttonDefinition = BT_CUST_LINE;
+ for (i = 0; i < 4; i++)
+ (btn++)->buttonDefinition = BT_SPEEDDIAL;
+ (btn++)->buttonDefinition = BT_HOLD;
+ (btn++)->buttonDefinition = BT_REDIAL;
+ (btn++)->buttonDefinition = BT_TRANSFER;
+ (btn++)->buttonDefinition = BT_FORWARDALL;
+ (btn++)->buttonDefinition = BT_CALLPARK;
+ (btn++)->buttonDefinition = BT_VOICEMAIL;
+ break;
+ case SKINNY_DEVICE_7910:
+ (btn++)->buttonDefinition = BT_LINE;
+ (btn++)->buttonDefinition = BT_HOLD;
+ (btn++)->buttonDefinition = BT_TRANSFER;
+ (btn++)->buttonDefinition = BT_DISPLAY;
+ (btn++)->buttonDefinition = BT_VOICEMAIL;
+ (btn++)->buttonDefinition = BT_CONFERENCE;
+ (btn++)->buttonDefinition = BT_FORWARDALL;
+ for (i = 0; i < 2; i++)
+ (btn++)->buttonDefinition = BT_SPEEDDIAL;
+ (btn++)->buttonDefinition = BT_REDIAL;
+ break;
+ case SKINNY_DEVICE_7960:
+ case SKINNY_DEVICE_7961:
+ case SKINNY_DEVICE_7961GE:
+ for (i = 0; i < 6; i++)
+ (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL;
+ break;
+ case SKINNY_DEVICE_7940:
+ case SKINNY_DEVICE_7941:
+ case SKINNY_DEVICE_7941GE:
+ for (i = 0; i < 2; i++)
+ (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL;
+ break;
+ case SKINNY_DEVICE_7935:
+ case SKINNY_DEVICE_7936:
+ for (i = 0; i < 2; i++)
+ (btn++)->buttonDefinition = BT_LINE;
+ break;
+ case SKINNY_DEVICE_ATA186:
+ (btn++)->buttonDefinition = BT_LINE;
+ break;
+ case SKINNY_DEVICE_7970:
+ case SKINNY_DEVICE_7971:
+ case SKINNY_DEVICE_CIPC:
+ for (i = 0; i < 8; i++)
+ (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL;
+ break;
+ case SKINNY_DEVICE_7985:
+ /* XXX I have no idea what the buttons look like on these. */
+ ast_log(LOG_WARNING, "Unsupported device type '%d (7985)' found.\n", d->type);
+ break;
+ case SKINNY_DEVICE_7912:
+ case SKINNY_DEVICE_7911:
+ case SKINNY_DEVICE_7905:
+ (btn++)->buttonDefinition = BT_LINE;
+ (btn++)->buttonDefinition = BT_HOLD;
+ break;
+ case SKINNY_DEVICE_7920:
+ case SKINNY_DEVICE_7921:
+ /* XXX I don't know if this is right. */
+ for (i = 0; i < 4; i++)
+ (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL;
+ break;
+ case SKINNY_DEVICE_7902:
+ ast_log(LOG_WARNING, "Unsupported device type '%d (7902)' found.\n", d->type);
+ break;
+ case SKINNY_DEVICE_SCCPGATEWAY_AN:
+ case SKINNY_DEVICE_SCCPGATEWAY_BRI:
+ ast_log(LOG_WARNING, "Unsupported device type '%d (SCCP gateway)' found.\n", d->type);
+ break;
+ default:
+ ast_log(LOG_WARNING, "Unknown device type '%d' found.\n", d->type);
+ break;
+ }
+
+ for (a = d->addons; a; a = a->next) {
+ if (!strcasecmp(a->type, "7914")) {
+ for (i = 0; i < 14; i++)
+ (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL;
+ } else {
+ ast_log(LOG_WARNING, "Unknown addon type '%s' found. Skipping.\n", a->type);
+ }
+ }
+
+ return btn;
+}
+
+static struct skinny_req *req_alloc(size_t size, int response_message)
+{
+ struct skinny_req *req;
+
+ if (!(req = ast_calloc(1, skinny_header_size + size + 4)))
+ return NULL;
+
+ req->len = htolel(size+4);
+ req->e = htolel(response_message);
+
+ return req;
+}
+
+static struct skinny_line *find_line_by_instance(struct skinny_device *d, int instance)
+{
+ struct skinny_line *l;
+
+ /*Dialing from on hook or on a 7920 uses instance 0 in requests
+ but we need to start looking at instance 1 */
+
+ if (!instance)
+ instance = 1;
+
+ for (l = d->lines; l; l = l->next) {
+ if (l->instance == instance)
+ break;
+ }
+
+ if (!l) {
+ ast_log(LOG_WARNING, "Could not find line with instance '%d' on device '%s'\n", instance, d->name);
+ }
+ return l;
+}
+
+static struct skinny_line *find_line_by_name(const char *dest)
+{
+ struct skinny_line *l;
+ struct skinny_line *tmpl = NULL;
+ struct skinny_device *d;
+ char line[256];
+ char *at;
+ char *device;
+ int checkdevice = 0;
+
+ ast_copy_string(line, dest, sizeof(line));
+ at = strchr(line, '@');
+ if (at)
+ *at++ = '\0';
+ device = at;
+
+ if (!ast_strlen_zero(device))
+ checkdevice = 1;
+
+ ast_mutex_lock(&devicelock);
+ for (d = devices; d; d = d->next) {
+ if (checkdevice && tmpl)
+ break;
+ else if (!checkdevice) {
+ /* This is a match, since we're checking for line on every device. */
+ } else if (!strcasecmp(d->name, device)) {
+ if (skinnydebug)
+ ast_verb(2, "Found device: %s\n", d->name);
+ } else
+ continue;
+
+ /* Found the device (or we don't care which device) */
+ for (l = d->lines; l; l = l->next) {
+ /* Search for the right line */
+ if (!strcasecmp(l->name, line)) {
+ if (tmpl) {
+ ast_verb(2, "Ambiguous line name: %s\n", line);
+ ast_mutex_unlock(&devicelock);
+ return NULL;
+ } else
+ tmpl = l;
+ }
+ }
+ }
+ ast_mutex_unlock(&devicelock);
+ return tmpl;
+}
+
+/*!
+ * implement the setvar config line
+ */
+static struct ast_variable *add_var(const char *buf, struct ast_variable *list)
+{
+ struct ast_variable *tmpvar = NULL;
+ char *varname = ast_strdupa(buf), *varval = NULL;
+
+ if ((varval = strchr(varname,'='))) {
+ *varval++ = '\0';
+ if ((tmpvar = ast_variable_new(varname, varval, ""))) {
+ tmpvar->next = list;
+ list = tmpvar;
+ }
+ }
+ return list;
+}
+
+/* It's quicker/easier to find the subchannel when we know the instance number too */
+static struct skinny_subchannel *find_subchannel_by_instance_reference(struct skinny_device *d, int instance, int reference)
+{
+ struct skinny_line *l = find_line_by_instance(d, instance);
+ struct skinny_subchannel *sub;
+
+ if (!l) {
+ return NULL;
+ }
+
+ /* 7920 phones set call reference to 0, so use the first
+ sub-channel on the list.
+ This MIGHT need more love to be right */
+ if (!reference)
+ sub = l->sub;
+ else {
+ for (sub = l->sub; sub; sub = sub->next) {
+ if (sub->callid == reference)
+ break;
+ }
+ }
+ if (!sub) {
+ ast_log(LOG_WARNING, "Could not find subchannel with reference '%d' on '%s'\n", reference, d->name);
+ }
+ return sub;
+}
+
+/* Find the subchannel when we only have the callid - this shouldn't happen often */
+static struct skinny_subchannel *find_subchannel_by_reference(struct skinny_device *d, int reference)
+{
+ struct skinny_line *l;
+ struct skinny_subchannel *sub = NULL;
+
+ for (l = d->lines; l; l = l->next) {
+ for (sub = l->sub; sub; sub = sub->next) {
+ if (sub->callid == reference)
+ break;
+ }
+ if (sub)
+ break;
+ }
+
+ if (!l) {
+ ast_log(LOG_WARNING, "Could not find any lines that contained a subchannel with reference '%d' on device '%s'\n", reference, d->name);
+ } else {
+ if (!sub) {
+ ast_log(LOG_WARNING, "Could not find subchannel with reference '%d' on '%s@%s'\n", reference, l->name, d->name);
+ }
+ }
+ return sub;
+}
+
+static struct skinny_speeddial *find_speeddial_by_instance(struct skinny_device *d, int instance, int isHint)
+{
+ struct skinny_speeddial *sd;
+
+ for (sd = d->speeddials; sd; sd = sd->next) {
+ if (sd->isHint == isHint && sd->instance == instance)
+ break;
+ }
+
+ if (!sd) {
+ ast_log(LOG_WARNING, "Could not find speeddial with instance '%d' on device '%s'\n", instance, d->name);
+ }
+ return sd;
+}
+
+static int codec_skinny2ast(enum skinny_codecs skinnycodec)
+{
+ switch (skinnycodec) {
+ case SKINNY_CODEC_ALAW:
+ return AST_FORMAT_ALAW;
+ case SKINNY_CODEC_ULAW:
+ return AST_FORMAT_ULAW;
+ case SKINNY_CODEC_G723_1:
+ return AST_FORMAT_G723_1;
+ case SKINNY_CODEC_G729A:
+ return AST_FORMAT_G729A;
+ case SKINNY_CODEC_G726_32:
+ return AST_FORMAT_G726_AAL2; /* XXX Is this right? */
+ case SKINNY_CODEC_H261:
+ return AST_FORMAT_H261;
+ case SKINNY_CODEC_H263:
+ return AST_FORMAT_H263;
+ default:
+ return 0;
+ }
+}
+
+static int codec_ast2skinny(int astcodec)
+{
+ switch (astcodec) {
+ case AST_FORMAT_ALAW:
+ return SKINNY_CODEC_ALAW;
+ case AST_FORMAT_ULAW:
+ return SKINNY_CODEC_ULAW;
+ case AST_FORMAT_G723_1:
+ return SKINNY_CODEC_G723_1;
+ case AST_FORMAT_G729A:
+ return SKINNY_CODEC_G729A;
+ case AST_FORMAT_G726_AAL2: /* XXX Is this right? */
+ return SKINNY_CODEC_G726_32;
+ case AST_FORMAT_H261:
+ return SKINNY_CODEC_H261;
+ case AST_FORMAT_H263:
+ return SKINNY_CODEC_H263;
+ default:
+ return 0;
+ }
+}
+
+static int set_callforwards(struct skinny_line *l, const char *cfwd, int cfwdtype)
+{
+ if (!l)
+ return 0;
+
+ if (!ast_strlen_zero(cfwd)) {
+ if (cfwdtype & SKINNY_CFWD_ALL) {
+ l->cfwdtype |= SKINNY_CFWD_ALL;
+ ast_copy_string(l->call_forward_all, cfwd, sizeof(l->call_forward_all));
+ }
+ if (cfwdtype & SKINNY_CFWD_BUSY) {
+ l->cfwdtype |= SKINNY_CFWD_BUSY;
+ ast_copy_string(l->call_forward_busy, cfwd, sizeof(l->call_forward_busy));
+ }
+ if (cfwdtype & SKINNY_CFWD_NOANSWER) {
+ l->cfwdtype |= SKINNY_CFWD_NOANSWER;
+ ast_copy_string(l->call_forward_noanswer, cfwd, sizeof(l->call_forward_noanswer));
+ }
+ } else {
+ if (cfwdtype & SKINNY_CFWD_ALL) {
+ l->cfwdtype &= ~SKINNY_CFWD_ALL;
+ memset(l->call_forward_all, 0, sizeof(l->call_forward_all));
+ }
+ if (cfwdtype & SKINNY_CFWD_BUSY) {
+ l->cfwdtype &= ~SKINNY_CFWD_BUSY;
+ memset(l->call_forward_busy, 0, sizeof(l->call_forward_busy));
+ }
+ if (cfwdtype & SKINNY_CFWD_NOANSWER) {
+ l->cfwdtype &= ~SKINNY_CFWD_NOANSWER;
+ memset(l->call_forward_noanswer, 0, sizeof(l->call_forward_noanswer));
+ }
+ }
+ return l->cfwdtype;
+}
+
+static void cleanup_stale_contexts(char *new, char *old)
+{
+ char *oldcontext, *newcontext, *stalecontext, *stringp, newlist[AST_MAX_CONTEXT];
+
+ while ((oldcontext = strsep(&old, "&"))) {
+ stalecontext = '\0';
+ ast_copy_string(newlist, new, sizeof(newlist));
+ stringp = newlist;
+ while ((newcontext = strsep(&stringp, "&"))) {
+ if (strcmp(newcontext, oldcontext) == 0) {
+ /* This is not the context you're looking for */
+ stalecontext = '\0';
+ break;
+ } else if (strcmp(newcontext, oldcontext)) {
+ stalecontext = oldcontext;
+ }
+
+ }
+ if (stalecontext)
+ ast_context_destroy(ast_context_find(stalecontext), "Skinny");
+ }
+}
+
+static void register_exten(struct skinny_line *l)
+{
+ char multi[256];
+ char *stringp, *ext, *context;
+
+ if (ast_strlen_zero(regcontext))
+ return;
+
+ ast_copy_string(multi, S_OR(l->regexten, l->name), sizeof(multi));
+ stringp = multi;
+ while ((ext = strsep(&stringp, "&"))) {
+ if ((context = strchr(ext, '@'))) {
+ *context++ = '\0'; /* split ext@context */
+ if (!ast_context_find(context)) {
+ ast_log(LOG_WARNING, "Context %s must exist in regcontext= in skinny.conf!\n", context);
+ continue;
+ }
+ } else {
+ context = regcontext;
+ }
+ ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop",
+ ast_strdup(l->name), ast_free_ptr, "Skinny");
+ }
+}
+
+static void unregister_exten(struct skinny_line *l)
+{
+ char multi[256];
+ char *stringp, *ext, *context;
+
+ if (ast_strlen_zero(regcontext))
+ return;
+
+ ast_copy_string(multi, S_OR(l->regexten, l->name), sizeof(multi));
+ stringp = multi;
+ while ((ext = strsep(&stringp, "&"))) {
+ if ((context = strchr(ext, '@'))) {
+ *context++ = '\0'; /* split ext@context */
+ if (!ast_context_find(context)) {
+ ast_log(LOG_WARNING, "Context %s must exist in regcontext= in skinny.conf!\n", context);
+ continue;
+ }
+ } else {
+ context = regcontext;
+ }
+ ast_context_remove_extension(context, ext, 1, NULL);
+ }
+}
+
+static int skinny_register(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d;
+ struct skinny_line *l;
+ struct skinny_speeddial *sd;
+ struct sockaddr_in sin;
+ socklen_t slen;
+
+ ast_mutex_lock(&devicelock);
+ for (d = devices; d; d = d->next) {
+ if (!strcasecmp(req->data.reg.name, d->id)
+ && ast_apply_ha(d->ha, &(s->sin))) {
+ s->device = d;
+ d->type = letohl(req->data.reg.type);
+ if (ast_strlen_zero(d->version_id)) {
+ ast_copy_string(d->version_id, version_id, sizeof(d->version_id));
+ }
+ d->registered = 1;
+ d->session = s;
+
+ slen = sizeof(sin);
+ if (getsockname(s->fd, (struct sockaddr *)&sin, &slen)) {
+ ast_log(LOG_WARNING, "Cannot get socket name\n");
+ sin.sin_addr = __ourip;
+ }
+ d->ourip = sin.sin_addr;
+
+ for (sd = d->speeddials; sd; sd = sd->next) {
+ sd->stateid = ast_extension_state_add(sd->context, sd->exten, skinny_extensionstate_cb, sd);
+ }
+ for (l = d->lines; l; l = l->next) {
+ register_exten(l);
+ ast_device_state_changed("Skinny/%s@%s", l->name, d->name);
+ }
+ break;
+ }
+ }
+ ast_mutex_unlock(&devicelock);
+ if (!d) {
+ return 0;
+ }
+ return 1;
+}
+
+static int skinny_unregister(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d;
+ struct skinny_line *l;
+ struct skinny_speeddial *sd;
+
+ d = s->device;
+
+ if (d) {
+ d->session = NULL;
+ d->registered = 0;
+
+ for (sd = d->speeddials; sd; sd = sd->next) {
+ if (sd->stateid > -1)
+ ast_extension_state_del(sd->stateid, NULL);
+ }
+ for (l = d->lines; l; l = l->next) {
+ unregister_exten(l);
+ ast_device_state_changed("Skinny/%s@%s", l->name, d->name);
+ }
+ }
+
+ return -1; /* main loop will destroy the session */
+}
+
+static int transmit_response(struct skinnysession *s, struct skinny_req *req)
+{
+ int res = 0;
+
+ if (!s) {
+ ast_log(LOG_WARNING, "Asked to transmit to a non-existant session!\n");
+ return -1;
+ }
+
+ ast_mutex_lock(&s->lock);
+
+ if (skinnydebug)
+ ast_log(LOG_VERBOSE, "writing packet type %04X (%d bytes) to socket %d\n", letohl(req->e), letohl(req->len)+8, s->fd);
+
+ if (letohl(req->len > SKINNY_MAX_PACKET) || letohl(req->len < 0)) {
+ ast_log(LOG_WARNING, "transmit_response: the length of the request is out of bounds\n");
+ return -1;
+ }
+
+ memset(s->outbuf,0,sizeof(s->outbuf));
+ memcpy(s->outbuf, req, skinny_header_size);
+ memcpy(s->outbuf+skinny_header_size, &req->data, letohl(req->len));
+
+ res = write(s->fd, s->outbuf, letohl(req->len)+8);
+
+ if (res != letohl(req->len)+8) {
+ ast_log(LOG_WARNING, "Transmit: write only sent %d out of %d bytes: %s\n", res, letohl(req->len)+8, strerror(errno));
+ if (res == -1) {
+ if (skinnydebug)
+ ast_log(LOG_WARNING, "Transmit: Skinny Client was lost, unregistering\n");
+ skinny_unregister(NULL, s);
+ }
+
+ }
+
+ ast_mutex_unlock(&s->lock);
+ return 1;
+}
+
+static void transmit_speaker_mode(struct skinnysession *s, int mode)
+{
+ struct skinny_req *req;
+
+ if (!(req = req_alloc(sizeof(struct set_speaker_message), SET_SPEAKER_MESSAGE)))
+ return;
+
+ req->data.setspeaker.mode = htolel(mode);
+ transmit_response(s, req);
+}
+/*
+static void transmit_microphone_mode(struct skinnysession *s, int mode)
+{
+ struct skinny_req *req;
+
+ if (!(req = req_alloc(sizeof(struct set_microphone_message), SET_MICROPHONE_MESSAGE)))
+ return;
+
+ req->data.setmicrophone.mode = htolel(mode);
+ transmit_response(s, req);
+}
+*/
+
+static void transmit_callinfo(struct skinnysession *s, const char *fromname, const char *fromnum, const char *toname, const char *tonum, int instance, int callid, int calltype)
+{
+ struct skinny_req *req;
+
+ if (!(req = req_alloc(sizeof(struct call_info_message), CALL_INFO_MESSAGE)))
+ return;
+
+ if (skinnydebug)
+ ast_verbose("Setting Callinfo to %s(%s) from %s(%s) on %s(%d)\n", fromname, fromnum, toname, tonum, s->device->name, instance);
+
+ if (fromname) {
+ ast_copy_string(req->data.callinfo.callingPartyName, fromname, sizeof(req->data.callinfo.callingPartyName));
+ }
+ if (fromnum) {
+ ast_copy_string(req->data.callinfo.callingParty, fromnum, sizeof(req->data.callinfo.callingParty));
+ }
+ if (toname) {
+ ast_copy_string(req->data.callinfo.calledPartyName, toname, sizeof(req->data.callinfo.calledPartyName));
+ }
+ if (tonum) {
+ ast_copy_string(req->data.callinfo.calledParty, tonum, sizeof(req->data.callinfo.calledParty));
+ }
+ req->data.callinfo.instance = htolel(instance);
+ req->data.callinfo.reference = htolel(callid);
+ req->data.callinfo.type = htolel(calltype);
+ transmit_response(s, req);
+}
+
+static void transmit_connect(struct skinnysession *s, struct skinny_subchannel *sub)
+{
+ struct skinny_req *req;
+ struct skinny_line *l = sub->parent;
+ struct ast_format_list fmt;
+
+ if (!(req = req_alloc(sizeof(struct open_receive_channel_message), OPEN_RECEIVE_CHANNEL_MESSAGE)))
+ return;
+
+ fmt = ast_codec_pref_getsize(&l->prefs, ast_best_codec(l->capability));
+
+ req->data.openreceivechannel.conferenceId = htolel(sub->callid);
+ req->data.openreceivechannel.partyId = htolel(sub->callid);
+ req->data.openreceivechannel.packets = htolel(fmt.cur_ms);
+ req->data.openreceivechannel.capability = htolel(codec_ast2skinny(fmt.bits));
+ req->data.openreceivechannel.echo = htolel(0);
+ req->data.openreceivechannel.bitrate = htolel(0);
+ transmit_response(s, req);
+}
+
+static void transmit_tone(struct skinnysession *s, int tone, int instance, int reference)
+{
+ struct skinny_req *req;
+
+ if (tone == SKINNY_NOTONE) {
+ /* This is bad, mmm'kay? */
+ return;
+ }
+
+ if (tone > 0) {
+ if (!(req = req_alloc(sizeof(struct start_tone_message), START_TONE_MESSAGE)))
+ return;
+ req->data.starttone.tone = htolel(tone);
+ req->data.starttone.instance = htolel(instance);
+ req->data.starttone.reference = htolel(reference);
+ } else {
+ if (!(req = req_alloc(sizeof(struct stop_tone_message), STOP_TONE_MESSAGE)))
+ return;
+ req->data.stoptone.instance = htolel(instance);
+ req->data.stoptone.reference = htolel(reference);
+ }
+
+ if (tone > 0) {
+ req->data.starttone.tone = htolel(tone);
+ }
+ transmit_response(s, req);
+}
+
+static void transmit_selectsoftkeys(struct skinnysession *s, int instance, int callid, int softkey)
+{
+ struct skinny_req *req;
+
+ if (!(req = req_alloc(sizeof(struct select_soft_keys_message), SELECT_SOFT_KEYS_MESSAGE)))
+ return;
+
+ req->data.selectsoftkey.instance = htolel(instance);
+ req->data.selectsoftkey.reference = htolel(callid);
+ req->data.selectsoftkey.softKeySetIndex = htolel(softkey);
+ req->data.selectsoftkey.validKeyMask = htolel(0xFFFFFFFF);
+ transmit_response(s, req);
+}
+
+static void transmit_lamp_indication(struct skinnysession *s, int stimulus, int instance, int indication)
+{
+ struct skinny_req *req;
+
+ if (!(req = req_alloc(sizeof(struct set_lamp_message), SET_LAMP_MESSAGE)))
+ return;
+
+ req->data.setlamp.stimulus = htolel(stimulus);
+ req->data.setlamp.stimulusInstance = htolel(instance);
+ req->data.setlamp.deviceStimulus = htolel(indication);
+ transmit_response(s, req);
+}
+
+static void transmit_ringer_mode(struct skinnysession *s, int mode)
+{
+ struct skinny_req *req;
+
+ if (skinnydebug)
+ ast_verbose("Setting ringer mode to '%d'.\n", mode);
+
+ if (!(req = req_alloc(sizeof(struct set_ringer_message), SET_RINGER_MESSAGE)))
+ return;
+
+ req->data.setringer.ringerMode = htolel(mode);
+ /* XXX okay, I don't quite know what this is, but here's what happens (on a 7960).
+ Note: The phone will always show as ringing on the display.
+
+ 1: phone will audibly ring over and over
+ 2: phone will audibly ring only once
+ any other value, will NOT cause the phone to audibly ring
+ */
+ req->data.setringer.unknown1 = htolel(1);
+ /* XXX the value here doesn't seem to change anything. Must be higher than 0.
+ Perhaps a packet capture can shed some light on this. */
+ req->data.setringer.unknown2 = htolel(1);
+ transmit_response(s, req);
+}
+
+static void transmit_displaymessage(struct skinnysession *s, const char *text, int instance, int reference)
+{
+ struct skinny_req *req;
+
+ if (text == 0) {
+ if (!(req = req_alloc(0, CLEAR_DISPLAY_MESSAGE)))
+ return;
+
+ req->data.clearpromptstatus.lineInstance = instance;
+ req->data.clearpromptstatus.callReference = reference;
+
+ if (skinnydebug)
+ ast_verbose("Clearing Display\n");
+ } else {
+ if (!(req = req_alloc(sizeof(struct displaytext_message), DISPLAYTEXT_MESSAGE)))
+ return;
+
+ ast_copy_string(req->data.displaytext.text, text, sizeof(req->data.displaytext.text));
+ if (skinnydebug)
+ ast_verbose("Displaying message '%s'\n", req->data.displaytext.text);
+ }
+
+ transmit_response(s, req);
+}
+
+static void transmit_displaynotify(struct skinnysession *s, const char *text, int t)
+{
+ struct skinny_req *req;
+
+ if (!(req = req_alloc(sizeof(struct display_notify_message), DISPLAY_NOTIFY_MESSAGE)))
+ return;
+
+ ast_copy_string(req->data.displaynotify.displayMessage, text, sizeof(req->data.displaynotify.displayMessage));
+ req->data.displaynotify.displayTimeout = htolel(t);
+
+ if (skinnydebug)
+ ast_verbose("Displaying notify '%s'\n", text);
+
+ transmit_response(s, req);
+}
+
+static void transmit_displaypromptstatus(struct skinnysession *s, const char *text, int t, int instance, int callid)
+{
+ struct skinny_req *req;
+
+ if (text == 0) {
+ if (!(req = req_alloc(sizeof(struct clear_prompt_message), CLEAR_PROMPT_MESSAGE)))
+ return;
+
+ req->data.clearpromptstatus.lineInstance = htolel(instance);
+ req->data.clearpromptstatus.callReference = htolel(callid);
+
+ if (skinnydebug)
+ ast_verbose("Clearing Prompt\n");
+ } else {
+ if (!(req = req_alloc(sizeof(struct display_prompt_status_message), DISPLAY_PROMPT_STATUS_MESSAGE)))
+ return;
+
+ ast_copy_string(req->data.displaypromptstatus.promptMessage, text, sizeof(req->data.displaypromptstatus.promptMessage));
+ req->data.displaypromptstatus.messageTimeout = htolel(t);
+ req->data.displaypromptstatus.lineInstance = htolel(instance);
+ req->data.displaypromptstatus.callReference = htolel(callid);
+
+ if (skinnydebug)
+ ast_verbose("Displaying Prompt Status '%s'\n", text);
+ }
+
+ transmit_response(s, req);
+}
+
+static void transmit_dialednumber(struct skinnysession *s, const char *text, int instance, int callid)
+{
+ struct skinny_req *req;
+
+ if (!(req = req_alloc(sizeof(struct dialed_number_message), DIALED_NUMBER_MESSAGE)))
+ return;
+
+ ast_copy_string(req->data.dialednumber.dialedNumber, text, sizeof(req->data.dialednumber.dialedNumber));
+ req->data.dialednumber.lineInstance = htolel(instance);
+ req->data.dialednumber.callReference = htolel(callid);
+
+ transmit_response(s, req);
+}
+
+static void transmit_callstate(struct skinnysession *s, int instance, int state, unsigned callid)
+{
+ struct skinny_req *req;
+
+ if (state == SKINNY_ONHOOK) {
+ if (!(req = req_alloc(sizeof(struct close_receive_channel_message), CLOSE_RECEIVE_CHANNEL_MESSAGE)))
+ return;
+
+ req->data.closereceivechannel.conferenceId = htolel(callid);
+ req->data.closereceivechannel.partyId = htolel(callid);
+ transmit_response(s, req);
+
+ if (!(req = req_alloc(sizeof(struct stop_media_transmission_message), STOP_MEDIA_TRANSMISSION_MESSAGE)))
+ return;
+
+ req->data.stopmedia.conferenceId = htolel(callid);
+ req->data.stopmedia.passThruPartyId = htolel(callid);
+ transmit_response(s, req);
+
+ transmit_speaker_mode(s, SKINNY_SPEAKEROFF);
+
+ transmit_displaypromptstatus(s, NULL, 0, instance, callid);
+ }
+
+ if (!(req = req_alloc(sizeof(struct call_state_message), CALL_STATE_MESSAGE)))
+ return;
+
+ req->data.callstate.callState = htolel(state);
+ req->data.callstate.lineInstance = htolel(instance);
+ req->data.callstate.callReference = htolel(callid);
+ transmit_response(s, req);
+
+ if (state == SKINNY_ONHOOK) {
+ transmit_selectsoftkeys(s, 0, 0, KEYDEF_ONHOOK);
+ }
+
+ if (state == SKINNY_OFFHOOK || state == SKINNY_ONHOOK) {
+ if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE)))
+ return;
+
+ req->data.activatecallplane.lineInstance = htolel(instance);
+ transmit_response(s, req);
+ }
+}
+
+
+static void transmit_cfwdstate(struct skinnysession *s, struct skinny_line *l)
+{
+ struct skinny_req *req;
+ int anyon = 0;
+
+ if (!(req = req_alloc(sizeof(struct forward_stat_message), FORWARD_STAT_MESSAGE)))
+ return;
+
+ if (l->cfwdtype & SKINNY_CFWD_ALL) {
+ if (!ast_strlen_zero(l->call_forward_all)) {
+ ast_copy_string(req->data.forwardstat.fwdallnum, l->call_forward_all, sizeof(req->data.forwardstat.fwdallnum));
+ req->data.forwardstat.fwdall = htolel(1);
+ anyon++;
+ } else {
+ req->data.forwardstat.fwdall = htolel(0);
+ }
+ }
+ if (l->cfwdtype & SKINNY_CFWD_BUSY) {
+ if (!ast_strlen_zero(l->call_forward_busy)) {
+ ast_copy_string(req->data.forwardstat.fwdbusynum, l->call_forward_busy, sizeof(req->data.forwardstat.fwdbusynum));
+ req->data.forwardstat.fwdbusy = htolel(1);
+ anyon++;
+ } else {
+ req->data.forwardstat.fwdbusy = htolel(0);
+ }
+ }
+ if (l->cfwdtype & SKINNY_CFWD_NOANSWER) {
+ if (!ast_strlen_zero(l->call_forward_noanswer)) {
+ ast_copy_string(req->data.forwardstat.fwdnoanswernum, l->call_forward_noanswer, sizeof(req->data.forwardstat.fwdnoanswernum));
+ req->data.forwardstat.fwdnoanswer = htolel(1);
+ anyon++;
+ } else {
+ req->data.forwardstat.fwdnoanswer = htolel(0);
+ }
+ }
+ req->data.forwardstat.lineNumber = htolel(l->instance);
+ if (anyon)
+ req->data.forwardstat.activeforward = htolel(7);
+ else
+ req->data.forwardstat.activeforward = htolel(0);
+
+ transmit_response(s, req);
+}
+
+static int skinny_extensionstate_cb(char *context, char *exten, int state, void *data)
+{
+ struct skinny_speeddial *sd = data;
+ struct skinny_device *d = sd->parent;
+ struct skinnysession *s = d->session;
+ char hint[AST_MAX_EXTENSION];
+ int callstate = SKINNY_CALLREMOTEMULTILINE;
+ int lamp = SKINNY_LAMP_OFF;
+
+ switch (state) {
+ case AST_EXTENSION_DEACTIVATED: /* Retry after a while */
+ case AST_EXTENSION_REMOVED: /* Extension is gone */
+ ast_verb(2, "Extension state: Watcher for hint %s %s. Notify Device %s\n", exten, state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", d->name);
+ sd->stateid = -1;
+ callstate = SKINNY_ONHOOK;
+ lamp = SKINNY_LAMP_OFF;
+ break;
+ case AST_EXTENSION_RINGING:
+ case AST_EXTENSION_UNAVAILABLE:
+ callstate = SKINNY_RINGIN;
+ lamp = SKINNY_LAMP_BLINK;
+ break;
+ case AST_EXTENSION_BUSY: /* callstate = SKINNY_BUSY wasn't wanting to work - I'll settle for this */
+ case AST_EXTENSION_INUSE:
+ callstate = SKINNY_CALLREMOTEMULTILINE;
+ lamp = SKINNY_LAMP_ON;
+ break;
+ case AST_EXTENSION_ONHOLD:
+ callstate = SKINNY_HOLD;
+ lamp = SKINNY_LAMP_WINK;
+ break;
+ case AST_EXTENSION_NOT_INUSE:
+ default:
+ callstate = SKINNY_ONHOOK;
+ lamp = SKINNY_LAMP_OFF;
+ break;
+ }
+
+ if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, sd->context, sd->exten)) {
+ /* If they are not registered, we will override notification and show no availability */
+ if (ast_device_state(hint) == AST_DEVICE_UNAVAILABLE) {
+ callstate = SKINNY_ONHOOK;
+ lamp = SKINNY_LAMP_FLASH;
+ }
+ }
+
+ transmit_lamp_indication(s, STIMULUS_LINE, sd->instance, lamp);
+ transmit_callstate(s, sd->instance, callstate, 0);
+ sd->laststate = state;
+
+ return 0;
+}
+
+static int has_voicemail(struct skinny_line *l)
+{
+ return ast_app_has_voicemail(l->mailbox, NULL);
+}
+
+static void do_housekeeping(struct skinnysession *s)
+{
+ int new;
+ int old;
+ int device_lamp = 0;
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+
+ /* Update time on device */
+ handle_time_date_req_message(NULL, s);
+
+ /* Set MWI on individual lines */
+ for (l = d->lines; l; l = l->next) {
+ if (has_voicemail(l)) {
+ if (skinnydebug)
+ ast_verbose("Checking for voicemail Skinny %s@%s\n", l->name, d->name);
+ ast_app_inboxcount(l->mailbox, &new, &old);
+ if (skinnydebug)
+ ast_verbose("Skinny %s@%s has voicemail!\n", l->name, d->name);
+ transmit_lamp_indication(s, STIMULUS_VOICEMAIL, l->instance, l->mwiblink?SKINNY_LAMP_BLINK:SKINNY_LAMP_ON);
+ device_lamp++;
+ } else {
+ transmit_lamp_indication(s, STIMULUS_VOICEMAIL, l->instance, SKINNY_LAMP_OFF);
+ }
+ }
+ /* If at least one line has VM, turn the device level lamp on */
+ if (device_lamp)
+ transmit_lamp_indication(s, STIMULUS_VOICEMAIL, 0, SKINNY_LAMP_ON);
+ else
+ transmit_lamp_indication(s, STIMULUS_VOICEMAIL, 0, SKINNY_LAMP_OFF);
+}
+
+/* I do not believe skinny can deal with video.
+ Anyone know differently? */
+/* Yes, it can. Currently 7985 and Cisco VT Advantage do video. */
+static enum ast_rtp_get_result skinny_get_vrtp_peer(struct ast_channel *c, struct ast_rtp **rtp)
+{
+ struct skinny_subchannel *sub = NULL;
+
+ if (!(sub = c->tech_pvt) || !(sub->vrtp))
+ return AST_RTP_GET_FAILED;
+
+ *rtp = sub->vrtp;
+
+ return AST_RTP_TRY_NATIVE;
+}
+
+static enum ast_rtp_get_result skinny_get_rtp_peer(struct ast_channel *c, struct ast_rtp **rtp)
+{
+ struct skinny_subchannel *sub = NULL;
+ struct skinny_line *l;
+ enum ast_rtp_get_result res = AST_RTP_TRY_NATIVE;
+
+ if (skinnydebug)
+ ast_verbose("skinny_get_rtp_peer() Channel = %s\n", c->name);
+
+
+ if (!(sub = c->tech_pvt))
+ return AST_RTP_GET_FAILED;
+
+ ast_mutex_lock(&sub->lock);
+
+ if (!(sub->rtp)){
+ ast_mutex_unlock(&sub->lock);
+ return AST_RTP_GET_FAILED;
+ }
+
+ *rtp = sub->rtp;
+
+ l = sub->parent;
+
+ if (!l->canreinvite || l->nat){
+ res = AST_RTP_TRY_PARTIAL;
+ if (skinnydebug)
+ ast_verbose("skinny_get_rtp_peer() Using AST_RTP_TRY_PARTIAL \n");
+ }
+
+ ast_mutex_unlock(&sub->lock);
+
+ return res;
+
+}
+
+static int skinny_set_rtp_peer(struct ast_channel *c, struct ast_rtp *rtp, struct ast_rtp *vrtp, struct ast_rtp *trtp, int codecs, int nat_active)
+{
+ struct skinny_subchannel *sub;
+ struct skinny_line *l;
+ struct skinny_device *d;
+ struct skinnysession *s;
+ struct ast_format_list fmt;
+ struct sockaddr_in us;
+ struct sockaddr_in them;
+ struct skinny_req *req;
+
+ sub = c->tech_pvt;
+
+ if (c->_state != AST_STATE_UP)
+ return 0;
+
+ if (!sub) {
+ return -1;
+ }
+
+ l = sub->parent;
+ d = l->parent;
+ s = d->session;
+
+ if (rtp){
+ ast_rtp_get_peer(rtp, &them);
+
+ /* Shutdown any early-media or previous media on re-invite */
+ if (!(req = req_alloc(sizeof(struct stop_media_transmission_message), STOP_MEDIA_TRANSMISSION_MESSAGE)))
+ return -1;
+
+ req->data.stopmedia.conferenceId = htolel(sub->callid);
+ req->data.stopmedia.passThruPartyId = htolel(sub->callid);
+ transmit_response(s, req);
+
+ if (skinnydebug)
+ ast_verbose("Peerip = %s:%d\n", ast_inet_ntoa(them.sin_addr), ntohs(them.sin_port));
+
+ if (!(req = req_alloc(sizeof(struct start_media_transmission_message), START_MEDIA_TRANSMISSION_MESSAGE)))
+ return -1;
+
+ fmt = ast_codec_pref_getsize(&l->prefs, ast_best_codec(l->capability));
+
+ if (skinnydebug)
+ ast_verbose("Setting payloadType to '%d' (%d ms)\n", fmt.bits, fmt.cur_ms);
+
+ req->data.startmedia.conferenceId = htolel(sub->callid);
+ req->data.startmedia.passThruPartyId = htolel(sub->callid);
+ if (!(l->canreinvite) || (l->nat)){
+ ast_rtp_get_us(rtp, &us);
+ req->data.startmedia.remoteIp = htolel(d->ourip.s_addr);
+ req->data.startmedia.remotePort = htolel(ntohs(us.sin_port));
+ } else {
+ req->data.startmedia.remoteIp = htolel(them.sin_addr.s_addr);
+ req->data.startmedia.remotePort = htolel(ntohs(them.sin_port));
+ }
+ req->data.startmedia.packetSize = htolel(fmt.cur_ms);
+ req->data.startmedia.payloadType = htolel(codec_ast2skinny(fmt.bits));
+ req->data.startmedia.qualifier.precedence = htolel(127);
+ req->data.startmedia.qualifier.vad = htolel(0);
+ req->data.startmedia.qualifier.packets = htolel(0);
+ req->data.startmedia.qualifier.bitRate = htolel(0);
+ transmit_response(s, req);
+
+ return 0;
+ }
+ /* Need a return here to break the bridge */
+ return 0;
+}
+
+static struct ast_rtp_protocol skinny_rtp = {
+ .type = "Skinny",
+ .get_rtp_info = skinny_get_rtp_peer,
+ .get_vrtp_info = skinny_get_vrtp_peer,
+ .set_rtp_peer = skinny_set_rtp_peer,
+};
+
+static char *handle_skinny_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "skinny set debug";
+ e->usage =
+ "Usage: skinny set debug\n"
+ " Enables dumping of Skinny packets for debugging purposes\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ skinnydebug = 1;
+ ast_cli(a->fd, "Skinny Debugging Enabled\n");
+ return CLI_SUCCESS;
+}
+
+static char *handle_skinny_set_debug_off(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "skinny set debug off";
+ e->usage =
+ "Usage: skinny set debug off\n"
+ " Disables dumping of Skinny packets for debugging purposes\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ skinnydebug = 0;
+ ast_cli(a->fd, "Skinny Debugging Disabled\n");
+ return CLI_SUCCESS;
+}
+
+static char *complete_skinny_devices(const char *word, int state)
+{
+ struct skinny_device *d;
+ char *result = NULL;
+ int wordlen = strlen(word), which = 0;
+
+ for (d = devices; d && !result; d = d->next) {
+ if (!strncasecmp(word, d->id, wordlen) && ++which > state)
+ result = ast_strdup(d->id);
+ }
+
+ return result;
+}
+
+static char *complete_skinny_show_device(const char *line, const char *word, int pos, int state)
+{
+ return (pos == 3 ? ast_strdup(complete_skinny_devices(word, state)) : NULL);
+}
+
+static char *complete_skinny_reset(const char *line, const char *word, int pos, int state)
+{
+ return (pos == 2 ? ast_strdup(complete_skinny_devices(word, state)) : NULL);
+}
+
+static char *complete_skinny_show_line(const char *line, const char *word, int pos, int state)
+{
+ struct skinny_device *d;
+ struct skinny_line *l;
+ char *result = NULL;
+ int wordlen = strlen(word), which = 0;
+
+ if (pos != 3)
+ return NULL;
+
+ for (d = devices; d && !result; d = d->next) {
+ for (l = d->lines; l && !result; l = l->next) {
+ if (!strncasecmp(word, l->name, wordlen) && ++which > state)
+ result = ast_strdup(l->name);
+ }
+ }
+
+ return result;
+}
+
+static char *handle_skinny_reset(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct skinny_device *d;
+ struct skinny_req *req;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "skinny reset";
+ e->usage =
+ "Usage: skinny reset <DeviceId|DeviceName|all> [restart]\n"
+ " Causes a Skinny device to reset itself, optionally with a full restart\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_skinny_reset(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc < 3 || a->argc > 4)
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&devicelock);
+
+ for (d = devices; d; d = d->next) {
+ int fullrestart = 0;
+ if (!strcasecmp(a->argv[2], d->id) || !strcasecmp(a->argv[2], d->name) || !strcasecmp(a->argv[2], "all")) {
+ if (!(d->session))
+ continue;
+
+ if (!(req = req_alloc(sizeof(struct reset_message), RESET_MESSAGE)))
+ continue;
+
+ if (a->argc == 4 && !strcasecmp(a->argv[3], "restart"))
+ fullrestart = 1;
+
+ if (fullrestart)
+ req->data.reset.resetType = 2;
+ else
+ req->data.reset.resetType = 1;
+
+ ast_verb(3, "%s device %s.\n", (fullrestart) ? "Restarting" : "Resetting", d->id);
+ transmit_response(d->session, req);
+ }
+ }
+ ast_mutex_unlock(&devicelock);
+ return CLI_SUCCESS;
+}
+
+static char *device2str(int type)
+{
+ char *tmp;
+
+ switch (type) {
+ case SKINNY_DEVICE_NONE:
+ return "No Device";
+ case SKINNY_DEVICE_30SPPLUS:
+ return "30SP Plus";
+ case SKINNY_DEVICE_12SPPLUS:
+ return "12SP Plus";
+ case SKINNY_DEVICE_12SP:
+ return "12SP";
+ case SKINNY_DEVICE_12:
+ return "12";
+ case SKINNY_DEVICE_30VIP:
+ return "30VIP";
+ case SKINNY_DEVICE_7910:
+ return "7910";
+ case SKINNY_DEVICE_7960:
+ return "7960";
+ case SKINNY_DEVICE_7940:
+ return "7940";
+ case SKINNY_DEVICE_7935:
+ return "7935";
+ case SKINNY_DEVICE_ATA186:
+ return "ATA186";
+ case SKINNY_DEVICE_7941:
+ return "7941";
+ case SKINNY_DEVICE_7971:
+ return "7971";
+ case SKINNY_DEVICE_7985:
+ return "7985";
+ case SKINNY_DEVICE_7911:
+ return "7911";
+ case SKINNY_DEVICE_7961GE:
+ return "7961GE";
+ case SKINNY_DEVICE_7941GE:
+ return "7941GE";
+ case SKINNY_DEVICE_7921:
+ return "7921";
+ case SKINNY_DEVICE_7905:
+ return "7905";
+ case SKINNY_DEVICE_7920:
+ return "7920";
+ case SKINNY_DEVICE_7970:
+ return "7970";
+ case SKINNY_DEVICE_7912:
+ return "7912";
+ case SKINNY_DEVICE_7902:
+ return "7902";
+ case SKINNY_DEVICE_CIPC:
+ return "IP Communicator";
+ case SKINNY_DEVICE_7961:
+ return "7961";
+ case SKINNY_DEVICE_7936:
+ return "7936";
+ case SKINNY_DEVICE_SCCPGATEWAY_AN:
+ return "SCCPGATEWAY_AN";
+ case SKINNY_DEVICE_SCCPGATEWAY_BRI:
+ return "SCCPGATEWAY_BRI";
+ case SKINNY_DEVICE_UNKNOWN:
+ return "Unknown";
+ default:
+ if (!(tmp = ast_threadstorage_get(&device2str_threadbuf, DEVICE2STR_BUFSIZE)))
+ return "Unknown";
+ snprintf(tmp, DEVICE2STR_BUFSIZE, "UNKNOWN-%d", type);
+ return tmp;
+ }
+}
+
+/*! \brief Print codec list from preference to CLI/manager */
+static void print_codec_to_cli(int fd, struct ast_codec_pref *pref)
+{
+ int x, codec;
+
+ for(x = 0; x < 32 ; x++) {
+ codec = ast_codec_pref_index(pref, x);
+ if (!codec)
+ break;
+ ast_cli(fd, "%s", ast_getformatname(codec));
+ ast_cli(fd, ":%d", pref->framing[x]);
+ if (x < 31 && ast_codec_pref_index(pref, x + 1))
+ ast_cli(fd, ",");
+ }
+ if (!x)
+ ast_cli(fd, "none");
+}
+
+static char *handle_skinny_show_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct skinny_device *d;
+ struct skinny_line *l;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "skinny show devices";
+ e->usage =
+ "Usage: skinny show devices\n"
+ " Lists all devices known to the Skinny subsystem.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&devicelock);
+
+ ast_cli(a->fd, "Name DeviceId IP Type R NL\n");
+ ast_cli(a->fd, "-------------------- ---------------- --------------- --------------- - --\n");
+
+ for (d = devices; d; d = d->next) {
+ int numlines = 0;
+
+ for (l = d->lines; l; l = l->next)
+ numlines++;
+
+ ast_cli(a->fd, "%-20s %-16s %-15s %-15s %c %2d\n",
+ d->name,
+ d->id,
+ d->session?ast_inet_ntoa(d->session->sin.sin_addr):"",
+ device2str(d->type),
+ d->registered?'Y':'N',
+ numlines);
+ }
+
+ ast_mutex_unlock(&devicelock);
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief Show device information */
+static char *handle_skinny_show_device(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct skinny_device *d;
+ struct skinny_line *l;
+ struct skinny_speeddial *sd;
+ struct skinny_addon *sa;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "skinny show device";
+ e->usage =
+ "Usage: skinny show device <DeviceId|DeviceName>\n"
+ " Lists all deviceinformation of a specific device known to the Skinny subsystem.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_skinny_show_device(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc < 4)
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&devicelock);
+ for (d = devices; d; d = d->next) {
+ if (!strcasecmp(a->argv[3], d->id) || !strcasecmp(a->argv[3], d->name)) {
+ int numlines = 0, numaddons = 0, numspeeddials = 0;
+
+ for (l = d->lines; l; l = l->next)
+ numlines++;
+
+ ast_cli(a->fd, "Name: %s\n", d->name);
+ ast_cli(a->fd, "Id: %s\n", d->id);
+ ast_cli(a->fd, "version: %s\n", S_OR(d->version_id, "Unknown"));
+ ast_cli(a->fd, "Ip address: %s\n", (d->session ? ast_inet_ntoa(d->session->sin.sin_addr) : "Unknown"));
+ ast_cli(a->fd, "Port: %d\n", (d->session ? ntohs(d->session->sin.sin_port) : 0));
+ ast_cli(a->fd, "Device Type: %s\n", device2str(d->type));
+ ast_cli(a->fd, "Registered: %s\n", (d->registered ? "Yes" : "No"));
+ ast_cli(a->fd, "Lines: %d\n", numlines);
+ for (l = d->lines; l; l = l->next)
+ ast_cli(a->fd, " %s (%s)\n", l->name, l->label);
+ for (sa = d->addons; sa; sa = sa->next)
+ numaddons++;
+ ast_cli(a->fd, "Addons: %d\n", numaddons);
+ for (sa = d->addons; sa; sa = sa->next)
+ ast_cli(a->fd, " %s\n", sa->type);
+ for (sd = d->speeddials; sd; sd = sd->next)
+ numspeeddials++;
+ ast_cli(a->fd, "Speeddials: %d\n", numspeeddials);
+ for (sd = d->speeddials; sd; sd = sd->next)
+ ast_cli(a->fd, " %s (%s) ishint: %d\n", sd->exten, sd->label, sd->isHint);
+ }
+ }
+ ast_mutex_unlock(&devicelock);
+ return CLI_SUCCESS;
+}
+
+static char *handle_skinny_show_lines(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct skinny_device *d;
+ struct skinny_line *l;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "skinny show lines";
+ e->usage =
+ "Usage: skinny show lines\n"
+ " Lists all lines known to the Skinny subsystem.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&devicelock);
+
+ ast_cli(a->fd, "Device Name Instance Name Label \n");
+ ast_cli(a->fd, "-------------------- -------- -------------------- --------------------\n");
+ for (d = devices; d; d = d->next) {
+ for (l = d->lines; l; l = l->next) {
+ ast_cli(a->fd, "%-20s %8d %-20s %-20s\n",
+ d->name,
+ l->instance,
+ l->name,
+ l->label);
+ }
+ }
+
+ ast_mutex_unlock(&devicelock);
+ return CLI_SUCCESS;
+}
+
+/*! \brief List line information. */
+static char *handle_skinny_show_line(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct skinny_device *d;
+ struct skinny_line *l;
+ char codec_buf[512];
+ char group_buf[256];
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "skinny show line";
+ e->usage =
+ "Usage: skinny show line <Line> [ on <DeviceID|DeviceName> ]\n"
+ " List all lineinformation of a specific line known to the Skinny subsystem.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_skinny_show_line(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc < 4)
+ return CLI_SHOWUSAGE;
+
+ ast_mutex_lock(&devicelock);
+
+ /* Show all lines matching the one supplied */
+ for (d = devices; d; d = d->next) {
+ if (a->argc == 6 && (strcasecmp(a->argv[5], d->id) && strcasecmp(a->argv[5], d->name)))
+ continue;
+ for (l = d->lines; l; l = l->next) {
+ if (strcasecmp(a->argv[3], l->name))
+ continue;
+ ast_cli(a->fd, "Line: %s\n", l->name);
+ ast_cli(a->fd, "On Device: %s\n", d->name);
+ ast_cli(a->fd, "Line Label: %s\n", l->label);
+ ast_cli(a->fd, "Extension: %s\n", S_OR(l->exten, "<not set>"));
+ ast_cli(a->fd, "Context: %s\n", l->context);
+ ast_cli(a->fd, "CallGroup: %s\n", ast_print_group(group_buf, sizeof(group_buf), l->callgroup));
+ ast_cli(a->fd, "PickupGroup: %s\n", ast_print_group(group_buf, sizeof(group_buf), l->pickupgroup));
+ ast_cli(a->fd, "Language: %s\n", S_OR(l->language, "<not set>"));
+ ast_cli(a->fd, "Accountcode: %s\n", S_OR(l->accountcode, "<not set>"));
+ ast_cli(a->fd, "AmaFlag: %s\n", ast_cdr_flags2str(l->amaflags));
+ ast_cli(a->fd, "CallerId Number: %s\n", S_OR(l->cid_num, "<not set>"));
+ ast_cli(a->fd, "CallerId Name: %s\n", S_OR(l->cid_name, "<not set>"));
+ ast_cli(a->fd, "Hide CallerId: %s\n", (l->hidecallerid ? "Yes" : "No"));
+ ast_cli(a->fd, "CFwdAll: %s\n", S_COR((l->cfwdtype & SKINNY_CFWD_ALL), l->call_forward_all, "<not set>"));
+ ast_cli(a->fd, "CFwdBusy: %s\n", S_COR((l->cfwdtype & SKINNY_CFWD_BUSY), l->call_forward_busy, "<not set>"));
+ ast_cli(a->fd, "CFwdNoAnswer: %s\n", S_COR((l->cfwdtype & SKINNY_CFWD_NOANSWER), l->call_forward_noanswer, "<not set>"));
+ ast_cli(a->fd, "VoicemailBox: %s\n", S_OR(l->mailbox, "<not set>"));
+ ast_cli(a->fd, "VoicemailNumber: %s\n", S_OR(l->vmexten, "<not set>"));
+ ast_cli(a->fd, "MWIblink: %d\n", l->mwiblink);
+ ast_cli(a->fd, "Regextension: %s\n", S_OR(l->regexten, "<not set>"));
+ ast_cli(a->fd, "Regcontext: %s\n", S_OR(l->regcontext, "<not set>"));
+ ast_cli(a->fd, "MoHInterpret: %s\n", S_OR(l->mohinterpret, "<not set>"));
+ ast_cli(a->fd, "MoHSuggest: %s\n", S_OR(l->mohsuggest, "<not set>"));
+ ast_cli(a->fd, "Last dialed nr: %s\n", S_OR(l->lastnumberdialed, "<no calls made yet>"));
+ ast_cli(a->fd, "Last CallerID: %s\n", S_OR(l->lastcallerid, "<not set>"));
+ ast_cli(a->fd, "Transfer enabled: %s\n", (l->transfer ? "Yes" : "No"));
+ ast_cli(a->fd, "Callwaiting: %s\n", (l->callwaiting ? "Yes" : "No"));
+ ast_cli(a->fd, "3Way Calling: %s\n", (l->threewaycalling ? "Yes" : "No"));
+ ast_cli(a->fd, "Can forward: %s\n", (l->cancallforward ? "Yes" : "No"));
+ ast_cli(a->fd, "Do Not Disturb: %s\n", (l->dnd ? "Yes" : "No"));
+ ast_cli(a->fd, "NAT: %s\n", (l->nat ? "Yes" : "No"));
+ ast_cli(a->fd, "immediate: %s\n", (l->immediate ? "Yes" : "No"));
+ ast_cli(a->fd, "Group: %d\n", l->group);
+ ast_cli(a->fd, "Codecs: ");
+ ast_getformatname_multiple(codec_buf, sizeof(codec_buf) - 1, l->capability);
+ ast_cli(a->fd, "%s\n", codec_buf);
+ ast_cli(a->fd, "Codec Order: (");
+ print_codec_to_cli(a->fd, &l->prefs);
+ ast_cli(a->fd, ")\n");
+ ast_cli(a->fd, "\n");
+ }
+ }
+
+ ast_mutex_unlock(&devicelock);
+ return CLI_SUCCESS;
+}
+
+/*! \brief List global settings for the Skinny subsystem. */
+static char *handle_skinny_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "skinny show settings";
+ e->usage =
+ "Usage: skinny show settings\n"
+ " Lists all global configuration settings of the Skinny subsystem.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ ast_cli(a->fd, "\nGlobal Settings:\n");
+ ast_cli(a->fd, " Skinny Port: %d\n", ntohs(bindaddr.sin_port));
+ ast_cli(a->fd, " Bindaddress: %s\n", ast_inet_ntoa(bindaddr.sin_addr));
+ ast_cli(a->fd, " KeepAlive: %d\n", keep_alive);
+ ast_cli(a->fd, " Date Format: %s\n", date_format);
+ ast_cli(a->fd, " Voice Mail Extension: %s\n", S_OR(vmexten, "(not set)"));
+ ast_cli(a->fd, " Reg. context: %s\n", S_OR(regcontext, "(not set)"));
+ ast_cli(a->fd, " Jitterbuffer enabled: %s\n", (ast_test_flag(&global_jbconf, AST_JB_ENABLED) ? "Yes" : "No"));
+ ast_cli(a->fd, " Jitterbuffer forced: %s\n", (ast_test_flag(&global_jbconf, AST_JB_FORCED) ? "Yes" : "No"));
+ ast_cli(a->fd, " Jitterbuffer max size: %ld\n", global_jbconf.max_size);
+ ast_cli(a->fd, " Jitterbuffer resync: %ld\n", global_jbconf.resync_threshold);
+ ast_cli(a->fd, " Jitterbuffer impl: %s\n", global_jbconf.impl);
+ ast_cli(a->fd, " Jitterbuffer log: %s\n", (ast_test_flag(&global_jbconf, AST_JB_LOG) ? "Yes" : "No"));
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_skinny[] = {
+ AST_CLI_DEFINE(handle_skinny_show_devices, "List defined Skinny devices"),
+ AST_CLI_DEFINE(handle_skinny_show_device, "List Skinny device information"),
+ AST_CLI_DEFINE(handle_skinny_show_lines, "List defined Skinny lines per device"),
+ AST_CLI_DEFINE(handle_skinny_show_line, "List Skinny line information"),
+ AST_CLI_DEFINE(handle_skinny_show_settings, "List global Skinny settings"),
+ AST_CLI_DEFINE(handle_skinny_set_debug, "Enable Skinny debugging"),
+ AST_CLI_DEFINE(handle_skinny_set_debug_off, "Disable Skinny debugging"),
+ AST_CLI_DEFINE(handle_skinny_reset, "Reset Skinny device(s)"),
+};
+
+#if 0
+static struct skinny_paging_device *build_paging_device(const char *cat, struct ast_variable *v)
+{
+ return NULL;
+}
+#endif
+
+static struct skinny_device *build_device(const char *cat, struct ast_variable *v)
+{
+ struct skinny_device *d;
+ struct skinny_line *l;
+ struct skinny_speeddial *sd;
+ struct skinny_addon *a;
+ char device_vmexten[AST_MAX_EXTENSION];
+ struct ast_variable *chanvars = NULL;
+ int lineInstance = 1;
+ int speeddialInstance = 1;
+ int y = 0;
+
+ if (!(d = ast_calloc(1, sizeof(*d)))) {
+ return NULL;
+ } else {
+ ast_copy_string(d->name, cat, sizeof(d->name));
+ d->lastlineinstance = 1;
+ d->capability = default_capability;
+ d->prefs = default_prefs;
+ if (!ast_strlen_zero(vmexten))
+ ast_copy_string(device_vmexten, vmexten, sizeof(device_vmexten));
+ else
+ memset(device_vmexten, 0, sizeof(device_vmexten));
+
+ while(v) {
+ if (!strcasecmp(v->name, "host")) {
+ if (ast_get_ip(&d->addr, v->value)) {
+ ast_free(d);
+ return NULL;
+ }
+ } else if (!strcasecmp(v->name, "port")) {
+ d->addr.sin_port = htons(atoi(v->value));
+ } else if (!strcasecmp(v->name, "device")) {
+ ast_copy_string(d->id, v->value, sizeof(d->id));
+ } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) {
+ d->ha = ast_append_ha(v->name, v->value, d->ha, NULL);
+ } else if (!strcasecmp(v->name, "vmexten")) {
+ ast_copy_string(device_vmexten, v->value, sizeof(device_vmexten));
+ } else if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(context, v->value, sizeof(context));
+ } else if (!strcasecmp(v->name, "regexten")) {
+ ast_copy_string(regexten, v->value, sizeof(regexten));
+ } else if (!strcasecmp(v->name, "allow")) {
+ ast_parse_allow_disallow(&d->prefs, &d->capability, v->value, 1);
+ } else if (!strcasecmp(v->name, "disallow")) {
+ ast_parse_allow_disallow(&d->prefs, &d->capability, v->value, 0);
+ } else if (!strcasecmp(v->name, "version")) {
+ ast_copy_string(d->version_id, v->value, sizeof(d->version_id));
+ } else if (!strcasecmp(v->name, "canreinvite")) {
+ canreinvite = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "nat")) {
+ nat = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callerid")) {
+ if (!strcasecmp(v->value, "asreceived")) {
+ cid_num[0] = '\0';
+ cid_name[0] = '\0';
+ } else {
+ ast_callerid_split(v->value, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num));
+ }
+ } else if (!strcasecmp(v->name, "language")) {
+ ast_copy_string(language, v->value, sizeof(language));
+ } else if (!strcasecmp(v->name, "accountcode")) {
+ ast_copy_string(accountcode, v->value, sizeof(accountcode));
+ } else if (!strcasecmp(v->name, "amaflags")) {
+ y = ast_cdr_amaflags2int(v->value);
+ if (y < 0) {
+ ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno);
+ } else {
+ amaflags = y;
+ }
+ } else if (!strcasecmp(v->name, "mohinterpret") || !strcasecmp(v->name, "musiconhold")) {
+ ast_copy_string(mohinterpret, v->value, sizeof(mohinterpret));
+ } else if (!strcasecmp(v->name, "mohsuggest")) {
+ ast_copy_string(mohsuggest, v->value, sizeof(mohsuggest));
+ } else if (!strcasecmp(v->name, "callgroup")) {
+ cur_callergroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "pickupgroup")) {
+ cur_pickupgroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "immediate")) {
+ immediate = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "cancallforward")) {
+ cancallforward = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "mailbox")) {
+ ast_copy_string(mailbox, v->value, sizeof(mailbox));
+ } else if (!strcasecmp(v->name, "callreturn")) {
+ callreturn = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callwaiting")) {
+ callwaiting = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "transfer")) {
+ transfer = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "threewaycalling")) {
+ threewaycalling = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "mwiblink")) {
+ mwiblink = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "linelabel")) {
+ ast_copy_string(linelabel, v->value, sizeof(linelabel));
+ } else if (!strcasecmp(v->name, "setvar")) {
+ chanvars = add_var(v->value, chanvars);
+ } else if (!strcasecmp(v->name, "speeddial")) {
+ if (!(sd = ast_calloc(1, sizeof(*sd)))) {
+ return NULL;
+ } else {
+ char buf[256];
+ char *stringp = buf, *exten, *context, *label;
+
+ ast_copy_string(buf, v->value, sizeof(buf));
+ exten = strsep(&stringp, ",");
+ if ((context = strchr(exten, '@'))) {
+ *context++ = '\0';
+ }
+ label = stringp;
+ ast_mutex_init(&sd->lock);
+ ast_copy_string(sd->exten, exten, sizeof(sd->exten));
+ if (!ast_strlen_zero(context)) {
+ sd->isHint = 1;
+ sd->instance = lineInstance++;
+ ast_copy_string(sd->context, context, sizeof(sd->context));
+ } else {
+ sd->isHint = 0;
+ sd->instance = speeddialInstance++;
+ sd->context[0] = '\0';
+ }
+ ast_copy_string(sd->label, S_OR(label, exten), sizeof(sd->label));
+
+ sd->parent = d;
+
+ sd->next = d->speeddials;
+ d->speeddials = sd;
+ }
+ } else if (!strcasecmp(v->name, "addon")) {
+ if (!(a = ast_calloc(1, sizeof(*a)))) {
+ return NULL;
+ } else {
+ ast_mutex_init(&a->lock);
+ ast_copy_string(a->type, v->value, sizeof(a->type));
+
+ a->next = d->addons;
+ d->addons = a;
+ }
+ } else if (!strcasecmp(v->name, "trunk") || !strcasecmp(v->name, "line")) {
+ if (!(l = ast_calloc(1, sizeof(*l)))) {
+ return NULL;
+ } else {
+ ast_mutex_init(&l->lock);
+ ast_copy_string(l->name, v->value, sizeof(l->name));
+
+ /* XXX Should we check for uniqueness?? XXX */
+ ast_copy_string(l->context, context, sizeof(l->context));
+ ast_copy_string(l->cid_num, cid_num, sizeof(l->cid_num));
+ ast_copy_string(l->cid_name, cid_name, sizeof(l->cid_name));
+ ast_copy_string(l->label, linelabel, sizeof(l->label));
+ ast_copy_string(l->language, language, sizeof(l->language));
+ ast_copy_string(l->mohinterpret, mohinterpret, sizeof(l->mohinterpret));
+ ast_copy_string(l->mohsuggest, mohsuggest, sizeof(l->mohsuggest));
+ ast_copy_string(l->regexten, regexten, sizeof(l->regexten));
+ ast_copy_string(l->mailbox, mailbox, sizeof(l->mailbox));
+ if (!ast_strlen_zero(mailbox))
+ ast_verb(3, "Setting mailbox '%s' on %s@%s\n", mailbox, d->name, l->name);
+ ast_copy_string(l->vmexten, device_vmexten, sizeof(vmexten));
+ l->chanvars = chanvars;
+ l->msgstate = -1;
+ l->capability = d->capability;
+ l->prefs = d->prefs;
+ l->parent = d;
+ if (!strcasecmp(v->name, "trunk")) {
+ l->type = TYPE_TRUNK;
+ } else {
+ l->type = TYPE_LINE;
+ }
+ l->immediate = immediate;
+ l->callgroup = cur_callergroup;
+ l->pickupgroup = cur_pickupgroup;
+ l->callreturn = callreturn;
+ l->cancallforward = cancallforward;
+ l->getforward = 0;
+ set_callforwards(l, NULL, 0);
+ l->callwaiting = callwaiting;
+ l->transfer = transfer;
+ l->threewaycalling = threewaycalling;
+ l->mwiblink = mwiblink;
+ l->onhooktime = time(NULL);
+ l->instance = lineInstance++;
+ /* ASSUME we're onhook at this point */
+ l->hookstate = SKINNY_ONHOOK;
+ l->nat = nat;
+ l->canreinvite = canreinvite;
+
+ l->next = d->lines;
+ d->lines = l;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Don't know keyword '%s' at line %d\n", v->name, v->lineno);
+ }
+ v = v->next;
+ }
+
+ if (!d->lines) {
+ ast_log(LOG_ERROR, "A Skinny device must have at least one line!\n");
+ return NULL;
+ }
+ if (/*d->addr.sin_addr.s_addr && */!ntohs(d->addr.sin_port)) {
+ d->addr.sin_port = htons(DEFAULT_SKINNY_PORT);
+ }
+#if 0
+ /* I don't think we need this anymore at all, since d->ourip is set in skinny_register now */
+ if (d->addr.sin_addr.s_addr) {
+ /* XXX See note above, in 'host' option. */
+ if (ast_ouraddrfor(&d->addr.sin_addr, &d->ourip)) {
+ d->ourip = __ourip;
+ }
+ } else {
+ d->ourip = __ourip;
+ }
+#endif
+ }
+ return d;
+}
+
+static void start_rtp(struct skinny_subchannel *sub)
+{
+ struct skinny_line *l = sub->parent;
+ struct skinny_device *d = l->parent;
+ int hasvideo = 0;
+
+ ast_mutex_lock(&sub->lock);
+ /* Allocate the RTP */
+ sub->rtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
+ if (hasvideo)
+ sub->vrtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
+
+ if (sub->rtp && sub->owner) {
+ ast_channel_set_fd(sub->owner, 0, ast_rtp_fd(sub->rtp));
+ ast_channel_set_fd(sub->owner, 1, ast_rtcp_fd(sub->rtp));
+ }
+ if (hasvideo && sub->vrtp && sub->owner) {
+ ast_channel_set_fd(sub->owner, 2, ast_rtp_fd(sub->vrtp));
+ ast_channel_set_fd(sub->owner, 3, ast_rtcp_fd(sub->vrtp));
+ }
+ if (sub->rtp) {
+ ast_rtp_setqos(sub->rtp, tos_audio, cos_audio, "Skinny RTP");
+ ast_rtp_setnat(sub->rtp, l->nat);
+ }
+ if (sub->vrtp) {
+ ast_rtp_setqos(sub->vrtp, tos_video, cos_video, "Skinny VRTP");
+ ast_rtp_setnat(sub->vrtp, l->nat);
+ }
+ /* Set Frame packetization */
+ if (sub->rtp)
+ ast_rtp_codec_setpref(sub->rtp, &l->prefs);
+
+ /* Create the RTP connection */
+ transmit_connect(d->session, sub);
+ ast_mutex_unlock(&sub->lock);
+}
+
+static void *skinny_newcall(void *data)
+{
+ struct ast_channel *c = data;
+ struct skinny_subchannel *sub = c->tech_pvt;
+ struct skinny_line *l = sub->parent;
+ struct skinny_device *d = l->parent;
+ struct skinnysession *s = d->session;
+ int res = 0;
+
+ ast_copy_string(l->lastnumberdialed, c->exten, sizeof(l->lastnumberdialed));
+ ast_set_callerid(c,
+ l->hidecallerid ? "" : l->cid_num,
+ l->hidecallerid ? "" : l->cid_name,
+ c->cid.cid_ani ? NULL : l->cid_num);
+ ast_setstate(c, AST_STATE_RING);
+ res = ast_pbx_run(c);
+ if (res) {
+ ast_log(LOG_WARNING, "PBX exited non-zero\n");
+ transmit_tone(s, SKINNY_REORDER, l->instance, sub->callid);
+ }
+ return NULL;
+}
+
+static void *skinny_ss(void *data)
+{
+ struct ast_channel *c = data;
+ struct skinny_subchannel *sub = c->tech_pvt;
+ struct skinny_line *l = sub->parent;
+ struct skinny_device *d = l->parent;
+ struct skinnysession *s = d->session;
+ int len = 0;
+ int timeout = firstdigittimeout;
+ int res = 0;
+ int loop_pause = 100;
+
+ ast_verb(3, "Starting simple switch on '%s@%s'\n", l->name, d->name);
+
+ len = strlen(d->exten);
+
+ while (len < AST_MAX_EXTENSION-1) {
+ res = 1; /* Assume that we will get a digit */
+ while (strlen(d->exten) == len){
+ ast_safe_sleep(c, loop_pause);
+ timeout -= loop_pause;
+ if ( (timeout -= loop_pause) <= 0){
+ res = 0;
+ break;
+ }
+ res = 1;
+ }
+
+ timeout = 0;
+ len = strlen(d->exten);
+
+ if (!ast_ignore_pattern(c->context, d->exten)) {
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ }
+ if (ast_exists_extension(c, c->context, d->exten, 1, l->cid_num)) {
+ if (!res || !ast_matchmore_extension(c, c->context, d->exten, 1, l->cid_num)) {
+ if (l->getforward) {
+ /* Record this as the forwarding extension */
+ set_callforwards(l, d->exten, l->getforward);
+ ast_verb(3, "Setting call forward (%d) to '%s' on channel %s\n",
+ l->cfwdtype, d->exten, c->name);
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ transmit_lamp_indication(s, STIMULUS_FORWARDALL, 1, SKINNY_LAMP_ON);
+ transmit_displaynotify(s, "CFwd enabled", 10);
+ transmit_cfwdstate(s, l);
+ ast_safe_sleep(c, 500);
+ ast_indicate(c, -1);
+ ast_safe_sleep(c, 1000);
+ memset(d->exten, 0, sizeof(d->exten));
+ len = 0;
+ l->getforward = 0;
+ if (sub->owner && sub->owner->_state != AST_STATE_UP) {
+ ast_indicate(c, -1);
+ ast_hangup(c);
+ }
+ return NULL;
+ } else {
+ ast_copy_string(c->exten, d->exten, sizeof(c->exten));
+ ast_copy_string(l->lastnumberdialed, d->exten, sizeof(l->lastnumberdialed));
+ memset(d->exten, 0, sizeof(d->exten));
+ skinny_newcall(c);
+ return NULL;
+ }
+ } else {
+ /* It's a match, but they just typed a digit, and there is an ambiguous match,
+ so just set the timeout to matchdigittimeout and wait some more */
+ timeout = matchdigittimeout;
+ }
+ } else if (res == 0) {
+ ast_debug(1, "Not enough digits (%s) (and no ambiguous match)...\n", d->exten);
+ memset(d->exten, 0, sizeof(d->exten));
+ transmit_tone(s, SKINNY_REORDER, l->instance, sub->callid);
+ if (sub->owner && sub->owner->_state != AST_STATE_UP) {
+ ast_indicate(c, -1);
+ ast_hangup(c);
+ }
+ return NULL;
+ } else if (!ast_canmatch_extension(c, c->context, d->exten, 1, c->cid.cid_num) &&
+ ((d->exten[0] != '*') || (!ast_strlen_zero(d->exten) > 2))) {
+ ast_log(LOG_WARNING, "Can't match [%s] from '%s' in context %s\n", d->exten, c->cid.cid_num ? c->cid.cid_num : "<Unknown Caller>", c->context);
+ memset(d->exten, 0, sizeof(d->exten));
+ transmit_tone(s, SKINNY_REORDER, l->instance, sub->callid);
+ /* hang out for 3 seconds to let congestion play */
+ ast_safe_sleep(c, 3000);
+ break;
+ }
+ if (!timeout) {
+ timeout = gendigittimeout;
+ }
+ if (len && !ast_ignore_pattern(c->context, d->exten)) {
+ ast_indicate(c, -1);
+ }
+ }
+ if (c)
+ ast_hangup(c);
+ memset(d->exten, 0, sizeof(d->exten));
+ return NULL;
+}
+
+
+
+static int skinny_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ int res = 0;
+ int tone = 0;
+ struct skinny_subchannel *sub = ast->tech_pvt;
+ struct skinny_line *l = sub->parent;
+ struct skinny_device *d = l->parent;
+ struct skinnysession *s = d->session;
+
+ if (!d->registered) {
+ ast_log(LOG_ERROR, "Device not registered, cannot call %s\n", dest);
+ return -1;
+ }
+
+ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "skinny_call called on %s, neither down nor reserved\n", ast->name);
+ return -1;
+ }
+
+ if (skinnydebug)
+ ast_verb(3, "skinny_call(%s)\n", ast->name);
+
+ if (l->dnd) {
+ ast_queue_control(ast, AST_CONTROL_BUSY);
+ return -1;
+ }
+
+ switch (l->hookstate) {
+ case SKINNY_OFFHOOK:
+ tone = SKINNY_CALLWAITTONE;
+ break;
+ case SKINNY_ONHOOK:
+ tone = SKINNY_ALERT;
+ break;
+ default:
+ ast_log(LOG_ERROR, "Don't know how to deal with hookstate %d\n", l->hookstate);
+ break;
+ }
+
+ transmit_callstate(s, l->instance, SKINNY_RINGIN, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_RINGIN);
+ transmit_displaypromptstatus(s, "Ring-In", 0, l->instance, sub->callid);
+ transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, l->cid_name, l->cid_num, l->instance, sub->callid, 1);
+ transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK);
+ transmit_ringer_mode(s, SKINNY_RING_INSIDE);
+
+ ast_setstate(ast, AST_STATE_RINGING);
+ ast_queue_control(ast, AST_CONTROL_RINGING);
+ sub->outgoing = 1;
+ return res;
+}
+
+static int skinny_hangup(struct ast_channel *ast)
+{
+ struct skinny_subchannel *sub = ast->tech_pvt;
+ struct skinny_line *l;
+ struct skinny_device *d;
+ struct skinnysession *s;
+
+ if (!sub) {
+ ast_debug(1, "Asked to hangup channel not connected\n");
+ return 0;
+ }
+ l = sub->parent;
+ d = l->parent;
+ s = d->session;
+ if (skinnydebug)
+ ast_verbose("skinny_hangup(%s) on %s@%s\n", ast->name, l->name, d->name);
+
+ if (d->registered) {
+ if ((l->type = TYPE_LINE) && (l->hookstate == SKINNY_OFFHOOK)) {
+ l->hookstate = SKINNY_ONHOOK;
+ transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid);
+ transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_OFF);
+ transmit_speaker_mode(s, SKINNY_SPEAKEROFF);
+ } else if ((l->type = TYPE_LINE) && (l->hookstate == SKINNY_ONHOOK)) {
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid);
+ transmit_ringer_mode(s, SKINNY_RING_OFF);
+ transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_OFF);
+ do_housekeeping(s);
+ }
+ }
+ ast_mutex_lock(&sub->lock);
+ sub->owner = NULL;
+ ast->tech_pvt = NULL;
+ sub->alreadygone = 0;
+ sub->outgoing = 0;
+ if (sub->rtp) {
+ ast_rtp_destroy(sub->rtp);
+ sub->rtp = NULL;
+ }
+ ast_mutex_unlock(&sub->lock);
+ return 0;
+}
+
+static int skinny_answer(struct ast_channel *ast)
+{
+ int res = 0;
+ struct skinny_subchannel *sub = ast->tech_pvt;
+ struct skinny_line *l = sub->parent;
+ struct skinny_device *d = l->parent;
+ struct skinnysession *s = d->session;
+ char exten[AST_MAX_EXTENSION] = "";
+
+ ast_copy_string(exten, S_OR(ast->macroexten, ast->exten), sizeof(exten));
+
+ sub->cxmode = SKINNY_CX_SENDRECV;
+ if (!sub->rtp) {
+ start_rtp(sub);
+ }
+ if (skinnydebug)
+ ast_verbose("skinny_answer(%s) on %s@%s-%d\n", ast->name, l->name, d->name, sub->callid);
+ if (ast->_state != AST_STATE_UP) {
+ ast_setstate(ast, AST_STATE_UP);
+ }
+
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ /* order matters here...
+ for some reason, transmit_callinfo must be before transmit_callstate,
+ or you won't get keypad messages in some situations. */
+ transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, exten, exten, l->instance, sub->callid, 2);
+ transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED);
+ transmit_displaypromptstatus(s, "Connected", 0, l->instance, sub->callid);
+ return res;
+}
+
+/* Retrieve audio/etc from channel. Assumes sub->lock is already held. */
+static struct ast_frame *skinny_rtp_read(struct skinny_subchannel *sub)
+{
+ struct ast_channel *ast = sub->owner;
+ struct ast_frame *f;
+
+ if (!sub->rtp) {
+ /* We have no RTP allocated for this channel */
+ return &ast_null_frame;
+ }
+
+ switch(ast->fdno) {
+ case 0:
+ f = ast_rtp_read(sub->rtp); /* RTP Audio */
+ break;
+ case 1:
+ f = ast_rtcp_read(sub->rtp); /* RTCP Control Channel */
+ break;
+ case 2:
+ f = ast_rtp_read(sub->vrtp); /* RTP Video */
+ break;
+ case 3:
+ f = ast_rtcp_read(sub->vrtp); /* RTCP Control Channel for video */
+ break;
+#if 0
+ case 5:
+ /* Not yet supported */
+ f = ast_udptl_read(sub->udptl); /* UDPTL for T.38 */
+ break;
+#endif
+ default:
+ f = &ast_null_frame;
+ }
+
+ if (ast) {
+ /* We already hold the channel lock */
+ if (f->frametype == AST_FRAME_VOICE) {
+ if (f->subclass != ast->nativeformats) {
+ ast_debug(1, "Oooh, format changed to %d\n", f->subclass);
+ ast->nativeformats = f->subclass;
+ ast_set_read_format(ast, ast->readformat);
+ ast_set_write_format(ast, ast->writeformat);
+ }
+ }
+ }
+ return f;
+}
+
+static struct ast_frame *skinny_read(struct ast_channel *ast)
+{
+ struct ast_frame *fr;
+ struct skinny_subchannel *sub = ast->tech_pvt;
+ ast_mutex_lock(&sub->lock);
+ fr = skinny_rtp_read(sub);
+ ast_mutex_unlock(&sub->lock);
+ return fr;
+}
+
+static int skinny_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct skinny_subchannel *sub = ast->tech_pvt;
+ int res = 0;
+ if (frame->frametype != AST_FRAME_VOICE) {
+ if (frame->frametype == AST_FRAME_IMAGE) {
+ return 0;
+ } else {
+ ast_log(LOG_WARNING, "Can't send %d type frames with skinny_write\n", frame->frametype);
+ return 0;
+ }
+ } else {
+ if (!(frame->subclass & ast->nativeformats)) {
+ ast_log(LOG_WARNING, "Asked to transmit frame type %d, while native formats is %d (read/write = %d/%d)\n",
+ frame->subclass, ast->nativeformats, ast->readformat, ast->writeformat);
+ return -1;
+ }
+ }
+ if (sub) {
+ ast_mutex_lock(&sub->lock);
+ if (sub->rtp) {
+ res = ast_rtp_write(sub->rtp, frame);
+ }
+ ast_mutex_unlock(&sub->lock);
+ }
+ return res;
+}
+
+static int skinny_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct skinny_subchannel *sub = newchan->tech_pvt;
+ ast_log(LOG_NOTICE, "skinny_fixup(%s, %s)\n", oldchan->name, newchan->name);
+ if (sub->owner != oldchan) {
+ ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, sub->owner);
+ return -1;
+ }
+ sub->owner = newchan;
+ return 0;
+}
+
+static int skinny_senddigit_begin(struct ast_channel *ast, char digit)
+{
+ return -1; /* Start inband indications */
+}
+
+static int skinny_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+#if 0
+ struct skinny_subchannel *sub = ast->tech_pvt;
+ struct skinny_line *l = sub->parent;
+ struct skinny_device *d = l->parent;
+ int tmp;
+ /* not right */
+ sprintf(tmp, "%d", digit);
+ transmit_tone(d->session, digit, l->instance, sub->callid);
+#endif
+ return -1; /* Stop inband indications */
+}
+
+static int get_devicestate(struct skinny_line *l)
+{
+ struct skinny_subchannel *sub;
+ int res = AST_DEVICE_UNKNOWN;
+
+ if (!l)
+ res = AST_DEVICE_INVALID;
+ else if (!l->parent)
+ res = AST_DEVICE_UNAVAILABLE;
+ else if (l->dnd)
+ res = AST_DEVICE_BUSY;
+ else {
+ if (l->hookstate == SKINNY_ONHOOK) {
+ res = AST_DEVICE_NOT_INUSE;
+ } else {
+ res = AST_DEVICE_INUSE;
+ }
+
+ for (sub = l->sub; sub; sub = sub->next) {
+ if (sub->onhold) {
+ res = AST_DEVICE_ONHOLD;
+ break;
+ }
+ }
+ }
+
+ return res;
+}
+
+static char *control2str(int ind) {
+ char *tmp;
+
+ switch (ind) {
+ case AST_CONTROL_HANGUP:
+ return "Other end has hungup";
+ case AST_CONTROL_RING:
+ return "Local ring";
+ case AST_CONTROL_RINGING:
+ return "Remote end is ringing";
+ case AST_CONTROL_ANSWER:
+ return "Remote end has answered";
+ case AST_CONTROL_BUSY:
+ return "Remote end is busy";
+ case AST_CONTROL_TAKEOFFHOOK:
+ return "Make it go off hook";
+ case AST_CONTROL_OFFHOOK:
+ return "Line is off hook";
+ case AST_CONTROL_CONGESTION:
+ return "Congestion (circuits busy)";
+ case AST_CONTROL_FLASH:
+ return "Flash hook";
+ case AST_CONTROL_WINK:
+ return "Wink";
+ case AST_CONTROL_OPTION:
+ return "Set a low-level option";
+ case AST_CONTROL_RADIO_KEY:
+ return "Key Radio";
+ case AST_CONTROL_RADIO_UNKEY:
+ return "Un-Key Radio";
+ case AST_CONTROL_PROGRESS:
+ return "Remote end is making Progress";
+ case AST_CONTROL_PROCEEDING:
+ return "Remote end is proceeding";
+ case AST_CONTROL_HOLD:
+ return "Hold";
+ case AST_CONTROL_UNHOLD:
+ return "Unhold";
+ case -1:
+ return "Stop tone";
+ default:
+ if (!(tmp = ast_threadstorage_get(&control2str_threadbuf, CONTROL2STR_BUFSIZE)))
+ return "Unknown";
+ snprintf(tmp, CONTROL2STR_BUFSIZE, "UNKNOWN-%d", ind);
+ return tmp;
+ }
+}
+
+
+static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen)
+{
+ struct skinny_subchannel *sub = ast->tech_pvt;
+ struct skinny_line *l = sub->parent;
+ struct skinny_device *d = l->parent;
+ struct skinnysession *s = d->session;
+ char exten[AST_MAX_EXTENSION] = "";
+
+ if (!s) {
+ ast_log(LOG_NOTICE, "Asked to indicate '%s' condition on channel %s, but session does not exist.\n", control2str(ind), ast->name);
+ return -1;
+ }
+
+ ast_copy_string(exten, S_OR(ast->macroexten, ast->exten), sizeof(exten));
+
+ if (skinnydebug)
+ ast_verb(3, "Asked to indicate '%s' condition on channel %s\n", control2str(ind), ast->name);
+ switch(ind) {
+ case AST_CONTROL_RINGING:
+ if (ast->_state != AST_STATE_UP) {
+ if (!sub->progress) {
+ transmit_tone(s, SKINNY_ALERT, l->instance, sub->callid);
+ transmit_callstate(s, l->instance, SKINNY_RINGOUT, sub->callid);
+ transmit_dialednumber(s, exten, l->instance, sub->callid);
+ transmit_displaypromptstatus(s, "Ring Out", 0, l->instance, sub->callid);
+ transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, exten, exten, l->instance, sub->callid, 2); /* 2 = outgoing from phone */
+ sub->ringing = 1;
+ break;
+ }
+ }
+ return -1;
+ case AST_CONTROL_BUSY:
+ if (ast->_state != AST_STATE_UP) {
+ transmit_tone(s, SKINNY_BUSYTONE, l->instance, sub->callid);
+ transmit_callstate(s, l->instance, SKINNY_BUSY, sub->callid);
+ sub->alreadygone = 1;
+ ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV);
+ break;
+ }
+ return -1;
+ case AST_CONTROL_CONGESTION:
+ if (ast->_state != AST_STATE_UP) {
+ transmit_tone(s, SKINNY_REORDER, l->instance, sub->callid);
+ transmit_callstate(s, l->instance, SKINNY_CONGESTION, sub->callid);
+ sub->alreadygone = 1;
+ ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV);
+ break;
+ }
+ return -1;
+ case AST_CONTROL_PROGRESS:
+ if ((ast->_state != AST_STATE_UP) && !sub->progress && !sub->outgoing) {
+ transmit_tone(s, SKINNY_ALERT, l->instance, sub->callid);
+ transmit_callstate(s, l->instance, SKINNY_PROGRESS, sub->callid);
+ transmit_displaypromptstatus(s, "Call Progress", 0, l->instance, sub->callid);
+ transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, exten, exten, l->instance, sub->callid, 2); /* 2 = outgoing from phone */
+ sub->progress = 1;
+ break;
+ }
+ return -1;
+ case -1:
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ break;
+ case AST_CONTROL_HOLD:
+ ast_moh_start(ast, data, l->mohinterpret);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_moh_stop(ast);
+ break;
+ case AST_CONTROL_PROCEEDING:
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", ind);
+ return -1;
+ }
+ return 0;
+}
+
+static struct ast_channel *skinny_new(struct skinny_line *l, int state)
+{
+ struct ast_channel *tmp;
+ struct skinny_subchannel *sub;
+ struct skinny_device *d = l->parent;
+ struct ast_variable *v = NULL;
+ int fmt;
+
+ tmp = ast_channel_alloc(1, state, l->cid_num, l->cid_name, l->accountcode, l->exten, l->context, l->amaflags, "Skinny/%s@%s-%d", l->name, d->name, callnums);
+ if (!tmp) {
+ ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+ return NULL;
+ } else {
+ sub = ast_calloc(1, sizeof(*sub));
+ if (!sub) {
+ ast_log(LOG_WARNING, "Unable to allocate Skinny subchannel\n");
+ return NULL;
+ } else {
+ ast_mutex_init(&sub->lock);
+
+ sub->owner = tmp;
+ sub->callid = callnums++;
+ d->lastlineinstance = l->instance;
+ d->lastcallreference = sub->callid;
+ sub->cxmode = SKINNY_CX_INACTIVE;
+ sub->nat = l->nat;
+ sub->parent = l;
+ sub->onhold = 0;
+
+ sub->next = l->sub;
+ l->sub = sub;
+ }
+ tmp->tech = &skinny_tech;
+ tmp->tech_pvt = sub;
+ tmp->nativeformats = l->capability;
+ if (!tmp->nativeformats)
+ tmp->nativeformats = default_capability;
+ fmt = ast_best_codec(tmp->nativeformats);
+ if (skinnydebug)
+ ast_verbose("skinny_new: tmp->nativeformats=%d fmt=%d\n", tmp->nativeformats, fmt);
+ if (sub->rtp) {
+ ast_channel_set_fd(tmp, 0, ast_rtp_fd(sub->rtp));
+ }
+ if (state == AST_STATE_RING) {
+ tmp->rings = 1;
+ }
+ tmp->writeformat = fmt;
+ tmp->rawwriteformat = fmt;
+ tmp->readformat = fmt;
+ tmp->rawreadformat = fmt;
+ if (!ast_strlen_zero(l->language))
+ ast_string_field_set(tmp, language, l->language);
+ if (!ast_strlen_zero(l->accountcode))
+ ast_string_field_set(tmp, accountcode, l->accountcode);
+ if (l->amaflags)
+ tmp->amaflags = l->amaflags;
+
+ ast_module_ref(ast_module_info->self);
+ tmp->callgroup = l->callgroup;
+ tmp->pickupgroup = l->pickupgroup;
+
+ /* XXX Need to figure out how to handle CFwdNoAnswer */
+ if (l->cfwdtype & SKINNY_CFWD_ALL) {
+ ast_string_field_set(tmp, call_forward, l->call_forward_all);
+ } else if (l->cfwdtype & SKINNY_CFWD_BUSY) {
+ if (get_devicestate(l) != AST_DEVICE_NOT_INUSE) {
+ ast_string_field_set(tmp, call_forward, l->call_forward_busy);
+ }
+ }
+
+ ast_copy_string(tmp->context, l->context, sizeof(tmp->context));
+ ast_copy_string(tmp->exten, l->exten, sizeof(tmp->exten));
+
+ /* Don't use ast_set_callerid() here because it will
+ * generate a needless NewCallerID event */
+ tmp->cid.cid_ani = ast_strdup(l->cid_num);
+
+ tmp->priority = 1;
+ tmp->adsicpe = AST_ADSI_UNAVAILABLE;
+
+ if (sub->rtp)
+ ast_jb_configure(tmp, &global_jbconf);
+
+ /* Set channel variables for this call from configuration */
+ for (v = l->chanvars ; v ; v = v->next)
+ pbx_builtin_setvar_helper(tmp, v->name, v->value);
+
+ if (state != AST_STATE_DOWN) {
+ if (ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ ast_hangup(tmp);
+ tmp = NULL;
+ }
+ }
+ }
+ return tmp;
+}
+
+static int skinny_hold(struct skinny_subchannel *sub)
+{
+ struct skinny_line *l = sub->parent;
+ struct skinny_device *d = l->parent;
+ struct skinnysession *s = d->session;
+ struct skinny_req *req;
+
+ /* Don't try to hold a channel that doesn't exist */
+ if (!sub || !sub->owner)
+ return 0;
+
+ /* Channel needs to be put on hold */
+ if (skinnydebug)
+ ast_verbose("Putting on Hold(%d)\n", l->instance);
+
+ ast_queue_control_data(sub->owner, AST_CONTROL_HOLD,
+ S_OR(l->mohsuggest, NULL),
+ !ast_strlen_zero(l->mohsuggest) ? strlen(l->mohsuggest) + 1 : 0);
+
+ if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE)))
+ return 0;
+
+ req->data.activatecallplane.lineInstance = htolel(l->instance);
+ transmit_response(s, req);
+
+ if (!(req = req_alloc(sizeof(struct close_receive_channel_message), CLOSE_RECEIVE_CHANNEL_MESSAGE)))
+ return 0;
+
+ req->data.closereceivechannel.conferenceId = htolel(sub->callid);
+ req->data.closereceivechannel.partyId = htolel(sub->callid);
+ transmit_response(s, req);
+
+ if (!(req = req_alloc(sizeof(struct stop_media_transmission_message), STOP_MEDIA_TRANSMISSION_MESSAGE)))
+ return 0;
+
+ req->data.stopmedia.conferenceId = htolel(sub->callid);
+ req->data.stopmedia.passThruPartyId = htolel(sub->callid);
+ transmit_response(s, req);
+
+ transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_WINK);
+ sub->onhold = 1;
+ return 1;
+}
+
+static int skinny_unhold(struct skinny_subchannel *sub)
+{
+ struct skinny_line *l = sub->parent;
+ struct skinny_device *d = l->parent;
+ struct skinnysession *s = d->session;
+ struct skinny_req *req;
+
+ /* Don't try to unhold a channel that doesn't exist */
+ if (!sub || !sub->owner)
+ return 0;
+
+ /* Channel is on hold, so we will unhold */
+ if (skinnydebug)
+ ast_verbose("Taking off Hold(%d)\n", l->instance);
+
+ ast_queue_control(sub->owner, AST_CONTROL_UNHOLD);
+
+ if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE)))
+ return 0;
+
+ req->data.activatecallplane.lineInstance = htolel(l->instance);
+ transmit_response(s, req);
+
+ transmit_connect(s, sub);
+ transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON);
+ sub->onhold = 0;
+ return 1;
+}
+
+static int handle_keep_alive_message(struct skinny_req *req, struct skinnysession *s)
+{
+ if (!(req = req_alloc(0, KEEP_ALIVE_ACK_MESSAGE)))
+ return -1;
+
+ transmit_response(s, req);
+ do_housekeeping(s);
+ return 1;
+}
+
+static int handle_register_message(struct skinny_req *req, struct skinnysession *s)
+{
+ char name[16];
+ int res;
+
+ memcpy(&name, req->data.reg.name, sizeof(name));
+
+ res = skinny_register(req, s);
+ if (!res) {
+ ast_log(LOG_ERROR, "Rejecting Device %s: Device not found\n", name);
+ if (!(req = req_alloc(sizeof(struct register_rej_message), REGISTER_REJ_MESSAGE)))
+ return -1;
+
+ snprintf(req->data.regrej.errMsg, sizeof(req->data.regrej.errMsg), "No Authority: %s", name);
+ transmit_response(s, req);
+ return 0;
+ }
+ ast_verb(3, "Device '%s' successfully registered\n", name);
+
+ if (!(req = req_alloc(sizeof(struct register_ack_message), REGISTER_ACK_MESSAGE)))
+ return -1;
+
+ req->data.regack.res[0] = '0';
+ req->data.regack.res[1] = '\0';
+ req->data.regack.keepAlive = htolel(keep_alive);
+ memcpy(req->data.regack.dateTemplate, date_format, sizeof(req->data.regack.dateTemplate));
+ req->data.regack.res2[0] = '0';
+ req->data.regack.res2[1] = '\0';
+ req->data.regack.secondaryKeepAlive = htolel(keep_alive);
+ transmit_response(s, req);
+ if (skinnydebug)
+ ast_verbose("Requesting capabilities\n");
+
+ if (!(req = req_alloc(0, CAPABILITIES_REQ_MESSAGE)))
+ return -1;
+
+ transmit_response(s, req);
+
+ return res;
+}
+
+static int handle_callforward_button(struct skinny_subchannel *sub, int cfwdtype)
+{
+ struct skinny_line *l = sub->parent;
+ struct skinny_device *d = l->parent;
+ struct skinnysession *s = d->session;
+ struct ast_channel *c = sub->owner;
+ pthread_t t;
+
+ if (l->hookstate == SKINNY_ONHOOK) {
+ l->hookstate = SKINNY_OFFHOOK;
+ transmit_speaker_mode(s, SKINNY_SPEAKERON);
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ }
+ if (skinnydebug)
+ ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name);
+ transmit_displaymessage(s, NULL, l->instance, sub->callid); /* clear display */
+
+ if (l->cfwdtype & cfwdtype) {
+ set_callforwards(l, NULL, cfwdtype);
+ ast_safe_sleep(c, 500);
+ transmit_speaker_mode(s, SKINNY_SPEAKEROFF);
+ transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid);
+ transmit_displaynotify(s, "CFwd disabled", 10);
+ if (sub->owner && sub->owner->_state != AST_STATE_UP) {
+ ast_indicate(c, -1);
+ ast_hangup(c);
+ }
+ transmit_cfwdstate(s, l);
+ } else {
+ l->getforward = cfwdtype;
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_RINGOUT);
+ if (ast_pthread_create(&t, NULL, skinny_ss, c)) {
+ ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno));
+ ast_hangup(c);
+ }
+ }
+ return 0;
+}
+static int handle_ip_port_message(struct skinny_req *req, struct skinnysession *s)
+{
+ /* no response necessary */
+ return 1;
+}
+
+static int handle_keypad_button_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_subchannel *sub = NULL;
+ struct skinny_line *l;
+ struct skinny_device *d = s->device;
+ struct ast_frame f = { 0, };
+ char dgt;
+ int digit;
+ int lineInstance;
+ int callReference;
+
+ digit = letohl(req->data.keypad.button);
+ lineInstance = letohl(req->data.keypad.lineInstance);
+ callReference = letohl(req->data.keypad.callReference);
+
+ if (digit == 14) {
+ dgt = '*';
+ } else if (digit == 15) {
+ dgt = '#';
+ } else if (digit >= 0 && digit <= 9) {
+ dgt = '0' + digit;
+ } else {
+ /* digit=10-13 (A,B,C,D ?), or
+ * digit is bad value
+ *
+ * probably should not end up here, but set
+ * value for backward compatibility, and log
+ * a warning.
+ */
+ dgt = '0' + digit;
+ ast_log(LOG_WARNING, "Unsupported digit %d\n", digit);
+ }
+
+ f.subclass = dgt;
+
+ f.src = "skinny";
+
+ if (lineInstance && callReference)
+ sub = find_subchannel_by_instance_reference(d, lineInstance, callReference);
+ else
+ sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference);
+
+ if (!sub)
+ return 0;
+
+ l = sub->parent;
+ if (sub->owner) {
+ if (sub->owner->_state == 0) {
+ f.frametype = AST_FRAME_DTMF_BEGIN;
+ ast_queue_frame(sub->owner, &f);
+ }
+ /* XXX MUST queue this frame to all lines in threeway call if threeway call is active */
+ f.frametype = AST_FRAME_DTMF_END;
+ ast_queue_frame(sub->owner, &f);
+ /* XXX This seriously needs to be fixed */
+ if (sub->next && sub->next->owner) {
+ if (sub->owner->_state == 0) {
+ f.frametype = AST_FRAME_DTMF_BEGIN;
+ ast_queue_frame(sub->next->owner, &f);
+ }
+ f.frametype = AST_FRAME_DTMF_END;
+ ast_queue_frame(sub->next->owner, &f);
+ }
+ } else {
+ if (skinnydebug)
+ ast_verbose("No owner: %s\n", l->name);
+ }
+ return 1;
+}
+
+static int handle_stimulus_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+ struct skinny_subchannel *sub;
+ /*struct skinny_speeddial *sd;*/
+ struct ast_channel *c;
+ pthread_t t;
+ int event;
+ int instance;
+ int callreference;
+ /*int res = 0;*/
+
+ event = letohl(req->data.stimulus.stimulus);
+ instance = letohl(req->data.stimulus.stimulusInstance);
+ callreference = letohl(req->data.stimulus.callreference);
+ if (skinnydebug)
+ ast_verbose("callreference in handle_stimulus_message is '%d'\n", callreference);
+
+ /* Note that this call should be using the passed in instance and callreference */
+ sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference);
+
+ if (!sub) {
+ l = find_line_by_instance(d, d->lastlineinstance);
+ if (!l) {
+ return 0;
+ }
+ } else {
+ l = sub->parent;
+ }
+
+ switch(event) {
+ case STIMULUS_REDIAL:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Redial(%d/%d)\n", instance, callreference);
+
+ if (ast_strlen_zero(l->lastnumberdialed)) {
+ ast_log(LOG_WARNING, "Attempted redial, but no previously dialed number found.\n");
+ l->hookstate = SKINNY_ONHOOK;
+ transmit_speaker_mode(s, SKINNY_SPEAKEROFF);
+ transmit_callstate(s, l->instance, SKINNY_ONHOOK, instance);
+ break;
+ }
+
+ c = skinny_new(l, AST_STATE_DOWN);
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ l = sub->parent;
+ if (l->hookstate == SKINNY_ONHOOK) {
+ l->hookstate = SKINNY_OFFHOOK;
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ }
+ if (skinnydebug)
+ ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name);
+ transmit_displaymessage(s, NULL, l->instance, sub->callid); /* clear display */
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_RINGOUT);
+
+ if (!ast_ignore_pattern(c->context, l->lastnumberdialed)) {
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ }
+ ast_copy_string(c->exten, l->lastnumberdialed, sizeof(c->exten));
+ if (ast_pthread_create(&t, NULL, skinny_newcall, c)) {
+ ast_log(LOG_WARNING, "Unable to create new call thread: %s\n", strerror(errno));
+ ast_hangup(c);
+ }
+ }
+ break;
+ case STIMULUS_SPEEDDIAL:
+ {
+ struct skinny_speeddial *sd;
+
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: SpeedDial(%d/%d)\n", instance, callreference);
+ if (!(sd = find_speeddial_by_instance(d, instance, 0))) {
+ return 0;
+ }
+
+ if (!sub || !sub->owner)
+ c = skinny_new(l, AST_STATE_DOWN);
+ else
+ c = sub->owner;
+
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ l = sub->parent;
+ if (l->hookstate == SKINNY_ONHOOK) {
+ l->hookstate = SKINNY_OFFHOOK;
+ transmit_speaker_mode(s, SKINNY_SPEAKERON);
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ }
+ if (skinnydebug)
+ ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name);
+ transmit_displaymessage(s, NULL, l->instance, sub->callid); /* clear display */
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_RINGOUT);
+
+ if (!ast_ignore_pattern(c->context, sd->exten)) {
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ }
+ if (ast_exists_extension(c, c->context, sd->exten, 1, l->cid_num)) {
+ ast_copy_string(c->exten, sd->exten, sizeof(c->exten));
+ ast_copy_string(l->lastnumberdialed, sd->exten, sizeof(l->lastnumberdialed));
+
+ if (ast_pthread_create(&t, NULL, skinny_newcall, c)) {
+ ast_log(LOG_WARNING, "Unable to create new call thread: %s\n", strerror(errno));
+ ast_hangup(c);
+ }
+ break;
+ }
+ }
+ }
+ break;
+ case STIMULUS_HOLD:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Hold(%d/%d)\n", instance, callreference);
+
+ if (!sub)
+ break;
+
+ if (sub->onhold) {
+ skinny_unhold(sub);
+ } else {
+ skinny_hold(sub);
+ }
+ break;
+ case STIMULUS_TRANSFER:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Transfer(%d/%d)\n", instance, callreference);
+ /* XXX figure out how to transfer */
+ break;
+ case STIMULUS_CONFERENCE:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Conference(%d/%d)\n", instance, callreference);
+ /* XXX determine the best way to pull off a conference. Meetme? */
+ break;
+ case STIMULUS_VOICEMAIL:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Voicemail(%d/%d)\n", instance, callreference);
+
+ if (!sub || !sub->owner) {
+ c = skinny_new(l, AST_STATE_DOWN);
+ } else {
+ c = sub->owner;
+ }
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ l = sub->parent;
+
+ if (ast_strlen_zero(l->vmexten)) /* Exit the call if no VM pilot */
+ break;
+
+ if (l->hookstate == SKINNY_ONHOOK){
+ l->hookstate = SKINNY_OFFHOOK;
+ transmit_speaker_mode(s, SKINNY_SPEAKERON);
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ }
+
+ if (skinnydebug)
+ ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name);
+
+ transmit_displaymessage(s, NULL, l->instance, sub->callid); /* clear display */
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_RINGOUT);
+
+ if (!ast_ignore_pattern(c->context, vmexten)) {
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ }
+
+ if (ast_exists_extension(c, c->context, l->vmexten, 1, l->cid_num)) {
+ ast_copy_string(c->exten, l->vmexten, sizeof(c->exten));
+ ast_copy_string(l->lastnumberdialed, l->vmexten, sizeof(l->lastnumberdialed));
+ if (ast_pthread_create(&t, NULL, skinny_newcall, c)) {
+ ast_log(LOG_WARNING, "Unable to create new call thread: %s\n", strerror(errno));
+ ast_hangup(c);
+ }
+ break;
+ }
+ }
+ break;
+ case STIMULUS_CALLPARK:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Park Call(%d/%d)\n", instance, callreference);
+ /* XXX Park the call */
+ break;
+ case STIMULUS_DND:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: DND (%d/%d)\n", instance, callreference);
+
+ /* Do not disturb */
+ if (l->dnd != 0){
+ ast_verb(3, "Disabling DND on %s@%s\n", l->name, d->name);
+ l->dnd = 0;
+ transmit_lamp_indication(s, STIMULUS_DND, 1, SKINNY_LAMP_ON);
+ transmit_displaynotify(s, "DnD disabled", 10);
+ } else {
+ ast_verb(3, "Enabling DND on %s@%s\n", l->name, d->name);
+ l->dnd = 1;
+ transmit_lamp_indication(s, STIMULUS_DND, 1, SKINNY_LAMP_OFF);
+ transmit_displaynotify(s, "DnD enabled", 10);
+ }
+ break;
+ case STIMULUS_FORWARDALL:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Forward All(%d/%d)\n", instance, callreference);
+
+ if (!sub || !sub->owner) {
+ c = skinny_new(l, AST_STATE_DOWN);
+ } else {
+ c = sub->owner;
+ }
+
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ handle_callforward_button(sub, SKINNY_CFWD_ALL);
+ }
+ break;
+ case STIMULUS_FORWARDBUSY:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Forward Busy (%d/%d)\n", instance, callreference);
+
+ if (!sub || !sub->owner) {
+ c = skinny_new(l, AST_STATE_DOWN);
+ } else {
+ c = sub->owner;
+ }
+
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ handle_callforward_button(sub, SKINNY_CFWD_BUSY);
+ }
+ break;
+ case STIMULUS_FORWARDNOANSWER:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Forward No Answer (%d/%d)\n", instance, callreference);
+
+#if 0 /* Not sure how to handle this yet */
+ if (!sub || !sub->owner) {
+ c = skinny_new(l, AST_STATE_DOWN);
+ } else {
+ c = sub->owner;
+ }
+
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ handle_callforward_button(sub, SKINNY_CFWD_NOANSWER);
+ }
+#endif
+ break;
+ case STIMULUS_DISPLAY:
+ /* Not sure what this is */
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Display(%d/%d)\n", instance, callreference);
+ break;
+ case STIMULUS_LINE:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Line(%d/%d)\n", instance, callreference);
+
+ l = find_line_by_instance(d, instance);
+
+ if (!l) {
+ return 0;
+ }
+
+ /* turn the speaker on */
+ transmit_speaker_mode(s, SKINNY_SPEAKERON);
+ transmit_ringer_mode(s, SKINNY_RING_OFF);
+ transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON);
+
+ l->hookstate = SKINNY_OFFHOOK;
+
+ ast_device_state_changed("Skinny/%s@%s", l->name, d->name);
+
+ if (sub && sub->outgoing) {
+ /* We're answering a ringing call */
+ ast_queue_control(sub->owner, AST_CONTROL_ANSWER);
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid);
+ transmit_displaypromptstatus(s, "Connected", 0, l->instance, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED);
+ start_rtp(sub);
+ ast_setstate(sub->owner, AST_STATE_UP);
+ } else {
+ if (sub && sub->owner) {
+ ast_debug(1, "Current subchannel [%s] already has owner\n", sub->owner->name);
+ } else {
+ c = skinny_new(l, AST_STATE_DOWN);
+ if (c) {
+ sub = c->tech_pvt;
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ if (skinnydebug)
+ ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name);
+ transmit_displaymessage(s, NULL, l->instance, sub->callid); /* clear display */
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_OFFHOOK);
+
+ /* start the switch thread */
+ if (ast_pthread_create(&t, NULL, skinny_ss, c)) {
+ ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno));
+ ast_hangup(c);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ }
+ }
+ }
+ break;
+ default:
+ if (skinnydebug)
+ ast_verbose("RECEIVED UNKNOWN STIMULUS: %d(%d/%d)\n", event, instance, callreference);
+ break;
+ }
+ ast_device_state_changed("Skinny/%s@%s", l->name, d->name);
+
+ return 1;
+}
+
+static int handle_offhook_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+ struct skinny_subchannel *sub;
+ struct ast_channel *c;
+ pthread_t t;
+ int unknown1;
+ int unknown2;
+
+ unknown1 = letohl(req->data.offhook.unknown1);
+ unknown2 = letohl(req->data.offhook.unknown2);
+
+ sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference);
+
+ if (!sub) {
+ l = find_line_by_instance(d, d->lastlineinstance);
+ if (!l) {
+ return 0;
+ }
+ } else {
+ l = sub->parent;
+ }
+
+ transmit_ringer_mode(s, SKINNY_RING_OFF);
+ l->hookstate = SKINNY_OFFHOOK;
+
+ ast_device_state_changed("Skinny/%s@%s", l->name, d->name);
+
+ if (sub && sub->onhold) {
+ return 1;
+ }
+
+ transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON);
+
+ if (sub && sub->outgoing) {
+ /* We're answering a ringing call */
+ ast_queue_control(sub->owner, AST_CONTROL_ANSWER);
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED);
+ start_rtp(sub);
+ ast_setstate(sub->owner, AST_STATE_UP);
+ } else {
+ if (sub && sub->owner) {
+ ast_debug(1, "Current sub [%s] already has owner\n", sub->owner->name);
+ } else {
+ c = skinny_new(l, AST_STATE_DOWN);
+ if (c) {
+ sub = c->tech_pvt;
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ if (skinnydebug)
+ ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name);
+ transmit_displaymessage(s, NULL, l->instance, sub->callid); /* clear display */
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_OFFHOOK);
+
+ /* start the switch thread */
+ if (ast_pthread_create(&t, NULL, skinny_ss, c)) {
+ ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno));
+ ast_hangup(c);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ }
+ }
+ }
+ return 1;
+}
+
+static int handle_onhook_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+ struct skinny_subchannel *sub;
+ int unknown1;
+ int unknown2;
+
+ unknown1 = letohl(req->data.onhook.unknown1);
+ unknown2 = letohl(req->data.onhook.unknown2);
+
+ sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference);
+
+ if (!sub) {
+ return 0;
+ }
+ l = sub->parent;
+
+ if (l->hookstate == SKINNY_ONHOOK) {
+ /* Something else already put us back on hook */
+ return 0;
+ }
+ l->hookstate = SKINNY_ONHOOK;
+
+ ast_device_state_changed("Skinny/%s@%s", l->name, d->name);
+
+ if (sub->onhold) {
+ return 0;
+ }
+
+ sub->cxmode = SKINNY_CX_RECVONLY;
+ transmit_callstate(s, l->instance, l->hookstate, sub->callid);
+ if (skinnydebug)
+ ast_verbose("Skinny %s@%s went on hook\n", l->name, d->name);
+ if (l->transfer && (sub->owner && sub->next && sub->next->owner) && ((!sub->outgoing) || (sub->next && !sub->next->outgoing))) {
+ /* We're allowed to transfer, we have two active calls and
+ we made at least one of the calls. Let's try and transfer */
+
+#if 0
+ if ((res = attempt_transfer(p)) < 0) {
+ if (sub->next && sub->next->owner) {
+ sub->next->alreadygone = 1;
+ ast_queue_hangup(sub->next->owner,1);
+ }
+ } else if (res) {
+ ast_log(LOG_WARNING, "Transfer attempt failed\n");
+ return 0;
+ }
+#endif
+ } else {
+ /* Hangup the current call */
+ /* If there is another active call, skinny_hangup will ring the phone with the other call */
+ if (sub->owner) {
+ sub->alreadygone = 1;
+ ast_queue_hangup(sub->owner);
+ } else {
+ ast_log(LOG_WARNING, "Skinny(%s@%s-%d) channel already destroyed\n",
+ l->name, d->name, sub->callid);
+ }
+ }
+ if ((l->hookstate == SKINNY_ONHOOK) && (sub->next && !sub->next->rtp)) {
+ do_housekeeping(s);
+ }
+ return 1;
+}
+
+static int handle_capabilities_res_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+ uint32_t count = 0;
+ int codecs = 0;
+ int i;
+
+ count = letohl(req->data.caps.count);
+ if (count > SKINNY_MAX_CAPABILITIES) {
+ count = SKINNY_MAX_CAPABILITIES;
+ ast_log(LOG_WARNING, "Received more capabilities than we can handle (%d). Ignoring the rest.\n", SKINNY_MAX_CAPABILITIES);
+ }
+
+ for (i = 0; i < count; i++) {
+ int acodec = 0;
+ int scodec = 0;
+ scodec = letohl(req->data.caps.caps[i].codec);
+ acodec = codec_skinny2ast(scodec);
+ if (skinnydebug)
+ ast_verbose("Adding codec capability '%d (%d)'\n", acodec, scodec);
+ codecs |= acodec;
+ }
+
+ d->capability &= codecs;
+ ast_verbose("Device capability set to '%d'\n", d->capability);
+ for (l = d->lines; l; l = l->next) {
+ ast_mutex_lock(&l->lock);
+ l->capability = d->capability;
+ ast_mutex_unlock(&l->lock);
+ }
+
+ return 1;
+}
+
+static int handle_speed_dial_stat_req_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_speeddial *sd;
+ int instance;
+
+ instance = letohl(req->data.speeddialreq.speedDialNumber);
+
+ sd = find_speeddial_by_instance(d, instance, 0);
+
+ if (!sd) {
+ return 0;
+ }
+
+ if (!(req = req_alloc(sizeof(struct speed_dial_stat_res_message), SPEED_DIAL_STAT_RES_MESSAGE)))
+ return -1;
+
+ req->data.speeddialreq.speedDialNumber = htolel(instance);
+ ast_copy_string(req->data.speeddial.speedDialDirNumber, sd->exten, sizeof(req->data.speeddial.speedDialDirNumber));
+ ast_copy_string(req->data.speeddial.speedDialDisplayName, sd->label, sizeof(req->data.speeddial.speedDialDisplayName));
+
+ transmit_response(s, req);
+ return 1;
+}
+
+static int handle_line_state_req_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+ struct skinny_speeddial *sd = NULL;
+ int instance;
+
+ instance = letohl(req->data.line.lineNumber);
+
+ ast_mutex_lock(&devicelock);
+
+ l = find_line_by_instance(d, instance);
+
+ if (!l) {
+ sd = find_speeddial_by_instance(d, instance, 1);
+ }
+
+ if (!l && !sd) {
+ return 0;
+ }
+
+ ast_mutex_unlock(&devicelock);
+
+ if (!(req = req_alloc(sizeof(struct line_stat_res_message), LINE_STAT_RES_MESSAGE)))
+ return -1;
+
+ req->data.linestat.lineNumber = letohl(instance);
+ if (!l) {
+ memcpy(req->data.linestat.lineDirNumber, sd->label, sizeof(req->data.linestat.lineDirNumber));
+ memcpy(req->data.linestat.lineDisplayName, sd->label, sizeof(req->data.linestat.lineDisplayName));
+ } else {
+ memcpy(req->data.linestat.lineDirNumber, l->name, sizeof(req->data.linestat.lineDirNumber));
+ memcpy(req->data.linestat.lineDisplayName, l->label, sizeof(req->data.linestat.lineDisplayName));
+ }
+ transmit_response(s,req);
+ return 1;
+}
+
+static int handle_time_date_req_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct timeval tv = ast_tvnow();
+ struct ast_tm cmtime;
+
+ if (!(req = req_alloc(sizeof(struct definetimedate_message), DEFINETIMEDATE_MESSAGE)))
+ return -1;
+
+ ast_localtime(&tv, &cmtime, NULL);
+ req->data.definetimedate.year = htolel(cmtime.tm_year+1900);
+ req->data.definetimedate.month = htolel(cmtime.tm_mon+1);
+ req->data.definetimedate.dayofweek = htolel(cmtime.tm_wday);
+ req->data.definetimedate.day = htolel(cmtime.tm_mday);
+ req->data.definetimedate.hour = htolel(cmtime.tm_hour);
+ req->data.definetimedate.minute = htolel(cmtime.tm_min);
+ req->data.definetimedate.seconds = htolel(cmtime.tm_sec);
+ req->data.definetimedate.milliseconds = htolel(cmtime.tm_usec / 1000);
+ req->data.definetimedate.timestamp = htolel(tv.tv_sec);
+ transmit_response(s, req);
+ return 1;
+}
+
+static int handle_button_template_req_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+ int i;
+
+ struct skinny_speeddial *sd;
+ struct button_definition_template btn[42];
+ int lineInstance = 1;
+ int speeddialInstance = 1;
+ int buttonCount = 0;
+
+ if (!(req = req_alloc(sizeof(struct button_template_res_message), BUTTON_TEMPLATE_RES_MESSAGE)))
+ return -1;
+
+ memset(&btn, 0, sizeof(btn));
+
+ get_button_template(s, btn);
+
+ for (i=0; i<42; i++) {
+ int btnSet = 0;
+ switch (btn[i].buttonDefinition) {
+ case BT_CUST_LINE:
+ /* assume failure */
+ req->data.buttontemplate.definition[i].buttonDefinition = BT_NONE;
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(0);
+
+ for (l = d->lines; l; l = l->next) {
+ if (l->instance == lineInstance) {
+ ast_verbose("Adding button: %d, %d\n", BT_LINE, lineInstance);
+ req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE;
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(lineInstance);
+ lineInstance++;
+ buttonCount++;
+ btnSet = 1;
+ break;
+ }
+ }
+
+ if (!btnSet) {
+ for (sd = d->speeddials; sd; sd = sd->next) {
+ if (sd->isHint && sd->instance == lineInstance) {
+ ast_verbose("Adding button: %d, %d\n", BT_LINE, lineInstance);
+ req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE;
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(lineInstance);
+ lineInstance++;
+ buttonCount++;
+ btnSet = 1;
+ break;
+ }
+ }
+ }
+ break;
+ case BT_CUST_LINESPEEDDIAL:
+ /* assume failure */
+ req->data.buttontemplate.definition[i].buttonDefinition = BT_NONE;
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(0);
+
+ for (l = d->lines; l; l = l->next) {
+ if (l->instance == lineInstance) {
+ ast_verbose("Adding button: %d, %d\n", BT_LINE, lineInstance);
+ req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE;
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(lineInstance);
+ lineInstance++;
+ buttonCount++;
+ btnSet = 1;
+ break;
+ }
+ }
+
+ if (!btnSet) {
+ for (sd = d->speeddials; sd; sd = sd->next) {
+ if (sd->isHint && sd->instance == lineInstance) {
+ ast_verbose("Adding button: %d, %d\n", BT_LINE, lineInstance);
+ req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE;
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(lineInstance);
+ lineInstance++;
+ buttonCount++;
+ btnSet = 1;
+ break;
+ } else if (!sd->isHint && sd->instance == speeddialInstance) {
+ ast_verbose("Adding button: %d, %d\n", BT_SPEEDDIAL, speeddialInstance);
+ req->data.buttontemplate.definition[i].buttonDefinition = BT_SPEEDDIAL;
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(speeddialInstance);
+ speeddialInstance++;
+ buttonCount++;
+ btnSet = 1;
+ break;
+ }
+ }
+ }
+ break;
+ case BT_LINE:
+ req->data.buttontemplate.definition[i].buttonDefinition = htolel(BT_NONE);
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(0);
+
+ for (l = d->lines; l; l = l->next) {
+ if (l->instance == lineInstance) {
+ ast_verbose("Adding button: %d, %d\n", BT_LINE, lineInstance);
+ req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE;
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(lineInstance);
+ lineInstance++;
+ buttonCount++;
+ btnSet = 1;
+ break;
+ }
+ }
+ break;
+ case BT_SPEEDDIAL:
+ req->data.buttontemplate.definition[i].buttonDefinition = BT_NONE;
+ req->data.buttontemplate.definition[i].instanceNumber = 0;
+
+ for (sd = d->speeddials; sd; sd = sd->next) {
+ if (!sd->isHint && sd->instance == speeddialInstance) {
+ ast_verbose("Adding button: %d, %d\n", BT_SPEEDDIAL, speeddialInstance);
+ req->data.buttontemplate.definition[i].buttonDefinition = BT_SPEEDDIAL;
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(speeddialInstance - 1);
+ speeddialInstance++;
+ buttonCount++;
+ btnSet = 1;
+ break;
+ }
+ }
+ break;
+ case BT_NONE:
+ break;
+ default:
+ ast_verbose("Adding button: %d, %d\n", btn[i].buttonDefinition, 0);
+ req->data.buttontemplate.definition[i].buttonDefinition = htolel(btn[i].buttonDefinition);
+ req->data.buttontemplate.definition[i].instanceNumber = htolel(0);
+ buttonCount++;
+ btnSet = 1;
+ break;
+ }
+ }
+
+ req->data.buttontemplate.buttonOffset = htolel(0);
+ req->data.buttontemplate.buttonCount = htolel(buttonCount);
+ req->data.buttontemplate.totalButtonCount = htolel(buttonCount);
+
+ if (skinnydebug)
+ ast_verbose("Sending %d template to %s\n",
+ d->type,
+ d->name);
+ transmit_response(s, req);
+ return 1;
+}
+
+static int handle_version_req_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ if (!(req = req_alloc(sizeof(struct version_res_message), VERSION_RES_MESSAGE)))
+ return -1;
+
+ ast_copy_string(req->data.version.version, d->version_id, sizeof(req->data.version.version));
+ transmit_response(s, req);
+ return 1;
+}
+
+static int handle_server_request_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ if (!(req = req_alloc(sizeof(struct server_res_message), SERVER_RES_MESSAGE)))
+ return -1;
+
+ memcpy(req->data.serverres.server[0].serverName, ourhost,
+ sizeof(req->data.serverres.server[0].serverName));
+ req->data.serverres.serverListenPort[0] = htolel(ourport);
+ req->data.serverres.serverIpAddr[0] = htolel(d->ourip.s_addr);
+ transmit_response(s, req);
+ return 1;
+}
+
+static int handle_alarm_message(struct skinny_req *req, struct skinnysession *s)
+{
+ /* no response necessary */
+ if (skinnydebug)
+ ast_verbose("Received Alarm Message: %s\n", req->data.alarm.displayMessage);
+
+ return 1;
+}
+
+static int handle_open_receive_channel_ack_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+ struct skinny_subchannel *sub;
+ struct ast_format_list fmt;
+ struct sockaddr_in sin;
+ struct sockaddr_in us;
+ uint32_t addr;
+ int port;
+ int status;
+ int passthruid;
+
+ status = letohl(req->data.openreceivechannelack.status);
+ if (status) {
+ ast_log(LOG_ERROR, "Open Receive Channel Failure\n");
+ return 0;
+ }
+ addr = letohl(req->data.openreceivechannelack.ipAddr);
+ port = letohl(req->data.openreceivechannelack.port);
+ passthruid = letohl(req->data.openreceivechannelack.passThruId);
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = addr;
+ sin.sin_port = htons(port);
+
+ sub = find_subchannel_by_reference(d, passthruid);
+
+ if (!sub)
+ return 0;
+
+ l = sub->parent;
+
+ if (sub->rtp) {
+ ast_rtp_set_peer(sub->rtp, &sin);
+ ast_rtp_get_us(sub->rtp, &us);
+ } else {
+ ast_log(LOG_ERROR, "No RTP structure, this is very bad\n");
+ return 0;
+ }
+
+ if (skinnydebug)
+ ast_verbose("ipaddr = %s:%d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+
+ if (!(req = req_alloc(sizeof(struct start_media_transmission_message), START_MEDIA_TRANSMISSION_MESSAGE)))
+ return -1;
+
+ fmt = ast_codec_pref_getsize(&l->prefs, ast_best_codec(l->capability));
+
+ if (skinnydebug)
+ ast_verbose("Setting payloadType to '%d' (%d ms)\n", fmt.bits, fmt.cur_ms);
+
+ req->data.startmedia.conferenceId = htolel(sub->callid);
+ req->data.startmedia.passThruPartyId = htolel(sub->callid);
+ req->data.startmedia.remoteIp = htolel(d->ourip.s_addr);
+ req->data.startmedia.remotePort = htolel(ntohs(us.sin_port));
+ req->data.startmedia.packetSize = htolel(fmt.cur_ms);
+ req->data.startmedia.payloadType = htolel(codec_ast2skinny(fmt.bits));
+ req->data.startmedia.qualifier.precedence = htolel(127);
+ req->data.startmedia.qualifier.vad = htolel(0);
+ req->data.startmedia.qualifier.packets = htolel(0);
+ req->data.startmedia.qualifier.bitRate = htolel(0);
+ transmit_response(s, req);
+
+ return 1;
+}
+
+static int handle_enbloc_call_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+ struct skinny_subchannel *sub = NULL;
+ struct ast_channel *c;
+ pthread_t t;
+
+ if (skinnydebug)
+ ast_verbose("Received Enbloc Call: %s\n", req->data.enbloccallmessage.calledParty);
+
+ sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference);
+
+ if (!sub) {
+ l = find_line_by_instance(d, d->lastlineinstance);
+ if (!l) {
+ return 0;
+ }
+ } else {
+ l = sub->parent;
+ }
+
+ c = skinny_new(l, AST_STATE_DOWN);
+
+ if(!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ l->hookstate = SKINNY_OFFHOOK;
+
+ sub = c->tech_pvt;
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ if (skinnydebug)
+ ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name);
+ transmit_displaymessage(s, NULL, l->instance, sub->callid); /* clear display */
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+
+ if (!ast_ignore_pattern(c->context, req->data.enbloccallmessage.calledParty)) {
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ }
+ ast_copy_string(c->exten, req->data.enbloccallmessage.calledParty, sizeof(c->exten));
+ if (ast_pthread_create(&t, NULL, skinny_newcall, c)) {
+ ast_log(LOG_WARNING, "Unable to create new call thread: %s\n", strerror(errno));
+ ast_hangup(c);
+ }
+ }
+
+ return 1;
+}
+
+
+static int handle_soft_key_set_req_message(struct skinny_req *req, struct skinnysession *s)
+{
+ int i;
+ int x;
+ int y;
+ const struct soft_key_definitions *softkeymode = soft_key_default_definitions;
+
+ if (!(req = req_alloc(sizeof(struct soft_key_set_res_message), SOFT_KEY_SET_RES_MESSAGE)))
+ return -1;
+
+ req->data.softkeysets.softKeySetOffset = htolel(0);
+ req->data.softkeysets.softKeySetCount = htolel(11);
+ req->data.softkeysets.totalSoftKeySetCount = htolel(11);
+ for (x = 0; x < sizeof(soft_key_default_definitions) / sizeof(struct soft_key_definitions); x++) {
+ const uint8_t *defaults = softkeymode->defaults;
+ /* XXX I wanted to get the size of the array dynamically, but that wasn't wanting to work.
+ This will have to do for now. */
+ for (y = 0; y < softkeymode->count; y++) {
+ for (i = 0; i < (sizeof(soft_key_template_default) / sizeof(struct soft_key_template_definition)); i++) {
+ if (defaults[y] == i+1) {
+ req->data.softkeysets.softKeySetDefinition[softkeymode->mode].softKeyTemplateIndex[y] = htolel(i+1);
+ req->data.softkeysets.softKeySetDefinition[softkeymode->mode].softKeyInfoIndex[y] = htolel(i+301);
+ }
+ }
+ }
+ softkeymode++;
+ }
+ transmit_response(s,req);
+ transmit_selectsoftkeys(s, 0, 0, KEYDEF_ONHOOK);
+ return 1;
+}
+
+static int handle_soft_key_event_message(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+ struct skinny_subchannel *sub = NULL;
+ struct ast_channel *c;
+ pthread_t t;
+ int event;
+ int instance;
+ int callreference;
+
+ event = letohl(req->data.softkeyeventmessage.softKeyEvent);
+ instance = letohl(req->data.softkeyeventmessage.instance);
+ callreference = letohl(req->data.softkeyeventmessage.callreference);
+
+ if (instance) {
+ l = find_line_by_instance(d, instance);
+ if (callreference) {
+ sub = find_subchannel_by_instance_reference(d, instance, callreference);
+ } else {
+ sub = find_subchannel_by_instance_reference(d, instance, d->lastcallreference);
+ }
+ } else {
+ l = find_line_by_instance(d, d->lastlineinstance);
+ }
+
+ if (!l) {
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: %d(%d/%d)\n", event, instance, callreference);
+ return 0;
+ }
+
+ ast_device_state_changed("Skinny/%s@%s", l->name, d->name);
+
+ switch(event) {
+ case SOFTKEY_NONE:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: None(%d/%d)\n", instance, callreference);
+ break;
+ case SOFTKEY_REDIAL:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Redial(%d/%d)\n", instance, callreference);
+
+ if (ast_strlen_zero(l->lastnumberdialed)) {
+ ast_log(LOG_WARNING, "Attempted redial, but no previously dialed number found.\n");
+ l->hookstate = SKINNY_ONHOOK;
+ transmit_speaker_mode(s, SKINNY_SPEAKEROFF);
+ transmit_callstate(s, l->instance, SKINNY_ONHOOK, instance);
+ break;
+ }
+
+ if (!sub || !sub->owner) {
+ c = skinny_new(l, AST_STATE_DOWN);
+ } else {
+ c = sub->owner;
+ }
+
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ if (l->hookstate == SKINNY_ONHOOK) {
+ l->hookstate = SKINNY_OFFHOOK;
+ transmit_speaker_mode(s, SKINNY_SPEAKERON);
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ }
+ if (skinnydebug)
+ ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name);
+ transmit_displaymessage(s, NULL, l->instance, sub->callid); /* clear display */
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_RINGOUT);
+
+ if (!ast_ignore_pattern(c->context, l->lastnumberdialed)) {
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ }
+ ast_copy_string(c->exten, l->lastnumberdialed, sizeof(c->exten));
+ if (ast_pthread_create(&t, NULL, skinny_newcall, c)) {
+ ast_log(LOG_WARNING, "Unable to create new call thread: %s\n", strerror(errno));
+ ast_hangup(c);
+ }
+ }
+ break;
+ case SOFTKEY_NEWCALL: /* Actually the DIAL softkey */
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: New Call(%d/%d)\n", instance, callreference);
+
+ if (!sub || !sub->owner) {
+ c = skinny_new(l, AST_STATE_DOWN);
+ } else {
+ c = sub->owner;
+ }
+
+ /* transmit_ringer_mode(s,SKINNY_RING_OFF);
+ transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); */
+
+ /* l->hookstate = SKINNY_OFFHOOK; */
+
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ if (l->hookstate == SKINNY_ONHOOK) {
+ l->hookstate = SKINNY_OFFHOOK;
+ transmit_speaker_mode(s, SKINNY_SPEAKERON);
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ }
+
+ if (skinnydebug)
+ ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name);
+ transmit_displaymessage(s, NULL, l->instance, sub->callid); /* clear display */
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_OFFHOOK);
+
+ /* start the switch thread */
+ if (ast_pthread_create(&t, NULL, skinny_ss, c)) {
+ ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno));
+ ast_hangup(c);
+ }
+ }
+ break;
+ case SOFTKEY_HOLD:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Hold(%d/%d)\n", instance, callreference);
+
+ if (sub) {
+ if (sub->onhold) {
+ skinny_unhold(sub);
+ } else {
+ skinny_hold(sub);
+ }
+ }
+
+ break;
+ case SOFTKEY_TRNSFER:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Transfer(%d/%d)\n", instance, callreference);
+ /* XXX figure out how to transfer */
+ break;
+ case SOFTKEY_DND:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: DND(%d/%d)\n", instance, callreference);
+
+ /* Do not disturb */
+ if (l->dnd != 0){
+ ast_verb(3, "Disabling DND on %s@%s\n", l->name, d->name);
+ l->dnd = 0;
+ transmit_lamp_indication(s, STIMULUS_DND, 1, SKINNY_LAMP_ON);
+ transmit_displaynotify(s, "DnD disabled", 10);
+ } else {
+ ast_verb(3, "Enabling DND on %s@%s\n", l->name, d->name);
+ l->dnd = 1;
+ transmit_lamp_indication(s, STIMULUS_DND, 1, SKINNY_LAMP_OFF);
+ transmit_displaynotify(s, "DnD enabled", 10);
+ }
+ break;
+ case SOFTKEY_CFWDALL:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Forward All(%d/%d)\n", instance, callreference);
+
+ if (!sub || !sub->owner) {
+ c = skinny_new(l, AST_STATE_DOWN);
+ } else {
+ c = sub->owner;
+ }
+
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ handle_callforward_button(sub, SKINNY_CFWD_ALL);
+ }
+ break;
+ case SOFTKEY_CFWDBUSY:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Forward Busy (%d/%d)\n", instance, callreference);
+
+ if (!sub || !sub->owner) {
+ c = skinny_new(l, AST_STATE_DOWN);
+ } else {
+ c = sub->owner;
+ }
+
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ handle_callforward_button(sub, SKINNY_CFWD_BUSY);
+ }
+ break;
+ case SOFTKEY_CFWDNOANSWER:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Forward No Answer (%d/%d)\n", instance, callreference);
+
+#if 0 /* Not sure how to handle this yet */
+ if (!sub || !sub->owner) {
+ c = skinny_new(l, AST_STATE_DOWN);
+ } else {
+ c = sub->owner;
+ }
+
+ if (!c) {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ } else {
+ sub = c->tech_pvt;
+ handle_callforward_button(sub, SKINNY_CFWD_NOANSWER);
+ }
+#endif
+ break;
+ case SOFTKEY_BKSPC:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Backspace(%d/%d)\n", instance, callreference);
+ break;
+ case SOFTKEY_ENDCALL:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: End Call(%d/%d)\n", instance, callreference);
+
+ if (l->hookstate == SKINNY_ONHOOK) {
+ /* Something else already put us back on hook */
+ break;
+ }
+ if (sub) {
+ sub->cxmode = SKINNY_CX_RECVONLY;
+ l->hookstate = SKINNY_ONHOOK;
+ transmit_callstate(s, l->instance, l->hookstate, sub->callid);
+ if (skinnydebug)
+ ast_verbose("Skinny %s@%s went on hook\n", l->name, d->name);
+ if (l->transfer && (sub->owner && sub->next && sub->next->owner) && ((!sub->outgoing) || (sub->next && !sub->next->outgoing))) {
+ /* We're allowed to transfer, we have two active calls and
+ we made at least one of the calls. Let's try and transfer */
+
+#if 0
+ if ((res = attempt_transfer(p)) < 0) {
+ if (sub->next && sub->next->owner) {
+ sub->next->alreadygone = 1;
+ ast_queue_hangup(sub->next->owner, 1);
+ }
+ } else if (res) {
+ ast_log(LOG_WARNING, "Transfer attempt failed\n");
+ break;
+ }
+#endif
+ } else {
+ /* Hangup the current call */
+ /* If there is another active call, skinny_hangup will ring the phone with the other call */
+ if (sub->owner) {
+ sub->alreadygone = 1;
+ ast_queue_hangup(sub->owner);
+ } else {
+ ast_log(LOG_WARNING, "Skinny(%s@%s-%d) channel already destroyed\n",
+ l->name, d->name, sub->callid);
+ }
+ }
+ if ((l->hookstate == SKINNY_ONHOOK) && (sub->next && !sub->next->rtp)) {
+ do_housekeeping(s);
+ }
+ }
+ break;
+ case SOFTKEY_RESUME:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Resume(%d/%d)\n", instance, callreference);
+ break;
+ case SOFTKEY_ANSWER:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Answer(%d/%d)\n", instance, callreference);
+
+ transmit_ringer_mode(s,SKINNY_RING_OFF);
+ transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON);
+
+ l->hookstate = SKINNY_OFFHOOK;
+
+ if (sub && sub->outgoing) {
+ /* We're answering a ringing call */
+ ast_queue_control(sub->owner, AST_CONTROL_ANSWER);
+ transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid);
+ transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid);
+ transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid);
+ transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED);
+ start_rtp(sub);
+ ast_setstate(sub->owner, AST_STATE_UP);
+ }
+ break;
+ case SOFTKEY_INFO:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Info(%d/%d)\n", instance, callreference);
+ break;
+ case SOFTKEY_CONFRN:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Conference(%d/%d)\n", instance, callreference);
+ /* XXX determine the best way to pull off a conference. Meetme? */
+ break;
+ case SOFTKEY_PARK:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Park Call(%d/%d)\n", instance, callreference);
+ /* XXX Park the call */
+ break;
+ case SOFTKEY_JOIN:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Join(%d/%d)\n", instance, callreference);
+ break;
+ case SOFTKEY_MEETME:
+ /* XXX How is this different from CONFRN? */
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Meetme(%d/%d)\n", instance, callreference);
+ break;
+ case SOFTKEY_PICKUP:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Pickup(%d/%d)\n", instance, callreference);
+ break;
+ case SOFTKEY_GPICKUP:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Group Pickup(%d/%d)\n", instance, callreference);
+ break;
+ default:
+ if (skinnydebug)
+ ast_verbose("Received unknown Softkey Event: %d(%d/%d)\n", event, instance, callreference);
+ break;
+ }
+ ast_device_state_changed("Skinny/%s@%s", l->name, d->name);
+
+ return 1;
+}
+
+static int handle_unregister_message(struct skinny_req *req, struct skinnysession *s)
+{
+ return skinny_unregister(req, s);
+}
+
+static int handle_soft_key_template_req_message(struct skinny_req *req, struct skinnysession *s)
+{
+ if (!(req = req_alloc(sizeof(struct soft_key_template_res_message), SOFT_KEY_TEMPLATE_RES_MESSAGE)))
+ return -1;
+
+ req->data.softkeytemplate.softKeyOffset = htolel(0);
+ req->data.softkeytemplate.softKeyCount = htolel(sizeof(soft_key_template_default) / sizeof(struct soft_key_template_definition));
+ req->data.softkeytemplate.totalSoftKeyCount = htolel(sizeof(soft_key_template_default) / sizeof(struct soft_key_template_definition));
+ memcpy(req->data.softkeytemplate.softKeyTemplateDefinition,
+ soft_key_template_default,
+ sizeof(soft_key_template_default));
+ transmit_response(s,req);
+ return 1;
+}
+
+static int handle_headset_status_message(struct skinny_req *req, struct skinnysession *s)
+{
+ /* XXX umm...okay? Why do I care? */
+ return 1;
+}
+
+static int handle_register_available_lines_message(struct skinny_req *req, struct skinnysession *s)
+{
+ /* XXX I have no clue what this is for, but my phone was sending it, so... */
+ return 1;
+}
+
+static int handle_message(struct skinny_req *req, struct skinnysession *s)
+{
+ int res = 0;
+
+ if ((!s->device) && (letohl(req->e) != REGISTER_MESSAGE && letohl(req->e) != ALARM_MESSAGE)) {
+ ast_log(LOG_WARNING, "Client sent message #%d without first registering.\n", req->e);
+ ast_free(req);
+ return 0;
+ }
+
+ switch(letohl(req->e)) {
+ case KEEP_ALIVE_MESSAGE:
+ res = handle_keep_alive_message(req, s);
+ break;
+ case REGISTER_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Device %s is attempting to register\n", req->data.reg.name);
+
+ res = handle_register_message(req, s);
+ break;
+ case IP_PORT_MESSAGE:
+ res = handle_ip_port_message(req, s);
+ break;
+ case KEYPAD_BUTTON_MESSAGE:
+ {
+ struct skinny_device *d = s->device;
+ struct skinny_subchannel *sub;
+ int lineInstance;
+ int callReference;
+
+ if (skinnydebug)
+ ast_verbose("Collected digit: [%d]\n", letohl(req->data.keypad.button));
+
+ lineInstance = letohl(req->data.keypad.lineInstance);
+ callReference = letohl(req->data.keypad.callReference);
+
+ sub = find_subchannel_by_instance_reference(d, lineInstance, callReference);
+
+ if (sub && (sub->owner && sub->owner->_state < AST_STATE_UP)) {
+ char dgt;
+ int digit = letohl(req->data.keypad.button);
+
+ if (digit == 14) {
+ dgt = '*';
+ } else if (digit == 15) {
+ dgt = '#';
+ } else if (digit >= 0 && digit <= 9) {
+ dgt = '0' + digit;
+ } else {
+ /* digit=10-13 (A,B,C,D ?), or
+ * digit is bad value
+ *
+ * probably should not end up here, but set
+ * value for backward compatibility, and log
+ * a warning.
+ */
+ dgt = '0' + digit;
+ ast_log(LOG_WARNING, "Unsupported digit %d\n", digit);
+ }
+
+ d->exten[strlen(d->exten)] = dgt;
+ d->exten[strlen(d->exten)+1] = '\0';
+ } else
+ res = handle_keypad_button_message(req, s);
+ }
+ break;
+ case ENBLOC_CALL_MESSAGE:
+ res = handle_enbloc_call_message(req, s);
+ break;
+ case STIMULUS_MESSAGE:
+ res = handle_stimulus_message(req, s);
+ break;
+ case OFFHOOK_MESSAGE:
+ res = handle_offhook_message(req, s);
+ break;
+ case ONHOOK_MESSAGE:
+ res = handle_onhook_message(req, s);
+ break;
+ case CAPABILITIES_RES_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Received CapabilitiesRes\n");
+
+ res = handle_capabilities_res_message(req, s);
+ break;
+ case SPEED_DIAL_STAT_REQ_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Received SpeedDialStatRequest\n");
+
+ res = handle_speed_dial_stat_req_message(req, s);
+ break;
+ case LINE_STATE_REQ_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Received LineStatRequest\n");
+ res = handle_line_state_req_message(req, s);
+ break;
+ case TIME_DATE_REQ_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Received Time/Date Request\n");
+
+ res = handle_time_date_req_message(req, s);
+ break;
+ case BUTTON_TEMPLATE_REQ_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Buttontemplate requested\n");
+
+ res = handle_button_template_req_message(req, s);
+ break;
+ case VERSION_REQ_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Version Request\n");
+
+ res = handle_version_req_message(req, s);
+ break;
+ case SERVER_REQUEST_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Received Server Request\n");
+
+ res = handle_server_request_message(req, s);
+ break;
+ case ALARM_MESSAGE:
+ res = handle_alarm_message(req, s);
+ break;
+ case OPEN_RECEIVE_CHANNEL_ACK_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Received Open Receive Channel Ack\n");
+
+ res = handle_open_receive_channel_ack_message(req, s);
+ break;
+ case SOFT_KEY_SET_REQ_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Received SoftKeySetReq\n");
+
+ res = handle_soft_key_set_req_message(req, s);
+ break;
+ case SOFT_KEY_EVENT_MESSAGE:
+ res = handle_soft_key_event_message(req, s);
+ break;
+ case UNREGISTER_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Received Unregister Request\n");
+
+ res = handle_unregister_message(req, s);
+ break;
+ case SOFT_KEY_TEMPLATE_REQ_MESSAGE:
+ if (skinnydebug)
+ ast_verbose("Received SoftKey Template Request\n");
+
+ res = handle_soft_key_template_req_message(req, s);
+ break;
+ case HEADSET_STATUS_MESSAGE:
+ res = handle_headset_status_message(req, s);
+ break;
+ case REGISTER_AVAILABLE_LINES_MESSAGE:
+ res = handle_register_available_lines_message(req, s);
+ break;
+ default:
+ if (skinnydebug)
+ ast_verbose("RECEIVED UNKNOWN MESSAGE TYPE: %x\n", letohl(req->e));
+ break;
+ }
+ if (res >= 0 && req)
+ ast_free(req);
+ return res;
+}
+
+static void destroy_session(struct skinnysession *s)
+{
+ struct skinnysession *cur, *prev = NULL;
+ ast_mutex_lock(&sessionlock);
+ cur = sessions;
+ while(cur) {
+ if (cur == s) {
+ break;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+ if (cur) {
+ if (prev) {
+ prev->next = cur->next;
+ } else {
+ sessions = cur->next;
+ }
+ if (s->fd > -1) {
+ close(s->fd);
+ }
+ ast_mutex_destroy(&s->lock);
+ ast_free(s);
+ } else {
+ ast_log(LOG_WARNING, "Trying to delete nonexistent session %p?\n", s);
+ }
+ ast_mutex_unlock(&sessionlock);
+}
+
+static int get_input(struct skinnysession *s)
+{
+ int res;
+ int dlen = 0;
+ struct pollfd fds[1];
+
+ fds[0].fd = s->fd;
+ fds[0].events = POLLIN;
+ fds[0].revents = 0;
+ res = poll(fds, 1, (keep_alive * 1100)); /* If nothing has happen, client is dead */
+ /* we add 10% to the keep_alive to deal */
+ /* with network delays, etc */
+ if (res < 0) {
+ if (errno != EINTR) {
+ ast_log(LOG_WARNING, "Select returned error: %s\n", strerror(errno));
+ return res;
+ }
+ } else if (res == 0) {
+ if (skinnydebug)
+ ast_verbose("Skinny Client was lost, unregistering\n");
+ skinny_unregister(NULL, s);
+ return -1;
+ }
+
+ if (fds[0].revents) {
+ ast_mutex_lock(&s->lock);
+ memset(s->inbuf,0,sizeof(s->inbuf));
+ res = read(s->fd, s->inbuf, 4);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "read() returned error: %s\n", strerror(errno));
+
+ if (skinnydebug)
+ ast_verbose("Skinny Client was lost, unregistering\n");
+
+ skinny_unregister(NULL,s);
+ ast_mutex_unlock(&s->lock);
+ return res;
+ } else if (res != 4) {
+ ast_log(LOG_WARNING, "Skinny Client sent less data than expected. Expected 4 but got %d.\n", res);
+ ast_mutex_unlock(&s->lock);
+
+ if (res == 0) {
+ if (skinnydebug)
+ ast_verbose("Skinny Client was lost, unregistering\n");
+ skinny_unregister(NULL, s);
+ }
+
+ return -1;
+ }
+
+ dlen = letohl(*(int *)s->inbuf);
+ if (dlen < 4) {
+ ast_log(LOG_WARNING, "Skinny Client sent invalid data.\n");
+ ast_mutex_unlock(&s->lock);
+ return -1;
+ }
+ if (dlen+8 > sizeof(s->inbuf)) {
+ dlen = sizeof(s->inbuf) - 8;
+ }
+ *(int *)s->inbuf = htolel(dlen);
+
+ res = read(s->fd, s->inbuf+4, dlen+4);
+ ast_mutex_unlock(&s->lock);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "read() returned error: %s\n", strerror(errno));
+ return res;
+ } else if (res != (dlen+4)) {
+ ast_log(LOG_WARNING, "Skinny Client sent less data than expected.\n");
+ return -1;
+ }
+ return res;
+ }
+ return 0;
+}
+
+static struct skinny_req *skinny_req_parse(struct skinnysession *s)
+{
+ struct skinny_req *req;
+
+ if (!(req = ast_calloc(1, SKINNY_MAX_PACKET)))
+ return NULL;
+
+ ast_mutex_lock(&s->lock);
+ memcpy(req, s->inbuf, skinny_header_size);
+ memcpy(&req->data, s->inbuf+skinny_header_size, letohl(*(int*)(s->inbuf))-4);
+
+ ast_mutex_unlock(&s->lock);
+
+ if (letohl(req->e) < 0) {
+ ast_log(LOG_ERROR, "Event Message is NULL from socket %d, This is bad\n", s->fd);
+ ast_free(req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static void *skinny_session(void *data)
+{
+ int res;
+ struct skinny_req *req;
+ struct skinnysession *s = data;
+
+ ast_verb(3, "Starting Skinny session from %s\n", ast_inet_ntoa(s->sin.sin_addr));
+
+ for (;;) {
+ res = get_input(s);
+ if (res < 0) {
+ break;
+ }
+
+ if (res > 0)
+ {
+ if (!(req = skinny_req_parse(s))) {
+ destroy_session(s);
+ return NULL;
+ }
+
+ res = handle_message(req, s);
+ if (res < 0) {
+ destroy_session(s);
+ return NULL;
+ }
+ }
+ }
+ ast_log(LOG_NOTICE, "Skinny Session returned: %s\n", strerror(errno));
+
+ if (s)
+ destroy_session(s);
+
+ return 0;
+}
+
+static void *accept_thread(void *ignore)
+{
+ int as;
+ struct sockaddr_in sin;
+ socklen_t sinlen;
+ struct skinnysession *s;
+ struct protoent *p;
+ int arg = 1;
+ pthread_t tcp_thread;
+
+ for (;;) {
+ sinlen = sizeof(sin);
+ as = accept(skinnysock, (struct sockaddr *)&sin, &sinlen);
+ if (as < 0) {
+ ast_log(LOG_NOTICE, "Accept returned -1: %s\n", strerror(errno));
+ continue;
+ }
+ p = getprotobyname("tcp");
+ if(p) {
+ if( setsockopt(as, p->p_proto, TCP_NODELAY, (char *)&arg, sizeof(arg) ) < 0 ) {
+ ast_log(LOG_WARNING, "Failed to set Skinny tcp connection to TCP_NODELAY mode: %s\n", strerror(errno));
+ }
+ }
+ if (!(s = ast_calloc(1, sizeof(struct skinnysession))))
+ continue;
+
+ memcpy(&s->sin, &sin, sizeof(sin));
+ ast_mutex_init(&s->lock);
+ s->fd = as;
+ ast_mutex_lock(&sessionlock);
+ s->next = sessions;
+ sessions = s;
+ ast_mutex_unlock(&sessionlock);
+
+ if (ast_pthread_create_detached(&tcp_thread, NULL, skinny_session, s)) {
+ destroy_session(s);
+ }
+ }
+ if (skinnydebug)
+ ast_verbose("killing accept thread\n");
+ close(as);
+ return 0;
+}
+
+static void *do_monitor(void *data)
+{
+ int res;
+
+ /* This thread monitors all the interfaces which are not yet in use
+ (and thus do not have a separate thread) indefinitely */
+ /* From here on out, we die whenever asked */
+ for(;;) {
+ pthread_testcancel();
+ /* Wait for sched or io */
+ res = ast_sched_wait(sched);
+ if ((res < 0) || (res > 1000)) {
+ res = 1000;
+ }
+ res = ast_io_wait(io, res);
+ ast_mutex_lock(&monlock);
+ if (res >= 0) {
+ ast_sched_runq(sched);
+ }
+ ast_mutex_unlock(&monlock);
+ }
+ /* Never reached */
+ return NULL;
+
+}
+
+static int restart_monitor(void)
+{
+ /* If we're supposed to be stopped -- stay stopped */
+ if (monitor_thread == AST_PTHREADT_STOP)
+ return 0;
+
+ ast_mutex_lock(&monlock);
+ if (monitor_thread == pthread_self()) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_WARNING, "Cannot kill myself\n");
+ return -1;
+ }
+ if (monitor_thread != AST_PTHREADT_NULL) {
+ /* Wake up the thread */
+ pthread_kill(monitor_thread, SIGURG);
+ } else {
+ /* Start a new monitor */
+ if (ast_pthread_create_background(&monitor_thread, NULL, do_monitor, NULL) < 0) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
+ return -1;
+ }
+ }
+ ast_mutex_unlock(&monlock);
+ return 0;
+}
+
+static int skinny_devicestate(void *data)
+{
+ struct skinny_line *l;
+ char *tmp;
+
+ tmp = ast_strdupa(data);
+
+ l = find_line_by_name(tmp);
+
+ return get_devicestate(l);
+}
+
+static struct ast_channel *skinny_request(const char *type, int format, void *data, int *cause)
+{
+ int oldformat;
+
+ struct skinny_line *l;
+ struct ast_channel *tmpc = NULL;
+ char tmp[256];
+ char *dest = data;
+
+ oldformat = format;
+
+ if (!(format &= AST_FORMAT_AUDIO_MASK)) {
+ ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", format);
+ return NULL;
+ }
+
+ ast_copy_string(tmp, dest, sizeof(tmp));
+ if (ast_strlen_zero(tmp)) {
+ ast_log(LOG_NOTICE, "Skinny channels require a device\n");
+ return NULL;
+ }
+ l = find_line_by_name(tmp);
+ if (!l) {
+ ast_log(LOG_NOTICE, "No available lines on: %s\n", dest);
+ return NULL;
+ }
+ ast_verb(3, "skinny_request(%s)\n", tmp);
+ tmpc = skinny_new(l, AST_STATE_DOWN);
+ if (!tmpc) {
+ ast_log(LOG_WARNING, "Unable to make channel for '%s'\n", tmp);
+ }
+ restart_monitor();
+ return tmpc;
+}
+
+static int reload_config(void)
+{
+ int on = 1;
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ char *cat;
+ struct skinny_device *d;
+ int oldport = ntohs(bindaddr.sin_port);
+ char *stringp, *context, *oldregcontext;
+ char newcontexts[AST_MAX_CONTEXT], oldcontexts[AST_MAX_CONTEXT];
+ struct ast_flags config_flags = { 0 };
+
+ if (gethostname(ourhost, sizeof(ourhost))) {
+ ast_log(LOG_WARNING, "Unable to get hostname, Skinny disabled\n");
+ return 0;
+ }
+ cfg = ast_config_load(config, config_flags);
+
+ /* We *must* have a config file otherwise stop immediately */
+ if (!cfg) {
+ ast_log(LOG_NOTICE, "Unable to load config %s, Skinny disabled\n", config);
+ return -1;
+ }
+ memset(&bindaddr, 0, sizeof(bindaddr));
+ memset(&default_prefs, 0, sizeof(default_prefs));
+
+ /* Initialize copy of current global_regcontext for later use in removing stale contexts */
+ ast_copy_string(oldcontexts, regcontext, sizeof(oldcontexts));
+ oldregcontext = oldcontexts;
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ /* load the general section */
+ v = ast_variable_browse(cfg, "general");
+ while (v) {
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value)) {
+ v = v->next;
+ continue;
+ }
+
+ /* Create the interface list */
+ if (!strcasecmp(v->name, "bindaddr")) {
+ if (!(hp = ast_gethostbyname(v->value, &ahp))) {
+ ast_log(LOG_WARNING, "Invalid address: %s\n", v->value);
+ } else {
+ memcpy(&bindaddr.sin_addr, hp->h_addr, sizeof(bindaddr.sin_addr));
+ }
+ } else if (!strcasecmp(v->name, "keepalive")) {
+ keep_alive = atoi(v->value);
+ } else if (!strcasecmp(v->name, "vmexten")) {
+ ast_copy_string(vmexten, v->value, sizeof(vmexten));
+ } else if (!strcasecmp(v->name, "regcontext")) {
+ ast_copy_string(newcontexts, v->value, sizeof(newcontexts));
+ stringp = newcontexts;
+ /* Let's remove any contexts that are no longer defined in regcontext */
+ cleanup_stale_contexts(stringp, oldregcontext);
+ /* Create contexts if they don't exist already */
+ while ((context = strsep(&stringp, "&"))) {
+ ast_copy_string(used_context, context, sizeof(used_context));
+ if (!ast_context_find(context))
+ ast_context_create(NULL, context, "Skinny");
+ }
+ ast_copy_string(regcontext, v->value, sizeof(regcontext));
+ } else if (!strcasecmp(v->name, "dateformat")) {
+ memcpy(date_format, v->value, sizeof(date_format));
+ } else if (!strcasecmp(v->name, "tos")) {
+ if (ast_str2tos(v->value, &tos))
+ ast_log(LOG_WARNING, "Invalid tos value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "tos_audio")) {
+ if (ast_str2tos(v->value, &tos_audio))
+ ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "tos_video")) {
+ if (ast_str2tos(v->value, &tos_video))
+ ast_log(LOG_WARNING, "Invalid tos_video value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos")) {
+ if (ast_str2cos(v->value, &cos))
+ ast_log(LOG_WARNING, "Invalid cos value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos_audio")) {
+ if (ast_str2cos(v->value, &cos_audio))
+ ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos_video")) {
+ if (ast_str2cos(v->value, &cos_video))
+ ast_log(LOG_WARNING, "Invalid cos_video value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "allow")) {
+ ast_parse_allow_disallow(&default_prefs, &default_capability, v->value, 1);
+ } else if (!strcasecmp(v->name, "disallow")) {
+ ast_parse_allow_disallow(&default_prefs, &default_capability, v->value, 0);
+ } else if (!strcasecmp(v->name, "bindport")) {
+ if (sscanf(v->value, "%d", &ourport) == 1) {
+ bindaddr.sin_port = htons(ourport);
+ } else {
+ ast_log(LOG_WARNING, "Invalid bindport '%s' at line %d of %s\n", v->value, v->lineno, config);
+ }
+ }
+ v = v->next;
+ }
+
+ if (ntohl(bindaddr.sin_addr.s_addr)) {
+ __ourip = bindaddr.sin_addr;
+ } else {
+ hp = ast_gethostbyname(ourhost, &ahp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "Unable to get our IP address, Skinny disabled\n");
+ ast_config_destroy(cfg);
+ return 0;
+ }
+ memcpy(&__ourip, hp->h_addr, sizeof(__ourip));
+ }
+ if (!ntohs(bindaddr.sin_port)) {
+ bindaddr.sin_port = ntohs(DEFAULT_SKINNY_PORT);
+ }
+ bindaddr.sin_family = AF_INET;
+
+ /* load the device sections */
+ cat = ast_category_browse(cfg, NULL);
+ while(cat) {
+ if (!strcasecmp(cat, "general")) {
+ /* Nothing to do */
+#if 0
+ } else if (!strncasecmp(cat, "paging-", 7)) {
+ p = build_paging_device(cat, ast_variable_browse(cfg, cat));
+ if (p) {
+ }
+#endif
+ } else {
+ d = build_device(cat, ast_variable_browse(cfg, cat));
+ if (d) {
+ ast_verb(3, "Added device '%s'\n", d->name);
+ ast_mutex_lock(&devicelock);
+ d->next = devices;
+ devices = d;
+ ast_mutex_unlock(&devicelock);
+ }
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+ ast_mutex_lock(&netlock);
+ if ((skinnysock > -1) && (ntohs(bindaddr.sin_port) != oldport)) {
+ close(skinnysock);
+ skinnysock = -1;
+ }
+ if (skinnysock < 0) {
+ skinnysock = socket(AF_INET, SOCK_STREAM, 0);
+ if(setsockopt(skinnysock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+ ast_log(LOG_ERROR, "Set Socket Options failed: errno %d, %s\n", errno, strerror(errno));
+ ast_config_destroy(cfg);
+ return 0;
+ }
+ if (skinnysock < 0) {
+ ast_log(LOG_WARNING, "Unable to create Skinny socket: %s\n", strerror(errno));
+ } else {
+ if (bind(skinnysock, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) {
+ ast_log(LOG_WARNING, "Failed to bind to %s:%d: %s\n",
+ ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port),
+ strerror(errno));
+ close(skinnysock);
+ skinnysock = -1;
+ ast_config_destroy(cfg);
+ return 0;
+ }
+ if (listen(skinnysock,DEFAULT_SKINNY_BACKLOG)) {
+ ast_log(LOG_WARNING, "Failed to start listening to %s:%d: %s\n",
+ ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port),
+ strerror(errno));
+ close(skinnysock);
+ skinnysock = -1;
+ ast_config_destroy(cfg);
+ return 0;
+ }
+ ast_verb(2, "Skinny listening on %s:%d\n",
+ ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port));
+ ast_netsock_set_qos(skinnysock, tos, cos, "Skinny");
+ ast_pthread_create_background(&accept_t,NULL, accept_thread, NULL);
+ }
+ }
+ ast_mutex_unlock(&netlock);
+ ast_config_destroy(cfg);
+ return 1;
+}
+
+static void delete_devices(void)
+{
+ struct skinny_device *d, *dlast;
+ struct skinny_line *l, *llast;
+ struct skinny_speeddial *sd, *sdlast;
+ struct skinny_addon *a, *alast;
+
+ ast_mutex_lock(&devicelock);
+
+ /* Delete all devices */
+ for (d=devices;d;) {
+ /* Delete all lines for this device */
+ for (l=d->lines;l;) {
+ llast = l;
+ l = l->next;
+ ast_mutex_destroy(&llast->lock);
+ ast_free(llast);
+ }
+ /* Delete all speeddials for this device */
+ for (sd=d->speeddials;sd;) {
+ sdlast = sd;
+ sd = sd->next;
+ ast_mutex_destroy(&sdlast->lock);
+ ast_free(sdlast);
+ }
+ /* Delete all addons for this device */
+ for (a=d->addons;a;) {
+ alast = a;
+ a = a->next;
+ ast_mutex_destroy(&alast->lock);
+ ast_free(alast);
+ }
+ dlast = d;
+ d = d->next;
+ ast_free(dlast);
+ }
+ devices=NULL;
+ ast_mutex_unlock(&devicelock);
+}
+
+#if 0
+/*
+ * XXX This never worked properly anyways.
+ * Let's get rid of it, until we can fix it.
+ */
+static int reload(void)
+{
+ delete_devices();
+ reload_config();
+ restart_monitor();
+ return 0;
+}
+#endif
+
+static int load_module(void)
+{
+ int res = 0;
+
+ for (; res < (sizeof(soft_key_template_default) / sizeof(soft_key_template_default[0])); res++) {
+ soft_key_template_default[res].softKeyEvent = htolel(soft_key_template_default[res].softKeyEvent);
+ }
+ /* load and parse config */
+ res = reload_config();
+ if (res == -1) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ /* Make sure we can register our skinny channel type */
+ if (ast_channel_register(&skinny_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'Skinny'\n");
+ return -1;
+ }
+
+ ast_rtp_proto_register(&skinny_rtp);
+ ast_cli_register_multiple(cli_skinny, sizeof(cli_skinny) / sizeof(struct ast_cli_entry));
+ sched = sched_context_create();
+ if (!sched) {
+ ast_log(LOG_WARNING, "Unable to create schedule context\n");
+ }
+ io = io_context_create();
+ if (!io) {
+ ast_log(LOG_WARNING, "Unable to create I/O context\n");
+ }
+ /* And start the monitor for the first time */
+ restart_monitor();
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ struct skinnysession *s, *slast;
+ struct skinny_device *d;
+ struct skinny_line *l;
+ struct skinny_subchannel *sub;
+ struct ast_context *con;
+
+ ast_mutex_lock(&sessionlock);
+ /* Destroy all the interfaces and free their memory */
+ s = sessions;
+ while(s) {
+ slast = s;
+ s = s->next;
+ for (d = slast->device; d; d = d->next) {
+ for (l = d->lines; l; l = l->next) {
+ ast_mutex_lock(&l->lock);
+ for (sub = l->sub; sub; sub = sub->next) {
+ ast_mutex_lock(&sub->lock);
+ if (sub->owner) {
+ sub->alreadygone = 1;
+ ast_softhangup(sub->owner, AST_SOFTHANGUP_APPUNLOAD);
+ }
+ ast_mutex_unlock(&sub->lock);
+ }
+ ast_mutex_unlock(&l->lock);
+ }
+ }
+ if (slast->fd > -1)
+ close(slast->fd);
+ ast_mutex_destroy(&slast->lock);
+ ast_free(slast);
+ }
+ sessions = NULL;
+ ast_mutex_unlock(&sessionlock);
+
+ delete_devices();
+
+ ast_mutex_lock(&monlock);
+ if ((monitor_thread != AST_PTHREADT_NULL) && (monitor_thread != AST_PTHREADT_STOP)) {
+ pthread_cancel(monitor_thread);
+ pthread_kill(monitor_thread, SIGURG);
+ pthread_join(monitor_thread, NULL);
+ }
+ monitor_thread = AST_PTHREADT_STOP;
+ ast_mutex_unlock(&monlock);
+
+ ast_mutex_lock(&netlock);
+ if (accept_t && (accept_t != AST_PTHREADT_STOP)) {
+ pthread_cancel(accept_t);
+ pthread_kill(accept_t, SIGURG);
+ pthread_join(accept_t, NULL);
+ }
+ accept_t = AST_PTHREADT_STOP;
+ ast_mutex_unlock(&netlock);
+
+ ast_rtp_proto_unregister(&skinny_rtp);
+ ast_channel_unregister(&skinny_tech);
+ ast_cli_unregister_multiple(cli_skinny, sizeof(cli_skinny) / sizeof(struct ast_cli_entry));
+
+ close(skinnysock);
+ if (sched)
+ sched_context_destroy(sched);
+
+ con = ast_context_find(used_context);
+ if (con)
+ ast_context_destroy(con, "Skinny");
+
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Skinny Client Control Protocol (Skinny)",
+ .load = load_module,
+ .unload = unload_module,
+ );
diff --git a/trunk/channels/chan_unistim.c b/trunk/channels/chan_unistim.c
new file mode 100644
index 000000000..3021f7c55
--- /dev/null
+++ b/trunk/channels/chan_unistim.c
@@ -0,0 +1,5668 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * UNISTIM channel driver for asterisk
+ *
+ * Copyright (C) 2005 - 2007, Cedric Hans
+ *
+ * Cedric Hans <cedric.hans@mlkj.net>
+ *
+ * Asterisk 1.4 patch by Peter Be
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \brief chan_unistim channel driver for Asterisk
+ * \author Cedric Hans <cedric.hans@mlkj.net>
+ *
+ * Unistim (Unified Networks IP Stimulus) channel driver
+ * for Nortel i2002, i2004 and i2050
+ *
+ * \ingroup channel_drivers
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/stat.h>
+#include <signal.h>
+
+#if defined(__CYGWIN__)
+/*
+ * cygwin headers are partly inconsistent. struct iovec is defined in sys/uio.h
+ * which is not included by default by sys/socket.h - in_pktinfo is defined in
+ * w32api/ws2tcpip.h but this probably has compatibility problems with sys/socket.h
+ * So for the time being we simply disable HAVE_PKTINFO when building under cygwin.
+ * This should be done in some common header, but for now this is the only file
+ * using iovec and in_pktinfo so it suffices to apply the fix here.
+ */
+#ifdef HAVE_PKTINFO
+#undef HAVE_PKTINFO
+#endif
+#endif /* __CYGWIN__ */
+
+#include "asterisk/paths.h" /* ast_config_AST_LOG_DIR used in (too ?) many places */
+#include "asterisk/network.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/event.h"
+#include "asterisk/rtp.h"
+#include "asterisk/netsock.h"
+#include "asterisk/acl.h"
+#include "asterisk/callerid.h"
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/causes.h"
+#include "asterisk/indications.h"
+
+/*! Beware, G729 and G723 are not supported by asterisk, except with the proper licence */
+#define CAPABILITY AST_FORMAT_ALAW | AST_FORMAT_ULAW /* | AST_FORMAT_G729A | AST_FORMAT_G723_1 */
+
+#define DEFAULTCONTEXT "default"
+#define DEFAULTCALLERID "Unknown"
+#define DEFAULTCALLERNAME " "
+#define USTM_LOG_DIR "unistimHistory"
+
+/*! Size of the transmit buffer */
+#define MAX_BUF_SIZE 64
+/*! Number of slots for the transmit queue */
+#define MAX_BUF_NUMBER 50
+/*! Try x times before removing the phone */
+#define NB_MAX_RETRANSMIT 8
+/*! Nb of milliseconds waited when no events are scheduled */
+#define IDLE_WAIT 1000
+/*! Wait x milliseconds before resending a packet */
+#define RETRANSMIT_TIMER 2000
+/*! How often the mailbox is checked for new messages */
+#define TIMER_MWI 10000
+/*! Not used */
+#define DEFAULT_CODEC 0x00
+#define SIZE_PAGE 4096
+#define DEVICE_NAME_LEN 16
+#define AST_CONFIG_MAX_PATH 255
+#define MAX_ENTRY_LOG 30
+
+#define SUB_REAL 0
+#define SUB_THREEWAY 1
+#define MAX_SUBS 2
+
+enum autoprovision {
+ AUTOPROVISIONING_NO = 0,
+ AUTOPROVISIONING_YES,
+ AUTOPROVISIONING_DB,
+ AUTOPROVISIONING_TN
+};
+
+enum autoprov_extn {
+ /*! Do not create an extension into the default dialplan */
+ EXTENSION_NONE = 0,
+ /*! Prompt user for an extension number and register it */
+ EXTENSION_ASK,
+ /*! Register an extension with the line=> value */
+ EXTENSION_LINE,
+ /*! Used with AUTOPROVISIONING_TN */
+ EXTENSION_TN
+};
+#define OUTPUT_HANDSET 0xC0
+#define OUTPUT_HEADPHONE 0xC1
+#define OUTPUT_SPEAKER 0xC2
+
+#define VOLUME_LOW 0x01
+#define VOLUME_LOW_SPEAKER 0x03
+#define VOLUME_NORMAL 0x02
+#define VOLUME_INSANELY_LOUD 0x07
+
+#define MUTE_OFF 0x00
+#define MUTE_ON 0xFF
+#define MUTE_ON_DISCRET 0xCE
+
+#define SIZE_HEADER 6
+#define SIZE_MAC_ADDR 17
+#define TEXT_LENGTH_MAX 24
+#define TEXT_LINE0 0x00
+#define TEXT_LINE1 0x20
+#define TEXT_LINE2 0x40
+#define TEXT_NORMAL 0x05
+#define TEXT_INVERSE 0x25
+#define STATUS_LENGTH_MAX 28
+
+#define FAV_ICON_NONE 0x00
+#define FAV_ICON_ONHOOK_BLACK 0x20
+#define FAV_ICON_ONHOOK_WHITE 0x21
+#define FAV_ICON_SPEAKER_ONHOOK_BLACK 0x22
+#define FAV_ICON_SPEAKER_ONHOOK_WHITE 0x23
+#define FAV_ICON_OFFHOOK_BLACK 0x24
+#define FAV_ICON_OFFHOOK_WHITE 0x25
+#define FAV_ICON_ONHOLD_BLACK 0x26
+#define FAV_ICON_ONHOLD_WHITE 0x27
+#define FAV_ICON_SPEAKER_OFFHOOK_BLACK 0x28
+#define FAV_ICON_SPEAKER_OFFHOOK_WHITE 0x29
+#define FAV_ICON_PHONE_BLACK 0x2A
+#define FAV_ICON_PHONE_WHITE 0x2B
+#define FAV_ICON_SPEAKER_ONHOLD_BLACK 0x2C
+#define FAV_ICON_SPEAKER_ONHOLD_WHITE 0x2D
+#define FAV_ICON_HEADPHONES 0x2E
+#define FAV_ICON_HEADPHONES_ONHOLD 0x2F
+#define FAV_ICON_HOME 0x30
+#define FAV_ICON_CITY 0x31
+#define FAV_ICON_SHARP 0x32
+#define FAV_ICON_PAGER 0x33
+#define FAV_ICON_CALL_CENTER 0x34
+#define FAV_ICON_FAX 0x35
+#define FAV_ICON_MAILBOX 0x36
+#define FAV_ICON_REFLECT 0x37
+#define FAV_ICON_COMPUTER 0x38
+#define FAV_ICON_FORWARD 0x39
+#define FAV_ICON_LOCKED 0x3A
+#define FAV_ICON_TRASH 0x3B
+#define FAV_ICON_INBOX 0x3C
+#define FAV_ICON_OUTBOX 0x3D
+#define FAV_ICON_MEETING 0x3E
+#define FAV_ICON_BOX 0x3F
+
+#define FAV_BLINK_FAST 0x20
+#define FAV_BLINK_SLOW 0x40
+
+#define FAV_MAX_LENGTH 0x0A
+
+static void dummy(char *dummy, ...)
+{
+ return;
+}
+
+/*! \brief Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = ""
+};
+static struct ast_jb_conf global_jbconf;
+
+
+/* #define DUMP_PACKET 1 */
+/* #define DEBUG_TIMER ast_verbose */
+
+#define DEBUG_TIMER dummy
+/*! Enable verbose output. can also be set with the CLI */
+static int unistimdebug = 0;
+static int unistim_port;
+static enum autoprovision autoprovisioning = AUTOPROVISIONING_NO;
+static int unistim_keepalive;
+static int unistimsock = -1;
+static unsigned int tos = 0;
+static unsigned int tos_audio = 0;
+static unsigned int cos = 0;
+static unsigned int cos_audio = 0;
+static struct io_context *io;
+static struct sched_context *sched;
+static struct sockaddr_in public_ip = { 0, };
+/*! give the IP address for the last packet received */
+static struct sockaddr_in addr_from;
+/*! size of the sockaddr_in (in WSARecvFrom) */
+static unsigned int size_addr_from = sizeof(addr_from);
+/*! Receive buffer address */
+static unsigned char *buff;
+static int unistim_reloading = 0;
+AST_MUTEX_DEFINE_STATIC(unistim_reload_lock);
+AST_MUTEX_DEFINE_STATIC(usecnt_lock);
+static int usecnt = 0;
+/* extern char ast_config_AST_LOG_DIR[AST_CONFIG_MAX_PATH]; */
+
+/*! This is the thread for the monitor which checks for input on the channels
+ * which are not currently in use. */
+static pthread_t monitor_thread = AST_PTHREADT_NULL;
+
+/*! Protect the monitoring thread, so only one process can kill or start it, and not
+ * when it's doing something critical. */
+AST_MUTEX_DEFINE_STATIC(monlock);
+/*! Protect the session list */
+AST_MUTEX_DEFINE_STATIC(sessionlock);
+/*! Protect the device list */
+AST_MUTEX_DEFINE_STATIC(devicelock);
+
+enum phone_state {
+ STATE_INIT,
+ STATE_AUTHDENY,
+ STATE_MAINPAGE,
+ STATE_EXTENSION,
+ STATE_DIALPAGE,
+ STATE_RINGING,
+ STATE_CALL,
+ STATE_SELECTCODEC,
+ STATE_CLEANING,
+ STATE_HISTORY
+};
+
+enum handset_state {
+ STATE_ONHOOK,
+ STATE_OFFHOOK,
+};
+
+enum phone_key {
+ KEY_0 = 0x40,
+ KEY_1 = 0x41,
+ KEY_2 = 0x42,
+ KEY_3 = 0x43,
+ KEY_4 = 0x44,
+ KEY_5 = 0x45,
+ KEY_6 = 0x46,
+ KEY_7 = 0x47,
+ KEY_8 = 0x48,
+ KEY_9 = 0x49,
+ KEY_STAR = 0x4a,
+ KEY_SHARP = 0x4b,
+ KEY_UP = 0x4c,
+ KEY_DOWN = 0x4d,
+ KEY_RIGHT = 0x4e,
+ KEY_LEFT = 0x4f,
+ KEY_QUIT = 0x50,
+ KEY_COPY = 0x51,
+ KEY_FUNC1 = 0x54,
+ KEY_FUNC2 = 0x55,
+ KEY_FUNC3 = 0x56,
+ KEY_FUNC4 = 0x57,
+ KEY_ONHOLD = 0x5b,
+ KEY_HANGUP = 0x5c,
+ KEY_MUTE = 0x5d,
+ KEY_HEADPHN = 0x5e,
+ KEY_LOUDSPK = 0x5f,
+ KEY_FAV0 = 0x60,
+ KEY_FAV1 = 0x61,
+ KEY_FAV2 = 0x62,
+ KEY_FAV3 = 0x63,
+ KEY_FAV4 = 0x64,
+ KEY_FAV5 = 0x65,
+ KEY_COMPUTR = 0x7b,
+ KEY_CONF = 0x7c,
+ KEY_SNDHIST = 0x7d,
+ KEY_RCVHIST = 0x7e,
+ KEY_INDEX = 0x7f
+};
+
+struct tone_zone_unistim {
+ char country[3];
+ int freq1;
+ int freq2;
+};
+
+static const struct tone_zone_unistim frequency[] = {
+ {"us", 350, 440},
+ {"fr", 440, 0},
+ {"au", 413, 438},
+ {"nl", 425, 0},
+ {"uk", 350, 440},
+ {"fi", 425, 0},
+ {"es", 425, 0},
+ {"jp", 400, 0},
+ {"no", 425, 0},
+ {"at", 420, 0},
+ {"nz", 400, 0},
+ {"tw", 350, 440},
+ {"cl", 400, 0},
+ {"se", 425, 0},
+ {"be", 425, 0},
+ {"sg", 425, 0},
+ {"il", 414, 0},
+ {"br", 425, 0},
+ {"hu", 425, 0},
+ {"lt", 425, 0},
+ {"pl", 425, 0},
+ {"za", 400, 0},
+ {"pt", 425, 0},
+ {"ee", 425, 0},
+ {"mx", 425, 0},
+ {"in", 400, 0},
+ {"de", 425, 0},
+ {"ch", 425, 0},
+ {"dk", 425, 0},
+ {"cn", 450, 0},
+ {"--", 0, 0}
+};
+
+struct wsabuf {
+ u_long len;
+ unsigned char *buf;
+};
+
+struct systemtime {
+ unsigned short w_year;
+ unsigned short w_month;
+ unsigned short w_day_of_week;
+ unsigned short w_day;
+ unsigned short w_hour;
+ unsigned short w_minute;
+ unsigned short w_second;
+ unsigned short w_milliseconds;
+};
+
+struct unistim_subchannel {
+ ast_mutex_t lock;
+ /*! SUBS_REAL or SUBS_THREEWAY */
+ unsigned int subtype;
+ /*! Asterisk channel used by the subchannel */
+ struct ast_channel *owner;
+ /*! Unistim line */
+ struct unistim_line *parent;
+ /*! RTP handle */
+ struct ast_rtp *rtp;
+ int alreadygone;
+ char ringvolume;
+ char ringstyle;
+};
+
+/*!
+ * \todo Convert to stringfields
+ */
+struct unistim_line {
+ ast_mutex_t lock;
+ /*! Like 200 */
+ char name[80];
+ /*! Like USTM/200\@black */
+ char fullname[80];
+ /*! pointer to our current connection, channel... */
+ struct unistim_subchannel *subs[MAX_SUBS];
+ /*! Extension where to start */
+ char exten[AST_MAX_EXTENSION];
+ /*! Context to start in */
+ char context[AST_MAX_EXTENSION];
+ /*! Language for asterisk sounds */
+ char language[MAX_LANGUAGE];
+ /*! CallerID Number */
+ char cid_num[AST_MAX_EXTENSION];
+ /*! Mailbox for MWI */
+ char mailbox[AST_MAX_EXTENSION];
+ /*! Used by MWI */
+ int lastmsgssent;
+ /*! Used by MWI */
+ time_t nextmsgcheck;
+ /*! MusicOnHold class */
+ char musicclass[MAX_MUSICCLASS];
+ /*! Call group */
+ unsigned int callgroup;
+ /*! Pickup group */
+ unsigned int pickupgroup;
+ /*! Account code (for billing) */
+ char accountcode[80];
+ /*! AMA flags (for billing) */
+ int amaflags;
+ /*! Codec supported */
+ int capability;
+ struct unistim_line *next;
+ struct unistim_device *parent;
+};
+
+/*!
+ * \brief A device containing one or more lines
+ */
+static struct unistim_device {
+ int receiver_state; /*!< state of the receiver (see ReceiverState) */
+ int size_phone_number; /*!< size of the phone number */
+ char phone_number[16]; /*!< the phone number entered by the user */
+ char redial_number[16]; /*!< the last phone number entered by the user */
+ int phone_current; /*!< Number of the current phone */
+ int pos_fav; /*!< Position of the displayed favorites (used for scrolling) */
+ char id[18]; /*!< mac address of the current phone in ascii */
+ char name[DEVICE_NAME_LEN]; /*!< name of the device */
+ int softkeylinepos; /*!< position of the line softkey (default 0) */
+ char softkeylabel[6][11]; /*!< soft key label */
+ char softkeynumber[6][16]; /*!< number dialed when the soft key is pressed */
+ char softkeyicon[6]; /*!< icon number */
+ char softkeydevice[6][16]; /*!< name of the device monitored */
+ struct unistim_device *sp[6]; /*!< pointer to the device monitored by this soft key */
+ char maintext0[25]; /*!< when the phone is idle, display this string on line 0 */
+ char maintext1[25]; /*!< when the phone is idle, display this string on line 1 */
+ char maintext2[25]; /*!< when the phone is idle, display this string on line 2 */
+ char titledefault[13]; /*!< title (text before date/time) */
+ char datetimeformat; /*!< format used for displaying time/date */
+ char contrast; /*!< contrast */
+ char country[3]; /*!< country used for dial tone frequency */
+ struct ind_tone_zone *tz; /*!< Tone zone for res_indications (ring, busy, congestion) */
+ char ringvolume; /*!< Ring volume */
+ char ringstyle; /*!< Ring melody */
+ int rtp_port; /*!< RTP port used by the phone */
+ int rtp_method; /*!< Select the unistim data used to establish a RTP session */
+ int status_method; /*!< Select the unistim packet used for sending status text */
+ char codec_number; /*!< The current codec used to make calls */
+ int missed_call; /*!< Number of call unanswered */
+ int callhistory; /*!< Allowed to record call history */
+ char lst_cid[TEXT_LENGTH_MAX]; /*!< Last callerID received */
+ char lst_cnm[TEXT_LENGTH_MAX]; /*!< Last callername recevied */
+ char call_forward[AST_MAX_EXTENSION]; /*!< Forward number */
+ int output; /*!< Handset, headphone or speaker */
+ int previous_output; /*!< Previous output */
+ int volume; /*!< Default volume */
+ int mute; /*!< Mute mode */
+ int moh; /*!< Music on hold in progress */
+ int nat; /*!< Used by the obscure ast_rtp_setnat */
+ enum autoprov_extn extension; /*!< See ifdef EXTENSION for valid values */
+ char extension_number[11]; /*!< Extension number entered by the user */
+ char to_delete; /*!< Used in reload */
+ time_t start_call_timestamp; /*!< timestamp for the length calculation of the call */
+ struct ast_silence_generator *silence_generator;
+ struct unistim_line *lines;
+ struct ast_ha *ha;
+ struct unistimsession *session;
+ struct unistim_device *next;
+} *devices = NULL;
+
+static struct unistimsession {
+ ast_mutex_t lock;
+ struct sockaddr_in sin; /*!< IP address of the phone */
+ struct sockaddr_in sout; /*!< IP address of server */
+ int timeout; /*!< time-out in ticks : resend packet if no ack was received before the timeout occured */
+ unsigned short seq_phone; /*!< sequence number for the next packet (when we receive a request) */
+ unsigned short seq_server; /*!< sequence number for the next packet (when we send a request) */
+ unsigned short last_seq_ack; /*!< sequence number of the last ACK received */
+ unsigned long tick_next_ping; /*!< time for the next ping */
+ int last_buf_available; /*!< number of a free slot */
+ int nb_retransmit; /*!< number of retransmition */
+ int state; /*!< state of the phone (see phone_state) */
+ int size_buff_entry; /*!< size of the buffer used to enter datas */
+ char buff_entry[16]; /*!< Buffer for temporary datas */
+ char macaddr[18]; /*!< mac adress of the phone (not always available) */
+ struct wsabuf wsabufsend[MAX_BUF_NUMBER]; /*!< Size of each paquet stored in the buffer array & pointer to this buffer */
+ unsigned char buf[MAX_BUF_NUMBER][MAX_BUF_SIZE]; /*!< Buffer array used to keep the lastest non-acked paquets */
+ struct unistim_device *device;
+ struct unistimsession *next;
+} *sessions = NULL;
+
+/*!
+ * \page Unistim datagram formats
+ *
+ * Format of datagrams :
+ * bytes 0 & 1 : ffff for discovery packet, 0000 for everything else
+ * byte 2 : sequence number (high part)
+ * byte 3 : sequence number (low part)
+ * byte 4 : 2 = ask question or send info, 1 = answer or ACK, 0 = retransmit request
+ * byte 5 : direction, 1 = server to phone, 2 = phone to server arguments
+ */
+
+const static unsigned char packet_rcv_discovery[] =
+ { 0xff, 0xff, 0xff, 0xff, 0x02, 0x02, 0xff, 0xff, 0xff, 0xff, 0x9e, 0x03, 0x08 };
+static unsigned char packet_send_discovery_ack[] =
+ { 0x00, 0x00, /*Initial Seq (2 bytes) */ 0x00, 0x00, 0x00, 0x01 };
+
+const static unsigned char packet_recv_firm_version[] =
+ { 0x00, 0x00, 0x00, 0x13, 0x9a, 0x0a, 0x02 };
+const static unsigned char packet_recv_pressed_key[] =
+ { 0x00, 0x00, 0x00, 0x13, 0x99, 0x04, 0x00 };
+const static unsigned char packet_recv_pick_up[] =
+ { 0x00, 0x00, 0x00, 0x13, 0x99, 0x03, 0x04 };
+const static unsigned char packet_recv_hangup[] =
+ { 0x00, 0x00, 0x00, 0x13, 0x99, 0x03, 0x03 };
+const static unsigned char packet_recv_r2[] = { 0x00, 0x00, 0x00, 0x13, 0x96, 0x03, 0x03 };
+
+/*! TransportAdapter */
+const static unsigned char packet_recv_resume_connection_with_server[] =
+ { 0xff, 0xff, 0xff, 0xff, 0x9e, 0x03, 0x08 };
+const static unsigned char packet_recv_mac_addr[] =
+ { 0xff, 0xff, 0xff, 0xff, 0x9a, 0x0d, 0x07, 0x31, 0x38 /*MacAddr */ };
+
+const static unsigned char packet_send_date_time3[] =
+ { 0x11, 0x09, 0x02, 0x02, /*Month */ 0x05, /*Day */ 0x06, /*Hour */ 0x07,
+/*Minutes */ 0x08, 0x32
+};
+const static unsigned char packet_send_date_time[] =
+ { 0x11, 0x09, 0x02, 0x0a, /*Month */ 0x05, /*Day */ 0x06, /*Hour */ 0x07, /*Minutes */
+0x08, 0x32, 0x17, 0x04, 0x24, 0x07, 0x19,
+ 0x04, 0x07, 0x00, 0x19, 0x05, 0x09, 0x3e, 0x0f, 0x16, 0x05, 0x00, 0x80, 0x00, 0x1e,
+ 0x05, 0x12, 0x00, 0x78
+};
+
+const static unsigned char packet_send_no_ring[] =
+ { 0x16, 0x04, 0x1a, 0x00, 0x16, 0x04, 0x11, 0x00 };
+const static unsigned char packet_send_s4[] =
+ { 0x16, 0x04, 0x1a, 0x00, 0x16, 0x04, 0x11, 0x00, 0x16, 0x06, 0x32, 0xdf, 0x00, 0xff,
+0x16, 0x05, 0x1c, 0x00, 0x00, 0x17, 0x05,
+ 0x0b, 0x00, 0x00, 0x19, 0x04, 0x00, 0x00, 0x19, 0x04, 0x00, 0x08, 0x19, 0x04, 0x00,
+ 0x10, 0x19, 0x04, 0x00, 0x18, 0x16, 0x05,
+ 0x31, 0x00, 0x00, 0x16, 0x05, 0x04, 0x00, 0x00
+};
+const static unsigned char packet_send_call[] =
+ { 0x16, 0x04, 0x1a, 0x00, 0x16, 0x04, 0x11, 0x00, 0x16, 0x06, 0x32, 0xdf,
+ 0x00, 0xff, 0x16, 0x05, 0x1c, 0x00, 0x00, 0x16, 0x0a, 0x38, 0x00, 0x12, 0xca, 0x03,
+ 0xc0, 0xc3, 0xc5, 0x16, 0x16, 0x30, 0x00,
+ 0x00, /*codec */ 0x12, 0x12, /* frames per packet */ 0x01, 0x5c, 0x00, /*port RTP */
+ 0x0f, 0xa0, /* port RTCP */ 0x9c, 0x41,
+ /*port RTP */ 0x0f, 0xa0, /* port RTCP */ 0x9c, 0x41, /* IP Address */ 0x0a, 0x01,
+ 0x16, 0x66
+};
+const static unsigned char packet_send_stream_based_tone_off[] =
+ { 0x16, 0x05, 0x1c, 0x00, 0x00 };
+
+/* const static unsigned char packet_send_Mute[] = { 0x16, 0x05, 0x04, 0x00, 0x00 };
+const static unsigned char packet_send_CloseAudioStreamRX[] = { 0x16, 0x05, 0x31, 0x00, 0xff };
+const static unsigned char packet_send_CloseAudioStreamTX[] = { 0x16, 0x05, 0x31, 0xff, 0x00 };*/
+const static unsigned char packet_send_stream_based_tone_on[] =
+ { 0x16, 0x06, 0x1b, 0x00, 0x00, 0x05 };
+const static unsigned char packet_send_stream_based_tone_single_freq[] =
+ { 0x16, 0x06, 0x1d, 0x00, 0x01, 0xb8 };
+const static unsigned char packet_send_stream_based_tone_dial_freq[] =
+ { 0x16, 0x08, 0x1d, 0x00, 0x01, 0xb8, 0x01, 0x5e };
+const static unsigned char packet_send_select_output[] =
+ { 0x16, 0x06, 0x32, 0xc0, 0x01, 0x00 };
+const static unsigned char packet_send_ring[] =
+ { 0x16, 0x06, 0x32, 0xdf, 0x00, 0xff, 0x16, 0x05, 0x1c, 0x00, 0x00, 0x16,
+ 0x04, 0x1a, 0x01, 0x16, 0x05, 0x12, 0x13 /* Ring type 10 to 17 */ , 0x18, 0x16, 0x04, 0x18, /* volume 00, 10, 20... */
+ 0x20, 0x16, 0x04, 0x10, 0x00
+};
+const static unsigned char packet_send_end_call[] =
+ { 0x16, 0x06, 0x32, 0xdf, 0x00, 0xff, 0x16, 0x05, 0x31, 0x00, 0x00, 0x19, 0x04, 0x00,
+0x10, 0x19, 0x04, 0x00, 0x18, 0x16, 0x05,
+ 0x04, 0x00, 0x00, 0x16, 0x04, 0x37, 0x10
+};
+const static unsigned char packet_send_s9[] =
+ { 0x16, 0x06, 0x32, 0xdf, 0x00, 0xff, 0x19, 0x04, 0x00, 0x10, 0x16, 0x05, 0x1c, 0x00,
+0x00 };
+const static unsigned char packet_send_rtp_packet_size[] =
+ { 0x16, 0x08, 0x38, 0x00, 0x00, 0xe0, 0x00, 0xa0 };
+const static unsigned char packet_send_jitter_buffer_conf[] =
+ { 0x16, 0x0e, 0x3a, 0x00, /* jitter */ 0x02, /* high water mark */ 0x04, 0x00, 0x00,
+/* early packet resync 2 bytes */ 0x3e, 0x80,
+ 0x00, 0x00, /* late packet resync 2 bytes */ 0x3e, 0x80
+};
+
+/* Duration in ms div 2 (0x20 = 64ms, 0x08 = 16ms)
+static unsigned char packet_send_StreamBasedToneCad[] =
+ { 0x16, 0x0a, 0x1e, 0x00, duration on 0x0a, duration off 0x0d, duration on 0x0a, duration off 0x0d, duration on 0x0a, duration off 0x2b }; */
+const static unsigned char packet_send_open_audio_stream_rx[] =
+ { 0x16, 0x1a, 0x30, 0x00, 0xff, /* Codec */ 0x00, 0x00, 0x01, 0x00, 0xb8, 0xb8, 0x0e,
+0x0e, 0x01, /* Port */ 0x14, 0x50, 0x00,
+ 0x00, /* Port */ 0x14, 0x50, 0x00, 0x00, /* Dest IP */ 0x0a, 0x93, 0x69, 0x05
+};
+const static unsigned char packet_send_open_audio_stream_tx[] =
+ { 0x16, 0x1a, 0x30, 0xff, 0x00, 0x00, /* Codec */ 0x00, 0x01, 0x00, 0xb8, 0xb8, 0x0e,
+0x0e, 0x01, /* Local port */ 0x14, 0x50,
+ 0x00, 0x00, /* Rmt Port */ 0x14, 0x50, 0x00, 0x00, /* Dest IP */ 0x0a, 0x93, 0x69, 0x05
+};
+
+const static unsigned char packet_send_open_audio_stream_rx3[] =
+ { 0x16, 0x1a, 0x30, 0x00, 0xff, /* Codec */ 0x00, 0x00, 0x02, 0x01, 0xb8, 0xb8, 0x06,
+0x06, 0x81, /* RTP Port */ 0x14, 0x50,
+/* RTCP Port */ 0x14,
+ 0x51, /* RTP Port */ 0x14, 0x50, /* RTCP Port */ 0x00, 0x00, /* Dest IP */ 0x0a, 0x93,
+ 0x69, 0x05
+};
+const static unsigned char packet_send_open_audio_stream_tx3[] =
+ { 0x16, 0x1a, 0x30, 0xff, 0x00, 0x00, /* Codec */ 0x00, 0x02, 0x01, 0xb8, 0xb8, 0x06,
+0x06, 0x81, /* RTP Local port */ 0x14, 0x50,
+ /* RTCP Port */ 0x00, 0x00, /* RTP Rmt Port */ 0x14, 0x50, /* RTCP Port */ 0x00, 0x00,
+ /* Dest IP */ 0x0a, 0x93, 0x69, 0x05
+};
+
+const static unsigned char packet_send_arrow[] = { 0x17, 0x04, 0x04, 0x00 };
+const static unsigned char packet_send_blink_cursor[] = { 0x17, 0x04, 0x10, 0x86 };
+const static unsigned char packet_send_date_time2[] = { 0x17, 0x04, 0x17, 0x3d, 0x11, 0x09, 0x02, 0x0a, /*Month */ 0x05, /*Day */
+ 0x06, /*Hour */ 0x07, /*Minutes */ 0x08, 0x32
+};
+const static unsigned char packet_send_Contrast[] =
+ { 0x17, 0x04, 0x24, /*Contrast */ 0x08 };
+const static unsigned char packet_send_StartTimer[] =
+ { 0x17, 0x05, 0x0b, 0x05, 0x00, 0x17, 0x08, 0x16, /* Text */ 0x44, 0x75, 0x72, 0xe9,
+0x65 };
+const static unsigned char packet_send_stop_timer[] = { 0x17, 0x05, 0x0b, 0x02, 0x00 };
+const static unsigned char packet_send_icon[] = { 0x17, 0x05, 0x14, /*pos */ 0x00, /*icon */ 0x25 }; /* display an icon in front of the text zone */
+const static unsigned char packet_send_S7[] = { 0x17, 0x06, 0x0f, 0x30, 0x07, 0x07 };
+const static unsigned char packet_send_set_pos_cursor[] =
+ { 0x17, 0x06, 0x10, 0x81, 0x04, /*pos */ 0x20 };
+
+/*static unsigned char packet_send_MonthLabelsDownload[] =
+ { 0x17, 0x0a, 0x15, Month (3 char) 0x46, 0x65, 0x62, 0x4d, 0xe4, 0x72, 0x20 }; */
+const static unsigned char packet_send_favorite[] =
+ { 0x17, 0x0f, 0x19, 0x10, /*pos */ 0x01, /*name */ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+0x20, 0x20, 0x20, 0x20, /*end_name */ 0x19,
+ 0x05, 0x0f, /*pos */ 0x01, /*icone */ 0x00
+};
+const static unsigned char packet_send_title[] =
+ { 0x17, 0x10, 0x19, 0x02, /*text */ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+0x20, 0x20, 0x20, 0x20 /*end_text */ };
+const static unsigned char packet_send_text[] =
+ { 0x17, 0x1e, 0x1b, 0x04, /*pos */ 0x00, /*inverse */ 0x25, /*text */ 0x20, 0x20,
+0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ /*end_text */ 0x17, 0x04, 0x10, 0x87
+};
+const static unsigned char packet_send_status[] =
+ { 0x17, 0x20, 0x19, 0x08, /*text */ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 /*end_text */
+};
+const static unsigned char packet_send_status2[] =
+ { 0x17, 0x0b, 0x19, /* pos [08|28|48|68] */ 0x00, /* text */ 0x20, 0x20, 0x20, 0x20,
+0x20, 0x20, 0x20 /* end_text */ };
+
+const static unsigned char packet_send_led_update[] = { 0x19, 0x04, 0x00, 0x00 };
+
+const static unsigned char packet_send_query_basic_manager_04[] = { 0x1a, 0x04, 0x01, 0x04 };
+const static unsigned char packet_send_query_mac_address[] = { 0x1a, 0x04, 0x01, 0x08 };
+const static unsigned char packet_send_query_basic_manager_10[] = { 0x1a, 0x04, 0x01, 0x10 };
+const static unsigned char packet_send_S1[] = { 0x1a, 0x07, 0x07, 0x00, 0x00, 0x00, 0x13 };
+
+static unsigned char packet_send_ping[] =
+ { 0x1e, 0x05, 0x12, 0x00, /*Watchdog timer */ 0x78 };
+
+#define BUFFSEND unsigned char buffsend[64] = { 0x00, 0x00, 0xaa, 0xbb, 0x02, 0x01 }
+
+const static char tdesc[] = "UNISTIM Channel Driver";
+const static char type[] = "USTM";
+
+/*! Protos */
+static struct ast_channel *unistim_new(struct unistim_subchannel *sub, int state);
+static int load_module(void);
+static int reload(void);
+static int unload_module(void);
+static int reload_config(void);
+static void show_main_page(struct unistimsession *pte);
+static struct ast_channel *unistim_request(const char *type, int format,
+ void *data, int *cause);
+static int unistim_call(struct ast_channel *ast, char *dest, int timeout);
+static int unistim_hangup(struct ast_channel *ast);
+static int unistim_answer(struct ast_channel *ast);
+static struct ast_frame *unistim_read(struct ast_channel *ast);
+static int unistim_write(struct ast_channel *ast, struct ast_frame *frame);
+static int unistim_indicate(struct ast_channel *ast, int ind, const void *data,
+ size_t datalen);
+static int unistim_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static int unistim_senddigit_begin(struct ast_channel *ast, char digit);
+static int unistim_senddigit_end(struct ast_channel *ast, char digit,
+ unsigned int duration);
+static int unistim_sendtext(struct ast_channel *ast, const char *text);
+
+static int write_entry_history(struct unistimsession *pte, FILE * f, char c,
+ char *line1);
+static void change_callerid(struct unistimsession *pte, int type, char *callerid);
+
+static const struct ast_channel_tech unistim_tech = {
+ .type = type,
+ .description = tdesc,
+ .capabilities = CAPABILITY,
+ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
+ .requester = unistim_request,
+ .call = unistim_call,
+ .hangup = unistim_hangup,
+ .answer = unistim_answer,
+ .read = unistim_read,
+ .write = unistim_write,
+ .indicate = unistim_indicate,
+ .fixup = unistim_fixup,
+ .send_digit_begin = unistim_senddigit_begin,
+ .send_digit_end = unistim_senddigit_end,
+ .send_text = unistim_sendtext,
+/* .bridge = ast_rtp_bridge, */
+};
+
+static void display_last_error(const char *sz_msg)
+{
+ time_t cur_time;
+
+ time(&cur_time);
+
+ /* Display the error message */
+ ast_log(LOG_WARNING, "%s %s : (%u) %s\n", ctime(&cur_time), sz_msg, errno,
+ strerror(errno));
+}
+
+static unsigned int get_tick_count(void)
+{
+ struct timeval tv = ast_tvnow();
+
+ return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
+}
+
+/* Send data to a phone without retransmit nor buffering */
+static void send_raw_client(int size, unsigned char *data, struct sockaddr_in *addr_to,
+ const struct sockaddr_in *addr_ourip)
+{
+#ifdef HAVE_PKTINFO
+ struct iovec msg_iov;
+ struct msghdr msg;
+ char buffer[CMSG_SPACE(sizeof(struct in_pktinfo))];
+ struct cmsghdr *ip_msg = (struct cmsghdr *) buffer;
+ struct in_pktinfo *pki = (struct in_pktinfo *) CMSG_DATA(ip_msg);
+
+ msg_iov.iov_base = data;
+ msg_iov.iov_len = size;
+
+ msg.msg_name = addr_to; /* optional address */
+ msg.msg_namelen = sizeof(struct sockaddr_in); /* size of address */
+ msg.msg_iov = &msg_iov; /* scatter/gather array */
+ msg.msg_iovlen = 1; /* # elements in msg_iov */
+ msg.msg_control = ip_msg; /* ancillary data */
+ msg.msg_controllen = sizeof(buffer); /* ancillary data buffer len */
+ msg.msg_flags = 0; /* flags on received message */
+
+ ip_msg->cmsg_len = CMSG_LEN(sizeof(*pki));
+ ip_msg->cmsg_level = IPPROTO_IP;
+ ip_msg->cmsg_type = IP_PKTINFO;
+ pki->ipi_ifindex = 0; /* Interface index, 0 = use interface specified in routing table */
+ pki->ipi_spec_dst.s_addr = addr_ourip->sin_addr.s_addr; /* Local address */
+ /* pki->ipi_addr = ; Header Destination address - ignored by kernel */
+
+#ifdef DUMP_PACKET
+ if (unistimdebug) {
+ int tmp;
+ char iabuf[INET_ADDRSTRLEN];
+ char iabuf2[INET_ADDRSTRLEN];
+ ast_verbose("\n**> From %s sending %d bytes to %s ***\n",
+ ast_inet_ntoa(addr_ourip->sin_addr), (int) size,
+ ast_inet_ntoa(addr_to->sin_addr));
+ for (tmp = 0; tmp < size; tmp++)
+ ast_verbose("%.2x ", (unsigned char) data[tmp]);
+ ast_verbose("\n******************************************\n");
+
+ }
+#endif
+
+ if (sendmsg(unistimsock, &msg, 0) == -1)
+ display_last_error("Error sending datas");
+#else
+ if (sendto(unistimsock, data, size, 0, (struct sockaddr *) addr_to, sizeof(*addr_to))
+ == -1)
+ display_last_error("Error sending datas");
+#endif
+}
+
+static void send_client(int size, const unsigned char *data, struct unistimsession *pte)
+{
+ unsigned int tick;
+ int buf_pos;
+ unsigned short *sdata = (unsigned short *) data;
+
+ ast_mutex_lock(&pte->lock);
+ buf_pos = pte->last_buf_available;
+
+ if (buf_pos >= MAX_BUF_NUMBER) {
+ ast_log(LOG_WARNING, "Error : send queue overflow\n");
+ ast_mutex_unlock(&pte->lock);
+ return;
+ }
+ sdata[1] = ntohs(++(pte->seq_server));
+ pte->wsabufsend[buf_pos].len = size;
+ memcpy(pte->wsabufsend[buf_pos].buf, data, size);
+
+ tick = get_tick_count();
+ pte->timeout = tick + RETRANSMIT_TIMER;
+
+/*#ifdef DUMP_PACKET */
+ if ((unistimdebug) && (option_verbose > 5)) {
+ ast_verbose("Sending datas with seq #0x%.4x Using slot #%d :\n", pte->seq_server,
+ buf_pos);
+ }
+/*#endif */
+ send_raw_client(pte->wsabufsend[buf_pos].len, pte->wsabufsend[buf_pos].buf, &(pte->sin),
+ &(pte->sout));
+ pte->last_buf_available++;
+ ast_mutex_unlock(&pte->lock);
+}
+
+static void send_ping(struct unistimsession *pte)
+{
+ BUFFSEND;
+ if ((unistimdebug) && (option_verbose > 5))
+ ast_verbose("Sending ping\n");
+ pte->tick_next_ping = get_tick_count() + unistim_keepalive;
+ memcpy(buffsend + SIZE_HEADER, packet_send_ping, sizeof(packet_send_ping));
+ send_client(SIZE_HEADER + sizeof(packet_send_ping), buffsend, pte);
+}
+
+static int get_to_address(int fd, struct sockaddr_in *toAddr)
+{
+#ifdef HAVE_PKTINFO
+ int err;
+ struct msghdr msg;
+ struct {
+ struct cmsghdr cm;
+ int len;
+ struct in_addr address;
+ } ip_msg;
+
+ /* Zero out the structures before we use them */
+ /* This sets several key values to NULL */
+ memset(&msg, 0, sizeof(msg));
+ memset(&ip_msg, 0, sizeof(ip_msg));
+
+ /* Initialize the message structure */
+ msg.msg_control = &ip_msg;
+ msg.msg_controllen = sizeof(ip_msg);
+ /* Get info about the incoming packet */
+ err = recvmsg(fd, &msg, MSG_PEEK);
+ if (err == -1)
+ ast_log(LOG_WARNING, "recvmsg returned an error: %s\n", strerror(errno));
+ memcpy(&toAddr->sin_addr, &ip_msg.address, sizeof(struct in_addr));
+ return err;
+#else
+ memcpy(&toAddr, &public_ip, sizeof(&toAddr));
+ return 0;
+#endif
+}
+
+/* Allocate memory & initialize structures for a new phone */
+/* addr_from : ip address of the phone */
+static struct unistimsession *create_client(const struct sockaddr_in *addr_from)
+{
+ int tmp;
+ struct unistimsession *s;
+
+ if (!(s = ast_calloc(1, sizeof(*s))))
+ return NULL;
+
+ memcpy(&s->sin, addr_from, sizeof(struct sockaddr_in));
+ get_to_address(unistimsock, &s->sout);
+ if (unistimdebug) {
+ ast_verbose
+ ("Creating a new entry for the phone from %s received via server ip %s\n",
+ ast_inet_ntoa(addr_from->sin_addr), ast_inet_ntoa(s->sout.sin_addr));
+ }
+ ast_mutex_init(&s->lock);
+ ast_mutex_lock(&sessionlock);
+ s->next = sessions;
+ sessions = s;
+
+ s->timeout = get_tick_count() + RETRANSMIT_TIMER;
+ s->seq_phone = (short) 0x0000;
+ s->seq_server = (short) 0x0000;
+ s->last_seq_ack = (short) 0x000;
+ s->last_buf_available = 0;
+ s->nb_retransmit = 0;
+ s->state = STATE_INIT;
+ s->tick_next_ping = get_tick_count() + unistim_keepalive;
+ /* Initialize struct wsabuf */
+ for (tmp = 0; tmp < MAX_BUF_NUMBER; tmp++) {
+ s->wsabufsend[tmp].buf = s->buf[tmp];
+ }
+ ast_mutex_unlock(&sessionlock);
+ return s;
+}
+
+static void send_end_call(struct unistimsession *pte)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending end call\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_end_call, sizeof(packet_send_end_call));
+ send_client(SIZE_HEADER + sizeof(packet_send_end_call), buffsend, pte);
+}
+
+static void set_ping_timer(struct unistimsession *pte)
+{
+ unsigned int tick = 0; /* XXX what is this for, anyways */
+
+ pte->timeout = pte->tick_next_ping;
+ DEBUG_TIMER("tick = %u next ping at %u tick\n", tick, pte->timeout);
+ return;
+}
+
+/* Checking if our send queue is empty,
+ * if true, setting up a timer for keepalive */
+static void check_send_queue(struct unistimsession *pte)
+{
+ /* Check if our send queue contained only one element */
+ if (pte->last_buf_available == 1) {
+ if ((unistimdebug) && (option_verbose > 5))
+ ast_verbose("Our single packet was ACKed.\n");
+ pte->last_buf_available--;
+ set_ping_timer(pte);
+ return;
+ }
+ /* Check if this ACK catch up our latest packet */
+ else if (pte->last_seq_ack + 1 == pte->seq_server + 1) {
+ if ((unistimdebug) && (option_verbose > 5))
+ ast_verbose("Our send queue is completely ACKed.\n");
+ pte->last_buf_available = 0; /* Purge the send queue */
+ set_ping_timer(pte);
+ return;
+ }
+ if ((unistimdebug) && (option_verbose > 5))
+ ast_verbose("We still have packets in our send queue\n");
+ return;
+}
+
+static void send_start_timer(struct unistimsession *pte)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending start timer\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_StartTimer, sizeof(packet_send_StartTimer));
+ send_client(SIZE_HEADER + sizeof(packet_send_StartTimer), buffsend, pte);
+}
+
+static void send_stop_timer(struct unistimsession *pte)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending stop timer\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_stop_timer, sizeof(packet_send_stop_timer));
+ send_client(SIZE_HEADER + sizeof(packet_send_stop_timer), buffsend, pte);
+}
+
+static void Sendicon(unsigned char pos, unsigned char status, struct unistimsession *pte)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending icon pos %d with status 0x%.2x\n", pos, status);
+ memcpy(buffsend + SIZE_HEADER, packet_send_icon, sizeof(packet_send_icon));
+ buffsend[9] = pos;
+ buffsend[10] = status;
+ send_client(SIZE_HEADER + sizeof(packet_send_icon), buffsend, pte);
+}
+
+static void send_tone(struct unistimsession *pte, uint16_t tone1, uint16_t tone2)
+{
+ BUFFSEND;
+ if (!tone1) {
+ if (unistimdebug)
+ ast_verbose("Sending Stream Based Tone Off\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_stream_based_tone_off,
+ sizeof(packet_send_stream_based_tone_off));
+ send_client(SIZE_HEADER + sizeof(packet_send_stream_based_tone_off), buffsend, pte);
+ return;
+ }
+ /* Since most of the world use a continuous tone, it's useless
+ if (unistimdebug)
+ ast_verbose ("Sending Stream Based Tone Cadence Download\n");
+ memcpy (buffsend + SIZE_HEADER, packet_send_StreamBasedToneCad, sizeof (packet_send_StreamBasedToneCad));
+ send_client (SIZE_HEADER + sizeof (packet_send_StreamBasedToneCad), buffsend, pte); */
+ if (unistimdebug)
+ ast_verbose("Sending Stream Based Tone Frequency Component List Download %d %d\n",
+ tone1, tone2);
+ tone1 *= 8;
+ if (!tone2) {
+ memcpy(buffsend + SIZE_HEADER, packet_send_stream_based_tone_single_freq,
+ sizeof(packet_send_stream_based_tone_single_freq));
+ buffsend[10] = (tone1 & 0xff00) >> 8;
+ buffsend[11] = (tone1 & 0x00ff);
+ send_client(SIZE_HEADER + sizeof(packet_send_stream_based_tone_single_freq), buffsend,
+ pte);
+ } else {
+ tone2 *= 8;
+ memcpy(buffsend + SIZE_HEADER, packet_send_stream_based_tone_dial_freq,
+ sizeof(packet_send_stream_based_tone_dial_freq));
+ buffsend[10] = (tone1 & 0xff00) >> 8;
+ buffsend[11] = (tone1 & 0x00ff);
+ buffsend[12] = (tone2 & 0xff00) >> 8;
+ buffsend[13] = (tone2 & 0x00ff);
+ send_client(SIZE_HEADER + sizeof(packet_send_stream_based_tone_dial_freq), buffsend,
+ pte);
+ }
+
+ if (unistimdebug)
+ ast_verbose("Sending Stream Based Tone On\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_stream_based_tone_on,
+ sizeof(packet_send_stream_based_tone_on));
+ send_client(SIZE_HEADER + sizeof(packet_send_stream_based_tone_on), buffsend, pte);
+}
+
+/* Positions for favorites
+ |--------------------|
+ | 5 2 |
+ | 4 1 |
+ | 3 0 |
+*/
+
+/* status (icons) : 00 = nothing, 2x/3x = see parser.h, 4x/5x = blink fast, 6x/7x = blink slow */
+static void
+send_favorite(unsigned char pos, unsigned char status, struct unistimsession *pte,
+ const char *text)
+{
+ BUFFSEND;
+ int i;
+
+ if (unistimdebug)
+ ast_verbose("Sending favorite pos %d with status 0x%.2x\n", pos, status);
+ memcpy(buffsend + SIZE_HEADER, packet_send_favorite, sizeof(packet_send_favorite));
+ buffsend[10] = pos;
+ buffsend[24] = pos;
+ buffsend[25] = status;
+ i = strlen(text);
+ if (i > FAV_MAX_LENGTH)
+ i = FAV_MAX_LENGTH;
+ memcpy(buffsend + FAV_MAX_LENGTH + 1, text, i);
+ send_client(SIZE_HEADER + sizeof(packet_send_favorite), buffsend, pte);
+}
+
+static void refresh_all_favorite(struct unistimsession *pte)
+{
+ int i = 0;
+
+ if (unistimdebug)
+ ast_verbose("Refreshing all favorite\n");
+ for (i = 0; i < 6; i++) {
+ if ((pte->device->softkeyicon[i] <= FAV_ICON_HEADPHONES_ONHOLD) &&
+ (pte->device->softkeylinepos != i))
+ send_favorite((unsigned char) i, pte->device->softkeyicon[i] + 1, pte,
+ pte->device->softkeylabel[i]);
+ else
+ send_favorite((unsigned char) i, pte->device->softkeyicon[i], pte,
+ pte->device->softkeylabel[i]);
+
+ }
+}
+
+/* Change the status for this phone (pte) and update for each phones where pte is bookmarked
+ * use FAV_ICON_*_BLACK constant in status parameters */
+static void change_favorite_icon(struct unistimsession *pte, unsigned char status)
+{
+ struct unistim_device *d = devices;
+ int i;
+ /* Update the current phone */
+ if (pte->state != STATE_CLEANING)
+ send_favorite(pte->device->softkeylinepos, status, pte,
+ pte->device->softkeylabel[pte->device->softkeylinepos]);
+ /* Notify other phones if we're in their bookmark */
+ while (d) {
+ for (i = 0; i < 6; i++) {
+ if (d->sp[i] == pte->device) { /* It's us ? */
+ if (d->softkeyicon[i] != status) { /* Avoid resending the same icon */
+ d->softkeyicon[i] = status;
+ if (d->session)
+ send_favorite(i, status + 1, d->session, d->softkeylabel[i]);
+ }
+ }
+ }
+ d = d->next;
+ }
+}
+
+static int RegisterExtension(const struct unistimsession *pte)
+{
+ if (unistimdebug)
+ ast_verbose("Trying to register extension '%s' into context '%s' to %s\n",
+ pte->device->extension_number, pte->device->lines->context,
+ pte->device->lines->fullname);
+ return ast_add_extension(pte->device->lines->context, 0,
+ pte->device->extension_number, 1, NULL, NULL, "Dial",
+ pte->device->lines->fullname, 0, "Unistim");
+}
+
+static int UnregisterExtension(const struct unistimsession *pte)
+{
+ if (unistimdebug)
+ ast_verbose("Trying to unregister extension '%s' context '%s'\n",
+ pte->device->extension_number, pte->device->lines->context);
+ return ast_context_remove_extension(pte->device->lines->context,
+ pte->device->extension_number, 1, "Unistim");
+}
+
+/* Free memory allocated for a phone */
+static void close_client(struct unistimsession *s)
+{
+ struct unistim_subchannel *sub;
+ struct unistimsession *cur, *prev = NULL;
+ ast_mutex_lock(&sessionlock);
+ cur = sessions;
+ /* Looking for the session in the linked chain */
+ while (cur) {
+ if (cur == s)
+ break;
+ prev = cur;
+ cur = cur->next;
+ }
+ if (cur) { /* Session found ? */
+ if (cur->device) { /* This session was registred ? */
+ s->state = STATE_CLEANING;
+ if (unistimdebug)
+ ast_verbose("close_client session %p device %p lines %p sub %p\n",
+ s, s->device, s->device->lines,
+ s->device->lines->subs[SUB_REAL]);
+ change_favorite_icon(s, FAV_ICON_NONE);
+ sub = s->device->lines->subs[SUB_REAL];
+ if (sub) {
+ if (sub->owner) { /* Call in progress ? */
+ if (unistimdebug)
+ ast_verbose("Aborting call\n");
+ ast_queue_hangup(sub->owner);
+ }
+ } else
+ ast_log(LOG_WARNING, "Freeing a client with no subchannel !\n");
+ if (!ast_strlen_zero(s->device->extension_number))
+ UnregisterExtension(s);
+ cur->device->session = NULL;
+ } else {
+ if (unistimdebug)
+ ast_verbose("Freeing an unregistered client\n");
+ }
+ if (prev)
+ prev->next = cur->next;
+ else
+ sessions = cur->next;
+ ast_mutex_destroy(&s->lock);
+ ast_free(s);
+ } else
+ ast_log(LOG_WARNING, "Trying to delete non-existant session %p?\n", s);
+ ast_mutex_unlock(&sessionlock);
+ return;
+}
+
+/* Return 1 if the session chained link was modified */
+static int send_retransmit(struct unistimsession *pte)
+{
+ int i;
+
+ ast_mutex_lock(&pte->lock);
+ if (++pte->nb_retransmit >= NB_MAX_RETRANSMIT) {
+ if (unistimdebug)
+ ast_verbose("Too many retransmit - freeing client\n");
+ ast_mutex_unlock(&pte->lock);
+ close_client(pte);
+ return 1;
+ }
+ pte->timeout = get_tick_count() + RETRANSMIT_TIMER;
+
+ for (i = pte->last_buf_available - (pte->seq_server - pte->last_seq_ack);
+ i < pte->last_buf_available; i++) {
+ if (i < 0) {
+ ast_log(LOG_WARNING,
+ "Asked to retransmit an ACKed slot ! last_buf_available=%d, seq_server = #0x%.4x last_seq_ack = #0x%.4x\n",
+ pte->last_buf_available, pte->seq_server, pte->last_seq_ack);
+ continue;
+ }
+
+ if (unistimdebug) {
+ unsigned short *sbuf = (unsigned short *) pte->wsabufsend[i].buf;
+ unsigned short seq;
+
+ seq = ntohs(sbuf[1]);
+ ast_verbose("Retransmit slot #%d (seq=#0x%.4x), last ack was #0x%.4x\n", i,
+ seq, pte->last_seq_ack);
+ }
+ send_raw_client(pte->wsabufsend[i].len, pte->wsabufsend[i].buf, &pte->sin,
+ &pte->sout);
+ }
+ ast_mutex_unlock(&pte->lock);
+ return 0;
+}
+
+/* inverse : TEXT_INVERSE : yes, TEXT_NORMAL : no */
+static void
+send_text(unsigned char pos, unsigned char inverse, struct unistimsession *pte,
+ const char *text)
+{
+ int i;
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending text at pos %d, inverse flag %d\n", pos, inverse);
+ memcpy(buffsend + SIZE_HEADER, packet_send_text, sizeof(packet_send_text));
+ buffsend[10] = pos;
+ buffsend[11] = inverse;
+ i = strlen(text);
+ if (i > TEXT_LENGTH_MAX)
+ i = TEXT_LENGTH_MAX;
+ memcpy(buffsend + 12, text, i);
+ send_client(SIZE_HEADER + sizeof(packet_send_text), buffsend, pte);
+}
+
+static void send_text_status(struct unistimsession *pte, const char *text)
+{
+ BUFFSEND;
+ int i;
+ if (unistimdebug)
+ ast_verbose("Sending status text\n");
+ if (pte->device) {
+ if (pte->device->status_method == 1) { /* For new firmware and i2050 soft phone */
+ int n = strlen(text);
+ /* Must send individual button separately */
+ int j;
+ for (i = 0, j = 0; i < 4; i++, j += 7) {
+ int pos = 0x08 + (i * 0x20);
+ memcpy(buffsend + SIZE_HEADER, packet_send_status2,
+ sizeof(packet_send_status2));
+
+ buffsend[9] = pos;
+ memcpy(buffsend + 10, (j < n) ? (text + j) : " ", 7);
+ send_client(SIZE_HEADER + sizeof(packet_send_status2), buffsend, pte);
+ }
+ return;
+ }
+ }
+
+
+ memcpy(buffsend + SIZE_HEADER, packet_send_status, sizeof(packet_send_status));
+ i = strlen(text);
+ if (i > STATUS_LENGTH_MAX)
+ i = STATUS_LENGTH_MAX;
+ memcpy(buffsend + 10, text, i);
+ send_client(SIZE_HEADER + sizeof(packet_send_status), buffsend, pte);
+
+}
+
+/* led values in hexa : 0 = bar off, 1 = bar on, 2 = bar 1s on/1s off, 3 = bar 2.5s on/0.5s off
+ * 4 = bar 0.6s on/0.3s off, 5 = bar 0.5s on/0.5s off, 6 = bar 2s on/0.5s off
+ * 7 = bar off, 8 = speaker off, 9 = speaker on, 10 = headphone off, 11 = headphone on
+ * 18 = mute off, 19 mute on */
+static void send_led_update(struct unistimsession *pte, unsigned char led)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending led_update (%x)\n", led);
+ memcpy(buffsend + SIZE_HEADER, packet_send_led_update, sizeof(packet_send_led_update));
+ buffsend[9] = led;
+ send_client(SIZE_HEADER + sizeof(packet_send_led_update), buffsend, pte);
+}
+
+/* output = OUTPUT_HANDSET, OUTPUT_HEADPHONE or OUTPUT_SPEAKER
+ * volume = VOLUME_LOW, VOLUME_NORMAL, VOLUME_INSANELY_LOUD
+ * mute = MUTE_OFF, MUTE_ON */
+static void
+send_select_output(struct unistimsession *pte, unsigned char output, unsigned char volume,
+ unsigned char mute)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending select output packet output=%x volume=%x mute=%x\n", output,
+ volume, mute);
+ memcpy(buffsend + SIZE_HEADER, packet_send_select_output,
+ sizeof(packet_send_select_output));
+ buffsend[9] = output;
+ if (output == OUTPUT_SPEAKER)
+ volume = VOLUME_LOW_SPEAKER;
+ else
+ volume = VOLUME_LOW;
+ buffsend[10] = volume;
+ if (mute == MUTE_ON_DISCRET)
+ buffsend[11] = MUTE_ON;
+ else
+ buffsend[11] = mute;
+ send_client(SIZE_HEADER + sizeof(packet_send_select_output), buffsend, pte);
+ if (mute == MUTE_OFF)
+ send_led_update(pte, 0x18);
+ else if (mute == MUTE_ON)
+ send_led_update(pte, 0x19);
+ pte->device->mute = mute;
+ if (output == OUTPUT_HANDSET) {
+ if (mute == MUTE_ON)
+ change_favorite_icon(pte, FAV_ICON_ONHOLD_BLACK);
+ else
+ change_favorite_icon(pte, FAV_ICON_OFFHOOK_BLACK);
+ send_led_update(pte, 0x08);
+ send_led_update(pte, 0x10);
+ } else if (output == OUTPUT_HEADPHONE) {
+ if (mute == MUTE_ON)
+ change_favorite_icon(pte, FAV_ICON_HEADPHONES_ONHOLD);
+ else
+ change_favorite_icon(pte, FAV_ICON_HEADPHONES);
+ send_led_update(pte, 0x08);
+ send_led_update(pte, 0x11);
+ } else if (output == OUTPUT_SPEAKER) {
+ send_led_update(pte, 0x10);
+ send_led_update(pte, 0x09);
+ if (pte->device->receiver_state == STATE_OFFHOOK) {
+ if (mute == MUTE_ON)
+ change_favorite_icon(pte, FAV_ICON_SPEAKER_ONHOLD_BLACK);
+ else
+ change_favorite_icon(pte, FAV_ICON_SPEAKER_ONHOOK_BLACK);
+ } else {
+ if (mute == MUTE_ON)
+ change_favorite_icon(pte, FAV_ICON_SPEAKER_ONHOLD_BLACK);
+ else
+ change_favorite_icon(pte, FAV_ICON_SPEAKER_OFFHOOK_BLACK);
+ }
+ } else
+ ast_log(LOG_WARNING, "Invalid ouput (%d)\n", output);
+ if (output != pte->device->output)
+ pte->device->previous_output = pte->device->output;
+ pte->device->output = output;
+}
+
+static void send_ring(struct unistimsession *pte, char volume, char style)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending ring packet\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_ring, sizeof(packet_send_ring));
+ buffsend[24] = style + 0x10;
+ buffsend[29] = volume * 0x10;
+ send_client(SIZE_HEADER + sizeof(packet_send_ring), buffsend, pte);
+}
+
+static void send_no_ring(struct unistimsession *pte)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending no ring packet\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_no_ring, sizeof(packet_send_no_ring));
+ send_client(SIZE_HEADER + sizeof(packet_send_no_ring), buffsend, pte);
+}
+
+static void send_texttitle(struct unistimsession *pte, const char *text)
+{
+ BUFFSEND;
+ int i;
+ if (unistimdebug)
+ ast_verbose("Sending title text\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_title, sizeof(packet_send_title));
+ i = strlen(text);
+ if (i > 12)
+ i = 12;
+ memcpy(buffsend + 10, text, i);
+ send_client(SIZE_HEADER + sizeof(packet_send_title), buffsend, pte);
+
+}
+
+static void send_date_time(struct unistimsession *pte)
+{
+ BUFFSEND;
+ struct timeval tv = ast_tvnow();
+ struct ast_tm atm = { 0, };
+
+ if (unistimdebug)
+ ast_verbose("Sending Time & Date\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_date_time, sizeof(packet_send_date_time));
+ ast_localtime(&tv, &atm, NULL);
+ buffsend[10] = (unsigned char) atm.tm_mon + 1;
+ buffsend[11] = (unsigned char) atm.tm_mday;
+ buffsend[12] = (unsigned char) atm.tm_hour;
+ buffsend[13] = (unsigned char) atm.tm_min;
+ send_client(SIZE_HEADER + sizeof(packet_send_date_time), buffsend, pte);
+}
+
+static void send_date_time2(struct unistimsession *pte)
+{
+ BUFFSEND;
+ struct timeval tv = ast_tvnow();
+ struct ast_tm atm = { 0, };
+
+ if (unistimdebug)
+ ast_verbose("Sending Time & Date #2\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_date_time2, sizeof(packet_send_date_time2));
+ ast_localtime(&tv, &atm, NULL);
+ if (pte->device)
+ buffsend[9] = pte->device->datetimeformat;
+ else
+ buffsend[9] = 61;
+ buffsend[14] = (unsigned char) atm.tm_mon + 1;
+ buffsend[15] = (unsigned char) atm.tm_mday;
+ buffsend[16] = (unsigned char) atm.tm_hour;
+ buffsend[17] = (unsigned char) atm.tm_min;
+ send_client(SIZE_HEADER + sizeof(packet_send_date_time2), buffsend, pte);
+}
+
+static void send_date_time3(struct unistimsession *pte)
+{
+ BUFFSEND;
+ struct timeval tv = ast_tvnow();
+ struct ast_tm atm = { 0, };
+
+ if (unistimdebug)
+ ast_verbose("Sending Time & Date #3\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_date_time3, sizeof(packet_send_date_time3));
+ ast_localtime(&tv, &atm, NULL);
+ buffsend[10] = (unsigned char) atm.tm_mon + 1;
+ buffsend[11] = (unsigned char) atm.tm_mday;
+ buffsend[12] = (unsigned char) atm.tm_hour;
+ buffsend[13] = (unsigned char) atm.tm_min;
+ send_client(SIZE_HEADER + sizeof(packet_send_date_time3), buffsend, pte);
+}
+
+static void send_blink_cursor(struct unistimsession *pte)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending set blink\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_blink_cursor, sizeof(packet_send_blink_cursor));
+ send_client(SIZE_HEADER + sizeof(packet_send_blink_cursor), buffsend, pte);
+ return;
+}
+
+/* pos : 0xab (a=0/2/4 = line ; b = row) */
+static void send_cursor_pos(struct unistimsession *pte, unsigned char pos)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending set cursor position\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_set_pos_cursor,
+ sizeof(packet_send_set_pos_cursor));
+ buffsend[11] = pos;
+ send_client(SIZE_HEADER + sizeof(packet_send_set_pos_cursor), buffsend, pte);
+ return;
+}
+
+static void rcv_resume_connection_with_server(struct unistimsession *pte)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("ResumeConnectionWithServer received\n");
+ if (unistimdebug)
+ ast_verbose("Sending packet_send_query_mac_address\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_query_mac_address,
+ sizeof(packet_send_query_mac_address));
+ send_client(SIZE_HEADER + sizeof(packet_send_query_mac_address), buffsend, pte);
+ return;
+}
+
+static int unistim_register(struct unistimsession *s)
+{
+ struct unistim_device *d;
+
+ ast_mutex_lock(&devicelock);
+ d = devices;
+ while (d) {
+ if (!strcasecmp(s->macaddr, d->id)) {
+ /* XXX Deal with IP authentication */
+ s->device = d;
+ d->session = s;
+ d->codec_number = DEFAULT_CODEC;
+ d->pos_fav = 0;
+ d->missed_call = 0;
+ d->receiver_state = STATE_ONHOOK;
+ break;
+ }
+ d = d->next;
+ }
+ ast_mutex_unlock(&devicelock);
+
+ if (!d)
+ return 0;
+
+ return 1;
+}
+
+static int alloc_sub(struct unistim_line *l, int x)
+{
+ struct unistim_subchannel *sub;
+ if (!(sub = ast_calloc(1, sizeof(*sub))))
+ return 0;
+
+ if (unistimdebug)
+ ast_verbose(VERBOSE_PREFIX_3
+ "Allocating UNISTIM subchannel #%d on %s@%s ptr=%p\n", x, l->name,
+ l->parent->name, sub);
+ sub->parent = l;
+ sub->subtype = x;
+ l->subs[x] = sub;
+ ast_mutex_init(&sub->lock);
+ return 1;
+}
+
+static int unalloc_sub(struct unistim_line *p, int x)
+{
+ if (!x) {
+ ast_log(LOG_WARNING, "Trying to unalloc the real channel %s@%s?!?\n", p->name,
+ p->parent->name);
+ return -1;
+ }
+ if (unistimdebug)
+ ast_debug(1, "Released sub %d of channel %s@%s\n", x, p->name,
+ p->parent->name);
+ ast_mutex_destroy(&p->lock);
+ ast_free(p->subs[x]);
+ p->subs[x] = 0;
+ return 0;
+}
+
+static void rcv_mac_addr(struct unistimsession *pte, const unsigned char *buf)
+{
+ BUFFSEND;
+ int tmp, i = 0;
+ char addrmac[19];
+ int res = 0;
+ if (unistimdebug)
+ ast_verbose("Mac Address received : ");
+ for (tmp = 15; tmp < 15 + SIZE_HEADER; tmp++) {
+ sprintf(&addrmac[i], "%.2x", (unsigned char) buf[tmp]);
+ i += 2;
+ }
+ if (unistimdebug)
+ ast_verbose("%s\n", addrmac);
+ strcpy(pte->macaddr, addrmac);
+ res = unistim_register(pte);
+ if (!res) {
+ switch (autoprovisioning) {
+ case AUTOPROVISIONING_NO:
+ ast_log(LOG_WARNING, "No entry found for this phone : %s\n", addrmac);
+ pte->state = STATE_AUTHDENY;
+ break;
+ case AUTOPROVISIONING_YES:
+ {
+ struct unistim_device *d, *newd;
+ struct unistim_line *newl;
+ if (unistimdebug)
+ ast_verbose("New phone, autoprovisioning on\n");
+ /* First : locate the [template] section */
+ ast_mutex_lock(&devicelock);
+ d = devices;
+ while (d) {
+ if (!strcasecmp(d->name, "template")) {
+ /* Found, cloning this entry */
+ if (!(newd = ast_malloc(sizeof(*newd)))) {
+ ast_mutex_unlock(&devicelock);
+ return;
+ }
+
+ memcpy(newd, d, sizeof(*newd));
+ if (!(newl = ast_malloc(sizeof(*newl)))) {
+ ast_free(newd);
+ ast_mutex_unlock(&devicelock);
+ return;
+ }
+
+ memcpy(newl, d->lines, sizeof(*newl));
+ if (!alloc_sub(newl, SUB_REAL)) {
+ ast_free(newd);
+ ast_free(newl);
+ ast_mutex_unlock(&devicelock);
+ return;
+ }
+ /* Ok, now updating some fields */
+ ast_copy_string(newd->id, addrmac, sizeof(newd->id));
+ ast_copy_string(newd->name, addrmac, sizeof(newd->name));
+ if (newd->extension == EXTENSION_NONE)
+ newd->extension = EXTENSION_ASK;
+ newd->lines = newl;
+ newd->receiver_state = STATE_ONHOOK;
+ newd->session = pte;
+ newd->to_delete = -1;
+ pte->device = newd;
+ newd->next = NULL;
+ newl->parent = newd;
+ strcpy(newl->name, d->lines->name);
+ snprintf(d->lines->name, sizeof(d->lines->name), "%d",
+ atoi(d->lines->name) + 1);
+ snprintf(newl->fullname, sizeof(newl->fullname), "USTM/%s@%s",
+ newl->name, newd->name);
+ /* Go to the end of the linked chain */
+ while (d->next) {
+ d = d->next;
+ }
+ d->next = newd;
+ d = newd;
+ break;
+ }
+ d = d->next;
+ }
+ ast_mutex_unlock(&devicelock);
+ if (!d) {
+ ast_log(LOG_WARNING, "No entry [template] found in unistim.conf\n");
+ pte->state = STATE_AUTHDENY;
+ }
+ }
+ break;
+ case AUTOPROVISIONING_TN:
+ pte->state = STATE_AUTHDENY;
+ break;
+ case AUTOPROVISIONING_DB:
+ ast_log(LOG_WARNING,
+ "Autoprovisioning with database is not yet functional\n");
+ break;
+ default:
+ ast_log(LOG_WARNING, "Internal error : unknown autoprovisioning value = %d\n",
+ autoprovisioning);
+ }
+ }
+ if (pte->state != STATE_AUTHDENY) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Device '%s' successfuly registered\n",
+ pte->device->name);
+ switch (pte->device->extension) {
+ case EXTENSION_NONE:
+ pte->state = STATE_MAINPAGE;
+ break;
+ case EXTENSION_ASK:
+ /* Checking if we already have an extension number */
+ if (ast_strlen_zero(pte->device->extension_number))
+ pte->state = STATE_EXTENSION;
+ else {
+ /* Yes, because of a phone reboot. We don't ask again for the TN */
+ if (RegisterExtension(pte))
+ pte->state = STATE_EXTENSION;
+ else
+ pte->state = STATE_MAINPAGE;
+ }
+ break;
+ case EXTENSION_LINE:
+ ast_copy_string(pte->device->extension_number, pte->device->lines->name,
+ sizeof(pte->device->extension_number));
+ if (RegisterExtension(pte))
+ pte->state = STATE_EXTENSION;
+ else
+ pte->state = STATE_MAINPAGE;
+ break;
+ case EXTENSION_TN:
+ /* If we are here, it's because of a phone reboot */
+ pte->state = STATE_MAINPAGE;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Internal error, extension value unknown : %d\n",
+ pte->device->extension);
+ pte->state = STATE_AUTHDENY;
+ break;
+ }
+ }
+ if (pte->state == STATE_EXTENSION) {
+ if (pte->device->extension != EXTENSION_TN)
+ pte->device->extension = EXTENSION_ASK;
+ pte->device->extension_number[0] = '\0';
+ }
+ if (unistimdebug)
+ ast_verbose("\nSending S1\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_S1, sizeof(packet_send_S1));
+ send_client(SIZE_HEADER + sizeof(packet_send_S1), buffsend, pte);
+
+ if (unistimdebug)
+ ast_verbose("Sending query_basic_manager_04\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_query_basic_manager_04,
+ sizeof(packet_send_query_basic_manager_04));
+ send_client(SIZE_HEADER + sizeof(packet_send_query_basic_manager_04), buffsend, pte);
+
+ if (unistimdebug)
+ ast_verbose("Sending query_basic_manager_10\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_query_basic_manager_10,
+ sizeof(packet_send_query_basic_manager_10));
+ send_client(SIZE_HEADER + sizeof(packet_send_query_basic_manager_10), buffsend, pte);
+
+ send_date_time(pte);
+ return;
+}
+
+static int write_entry_history(struct unistimsession *pte, FILE * f, char c, char *line1)
+{
+ if (fwrite(&c, 1, 1, f) != 1) {
+ display_last_error("Unable to write history log header.");
+ return -1;
+ }
+ if (fwrite(line1, TEXT_LENGTH_MAX, 1, f) != 1) {
+ display_last_error("Unable to write history entry - date.");
+ return -1;
+ }
+ if (fwrite(pte->device->lst_cid, TEXT_LENGTH_MAX, 1, f) != 1) {
+ display_last_error("Unable to write history entry - callerid.");
+ return -1;
+ }
+ if (fwrite(pte->device->lst_cnm, TEXT_LENGTH_MAX, 1, f) != 1) {
+ display_last_error("Unable to write history entry - callername.");
+ return -1;
+ }
+ return 0;
+}
+
+static int write_history(struct unistimsession *pte, char way, char ismissed)
+{
+ char tmp[AST_CONFIG_MAX_PATH], tmp2[AST_CONFIG_MAX_PATH];
+ char line1[TEXT_LENGTH_MAX + 1];
+ char count = 0, *histbuf;
+ int size;
+ FILE *f, *f2;
+ struct timeval tv = ast_tvnow();
+ struct ast_tm atm = { 0, };
+
+ if (!pte->device)
+ return -1;
+ if (!pte->device->callhistory)
+ return 0;
+ if (strchr(pte->device->name, '/') || (pte->device->name[0] == '.')) {
+ ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n",
+ pte->device->name);
+ return -1;
+ }
+
+ snprintf(tmp, sizeof(tmp), "%s/%s", ast_config_AST_LOG_DIR, USTM_LOG_DIR);
+ if (ast_mkdir(tmp, 0770)) {
+ if (errno != EEXIST) {
+ display_last_error("Unable to create directory for history");
+ return -1;
+ }
+ }
+
+ ast_localtime(&tv, &atm, NULL);
+ if (ismissed) {
+ if (way == 'i')
+ strcpy(tmp2, "Miss");
+ else
+ strcpy(tmp2, "Fail");
+ } else
+ strcpy(tmp2, "Answ");
+ snprintf(line1, sizeof(line1), "%04d/%02d/%02d %02d:%02d:%02d %s",
+ atm.tm_year + 1900, atm.tm_mon + 1, atm.tm_mday, atm.tm_hour,
+ atm.tm_min, atm.tm_sec, tmp2);
+
+ snprintf(tmp, sizeof(tmp), "%s/%s/%s-%c.csv", ast_config_AST_LOG_DIR,
+ USTM_LOG_DIR, pte->device->name, way);
+ if ((f = fopen(tmp, "r"))) {
+ struct stat bufstat;
+
+ if (stat(tmp, &bufstat)) {
+ display_last_error("Unable to stat history log.");
+ fclose(f);
+ return -1;
+ }
+ size = 1 + (MAX_ENTRY_LOG * TEXT_LENGTH_MAX * 3);
+ if (bufstat.st_size != size) {
+ ast_log(LOG_WARNING,
+ "History file %s has an incorrect size (%d instead of %d). It will be replaced by a new one.",
+ tmp, (int) bufstat.st_size, size);
+ fclose(f);
+ f = NULL;
+ count = 1;
+ }
+ }
+
+ /* If we can't open the log file, we create a brand new one */
+ if (!f) {
+ char c = 1;
+ int i;
+
+ if ((errno != ENOENT) && (count == 0)) {
+ display_last_error("Unable to open history log.");
+ return -1;
+ }
+ f = fopen(tmp, "w");
+ if (!f) {
+ display_last_error("Unable to create history log.");
+ return -1;
+ }
+ if (write_entry_history(pte, f, c, line1)) {
+ fclose(f);
+ return -1;
+ }
+ memset(line1, ' ', TEXT_LENGTH_MAX);
+ for (i = 3; i < MAX_ENTRY_LOG * 3; i++) {
+ if (fwrite(line1, TEXT_LENGTH_MAX, 1, f) != 1) {
+ display_last_error("Unable to write history entry - stuffing.");
+ fclose(f);
+ return -1;
+ }
+ }
+ if (fclose(f))
+ display_last_error("Unable to close history - creation.");
+ return 0;
+ }
+ /* We can open the log file, we create a temporary one, we add our entry and copy the rest */
+ if (fread(&count, 1, 1, f) != 1) {
+ display_last_error("Unable to read history header.");
+ fclose(f);
+ return -1;
+ }
+ if (count > MAX_ENTRY_LOG) {
+ ast_log(LOG_WARNING, "Invalid count in history header of %s (%d max %d)\n", tmp,
+ count, MAX_ENTRY_LOG);
+ fclose(f);
+ return -1;
+ }
+ snprintf(tmp2, sizeof(tmp2), "%s/%s/%s-%c.csv.tmp", ast_config_AST_LOG_DIR,
+ USTM_LOG_DIR, pte->device->name, way);
+ if (!(f2 = fopen(tmp2, "w"))) {
+ display_last_error("Unable to create temporary history log.");
+ fclose(f);
+ return -1;
+ }
+
+ if (++count > MAX_ENTRY_LOG)
+ count = MAX_ENTRY_LOG;
+
+ if (write_entry_history(pte, f2, count, line1)) {
+ fclose(f);
+ fclose(f2);
+ return -1;
+ }
+
+ size = (MAX_ENTRY_LOG - 1) * TEXT_LENGTH_MAX * 3;
+ if (!(histbuf = ast_malloc(size))) {
+ fclose(f);
+ fclose(f2);
+ return -1;
+ }
+
+ if (fread(histbuf, size, 1, f) != 1) {
+ ast_free(histbuf);
+ fclose(f);
+ fclose(f2);
+ display_last_error("Unable to read previous history entries.");
+ return -1;
+ }
+ if (fwrite(histbuf, size, 1, f2) != 1) {
+ ast_free(histbuf);
+ fclose(f);
+ fclose(f2);
+ display_last_error("Unable to write previous history entries.");
+ return -1;
+ }
+ ast_free(histbuf);
+ if (fclose(f))
+ display_last_error("Unable to close history log.");
+ if (fclose(f2))
+ display_last_error("Unable to close temporary history log.");
+ if (unlink(tmp))
+ display_last_error("Unable to remove old history log.");
+ if (rename(tmp2, tmp))
+ display_last_error("Unable to rename new history log.");
+ return 0;
+}
+
+static void cancel_dial(struct unistimsession *pte)
+{
+ send_no_ring(pte);
+ pte->device->missed_call++;
+ write_history(pte, 'i', 1);
+ show_main_page(pte);
+ return;
+}
+
+static void swap_subs(struct unistim_line *p, int a, int b)
+{
+/* struct ast_channel *towner; */
+ struct ast_rtp *rtp;
+ int fds;
+
+ if (unistimdebug)
+ ast_verbose("Swapping %d and %d\n", a, b);
+
+ if ((!p->subs[a]->owner) || (!p->subs[b]->owner)) {
+ ast_log(LOG_WARNING,
+ "Attempted to swap subchannels with a null owner : sub #%d=%p sub #%d=%p\n",
+ a, p->subs[a]->owner, b, p->subs[b]->owner);
+ return;
+ }
+ rtp = p->subs[a]->rtp;
+ p->subs[a]->rtp = p->subs[b]->rtp;
+ p->subs[b]->rtp = rtp;
+
+ fds = p->subs[a]->owner->fds[0];
+ p->subs[a]->owner->fds[0] = p->subs[b]->owner->fds[0];
+ p->subs[b]->owner->fds[0] = fds;
+
+ fds = p->subs[a]->owner->fds[1];
+ p->subs[a]->owner->fds[1] = p->subs[b]->owner->fds[1];
+ p->subs[b]->owner->fds[1] = fds;
+}
+
+static int attempt_transfer(struct unistim_subchannel *p1, struct unistim_subchannel *p2)
+{
+ int res = 0;
+ struct ast_channel
+ *chana = NULL, *chanb = NULL, *bridgea = NULL, *bridgeb = NULL, *peera =
+ NULL, *peerb = NULL, *peerc = NULL, *peerd = NULL;
+
+ if (!p1->owner || !p2->owner) {
+ ast_log(LOG_WARNING, "Transfer attempted without dual ownership?\n");
+ return -1;
+ }
+ chana = p1->owner;
+ chanb = p2->owner;
+ bridgea = ast_bridged_channel(chana);
+ bridgeb = ast_bridged_channel(chanb);
+
+ if (bridgea) {
+ peera = chana;
+ peerb = chanb;
+ peerc = bridgea;
+ peerd = bridgeb;
+ } else if (bridgeb) {
+ peera = chanb;
+ peerb = chana;
+ peerc = bridgeb;
+ peerd = bridgea;
+ }
+
+ if (peera && peerb && peerc && (peerb != peerc)) {
+ /*ast_quiet_chan(peera);
+ ast_quiet_chan(peerb);
+ ast_quiet_chan(peerc);
+ ast_quiet_chan(peerd); */
+
+ if (peera->cdr && peerb->cdr) {
+ peerb->cdr = ast_cdr_append(peerb->cdr, peera->cdr);
+ } else if (peera->cdr) {
+ peerb->cdr = peera->cdr;
+ }
+ peera->cdr = NULL;
+
+ if (peerb->cdr && peerc->cdr) {
+ peerb->cdr = ast_cdr_append(peerb->cdr, peerc->cdr);
+ } else if (peerc->cdr) {
+ peerb->cdr = peerc->cdr;
+ }
+ peerc->cdr = NULL;
+
+ if (ast_channel_masquerade(peerb, peerc)) {
+ ast_log(LOG_WARNING, "Failed to masquerade %s into %s\n", peerb->name,
+ peerc->name);
+ res = -1;
+ }
+ return res;
+ } else {
+ ast_log(LOG_NOTICE,
+ "Transfer attempted with no appropriate bridged calls to transfer\n");
+ if (chana)
+ ast_softhangup_nolock(chana, AST_SOFTHANGUP_DEV);
+ if (chanb)
+ ast_softhangup_nolock(chanb, AST_SOFTHANGUP_DEV);
+ return -1;
+ }
+ return 0;
+}
+
+void change_callerid(struct unistimsession *pte, int type, char *callerid)
+{
+ char *data;
+ int size;
+
+ if (type)
+ data = pte->device->lst_cnm;
+ else
+ data = pte->device->lst_cid;
+
+ /* This is very nearly strncpy(), except that the remaining buffer
+ * is padded with ' ', instead of '\0' */
+ memset(data, ' ', TEXT_LENGTH_MAX);
+ size = strlen(callerid);
+ if (size > TEXT_LENGTH_MAX)
+ size = TEXT_LENGTH_MAX;
+ memcpy(data, callerid, size);
+}
+
+static void close_call(struct unistimsession *pte)
+{
+ struct unistim_subchannel *sub;
+ struct unistim_line *l = pte->device->lines;
+
+ sub = pte->device->lines->subs[SUB_REAL];
+ send_stop_timer(pte);
+ if (sub->owner) {
+ sub->alreadygone = 1;
+ if (l->subs[SUB_THREEWAY]) {
+ l->subs[SUB_THREEWAY]->alreadygone = 1;
+ if (attempt_transfer(sub, l->subs[SUB_THREEWAY]) < 0)
+ ast_verbose("attempt_transfer failed.\n");
+ } else
+ ast_queue_hangup(sub->owner);
+ } else {
+ if (l->subs[SUB_THREEWAY]) {
+ if (l->subs[SUB_THREEWAY]->owner)
+ ast_queue_hangup(l->subs[SUB_THREEWAY]->owner);
+ else
+ ast_log(LOG_WARNING, "threeway sub without owner\n");
+ } else
+ ast_verbose("USTM(%s@%s-%d) channel already destroyed\n", sub->parent->name,
+ sub->parent->parent->name, sub->subtype);
+ }
+ change_callerid(pte, 0, pte->device->redial_number);
+ change_callerid(pte, 1, "");
+ write_history(pte, 'o', pte->device->missed_call);
+ pte->device->missed_call = 0;
+ show_main_page(pte);
+ return;
+}
+
+static void IgnoreCall(struct unistimsession *pte)
+{
+ send_no_ring(pte);
+ return;
+}
+
+static void *unistim_ss(void *data)
+{
+ struct ast_channel *chan = data;
+ struct unistim_subchannel *sub = chan->tech_pvt;
+ struct unistim_line *l = sub->parent;
+ struct unistimsession *s = l->parent->session;
+ int res;
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Starting switch on '%s@%s-%d' to %s\n",
+ l->name, l->parent->name, sub->subtype, s->device->phone_number);
+ ast_copy_string(chan->exten, s->device->phone_number, sizeof(chan->exten));
+ ast_copy_string(s->device->redial_number, s->device->phone_number,
+ sizeof(s->device->redial_number));
+ ast_setstate(chan, AST_STATE_RING);
+ res = ast_pbx_run(chan);
+ if (res) {
+ ast_log(LOG_WARNING, "PBX exited non-zero\n");
+ send_tone(s, 1000, 0);;
+ }
+ return NULL;
+}
+
+static void start_rtp(struct unistim_subchannel *sub)
+{
+ BUFFSEND;
+ struct sockaddr_in us;
+ struct sockaddr_in public;
+ struct sockaddr_in sin;
+ int codec;
+ struct sockaddr_in sout;
+
+ /* Sanity checks */
+ if (!sub) {
+ ast_log(LOG_WARNING, "start_rtp with a null subchannel !\n");
+ return;
+ }
+ if (!sub->parent) {
+ ast_log(LOG_WARNING, "start_rtp with a null line !\n");
+ return;
+ }
+ if (!sub->parent->parent) {
+ ast_log(LOG_WARNING, "start_rtp with a null device !\n");
+ return;
+ }
+ if (!sub->parent->parent->session) {
+ ast_log(LOG_WARNING, "start_rtp with a null session !\n");
+ return;
+ }
+ sout = sub->parent->parent->session->sout;
+
+ ast_mutex_lock(&sub->lock);
+ /* Allocate the RTP */
+ if (unistimdebug)
+ ast_verbose("Starting RTP. Bind on %s\n", ast_inet_ntoa(sout.sin_addr));
+ sub->rtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, sout.sin_addr);
+ if (!sub->rtp) {
+ ast_log(LOG_WARNING, "Unable to create RTP session: %s binaddr=%s\n",
+ strerror(errno), ast_inet_ntoa(sout.sin_addr));
+ ast_mutex_unlock(&sub->lock);
+ return;
+ }
+ if (sub->rtp && sub->owner) {
+ sub->owner->fds[0] = ast_rtp_fd(sub->rtp);
+ sub->owner->fds[1] = ast_rtcp_fd(sub->rtp);
+ }
+ if (sub->rtp) {
+ ast_rtp_setqos(sub->rtp, tos_audio, cos_audio, "UNISTIM RTP");
+ ast_rtp_setnat(sub->rtp, sub->parent->parent->nat);
+ }
+
+ /* Create the RTP connection */
+ ast_rtp_get_us(sub->rtp, &us);
+ sin.sin_family = AF_INET;
+ /* Setting up RTP for our side */
+ memcpy(&sin.sin_addr, &sub->parent->parent->session->sin.sin_addr,
+ sizeof(sin.sin_addr));
+ sin.sin_port = htons(sub->parent->parent->rtp_port);
+ ast_rtp_set_peer(sub->rtp, &sin);
+ if (!(sub->owner->nativeformats & sub->owner->readformat)) {
+ int fmt;
+ fmt = ast_best_codec(sub->owner->nativeformats);
+ ast_log(LOG_WARNING,
+ "Our read/writeformat has been changed to something incompatible : %s (%d), using %s (%d) best codec from %d\n",
+ ast_getformatname(sub->owner->readformat),
+ sub->owner->readformat, ast_getformatname(fmt), fmt,
+ sub->owner->nativeformats);
+ sub->owner->readformat = fmt;
+ sub->owner->writeformat = fmt;
+ }
+ codec = ast_rtp_lookup_code(sub->rtp, 1, sub->owner->readformat);
+ /* Setting up RTP of the phone */
+ if (public_ip.sin_family == 0) /* NAT IP override ? */
+ memcpy(&public, &us, sizeof(public)); /* No defined, using IP from recvmsg */
+ else
+ memcpy(&public, &public_ip, sizeof(public)); /* override */
+ if (unistimdebug) {
+ ast_verbose
+ ("RTP started : Our IP/port is : %s:%hd with codec %s (%d)\n",
+ ast_inet_ntoa(us.sin_addr),
+ htons(us.sin_port), ast_getformatname(sub->owner->readformat),
+ sub->owner->readformat);
+ ast_verbose("Starting phone RTP stack. Our public IP is %s\n",
+ ast_inet_ntoa(public.sin_addr));
+ }
+ if ((sub->owner->readformat == AST_FORMAT_ULAW) ||
+ (sub->owner->readformat == AST_FORMAT_ALAW)) {
+ if (unistimdebug)
+ ast_verbose("Sending packet_send_rtp_packet_size for codec %d\n", codec);
+ memcpy(buffsend + SIZE_HEADER, packet_send_rtp_packet_size,
+ sizeof(packet_send_rtp_packet_size));
+ buffsend[10] = codec;
+ send_client(SIZE_HEADER + sizeof(packet_send_rtp_packet_size), buffsend,
+ sub->parent->parent->session);
+ }
+ if (unistimdebug)
+ ast_verbose("Sending Jitter Buffer Parameters Configuration\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_jitter_buffer_conf,
+ sizeof(packet_send_jitter_buffer_conf));
+ send_client(SIZE_HEADER + sizeof(packet_send_jitter_buffer_conf), buffsend,
+ sub->parent->parent->session);
+ if (sub->parent->parent->rtp_method != 0) {
+ uint16_t rtcpsin_port = htons(us.sin_port) + 1; /* RTCP port is RTP + 1 */
+
+ if (unistimdebug)
+ ast_verbose("Sending OpenAudioStreamTX using method #%d\n",
+ sub->parent->parent->rtp_method);
+ if (sub->parent->parent->rtp_method == 3)
+ memcpy(buffsend + SIZE_HEADER, packet_send_open_audio_stream_tx3,
+ sizeof(packet_send_open_audio_stream_tx3));
+ else
+ memcpy(buffsend + SIZE_HEADER, packet_send_open_audio_stream_tx,
+ sizeof(packet_send_open_audio_stream_tx));
+ if (sub->parent->parent->rtp_method != 2) {
+ memcpy(buffsend + 28, &public.sin_addr, sizeof(public.sin_addr));
+ buffsend[20] = (htons(sin.sin_port) & 0xff00) >> 8;
+ buffsend[21] = (htons(sin.sin_port) & 0x00ff);
+ buffsend[23] = (rtcpsin_port & 0x00ff);
+ buffsend[22] = (rtcpsin_port & 0xff00) >> 8;
+ buffsend[25] = (us.sin_port & 0xff00) >> 8;
+ buffsend[24] = (us.sin_port & 0x00ff);
+ buffsend[27] = (rtcpsin_port & 0x00ff);
+ buffsend[26] = (rtcpsin_port & 0xff00) >> 8;
+ } else {
+ memcpy(buffsend + 23, &public.sin_addr, sizeof(public.sin_addr));
+ buffsend[15] = (htons(sin.sin_port) & 0xff00) >> 8;
+ buffsend[16] = (htons(sin.sin_port) & 0x00ff);
+ buffsend[20] = (us.sin_port & 0xff00) >> 8;
+ buffsend[19] = (us.sin_port & 0x00ff);
+ buffsend[11] = codec;
+ }
+ buffsend[12] = codec;
+ send_client(SIZE_HEADER + sizeof(packet_send_open_audio_stream_tx), buffsend,
+ sub->parent->parent->session);
+
+ if (unistimdebug)
+ ast_verbose("Sending OpenAudioStreamRX\n");
+ if (sub->parent->parent->rtp_method == 3)
+ memcpy(buffsend + SIZE_HEADER, packet_send_open_audio_stream_rx3,
+ sizeof(packet_send_open_audio_stream_rx3));
+ else
+ memcpy(buffsend + SIZE_HEADER, packet_send_open_audio_stream_rx,
+ sizeof(packet_send_open_audio_stream_rx));
+ if (sub->parent->parent->rtp_method != 2) {
+ memcpy(buffsend + 28, &public.sin_addr, sizeof(public.sin_addr));
+ buffsend[20] = (htons(sin.sin_port) & 0xff00) >> 8;
+ buffsend[21] = (htons(sin.sin_port) & 0x00ff);
+ buffsend[23] = (rtcpsin_port & 0x00ff);
+ buffsend[22] = (rtcpsin_port & 0xff00) >> 8;
+ buffsend[25] = (us.sin_port & 0xff00) >> 8;
+ buffsend[24] = (us.sin_port & 0x00ff);
+ buffsend[27] = (rtcpsin_port & 0x00ff);
+ buffsend[26] = (rtcpsin_port & 0xff00) >> 8;
+ } else {
+ memcpy(buffsend + 23, &public.sin_addr, sizeof(public.sin_addr));
+ buffsend[15] = (htons(sin.sin_port) & 0xff00) >> 8;
+ buffsend[16] = (htons(sin.sin_port) & 0x00ff);
+ buffsend[20] = (us.sin_port & 0xff00) >> 8;
+ buffsend[19] = (us.sin_port & 0x00ff);
+ buffsend[12] = codec;
+ }
+ buffsend[11] = codec;
+ send_client(SIZE_HEADER + sizeof(packet_send_open_audio_stream_rx), buffsend,
+ sub->parent->parent->session);
+ } else {
+ uint16_t rtcpsin_port = htons(us.sin_port) + 1; /* RTCP port is RTP + 1 */
+
+ if (unistimdebug)
+ ast_verbose("Sending packet_send_call default method\n");
+
+ memcpy(buffsend + SIZE_HEADER, packet_send_call, sizeof(packet_send_call));
+ memcpy(buffsend + 53, &public.sin_addr, sizeof(public.sin_addr));
+ /* Destination port when sending RTP */
+ buffsend[49] = (us.sin_port & 0x00ff);
+ buffsend[50] = (us.sin_port & 0xff00) >> 8;
+ /* Destination port when sending RTCP */
+ buffsend[52] = (rtcpsin_port & 0x00ff);
+ buffsend[51] = (rtcpsin_port & 0xff00) >> 8;
+ /* Codec */
+ buffsend[40] = codec;
+ buffsend[41] = codec;
+ if (sub->owner->readformat == AST_FORMAT_ULAW)
+ buffsend[42] = 1; /* 1 = 20ms (160 bytes), 2 = 40ms (320 bytes) */
+ else if (sub->owner->readformat == AST_FORMAT_ALAW)
+ buffsend[42] = 1; /* 1 = 20ms (160 bytes), 2 = 40ms (320 bytes) */
+ else if (sub->owner->readformat == AST_FORMAT_G723_1)
+ buffsend[42] = 2; /* 1 = 30ms (24 bytes), 2 = 60 ms (48 bytes) */
+ else if (sub->owner->readformat == AST_FORMAT_G729A)
+ buffsend[42] = 2; /* 1 = 10ms (10 bytes), 2 = 20ms (20 bytes) */
+ else
+ ast_log(LOG_WARNING, "Unsupported codec %s (%d) !\n",
+ ast_getformatname(sub->owner->readformat), sub->owner->readformat);
+ /* Source port for transmit RTP and Destination port for receiving RTP */
+ buffsend[45] = (htons(sin.sin_port) & 0xff00) >> 8;
+ buffsend[46] = (htons(sin.sin_port) & 0x00ff);
+ buffsend[47] = (rtcpsin_port & 0xff00) >> 8;
+ buffsend[48] = (rtcpsin_port & 0x00ff);
+ send_client(SIZE_HEADER + sizeof(packet_send_call), buffsend,
+ sub->parent->parent->session);
+ }
+ ast_mutex_unlock(&sub->lock);
+}
+
+static void SendDialTone(struct unistimsession *pte)
+{
+ int i;
+ /* No country defined ? Using US tone */
+ if (ast_strlen_zero(pte->device->country)) {
+ if (unistimdebug)
+ ast_verbose("No country defined, using US tone\n");
+ send_tone(pte, 350, 440);
+ return;
+ }
+ if (strlen(pte->device->country) != 2) {
+ if (unistimdebug)
+ ast_verbose("Country code != 2 char, using US tone\n");
+ send_tone(pte, 350, 440);
+ return;
+ }
+ i = 0;
+ while (frequency[i].freq1) {
+ if ((frequency[i].country[0] == pte->device->country[0]) &&
+ (frequency[i].country[1] == pte->device->country[1])) {
+ if (unistimdebug)
+ ast_verbose("Country code found (%s), freq1=%d freq2=%d\n",
+ frequency[i].country, frequency[i].freq1, frequency[i].freq2);
+ send_tone(pte, frequency[i].freq1, frequency[i].freq2);
+ }
+ i++;
+ }
+}
+
+static void handle_dial_page(struct unistimsession *pte)
+{
+ pte->state = STATE_DIALPAGE;
+ if (pte->device->call_forward[0] == -1) {
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, "");
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, "Enter forward");
+ send_text_status(pte, "ForwardCancel BackSpcErase");
+ if (pte->device->call_forward[1] != 0) {
+ char tmp[TEXT_LENGTH_MAX + 1];
+
+ ast_copy_string(pte->device->phone_number, pte->device->call_forward + 1,
+ sizeof(pte->device->phone_number));
+ pte->device->size_phone_number = strlen(pte->device->phone_number);
+ if (pte->device->size_phone_number > 15)
+ pte->device->size_phone_number = 15;
+ strcpy(tmp, "Number : ...............");
+ memcpy(tmp + 9, pte->device->phone_number, pte->device->size_phone_number);
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmp);
+ send_blink_cursor(pte);
+ send_cursor_pos(pte,
+ (unsigned char) (TEXT_LINE2 + 0x09 +
+ pte->device->size_phone_number));
+ send_led_update(pte, 0);
+ return;
+ }
+ } else {
+ if ((pte->device->output == OUTPUT_HANDSET) &&
+ (pte->device->receiver_state == STATE_ONHOOK))
+ send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF);
+ else
+ send_select_output(pte, pte->device->output, pte->device->volume, MUTE_OFF);
+ SendDialTone(pte);
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, "Enter the number to dial");
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, "and press Call");
+ send_text_status(pte, "Call Redial BackSpcErase");
+ }
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, "Number : ...............");
+ send_blink_cursor(pte);
+ send_cursor_pos(pte, TEXT_LINE2 + 0x09);
+ pte->device->size_phone_number = 0;
+ pte->device->phone_number[0] = 0;
+ change_favorite_icon(pte, FAV_ICON_PHONE_BLACK);
+ Sendicon(TEXT_LINE0, FAV_ICON_NONE, pte);
+ pte->device->missed_call = 0;
+ send_led_update(pte, 0);
+ return;
+}
+
+/* Step 1 : Music On Hold for peer, Dialing screen for us */
+static void TransferCallStep1(struct unistimsession *pte)
+{
+ struct unistim_subchannel *sub;
+ struct unistim_line *p = pte->device->lines;
+
+ sub = p->subs[SUB_REAL];
+
+ if (!sub->owner) {
+ ast_log(LOG_WARNING, "Unable to find subchannel for music on hold\n");
+ return;
+ }
+ if (p->subs[SUB_THREEWAY]) {
+ if (unistimdebug)
+ ast_verbose("Transfer canceled, hangup our threeway channel\n");
+ if (p->subs[SUB_THREEWAY]->owner)
+ ast_queue_hangup(p->subs[SUB_THREEWAY]->owner);
+ else
+ ast_log(LOG_WARNING, "Canceling a threeway channel without owner\n");
+ return;
+ }
+ /* Start music on hold if appropriate */
+ if (pte->device->moh)
+ ast_log(LOG_WARNING, "Transfer with peer already listening music on hold\n");
+ else {
+ if (ast_bridged_channel(p->subs[SUB_REAL]->owner)) {
+ ast_moh_start(ast_bridged_channel(p->subs[SUB_REAL]->owner),
+ pte->device->lines->musicclass, NULL);
+ pte->device->moh = 1;
+ } else {
+ ast_log(LOG_WARNING, "Unable to find peer subchannel for music on hold\n");
+ return;
+ }
+ }
+ /* Silence our channel */
+ if (!pte->device->silence_generator) {
+ pte->device->silence_generator =
+ ast_channel_start_silence_generator(p->subs[SUB_REAL]->owner);
+ if (pte->device->silence_generator == NULL)
+ ast_log(LOG_WARNING, "Unable to start a silence generator.\n");
+ else if (unistimdebug)
+ ast_verbose("Starting silence generator\n");
+ }
+ handle_dial_page(pte);
+}
+
+/* From phone to PBX */
+static void HandleCallOutgoing(struct unistimsession *s)
+{
+ struct ast_channel *c;
+ struct unistim_subchannel *sub;
+ pthread_t t;
+ s->state = STATE_CALL;
+ sub = s->device->lines->subs[SUB_REAL];
+ if (!sub) {
+ ast_log(LOG_NOTICE, "No available lines on: %s\n", s->device->name);
+ return;
+ }
+ if (!sub->owner) { /* A call is already in progress ? */
+ c = unistim_new(sub, AST_STATE_DOWN); /* No, starting a new one */
+ if (c) {
+ /* Need to start RTP before calling ast_pbx_run */
+ if (!sub->rtp)
+ start_rtp(sub);
+ send_select_output(s, s->device->output, s->device->volume, MUTE_OFF);
+ send_text(TEXT_LINE0, TEXT_NORMAL, s, "Calling :");
+ send_text(TEXT_LINE1, TEXT_NORMAL, s, s->device->phone_number);
+ send_text(TEXT_LINE2, TEXT_NORMAL, s, "Dialing...");
+ send_text_status(s, "Hangup");
+ /* start switch */
+ if (ast_pthread_create(&t, NULL, unistim_ss, c)) {
+ display_last_error("Unable to create switch thread");
+ ast_queue_hangup(c);
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n",
+ sub->parent->name, s->device->name);
+ } else { /* We already have a call, so we switch in a threeway call */
+
+ if (s->device->moh) {
+ struct unistim_subchannel *sub;
+ struct unistim_line *p = s->device->lines;
+ sub = p->subs[SUB_REAL];
+
+ if (!sub->owner) {
+ ast_log(LOG_WARNING, "Unable to find subchannel for music on hold\n");
+ return;
+ }
+ if (p->subs[SUB_THREEWAY]) {
+ ast_log(LOG_WARNING,
+ "Can't transfer while an another transfer is taking place\n");
+ return;
+ }
+ if (!alloc_sub(p, SUB_THREEWAY)) {
+ ast_log(LOG_WARNING, "Unable to allocate three-way subchannel\n");
+ return;
+ }
+ /* Stop the silence generator */
+ if (s->device->silence_generator) {
+ if (unistimdebug)
+ ast_verbose("Stopping silence generator\n");
+ ast_channel_stop_silence_generator(sub->owner,
+ s->device->silence_generator);
+ s->device->silence_generator = NULL;
+ }
+ send_tone(s, 0, 0);
+ /* Make new channel */
+ c = unistim_new(p->subs[SUB_THREEWAY], AST_STATE_DOWN);
+ if (!c) {
+ ast_log(LOG_WARNING, "Cannot allocate new structure on channel %p\n", p);
+ return;
+ }
+ /* Swap things around between the three-way and real call */
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ send_select_output(s, s->device->output, s->device->volume, MUTE_OFF);
+ send_text(TEXT_LINE0, TEXT_NORMAL, s, "Calling (pre-transfer)");
+ send_text(TEXT_LINE1, TEXT_NORMAL, s, s->device->phone_number);
+ send_text(TEXT_LINE2, TEXT_NORMAL, s, "Dialing...");
+ send_text_status(s, "TransfrCancel");
+
+ if (ast_pthread_create(&t, NULL, unistim_ss, p->subs[SUB_THREEWAY]->owner)) {
+ ast_log(LOG_WARNING, "Unable to start simple switch on channel %p\n", p);
+ ast_hangup(c);
+ return;
+ }
+ if (unistimdebug)
+ ast_verbose
+ ("Started three way call on channel %p (%s) subchan %d\n",
+ p->subs[SUB_THREEWAY]->owner, p->subs[SUB_THREEWAY]->owner->name,
+ p->subs[SUB_THREEWAY]->subtype);
+ } else
+ ast_debug(1, "Current sub [%s] already has owner\n", sub->owner->name);
+ }
+ return;
+}
+
+/* From PBX to phone */
+static void HandleCallIncoming(struct unistimsession *s)
+{
+ struct unistim_subchannel *sub;
+ s->state = STATE_CALL;
+ s->device->missed_call = 0;
+ send_no_ring(s);
+ sub = s->device->lines->subs[SUB_REAL];
+ if (!sub) {
+ ast_log(LOG_NOTICE, "No available lines on: %s\n", s->device->name);
+ return;
+ } else if (unistimdebug)
+ ast_verbose("Handle Call Incoming for %s@%s\n", sub->parent->name,
+ s->device->name);
+ start_rtp(sub);
+ if (!sub->rtp)
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", sub->parent->name,
+ s->device->name);
+ ast_queue_control(sub->owner, AST_CONTROL_ANSWER);
+ send_text(TEXT_LINE2, TEXT_NORMAL, s, "is on-line");
+ send_text_status(s, "Hangup Transf");
+ send_start_timer(s);
+
+ if ((s->device->output == OUTPUT_HANDSET) &&
+ (s->device->receiver_state == STATE_ONHOOK))
+ send_select_output(s, OUTPUT_SPEAKER, s->device->volume, MUTE_OFF);
+ else
+ send_select_output(s, s->device->output, s->device->volume, MUTE_OFF);
+ s->device->start_call_timestamp = time(0);
+ write_history(s, 'i', 0);
+ return;
+}
+
+static int unistim_do_senddigit(struct unistimsession *pte, char digit)
+{
+
+ struct ast_frame f = { 0, };
+ struct unistim_subchannel *sub;
+ sub = pte->device->lines->subs[SUB_REAL];
+ if (!sub->owner) {
+ ast_log(LOG_WARNING, "Unable to find subchannel in dtmf senddigit\n");
+ return -1;
+ }
+ if (unistimdebug)
+ ast_verbose("Send Digit %c\n", digit);
+ switch (digit) {
+ case '0':
+ send_tone(pte, 941, 1336);
+ break;
+ case '1':
+ send_tone(pte, 697, 1209);
+ break;
+ case '2':
+ send_tone(pte, 697, 1336);
+ break;
+ case '3':
+ send_tone(pte, 697, 1477);
+ break;
+ case '4':
+ send_tone(pte, 770, 1209);
+ break;
+ case '5':
+ send_tone(pte, 770, 1336);
+ break;
+ case '6':
+ send_tone(pte, 770, 1477);
+ break;
+ case '7':
+ send_tone(pte, 852, 1209);
+ break;
+ case '8':
+ send_tone(pte, 852, 1336);
+ break;
+ case '9':
+ send_tone(pte, 852, 1477);
+ break;
+ case 'A':
+ send_tone(pte, 697, 1633);
+ break;
+ case 'B':
+ send_tone(pte, 770, 1633);
+ break;
+ case 'C':
+ send_tone(pte, 852, 1633);
+ break;
+ case 'D':
+ send_tone(pte, 941, 1633);
+ break;
+ case '*':
+ send_tone(pte, 941, 1209);
+ break;
+ case '#':
+ send_tone(pte, 941, 1477);
+ break;
+ default:
+ send_tone(pte, 500, 2000);
+ }
+ usleep(150000); /* XXX Less than perfect, blocking an important thread is not a good idea */
+ send_tone(pte, 0, 0);
+ f.frametype = AST_FRAME_DTMF;
+ f.subclass = digit;
+ f.src = "unistim";
+ ast_queue_frame(sub->owner, &f);
+ return 0;
+}
+
+static void key_call(struct unistimsession *pte, char keycode)
+{
+ if ((keycode >= KEY_0) && (keycode <= KEY_SHARP)) {
+ if (keycode == KEY_SHARP)
+ keycode = '#';
+ else if (keycode == KEY_STAR)
+ keycode = '*';
+ else
+ keycode -= 0x10;
+ unistim_do_senddigit(pte, keycode);
+ return;
+ }
+ switch (keycode) {
+ case KEY_HANGUP:
+ case KEY_FUNC1:
+ close_call(pte);
+ break;
+ case KEY_FUNC2:
+ TransferCallStep1(pte);
+ break;
+ case KEY_HEADPHN:
+ if (pte->device->output == OUTPUT_HEADPHONE)
+ send_select_output(pte, OUTPUT_HANDSET, pte->device->volume, MUTE_OFF);
+ else
+ send_select_output(pte, OUTPUT_HEADPHONE, pte->device->volume, MUTE_OFF);
+ break;
+ case KEY_LOUDSPK:
+ if (pte->device->output != OUTPUT_SPEAKER)
+ send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF);
+ else
+ send_select_output(pte, pte->device->previous_output, pte->device->volume,
+ MUTE_OFF);
+ break;
+ case KEY_MUTE:
+ if (!pte->device->moh) {
+ if (pte->device->mute == MUTE_ON)
+ send_select_output(pte, pte->device->output, pte->device->volume, MUTE_OFF);
+ else
+ send_select_output(pte, pte->device->output, pte->device->volume, MUTE_ON);
+ break;
+ }
+ case KEY_ONHOLD:
+ {
+ struct unistim_subchannel *sub;
+ struct ast_channel *bridgepeer = NULL;
+ sub = pte->device->lines->subs[SUB_REAL];
+ if (!sub->owner) {
+ ast_log(LOG_WARNING, "Unable to find subchannel for music on hold\n");
+ return;
+ }
+ if ((bridgepeer = ast_bridged_channel(sub->owner))) {
+ if (pte->device->moh) {
+ ast_moh_stop(bridgepeer);
+ pte->device->moh = 0;
+ send_select_output(pte, pte->device->output, pte->device->volume,
+ MUTE_OFF);
+ } else {
+ ast_moh_start(bridgepeer, pte->device->lines->musicclass, NULL);
+ pte->device->moh = 1;
+ send_select_output(pte, pte->device->output, pte->device->volume,
+ MUTE_ON);
+ }
+ } else
+ ast_log(LOG_WARNING,
+ "Unable to find peer subchannel for music on hold\n");
+ break;
+ }
+ }
+ return;
+}
+
+static void key_ringing(struct unistimsession *pte, char keycode)
+{
+ if (keycode == KEY_FAV0 + pte->device->softkeylinepos) {
+ HandleCallIncoming(pte);
+ return;
+ }
+ switch (keycode) {
+ case KEY_HANGUP:
+ case KEY_FUNC4:
+ IgnoreCall(pte);
+ break;
+ case KEY_FUNC1:
+ HandleCallIncoming(pte);
+ break;
+ }
+ return;
+}
+
+static void Keyfavorite(struct unistimsession *pte, char keycode)
+{
+ int fav;
+
+ if ((keycode < KEY_FAV1) && (keycode > KEY_FAV5)) {
+ ast_log(LOG_WARNING, "It's not a favorite key\n");
+ return;
+ }
+ if (keycode == KEY_FAV0)
+ return;
+ fav = keycode - KEY_FAV0;
+ if (pte->device->softkeyicon[fav] == 0)
+ return;
+ ast_copy_string(pte->device->phone_number, pte->device->softkeynumber[fav],
+ sizeof(pte->device->phone_number));
+ HandleCallOutgoing(pte);
+ return;
+}
+
+static void key_dial_page(struct unistimsession *pte, char keycode)
+{
+ if (keycode == KEY_FUNC3) {
+ if (pte->device->size_phone_number <= 1)
+ keycode = KEY_FUNC4;
+ else {
+ pte->device->size_phone_number -= 2;
+ keycode = pte->device->phone_number[pte->device->size_phone_number] + 0x10;
+ }
+ }
+ if ((keycode >= KEY_0) && (keycode <= KEY_SHARP)) {
+ char tmpbuf[] = "Number : ...............";
+ int i = 0;
+
+ if (pte->device->size_phone_number >= 15)
+ return;
+ if (pte->device->size_phone_number == 0)
+ send_tone(pte, 0, 0);
+ while (i < pte->device->size_phone_number) {
+ tmpbuf[i + 9] = pte->device->phone_number[i];
+ i++;
+ }
+ if (keycode == KEY_SHARP)
+ keycode = '#';
+ else if (keycode == KEY_STAR)
+ keycode = '*';
+ else
+ keycode -= 0x10;
+ tmpbuf[i + 9] = keycode;
+ pte->device->phone_number[i] = keycode;
+ pte->device->size_phone_number++;
+ pte->device->phone_number[i + 1] = 0;
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmpbuf);
+ send_blink_cursor(pte);
+ send_cursor_pos(pte, (unsigned char) (TEXT_LINE2 + 0x0a + i));
+ return;
+ }
+ if (keycode == KEY_FUNC4) {
+
+ pte->device->size_phone_number = 0;
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, "Number : ...............");
+ send_blink_cursor(pte);
+ send_cursor_pos(pte, TEXT_LINE2 + 0x09);
+ return;
+ }
+
+ if (pte->device->call_forward[0] == -1) {
+ if (keycode == KEY_FUNC1) {
+ ast_copy_string(pte->device->call_forward, pte->device->phone_number,
+ sizeof(pte->device->call_forward));
+ show_main_page(pte);
+ } else if ((keycode == KEY_FUNC2) || (keycode == KEY_HANGUP)) {
+ pte->device->call_forward[0] = '\0';
+ show_main_page(pte);
+ }
+ return;
+ }
+ switch (keycode) {
+ case KEY_FUNC2:
+ if (ast_strlen_zero(pte->device->redial_number))
+ break;
+ ast_copy_string(pte->device->phone_number, pte->device->redial_number,
+ sizeof(pte->device->phone_number));
+ case KEY_FUNC1:
+ HandleCallOutgoing(pte);
+ break;
+ case KEY_HANGUP:
+ if (pte->device->lines->subs[SUB_REAL]->owner) {
+ /* Stop the silence generator */
+ if (pte->device->silence_generator) {
+ if (unistimdebug)
+ ast_verbose("Stopping silence generator\n");
+ ast_channel_stop_silence_generator(pte->device->lines->subs[SUB_REAL]->
+ owner, pte->device->silence_generator);
+ pte->device->silence_generator = NULL;
+ }
+ send_tone(pte, 0, 0);
+ ast_moh_stop(ast_bridged_channel(pte->device->lines->subs[SUB_REAL]->owner));
+ pte->device->moh = 0;
+ pte->state = STATE_CALL;
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, "Dialing canceled,");
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, "switching back to");
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, "previous call.");
+ send_text_status(pte, "Hangup Transf");
+ } else
+ show_main_page(pte);
+ break;
+ case KEY_FAV1:
+ case KEY_FAV2:
+ case KEY_FAV3:
+ case KEY_FAV4:
+ case KEY_FAV5:
+ Keyfavorite(pte, keycode);
+ break;
+ case KEY_LOUDSPK:
+ if (pte->device->output == OUTPUT_SPEAKER) {
+ if (pte->device->receiver_state == STATE_OFFHOOK)
+ send_select_output(pte, pte->device->previous_output, pte->device->volume,
+ MUTE_OFF);
+ else
+ show_main_page(pte);
+ } else
+ send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF);
+ break;
+ case KEY_HEADPHN:
+ if (pte->device->output == OUTPUT_HEADPHONE) {
+ if (pte->device->receiver_state == STATE_OFFHOOK)
+ send_select_output(pte, OUTPUT_HANDSET, pte->device->volume, MUTE_OFF);
+ else
+ show_main_page(pte);
+ } else
+ send_select_output(pte, OUTPUT_HEADPHONE, pte->device->volume, MUTE_OFF);
+ break;
+ }
+ return;
+}
+
+#define SELECTCODEC_START_ENTRY_POS 15
+#define SELECTCODEC_MAX_LENGTH 2
+#define SELECTCODEC_MSG "Codec number : .."
+static void HandleSelectCodec(struct unistimsession *pte)
+{
+ char buf[30], buf2[5];
+
+ pte->state = STATE_SELECTCODEC;
+ strcpy(buf, "Using codec ");
+ sprintf(buf2, "%d", pte->device->codec_number);
+ strcat(buf, buf2);
+ strcat(buf, " (G711u=0,");
+
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, buf);
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, "G723=4,G711a=8,G729A=18)");
+ send_text(TEXT_LINE2, TEXT_INVERSE, pte, SELECTCODEC_MSG);
+ send_blink_cursor(pte);
+ send_cursor_pos(pte, TEXT_LINE2 + SELECTCODEC_START_ENTRY_POS);
+ pte->size_buff_entry = 0;
+ send_text_status(pte, "Select BackSpcErase Cancel");
+ return;
+}
+
+static void key_select_codec(struct unistimsession *pte, char keycode)
+{
+ if (keycode == KEY_FUNC2) {
+ if (pte->size_buff_entry <= 1)
+ keycode = KEY_FUNC3;
+ else {
+ pte->size_buff_entry -= 2;
+ keycode = pte->buff_entry[pte->size_buff_entry] + 0x10;
+ }
+ }
+ if ((keycode >= KEY_0) && (keycode <= KEY_9)) {
+ char tmpbuf[] = SELECTCODEC_MSG;
+ int i = 0;
+
+ if (pte->size_buff_entry >= SELECTCODEC_MAX_LENGTH)
+ return;
+
+ while (i < pte->size_buff_entry) {
+ tmpbuf[i + SELECTCODEC_START_ENTRY_POS] = pte->buff_entry[i];
+ i++;
+ }
+ tmpbuf[i + SELECTCODEC_START_ENTRY_POS] = keycode - 0x10;
+ pte->buff_entry[i] = keycode - 0x10;
+ pte->size_buff_entry++;
+ send_text(TEXT_LINE2, TEXT_INVERSE, pte, tmpbuf);
+ send_blink_cursor(pte);
+ send_cursor_pos(pte,
+ (unsigned char) (TEXT_LINE2 + SELECTCODEC_START_ENTRY_POS + 1 + i));
+ return;
+ }
+
+ switch (keycode) {
+ case KEY_FUNC1:
+ if (pte->size_buff_entry == 1)
+ pte->device->codec_number = pte->buff_entry[0] - 48;
+ else if (pte->size_buff_entry == 2)
+ pte->device->codec_number =
+ ((pte->buff_entry[0] - 48) * 10) + (pte->buff_entry[1] - 48);
+ show_main_page(pte);
+ break;
+ case KEY_FUNC3:
+ pte->size_buff_entry = 0;
+ send_text(TEXT_LINE2, TEXT_INVERSE, pte, SELECTCODEC_MSG);
+ send_blink_cursor(pte);
+ send_cursor_pos(pte, TEXT_LINE2 + SELECTCODEC_START_ENTRY_POS);
+ break;
+ case KEY_HANGUP:
+ case KEY_FUNC4:
+ show_main_page(pte);
+ break;
+ }
+ return;
+}
+
+#define SELECTEXTENSION_START_ENTRY_POS 0
+#define SELECTEXTENSION_MAX_LENGTH 10
+#define SELECTEXTENSION_MSG ".........."
+static void ShowExtensionPage(struct unistimsession *pte)
+{
+ pte->state = STATE_EXTENSION;
+
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, "Please enter a Terminal");
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, "Number (TN) :");
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, SELECTEXTENSION_MSG);
+ send_blink_cursor(pte);
+ send_cursor_pos(pte, TEXT_LINE2 + SELECTEXTENSION_START_ENTRY_POS);
+ send_text_status(pte, "Enter BackSpcErase");
+ pte->size_buff_entry = 0;
+ return;
+}
+
+static void key_select_extension(struct unistimsession *pte, char keycode)
+{
+ if (keycode == KEY_FUNC2) {
+ if (pte->size_buff_entry <= 1)
+ keycode = KEY_FUNC3;
+ else {
+ pte->size_buff_entry -= 2;
+ keycode = pte->buff_entry[pte->size_buff_entry] + 0x10;
+ }
+ }
+ if ((keycode >= KEY_0) && (keycode <= KEY_9)) {
+ char tmpbuf[] = SELECTEXTENSION_MSG;
+ int i = 0;
+
+ if (pte->size_buff_entry >= SELECTEXTENSION_MAX_LENGTH)
+ return;
+
+ while (i < pte->size_buff_entry) {
+ tmpbuf[i + SELECTEXTENSION_START_ENTRY_POS] = pte->buff_entry[i];
+ i++;
+ }
+ tmpbuf[i + SELECTEXTENSION_START_ENTRY_POS] = keycode - 0x10;
+ pte->buff_entry[i] = keycode - 0x10;
+ pte->size_buff_entry++;
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmpbuf);
+ send_blink_cursor(pte);
+ send_cursor_pos(pte,
+ (unsigned char) (TEXT_LINE2 + SELECTEXTENSION_START_ENTRY_POS + 1 +
+ i));
+ return;
+ }
+
+ switch (keycode) {
+ case KEY_FUNC1:
+ if (pte->size_buff_entry < 1)
+ return;
+ if (autoprovisioning == AUTOPROVISIONING_TN) {
+ struct unistim_device *d;
+
+ /* First step : looking for this TN in our device list */
+ ast_mutex_lock(&devicelock);
+ d = devices;
+ pte->buff_entry[pte->size_buff_entry] = '\0';
+ while (d) {
+ if (d->id[0] == 'T') { /* It's a TN device ? */
+ /* It's the TN we're looking for ? */
+ if (!strcmp((d->id) + 1, pte->buff_entry)) {
+ pte->device = d;
+ d->session = pte;
+ d->codec_number = DEFAULT_CODEC;
+ d->pos_fav = 0;
+ d->missed_call = 0;
+ d->receiver_state = STATE_ONHOOK;
+ strcpy(d->id, pte->macaddr);
+ pte->device->extension_number[0] = 'T';
+ pte->device->extension = EXTENSION_TN;
+ ast_copy_string((pte->device->extension_number) + 1,
+ pte->buff_entry, pte->size_buff_entry + 1);
+ ast_mutex_unlock(&devicelock);
+ show_main_page(pte);
+ refresh_all_favorite(pte);
+ return;
+ }
+ }
+ d = d->next;
+ }
+ ast_mutex_unlock(&devicelock);
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, "Invalid Terminal Number.");
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, "Please try again :");
+ send_cursor_pos(pte,
+ (unsigned char) (TEXT_LINE2 + SELECTEXTENSION_START_ENTRY_POS +
+ pte->size_buff_entry));
+ send_blink_cursor(pte);
+ } else {
+ ast_copy_string(pte->device->extension_number, pte->buff_entry,
+ pte->size_buff_entry + 1);
+ if (RegisterExtension(pte)) {
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, "Invalid extension.");
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, "Please try again :");
+ send_cursor_pos(pte,
+ (unsigned char) (TEXT_LINE2 +
+ SELECTEXTENSION_START_ENTRY_POS +
+ pte->size_buff_entry));
+ send_blink_cursor(pte);
+ } else
+ show_main_page(pte);
+ }
+ break;
+ case KEY_FUNC3:
+ pte->size_buff_entry = 0;
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, SELECTEXTENSION_MSG);
+ send_blink_cursor(pte);
+ send_cursor_pos(pte, TEXT_LINE2 + SELECTEXTENSION_START_ENTRY_POS);
+ break;
+ }
+ return;
+}
+
+static int ReformatNumber(char *number)
+{
+ int pos = 0, i = 0, size = strlen(number);
+
+ for (; i < size; i++) {
+ if ((number[i] >= '0') && (number[i] <= '9')) {
+ if (i == pos) {
+ pos++;
+ continue;
+ }
+ number[pos] = number[i];
+ pos++;
+ }
+ }
+ number[pos] = 0;
+ return pos;
+}
+
+static void show_entry_history(struct unistimsession *pte, FILE ** f)
+{
+ char line[TEXT_LENGTH_MAX + 1], status[STATUS_LENGTH_MAX + 1], func1[10], func2[10],
+ func3[10];
+
+ if (fread(line, TEXT_LENGTH_MAX, 1, *f) != 1) {
+ display_last_error("Can't read history date entry");
+ fclose(*f);
+ return;
+ }
+ line[sizeof(line) - 1] = '\0';
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, line);
+ if (fread(line, TEXT_LENGTH_MAX, 1, *f) != 1) {
+ display_last_error("Can't read callerid entry");
+ fclose(*f);
+ return;
+ }
+ line[sizeof(line) - 1] = '\0';
+ ast_copy_string(pte->device->lst_cid, line, sizeof(pte->device->lst_cid));
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, line);
+ if (fread(line, TEXT_LENGTH_MAX, 1, *f) != 1) {
+ display_last_error("Can't read callername entry");
+ fclose(*f);
+ return;
+ }
+ line[sizeof(line) - 1] = '\0';
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, line);
+ fclose(*f);
+
+ snprintf(line, sizeof(line), "Call %03d/%03d", pte->buff_entry[2],
+ pte->buff_entry[1]);
+ send_texttitle(pte, line);
+
+ if (pte->buff_entry[2] == 1)
+ strcpy(func1, " ");
+ else
+ strcpy(func1, "Prvious");
+ if (pte->buff_entry[2] >= pte->buff_entry[1])
+ strcpy(func2, " ");
+ else
+ strcpy(func2, "Next ");
+ if (ReformatNumber(pte->device->lst_cid))
+ strcpy(func3, "Redial ");
+ else
+ strcpy(func3, " ");
+ snprintf(status, sizeof(status), "%s%s%sCancel", func1, func2, func3);
+ send_text_status(pte, status);
+}
+
+static char OpenHistory(struct unistimsession *pte, char way, FILE ** f)
+{
+ char tmp[AST_CONFIG_MAX_PATH];
+ char count;
+
+ snprintf(tmp, sizeof(tmp), "%s/%s/%s-%c.csv", ast_config_AST_LOG_DIR,
+ USTM_LOG_DIR, pte->device->name, way);
+ *f = fopen(tmp, "r");
+ if (!*f) {
+ display_last_error("Unable to open history file");
+ return 0;
+ }
+ if (fread(&count, 1, 1, *f) != 1) {
+ display_last_error("Unable to read history header - display.");
+ fclose(*f);
+ return 0;
+ }
+ if (count > MAX_ENTRY_LOG) {
+ ast_log(LOG_WARNING, "Invalid count in history header of %s (%d max %d)\n", tmp,
+ count, MAX_ENTRY_LOG);
+ fclose(*f);
+ return 0;
+ }
+ return count;
+}
+
+static void show_history(struct unistimsession *pte, char way)
+{
+ FILE *f;
+ char count;
+
+ if (!pte->device)
+ return;
+ if (!pte->device->callhistory)
+ return;
+ count = OpenHistory(pte, way, &f);
+ if (!count)
+ return;
+ pte->buff_entry[0] = way;
+ pte->buff_entry[1] = count;
+ pte->buff_entry[2] = 1;
+ show_entry_history(pte, &f);
+ pte->state = STATE_HISTORY;
+}
+
+static void show_main_page(struct unistimsession *pte)
+{
+ char tmpbuf[TEXT_LENGTH_MAX + 1];
+
+
+ if ((pte->device->extension == EXTENSION_ASK) &&
+ (ast_strlen_zero(pte->device->extension_number))) {
+ ShowExtensionPage(pte);
+ return;
+ }
+
+ pte->state = STATE_MAINPAGE;
+
+ send_tone(pte, 0, 0);
+ send_select_output(pte, pte->device->output, pte->device->volume, MUTE_ON_DISCRET);
+ pte->device->lines->lastmsgssent = 0;
+ send_favorite(pte->device->softkeylinepos, FAV_ICON_ONHOOK_BLACK, pte,
+ pte->device->softkeylabel[pte->device->softkeylinepos]);
+ if (!ast_strlen_zero(pte->device->call_forward)) {
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, "Call forwarded to :");
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, pte->device->call_forward);
+ Sendicon(TEXT_LINE0, FAV_ICON_REFLECT + FAV_BLINK_SLOW, pte);
+ send_text_status(pte, "Dial Redial NoForwd");
+ } else {
+ if ((pte->device->extension == EXTENSION_ASK) ||
+ (pte->device->extension == EXTENSION_TN))
+ send_text_status(pte, "Dial Redial ForwardUnregis");
+ else
+ send_text_status(pte, "Dial Redial Forward");
+
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, pte->device->maintext1);
+ if (pte->device->missed_call == 0)
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, pte->device->maintext0);
+ else {
+ sprintf(tmpbuf, "%d unanswered call(s)", pte->device->missed_call);
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, tmpbuf);
+ Sendicon(TEXT_LINE0, FAV_ICON_CALL_CENTER + FAV_BLINK_SLOW, pte);
+ }
+ }
+ if (ast_strlen_zero(pte->device->maintext2)) {
+ strcpy(tmpbuf, "IP : ");
+ strcat(tmpbuf, ast_inet_ntoa(pte->sin.sin_addr));
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmpbuf);
+ } else
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, pte->device->maintext2);
+ send_texttitle(pte, pte->device->titledefault);
+ change_favorite_icon(pte, FAV_ICON_ONHOOK_BLACK);
+}
+
+static void key_main_page(struct unistimsession *pte, char keycode)
+{
+ if (pte->device->missed_call) {
+ Sendicon(TEXT_LINE0, FAV_ICON_NONE, pte);
+ pte->device->missed_call = 0;
+ }
+ if ((keycode >= KEY_0) && (keycode <= KEY_SHARP)) {
+ handle_dial_page(pte);
+ key_dial_page(pte, keycode);
+ return;
+ }
+ switch (keycode) {
+ case KEY_FUNC1:
+ handle_dial_page(pte);
+ break;
+ case KEY_FUNC2:
+ if (ast_strlen_zero(pte->device->redial_number))
+ break;
+ if ((pte->device->output == OUTPUT_HANDSET) &&
+ (pte->device->receiver_state == STATE_ONHOOK))
+ send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF);
+ else
+ send_select_output(pte, pte->device->output, pte->device->volume, MUTE_OFF);
+
+ ast_copy_string(pte->device->phone_number, pte->device->redial_number,
+ sizeof(pte->device->phone_number));
+ HandleCallOutgoing(pte);
+ break;
+ case KEY_FUNC3:
+ if (!ast_strlen_zero(pte->device->call_forward)) {
+ /* Cancel call forwarding */
+ memmove(pte->device->call_forward + 1, pte->device->call_forward,
+ sizeof(pte->device->call_forward));
+ pte->device->call_forward[0] = '\0';
+ Sendicon(TEXT_LINE0, FAV_ICON_NONE, pte);
+ pte->device->output = OUTPUT_HANDSET; /* Seems to be reseted somewhere */
+ show_main_page(pte);
+ break;
+ }
+ pte->device->call_forward[0] = -1;
+ handle_dial_page(pte);
+ break;
+ case KEY_FUNC4:
+ if (pte->device->extension == EXTENSION_ASK) {
+ UnregisterExtension(pte);
+ pte->device->extension_number[0] = '\0';
+ ShowExtensionPage(pte);
+ } else if (pte->device->extension == EXTENSION_TN) {
+ ast_mutex_lock(&devicelock);
+ strcpy(pte->device->id, pte->device->extension_number);
+ pte->buff_entry[0] = '\0';
+ pte->size_buff_entry = 0;
+ pte->device->session = NULL;
+ pte->device = NULL;
+ ast_mutex_unlock(&devicelock);
+ ShowExtensionPage(pte);
+ }
+ break;
+ case KEY_FAV0:
+ handle_dial_page(pte);
+ break;
+ case KEY_FAV1:
+ case KEY_FAV2:
+ case KEY_FAV3:
+ case KEY_FAV4:
+ case KEY_FAV5:
+ if ((pte->device->output == OUTPUT_HANDSET) &&
+ (pte->device->receiver_state == STATE_ONHOOK))
+ send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF);
+ else
+ send_select_output(pte, pte->device->output, pte->device->volume, MUTE_OFF);
+ Keyfavorite(pte, keycode);
+ break;
+ case KEY_CONF:
+ HandleSelectCodec(pte);
+ break;
+ case KEY_LOUDSPK:
+ send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF);
+ handle_dial_page(pte);
+ break;
+ case KEY_HEADPHN:
+ send_select_output(pte, OUTPUT_HEADPHONE, pte->device->volume, MUTE_OFF);
+ handle_dial_page(pte);
+ break;
+ case KEY_SNDHIST:
+ show_history(pte, 'o');
+ break;
+ case KEY_RCVHIST:
+ show_history(pte, 'i');
+ break;
+ }
+ return;
+}
+
+static void key_history(struct unistimsession *pte, char keycode)
+{
+ FILE *f;
+ char count;
+ long offset;
+
+ switch (keycode) {
+ case KEY_UP:
+ case KEY_LEFT:
+ case KEY_FUNC1:
+ if (pte->buff_entry[2] <= 1)
+ return;
+ pte->buff_entry[2]--;
+ count = OpenHistory(pte, pte->buff_entry[0], &f);
+ if (!count)
+ return;
+ offset = ((pte->buff_entry[2] - 1) * TEXT_LENGTH_MAX * 3);
+ if (fseek(f, offset, SEEK_CUR)) {
+ display_last_error("Unable to seek history entry.");
+ fclose(f);
+ return;
+ }
+ show_entry_history(pte, &f);
+ break;
+ case KEY_DOWN:
+ case KEY_RIGHT:
+ case KEY_FUNC2:
+ if (pte->buff_entry[2] >= pte->buff_entry[1])
+ return;
+ pte->buff_entry[2]++;
+ count = OpenHistory(pte, pte->buff_entry[0], &f);
+ if (!count)
+ return;
+ offset = ((pte->buff_entry[2] - 1) * TEXT_LENGTH_MAX * 3);
+ if (fseek(f, offset, SEEK_CUR)) {
+ display_last_error("Unable to seek history entry.");
+ fclose(f);
+ return;
+ }
+ show_entry_history(pte, &f);
+ break;
+ case KEY_FUNC3:
+ if (!ReformatNumber(pte->device->lst_cid))
+ break;
+ ast_copy_string(pte->device->redial_number, pte->device->lst_cid,
+ sizeof(pte->device->redial_number));
+ key_main_page(pte, KEY_FUNC2);
+ break;
+ case KEY_FUNC4:
+ case KEY_HANGUP:
+ show_main_page(pte);
+ break;
+ case KEY_SNDHIST:
+ if (pte->buff_entry[0] == 'i')
+ show_history(pte, 'o');
+ else
+ show_main_page(pte);
+ break;
+ case KEY_RCVHIST:
+ if (pte->buff_entry[0] == 'i')
+ show_main_page(pte);
+ else
+ show_history(pte, 'i');
+ break;
+ }
+ return;
+}
+
+static void init_phone_step2(struct unistimsession *pte)
+{
+ BUFFSEND;
+ if (unistimdebug)
+ ast_verbose("Sending S4\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_s4, sizeof(packet_send_s4));
+ send_client(SIZE_HEADER + sizeof(packet_send_s4), buffsend, pte);
+ send_date_time2(pte);
+ send_date_time3(pte);
+ if (unistimdebug)
+ ast_verbose("Sending S7\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_S7, sizeof(packet_send_S7));
+ send_client(SIZE_HEADER + sizeof(packet_send_S7), buffsend, pte);
+ if (unistimdebug)
+ ast_verbose("Sending Contrast\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_Contrast, sizeof(packet_send_Contrast));
+ if (pte->device != NULL)
+ buffsend[9] = pte->device->contrast;
+ send_client(SIZE_HEADER + sizeof(packet_send_Contrast), buffsend, pte);
+
+ if (unistimdebug)
+ ast_verbose("Sending S9\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_s9, sizeof(packet_send_s9));
+ send_client(SIZE_HEADER + sizeof(packet_send_s9), buffsend, pte);
+ send_no_ring(pte);
+
+ if (unistimdebug)
+ ast_verbose("Sending S7\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_S7, sizeof(packet_send_S7));
+ send_client(SIZE_HEADER + sizeof(packet_send_S7), buffsend, pte);
+ send_led_update(pte, 0);
+ send_ping(pte);
+ if (pte->state < STATE_MAINPAGE) {
+ if (autoprovisioning == AUTOPROVISIONING_TN) {
+ ShowExtensionPage(pte);
+ return;
+ } else {
+ int i;
+ char tmp[30];
+
+ for (i = 1; i < 6; i++)
+ send_favorite(i, 0, pte, "");
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, "Sorry, this phone is not");
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, "registred in unistim.cfg");
+ strcpy(tmp, "MAC = ");
+ strcat(tmp, pte->macaddr);
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmp);
+ send_text_status(pte, "");
+ send_texttitle(pte, "UNISTIM for*");
+ return;
+ }
+ }
+ show_main_page(pte);
+ refresh_all_favorite(pte);
+ if (unistimdebug)
+ ast_verbose("Sending arrow\n");
+ memcpy(buffsend + SIZE_HEADER, packet_send_arrow, sizeof(packet_send_arrow));
+ send_client(SIZE_HEADER + sizeof(packet_send_arrow), buffsend, pte);
+ return;
+}
+
+static void process_request(int size, unsigned char *buf, struct unistimsession *pte)
+{
+ char tmpbuf[255];
+ if (memcmp
+ (buf + SIZE_HEADER, packet_recv_resume_connection_with_server,
+ sizeof(packet_recv_resume_connection_with_server)) == 0) {
+ rcv_resume_connection_with_server(pte);
+ return;
+ }
+ if (memcmp(buf + SIZE_HEADER, packet_recv_firm_version, sizeof(packet_recv_firm_version)) ==
+ 0) {
+ buf[size] = 0;
+ if (unistimdebug)
+ ast_verbose("Got the firmware version : '%s'\n", buf + 13);
+ init_phone_step2(pte);
+ return;
+ }
+ if (memcmp(buf + SIZE_HEADER, packet_recv_mac_addr, sizeof(packet_recv_mac_addr)) == 0) {
+ rcv_mac_addr(pte, buf);
+ return;
+ }
+ if (memcmp(buf + SIZE_HEADER, packet_recv_r2, sizeof(packet_recv_r2)) == 0) {
+ if (unistimdebug)
+ ast_verbose("R2 received\n");
+ return;
+ }
+
+ if (pte->state < STATE_MAINPAGE) {
+ if (unistimdebug)
+ ast_verbose("Request not authorized in this state\n");
+ return;
+ }
+ if (!memcmp(buf + SIZE_HEADER, packet_recv_pressed_key, sizeof(packet_recv_pressed_key))) {
+ char keycode = buf[13];
+
+ if (unistimdebug)
+ ast_verbose("Key pressed : keycode = 0x%.2x - current state : %d\n", keycode,
+ pte->state);
+
+ switch (pte->state) {
+ case STATE_INIT:
+ if (unistimdebug)
+ ast_verbose("No keys allowed in the init state\n");
+ break;
+ case STATE_AUTHDENY:
+ if (unistimdebug)
+ ast_verbose("No keys allowed in authdeny state\n");
+ break;
+ case STATE_MAINPAGE:
+ key_main_page(pte, keycode);
+ break;
+ case STATE_DIALPAGE:
+ key_dial_page(pte, keycode);
+ break;
+ case STATE_RINGING:
+ key_ringing(pte, keycode);
+ break;
+ case STATE_CALL:
+ key_call(pte, keycode);
+ break;
+ case STATE_EXTENSION:
+ key_select_extension(pte, keycode);
+ break;
+ case STATE_SELECTCODEC:
+ key_select_codec(pte, keycode);
+ break;
+ case STATE_HISTORY:
+ key_history(pte, keycode);
+ break;
+ default:
+ ast_log(LOG_WARNING, "Key : Unknown state\n");
+ }
+ return;
+ }
+ if (memcmp(buf + SIZE_HEADER, packet_recv_pick_up, sizeof(packet_recv_pick_up)) == 0) {
+ if (unistimdebug)
+ ast_verbose("Handset off hook\n");
+ if (!pte->device) /* We are not yet registred (asking for a TN in AUTOPROVISIONING_TN) */
+ return;
+ pte->device->receiver_state = STATE_OFFHOOK;
+ if (pte->device->output == OUTPUT_HEADPHONE)
+ send_select_output(pte, OUTPUT_HEADPHONE, pte->device->volume, MUTE_OFF);
+ else
+ send_select_output(pte, OUTPUT_HANDSET, pte->device->volume, MUTE_OFF);
+ if (pte->state == STATE_RINGING)
+ HandleCallIncoming(pte);
+ else if ((pte->state == STATE_DIALPAGE) || (pte->state == STATE_CALL))
+ send_select_output(pte, OUTPUT_HANDSET, pte->device->volume, MUTE_OFF);
+ else if (pte->state == STATE_EXTENSION) /* We must have a TN before calling */
+ return;
+ else {
+ send_select_output(pte, OUTPUT_HANDSET, pte->device->volume, MUTE_OFF);
+ handle_dial_page(pte);
+ }
+ return;
+ }
+ if (memcmp(buf + SIZE_HEADER, packet_recv_hangup, sizeof(packet_recv_hangup)) == 0) {
+ if (unistimdebug)
+ ast_verbose("Handset on hook\n");
+ if (!pte->device)
+ return;
+ pte->device->receiver_state = STATE_ONHOOK;
+ if (pte->state == STATE_CALL)
+ close_call(pte);
+ else if (pte->device->lines->subs[SUB_REAL]->owner)
+ close_call(pte);
+ else if (pte->state == STATE_EXTENSION)
+ return;
+ else
+ show_main_page(pte);
+ return;
+ }
+ strcpy(tmpbuf, ast_inet_ntoa(pte->sin.sin_addr));
+ strcat(tmpbuf, " Unknown request packet\n");
+ if (unistimdebug)
+ ast_debug(1, "%s", tmpbuf);
+ return;
+}
+
+static void parsing(int size, unsigned char *buf, struct unistimsession *pte,
+ struct sockaddr_in *addr_from)
+{
+ unsigned short *sbuf = (unsigned short *) buf;
+ unsigned short seq;
+ char tmpbuf[255];
+
+ strcpy(tmpbuf, ast_inet_ntoa(addr_from->sin_addr));
+
+ if (size < 10) {
+ if (size == 0) {
+ ast_log(LOG_WARNING, "%s Read error\n", tmpbuf);
+ } else {
+ ast_log(LOG_NOTICE, "%s Packet too short - ignoring\n", tmpbuf);
+ }
+ return;
+ }
+ if (sbuf[0] == 0xffff) { /* Starting with 0xffff ? *//* Yes, discovery packet ? */
+ if (size != sizeof(packet_rcv_discovery)) {
+ ast_log(LOG_NOTICE, "%s Invalid size of a discovery packet\n", tmpbuf);
+ } else {
+ if (memcmp(buf, packet_rcv_discovery, sizeof(packet_rcv_discovery)) == 0) {
+ if (unistimdebug)
+ ast_verbose("Discovery packet received - Sending Discovery ACK\n");
+ if (pte) { /* A session was already active for this IP ? */
+ if (pte->state == STATE_INIT) { /* Yes, but it's a dupe */
+ if (unistimdebug)
+ ast_verbose("Duplicated Discovery packet\n");
+ send_raw_client(sizeof(packet_send_discovery_ack),
+ packet_send_discovery_ack, addr_from, &pte->sout);
+ pte->seq_phone = (short) 0x0000; /* reset sequence number */
+ } else { /* No, probably a reboot, phone side */
+ close_client(pte); /* Cleanup the previous session */
+ if (create_client(addr_from))
+ send_raw_client(sizeof(packet_send_discovery_ack),
+ packet_send_discovery_ack, addr_from, &pte->sout);
+ }
+ } else {
+ /* Creating new entry in our phone list */
+ if ((pte = create_client(addr_from)))
+ send_raw_client(sizeof(packet_send_discovery_ack),
+ packet_send_discovery_ack, addr_from, &pte->sout);
+ }
+ return;
+ }
+ ast_log(LOG_NOTICE, "%s Invalid discovery packet\n", tmpbuf);
+ }
+ return;
+ }
+ if (!pte) {
+ if (unistimdebug)
+ ast_verbose("%s Not a discovery packet from an unknown source : ignoring\n",
+ tmpbuf);
+ return;
+ }
+
+ if (sbuf[0] != 0) { /* Starting with something else than 0x0000 ? */
+ ast_log(LOG_NOTICE, "Unknown packet received - ignoring\n");
+ return;
+ }
+ if (buf[5] != 2) {
+ ast_log(LOG_NOTICE, "%s Wrong direction : got 0x%.2x expected 0x02\n", tmpbuf,
+ buf[5]);
+ return;
+ }
+ seq = ntohs(sbuf[1]);
+ if (buf[4] == 1) {
+ ast_mutex_lock(&pte->lock);
+ if ((unistimdebug) && (option_verbose > 5))
+ ast_verbose("ACK received for packet #0x%.4x\n", seq);
+ pte->nb_retransmit = 0;
+
+ if ((pte->last_seq_ack) + 1 == seq) {
+ pte->last_seq_ack++;
+ check_send_queue(pte);
+ ast_mutex_unlock(&pte->lock);
+ return;
+ }
+ if (pte->last_seq_ack > seq) {
+ if (pte->last_seq_ack == 0xffff) {
+ ast_verbose("ACK at 0xffff, restarting counter.\n");
+ pte->last_seq_ack = 0;
+ } else
+ ast_log(LOG_NOTICE,
+ "%s Warning : ACK received for an already ACKed packet : #0x%.4x we are at #0x%.4x\n",
+ tmpbuf, seq, pte->last_seq_ack);
+ ast_mutex_unlock(&pte->lock);
+ return;
+ }
+ if (pte->seq_server < seq) {
+ ast_log(LOG_NOTICE,
+ "%s Error : ACK received for a non-existant packet : #0x%.4x\n",
+ tmpbuf, pte->seq_server);
+ ast_mutex_unlock(&pte->lock);
+ return;
+ }
+ if (unistimdebug)
+ ast_verbose("%s ACK gap : Received ACK #0x%.4x, previous was #0x%.4x\n",
+ tmpbuf, seq, pte->last_seq_ack);
+ pte->last_seq_ack = seq;
+ check_send_queue(pte);
+ ast_mutex_unlock(&pte->lock);
+ return;
+ }
+ if (buf[4] == 2) {
+ if (unistimdebug)
+ ast_verbose("Request received\n");
+ if (pte->seq_phone == seq) {
+ /* Send ACK */
+ buf[4] = 1;
+ buf[5] = 1;
+ send_raw_client(SIZE_HEADER, buf, addr_from, &pte->sout);
+ pte->seq_phone++;
+
+ process_request(size, buf, pte);
+ return;
+ }
+ if (pte->seq_phone > seq) {
+ ast_log(LOG_NOTICE,
+ "%s Warning : received a retransmitted packet : #0x%.4x (we are at #0x%.4x)\n",
+ tmpbuf, seq, pte->seq_phone);
+ /* BUG ? pte->device->seq_phone = seq; */
+ /* Send ACK */
+ buf[4] = 1;
+ buf[5] = 1;
+ send_raw_client(SIZE_HEADER, buf, addr_from, &pte->sout);
+ return;
+ }
+ ast_log(LOG_NOTICE,
+ "%s Warning : we lost a packet : received #0x%.4x (we are at #0x%.4x)\n",
+ tmpbuf, seq, pte->seq_phone);
+ return;
+ }
+ if (buf[4] == 0) {
+ ast_log(LOG_NOTICE, "%s Retransmit request for packet #0x%.4x\n", tmpbuf, seq);
+ if (pte->last_seq_ack > seq) {
+ ast_log(LOG_NOTICE,
+ "%s Error : received a request for an already ACKed packet : #0x%.4x\n",
+ tmpbuf, pte->last_seq_ack);
+ return;
+ }
+ if (pte->seq_server < seq) {
+ ast_log(LOG_NOTICE,
+ "%s Error : received a request for a non-existant packet : #0x%.4x\n",
+ tmpbuf, pte->seq_server);
+ return;
+ }
+ send_retransmit(pte);
+ return;
+ }
+ ast_log(LOG_NOTICE, "%s Unknown request : got 0x%.2x expected 0x00,0x01 or 0x02\n",
+ tmpbuf, buf[4]);
+ return;
+}
+
+static struct unistimsession *channel_to_session(struct ast_channel *ast)
+{
+ struct unistim_subchannel *sub;
+ if (!ast) {
+ ast_log(LOG_WARNING, "Unistim callback function called with a null channel\n");
+ return NULL;
+ }
+ if (!ast->tech_pvt) {
+ ast_log(LOG_WARNING, "Unistim callback function called without a tech_pvt\n");
+ return NULL;
+ }
+ sub = ast->tech_pvt;
+
+ if (!sub->parent) {
+ ast_log(LOG_WARNING, "Unistim callback function called without a line\n");
+ return NULL;
+ }
+ if (!sub->parent->parent) {
+ ast_log(LOG_WARNING, "Unistim callback function called without a device\n");
+ return NULL;
+ }
+ if (!sub->parent->parent->session) {
+ ast_log(LOG_WARNING, "Unistim callback function called without a session\n");
+ return NULL;
+ }
+ return sub->parent->parent->session;
+}
+
+/*--- unistim_call: Initiate UNISTIM call from PBX ---*/
+/* used from the dial() application */
+static int unistim_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ int res = 0;
+ struct unistim_subchannel *sub;
+ struct unistimsession *session;
+
+ session = channel_to_session(ast);
+ if (!session) {
+ ast_log(LOG_ERROR, "Device not registered, cannot call %s\n", dest);
+ return -1;
+ }
+
+ sub = ast->tech_pvt;
+ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "unistim_call called on %s, neither down nor reserved\n",
+ ast->name);
+ return -1;
+ }
+
+ if (unistimdebug)
+ ast_verbose(VERBOSE_PREFIX_3 "unistim_call(%s)\n", ast->name);
+
+ session->state = STATE_RINGING;
+ Sendicon(TEXT_LINE0, FAV_ICON_NONE, session);
+
+ if (sub->owner) {
+ if (sub->owner->cid.cid_num) {
+ send_text(TEXT_LINE1, TEXT_NORMAL, session, sub->owner->cid.cid_num);
+ change_callerid(session, 0, sub->owner->cid.cid_num);
+ } else {
+ send_text(TEXT_LINE1, TEXT_NORMAL, session, DEFAULTCALLERID);
+ change_callerid(session, 0, DEFAULTCALLERID);
+ }
+ if (sub->owner->cid.cid_name) {
+ send_text(TEXT_LINE0, TEXT_NORMAL, session, sub->owner->cid.cid_name);
+ change_callerid(session, 1, sub->owner->cid.cid_name);
+ } else {
+ send_text(TEXT_LINE0, TEXT_NORMAL, session, DEFAULTCALLERNAME);
+ change_callerid(session, 1, DEFAULTCALLERNAME);
+ }
+ }
+ send_text(TEXT_LINE2, TEXT_NORMAL, session, "is calling you.");
+ send_text_status(session, "Accept Ignore");
+
+ if (sub->ringstyle == -1)
+ send_ring(session, session->device->ringvolume, session->device->ringstyle);
+ else {
+ if (sub->ringvolume == -1)
+ send_ring(session, session->device->ringvolume, sub->ringstyle);
+ else
+ send_ring(session, sub->ringvolume, sub->ringstyle);
+ }
+ change_favorite_icon(session, FAV_ICON_SPEAKER_ONHOOK_BLACK + FAV_BLINK_FAST);
+
+ ast_setstate(ast, AST_STATE_RINGING);
+ ast_queue_control(ast, AST_CONTROL_RINGING);
+ return res;
+}
+
+/*--- unistim_hangup: Hangup UNISTIM call */
+static int unistim_hangup(struct ast_channel *ast)
+{
+ struct unistim_subchannel *sub;
+ struct unistim_line *l;
+ struct unistimsession *s;
+
+ s = channel_to_session(ast);
+ sub = ast->tech_pvt;
+ if (!s) {
+ ast_debug(1, "Asked to hangup channel not connected\n");
+ ast_mutex_lock(&sub->lock);
+ sub->owner = NULL;
+ ast->tech_pvt = NULL;
+ sub->alreadygone = 0;
+ ast_mutex_unlock(&sub->lock);
+ if (sub->rtp) {
+ if (unistimdebug)
+ ast_verbose("Destroying RTP session\n");
+ ast_rtp_destroy(sub->rtp);
+ sub->rtp = NULL;
+ }
+ return 0;
+ }
+ l = sub->parent;
+ if (unistimdebug)
+ ast_verbose("unistim_hangup(%s) on %s@%s\n", ast->name, l->name, l->parent->name);
+
+ if ((l->subs[SUB_THREEWAY]) && (sub->subtype == SUB_REAL)) {
+ if (unistimdebug)
+ ast_verbose("Real call disconnected while talking to threeway\n");
+ sub->owner = NULL;
+ ast->tech_pvt = NULL;
+ return 0;
+ }
+ if ((l->subs[SUB_REAL]->owner) && (sub->subtype == SUB_THREEWAY) &&
+ (sub->alreadygone == 0)) {
+ if (unistimdebug)
+ ast_verbose("threeway call disconnected, switching to real call\n");
+ send_text(TEXT_LINE0, TEXT_NORMAL, s, "Three way call canceled,");
+ send_text(TEXT_LINE1, TEXT_NORMAL, s, "switching back to");
+ send_text(TEXT_LINE2, TEXT_NORMAL, s, "previous call.");
+ send_text_status(s, "Hangup Transf");
+ ast_moh_stop(ast_bridged_channel(l->subs[SUB_REAL]->owner));
+ swap_subs(l, SUB_THREEWAY, SUB_REAL);
+ l->parent->moh = 0;
+ ast_mutex_lock(&sub->lock);
+ sub->owner = NULL;
+ ast->tech_pvt = NULL;
+ ast_mutex_unlock(&sub->lock);
+ unalloc_sub(l, SUB_THREEWAY);
+ return 0;
+ }
+ ast_mutex_lock(&sub->lock);
+ sub->owner = NULL;
+ ast->tech_pvt = NULL;
+ sub->alreadygone = 0;
+ ast_mutex_unlock(&sub->lock);
+ if (!s) {
+ if (unistimdebug)
+ ast_verbose("Asked to hangup channel not connected (no session)\n");
+ if (sub->rtp) {
+ if (unistimdebug)
+ ast_verbose("Destroying RTP session\n");
+ ast_rtp_destroy(sub->rtp);
+ sub->rtp = NULL;
+ }
+ return 0;
+ }
+ if (sub->subtype == SUB_REAL) {
+ /* Stop the silence generator */
+ if (s->device->silence_generator) {
+ if (unistimdebug)
+ ast_verbose("Stopping silence generator\n");
+ if (sub->owner)
+ ast_channel_stop_silence_generator(sub->owner,
+ s->device->silence_generator);
+ else
+ ast_log(LOG_WARNING,
+ "Trying to stop silence generator on a null channel !\n");
+ s->device->silence_generator = NULL;
+ }
+ }
+ l->parent->moh = 0;
+ send_no_ring(s);
+ send_end_call(s);
+ if (sub->rtp) {
+ if (unistimdebug)
+ ast_verbose("Destroying RTP session\n");
+ ast_rtp_destroy(sub->rtp);
+ sub->rtp = NULL;
+ } else if (unistimdebug)
+ ast_verbose("No RTP session to destroy\n");
+ if (l->subs[SUB_THREEWAY]) {
+ if (unistimdebug)
+ ast_verbose("Cleaning other subchannels\n");
+ unalloc_sub(l, SUB_THREEWAY);
+ }
+ if (s->state == STATE_RINGING)
+ cancel_dial(s);
+ else if (s->state == STATE_CALL)
+ close_call(s);
+
+ return 0;
+}
+
+/*--- unistim_answer: Answer UNISTIM call */
+static int unistim_answer(struct ast_channel *ast)
+{
+ int res = 0;
+ struct unistim_subchannel *sub;
+ struct unistim_line *l;
+ struct unistimsession *s;
+
+ s = channel_to_session(ast);
+ if (!s) {
+ ast_log(LOG_WARNING, "unistim_answer on a disconnected device ?\n");
+ return -1;
+ }
+ sub = ast->tech_pvt;
+ l = sub->parent;
+
+ if ((!sub->rtp) && (!l->subs[SUB_THREEWAY]))
+ start_rtp(sub);
+ if (unistimdebug)
+ ast_verbose("unistim_answer(%s) on %s@%s-%d\n", ast->name, l->name,
+ l->parent->name, sub->subtype);
+ send_text(TEXT_LINE2, TEXT_NORMAL, l->parent->session, "is now on-line");
+ if (l->subs[SUB_THREEWAY])
+ send_text_status(l->parent->session, "Transf Cancel");
+ else
+ send_text_status(l->parent->session, "Hangup Transf");
+ send_start_timer(l->parent->session);
+ if (ast->_state != AST_STATE_UP)
+ ast_setstate(ast, AST_STATE_UP);
+ return res;
+}
+
+/*--- unistimsock_read: Read data from UNISTIM socket ---*/
+/* Successful messages is connected to UNISTIM call and forwarded to parsing() */
+static int unistimsock_read(int *id, int fd, short events, void *ignore)
+{
+ struct sockaddr_in addr_from = { 0, };
+ struct unistimsession *cur = NULL;
+ int found = 0;
+ int tmp = 0;
+ int dw_num_bytes_rcvd;
+#ifdef DUMP_PACKET
+ int dw_num_bytes_rcvdd;
+ char iabuf[INET_ADDRSTRLEN];
+#endif
+
+ dw_num_bytes_rcvd =
+ recvfrom(unistimsock, buff, SIZE_PAGE, 0, (struct sockaddr *) &addr_from,
+ &size_addr_from);
+ if (dw_num_bytes_rcvd == -1) {
+ if (errno == EAGAIN)
+ ast_log(LOG_NOTICE, "UNISTIM: Received packet with bad UDP checksum\n");
+ else if (errno != ECONNREFUSED)
+ ast_log(LOG_WARNING, "Recv error %d (%s)\n", errno, strerror(errno));
+ return 1;
+ }
+
+ /* Looking in the phone list if we already have a registration for him */
+ ast_mutex_lock(&sessionlock);
+ cur = sessions;
+ while (cur) {
+ if (cur->sin.sin_addr.s_addr == addr_from.sin_addr.s_addr) {
+ found = 1;
+ break;
+ }
+ tmp++;
+ cur = cur->next;
+ }
+ ast_mutex_unlock(&sessionlock);
+
+#ifdef DUMP_PACKET
+ if (unistimdebug)
+ ast_verbose("\n*** Dump %d bytes from %s - phone_table[%d] ***\n",
+ dw_num_bytes_rcvd, ast_inet_ntoa(addr_from.sin_addr), tmp);
+ for (dw_num_bytes_rcvdd = 0; dw_num_bytes_rcvdd < dw_num_bytes_rcvd;
+ dw_num_bytes_rcvdd++)
+ ast_verbose("%.2x ", (unsigned char) buff[dw_num_bytes_rcvdd]);
+ ast_verbose("\n******************************************\n");
+#endif
+
+ if (!found) {
+ if (unistimdebug)
+ ast_verbose("Received a packet from an unknown source\n");
+ parsing(dw_num_bytes_rcvd, buff, NULL, (struct sockaddr_in *) &addr_from);
+
+ } else
+ parsing(dw_num_bytes_rcvd, buff, cur, (struct sockaddr_in *) &addr_from);
+
+ return 1;
+}
+
+static struct ast_frame *unistim_rtp_read(const struct ast_channel *ast,
+ const struct unistim_subchannel *sub)
+{
+ /* Retrieve audio/etc from channel. Assumes sub->lock is already held. */
+ struct ast_frame *f;
+
+ if (!ast) {
+ ast_log(LOG_WARNING, "Channel NULL while reading\n");
+ return &ast_null_frame;
+ }
+
+ if (!sub->rtp) {
+ ast_log(LOG_WARNING, "RTP handle NULL while reading on subchannel %d\n",
+ sub->subtype);
+ return &ast_null_frame;
+ }
+
+ switch (ast->fdno) {
+ case 0:
+ f = ast_rtp_read(sub->rtp); /* RTP Audio */
+ break;
+ case 1:
+ f = ast_rtcp_read(sub->rtp); /* RTCP Control Channel */
+ break;
+ default:
+ f = &ast_null_frame;
+ }
+
+ if (sub->owner) {
+ /* We already hold the channel lock */
+ if (f->frametype == AST_FRAME_VOICE) {
+ if (f->subclass != sub->owner->nativeformats) {
+ ast_debug(1,
+ "Oooh, format changed from %s (%d) to %s (%d)\n",
+ ast_getformatname(sub->owner->nativeformats),
+ sub->owner->nativeformats, ast_getformatname(f->subclass),
+ f->subclass);
+
+ sub->owner->nativeformats = f->subclass;
+ ast_set_read_format(sub->owner, sub->owner->readformat);
+ ast_set_write_format(sub->owner, sub->owner->writeformat);
+ }
+ }
+ }
+
+ return f;
+}
+
+static struct ast_frame *unistim_read(struct ast_channel *ast)
+{
+ struct ast_frame *fr;
+ struct unistim_subchannel *sub = ast->tech_pvt;
+
+ ast_mutex_lock(&sub->lock);
+ fr = unistim_rtp_read(ast, sub);
+ ast_mutex_unlock(&sub->lock);
+
+ return fr;
+}
+
+static int unistim_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct unistim_subchannel *sub = ast->tech_pvt;
+ int res = 0;
+
+ if (frame->frametype != AST_FRAME_VOICE) {
+ if (frame->frametype == AST_FRAME_IMAGE)
+ return 0;
+ else {
+ ast_log(LOG_WARNING, "Can't send %d type frames with unistim_write\n",
+ frame->frametype);
+ return 0;
+ }
+ } else {
+ if (!(frame->subclass & ast->nativeformats)) {
+ ast_log(LOG_WARNING,
+ "Asked to transmit frame type %s (%d), while native formats is %s (%d) (read/write = %s (%d)/%d)\n",
+ ast_getformatname(frame->subclass), frame->subclass,
+ ast_getformatname(ast->nativeformats), ast->nativeformats,
+ ast_getformatname(ast->readformat), ast->readformat,
+ ast->writeformat);
+ return -1;
+ }
+ }
+
+ if (sub) {
+ ast_mutex_lock(&sub->lock);
+ if (sub->rtp) {
+ res = ast_rtp_write(sub->rtp, frame);
+ }
+ ast_mutex_unlock(&sub->lock);
+ }
+
+ return res;
+}
+
+static int unistim_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct unistim_subchannel *p = newchan->tech_pvt;
+ struct unistim_line *l = p->parent;
+
+ ast_mutex_lock(&p->lock);
+
+ ast_debug(1, "New owner for channel USTM/%s@%s-%d is %s\n", l->name,
+ l->parent->name, p->subtype, newchan->name);
+
+ if (p->owner != oldchan) {
+ ast_log(LOG_WARNING, "old channel wasn't %s (%p) but was %s (%p)\n",
+ oldchan->name, oldchan, p->owner->name, p->owner);
+ return -1;
+ }
+
+ p->owner = newchan;
+
+ ast_mutex_unlock(&p->lock);
+
+ return 0;
+
+}
+
+static char *control2str(int ind)
+{
+ switch (ind) {
+ case AST_CONTROL_HANGUP:
+ return "Other end has hungup";
+ case AST_CONTROL_RING:
+ return "Local ring";
+ case AST_CONTROL_RINGING:
+ return "Remote end is ringing";
+ case AST_CONTROL_ANSWER:
+ return "Remote end has answered";
+ case AST_CONTROL_BUSY:
+ return "Remote end is busy";
+ case AST_CONTROL_TAKEOFFHOOK:
+ return "Make it go off hook";
+ case AST_CONTROL_OFFHOOK:
+ return "Line is off hook";
+ case AST_CONTROL_CONGESTION:
+ return "Congestion (circuits busy)";
+ case AST_CONTROL_FLASH:
+ return "Flash hook";
+ case AST_CONTROL_WINK:
+ return "Wink";
+ case AST_CONTROL_OPTION:
+ return "Set a low-level option";
+ case AST_CONTROL_RADIO_KEY:
+ return "Key Radio";
+ case AST_CONTROL_RADIO_UNKEY:
+ return "Un-Key Radio";
+ case -1:
+ return "Stop tone";
+ }
+ return "UNKNOWN";
+}
+
+static void in_band_indication(struct ast_channel *ast, const struct ind_tone_zone *tz,
+ const char *indication)
+{
+ const struct ind_tone_zone_sound *ts = NULL;
+
+ ts = ast_get_indication_tone(tz, indication);
+
+ if (ts && ts->data[0])
+ ast_playtones_start(ast, 0, ts->data, 1);
+ else
+ ast_log(LOG_WARNING, "Unable to get indication tone for %s\n", indication);
+}
+
+static int unistim_indicate(struct ast_channel *ast, int ind, const void *data,
+ size_t datalen)
+{
+ struct unistim_subchannel *sub;
+ struct unistim_line *l;
+ struct unistimsession *s;
+
+ if (unistimdebug) {
+ ast_verbose(VERBOSE_PREFIX_3 "Asked to indicate '%s' condition on channel %s\n",
+ control2str(ind), ast->name);
+ }
+
+ s = channel_to_session(ast);
+ if (!s)
+ return -1;
+
+ sub = ast->tech_pvt;
+ l = sub->parent;
+
+ switch (ind) {
+ case AST_CONTROL_RINGING:
+ if (ast->_state != AST_STATE_UP) {
+ send_text(TEXT_LINE2, TEXT_NORMAL, s, "Ringing...");
+ in_band_indication(ast, l->parent->tz, "ring");
+ s->device->missed_call = -1;
+ break;
+ }
+ return -1;
+ case AST_CONTROL_BUSY:
+ if (ast->_state != AST_STATE_UP) {
+ sub->alreadygone = 1;
+ send_text(TEXT_LINE2, TEXT_NORMAL, s, "Busy");
+ in_band_indication(ast, l->parent->tz, "busy");
+ s->device->missed_call = -1;
+ break;
+ }
+ return -1;
+ case AST_CONTROL_CONGESTION:
+ if (ast->_state != AST_STATE_UP) {
+ sub->alreadygone = 1;
+ send_text(TEXT_LINE2, TEXT_NORMAL, s, "Congestion");
+ in_band_indication(ast, l->parent->tz, "congestion");
+ s->device->missed_call = -1;
+ break;
+ }
+ return -1;
+ case AST_CONTROL_HOLD:
+ ast_moh_start(ast, data, NULL);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_moh_stop(ast);
+ break;
+ case AST_CONTROL_PROGRESS:
+ break;
+ case -1:
+ ast_playtones_stop(ast);
+ s->device->missed_call = 0;
+ break;
+ case AST_CONTROL_PROCEEDING:
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", ind);
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct unistim_subchannel *find_subchannel_by_name(const char *dest)
+{
+ struct unistim_line *l;
+ struct unistim_device *d;
+ char line[256];
+ char *at;
+ char *device;
+
+ ast_copy_string(line, dest, sizeof(line));
+ at = strchr(line, '@');
+ if (!at) {
+ ast_log(LOG_NOTICE, "Device '%s' has no @ (at) sign!\n", dest);
+ return NULL;
+ }
+ *at = '\0';
+ at++;
+ device = at;
+ ast_mutex_lock(&devicelock);
+ d = devices;
+ at = strchr(device, '/'); /* Extra options ? */
+ if (at)
+ *at = '\0';
+ while (d) {
+ if (!strcasecmp(d->name, device)) {
+ if (unistimdebug)
+ ast_verbose("Found device: %s\n", d->name);
+ /* Found the device */
+ l = d->lines;
+ while (l) {
+ /* Search for the right line */
+ if (!strcasecmp(l->name, line)) {
+ l->subs[SUB_REAL]->ringvolume = -1;
+ l->subs[SUB_REAL]->ringstyle = -1;
+ if (at) { /* Other options ? */
+ at++; /* Skip slash */
+ if (*at == 'r') { /* distinctive ring */
+ at++;
+ if ((*at < '0') || (*at > '7')) /* ring style */
+ ast_log(LOG_WARNING, "Invalid ring selection (%s)", at);
+ else {
+ char ring_volume = -1;
+ char ring_style = *at - '0';
+ at++;
+ if ((*at >= '0') && (*at <= '3')) /* ring volume */
+ ring_volume = *at - '0';
+ if (unistimdebug)
+ ast_verbose
+ ("Distinctive ring : style #%d volume %d\n",
+ ring_style, ring_volume);
+ l->subs[SUB_REAL]->ringvolume = ring_volume;
+ l->subs[SUB_REAL]->ringstyle = ring_style;
+ }
+ }
+ }
+ ast_mutex_unlock(&devicelock);
+ return l->subs[SUB_REAL];
+ }
+ l = l->next;
+ }
+ }
+ d = d->next;
+ }
+ /* Device not found */
+ ast_mutex_unlock(&devicelock);
+
+ return NULL;
+}
+
+static int unistim_senddigit_begin(struct ast_channel *ast, char digit)
+{
+ struct unistimsession *pte = channel_to_session(ast);
+
+ if (!pte)
+ return -1;
+
+ return unistim_do_senddigit(pte, digit);
+}
+
+static int unistim_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct unistimsession *pte = channel_to_session(ast);
+ struct ast_frame f = { 0, };
+ struct unistim_subchannel *sub;
+
+ sub = pte->device->lines->subs[SUB_REAL];
+
+ if (!sub->owner) {
+ ast_log(LOG_WARNING, "Unable to find subchannel in dtmf senddigiti_end\n");
+ return -1;
+ }
+
+ if (unistimdebug)
+ ast_verbose("Send Digit off %c\n", digit);
+
+ if (!pte)
+ return -1;
+
+ send_tone(pte, 0, 0);
+ f.frametype = AST_FRAME_DTMF;
+ f.subclass = digit;
+ f.src = "unistim";
+ ast_queue_frame(sub->owner, &f);
+
+ return 0;
+}
+
+/*--- unistim_sendtext: Display a text on the phone screen ---*/
+/* Called from PBX core text message functions */
+static int unistim_sendtext(struct ast_channel *ast, const char *text)
+{
+ struct unistimsession *pte = channel_to_session(ast);
+ int size;
+ char tmp[TEXT_LENGTH_MAX + 1];
+
+ if (unistimdebug)
+ ast_verbose("unistim_sendtext called\n");
+
+ if (!text) {
+ ast_log(LOG_WARNING, "unistim_sendtext called with a null text\n");
+ return 1;
+ }
+
+ size = strlen(text);
+ if (text[0] == '@') {
+ int pos = 0, i = 1, tok = 0, sz = 0;
+ char label[11];
+ char number[16];
+ char icon = '\0';
+ char cur = '\0';
+
+ memset(label, 0, 11);
+ memset(number, 0, 16);
+ while (text[i]) {
+ cur = text[i++];
+ switch (tok) {
+ case 0:
+ if ((cur < '0') && (cur > '5')) {
+ ast_log(LOG_WARNING,
+ "sendtext failed : position must be a number beetween 0 and 5\n");
+ return 1;
+ }
+ pos = cur - '0';
+ tok = 1;
+ continue;
+ case 1:
+ if (cur != '@') {
+ ast_log(LOG_WARNING, "sendtext failed : invalid position\n");
+ return 1;
+ }
+ tok = 2;
+ continue;
+ case 2:
+ if ((cur < '3') && (cur > '6')) {
+ ast_log(LOG_WARNING,
+ "sendtext failed : icon must be a number beetween 32 and 63 (first digit invalid)\n");
+ return 1;
+ }
+ icon = (cur - '0') * 10;
+ tok = 3;
+ continue;
+ case 3:
+ if ((cur < '0') && (cur > '9')) {
+ ast_log(LOG_WARNING,
+ "sendtext failed : icon must be a number beetween 32 and 63 (second digit invalid)\n");
+ return 1;
+ }
+ icon += (cur - '0');
+ tok = 4;
+ continue;
+ case 4:
+ if (cur != '@') {
+ ast_log(LOG_WARNING,
+ "sendtext failed : icon must be a number beetween 32 and 63 (too many digits)\n");
+ return 1;
+ }
+ tok = 5;
+ continue;
+ case 5:
+ if (cur == '@') {
+ tok = 6;
+ sz = 0;
+ continue;
+ }
+ if (sz > 10)
+ continue;
+ label[sz] = cur;
+ sz++;
+ continue;
+ case 6:
+ if (sz > 15) {
+ ast_log(LOG_WARNING,
+ "sendtext failed : extension too long = %d (15 car max)\n",
+ sz);
+ return 1;
+ }
+ number[sz] = cur;
+ sz++;
+ continue;
+ }
+ }
+ if (tok != 6) {
+ ast_log(LOG_WARNING, "sendtext failed : incomplet command\n");
+ return 1;
+ }
+ if (!pte->device) {
+ ast_log(LOG_WARNING, "sendtext failed : no device ?\n");
+ return 1;
+ }
+ strcpy(pte->device->softkeylabel[pos], label);
+ strcpy(pte->device->softkeynumber[pos], number);
+ pte->device->softkeyicon[pos] = icon;
+ send_favorite(pos, icon, pte, label);
+ return 0;
+ }
+
+ if (size <= TEXT_LENGTH_MAX * 2) {
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, "Message :");
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, text);
+ if (size <= TEXT_LENGTH_MAX) {
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, "");
+ return 0;
+ }
+ memcpy(tmp, text + TEXT_LENGTH_MAX, TEXT_LENGTH_MAX);
+ tmp[sizeof(tmp) - 1] = '\0';
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmp);
+ return 0;
+ }
+ send_text(TEXT_LINE0, TEXT_NORMAL, pte, text);
+ memcpy(tmp, text + TEXT_LENGTH_MAX, TEXT_LENGTH_MAX);
+ tmp[sizeof(tmp) - 1] = '\0';
+ send_text(TEXT_LINE1, TEXT_NORMAL, pte, tmp);
+ memcpy(tmp, text + TEXT_LENGTH_MAX * 2, TEXT_LENGTH_MAX);
+ tmp[sizeof(tmp) - 1] = '\0';
+ send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmp);
+ return 0;
+}
+
+/*--- unistim_send_mwi_to_peer: Send message waiting indication ---*/
+static int unistim_send_mwi_to_peer(struct unistimsession *s, unsigned int tick)
+{
+ struct ast_event *event;
+ int new, old;
+ char *mailbox, *context;
+ struct unistim_line *peer = s->device->lines;
+
+ context = mailbox = ast_strdupa(peer->mailbox);
+ strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+
+ event = ast_event_get_cached(AST_EVENT_MWI,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
+ AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_EXISTS,
+ AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_EXISTS,
+ AST_EVENT_IE_END);
+
+ if (event) {
+ new = ast_event_get_ie_uint(event, AST_EVENT_IE_NEWMSGS);
+ old = ast_event_get_ie_uint(event, AST_EVENT_IE_OLDMSGS);
+ ast_event_destroy(event);
+ } else /* Fall back on checking the mailbox directly */
+ ast_app_inboxcount(peer->mailbox, &new, &old);
+
+ peer->nextmsgcheck = tick + TIMER_MWI;
+
+ /* Return now if it's the same thing we told them last time */
+ if (((new << 8) | (old)) == peer->lastmsgssent)
+ return 0;
+
+ peer->lastmsgssent = ((new << 8) | (old));
+ if (new == 0)
+ send_led_update(s, 0);
+ else
+ send_led_update(s, 1);
+
+ return 0;
+}
+
+/*--- unistim_new: Initiate a call in the UNISTIM channel */
+/* called from unistim_request (calls from the pbx ) */
+static struct ast_channel *unistim_new(struct unistim_subchannel *sub, int state)
+{
+ struct ast_channel *tmp;
+ struct unistim_line *l;
+ int fmt;
+
+ if (!sub) {
+ ast_log(LOG_WARNING, "subchannel null in unistim_new\n");
+ return NULL;
+ }
+ if (!sub->parent) {
+ ast_log(LOG_WARNING, "no line for subchannel %p\n", sub);
+ return NULL;
+ }
+ l = sub->parent;
+ tmp = ast_channel_alloc(1, state, l->cid_num, NULL, l->accountcode, l->exten,
+ l->context, l->amaflags, "%s-%08x", l->fullname, (int) (long) sub);
+ if (unistimdebug)
+ ast_verbose("unistim_new sub=%d (%p) chan=%p\n", sub->subtype, sub, tmp);
+ if (!tmp) {
+ ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+ return NULL;
+ }
+
+ tmp->nativeformats = l->capability;
+ if (!tmp->nativeformats)
+ tmp->nativeformats = CAPABILITY;
+ fmt = ast_best_codec(tmp->nativeformats);
+ if (unistimdebug)
+ ast_verbose
+ ("Best codec = %d from nativeformats %d (line cap=%d global=%d)\n", fmt,
+ tmp->nativeformats, l->capability, CAPABILITY);
+ ast_string_field_build(tmp, name, "USTM/%s@%s-%d", l->name, l->parent->name,
+ sub->subtype);
+ if ((sub->rtp) && (sub->subtype == 0)) {
+ if (unistimdebug)
+ ast_verbose("New unistim channel with a previous rtp handle ?\n");
+ tmp->fds[0] = ast_rtp_fd(sub->rtp);
+ tmp->fds[1] = ast_rtcp_fd(sub->rtp);
+ }
+ if (sub->rtp)
+ ast_jb_configure(tmp, &global_jbconf);
+
+/* tmp->type = type; */
+ ast_setstate(tmp, state);
+ if (state == AST_STATE_RING)
+ tmp->rings = 1;
+ tmp->writeformat = fmt;
+ tmp->rawwriteformat = fmt;
+ tmp->readformat = fmt;
+ tmp->rawreadformat = fmt;
+ tmp->tech_pvt = sub;
+ tmp->tech = &unistim_tech;
+ if (!ast_strlen_zero(l->language))
+ ast_string_field_set(tmp, language, l->language);
+ sub->owner = tmp;
+ ast_mutex_lock(&usecnt_lock);
+ usecnt++;
+ ast_mutex_unlock(&usecnt_lock);
+ ast_update_use_count();
+ tmp->callgroup = l->callgroup;
+ tmp->pickupgroup = l->pickupgroup;
+ ast_string_field_set(tmp, call_forward, l->parent->call_forward);
+ if (!ast_strlen_zero(l->cid_num)) {
+ char *name, *loc, *instr;
+ instr = ast_strdup(l->cid_num);
+ if (instr) {
+ ast_callerid_parse(instr, &name, &loc);
+ tmp->cid.cid_num = ast_strdup(loc);
+ tmp->cid.cid_name = ast_strdup(name);
+ ast_free(instr);
+ }
+ }
+ tmp->priority = 1;
+ if (state != AST_STATE_DOWN) {
+ if (unistimdebug)
+ ast_verbose("Starting pbx in unistim_new\n");
+ if (ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ ast_hangup(tmp);
+ tmp = NULL;
+ }
+ }
+
+ return tmp;
+}
+
+static void *do_monitor(void *data)
+{
+ struct unistimsession *cur = NULL;
+ unsigned int dw_timeout = 0;
+ unsigned int tick;
+ int res;
+ int reloading;
+
+ /* Add an I/O event to our UDP socket */
+ if (unistimsock > -1)
+ ast_io_add(io, unistimsock, unistimsock_read, AST_IO_IN, NULL);
+
+ /* This thread monitors our UDP socket and timers */
+ for (;;) {
+ /* This loop is executed at least every IDLE_WAITus (1s) or every time a packet is received */
+ /* Looking for the smallest time-out value */
+ tick = get_tick_count();
+ dw_timeout = UINT_MAX;
+ ast_mutex_lock(&sessionlock);
+ cur = sessions;
+ DEBUG_TIMER("checking timeout for session %p with tick = %u\n", cur, tick);
+ while (cur) {
+ DEBUG_TIMER("checking timeout for session %p timeout = %u\n", cur,
+ cur->timeout);
+ /* Check if we have miss something */
+ if (cur->timeout <= tick) {
+ DEBUG_TIMER("Event for session %p\n", cur);
+ /* If the queue is empty, send a ping */
+ if (cur->last_buf_available == 0)
+ send_ping(cur);
+ else {
+ if (send_retransmit(cur)) {
+ DEBUG_TIMER("The chained link was modified, restarting...\n");
+ cur = sessions;
+ dw_timeout = UINT_MAX;
+ continue;
+ }
+ }
+ }
+ if (dw_timeout > cur->timeout - tick)
+ dw_timeout = cur->timeout - tick;
+ /* Checking if the phone is logged on for a new MWI */
+ if (cur->device) {
+ if ((!ast_strlen_zero(cur->device->lines->mailbox)) &&
+ ((tick >= cur->device->lines->nextmsgcheck))) {
+ DEBUG_TIMER("Checking mailbox for MWI\n");
+ unistim_send_mwi_to_peer(cur, tick);
+ break;
+ }
+ }
+ cur = cur->next;
+ }
+ ast_mutex_unlock(&sessionlock);
+ DEBUG_TIMER("Waiting for %dus\n", dw_timeout);
+ res = dw_timeout;
+ /* We should not wait more than IDLE_WAIT */
+ if ((res < 0) || (res > IDLE_WAIT))
+ res = IDLE_WAIT;
+ /* Wait for UDP messages for a maximum of res us */
+ res = ast_io_wait(io, res); /* This function will call unistimsock_read if a packet is received */
+ /* Check for a reload request */
+ ast_mutex_lock(&unistim_reload_lock);
+ reloading = unistim_reloading;
+ unistim_reloading = 0;
+ ast_mutex_unlock(&unistim_reload_lock);
+ if (reloading) {
+ if (option_verbose > 0)
+ ast_verbose(VERBOSE_PREFIX_1 "Reloading unistim.conf...\n");
+ reload_config();
+ }
+ pthread_testcancel();
+ }
+ /* Never reached */
+ return NULL;
+}
+
+/*--- restart_monitor: Start the channel monitor thread ---*/
+static int restart_monitor(void)
+{
+ pthread_attr_t attr;
+ /* If we're supposed to be stopped -- stay stopped */
+ if (monitor_thread == AST_PTHREADT_STOP)
+ return 0;
+ if (ast_mutex_lock(&monlock)) {
+ ast_log(LOG_WARNING, "Unable to lock monitor\n");
+ return -1;
+ }
+ if (monitor_thread == pthread_self()) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_WARNING, "Cannot kill myself\n");
+ return -1;
+ }
+ if (monitor_thread != AST_PTHREADT_NULL) {
+ /* Wake up the thread */
+ pthread_kill(monitor_thread, SIGURG);
+ } else {
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ /* Start a new monitor */
+ if (ast_pthread_create(&monitor_thread, &attr, do_monitor, NULL) < 0) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
+ return -1;
+ }
+ }
+ ast_mutex_unlock(&monlock);
+ return 0;
+}
+
+/*--- unistim_request: PBX interface function ---*/
+/* UNISTIM calls initiated by the PBX arrive here */
+static struct ast_channel *unistim_request(const char *type, int format, void *data,
+ int *cause)
+{
+ int oldformat;
+ struct unistim_subchannel *sub;
+ struct ast_channel *tmpc = NULL;
+ char tmp[256];
+ char *dest = data;
+
+ oldformat = format;
+ format &= CAPABILITY;
+ ast_log(LOG_NOTICE,
+ "Asked to get a channel of format %s while capability is %d result : %s (%d) \n",
+ ast_getformatname(oldformat), CAPABILITY, ast_getformatname(format), format);
+ if (!format) {
+ ast_log(LOG_NOTICE,
+ "Asked to get a channel of unsupported format %s while capability is %s\n",
+ ast_getformatname(oldformat), ast_getformatname(CAPABILITY));
+ return NULL;
+ }
+
+ ast_copy_string(tmp, dest, sizeof(tmp));
+ if (ast_strlen_zero(tmp)) {
+ ast_log(LOG_NOTICE, "Unistim channels require a device\n");
+ return NULL;
+ }
+
+ sub = find_subchannel_by_name(tmp);
+ if (!sub) {
+ ast_log(LOG_NOTICE, "No available lines on: %s\n", dest);
+ *cause = AST_CAUSE_CONGESTION;
+ return NULL;
+ }
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "unistim_request(%s)\n", tmp);
+ /* Busy ? */
+ if (sub->owner) {
+ if (unistimdebug)
+ ast_verbose("Can't create channel : Busy !\n");
+ *cause = AST_CAUSE_BUSY;
+ return NULL;
+ }
+ sub->parent->capability = format;
+ tmpc = unistim_new(sub, AST_STATE_DOWN);
+ if (!tmpc)
+ ast_log(LOG_WARNING, "Unable to make channel for '%s'\n", tmp);
+ if (unistimdebug)
+ ast_verbose("unistim_request owner = %p\n", sub->owner);
+ restart_monitor();
+
+ /* and finish */
+ return tmpc;
+}
+
+static char *unistim_info(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct unistim_device *device = devices;
+ struct unistim_line *line;
+ struct unistim_subchannel *sub;
+ struct unistimsession *s;
+ int i;
+ struct ast_channel *tmp;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "unistim info";
+ e->usage =
+ "Usage: unistim info\n"
+ " Dump internal structures.\n";
+ return NULL;
+
+ case CLI_GENERATE:
+ return NULL; /* no completion */
+ }
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+
+ ast_cli(a->fd, "Dumping internal structures :\ndevice\n->line\n-->sub\n");
+ while (device) {
+ ast_cli(a->fd, "\nname=%s id=%s line=%p ha=%p sess=%p device=%p\n",
+ device->name, device->id, device->lines, device->ha, device->session,
+ device);
+ line = device->lines;
+ while (line) {
+ ast_cli(a->fd,
+ "->name=%s fullname=%s exten=%s callid=%s cap=%d device=%p line=%p\n",
+ line->name, line->fullname, line->exten, line->cid_num,
+ line->capability, line->parent, line);
+ for (i = 0; i < MAX_SUBS; i++) {
+ sub = line->subs[i];
+ if (!sub)
+ continue;
+ if (!sub->owner)
+ tmp = (void *) -42;
+ else
+ tmp = sub->owner->_bridge;
+ if (sub->subtype != i)
+ ast_cli(a->fd, "Warning ! subchannel->subs[%d] have a subtype=%d\n", i,
+ sub->subtype);
+ ast_cli(a->fd,
+ "-->subtype=%d chan=%p rtp=%p bridge=%p line=%p alreadygone=%d\n",
+ sub->subtype, sub->owner, sub->rtp, tmp, sub->parent,
+ sub->alreadygone);
+ }
+ line = line->next;
+ }
+ device = device->next;
+ }
+ ast_cli(a->fd, "\nSessions:\n");
+ ast_mutex_lock(&sessionlock);
+ s = sessions;
+ while (s) {
+ ast_cli(a->fd,
+ "sin=%s timeout=%u state=%d macaddr=%s device=%p session=%p\n",
+ ast_inet_ntoa(s->sin.sin_addr), s->timeout, s->state, s->macaddr,
+ s->device, s);
+ s = s->next;
+ }
+ ast_mutex_unlock(&sessionlock);
+
+ return CLI_SUCCESS;
+}
+
+static char *unistim_sp(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ BUFFSEND;
+ struct unistim_subchannel *sub;
+ int i, j = 0, len;
+ unsigned char c, cc;
+ char tmp[256];
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "unistim sp";
+ e->usage =
+ "Usage: unistim sp USTM/line@name hexa\n"
+ " unistim sp USTM/1000@hans 19040004\n";
+ return NULL;
+
+ case CLI_GENERATE:
+ return NULL; /* no completion */
+ }
+
+ if (a->argc < 4)
+ return CLI_SHOWUSAGE;
+
+ if (strlen(a->argv[2]) < 9)
+ return CLI_SHOWUSAGE;
+
+ len = strlen(a->argv[3]);
+ if (len % 2)
+ return CLI_SHOWUSAGE;
+
+ ast_copy_string(tmp, a->argv[2] + 5, sizeof(tmp));
+ sub = find_subchannel_by_name(tmp);
+ if (!sub) {
+ ast_cli(a->fd, "Can't find '%s'\n", tmp);
+ return CLI_SUCCESS;
+ }
+ if (!sub->parent->parent->session) {
+ ast_cli(a->fd, "'%s' is not connected\n", tmp);
+ return CLI_SUCCESS;
+ }
+ ast_cli(a->fd, "Sending '%s' to %s (%p)\n", a->argv[3], tmp, sub->parent->parent->session);
+ for (i = 0; i < len; i++) {
+ c = a->argv[3][i];
+ if (c >= 'a')
+ c -= 'a' - 10;
+ else
+ c -= '0';
+ i++;
+ cc = a->argv[3][i];
+ if (cc >= 'a')
+ cc -= 'a' - 10;
+ else
+ cc -= '0';
+ tmp[j++] = (c << 4) | cc;
+ }
+ memcpy(buffsend + SIZE_HEADER, tmp, j);
+ send_client(SIZE_HEADER + j, buffsend, sub->parent->parent->session);
+ return CLI_SUCCESS;
+}
+
+static char *unistim_do_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "unistim set debug {on|off}";
+ e->usage =
+ "Usage: unistim set debug\n"
+ " Display debug messages.\n";
+ return NULL;
+
+ case CLI_GENERATE:
+ return NULL; /* no completion */
+ }
+
+ if (a->argc != e->args)
+ return CLI_SHOWUSAGE;
+
+ if (!strcasecmp(a->argv[3], "on")) {
+ unistimdebug = 1;
+ ast_cli(a->fd, "UNISTIM Debugging Enabled\n");
+ } else if (!strcasecmp(a->argv[3], "off")) {
+ unistimdebug = 0;
+ ast_cli(a->fd, "UNISTIM Debugging Disabled\n");
+ } else
+ return CLI_SHOWUSAGE;
+
+ return CLI_SUCCESS;
+}
+
+/*! \brief --- unistim_reload: Force reload of module from cli ---
+ * Runs in the asterisk main thread, so don't do anything useful
+ * but setting a flag and waiting for do_monitor to do the job
+ * in our thread */
+static char *unistim_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "unistim reload";
+ e->usage =
+ "Usage: unistim reload\n"
+ " Reloads UNISTIM configuration from unistim.conf\n";
+ return NULL;
+
+ case CLI_GENERATE:
+ return NULL; /* no completion */
+ }
+
+ if (e && a && a->argc != e->args)
+ return CLI_SHOWUSAGE;
+
+ if (unistimdebug)
+ ast_verbose("reload unistim\n");
+
+ ast_mutex_lock(&unistim_reload_lock);
+ if (!unistim_reloading)
+ unistim_reloading = 1;
+ ast_mutex_unlock(&unistim_reload_lock);
+
+ restart_monitor();
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry unistim_cli[] = {
+ AST_CLI_DEFINE(unistim_reload, "Reload UNISTIM configuration"),
+ AST_CLI_DEFINE(unistim_info, "Show UNISTIM info"),
+ AST_CLI_DEFINE(unistim_sp, "Send packet (for reverse engineering)"),
+ AST_CLI_DEFINE(unistim_do_debug, "Toggle UNITSTIM debugging"),
+};
+
+static void unquote(char *out, const char *src, int maxlen)
+{
+ int len = strlen(src);
+ if (!len)
+ return;
+ if ((len > 1) && src[0] == '\"') {
+ /* This is a quoted string */
+ src++;
+ /* Don't take more than what's there */
+ len--;
+ if (maxlen > len - 1)
+ maxlen = len - 1;
+ memcpy(out, src, maxlen);
+ ((char *) out)[maxlen] = '\0';
+ } else
+ memcpy(out, src, maxlen);
+ return;
+}
+
+static int ParseBookmark(const char *text, struct unistim_device *d)
+{
+ char line[256];
+ char *at;
+ char *number;
+ char *icon;
+ int p;
+ int len = strlen(text);
+
+ ast_copy_string(line, text, sizeof(line));
+ /* Position specified ? */
+ if ((len > 2) && (line[1] == '@')) {
+ p = line[0];
+ if ((p >= '0') && (p <= '5'))
+ p -= '0';
+ else {
+ ast_log(LOG_WARNING,
+ "Invalid position for bookmark : must be between 0 and 5\n");
+ return 0;
+ }
+ if (d->softkeyicon[p] != 0) {
+ ast_log(LOG_WARNING, "Invalid position %d for bookmark : already used\n:", p);
+ return 0;
+ }
+ memmove(line, line + 2, sizeof(line));
+ } else {
+ /* No position specified, looking for a free slot */
+ for (p = 0; p <= 5; p++) {
+ if (!d->softkeyicon[p])
+ break;
+ }
+ if (p > 5) {
+ ast_log(LOG_WARNING, "No more free bookmark position\n");
+ return 0;
+ }
+ }
+ at = strchr(line, '@');
+ if (!at) {
+ ast_log(LOG_NOTICE, "Bookmark entry '%s' has no @ (at) sign!\n", text);
+ return 0;
+ }
+ *at = '\0';
+ at++;
+ number = at;
+ at = strchr(at, '@');
+ if (ast_strlen_zero(number)) {
+ ast_log(LOG_NOTICE, "Bookmark entry '%s' has no number\n", text);
+ return 0;
+ }
+ if (ast_strlen_zero(line)) {
+ ast_log(LOG_NOTICE, "Bookmark entry '%s' has no description\n", text);
+ return 0;
+ }
+
+ at = strchr(number, '@');
+ if (!at)
+ d->softkeyicon[p] = FAV_ICON_SHARP; /* default icon */
+ else {
+ *at = '\0';
+ at++;
+ icon = at;
+ if (ast_strlen_zero(icon)) {
+ ast_log(LOG_NOTICE, "Bookmark entry '%s' has no icon value\n", text);
+ return 0;
+ }
+ if (strncmp(icon, "USTM/", 5))
+ d->softkeyicon[p] = atoi(icon);
+ else {
+ d->softkeyicon[p] = 1;
+ ast_copy_string(d->softkeydevice[p], icon + 5, sizeof(d->softkeydevice[p]));
+ }
+ }
+ ast_copy_string(d->softkeylabel[p], line, sizeof(d->softkeylabel[p]));
+ ast_copy_string(d->softkeynumber[p], number, sizeof(d->softkeynumber[p]));
+ if (unistimdebug)
+ ast_verbose("New bookmark at pos %d label='%s' number='%s' icon=%x\n",
+ p, d->softkeylabel[p], d->softkeynumber[p], d->softkeyicon[p]);
+ return 1;
+}
+
+/* Looking for dynamic icons entries in bookmarks */
+static void finish_bookmark(void)
+{
+ struct unistim_device *d = devices;
+ int i;
+ while (d) {
+ for (i = 0; i < 6; i++) {
+ if (d->softkeyicon[i] == 1) { /* Something for us */
+ struct unistim_device *d2 = devices;
+ while (d2) {
+ if (!strcmp(d->softkeydevice[i], d2->name)) {
+ d->sp[i] = d2;
+ d->softkeyicon[i] = 0;
+ break;
+ }
+ d2 = d2->next;
+ }
+ if (d->sp[i] == NULL)
+ ast_log(LOG_NOTICE, "Bookmark entry with device %s not found\n",
+ d->softkeydevice[i]);
+ }
+ }
+ d = d->next;
+ }
+}
+
+static struct unistim_device *build_device(const char *cat, const struct ast_variable *v)
+{
+ struct unistim_device *d;
+ struct unistim_line *l = NULL;
+ int create = 1;
+ int nbsoftkey, dateformat, timeformat, callhistory;
+ char linelabel[AST_MAX_EXTENSION];
+ char context[AST_MAX_EXTENSION];
+ char ringvolume, ringstyle;
+
+ /* First, we need to know if we already have this name in our list */
+ /* Get a lock for the device chained list */
+ ast_mutex_lock(&devicelock);
+ d = devices;
+ while (d) {
+ if (!strcmp(d->name, cat)) {
+ /* Yep, we alreay have this one */
+ if (unistimsock < 0) {
+ /* It's a dupe */
+ ast_log(LOG_WARNING, "Duplicate entry found (%s), ignoring.\n", cat);
+ ast_mutex_unlock(&devicelock);
+ return NULL;
+ }
+ /* we're reloading right now */
+ create = 0;
+ l = d->lines;
+ break;
+ }
+ d = d->next;
+ }
+ ast_mutex_unlock(&devicelock);
+ if (create) {
+ if (!(d = ast_calloc(1, sizeof(*d))))
+ return NULL;
+
+ if (!(l = ast_calloc(1, sizeof(*l)))) {
+ ast_free(d);
+ return NULL;
+ }
+ ast_copy_string(d->name, cat, sizeof(d->name));
+ }
+ ast_copy_string(context, DEFAULTCONTEXT, sizeof(context));
+ d->contrast = -1;
+ d->output = OUTPUT_HANDSET;
+ d->previous_output = OUTPUT_HANDSET;
+ d->volume = VOLUME_LOW;
+ d->mute = MUTE_OFF;
+ linelabel[0] = '\0';
+ dateformat = 1;
+ timeformat = 1;
+ ringvolume = 2;
+ callhistory = 1;
+ ringstyle = 3;
+ nbsoftkey = 0;
+ while (v) {
+ if (!strcasecmp(v->name, "rtp_port"))
+ d->rtp_port = atoi(v->value);
+ else if (!strcasecmp(v->name, "rtp_method"))
+ d->rtp_method = atoi(v->value);
+ else if (!strcasecmp(v->name, "status_method"))
+ d->status_method = atoi(v->value);
+ else if (!strcasecmp(v->name, "device"))
+ ast_copy_string(d->id, v->value, sizeof(d->id));
+ else if (!strcasecmp(v->name, "tn"))
+ ast_copy_string(d->extension_number, v->value, sizeof(d->extension_number));
+ else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny"))
+ d->ha = ast_append_ha(v->name, v->value, d->ha, NULL);
+ else if (!strcasecmp(v->name, "context"))
+ ast_copy_string(context, v->value, sizeof(context));
+ else if (!strcasecmp(v->name, "maintext0"))
+ unquote(d->maintext0, v->value, sizeof(d->maintext0) - 1);
+ else if (!strcasecmp(v->name, "maintext1"))
+ unquote(d->maintext1, v->value, sizeof(d->maintext1) - 1);
+ else if (!strcasecmp(v->name, "maintext2"))
+ unquote(d->maintext2, v->value, sizeof(d->maintext2) - 1);
+ else if (!strcasecmp(v->name, "titledefault"))
+ unquote(d->titledefault, v->value, sizeof(d->titledefault) - 1);
+ else if (!strcasecmp(v->name, "dateformat"))
+ dateformat = atoi(v->value);
+ else if (!strcasecmp(v->name, "timeformat"))
+ timeformat = atoi(v->value);
+ else if (!strcasecmp(v->name, "contrast")) {
+ d->contrast = atoi(v->value);
+ if ((d->contrast < 0) || (d->contrast > 15)) {
+ ast_log(LOG_WARNING, "constrast must be beetween 0 and 15");
+ d->contrast = 8;
+ }
+ } else if (!strcasecmp(v->name, "nat"))
+ d->nat = ast_true(v->value);
+ else if (!strcasecmp(v->name, "ringvolume"))
+ ringvolume = atoi(v->value);
+ else if (!strcasecmp(v->name, "ringstyle"))
+ ringstyle = atoi(v->value);
+ else if (!strcasecmp(v->name, "callhistory"))
+ callhistory = atoi(v->value);
+ else if (!strcasecmp(v->name, "callerid")) {
+ if (!strcasecmp(v->value, "asreceived"))
+ l->cid_num[0] = '\0';
+ else
+ ast_copy_string(l->cid_num, v->value, sizeof(l->cid_num));
+ } else if (!strcasecmp(v->name, "language"))
+ ast_copy_string(l->language, v->value, sizeof(l->language));
+ else if (!strcasecmp(v->name, "country"))
+ ast_copy_string(d->country, v->value, sizeof(d->country));
+ else if (!strcasecmp(v->name, "accountcode"))
+ ast_copy_string(l->accountcode, v->value, sizeof(l->accountcode));
+ else if (!strcasecmp(v->name, "amaflags")) {
+ int y;
+ y = ast_cdr_amaflags2int(v->value);
+ if (y < 0)
+ ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value,
+ v->lineno);
+ else
+ l->amaflags = y;
+ } else if (!strcasecmp(v->name, "musiconhold"))
+ ast_copy_string(l->musicclass, v->value, sizeof(l->musicclass));
+ else if (!strcasecmp(v->name, "callgroup"))
+ l->callgroup = ast_get_group(v->value);
+ else if (!strcasecmp(v->name, "pickupgroup"))
+ l->pickupgroup = ast_get_group(v->value);
+ else if (!strcasecmp(v->name, "mailbox"))
+ ast_copy_string(l->mailbox, v->value, sizeof(l->mailbox));
+ else if (!strcasecmp(v->name, "linelabel"))
+ unquote(linelabel, v->value, sizeof(linelabel) - 1);
+ else if (!strcasecmp(v->name, "extension")) {
+ if (!strcasecmp(v->value, "none"))
+ d->extension = EXTENSION_NONE;
+ else if (!strcasecmp(v->value, "ask"))
+ d->extension = EXTENSION_ASK;
+ else if (!strcasecmp(v->value, "line"))
+ d->extension = EXTENSION_LINE;
+ else
+ ast_log(LOG_WARNING, "Unknown extension option.\n");
+ } else if (!strcasecmp(v->name, "bookmark")) {
+ if (nbsoftkey > 5)
+ ast_log(LOG_WARNING,
+ "More than 6 softkeys defined. Ignoring new entries.\n");
+ else {
+ if (ParseBookmark(v->value, d))
+ nbsoftkey++;
+ }
+ } else if (!strcasecmp(v->name, "line")) {
+ int len = strlen(linelabel);
+
+ if (nbsoftkey) {
+ ast_log(LOG_WARNING,
+ "You must use bookmark AFTER line=>. Only one line is supported in this version\n");
+ if (create) {
+ ast_free(d);
+ ast_free(l);
+ }
+ return NULL;
+ }
+ if (create) {
+ ast_mutex_init(&l->lock);
+ } else {
+ d->to_delete = 0;
+ /* reset bookmarks */
+ memset(d->softkeylabel, 0, sizeof(d->softkeylabel));
+ memset(d->softkeynumber, 0, sizeof(d->softkeynumber));
+ memset(d->softkeyicon, 0, sizeof(d->softkeyicon));
+ memset(d->softkeydevice, 0, sizeof(d->softkeydevice));
+ memset(d->sp, 0, sizeof(d->sp));
+ }
+ ast_copy_string(l->name, v->value, sizeof(l->name));
+ snprintf(l->fullname, sizeof(l->fullname), "USTM/%s@%s", l->name, d->name);
+ d->softkeyicon[0] = FAV_ICON_ONHOOK_BLACK;
+ if (!len) /* label is undefined ? */
+ ast_copy_string(d->softkeylabel[0], v->value, sizeof(d->softkeylabel[0]));
+ else {
+ if ((len > 2) && (linelabel[1] == '@')) {
+ d->softkeylinepos = linelabel[0];
+ if ((d->softkeylinepos >= '0') && (d->softkeylinepos <= '5')) {
+ d->softkeylinepos -= '0';
+ d->softkeyicon[0] = 0;
+ } else {
+ ast_log(LOG_WARNING,
+ "Invalid position for linelabel : must be between 0 and 5\n");
+ d->softkeylinepos = 0;
+ }
+ ast_copy_string(d->softkeylabel[d->softkeylinepos], linelabel + 2,
+ sizeof(d->softkeylabel[d->softkeylinepos]));
+ d->softkeyicon[d->softkeylinepos] = FAV_ICON_ONHOOK_BLACK;
+ } else
+ ast_copy_string(d->softkeylabel[0], linelabel,
+ sizeof(d->softkeylabel[0]));
+ }
+ nbsoftkey++;
+ ast_copy_string(l->context, context, sizeof(l->context));
+ if (!ast_strlen_zero(l->mailbox)) {
+ if (unistimdebug)
+ ast_verbose(VERBOSE_PREFIX_3 "Setting mailbox '%s' on %s@%s\n",
+ l->mailbox, d->name, l->name);
+ }
+
+ l->capability = CAPABILITY;
+ l->parent = d;
+
+ if (create) {
+ if (!alloc_sub(l, SUB_REAL)) {
+ ast_mutex_destroy(&l->lock);
+ ast_free(l);
+ ast_free(d);
+ return NULL;
+ }
+ l->next = d->lines;
+ d->lines = l;
+ }
+ } else
+ ast_log(LOG_WARNING, "Don't know keyword '%s' at line %d\n", v->name,
+ v->lineno);
+ v = v->next;
+ }
+ d->ringvolume = ringvolume;
+ d->ringstyle = ringstyle;
+ d->callhistory = callhistory;
+ d->tz = ast_get_indication_zone(d->country);
+ if ((d->tz == NULL) && !ast_strlen_zero(d->country))
+ ast_log(LOG_WARNING, "Country '%s' was not found in indications.conf\n",
+ d->country);
+ d->datetimeformat = 56 + (dateformat * 4);
+ d->datetimeformat += timeformat;
+ if (!d->lines) {
+ ast_log(LOG_ERROR, "An Unistim device must have at least one line!\n");
+ ast_mutex_destroy(&l->lock);
+ ast_free(l);
+ ast_free(d);
+ return NULL;
+ }
+ if ((autoprovisioning == AUTOPROVISIONING_TN) &&
+ (!ast_strlen_zero(d->extension_number))) {
+ d->extension = EXTENSION_TN;
+ if (!ast_strlen_zero(d->id))
+ ast_log(LOG_WARNING,
+ "tn= and device= can't be used together. Ignoring device= entry\n");
+ d->id[0] = 'T'; /* magic : this is a tn entry */
+ ast_copy_string((d->id) + 1, d->extension_number, sizeof(d->id) - 1);
+ d->extension_number[0] = '\0';
+ } else if (ast_strlen_zero(d->id)) {
+ if (strcmp(d->name, "template")) {
+ ast_log(LOG_ERROR, "You must specify the mac address with device=\n");
+ ast_mutex_destroy(&l->lock);
+ ast_free(l);
+ ast_free(d);
+ return NULL;
+ } else
+ strcpy(d->id, "000000000000");
+ }
+ if (!d->rtp_port)
+ d->rtp_port = 10000;
+ if (d->contrast == -1)
+ d->contrast = 8;
+ if (ast_strlen_zero(d->maintext0))
+ strcpy(d->maintext0, "Welcome");
+ if (ast_strlen_zero(d->maintext1))
+ strcpy(d->maintext1, d->name);
+ if (ast_strlen_zero(d->titledefault)) {
+ struct ast_tm tm = { 0, };
+ struct timeval cur_time = ast_tvnow();
+
+ if ((ast_localtime(&cur_time, &tm, 0)) == 0 || ast_strlen_zero(tm.tm_zone)) {
+ display_last_error("Error in ast_localtime()");
+ ast_copy_string(d->titledefault, "UNISTIM for*", 12);
+ } else {
+ if (strlen(tm.tm_zone) < 4) {
+ strcpy(d->titledefault, "TimeZone ");
+ strcat(d->titledefault, tm.tm_zone);
+ } else if (strlen(tm.tm_zone) < 9) {
+ strcpy(d->titledefault, "TZ ");
+ strcat(d->titledefault, tm.tm_zone);
+ } else
+ ast_copy_string(d->titledefault, tm.tm_zone, 12);
+ }
+ }
+ /* Update the chained link if it's a new device */
+ if (create) {
+ ast_mutex_lock(&devicelock);
+ d->next = devices;
+ devices = d;
+ ast_mutex_unlock(&devicelock);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Added device '%s'\n", d->name);
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Device '%s' reloaded\n", d->name);
+ }
+ return d;
+}
+
+/*--- reload_config: Re-read unistim.conf config file ---*/
+static int reload_config(void)
+{
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ struct ast_hostent ahp;
+ struct hostent *hp;
+ struct sockaddr_in bindaddr = { 0, };
+ char *config = "unistim.conf";
+ char *cat;
+ struct unistim_device *d;
+ const int reuseFlag = 1;
+ struct unistimsession *s;
+ struct ast_flags config_flags = { 0, };
+
+ cfg = ast_config_load(config, config_flags);
+ /* We *must* have a config file otherwise stop immediately */
+ if (!cfg) {
+ ast_log(LOG_ERROR, "Unable to load config %s\n", config);
+ return -1;
+ }
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ unistim_keepalive = 120;
+ unistim_port = 0;
+ v = ast_variable_browse(cfg, "general");
+ while (v) {
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
+ continue;
+
+ if (!strcasecmp(v->name, "keepalive"))
+ unistim_keepalive = atoi(v->value);
+ else if (!strcasecmp(v->name, "port"))
+ unistim_port = atoi(v->value);
+ else if (!strcasecmp(v->name, "tos")) {
+ if (ast_str2tos(v->value, &tos))
+ ast_log(LOG_WARNING, "Invalid tos value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "tos_audio")) {
+ if (ast_str2tos(v->value, &tos_audio))
+ ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos")) {
+ if (ast_str2cos(v->value, &cos))
+ ast_log(LOG_WARNING, "Invalid cos value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "cos_audio")) {
+ if (ast_str2cos(v->value, &cos_audio))
+ ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "autoprovisioning")) {
+ if (!strcasecmp(v->value, "no"))
+ autoprovisioning = AUTOPROVISIONING_NO;
+ else if (!strcasecmp(v->value, "yes"))
+ autoprovisioning = AUTOPROVISIONING_YES;
+ else if (!strcasecmp(v->value, "db"))
+ autoprovisioning = AUTOPROVISIONING_DB;
+ else if (!strcasecmp(v->value, "tn"))
+ autoprovisioning = AUTOPROVISIONING_TN;
+ else
+ ast_log(LOG_WARNING, "Unknown autoprovisioning option.\n");
+ } else if (!strcasecmp(v->name, "public_ip")) {
+ if (!ast_strlen_zero(v->value)) {
+ if (!(hp = ast_gethostbyname(v->value, &ahp)))
+ ast_log(LOG_WARNING, "Invalid address: %s\n", v->value);
+ else {
+ memcpy(&public_ip.sin_addr, hp->h_addr, sizeof(public_ip.sin_addr));
+ public_ip.sin_family = AF_INET;
+ }
+ }
+ }
+ v = v->next;
+ }
+ if ((unistim_keepalive < 10) ||
+ (unistim_keepalive >
+ 255 - (((NB_MAX_RETRANSMIT + 1) * RETRANSMIT_TIMER) / 1000))) {
+ ast_log(LOG_ERROR, "keepalive is invalid in %s\n", config);
+ ast_config_destroy(cfg);
+ return -1;
+ }
+ packet_send_ping[4] =
+ unistim_keepalive + (((NB_MAX_RETRANSMIT + 1) * RETRANSMIT_TIMER) / 1000);
+ if ((unistim_port < 1) || (unistim_port > 65535)) {
+ ast_log(LOG_ERROR, "port is not set or invalid in %s\n", config);
+ ast_config_destroy(cfg);
+ return -1;
+ }
+ unistim_keepalive *= 1000;
+
+ ast_mutex_lock(&devicelock);
+ d = devices;
+ while (d) {
+ if (d->to_delete >= 0)
+ d->to_delete = 1;
+ d = d->next;
+ }
+ ast_mutex_unlock(&devicelock);
+ /* load the device sections */
+ cat = ast_category_browse(cfg, NULL);
+ while (cat) {
+ if (strcasecmp(cat, "general")) {
+ d = build_device(cat, ast_variable_browse(cfg, cat));
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+ ast_mutex_lock(&devicelock);
+ d = devices;
+ while (d) {
+ if (d->to_delete) {
+ int i;
+
+ if (unistimdebug)
+ ast_verbose("Removing device '%s'\n", d->name);
+ if (!d->lines) {
+ ast_log(LOG_ERROR, "Device '%s' without a line !, aborting\n", d->name);
+ ast_config_destroy(cfg);
+ return 0;
+ }
+ if (!d->lines->subs[0]) {
+ ast_log(LOG_ERROR, "Device '%s' without a subchannel !, aborting\n",
+ d->name);
+ ast_config_destroy(cfg);
+ return 0;
+ }
+ if (d->lines->subs[0]->owner) {
+ ast_log(LOG_WARNING,
+ "Device '%s' was not deleted : a call is in progress. Try again later.\n",
+ d->name);
+ d = d->next;
+ continue;
+ }
+ ast_mutex_destroy(&d->lines->subs[0]->lock);
+ ast_free(d->lines->subs[0]);
+ for (i = 1; i < MAX_SUBS; i++) {
+ if (d->lines->subs[i]) {
+ ast_log(LOG_WARNING,
+ "Device '%s' with threeway call subchannels allocated, aborting.\n",
+ d->name);
+ break;
+ }
+ }
+ if (i < MAX_SUBS) {
+ d = d->next;
+ continue;
+ }
+ ast_mutex_destroy(&d->lines->lock);
+ ast_free(d->lines);
+ if (d->session) {
+ if (sessions == d->session)
+ sessions = d->session->next;
+ else {
+ s = sessions;
+ while (s) {
+ if (s->next == d->session) {
+ s->next = d->session->next;
+ break;
+ }
+ s = s->next;
+ }
+ }
+ ast_mutex_destroy(&d->session->lock);
+ ast_free(d->session);
+ }
+ if (devices == d)
+ devices = d->next;
+ else {
+ struct unistim_device *d2 = devices;
+ while (d2) {
+ if (d2->next == d) {
+ d2->next = d->next;
+ break;
+ }
+ d2 = d2->next;
+ }
+ }
+ ast_free(d);
+ d = devices;
+ continue;
+ }
+ d = d->next;
+ }
+ finish_bookmark();
+ ast_mutex_unlock(&devicelock);
+ ast_config_destroy(cfg);
+ ast_mutex_lock(&sessionlock);
+ s = sessions;
+ while (s) {
+ if (s->device)
+ refresh_all_favorite(s);
+ s = s->next;
+ }
+ ast_mutex_unlock(&sessionlock);
+ /* We don't recreate a socket when reloading (locks would be necessary). */
+ if (unistimsock > -1)
+ return 0;
+ bindaddr.sin_addr.s_addr = INADDR_ANY;
+ bindaddr.sin_port = htons(unistim_port);
+ bindaddr.sin_family = AF_INET;
+ unistimsock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (unistimsock < 0) {
+ ast_log(LOG_WARNING, "Unable to create UNISTIM socket: %s\n", strerror(errno));
+ return -1;
+ }
+#ifdef HAVE_PKTINFO
+ {
+ const int pktinfoFlag = 1;
+ setsockopt(unistimsock, IPPROTO_IP, IP_PKTINFO, &pktinfoFlag,
+ sizeof(pktinfoFlag));
+ }
+#else
+ if (public_ip.sin_family == 0) {
+ ast_log(LOG_WARNING,
+ "Your OS does not support IP_PKTINFO, you must set public_ip.\n");
+ unistimsock = -1;
+ return -1;
+ }
+#endif
+ setsockopt(unistimsock, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuseFlag,
+ sizeof(reuseFlag));
+ if (bind(unistimsock, (struct sockaddr *) &bindaddr, sizeof(bindaddr)) < 0) {
+ ast_log(LOG_WARNING, "Failed to bind to %s:%d: %s\n",
+ ast_inet_ntoa(bindaddr.sin_addr), htons(bindaddr.sin_port),
+ strerror(errno));
+ close(unistimsock);
+ unistimsock = -1;
+ } else {
+ if (option_verbose > 1) {
+ ast_verbose(VERBOSE_PREFIX_2
+ "UNISTIM Listening on %s:%d\n",
+ ast_inet_ntoa(bindaddr.sin_addr), htons(bindaddr.sin_port));
+ }
+ ast_netsock_set_qos(unistimsock, tos, cos, "UNISTIM");
+ }
+ return 0;
+}
+
+static enum ast_rtp_get_result unistim_get_vrtp_peer(struct ast_channel *chan,
+ struct ast_rtp **rtp)
+{
+ return AST_RTP_TRY_NATIVE;
+}
+
+static enum ast_rtp_get_result unistim_get_rtp_peer(struct ast_channel *chan,
+ struct ast_rtp **rtp)
+{
+ struct unistim_subchannel *sub;
+ enum ast_rtp_get_result res = AST_RTP_GET_FAILED;
+
+ if (unistimdebug)
+ ast_verbose("unistim_get_rtp_peer called\n");
+
+ sub = chan->tech_pvt;
+ if (sub && sub->rtp) {
+ *rtp = sub->rtp;
+ res = AST_RTP_TRY_NATIVE;
+ }
+
+ return res;
+}
+
+static int unistim_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp,
+ struct ast_rtp *vrtp, struct ast_rtp *trtp, int codecs, int nat_active)
+{
+ struct unistim_subchannel *sub;
+
+ if (unistimdebug)
+ ast_verbose("unistim_set_rtp_peer called\n");
+
+ sub = chan->tech_pvt;
+
+ if (sub)
+ return 0;
+
+ return -1;
+}
+
+static struct ast_rtp_protocol unistim_rtp = {
+ .type = type,
+ .get_rtp_info = unistim_get_rtp_peer,
+ .get_vrtp_info = unistim_get_vrtp_peer,
+ .set_rtp_peer = unistim_set_rtp_peer,
+};
+
+/*--- load_module: PBX load module - initialization ---*/
+int load_module(void)
+{
+ int res;
+
+ if (!(buff = ast_malloc(SIZE_PAGE)))
+ goto buff_failed;
+
+ io = io_context_create();
+ if (!io) {
+ ast_log(LOG_ERROR, "Failed to allocate IO context\n");
+ goto io_failed;
+ }
+
+ sched = sched_context_create();
+ if (!sched) {
+ ast_log(LOG_ERROR, "Failed to allocate scheduler context\n");
+ goto sched_failed;
+ }
+
+ res = reload_config();
+ if (res)
+ return AST_MODULE_LOAD_DECLINE;
+
+ /* Make sure we can register our unistim channel type */
+ if (ast_channel_register(&unistim_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel type '%s'\n", type);
+ goto chanreg_failed;
+ }
+
+ ast_rtp_proto_register(&unistim_rtp);
+
+ ast_cli_register_multiple(unistim_cli, ARRAY_LEN(unistim_cli));
+
+ restart_monitor();
+
+ return AST_MODULE_LOAD_SUCCESS;
+
+chanreg_failed:
+ /*! XXX \todo Leaking anything allocated by reload_config() ... */
+ sched_context_destroy(sched);
+ sched = NULL;
+sched_failed:
+ io_context_destroy(io);
+ io = NULL;
+io_failed:
+ ast_free(buff);
+ buff = NULL;
+buff_failed:
+ return AST_MODULE_LOAD_FAILURE;
+}
+
+static int unload_module(void)
+{
+ /* First, take us out of the channel loop */
+ if (sched)
+ sched_context_destroy(sched);
+
+ ast_cli_unregister_multiple(unistim_cli, ARRAY_LEN(unistim_cli));
+
+ ast_channel_unregister(&unistim_tech);
+ ast_rtp_proto_unregister(&unistim_rtp);
+
+ ast_mutex_lock(&monlock);
+ if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) {
+ pthread_cancel(monitor_thread);
+ pthread_kill(monitor_thread, SIGURG);
+ pthread_join(monitor_thread, NULL);
+ }
+ monitor_thread = AST_PTHREADT_STOP;
+ ast_mutex_unlock(&monlock);
+
+ if (buff)
+ ast_free(buff);
+ if (unistimsock > -1)
+ close(unistimsock);
+
+ return 0;
+}
+
+/*! reload: Part of Asterisk module interface ---*/
+int reload(void)
+{
+ unistim_reload(NULL, 0, NULL);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "UNISTIM Protocol (USTM)",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+);
diff --git a/trunk/channels/chan_usbradio.c b/trunk/channels/chan_usbradio.c
new file mode 100644
index 000000000..9ebfa44e8
--- /dev/null
+++ b/trunk/channels/chan_usbradio.c
@@ -0,0 +1,2494 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ * Copyright (C) 2007, Jim Dixon
+ *
+ * Jim Dixon, WB6NIL <jim@lambdatel.com>
+ * Steve Henke, W9SH <w9sh@arrl.net>
+ * Based upon work by Mark Spencer <markster@digium.com> and Luigi Rizzo
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Channel driver for CM108 USB Cards with Radio Interface
+ *
+ * \author Jim Dixon <jim@lambdatel.com>
+ * \author Steve Henke <w9sh@arrl.net>
+ *
+ * \par See also
+ * \arg \ref Config_usbradio
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <depend>asound</depend>
+ <depend>usb</depend>
+ <defaultenabled>no</defaultenabled>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <ctype.h>
+#include <math.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <usb.h>
+#include <alsa/asoundlib.h>
+
+#define CHAN_USBRADIO 1
+
+#define DEBUG_USBRADIO 0
+#define DEBUG_CAPTURES 1
+
+#define DEBUG_CAP_RX_OUT 0
+#define DEBUG_CAP_TX_OUT 0
+
+#define DEBUG_FILETEST 0
+
+#define RX_CAP_RAW_FILE "/tmp/rx_cap_in.pcm"
+#define RX_CAP_TRACE_FILE "/tmp/rx_trace.pcm"
+#define RX_CAP_OUT_FILE "/tmp/rx_cap_out.pcm"
+
+#define TX_CAP_RAW_FILE "/tmp/tx_cap_in.pcm"
+#define TX_CAP_TRACE_FILE "/tmp/tx_trace.pcm"
+#define TX_CAP_OUT_FILE "/tmp/tx_cap_out.pcm"
+
+#define MIXER_PARAM_MIC_PLAYBACK_SW "Mic Playback Switch"
+#define MIXER_PARAM_MIC_PLAYBACK_VOL "Mic Playback Volume"
+#define MIXER_PARAM_MIC_CAPTURE_SW "Mic Capture Switch"
+#define MIXER_PARAM_MIC_CAPTURE_VOL "Mic Capture Volume"
+#define MIXER_PARAM_MIC_BOOST "Auto Gain Control"
+#define MIXER_PARAM_SPKR_PLAYBACK_SW "Speaker Playback Switch"
+#define MIXER_PARAM_SPKR_PLAYBACK_VOL "Speaker Playback Volume"
+
+#include "./xpmr/xpmr.h"
+
+#if 0
+#define traceusb1(a, ...) ast_debug(4, a __VA_ARGS__)
+#else
+#define traceusb1(a, ...)
+#endif
+
+#if 0
+#define traceusb2(a, ...) ast_debug(4, a __VA_ARGS__)
+#else
+#define traceusb2(a, ...)
+#endif
+
+#ifdef __linux
+#include <linux/soundcard.h>
+#elif defined(__FreeBSD__)
+#include <sys/soundcard.h>
+#else
+#include <soundcard.h>
+#endif
+
+#include "asterisk/lock.h"
+#include "asterisk/frame.h"
+#include "asterisk/callerid.h"
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/endian.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/dsp.h"
+
+#define C108_VENDOR_ID 0x0d8c
+#define C108_PRODUCT_ID 0x000c
+#define C108_HID_INTERFACE 3
+
+#define HID_REPORT_GET 0x01
+#define HID_REPORT_SET 0x09
+
+#define HID_RT_INPUT 0x01
+#define HID_RT_OUTPUT 0x02
+
+/*! Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = "",
+};
+static struct ast_jb_conf global_jbconf;
+
+/*!
+ * usbradio.conf parameters are
+START_CONFIG
+
+[general]
+ ; General config options, with default values shown.
+ ; You should use one section per device, with [general] being used
+ ; for the device.
+ ;
+ ;
+ ; debug = 0x0 ; misc debug flags, default is 0
+
+ ; Set the device to use for I/O
+ ; devicenum = 0
+ ; Set hardware type here
+ ; hdwtype=0 ; 0=limey, 1=sph
+
+ ; rxboostset=0 ; no rx gain boost
+ ; rxctcssrelax=1 ; reduce talkoff from radios w/o CTCSS Tx HPF
+ ; rxctcssfreq=100.0 ; rx ctcss freq in floating point. must be in table
+ ; txctcssfreq=100.0 ; tx ctcss freq, any frequency permitted
+
+ ; carrierfrom=dsp ;no,usb,usbinvert,dsp,vox
+ ; ctcssfrom=dsp ;no,usb,dsp
+
+ ; rxdemod=flat ; input type from radio: no,speaker,flat
+ ; txprelim=yes ; output is pre-emphasised and limited
+ ; txtoctype=no ; no,phase,notone
+
+ ; txmixa=composite ;no,voice,tone,composite,auxvoice
+ ; txmixb=no ;no,voice,tone,composite,auxvoice
+
+ ; invertptt=0
+
+ ;------------------------------ JITTER BUFFER CONFIGURATION --------------------------
+ ; jbenable = yes ; Enables the use of a jitterbuffer on the receiving side of an
+ ; USBRADIO channel. Defaults to "no". An enabled jitterbuffer will
+ ; be used only if the sending side can create and the receiving
+ ; side can not accept jitter. The USBRADIO channel can't accept jitter,
+ ; thus an enabled jitterbuffer on the receive USBRADIO side will always
+ ; be used if the sending side can create jitter.
+
+ ; jbmaxsize = 200 ; Max length of the jitterbuffer in milliseconds.
+
+ ; jbresyncthreshold = 1000 ; Jump in the frame timestamps over which the jitterbuffer is
+ ; resynchronized. Useful to improve the quality of the voice, with
+ ; big jumps in/broken timestamps, usualy sent from exotic devices
+ ; and programs. Defaults to 1000.
+
+ ; jbimpl = fixed ; Jitterbuffer implementation, used on the receiving side of an USBRADIO
+ ; channel. Two implementations are currenlty available - "fixed"
+ ; (with size always equals to jbmax-size) and "adaptive" (with
+ ; variable size, actually the new jb of IAX2). Defaults to fixed.
+
+ ; jblog = no ; Enables jitterbuffer frame logging. Defaults to "no".
+ ;-----------------------------------------------------------------------------------
+
+
+END_CONFIG
+
+ */
+
+/*!
+ * The following parameters are used in the driver:
+ *
+ * FRAME_SIZE the size of an audio frame, in samples.
+ * 160 is used almost universally, so you should not change it.
+ *
+ * FRAGS the argument for the SETFRAGMENT ioctl.
+ * Overridden by the 'frags' parameter in usbradio.conf
+ *
+ * Bits 0-7 are the base-2 log of the device's block size,
+ * bits 16-31 are the number of blocks in the driver's queue.
+ * There are a lot of differences in the way this parameter
+ * is supported by different drivers, so you may need to
+ * experiment a bit with the value.
+ * A good default for linux is 30 blocks of 64 bytes, which
+ * results in 6 frames of 320 bytes (160 samples).
+ * FreeBSD works decently with blocks of 256 or 512 bytes,
+ * leaving the number unspecified.
+ * Note that this only refers to the device buffer size,
+ * this module will then try to keep the lenght of audio
+ * buffered within small constraints.
+ *
+ * QUEUE_SIZE The max number of blocks actually allowed in the device
+ * driver's buffer, irrespective of the available number.
+ * Overridden by the 'queuesize' parameter in usbradio.conf
+ *
+ * Should be >=2, and at most as large as the hw queue above
+ * (otherwise it will never be full).
+ */
+
+#define FRAME_SIZE 160
+#define QUEUE_SIZE 20
+
+#if defined(__FreeBSD__)
+#define FRAGS 0x8
+#else
+#define FRAGS ( ( (6 * 5) << 16 ) | 0xc )
+#endif
+
+/*
+ * XXX text message sizes are probably 256 chars, but i am
+ * not sure if there is a suitable definition anywhere.
+ */
+#define TEXT_SIZE 256
+
+#if 0
+#define TRYOPEN 1 /* try to open on startup */
+#endif
+#define O_CLOSE 0x444 /* special 'close' mode for device */
+/* Which device to use */
+#if defined( __OpenBSD__ ) || defined( __NetBSD__ )
+#define DEV_DSP "/dev/audio"
+#else
+#define DEV_DSP "/dev/dsp"
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+static char *config = "usbradio.conf"; /* default config file */
+static char *config1 = "usbradio_tune.conf"; /* tune config file */
+
+static FILE *frxcapraw = NULL, *frxcaptrace = NULL, *frxoutraw = NULL;
+static FILE *ftxcapraw = NULL, *ftxcaptrace = NULL, *ftxoutraw = NULL;
+
+static int usbradio_debug;
+#if 0 /* maw asdf sph */
+static int usbradio_debug_level = 0;
+#endif
+
+enum {RX_AUDIO_NONE,RX_AUDIO_SPEAKER,RX_AUDIO_FLAT};
+enum {CD_IGNORE,CD_XPMR_NOISE,CD_XPMR_VOX,CD_HID,CD_HID_INVERT};
+enum {SD_IGNORE,SD_HID,SD_HID_INVERT,SD_XPMR}; /* no,external,externalinvert,software */
+enum {RX_KEY_CARRIER,RX_KEY_CARRIER_CODE};
+enum {TX_OUT_OFF,TX_OUT_VOICE,TX_OUT_LSD,TX_OUT_COMPOSITE,TX_OUT_AUX};
+enum {TOC_NONE,TOC_PHASE,TOC_NOTONE};
+
+/* DECLARE STRUCTURES */
+
+/*
+ * descriptor for one of our channels.
+ * There is one used for 'default' values (from the [general] entry in
+ * the configuration file), and then one instance for each device
+ * (the default is cloned from [general], others are only created
+ * if the relevant section exists).
+ */
+struct chan_usbradio_pvt {
+ struct chan_usbradio_pvt *next;
+
+ char *name;
+
+ int total_blocks; /* total blocks in the output device */
+ int sounddev;
+ enum { M_UNSET, M_FULL, M_READ, M_WRITE } duplex;
+ i16 cdMethod;
+ int autoanswer;
+ int autohangup;
+ int hookstate;
+ unsigned int queuesize; /* max fragments in queue */
+ unsigned int frags; /* parameter for SETFRAGMENT */
+
+ int warned; /* various flags used for warnings */
+#define WARN_used_blocks 1
+#define WARN_speed 2
+#define WARN_frag 4
+ int w_errors; /* overfull in the write path */
+ struct timeval lastopen;
+
+ int overridecontext;
+ int mute;
+
+ /* boost support. BOOST_SCALE * 10 ^(BOOST_MAX/20) must
+ * be representable in 16 bits to avoid overflows.
+ */
+#define BOOST_SCALE (1<<9)
+#define BOOST_MAX 40 /* slightly less than 7 bits */
+ int boost; /* input boost, scaled by BOOST_SCALE */
+ char devicenum;
+ int spkrmax;
+ int micmax;
+
+ pthread_t sthread;
+ pthread_t hidthread;
+
+ int stophid;
+ struct ast_channel *owner;
+ char ext[AST_MAX_EXTENSION];
+ char ctx[AST_MAX_CONTEXT];
+ char language[MAX_LANGUAGE];
+ char cid_name[256]; /* XXX */
+ char cid_num[256]; /* XXX */
+ char mohinterpret[MAX_MUSICCLASS];
+
+ /* buffers used in usbradio_write, 2 per int by 2 channels by 6 times oversampling (48KS/s) */
+ char usbradio_write_buf[FRAME_SIZE * 2 * 2 * 6];
+ char usbradio_write_buf_1[FRAME_SIZE * 2 * 2 * 6];
+
+ int usbradio_write_dst;
+ /* buffers used in usbradio_read - AST_FRIENDLY_OFFSET space for headers
+ * plus enough room for a full frame
+ */
+ char usbradio_read_buf[FRAME_SIZE * (2 * 12) + AST_FRIENDLY_OFFSET];
+ char usbradio_read_buf_8k[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET];
+ int readpos; /* read position above */
+ struct ast_frame read_f; /* returned by usbradio_read */
+
+
+ char debuglevel;
+ char radioduplex;
+
+ char lastrx;
+ char rxhidsq;
+ char rxcarrierdetect; /*!< status from pmr channel */
+ char rxctcssdecode; /*!< status from pmr channel */
+
+ char rxkeytype;
+ char rxkeyed; /*!< indicates rx signal present */
+
+ char lasttx;
+ char txkeyed; /*! tx key request from upper layers */
+ char txchankey;
+ char txtestkey;
+
+ time_t lasthidtime;
+ struct ast_dsp *dsp;
+
+ t_pmr_chan *pmrChan;
+
+ char rxcpusaver;
+ char txcpusaver;
+
+ char rxdemod;
+ float rxgain;
+ char rxcdtype;
+ char rxsdtype;
+ int rxsquelchadj; /*!< this copy needs to be here for initialization */
+ char txtoctype;
+
+ char txprelim;
+ float txctcssgain;
+ char txmixa;
+ char txmixb;
+
+ char invertptt;
+
+ char rxctcssrelax;
+ float rxctcssgain;
+ float rxctcssfreq;
+ float txctcssfreq;
+
+ int rxmixerset;
+ int rxboostset;
+ float rxvoiceadj;
+ float rxctcssadj;
+ int txmixaset;
+ int txmixbset;
+ int txctcssadj;
+
+ int hdwtype;
+ int hid_gpio_ctl;
+ int hid_gpio_ctl_loc;
+ int hid_io_cor;
+ int hid_io_cor_loc;
+ int hid_io_ctcss;
+ int hid_io_ctcss_loc;
+ int hid_io_ptt;
+ int hid_gpio_loc;
+
+ struct {
+ unsigned rxcapraw:1;
+ unsigned txcapraw:1;
+ unsigned txcap2:1;
+ unsigned rxcap2:1;
+ } b;
+};
+
+/* maw add additional defaults !!! */
+static struct chan_usbradio_pvt usbradio_default = {
+ .sounddev = -1,
+ .duplex = M_UNSET, /* XXX check this */
+ .autoanswer = 1,
+ .autohangup = 1,
+ .queuesize = QUEUE_SIZE,
+ .frags = FRAGS,
+ .ext = "s",
+ .ctx = "default",
+ .readpos = AST_FRIENDLY_OFFSET, /* start here on reads */
+ .lastopen = { 0, 0 },
+ .boost = BOOST_SCALE,
+};
+
+/* DECLARE FUNCTION PROTOTYPES */
+
+static void store_txtoctype(struct chan_usbradio_pvt *o, const char *s);
+static int hidhdwconfig(struct chan_usbradio_pvt *o);
+static int set_txctcss_level(struct chan_usbradio_pvt *o);
+static void pmrdump(struct chan_usbradio_pvt *o);
+static void mult_set(struct chan_usbradio_pvt *o);
+static int mult_calc(int value);
+static void mixer_write(struct chan_usbradio_pvt *o);
+static void tune_rxinput(struct chan_usbradio_pvt *o);
+static void tune_rxvoice(struct chan_usbradio_pvt *o);
+static void tune_rxctcss(struct chan_usbradio_pvt *o);
+static void tune_txoutput(struct chan_usbradio_pvt *o, int value);
+static void tune_write(struct chan_usbradio_pvt *o);
+
+static char *usbradio_active; /* the active device */
+
+static int setformat(struct chan_usbradio_pvt *o, int mode);
+
+static struct ast_channel *usbradio_request(const char *type, int format, void *data
+, int *cause);
+static int usbradio_digit_begin(struct ast_channel *c, char digit);
+static int usbradio_digit_end(struct ast_channel *c, char digit, unsigned int duration);
+static int usbradio_text(struct ast_channel *c, const char *text);
+static int usbradio_hangup(struct ast_channel *c);
+static int usbradio_answer(struct ast_channel *c);
+static struct ast_frame *usbradio_read(struct ast_channel *chan);
+static int usbradio_call(struct ast_channel *c, char *dest, int timeout);
+static int usbradio_write(struct ast_channel *chan, struct ast_frame *f);
+static int usbradio_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen);
+static int usbradio_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+
+#if DEBUG_FILETEST == 1
+static int RxTestIt(struct chan_usbradio_pvt *o);
+#endif
+
+static char tdesc[] = "USB (CM108) Radio Channel Driver";
+
+static const struct ast_channel_tech usbradio_tech = {
+ .type = "Radio",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_SLINEAR,
+ .requester = usbradio_request,
+ .send_digit_begin = usbradio_digit_begin,
+ .send_digit_end = usbradio_digit_end,
+ .send_text = usbradio_text,
+ .hangup = usbradio_hangup,
+ .answer = usbradio_answer,
+ .read = usbradio_read,
+ .call = usbradio_call,
+ .write = usbradio_write,
+ .indicate = usbradio_indicate,
+ .fixup = usbradio_fixup,
+};
+
+/* Call with: devnum: alsa major device number, param: ascii Formal
+Parameter Name, val1, first or only value, val2 second value, or 0
+if only 1 value. Values: 0-99 (percent) or 0-1 for baboon.
+
+Note: must add -lasound to end of linkage */
+
+static int amixer_max(int devnum,char *param)
+{
+ int rv,type;
+ char str[15];
+ snd_hctl_t *hctl;
+ snd_ctl_elem_id_t *id;
+ snd_hctl_elem_t *elem;
+ snd_ctl_elem_info_t *info;
+
+ snprintf(str, sizeof(str), "hw:%d", devnum);
+ if (snd_hctl_open(&hctl, str, 0))
+ return -1;
+ snd_hctl_load(hctl);
+ id = alloca(snd_ctl_elem_id_sizeof());
+ memset(id, 0, snd_ctl_elem_id_sizeof());
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+ snd_ctl_elem_id_set_name(id, param);
+ elem = snd_hctl_find_elem(hctl, id);
+ if (!elem) {
+ snd_hctl_close(hctl);
+ return -1;
+ }
+ info = alloca(snd_ctl_elem_info_sizeof());
+ memset(info, 0, snd_ctl_elem_info_sizeof());
+ snd_hctl_elem_info(elem,info);
+ type = snd_ctl_elem_info_get_type(info);
+ rv = 0;
+ switch (type) {
+ case SND_CTL_ELEM_TYPE_INTEGER:
+ rv = snd_ctl_elem_info_get_max(info);
+ break;
+ case SND_CTL_ELEM_TYPE_BOOLEAN:
+ rv = 1;
+ break;
+ }
+ snd_hctl_close(hctl);
+ return(rv);
+}
+
+/*! \brief Call with: devnum: alsa major device number, param: ascii Formal
+Parameter Name, val1, first or only value, val2 second value, or 0
+if only 1 value. Values: 0-99 (percent) or 0-1 for baboon.
+
+Note: must add -lasound to end of linkage */
+
+static int setamixer(int devnum, char *param, int v1, int v2)
+{
+ int type;
+ char str[15];
+ snd_hctl_t *hctl;
+ snd_ctl_elem_id_t *id;
+ snd_ctl_elem_value_t *control;
+ snd_hctl_elem_t *elem;
+ snd_ctl_elem_info_t *info;
+
+ snprintf(str, sizeof(str), "hw:%d", devnum);
+ if (snd_hctl_open(&hctl, str, 0))
+ return -1;
+ snd_hctl_load(hctl);
+ id = alloca(snd_ctl_elem_id_sizeof());
+ memset(id, 0, snd_ctl_elem_id_sizeof());
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+ snd_ctl_elem_id_set_name(id, param);
+ elem = snd_hctl_find_elem(hctl, id);
+ if (!elem) {
+ snd_hctl_close(hctl);
+ return -1;
+ }
+ info = alloca(snd_ctl_elem_info_sizeof());
+ memset(info, 0, snd_ctl_elem_info_sizeof());
+ snd_hctl_elem_info(elem,info);
+ type = snd_ctl_elem_info_get_type(info);
+ control = alloca(snd_ctl_elem_value_sizeof());
+ memset(control, 0, snd_ctl_elem_value_sizeof());
+ snd_ctl_elem_value_set_id(control, id);
+ switch (type) {
+ case SND_CTL_ELEM_TYPE_INTEGER:
+ snd_ctl_elem_value_set_integer(control, 0, v1);
+ if (v2 > 0) snd_ctl_elem_value_set_integer(control, 1, v2);
+ break;
+ case SND_CTL_ELEM_TYPE_BOOLEAN:
+ snd_ctl_elem_value_set_integer(control, 0, (v1 != 0));
+ break;
+ }
+ if (snd_hctl_elem_write(elem, control)) {
+ snd_hctl_close(hctl);
+ return(-1);
+ }
+ snd_hctl_close(hctl);
+ return 0;
+}
+
+static void hid_set_outputs(struct usb_dev_handle *handle,
+ unsigned char *outputs)
+{
+ usb_control_msg(handle,
+ USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
+ HID_REPORT_SET,
+ 0 + (HID_RT_OUTPUT << 8),
+ C108_HID_INTERFACE,
+ (char *)outputs, 4, 5000);
+}
+
+static void hid_get_inputs(struct usb_dev_handle *handle,
+ unsigned char *inputs)
+{
+ usb_control_msg(handle,
+ USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
+ HID_REPORT_GET,
+ 0 + (HID_RT_INPUT << 8),
+ C108_HID_INTERFACE,
+ (char *)inputs, 4, 5000);
+}
+
+static struct usb_device *hid_device_init(void)
+{
+ struct usb_bus *usb_bus;
+ struct usb_device *dev;
+
+ usb_init();
+ usb_find_busses();
+ usb_find_devices();
+ for (usb_bus = usb_busses; usb_bus; usb_bus = usb_bus->next) {
+ for (dev = usb_bus->devices; dev; dev = dev->next) {
+ if ((dev->descriptor.idVendor == C108_VENDOR_ID) && (dev->descriptor.idProduct == C108_PRODUCT_ID))
+ return dev;
+ }
+ }
+ return NULL;
+}
+
+static int hidhdwconfig(struct chan_usbradio_pvt *o)
+{
+ if (o->hdwtype == 1) { /*sphusb */
+ o->hid_gpio_ctl = 0x08; /* set GPIO4 to output mode */
+ o->hid_gpio_ctl_loc = 2; /* For CTL of GPIO */
+ o->hid_io_cor = 4; /* GPIO3 is COR */
+ o->hid_io_cor_loc = 1; /* GPIO3 is COR */
+ o->hid_io_ctcss = 2; /* GPIO 2 is External CTCSS */
+ o->hid_io_ctcss_loc = 1; /* is GPIO 2 */
+ o->hid_io_ptt = 8; /* GPIO 4 is PTT */
+ o->hid_gpio_loc = 1; /* For ALL GPIO */
+ } else if (o->hdwtype == 0) { /* dudeusb */
+ o->hid_gpio_ctl = 0x0c;/* set GPIO 3 & 4 to output mode */
+ o->hid_gpio_ctl_loc = 2; /* For CTL of GPIO */
+ o->hid_io_cor = 2; /* VOLD DN is COR */
+ o->hid_io_cor_loc = 0; /* VOL DN COR */
+ o->hid_io_ctcss = 2; /* GPIO 2 is External CTCSS */
+ o->hid_io_ctcss_loc = 1; /* is GPIO 2 */
+ o->hid_io_ptt = 4; /* GPIO 3 is PTT */
+ o->hid_gpio_loc = 1; /* For ALL GPIO */
+ } else if (o->hdwtype == 3) { /* custom version */
+ o->hid_gpio_ctl = 0x0c; /* set GPIO 3 & 4 to output mode */
+ o->hid_gpio_ctl_loc = 2; /* For CTL of GPIO */
+ o->hid_io_cor = 2; /* VOLD DN is COR */
+ o->hid_io_cor_loc = 0; /* VOL DN COR */
+ o->hid_io_ctcss = 2; /* GPIO 2 is External CTCSS */
+ o->hid_io_ctcss_loc = 1; /* is GPIO 2 */
+ o->hid_io_ptt = 4; /* GPIO 3 is PTT */
+ o->hid_gpio_loc = 1; /* For ALL GPIO */
+ }
+
+ return 0;
+}
+
+
+static void *hidthread(void *arg)
+{
+ unsigned char buf[4], keyed;
+ char lastrx, txtmp;
+ struct usb_device *usb_dev;
+ struct usb_dev_handle *usb_handle;
+ struct chan_usbradio_pvt *o = arg;
+
+ usb_dev = hid_device_init();
+ if (usb_dev == NULL) {
+ ast_log(LOG_ERROR, "USB HID device not found\n");
+ pthread_exit(NULL);
+ }
+ usb_handle = usb_open(usb_dev);
+ if (usb_handle == NULL) {
+ ast_log(LOG_ERROR, "Not able to open USB device\n");
+ pthread_exit(NULL);
+ }
+ if (usb_claim_interface(usb_handle, C108_HID_INTERFACE) < 0) {
+ if (usb_detach_kernel_driver_np(usb_handle, C108_HID_INTERFACE) < 0) {
+ ast_log(LOG_ERROR, "Not able to detach the USB device\n");
+ pthread_exit(NULL);
+ }
+ if (usb_claim_interface(usb_handle, C108_HID_INTERFACE) < 0) {
+ ast_log(LOG_ERROR, "Not able to claim the USB device\n");
+ pthread_exit(NULL);
+ }
+ }
+ memset(buf, 0, sizeof(buf));
+ buf[2] = o->hid_gpio_ctl;
+ buf[1] = 0;
+ hid_set_outputs(usb_handle, buf);
+ traceusb1("hidthread: Starting normally!!\n");
+ lastrx = 0;
+ while (!o->stophid) {
+ buf[o->hid_gpio_ctl_loc] = o->hid_gpio_ctl;
+ hid_get_inputs(usb_handle, buf);
+ keyed = !(buf[o->hid_io_cor_loc] & o->hid_io_cor);
+ if (keyed != o->rxhidsq) {
+ if (o->debuglevel)
+ ast_log(LOG_NOTICE, "chan_usbradio() hidthread: update rxhidsq = %d\n", keyed);
+ o->rxhidsq = keyed;
+ }
+
+ /* if change in tx stuff */
+ txtmp = 0;
+ if (o->txkeyed || o->txchankey || o->txtestkey || o->pmrChan->txPttOut)
+ txtmp = 1;
+
+ if (o->lasttx != txtmp) {
+ o->lasttx = txtmp;
+ if (o->debuglevel)
+ ast_log(LOG_NOTICE, "hidthread: tx set to %d\n", txtmp);
+ buf[o->hid_gpio_loc] = 0;
+ if (txtmp)
+ buf[o->hid_gpio_loc] = o->hid_io_ptt;
+ buf[o->hid_gpio_ctl_loc] = o->hid_gpio_ctl;
+ hid_set_outputs(usb_handle, buf);
+ }
+
+ time(&o->lasthidtime);
+ usleep(50000);
+ }
+ buf[o->hid_gpio_loc] = 0;
+ if (o->invertptt)
+ buf[o->hid_gpio_loc] = o->hid_io_ptt;
+ buf[o->hid_gpio_ctl_loc] = o->hid_gpio_ctl;
+ hid_set_outputs(usb_handle, buf);
+ pthread_exit(0);
+}
+
+/*! \brief
+ * returns a pointer to the descriptor with the given name
+ */
+static struct chan_usbradio_pvt *find_desc(char *dev)
+{
+ struct chan_usbradio_pvt *o = NULL;
+
+ if (!dev)
+ ast_log(LOG_WARNING, "null dev\n");
+
+ for (o = usbradio_default.next; o && o->name && dev && strcmp(o->name, dev) != 0; o = o->next);
+
+ if (!o)
+ ast_log(LOG_WARNING, "could not find <%s>\n", dev ? dev : "--no-device--");
+
+ return o;
+}
+
+/*! \brief
+ * split a string in extension-context, returns pointers to malloc'ed
+ * strings.
+ * If we do not have 'overridecontext' then the last @ is considered as
+ * a context separator, and the context is overridden.
+ * This is usually not very necessary as you can play with the dialplan,
+ * and it is nice not to need it because you have '@' in SIP addresses.
+ * Return value is the buffer address.
+ */
+#if 0
+static char *ast_ext_ctx(const char *src, char **ext, char **ctx)
+{
+ struct chan_usbradio_pvt *o = find_desc(usbradio_active);
+
+ if (ext == NULL || ctx == NULL)
+ return NULL; /* error */
+
+ *ext = *ctx = NULL;
+
+ if (src && *src != '\0')
+ *ext = ast_strdup(src);
+
+ if (*ext == NULL)
+ return NULL;
+
+ if (!o->overridecontext) {
+ /* parse from the right */
+ *ctx = strrchr(*ext, '@');
+ if (*ctx)
+ *(*ctx)++ = '\0';
+ }
+
+ return *ext;
+}
+#endif
+
+/*! \brief
+ * Returns the number of blocks used in the audio output channel
+ */
+static int used_blocks(struct chan_usbradio_pvt *o)
+{
+ struct audio_buf_info info;
+
+ if (ioctl(o->sounddev, SNDCTL_DSP_GETOSPACE, &info)) {
+ if (!(o->warned & WARN_used_blocks)) {
+ ast_log(LOG_WARNING, "Error reading output space\n");
+ o->warned |= WARN_used_blocks;
+ }
+ return 1;
+ }
+
+ if (o->total_blocks == 0) {
+ ast_debug(4, "fragtotal %d size %d avail %d\n", info.fragstotal, info.fragsize, info.fragments);
+ o->total_blocks = info.fragments;
+ }
+
+ return o->total_blocks - info.fragments;
+}
+
+/*! \brief Write an exactly FRAME_SIZE sized frame */
+static int soundcard_writeframe(struct chan_usbradio_pvt *o, short *data)
+{
+ int res;
+
+ if (o->sounddev < 0)
+ setformat(o, O_RDWR);
+ if (o->sounddev < 0)
+ return 0; /* not fatal */
+ /*
+ * Nothing complex to manage the audio device queue.
+ * If the buffer is full just drop the extra, otherwise write.
+ * XXX in some cases it might be useful to write anyways after
+ * a number of failures, to restart the output chain.
+ */
+ res = used_blocks(o);
+ if (res > o->queuesize) { /* no room to write a block */
+ if (o->w_errors++ == 0 && (usbradio_debug & 0x4))
+ ast_log(LOG_WARNING, "write: used %d blocks (%d)\n", res, o->w_errors);
+ return 0;
+ }
+ o->w_errors = 0;
+
+ return write(o->sounddev, ((void *) data), FRAME_SIZE * 2 * 12);
+}
+
+/*
+ * reset and close the device if opened,
+ * then open and initialize it in the desired mode,
+ * trigger reads and writes so we can start using it.
+ */
+static int setformat(struct chan_usbradio_pvt *o, int mode)
+{
+ int fmt, desired, res, fd;
+ char device[20];
+
+ if (o->sounddev >= 0) {
+ ioctl(o->sounddev, SNDCTL_DSP_RESET, 0);
+ close(o->sounddev);
+ o->duplex = M_UNSET;
+ o->sounddev = -1;
+ }
+ if (mode == O_CLOSE) /* we are done */
+ return 0;
+ if (ast_tvdiff_ms(ast_tvnow(), o->lastopen) < 1000)
+ return -1; /* don't open too often */
+ o->lastopen = ast_tvnow();
+ strcpy(device, "/dev/dsp");
+ if (o->devicenum)
+ snprintf(device + strlen("/dev/dsp"), sizeof(device) - strlen("/dev/dsp"), "%d", o->devicenum);
+ fd = o->sounddev = open(device, mode | O_NONBLOCK);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Unable to re-open DSP device %d: %s\n", o->devicenum, strerror(errno));
+ return -1;
+ }
+ if (o->owner)
+ o->owner->fds[0] = fd;
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ fmt = AFMT_S16_LE;
+#else
+ fmt = AFMT_S16_BE;
+#endif
+ res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set format to 16-bit signed\n");
+ return -1;
+ }
+ switch (mode) {
+ case O_RDWR:
+ res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
+ /* Check to see if duplex set (FreeBSD Bug) */
+ res = ioctl(fd, SNDCTL_DSP_GETCAPS, &fmt);
+ if (res == 0 && (fmt & DSP_CAP_DUPLEX)) {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Console is full duplex\n");
+ o->duplex = M_FULL;
+ };
+ break;
+ case O_WRONLY:
+ o->duplex = M_WRITE;
+ break;
+ case O_RDONLY:
+ o->duplex = M_READ;
+ break;
+ }
+
+ fmt = 1;
+ res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Failed to set audio device to mono\n");
+ return -1;
+ }
+ fmt = desired = 48000; /* 8000 Hz desired */
+ res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt);
+
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Failed to set audio device to mono\n");
+ return -1;
+ }
+ if (fmt != desired) {
+ if (!(o->warned & WARN_speed)) {
+ ast_log(LOG_WARNING,
+ "Requested %d Hz, got %d Hz -- sound may be choppy\n",
+ desired, fmt);
+ o->warned |= WARN_speed;
+ }
+ }
+ /*
+ * on Freebsd, SETFRAGMENT does not work very well on some cards.
+ * Default to use 256 bytes, let the user override
+ */
+ if (o->frags) {
+ fmt = o->frags;
+ res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt);
+ if (res < 0) {
+ if (!(o->warned & WARN_frag)) {
+ ast_log(LOG_WARNING,
+ "Unable to set fragment size -- sound may be choppy\n");
+ o->warned |= WARN_frag;
+ }
+ }
+ }
+ /* on some cards, we need SNDCTL_DSP_SETTRIGGER to start outputting */
+ res = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
+ res = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &res);
+ /* it may fail if we are in half duplex, never mind */
+ return 0;
+}
+
+/*
+ * some of the standard methods supported by channels.
+ */
+static int usbradio_digit_begin(struct ast_channel *c, char digit)
+{
+ return 0;
+}
+
+static int usbradio_digit_end(struct ast_channel *c, char digit, unsigned int duration)
+{
+ /* no better use for received digits than print them */
+ ast_verbose(" << Console Received digit %c of duration %u ms >> \n",
+ digit, duration);
+ return 0;
+}
+
+static int usbradio_text(struct ast_channel *c, const char *text)
+{
+ /* print received messages */
+ ast_verbose(" << Console Received text %s >> \n", text);
+ return 0;
+}
+
+/*
+ * handler for incoming calls. Either autoanswer, or start ringing
+ */
+static int usbradio_call(struct ast_channel *c, char *dest, int timeout)
+{
+ struct chan_usbradio_pvt *o = c->tech_pvt;
+
+ time(&o->lasthidtime);
+ ast_pthread_create_background(&o->hidthread, NULL, hidthread, o);
+ ast_setstate(c, AST_STATE_UP);
+ return 0;
+}
+
+/*
+ * remote side answered the phone
+ */
+static int usbradio_answer(struct ast_channel *c)
+{
+ ast_setstate(c, AST_STATE_UP);
+
+ return 0;
+}
+
+static int usbradio_hangup(struct ast_channel *c)
+{
+ struct chan_usbradio_pvt *o = c->tech_pvt;
+
+ c->tech_pvt = NULL;
+ o->owner = NULL;
+ ast_module_unref(ast_module_info->self);
+ if (o->hookstate) {
+ if (o->autoanswer || o->autohangup) {
+ /* Assume auto-hangup too */
+ o->hookstate = 0;
+ setformat(o, O_CLOSE);
+ }
+ }
+ o->stophid = 1;
+ pthread_join(o->hidthread, NULL);
+ return 0;
+}
+
+
+/* used for data coming from the network */
+static int usbradio_write(struct ast_channel *c, struct ast_frame *f)
+{
+ int src,datalen;
+ struct chan_usbradio_pvt *o = c->tech_pvt;
+
+ traceusb2("usbradio_write() o->nosound=%d\n", o->nosound); /*sph maw asdf */
+
+ /*
+ * we could receive a block which is not a multiple of our
+ * FRAME_SIZE, so buffer it locally and write to the device
+ * in FRAME_SIZE chunks.
+ * Keep the residue stored for future use.
+ */
+
+ if (o->txkeyed || o->txtestkey)
+ o->pmrChan->txPttIn = 1;
+ else
+ o->pmrChan->txPttIn = 0;
+
+ #if DEBUG_CAPTURES == 1 /* to write input data to a file datalen=320 */
+ if (ftxcapraw && o->b.txcapraw) {
+ i16 i, tbuff[f->datalen];
+ for (i = 0; i < f->datalen; i += 2) {
+ tbuff[i] = ((i16 *)(f->data))[i / 2];
+ tbuff[i + 1] = o->txkeyed * M_Q13;
+ }
+ fwrite(tbuff, 2, f->datalen, ftxcapraw);
+ /*fwrite(f->data,1,f->datalen,ftxcapraw); */
+ }
+ #endif
+
+ PmrTx(o->pmrChan,(i16*)f->data,(i16*)o->usbradio_write_buf_1);
+
+ #if 0 /* to write 48KS/s stereo data to a file */
+ if (!ftxoutraw) ftxoutraw = fopen(TX_CAP_OUT_FILE,"w");
+ if (ftxoutraw) fwrite(o->usbradio_write_buf_1,1,f->datalen * 2 * 6,ftxoutraw);
+ #endif
+
+ #if DEBUG_CAPTURES == 1
+ if (o->b.txcap2 && ftxcaptrace)
+ fwrite((o->pmrChan->ptxDebug), 1, FRAME_SIZE * 2 * 16, ftxcaptrace);
+ #endif
+
+ src = 0; /* read position into f->data */
+ datalen = f->datalen * 12;
+ while (src < datalen) {
+ /* Compute spare room in the buffer */
+ int l = sizeof(o->usbradio_write_buf) - o->usbradio_write_dst;
+
+ if (datalen - src >= l) { /* enough to fill a frame */
+ memcpy(o->usbradio_write_buf + o->usbradio_write_dst, o->usbradio_write_buf_1 + src, l);
+ soundcard_writeframe(o, (short *) o->usbradio_write_buf);
+ src += l;
+ o->usbradio_write_dst = 0;
+ } else { /* copy residue */
+ l = datalen - src;
+ memcpy(o->usbradio_write_buf + o->usbradio_write_dst, o->usbradio_write_buf_1 + src, l);
+ src += l; /* but really, we are done */
+ o->usbradio_write_dst += l;
+ }
+ }
+ return 0;
+}
+
+static struct ast_frame *usbradio_read(struct ast_channel *c)
+{
+ int res;
+ struct chan_usbradio_pvt *o = c->tech_pvt;
+ struct ast_frame *f = &o->read_f, *f1;
+ struct ast_frame wf = { AST_FRAME_CONTROL };
+ time_t now;
+
+ traceusb2("usbradio_read()\n"); /* sph maw asdf */
+
+ if (o->lasthidtime) {
+ time(&now);
+ if ((now - o->lasthidtime) > 3) {
+ ast_log(LOG_ERROR, "HID process has died or something!!\n");
+ return NULL;
+ }
+ }
+ if (o->lastrx && (!o->rxkeyed)) {
+ o->lastrx = 0;
+ wf.subclass = AST_CONTROL_RADIO_UNKEY;
+ ast_queue_frame(o->owner, &wf);
+ } else if ((!o->lastrx) && (o->rxkeyed)) {
+ o->lastrx = 1;
+ wf.subclass = AST_CONTROL_RADIO_KEY;
+ ast_queue_frame(o->owner, &wf);
+ }
+ /* XXX can be simplified returning &ast_null_frame */
+ /* prepare a NULL frame in case we don't have enough data to return */
+ memset(f, 0, sizeof(struct ast_frame));
+ f->frametype = AST_FRAME_NULL;
+ f->src = usbradio_tech.type;
+
+ res = read(o->sounddev, o->usbradio_read_buf + o->readpos,
+ sizeof(o->usbradio_read_buf) - o->readpos);
+ if (res < 0) /* audio data not ready, return a NULL frame */
+ return f;
+
+ o->readpos += res;
+ if (o->readpos < sizeof(o->usbradio_read_buf)) /* not enough samples */
+ return f;
+
+ if (o->mute)
+ return f;
+
+ #if DEBUG_CAPTURES == 1
+ if (o->b.rxcapraw && frxcapraw)
+ fwrite((o->usbradio_read_buf + AST_FRIENDLY_OFFSET), 1, FRAME_SIZE * 2 * 2 * 6, frxcapraw);
+ #endif
+
+ #if 1
+ PmrRx( o->pmrChan,
+ (i16 *)(o->usbradio_read_buf + AST_FRIENDLY_OFFSET),
+ (i16 *)(o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET));
+
+ #else
+ static FILE *hInput;
+ i16 iBuff[FRAME_SIZE * 2 * 6];
+
+ o->pmrChan->b.rxCapture = 1;
+
+ if(!hInput) {
+ hInput = fopen("/usr/src/xpmr/testdata/rx_in.pcm", "r");
+ if(!hInput) {
+ ast_log(LOG_ERROR, " Input Data File Not Found.\n");
+ return 0;
+ }
+ }
+
+ if (0 == fread((void *)iBuff, 2, FRAME_SIZE * 2 * 6, hInput))
+ exit;
+
+ PmrRx( o->pmrChan,
+ (i16 *)iBuff,
+ (i16 *)(o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET));
+
+ #endif
+
+ #if 0
+ if (!frxoutraw) frxoutraw = fopen(RX_CAP_OUT_FILE, "w");
+ if (frxoutraw) fwrite((o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET), 1, FRAME_SIZE * 2, frxoutraw);
+ #endif
+
+ #if DEBUG_CAPTURES == 1
+ if (frxcaptrace && o->b.rxcap2) fwrite((o->pmrChan->prxDebug), 1, FRAME_SIZE * 2 * 16, frxcaptrace);
+ #endif
+
+ if (o->rxcdtype == CD_HID && (o->pmrChan->rxExtCarrierDetect != o->rxhidsq))
+ o->pmrChan->rxExtCarrierDetect = o->rxhidsq;
+ if (o->rxcdtype == CD_HID_INVERT && (o->pmrChan->rxExtCarrierDetect == o->rxhidsq))
+ o->pmrChan->rxExtCarrierDetect = !o->rxhidsq;
+
+ if ( (o->rxcdtype == CD_HID && o->rxhidsq) ||
+ (o->rxcdtype == CD_HID_INVERT && !o->rxhidsq) ||
+ (o->rxcdtype == CD_XPMR_NOISE && o->pmrChan->rxCarrierDetect) ||
+ (o->rxcdtype == CD_XPMR_VOX && o->pmrChan->rxCarrierDetect) )
+ res = 1;
+ else
+ res = 0;
+
+ if (res != o->rxcarrierdetect) {
+ o->rxcarrierdetect = res;
+ if (o->debuglevel)
+ ast_debug(4, "rxcarrierdetect = %d\n", res);
+ }
+
+ if (o->pmrChan->rxCtcss->decode != o->rxctcssdecode) {
+ if (o->debuglevel)
+ ast_debug(4, "rxctcssdecode = %d\n", o->pmrChan->rxCtcss->decode);
+ o->rxctcssdecode = o->pmrChan->rxCtcss->decode;
+ }
+
+ if ( ( o->rxctcssfreq && (o->rxctcssdecode == o->pmrChan->rxCtcssIndex)) ||
+ ( !o->rxctcssfreq && o->rxcarrierdetect) )
+ o->rxkeyed = 1;
+ else
+ o->rxkeyed = 0;
+
+
+ o->readpos = AST_FRIENDLY_OFFSET; /* reset read pointer for next frame */
+ if (c->_state != AST_STATE_UP) /* drop data if frame is not up */
+ return f;
+ /* ok we can build and deliver the frame to the caller */
+ f->frametype = AST_FRAME_VOICE;
+ f->subclass = AST_FORMAT_SLINEAR;
+ f->samples = FRAME_SIZE;
+ f->datalen = FRAME_SIZE * 2;
+ f->data = o->usbradio_read_buf_8k + AST_FRIENDLY_OFFSET;
+ if (o->boost != BOOST_SCALE) { /* scale and clip values */
+ int i, x;
+ int16_t *p = (int16_t *) f->data;
+ for (i = 0; i < f->samples; i++) {
+ x = (p[i] * o->boost) / BOOST_SCALE;
+ if (x > 32767)
+ x = 32767;
+ else if (x < -32768)
+ x = -32768;
+ p[i] = x;
+ }
+ }
+
+ f->offset = AST_FRIENDLY_OFFSET;
+ if (o->dsp) {
+ f1 = ast_dsp_process(c, o->dsp, f);
+ if ((f1->frametype == AST_FRAME_DTMF_END) || (f1->frametype == AST_FRAME_DTMF_BEGIN)) {
+ if ((f1->subclass == 'm') || (f1->subclass == 'u'))
+ f1->frametype = AST_FRAME_DTMF_BEGIN;
+ if (f1->frametype == AST_FRAME_DTMF_END)
+ ast_log(LOG_NOTICE,"Got DTMF char %c\n",f1->subclass);
+ return f1;
+ }
+ }
+ return f;
+}
+
+static int usbradio_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct chan_usbradio_pvt *o = newchan->tech_pvt;
+ ast_log(LOG_WARNING,"usbradio_fixup()\n");
+ o->owner = newchan;
+ return 0;
+}
+
+static int usbradio_indicate(struct ast_channel *c, int cond, const void *data, size_t datalen)
+{
+ struct chan_usbradio_pvt *o = c->tech_pvt;
+ int res = 0;
+
+ switch (cond) {
+ case AST_CONTROL_BUSY:
+ case AST_CONTROL_CONGESTION:
+ case AST_CONTROL_RINGING:
+ case -1:
+ res = -1;
+ break;
+ case AST_CONTROL_PROGRESS:
+ case AST_CONTROL_PROCEEDING:
+ case AST_CONTROL_VIDUPDATE:
+ break;
+ case AST_CONTROL_HOLD:
+ ast_verbose(" << Console Has Been Placed on Hold >> \n");
+ ast_moh_start(c, data, o->mohinterpret);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_verbose(" << Console Has Been Retrieved from Hold >> \n");
+ ast_moh_stop(c);
+ break;
+ case AST_CONTROL_RADIO_KEY:
+ o->txkeyed = 1;
+ if (o->debuglevel)
+ ast_verbose(" << Radio Transmit On. >> \n");
+ break;
+ case AST_CONTROL_RADIO_UNKEY:
+ o->txkeyed = 0;
+ if (o->debuglevel)
+ ast_verbose(" << Radio Transmit Off. >> \n");
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, c->name);
+ return -1;
+ }
+
+ return res;
+}
+
+/*
+ * allocate a new channel.
+ */
+static struct ast_channel *usbradio_new(struct chan_usbradio_pvt *o, char *ext, char *ctx, int state)
+{
+ struct ast_channel *c;
+ char device[15] = "dsp";
+
+ if (o->devicenum)
+ snprintf(device + 3, sizeof(device) - 3, "%d", o->devicenum);
+ c = ast_channel_alloc(1, state, o->cid_num, o->cid_name, "", ext, ctx, 0, "usbRadio/%s", device);
+ if (c == NULL)
+ return NULL;
+ c->tech = &usbradio_tech;
+ if (o->sounddev < 0)
+ setformat(o, O_RDWR);
+ c->fds[0] = o->sounddev; /* -1 if device closed, override later */
+ c->nativeformats = AST_FORMAT_SLINEAR;
+ c->readformat = AST_FORMAT_SLINEAR;
+ c->writeformat = AST_FORMAT_SLINEAR;
+ c->tech_pvt = o;
+
+ if (!ast_strlen_zero(o->language))
+ ast_string_field_set(c, language, o->language);
+ /* Don't use ast_set_callerid() here because it will
+ * generate a needless NewCallerID event */
+ c->cid.cid_num = ast_strdup(o->cid_num);
+ c->cid.cid_ani = ast_strdup(o->cid_num);
+ c->cid.cid_name = ast_strdup(o->cid_name);
+ if (!ast_strlen_zero(ext))
+ c->cid.cid_dnid = ast_strdup(ext);
+
+ o->owner = c;
+ ast_module_ref(ast_module_info->self);
+ ast_jb_configure(c, &global_jbconf);
+ if (state != AST_STATE_DOWN) {
+ if (ast_pbx_start(c)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", c->name);
+ ast_hangup(c);
+ o->owner = c = NULL;
+ /* XXX what about the channel itself ? */
+ /* XXX what about usecnt ? */
+ }
+ }
+
+ return c;
+}
+
+static struct ast_channel *usbradio_request(const char *type, int format, void *data, int *cause)
+{
+ struct ast_channel *c;
+ struct chan_usbradio_pvt *o = find_desc(data);
+
+ ast_debug(4, "usbradio_request ty <%s> data 0x%p <%s>\n", type, data, (char *) data);
+ if (o == NULL) {
+ ast_log(LOG_NOTICE, "Device %s not found\n", (char *) data);
+ /* XXX we could default to 'dsp' perhaps ? */
+ return NULL;
+ }
+ if ((format & AST_FORMAT_SLINEAR) == 0) {
+ ast_log(LOG_NOTICE, "Format 0x%x unsupported\n", format);
+ return NULL;
+ }
+ if (o->owner) {
+ ast_log(LOG_NOTICE, "Already have a call (chan %p) on the usb channel\n", o->owner);
+ *cause = AST_CAUSE_BUSY;
+ return NULL;
+ }
+ c = usbradio_new(o, NULL, NULL, AST_STATE_DOWN);
+ if (c == NULL) {
+ ast_log(LOG_WARNING, "Unable to create new usb channel\n");
+ return NULL;
+ }
+ return c;
+}
+
+static char *handle_cli_radio_key(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_usbradio_pvt *o = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "radio key";
+ e->usage =
+ "Usage: radio key\n"
+ " Simulates COR active.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 2)
+ return CLI_SHOWUSAGE;
+
+ o = find_desc(usbradio_active);
+ o->txtestkey = 1;
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_radio_unkey(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_usbradio_pvt *o = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "radio unkey";
+ e->usage =
+ "Usage: radio unkey\n"
+ " Simulates COR un-active.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 2)
+ return CLI_SHOWUSAGE;
+
+ o = find_desc(usbradio_active);
+ o->txtestkey = 0;
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_cli_radio_tune(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_usbradio_pvt *o = NULL;
+ int i = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "radio tune [rxnoise|rxvoice|rxtone|rxsquelch|rxcap|rxtracecap|"
+ "txvoice|txtone|txcap|txtracecap|auxvoice|nocap|dump|save]";
+ /* radio tune 6 3000 measured tx value */
+ e->usage =
+ "Usage: radio tune <function>\n"
+ " rxnoise\n"
+ " rxvoice\n"
+ " rxtone\n"
+ " rxsquelch [newsetting]\n"
+ " rxcap\n"
+ " rxtracecap\n"
+ " txvoice [newsetting]\n"
+ " txtone [newsetting]\n"
+ " txcap\n"
+ " txtracecap\n"
+ " auxvoice [newsetting]\n"
+ " nocap\n"
+ " dump\n"
+ " save (settings to tuning file)\n"
+ "\n"
+ " All [newsetting]s are values 0-999\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if ((a->argc < 2) || (a->argc > 4))
+ return CLI_SHOWUSAGE;
+
+ if (a->argc == 2) { /* just show stuff */
+ ast_cli(a->fd, "Output A is currently set to %s.\n",
+ o->txmixa == TX_OUT_COMPOSITE ? "composite" :
+ o->txmixa == TX_OUT_VOICE ? "voice" :
+ o->txmixa == TX_OUT_LSD ? "tone" :
+ o->txmixa == TX_OUT_AUX ? "auxvoice" :
+ "off");
+
+ ast_cli(a->fd, "Output B is currently set to %s.\n",
+ o->txmixb == TX_OUT_COMPOSITE ? "composite" :
+ o->txmixb == TX_OUT_VOICE ? "voice" :
+ o->txmixb == TX_OUT_LSD ? "tone" :
+ o->txmixb == TX_OUT_AUX ? "auxvoice" :
+ "off");
+
+ ast_cli(a->fd, "Tx Voice Level currently set to %d\n", o->txmixaset);
+ ast_cli(a->fd, "Tx Tone Level currently set to %d\n", o->txctcssadj);
+ ast_cli(a->fd, "Rx Squelch currently set to %d\n", o->rxsquelchadj);
+ return CLI_SHOWUSAGE;
+ }
+
+ o = find_desc(usbradio_active);
+
+ if (!strcasecmp(a->argv[2], "rxnoise"))
+ tune_rxinput(o);
+ else if (!strcasecmp(a->argv[2], "rxvoice"))
+ tune_rxvoice(o);
+ else if (!strcasecmp(a->argv[2], "rxtone"))
+ tune_rxctcss(o);
+ else if (!strcasecmp(a->argv[2], "rxsquelch")) {
+ if (a->argc == 3) {
+ ast_cli(a->fd, "Current Signal Strength is %d\n", ((32767 - o->pmrChan->rxRssi) * 1000 / 32767));
+ ast_cli(a->fd, "Current Squelch setting is %d\n", o->rxsquelchadj);
+#if 0
+ ast_cli(a->fd,"Current Raw RSSI is %d\n",o->pmrChan->rxRssi);
+ ast_cli(a->fd,"Current (real) Squelch setting is %d\n",*(o->pmrChan->prxSquelchAdjust));
+#endif
+ } else {
+ i = atoi(a->argv[3]);
+ if ((i < 0) || (i > 999))
+ return CLI_SHOWUSAGE;
+ ast_cli(a->fd, "Changed Squelch setting to %d\n", i);
+ o->rxsquelchadj = i;
+ *(o->pmrChan->prxSquelchAdjust) = ((999 - i) * 32767) / 1000;
+ }
+ } else if (!strcasecmp(a->argv[2], "txvoice")) {
+ i = 0;
+
+ if ((o->txmixa != TX_OUT_VOICE) && (o->txmixb != TX_OUT_VOICE) &&
+ (o->txmixa != TX_OUT_COMPOSITE) && (o->txmixb != TX_OUT_COMPOSITE)) {
+ ast_log(LOG_ERROR, "No txvoice output configured.\n");
+ } else if (a->argc == 3) {
+ if ((o->txmixa == TX_OUT_VOICE) || (o->txmixa == TX_OUT_COMPOSITE))
+ ast_cli(a->fd, "Current txvoice setting on Channel A is %d\n", o->txmixaset);
+ else
+ ast_cli(a->fd, "Current txvoice setting on Channel B is %d\n", o->txmixbset);
+ } else {
+ i = atoi(a->argv[3]);
+ if ((i < 0) || (i > 999))
+ return CLI_SHOWUSAGE;
+
+ if ((o->txmixa == TX_OUT_VOICE) || (o->txmixa == TX_OUT_COMPOSITE)) {
+ o->txmixaset = i;
+ ast_cli(a->fd, "Changed txvoice setting on Channel A to %d\n", o->txmixaset);
+ } else {
+ o->txmixbset = i;
+ ast_cli(a->fd, "Changed txvoice setting on Channel B to %d\n", o->txmixbset);
+ }
+ mixer_write(o);
+ mult_set(o);
+ ast_cli(a->fd, "Changed Tx Voice Output setting to %d\n", i);
+ }
+ tune_txoutput(o,i);
+ } else if (!strcasecmp(a->argv[2], "auxvoice")) {
+ i = 0;
+ if ( (o->txmixa != TX_OUT_AUX) && (o->txmixb != TX_OUT_AUX))
+ ast_log(LOG_WARNING, "No auxvoice output configured.\n");
+ else if (a->argc == 3) {
+ if (o->txmixa == TX_OUT_AUX)
+ ast_cli(a->fd, "Current auxvoice setting on Channel A is %d\n", o->txmixaset);
+ else
+ ast_cli(a->fd, "Current auxvoice setting on Channel B is %d\n", o->txmixbset);
+ } else {
+ i = atoi(a->argv[3]);
+ if ((i < 0) || (i > 999))
+ return CLI_SHOWUSAGE;
+ if (o->txmixa == TX_OUT_AUX) {
+ o->txmixbset = i;
+ ast_cli(a->fd, "Changed auxvoice setting on Channel A to %d\n", o->txmixaset);
+ } else {
+ o->txmixbset = i;
+ ast_cli(a->fd, "Changed auxvoice setting on Channel B to %d\n", o->txmixbset);
+ }
+ mixer_write(o);
+ mult_set(o);
+ }
+ /* tune_auxoutput(o,i); */
+ } else if (!strcasecmp(a->argv[2], "txtone")) {
+ if (a->argc == 3)
+ ast_cli(a->fd, "Current Tx CTCSS modulation setting = %d\n", o->txctcssadj);
+ else {
+ i = atoi(a->argv[3]);
+ if ((i < 0) || (i > 999))
+ return CLI_SHOWUSAGE;
+ o->txctcssadj = i;
+ set_txctcss_level(o);
+ ast_cli(a->fd, "Changed Tx CTCSS modulation setting to %i\n", i);
+ }
+ o->txtestkey = 1;
+ usleep(5000000);
+ o->txtestkey = 0;
+ } else if (!strcasecmp(a->argv[2],"dump"))
+ pmrdump(o);
+ else if (!strcasecmp(a->argv[2],"nocap")) {
+ ast_cli(a->fd, "File capture (trace) was rx=%d tx=%d and now off.\n", o->b.rxcap2, o->b.txcap2);
+ ast_cli(a->fd, "File capture (raw) was rx=%d tx=%d and now off.\n", o->b.rxcapraw, o->b.txcapraw);
+ o->b.rxcapraw = o->b.txcapraw = o->b.rxcap2 = o->b.txcap2 = o->pmrChan->b.rxCapture = o->pmrChan->b.txCapture = 0;
+ if (frxcapraw) {
+ fclose(frxcapraw);
+ frxcapraw = NULL;
+ }
+ if (frxcaptrace) {
+ fclose(frxcaptrace);
+ frxcaptrace = NULL;
+ }
+ if (frxoutraw) {
+ fclose(frxoutraw);
+ frxoutraw = NULL;
+ }
+ if (ftxcapraw) {
+ fclose(ftxcapraw);
+ ftxcapraw = NULL;
+ }
+ if (ftxcaptrace) {
+ fclose(ftxcaptrace);
+ ftxcaptrace = NULL;
+ }
+ if (ftxoutraw) {
+ fclose(ftxoutraw);
+ ftxoutraw = NULL;
+ }
+ } else if (!strcasecmp(a->argv[2], "rxtracecap")) {
+ if (!frxcaptrace)
+ frxcaptrace = fopen(RX_CAP_TRACE_FILE, "w");
+ ast_cli(a->fd, "Trace rx on.\n");
+ o->b.rxcap2 = o->pmrChan->b.rxCapture = 1;
+ } else if (!strcasecmp(a->argv[2], "txtracecap")) {
+ if (!ftxcaptrace)
+ ftxcaptrace = fopen(TX_CAP_TRACE_FILE, "w");
+ ast_cli(a->fd, "Trace tx on.\n");
+ o->b.txcap2 = o->pmrChan->b.txCapture = 1;
+ } else if (!strcasecmp(a->argv[2], "rxcap")) {
+ if (!frxcapraw)
+ frxcapraw = fopen(RX_CAP_RAW_FILE, "w");
+ ast_cli(a->fd, "cap rx raw on.\n");
+ o->b.rxcapraw = 1;
+ } else if (!strcasecmp(a->argv[2], "txcap")) {
+ if (!ftxcapraw)
+ ftxcapraw = fopen(TX_CAP_RAW_FILE, "w");
+ ast_cli(a->fd, "cap tx raw on.\n");
+ o->b.txcapraw = 1;
+ } else if (!strcasecmp(a->argv[2], "save")) {
+ tune_write(o);
+ ast_cli(a->fd, "Saved radio tuning settings to usbradio_tune.conf\n");
+ } else
+ return CLI_SHOWUSAGE;
+ return CLI_SUCCESS;
+}
+
+/*
+ set transmit ctcss modulation level
+ adjust mixer output or internal gain depending on output type
+ setting range is 0.0 to 0.9
+*/
+static int set_txctcss_level(struct chan_usbradio_pvt *o)
+{
+ if (o->txmixa == TX_OUT_LSD) {
+ o->txmixaset = (151 * o->txctcssadj) / 1000;
+ mixer_write(o);
+ mult_set(o);
+ } else if (o->txmixb == TX_OUT_LSD) {
+ o->txmixbset = (151 * o->txctcssadj) / 1000;
+ mixer_write(o);
+ mult_set(o);
+ } else {
+ *o->pmrChan->ptxCtcssAdjust = (o->txctcssadj * M_Q8) / 1000;
+ }
+ return 0;
+}
+/*
+ CLI debugging on and off
+*/
+static char *handle_cli_radio_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct chan_usbradio_pvt *o = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "radio set debug [off]";
+ e->usage =
+ "Usage: radio set debug [off]\n"
+ " Enable/Disable radio debugging.\n";
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc < 3 || a->argc > 4)
+ return CLI_SHOWUSAGE;
+ if (a->argc == 4 && strncasecmp(a->argv[3], "off", 3))
+ return CLI_SHOWUSAGE;
+
+ o = find_desc(usbradio_active);
+
+ if (a->argc == 3)
+ o->debuglevel = 1;
+ else
+ o->debuglevel = 0;
+
+ ast_cli(a->fd, "USB Radio debugging %s.\n", o->debuglevel ? "enabled" : "disabled");
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_usbradio[] = {
+ AST_CLI_DEFINE(handle_cli_radio_key, "Simulate Rx Signal Present"),
+ AST_CLI_DEFINE(handle_cli_radio_unkey, "Simulate Rx Signal Lusb"),
+ AST_CLI_DEFINE(handle_cli_radio_tune, "Radio Tune"),
+ AST_CLI_DEFINE(handle_cli_radio_set_debug, "Enable/Disable Radio Debugging"),
+};
+
+/*
+ * store the callerid components
+ */
+#if 0
+static void store_callerid(struct chan_usbradio_pvt *o, const char *s)
+{
+ ast_callerid_split(s, o->cid_name, sizeof(o->cid_name), o->cid_num, sizeof(o->cid_num));
+}
+#endif
+
+static void store_rxdemod(struct chan_usbradio_pvt *o, const char *s)
+{
+ if (!strcasecmp(s, "no")) {
+ o->rxdemod = RX_AUDIO_NONE;
+ } else if (!strcasecmp(s, "speaker")) {
+ o->rxdemod = RX_AUDIO_SPEAKER;
+ } else if (!strcasecmp(s, "flat")) {
+ o->rxdemod = RX_AUDIO_FLAT;
+ } else {
+ ast_log(LOG_WARNING, "Unrecognized rxdemod parameter: %s\n", s);
+ }
+
+ ast_debug(4, "set rxdemod = %s\n", s);
+}
+
+
+static void store_txmixa(struct chan_usbradio_pvt *o, const char *s)
+{
+ if (!strcasecmp(s, "no"))
+ o->txmixa = TX_OUT_OFF;
+
+ else if (!strcasecmp(s, "voice"))
+ o->txmixa = TX_OUT_VOICE;
+ else if (!strcasecmp(s, "tone"))
+ o->txmixa = TX_OUT_LSD;
+ else if (!strcasecmp(s, "composite"))
+ o->txmixa = TX_OUT_COMPOSITE;
+ else if (!strcasecmp(s, "auxvoice"))
+ o->txmixb = TX_OUT_AUX;
+ else
+ ast_log(LOG_WARNING, "Unrecognized txmixa parameter: %s\n", s);
+
+ ast_debug(4, "set txmixa = %s\n", s);
+}
+
+static void store_txmixb(struct chan_usbradio_pvt *o, const char *s)
+{
+ if (!strcasecmp(s, "no"))
+ o->txmixb = TX_OUT_OFF;
+ else if (!strcasecmp(s, "voice"))
+ o->txmixb = TX_OUT_VOICE;
+ else if (!strcasecmp(s, "tone"))
+ o->txmixb = TX_OUT_LSD;
+ else if (!strcasecmp(s, "composite"))
+ o->txmixb = TX_OUT_COMPOSITE;
+ else if (!strcasecmp(s, "auxvoice"))
+ o->txmixb = TX_OUT_AUX;
+ else
+ ast_log(LOG_WARNING, "Unrecognized txmixb parameter: %s\n", s);
+
+ ast_debug(4, "set txmixb = %s\n", s);
+}
+
+static void store_rxcdtype(struct chan_usbradio_pvt *o, const char *s)
+{
+ if (!strcasecmp(s, "no"))
+ o->rxcdtype = CD_IGNORE;
+ else if (!strcasecmp(s, "usb"))
+ o->rxcdtype = CD_HID;
+ else if (!strcasecmp(s, "dsp"))
+ o->rxcdtype = CD_XPMR_NOISE;
+ else if (!strcasecmp(s, "vox"))
+ o->rxcdtype = CD_XPMR_VOX;
+ else if (!strcasecmp(s, "usbinvert"))
+ o->rxcdtype = CD_HID_INVERT;
+ else
+ ast_log(LOG_WARNING, "Unrecognized rxcdtype parameter: %s\n", s);
+
+ ast_debug(4, "set rxcdtype = %s\n", s);
+}
+
+static void store_rxsdtype(struct chan_usbradio_pvt *o, const char *s)
+{
+ if (!strcasecmp(s, "no") || !strcasecmp(s, "SD_IGNORE"))
+ o->rxsdtype = SD_IGNORE;
+ else if (!strcasecmp(s, "usb") || !strcasecmp(s, "SD_HID"))
+ o->rxsdtype = SD_HID;
+ else if (!strcasecmp(s, "usbinvert") || !strcasecmp(s, "SD_HID_INVERT"))
+ o->rxsdtype = SD_HID_INVERT;
+ else if (!strcasecmp(s, "software") || !strcasecmp(s, "SD_XPMR"))
+ o->rxsdtype = SD_XPMR;
+ else
+ ast_log(LOG_WARNING, "Unrecognized rxsdtype parameter: %s\n", s);
+
+ ast_debug(4, "set rxsdtype = %s\n", s);
+}
+
+static void store_rxgain(struct chan_usbradio_pvt *o, const char *s)
+{
+ float f;
+ if (sscanf(s, "%f", &f) == 1)
+ o->rxgain = f;
+ ast_debug(4, "set rxgain = %f\n", f);
+}
+
+static void store_rxvoiceadj(struct chan_usbradio_pvt *o, const char *s)
+{
+ float f;
+ if (sscanf(s, "%f", &f) == 1)
+ o->rxvoiceadj = f;
+ ast_debug(4, "set rxvoiceadj = %f\n", f);
+}
+
+static void store_rxctcssadj(struct chan_usbradio_pvt *o, const char *s)
+{
+ float f;
+ if (sscanf(s, "%f", &f) == 1)
+ o->rxctcssadj = f;
+ ast_debug(4, "set rxctcssadj = %f\n", f);
+}
+
+static void store_txtoctype(struct chan_usbradio_pvt *o, const char *s)
+{
+ if (!strcasecmp(s, "no") || !strcasecmp(s, "TOC_NONE"))
+ o->txtoctype = TOC_NONE;
+ else if (!strcasecmp(s, "phase") || !strcasecmp(s, "TOC_PHASE"))
+ o->txtoctype = TOC_PHASE;
+ else if (!strcasecmp(s, "notone") || !strcasecmp(s, "TOC_NOTONE"))
+ o->txtoctype = TOC_NOTONE;
+ else
+ ast_log(LOG_WARNING, "Unrecognized txtoctype parameter: %s\n", s);
+
+ ast_debug(4, "set txtoctype = %s\n", s);
+}
+
+static void store_rxctcssfreq(struct chan_usbradio_pvt *o, const char *s)
+{
+ float f;
+ if (sscanf(s, "%f", &f) == 1)
+ o->rxctcssfreq = f;
+ ast_debug(4, "set rxctcss = %f\n", f);
+}
+
+static void store_txctcssfreq(struct chan_usbradio_pvt *o, const char *s)
+{
+ float f;
+ if (sscanf(s, "%f", &f) == 1)
+ o->txctcssfreq = f;
+ ast_debug(4, "set txctcss = %f\n", f);
+}
+
+static void tune_txoutput(struct chan_usbradio_pvt *o, int value)
+{
+ o->txtestkey = 1;
+ o->pmrChan->txPttIn = 1;
+
+#if 0
+ /* generate 1KHz tone at 7200 peak */
+ o->pmrChan->spsSigGen1->freq = 10000;
+ o->pmrChan->spsSigGen1->outputGain = (float)(0.22 * M_Q8);
+ o->pmrChan->b.startSpecialTone = 1;
+#endif
+
+ TxTestTone(o->pmrChan, 1);
+
+ usleep(5000000);
+ /* o->pmrChan->b.stopSpecialTone = 1; */
+ usleep(100000);
+
+ TxTestTone(o->pmrChan, 0);
+
+ o->pmrChan->txPttIn = 0;
+ o->txtestkey = 0;
+}
+
+static void tune_rxinput(struct chan_usbradio_pvt *o)
+{
+ const int target = 23000;
+ const int tolerance = 2000;
+ const int settingmin = 1;
+ const int settingstart = 2;
+ const int maxtries = 12;
+
+ float settingmax;
+
+ int setting = 0, tries = 0, tmpdiscfactor, meas;
+ int tunetype = 0;
+
+ settingmax = o->micmax;
+
+ if (o->pmrChan->rxDemod)
+ tunetype = 1;
+
+ setting = settingstart;
+
+ while (tries < maxtries) {
+ setamixer(o->devicenum, MIXER_PARAM_MIC_CAPTURE_VOL, setting, 0);
+ setamixer(o->devicenum, MIXER_PARAM_MIC_BOOST, o->rxboostset, 0);
+ usleep(100000);
+ if (o->rxcdtype == CD_XPMR_VOX || o->rxdemod == RX_AUDIO_SPEAKER) {
+ ast_debug(4, "Measure Direct Input\n");
+ o->pmrChan->spsMeasure->source = o->pmrChan->spsRx->source;
+ o->pmrChan->spsMeasure->discfactor = 1000;
+ o->pmrChan->spsMeasure->enabled = 1;
+ o->pmrChan->spsMeasure->amax = o->pmrChan->spsMeasure->amin = 0;
+ usleep(400000);
+ meas = o->pmrChan->spsMeasure->apeak;
+ o->pmrChan->spsMeasure->enabled = 0;
+ } else {
+ ast_debug(4, "Measure HF Noise\n");
+ tmpdiscfactor = o->pmrChan->spsRx->discfactor;
+ o->pmrChan->spsRx->discfactor = (i16)1000;
+ o->pmrChan->spsRx->discounteru = o->pmrChan->spsRx->discounterl = 0;
+ o->pmrChan->spsRx->amax = o->pmrChan->spsRx->amin = 0;
+ usleep(200000);
+ meas = o->pmrChan->rxRssi;
+ o->pmrChan->spsRx->discfactor = tmpdiscfactor;
+ o->pmrChan->spsRx->discounteru = o->pmrChan->spsRx->discounterl = 0;
+ o->pmrChan->spsRx->amax = o->pmrChan->spsRx->amin = 0;
+ }
+ if (!meas)
+ meas++;
+ ast_log(LOG_NOTICE, "tries=%d, setting=%d, meas=%i\n", tries, setting, meas);
+
+ if ( meas < (target - tolerance) || meas > (target + tolerance) || tries < 3)
+ setting = setting * target / meas;
+ else if (tries > 4 && meas > (target - tolerance) && meas < (target + tolerance) )
+ break;
+
+ if (setting < settingmin)
+ setting = settingmin;
+ else if (setting > settingmax)
+ setting = settingmax;
+
+ tries++;
+ }
+ ast_log(LOG_NOTICE, "DONE tries=%d, setting=%d, meas=%i\n", tries,
+ (setting * 1000) / o->micmax, meas);
+ if (meas < (target - tolerance) || meas > (target + tolerance))
+ ast_log(LOG_NOTICE, "ERROR: RX INPUT ADJUST FAILED.\n");
+ else {
+ ast_log(LOG_NOTICE, "INFO: RX INPUT ADJUST SUCCESS.\n");
+ o->rxmixerset = (setting * 1000) / o->micmax;
+ }
+}
+/*
+*/
+static void tune_rxvoice(struct chan_usbradio_pvt *o)
+{
+ const int target = 7200; /* peak */
+ const int tolerance = 360; /* peak to peak */
+ const float settingmin = 0.1;
+ const float settingmax = 4;
+ const float settingstart = 1;
+ const int maxtries = 12;
+
+ float setting;
+
+ int tries = 0, meas;
+
+ ast_log(LOG_NOTICE, "INFO: RX VOICE ADJUST START.\n");
+ ast_log(LOG_NOTICE, "target=%d tolerance=%d\n", target, tolerance);
+
+ if (!o->pmrChan->spsMeasure)
+ ast_log(LOG_ERROR, "NO MEASURE BLOCK.\n");
+
+ if (!o->pmrChan->spsMeasure->source || !o->pmrChan->prxVoiceAdjust )
+ ast_log(LOG_ERROR, "NO SOURCE OR MEASURE SETTING.\n");
+
+ o->pmrChan->spsMeasure->source = o->pmrChan->spsRxOut->sink;
+ o->pmrChan->spsMeasure->enabled = 1;
+ o->pmrChan->spsMeasure->discfactor = 1000;
+
+ setting=settingstart;
+
+ ast_debug(4, "ERROR: NO MEASURE BLOCK.\n");
+
+ while (tries < maxtries) {
+ *(o->pmrChan->prxVoiceAdjust) = setting * M_Q8;
+ usleep(10000);
+ o->pmrChan->spsMeasure->amax = o->pmrChan->spsMeasure->amin = 0;
+ usleep(1000000);
+ meas = o->pmrChan->spsMeasure->apeak;
+ ast_log(LOG_NOTICE, "tries=%d, setting=%f, meas=%i\n", tries, setting, meas);
+
+ if (meas < (target - tolerance) || meas > (target + tolerance) || tries < 3)
+ setting = setting * target / meas;
+ else if (tries > 4 && meas > (target - tolerance) && meas < (target + tolerance))
+ break;
+ if (setting < settingmin)
+ setting = settingmin;
+ else if (setting > settingmax)
+ setting = settingmax;
+
+ tries++;
+ }
+
+ o->pmrChan->spsMeasure->enabled = 0;
+
+ ast_log(LOG_NOTICE, "DONE tries=%d, setting=%f, meas=%f\n", tries, setting, (float)meas);
+ if (meas < (target - tolerance) || meas > (target + tolerance))
+ ast_log(LOG_ERROR, "RX VOICE GAIN ADJUST FAILED.\n");
+ else {
+ ast_log(LOG_NOTICE, "RX VOICE GAIN ADJUST SUCCESS.\n");
+ o->rxvoiceadj = setting;
+ }
+}
+
+static void tune_rxctcss(struct chan_usbradio_pvt *o)
+{
+ const int target = 4096;
+ const int tolerance = 100;
+ const float settingmin = 0.1;
+ const float settingmax = 4;
+ const float settingstart = 1;
+ const int maxtries = 12;
+
+ float setting;
+ int tries = 0, meas;
+
+ ast_log(LOG_NOTICE, "RX CTCSS ADJUST START.\n");
+ ast_log(LOG_NOTICE, "target=%d tolerance=%d \n", target, tolerance);
+
+ o->pmrChan->spsMeasure->source = o->pmrChan->prxCtcssMeasure;
+ o->pmrChan->spsMeasure->discfactor = 400;
+ o->pmrChan->spsMeasure->enabled = 1;
+
+ setting = settingstart;
+
+ while (tries < maxtries) {
+ *(o->pmrChan->prxCtcssAdjust) = setting * M_Q8;
+ usleep(10000);
+ o->pmrChan->spsMeasure->amax = o->pmrChan->spsMeasure->amin = 0;
+ usleep(500000);
+ meas = o->pmrChan->spsMeasure->apeak;
+ ast_debug(4, "tries=%d, setting=%f, meas=%i\n", tries, setting, meas);
+
+ if (meas < (target - tolerance) || meas > (target + tolerance) || tries < 3)
+ setting = setting * target / meas;
+ else if (tries > 4 && meas > (target - tolerance) && meas < (target + tolerance))
+ break;
+ if (setting < settingmin)
+ setting = settingmin;
+ else if (setting > settingmax)
+ setting = settingmax;
+
+ tries++;
+ }
+ o->pmrChan->spsMeasure->enabled = 0;
+ ast_debug(4, "DONE tries=%d, setting=%f, meas=%f\n", tries, setting, (float)meas);
+ if (meas < (target - tolerance) || meas > (target + tolerance))
+ ast_log(LOG_ERROR, "RX CTCSS GAIN ADJUST FAILED.\n");
+ else {
+ ast_log(LOG_NOTICE, "RX CTCSS GAIN ADJUST SUCCESS.\n");
+ o->rxctcssadj = setting;
+ }
+}
+/*
+ this file then is included in chan_usbradio.conf
+ #include /etc/asterisk/usbradio_tune.conf
+*/
+static void tune_write(struct chan_usbradio_pvt *o)
+{
+ FILE *fp;
+
+ fp = fopen("/etc/asterisk/usbradio_tune.conf", "w");
+
+ if (!strcmp(o->name, "dsp"))
+ fprintf(fp, "[general]\n");
+ else
+ fprintf(fp, "[%s]\n", o->name);
+
+ fprintf(fp, "; name=%s\n", o->name);
+ fprintf(fp, "; devicenum=%d\n", o->devicenum);
+
+ fprintf(fp, "rxmixerset=%d\n", o->rxmixerset);
+ fprintf(fp, "rxboostset=%d\n", o->rxboostset);
+ fprintf(fp, "txmixaset=%d\n", o->txmixaset);
+ fprintf(fp, "txmixbset=%d\n", o->txmixbset);
+
+ fprintf(fp, "rxvoiceadj=%f\n", o->rxvoiceadj);
+ fprintf(fp, "rxctcssadj=%f\n", o->rxctcssadj);
+ fprintf(fp, "txctcssadj=%d\n", o->txctcssadj);
+
+ fprintf(fp, "rxsquelchadj=%d\n", o->rxsquelchadj);
+ fclose(fp);
+}
+
+static void mixer_write(struct chan_usbradio_pvt *o)
+{
+ setamixer(o->devicenum, MIXER_PARAM_MIC_PLAYBACK_SW, 0, 0);
+ setamixer(o->devicenum, MIXER_PARAM_MIC_PLAYBACK_VOL, 0, 0);
+ setamixer(o->devicenum, MIXER_PARAM_SPKR_PLAYBACK_SW, 1, 0);
+ setamixer(o->devicenum, MIXER_PARAM_SPKR_PLAYBACK_VOL,
+ o->txmixaset * o->spkrmax / 1000,
+ o->txmixbset * o->spkrmax / 1000);
+ setamixer(o->devicenum, MIXER_PARAM_MIC_CAPTURE_VOL,
+ o->rxmixerset * o->micmax / 1000, 0);
+ setamixer(o->devicenum, MIXER_PARAM_MIC_BOOST, o->rxboostset, 0);
+ setamixer(o->devicenum, MIXER_PARAM_MIC_CAPTURE_SW, 1, 0);
+}
+/*
+ adjust dsp multiplier to add resolution to tx level adjustment
+*/
+static void mult_set(struct chan_usbradio_pvt *o)
+{
+
+ if (o->pmrChan->spsTxOutA) {
+ o->pmrChan->spsTxOutA->outputGain =
+ mult_calc((o->txmixaset * 152) / 1000);
+ }
+ if (o->pmrChan->spsTxOutB) {
+ o->pmrChan->spsTxOutB->outputGain =
+ mult_calc((o->txmixbset * 152) / 1000);
+ }
+}
+/*
+ * input 0 - 151 outputs are pot and multiplier
+ */
+static int mult_calc(int value)
+{
+ const int multx = M_Q8;
+ int pot, mult;
+
+ pot= ((int)(value / 4) * 4) + 2;
+ mult = multx - ((multx * (3 - (value % 4))) / (pot + 2));
+ return mult;
+}
+
+#define pd(x) ast_debug(4, #x" = %d\n", x)
+#define pp(x) ast_debug(4, #x" = %p\n", x)
+#define ps(x) ast_debug(4, #x" = %s\n", x)
+#define pf(x) ast_debug(4, #x" = %f\n", x)
+/*
+*/
+static void pmrdump(struct chan_usbradio_pvt *o)
+{
+ t_pmr_chan *p;
+
+ p = o->pmrChan;
+
+ ast_debug(4, "odump()\n");
+
+ pd(o->devicenum);
+
+ pd(o->rxdemod);
+ pd(o->rxcdtype);
+ pd(o->rxsdtype);
+ pd(o->txtoctype);
+
+ pd(o->rxmixerset);
+ pf(o->rxvoiceadj);
+ pf(o->rxctcssadj);
+ pd(o->rxsquelchadj);
+
+ pd(o->txprelim);
+ pd(o->txmixa);
+ pd(o->txmixb);
+
+ pd(o->txmixaset);
+ pd(o->txmixbset);
+
+ ast_debug(4, "pmrdump()\n");
+
+ ast_debug(4, "prxSquelchAdjust=%d\n", *(o->pmrChan->prxSquelchAdjust));
+
+ pd(p->rxCarrierPoint);
+ pd(p->rxCarrierHyst);
+
+ pd(p->rxCtcss->relax);
+ pf(p->rxCtcssFreq);
+ pd(p->rxCtcssIndex);
+ pf(p->txCtcssFreq);
+
+ pd(p->txMixA);
+ pd(p->txMixB);
+
+ pd(p->rxDeEmpEnable);
+ pd(p->rxCenterSlicerEnable);
+ pd(p->rxCtcssDecodeEnable);
+ pd(p->rxDcsDecodeEnable);
+
+ pd(p->txHpfEnable);
+ pd(p->txLimiterEnable);
+ pd(p->txPreEmpEnable);
+ pd(p->txLpfEnable);
+
+ if (p->spsTxOutA)
+ pd(p->spsTxOutA->outputGain);
+ if (p->spsTxOutB)
+ pd(p->spsTxOutB->outputGain);
+
+ return;
+}
+
+
+/*
+ * grab fields from the config file, init the descriptor and open the device.
+ */
+static struct chan_usbradio_pvt *store_config(struct ast_config *cfg, char *ctg)
+{
+ struct ast_variable *v;
+ struct chan_usbradio_pvt *o;
+ struct ast_config *cfg1;
+ struct ast_flags config_flags = { 0 };
+
+ if (ctg == NULL) {
+ traceusb1(" store_config() ctg == NULL\n");
+ o = &usbradio_default;
+ ctg = "general";
+ } else {
+ if (!(o = ast_calloc(1, sizeof(*o)))){
+ return NULL;
+ }
+ *o = usbradio_default;
+ /* "general" is also the default thing */
+ if (strcmp(ctg, "general") == 0) {
+ o->name = ast_strdup("dsp");
+ usbradio_active = o->name;
+ } else
+ o->name = ast_strdup(ctg);
+ }
+
+ strcpy(o->mohinterpret, "default");
+ o->micmax = amixer_max(o->devicenum, MIXER_PARAM_MIC_CAPTURE_VOL);
+ o->spkrmax = amixer_max(o->devicenum, MIXER_PARAM_SPKR_PLAYBACK_VOL);
+ /* fill other fields from configuration */
+ for (v = ast_variable_browse(cfg, ctg); v; v = v->next) {
+
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
+ continue;
+ CV_START(v->name, v->value);
+
+ CV_UINT("frags", o->frags);
+ CV_UINT("queuesize", o->queuesize);
+ CV_UINT("devicenum", o->devicenum);
+ CV_UINT("debug", usbradio_debug);
+ CV_BOOL("rxcpusaver", o->rxcpusaver);
+ CV_BOOL("txcpusaver", o->txcpusaver);
+ CV_BOOL("invertptt", o->invertptt);
+ CV_F("rxdemod", store_rxdemod(o, v->value));
+ CV_BOOL("txprelim", o->txprelim);;
+ CV_F("txmixa", store_txmixa(o, v->value));
+ CV_F("txmixb", store_txmixb(o, v->value));
+ CV_F("carrierfrom", store_rxcdtype(o, v->value));
+ CV_F("rxsdtype", store_rxsdtype(o, v->value));
+ CV_F("rxctcssfreq", store_rxctcssfreq(o, v->value));
+ CV_F("txctcssfreq", store_txctcssfreq(o, v->value));
+ CV_F("rxgain", store_rxgain(o, v->value));
+ CV_BOOL("rxboostset", o->rxboostset);
+ CV_UINT("rxctcssrelax", o->rxctcssrelax);
+ CV_F("txtoctype", store_txtoctype(o, v->value));
+ CV_UINT("hdwtype", o->hdwtype);
+ CV_UINT("duplex", o->radioduplex);
+
+ CV_END;
+ }
+
+ cfg1 = ast_config_load(config1, config_flags);
+ if (!cfg1) {
+ o->rxmixerset = 500;
+ o->txmixaset = 500;
+ o->txmixbset = 500;
+ o->rxvoiceadj = 0.5;
+ o->rxctcssadj = 0.5;
+ o->txctcssadj = 200;
+ o->rxsquelchadj = 500;
+ ast_log(LOG_WARNING, "File %s not found, using default parameters.\n", config1);
+ } else {
+ for (v = ast_variable_browse(cfg1, ctg); v; v = v->next) {
+
+ CV_START(v->name, v->value);
+ CV_UINT("rxmixerset", o->rxmixerset);
+ CV_UINT("txmixaset", o->txmixaset);
+ CV_UINT("txmixbset", o->txmixbset);
+ CV_F("rxvoiceadj", store_rxvoiceadj(o, v->value));
+ CV_F("rxctcssadj", store_rxctcssadj(o, v->value));
+ CV_UINT("txctcssadj", o->txctcssadj);
+ CV_UINT("rxsquelchadj", o->rxsquelchadj);
+ CV_END;
+ }
+ ast_config_destroy(cfg1);
+ }
+
+ o->debuglevel = 0;
+
+ if (o == &usbradio_default) /* we are done with the default */
+ return NULL;
+
+ o->lastopen = ast_tvnow(); /* don't leave it 0 or tvdiff may wrap */
+ o->dsp = ast_dsp_new();
+ if (o->dsp) {
+ ast_dsp_set_features(o->dsp, DSP_FEATURE_DTMF_DETECT);
+ ast_dsp_digitmode(o->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_RELAXDTMF);
+ }
+
+ if (o->rxctcssfreq != 0 && o->rxdemod == RX_AUDIO_SPEAKER)
+ ast_log(LOG_ERROR, "Incompatable Options o->rxctcssfreq=%f and o->rxdemod=speaker\n", o->rxctcssfreq);
+
+ if (o->pmrChan == NULL) {
+ t_pmr_chan tChan;
+
+ memset(&tChan, 0, sizeof(tChan));
+
+ tChan.rxDemod = o->rxdemod;
+ tChan.rxCdType = o->rxcdtype;
+
+ tChan.txMod = o->txprelim;
+
+ tChan.txMixA = o->txmixa;
+ tChan.txMixB = o->txmixb;
+
+ tChan.rxCpuSaver = o->rxcpusaver;
+ tChan.txCpuSaver = o->txcpusaver;
+
+ tChan.rxCtcssFreq = o->rxctcssfreq;
+ tChan.txCtcssFreq = o->txctcssfreq;
+
+ o->pmrChan = createPmrChannel(&tChan, FRAME_SIZE);
+
+ o->pmrChan->radioDuplex = o->radioduplex;
+
+ o->pmrChan->rxCpuSaver = o->rxcpusaver;
+ o->pmrChan->txCpuSaver = o->txcpusaver;
+
+ *(o->pmrChan->prxSquelchAdjust) =
+ ((999 - o->rxsquelchadj) * 32767) / 1000;
+
+ o->pmrChan->spsRx->outputGain = o->rxvoiceadj*M_Q8;
+
+ o->pmrChan->txTocType = o->txtoctype;
+
+ if ((o->txmixa == TX_OUT_LSD) ||
+ (o->txmixa == TX_OUT_COMPOSITE) ||
+ (o->txmixb == TX_OUT_LSD) ||
+ (o->txmixb == TX_OUT_COMPOSITE)) {
+ *(o->pmrChan->prxCtcssAdjust) = o->rxctcssadj * M_Q8;
+ set_txctcss_level(o);
+ }
+
+ o->pmrChan->rxCtcss->relax = o->rxctcssrelax;
+
+ }
+
+ if ((o->txmixa != TX_OUT_VOICE) && (o->txmixb != TX_OUT_VOICE) &&
+ (o->txmixa != TX_OUT_COMPOSITE) && (o->txmixb != TX_OUT_COMPOSITE))
+ ast_log(LOG_ERROR, "No txvoice output configured.\n");
+
+ if (o->txctcssfreq &&
+ o->txmixa != TX_OUT_LSD && o->txmixa != TX_OUT_COMPOSITE &&
+ o->txmixb != TX_OUT_LSD && o->txmixb != TX_OUT_COMPOSITE)
+ ast_log(LOG_ERROR, "No txtone output configured.\n");
+
+ if (o->rxctcssfreq && o->pmrChan->rxCtcssIndex < 0)
+ ast_log(LOG_ERROR, "Invalid CTCSS Frequency.\n");
+
+ /* RxTestIt(o); */
+
+ mixer_write(o);
+ mult_set(o);
+ hidhdwconfig(o);
+
+ /* pmrdump(o); */
+
+ /* link into list of devices */
+ if (o != &usbradio_default) {
+ o->next = usbradio_default.next;
+ usbradio_default.next = o;
+ }
+ return o;
+}
+
+#if DEBUG_FILETEST == 1
+/*
+ Test It on a File
+*/
+int RxTestIt(struct chan_usbradio_pvt *o)
+{
+ const int numSamples = SAMPLES_PER_BLOCK;
+ const int numChannels = 16;
+
+ i16 sample, i, ii;
+
+ i32 txHangTime;
+
+ i16 txEnable;
+
+ t_pmr_chan tChan;
+ t_pmr_chan *pChan;
+
+ FILE *hInput = NULL, *hOutput = NULL, *hOutputTx = NULL;
+
+ i16 iBuff[numSamples * 2 * 6], oBuff[numSamples];
+
+ ast_debug(4, "RxTestIt()\n");
+
+ pChan = o->pmrChan;
+ pChan->b.txCapture = 1;
+ pChan->b.rxCapture = 1;
+
+ txEnable = 0;
+
+ hInput = fopen("/usr/src/xpmr/testdata/rx_in.pcm", "r");
+ if (!hInput){
+ ast_debug(4, " RxTestIt() File Not Found.\n");
+ return 0;
+ }
+ hOutput = fopen("/usr/src/xpmr/testdata/rx_debug.pcm", "w");
+
+ ast_debug(4, " RxTestIt() Working...\n");
+
+ while (!feof(hInput)) {
+ fread((void *)iBuff, 2, numSamples * 2 * 6, hInput);
+
+ if (txHangTime)
+ txHangTime -= numSamples;
+ if (txHangTime < 0)
+ txHangTime = 0;
+
+ if (pChan->rxCtcss->decode)
+ txHangTime = (8000 / 1000 * 2000);
+
+ if (pChan->rxCtcss->decode && !txEnable) {
+ txEnable = 1;
+ /* pChan->inputBlanking = (8000 / 1000 * 200); */
+ } else if (!pChan->rxCtcss->decode && txEnable) {
+ txEnable = 0;
+ }
+
+ PmrRx(pChan, iBuff, oBuff);
+
+ fwrite((void *)pChan->prxDebug, 2, numSamples * numChannels, hOutput);
+ }
+ pChan->b.txCapture = 0;
+ pChan->b.rxCapture = 0;
+
+ if (hInput)
+ fclose(hInput);
+ if (hOutput)
+ fclose(hOutput);
+
+ ast_debug(4, " RxTestIt() Complete.\n");
+
+ return 0;
+}
+#endif
+
+#include "./xpmr/xpmr.c"
+/*
+*/
+static int load_module(void)
+{
+ struct ast_config *cfg = NULL;
+ char *ctg = NULL;
+ struct ast_flags config_flags = { 0 };
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ /* load config file */
+ if (!(cfg = ast_config_load(config, config_flags))) {
+ ast_log(LOG_NOTICE, "Unable to load config %s\n", config);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ do {
+ store_config(cfg, ctg);
+ } while ( (ctg = ast_category_browse(cfg, ctg)) != NULL);
+
+ ast_config_destroy(cfg);
+
+ if (find_desc(usbradio_active) == NULL) {
+ ast_log(LOG_NOTICE, "Device %s not found\n", usbradio_active);
+ /* XXX we could default to 'dsp' perhaps ? */
+ /* XXX should cleanup allocated memory etc. */
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ if (ast_channel_register(&usbradio_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel type 'usb'\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ ast_cli_register_multiple(cli_usbradio, sizeof(cli_usbradio) / sizeof(struct ast_cli_entry));
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+/*
+*/
+static int unload_module(void)
+{
+ struct chan_usbradio_pvt *o;
+
+ ast_log(LOG_WARNING, "unload_module() called\n");
+
+ ast_channel_unregister(&usbradio_tech);
+ ast_cli_unregister_multiple(cli_usbradio, sizeof(cli_usbradio) / sizeof(struct ast_cli_entry));
+
+ for (o = usbradio_default.next; o; o = o->next) {
+
+ ast_log(LOG_WARNING, "destroyPmrChannel() called\n");
+ if (o->pmrChan)
+ destroyPmrChannel(o->pmrChan);
+
+ #if DEBUG_CAPTURES == 1
+ if (frxcapraw) { fclose(frxcapraw); frxcapraw = NULL; }
+ if (frxcaptrace) { fclose(frxcaptrace); frxcaptrace = NULL; }
+ if (frxoutraw) { fclose(frxoutraw); frxoutraw = NULL; }
+ if (ftxcapraw) { fclose(ftxcapraw); ftxcapraw = NULL; }
+ if (ftxcaptrace) { fclose(ftxcaptrace); ftxcaptrace = NULL; }
+ if (ftxoutraw) { fclose(ftxoutraw); ftxoutraw = NULL; }
+ #endif
+
+ close(o->sounddev);
+ if (o->dsp)
+ ast_dsp_free(o->dsp);
+ if (o->owner)
+ ast_softhangup(o->owner, AST_SOFTHANGUP_APPUNLOAD);
+ if (o->owner) /* XXX how ??? */
+ return -1;
+ /* XXX what about the thread ? */
+ /* XXX what about the memory allocated ? */
+ }
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "usb Console Channel Driver");
+
+/* end of file */
+
+
diff --git a/trunk/channels/chan_vpb.cc b/trunk/channels/chan_vpb.cc
new file mode 100644
index 000000000..eee797b18
--- /dev/null
+++ b/trunk/channels/chan_vpb.cc
@@ -0,0 +1,2899 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2003, Paul Bagyenda
+ * Paul Bagyenda <bagyenda@dsmagic.com>
+ * Copyright (C) 2004 - 2005, Ben Kramer
+ * Ben Kramer <ben@voicetronix.com.au>
+ *
+ * Daniel Bichara <daniel@bichara.com.br> - Brazilian CallerID detection (c)2004
+ *
+ * Welber Silveira - welberms@magiclink.com.br - (c)2004
+ * Copying CLID string to propper structure after detection
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief VoiceTronix Interface driver
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+ <depend>vpbapi</depend>
+ ***/
+
+extern "C" {
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/lock.h"
+#include "asterisk/utils.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/callerid.h"
+#include "asterisk/dsp.h"
+#include "asterisk/features.h"
+#include "asterisk/musiconhold.h"
+}
+
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <ctype.h>
+
+#include <vpbapi.h>
+#include <assert.h>
+
+#ifdef pthread_create
+#undef pthread_create
+#endif
+
+#define DEFAULT_GAIN 0
+#define DEFAULT_ECHO_CANCEL 1
+
+#define VPB_SAMPLES 160
+#define VPB_MAX_BUF VPB_SAMPLES*4 + AST_FRIENDLY_OFFSET
+
+#define VPB_NULL_EVENT 200
+
+#define VPB_WAIT_TIMEOUT 4000
+
+#define MAX_VPB_GAIN 12.0
+#define MIN_VPB_GAIN -12.0
+
+#define DTMF_CALLERID
+#define DTMF_CID_START 'D'
+#define DTMF_CID_STOP 'C'
+
+/**/
+#if defined(__cplusplus) || defined(c_plusplus)
+ extern "C" {
+#endif
+/**/
+
+static const char desc[] = "VoiceTronix V6PCI/V12PCI/V4PCI API Support";
+static const char tdesc[] = "Standard VoiceTronix API Driver";
+static const char config[] = "vpb.conf";
+
+/* Default context for dialtone mode */
+static char context[AST_MAX_EXTENSION] = "default";
+
+/* Default language */
+static char language[MAX_LANGUAGE] = "";
+
+static int gruntdetect_timeout = 3600000; /* Grunt detect timeout is 1hr. */
+
+static const int prefformat = AST_FORMAT_SLINEAR;
+
+/* Protect the interface list (of vpb_pvt's) */
+AST_MUTEX_DEFINE_STATIC(iflock);
+
+/* Protect the monitoring thread, so only one process can kill or start it, and not
+ when it's doing something critical. */
+AST_MUTEX_DEFINE_STATIC(monlock);
+
+/* This is the thread for the monitor which checks for input on the channels
+ which are not currently in use. */
+static pthread_t monitor_thread;
+
+static int mthreadactive = -1; /* Flag for monitoring monitorthread.*/
+
+
+static int restart_monitor(void);
+
+/* The private structures of the VPB channels are
+ linked for selecting outgoing channels */
+
+#define MODE_DIALTONE 1
+#define MODE_IMMEDIATE 2
+#define MODE_FXO 3
+
+/* Pick a country or add your own! */
+/* These are the tones that are played to the user */
+#define TONES_AU
+/* #define TONES_USA */
+
+#ifdef TONES_AU
+static VPB_TONE Dialtone = {440, 440, 440, -10, -10, -10, 5000, 0 };
+static VPB_TONE Busytone = {470, 0, 0, -10, -100, -100, 5000, 0 };
+static VPB_TONE Ringbacktone = {400, 50, 440, -10, -10, -10, 1400, 800 };
+#endif
+#ifdef TONES_USA
+static VPB_TONE Dialtone = {350, 440, 0, -16, -16, -100, 10000, 0};
+static VPB_TONE Busytone = {480, 620, 0, -10, -10, -100, 500, 500};
+static VPB_TONE Ringbacktone = {440, 480, 0, -20, -20, -100, 2000, 4000};
+#endif
+
+/* grunt tone defn's */
+#if 0
+static VPB_DETECT toned_grunt = { 3, VPB_GRUNT, 1, 2000, 3000, 0, 0, -40, 0, 0, 0, 40, { { VPB_DELAY, 1000, 0, 0 }, { VPB_RISING, 0, 40, 0 }, { 0, 100, 0, 0 } } };
+#endif
+static VPB_DETECT toned_ungrunt = { 2, VPB_GRUNT, 1, 2000, 1, 0, 0, -40, 0, 0, 30, 40, { { 0, 0, 0, 0 } } };
+
+/* Use loop polarity detection for CID */
+static int UsePolarityCID=0;
+
+/* Use loop drop detection */
+static int UseLoopDrop=1;
+
+/* To use or not to use Native bridging */
+static int UseNativeBridge=1;
+
+/* Use Asterisk Indication or VPB */
+static int use_ast_ind=0;
+
+/* Use Asterisk DTMF detection or VPB */
+static int use_ast_dtmfdet=0;
+
+static int relaxdtmf=0;
+
+/* Use Asterisk DTMF play back or VPB */
+static int use_ast_dtmf=0;
+
+/* Break for DTMF on native bridge ? */
+static int break_for_dtmf=1;
+
+/* Set EC suppression threshold */
+static int ec_supp_threshold=-1;
+
+/* Inter Digit Delay for collecting DTMF's */
+static int dtmf_idd = 3000;
+
+#define TIMER_PERIOD_RINGBACK 2000
+#define TIMER_PERIOD_BUSY 700
+#define TIMER_PERIOD_RING 4000
+static int timer_period_ring = TIMER_PERIOD_RING;
+
+#define VPB_EVENTS_ALL (VPB_MRING|VPB_MDIGIT|VPB_MDTMF|VPB_MTONEDETECT|VPB_MTIMEREXP|VPB_MPLAY_UNDERFLOW \
+ |VPB_MRECORD_OVERFLOW|VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \
+ |VPB_MRING_OFF|VPB_MDROP|VPB_MSTATION_FLASH)
+#define VPB_EVENTS_NODROP (VPB_MRING|VPB_MDIGIT|VPB_MDTMF|VPB_MTONEDETECT|VPB_MTIMEREXP|VPB_MPLAY_UNDERFLOW \
+ |VPB_MRECORD_OVERFLOW|VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \
+ |VPB_MRING_OFF|VPB_MSTATION_FLASH)
+#define VPB_EVENTS_NODTMF (VPB_MRING|VPB_MDIGIT|VPB_MTONEDETECT|VPB_MTIMEREXP|VPB_MPLAY_UNDERFLOW \
+ |VPB_MRECORD_OVERFLOW|VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \
+ |VPB_MRING_OFF|VPB_MDROP|VPB_MSTATION_FLASH)
+#define VPB_EVENTS_STAT (VPB_MRING|VPB_MDIGIT|VPB_MDTMF|VPB_MTONEDETECT|VPB_MTIMEREXP|VPB_MPLAY_UNDERFLOW \
+ |VPB_MRECORD_OVERFLOW|VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \
+ |VPB_MRING_OFF|VPB_MSTATION_FLASH)
+
+
+/* Dialing parameters for Australia */
+/* #define DIAL_WITH_CALL_PROGRESS */
+VPB_TONE_MAP DialToneMap[] = { { VPB_BUSY_AUST, VPB_CALL_DISCONNECT, 0 },
+ { VPB_DIAL, VPB_CALL_DIALTONE, 0 },
+ { VPB_RINGBACK_308, VPB_CALL_RINGBACK, 0 },
+ { VPB_BUSY_AUST, VPB_CALL_BUSY, 0 },
+ { VPB_GRUNT, VPB_CALL_GRUNT, 0 },
+ { 0, 0, 1 } };
+#define VPB_DIALTONE_WAIT 2000 /* Wait up to 2s for a dialtone */
+#define VPB_RINGWAIT 4000 /* Wait up to 4s for ring tone after dialing */
+#define VPB_CONNECTED_WAIT 4000 /* If no ring tone detected for 4s then consider call connected */
+#define TIMER_PERIOD_NOANSWER 120000 /* Let it ring for 120s before deciding theres noone there */
+
+#define MAX_BRIDGES_V4PCI 2
+#define MAX_BRIDGES_V12PCI 128
+
+/* port states */
+#define VPB_STATE_ONHOOK 0
+#define VPB_STATE_OFFHOOK 1
+#define VPB_STATE_DIALLING 2
+#define VPB_STATE_JOINED 3
+#define VPB_STATE_GETDTMF 4
+#define VPB_STATE_PLAYDIAL 5
+#define VPB_STATE_PLAYBUSY 6
+#define VPB_STATE_PLAYRING 7
+
+#define VPB_GOT_RXHWG 1
+#define VPB_GOT_TXHWG 2
+#define VPB_GOT_RXSWG 4
+#define VPB_GOT_TXSWG 8
+
+typedef struct {
+ int inuse;
+ struct ast_channel *c0, *c1, **rc;
+ struct ast_frame **fo;
+ int flags;
+ ast_mutex_t lock;
+ ast_cond_t cond;
+ int endbridge;
+} vpb_bridge_t;
+
+static vpb_bridge_t * bridges;
+static int max_bridges = MAX_BRIDGES_V4PCI;
+
+AST_MUTEX_DEFINE_STATIC(bridge_lock);
+
+typedef enum {
+ vpb_model_unknown = 0,
+ vpb_model_v4pci,
+ vpb_model_v12pci
+} vpb_model_t;
+
+static struct vpb_pvt {
+
+ ast_mutex_t owner_lock; /* Protect blocks that expect ownership to remain the same */
+ struct ast_channel *owner; /* Channel who owns us, possibly NULL */
+
+ int golock; /* Got owner lock ? */
+
+ int mode; /* fxo/imediate/dialtone*/
+ int handle; /* Handle for vpb interface */
+
+ int state; /* used to keep port state (internal to driver) */
+
+ int group; /* Which group this port belongs to */
+ ast_group_t callgroup; /* Call group */
+ ast_group_t pickupgroup; /* Pickup group */
+
+
+ char dev[256]; /* Device name, eg vpb/1-1 */
+ vpb_model_t vpb_model; /* card model */
+
+ struct ast_frame f, fr; /* Asterisk frame interface */
+ char buf[VPB_MAX_BUF]; /* Static buffer for reading frames */
+
+ int dialtone; /* NOT USED */
+ float txgain, rxgain; /* Hardware gain control */
+ float txswgain, rxswgain; /* Software gain control */
+
+ int wantdtmf; /* Waiting for DTMF. */
+ char context[AST_MAX_EXTENSION]; /* The context for this channel */
+
+ char ext[AST_MAX_EXTENSION]; /* DTMF buffer for the ext[ens] */
+ char language[MAX_LANGUAGE]; /* language being used */
+ char callerid[AST_MAX_EXTENSION]; /* CallerId used for directly connected phone */
+ int callerid_type; /* Caller ID type: 0=>none 1=>vpb 2=>AstV23 3=>AstBell */
+ char cid_num[AST_MAX_EXTENSION];
+ char cid_name[AST_MAX_EXTENSION];
+
+ int dtmf_caller_pos; /* DTMF CallerID detection (Brazil)*/
+
+ int lastoutput; /* Holds the last Audio format output'ed */
+ int lastinput; /* Holds the last Audio format input'ed */
+ int last_ignore_dtmf;
+
+ void *busy_timer; /* Void pointer for busy vpb_timer */
+ int busy_timer_id; /* unique timer ID for busy timer */
+
+ void *ringback_timer; /* Void pointer for ringback vpb_timer */
+ int ringback_timer_id; /* unique timer ID for ringback timer */
+
+ void *ring_timer; /* Void pointer for ring vpb_timer */
+ int ring_timer_id; /* unique timer ID for ring timer */
+
+ void *dtmfidd_timer; /* Void pointer for DTMF IDD vpb_timer */
+ int dtmfidd_timer_id; /* unique timer ID for DTMF IDD timer */
+
+ struct ast_dsp *vad; /* AST Voice Activation Detection dsp */
+
+ struct timeval lastgrunt; /* time stamp of last grunt event */
+
+ ast_mutex_t lock; /* This one just protects bridge ptr below */
+ vpb_bridge_t *bridge;
+
+ int stopreads; /* Stop reading...*/
+ int read_state; /* Read state */
+ int chuck_count; /* a count of packets weve chucked away!*/
+ pthread_t readthread; /* For monitoring read channel. One per owned channel. */
+
+ ast_mutex_t record_lock; /* This one prevents reentering a record_buf block */
+ ast_mutex_t play_lock; /* This one prevents reentering a play_buf block */
+ int play_buf_time; /* How long the last play_buf took */
+ struct timeval lastplay; /* Last play time */
+
+ ast_mutex_t play_dtmf_lock;
+ char play_dtmf[16];
+
+ int faxhandled; /* has a fax tone been handled ? */
+
+ struct vpb_pvt *next; /* Next channel in list */
+
+} *iflist = NULL;
+
+static struct ast_channel *vpb_new(struct vpb_pvt *i, enum ast_channel_state state, char *context);
+static void *do_chanreads(void *pvt);
+static struct ast_channel *vpb_request(const char *type, int format, void *data, int *cause);
+static int vpb_digit_begin(struct ast_channel *ast, char digit);
+static int vpb_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int vpb_call(struct ast_channel *ast, char *dest, int timeout);
+static int vpb_hangup(struct ast_channel *ast);
+static int vpb_answer(struct ast_channel *ast);
+static struct ast_frame *vpb_read(struct ast_channel *ast);
+static int vpb_write(struct ast_channel *ast, struct ast_frame *frame);
+static enum ast_bridge_result ast_vpb_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
+static int vpb_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+static int vpb_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+
+static struct ast_channel_tech vpb_tech = {
+ type: "vpb",
+ description: tdesc,
+ capabilities: AST_FORMAT_SLINEAR,
+ properties: 0,
+ requester: vpb_request,
+ devicestate: NULL,
+ send_digit_begin: vpb_digit_begin,
+ send_digit_end: vpb_digit_end,
+ call: vpb_call,
+ hangup: vpb_hangup,
+ answer: vpb_answer,
+ read: vpb_read,
+ write: vpb_write,
+ send_text: NULL,
+ send_image: NULL,
+ send_html: NULL,
+ exception: NULL,
+ bridge: ast_vpb_bridge,
+ indicate: vpb_indicate,
+ fixup: vpb_fixup,
+ setoption: NULL,
+ queryoption: NULL,
+ transfer: NULL,
+ write_video: NULL,
+ bridged_channel: NULL
+};
+
+static struct ast_channel_tech vpb_tech_indicate = {
+ type: "vpb",
+ description: tdesc,
+ capabilities: AST_FORMAT_SLINEAR,
+ properties: 0,
+ requester: vpb_request,
+ devicestate: NULL,
+ send_digit_begin: vpb_digit_begin,
+ send_digit_end: vpb_digit_end,
+ call: vpb_call,
+ hangup: vpb_hangup,
+ answer: vpb_answer,
+ read: vpb_read,
+ write: vpb_write,
+ send_text: NULL,
+ send_image: NULL,
+ send_html: NULL,
+ exception: NULL,
+ bridge: ast_vpb_bridge,
+ indicate: NULL,
+ fixup: vpb_fixup,
+ setoption: NULL,
+ queryoption: NULL,
+ transfer: NULL,
+ write_video: NULL,
+ bridged_channel: NULL
+};
+
+/* Can't get ast_vpb_bridge() working on v4pci without either a horrible
+* high pitched feedback noise or bad hiss noise depending on gain settings
+* Get asterisk to do the bridging
+*/
+#define BAD_V4PCI_BRIDGE
+
+/* This one enables a half duplex bridge which may be required to prevent high pitched
+ * feedback when getting asterisk to do the bridging and when using certain gain settings.
+ */
+/* #define HALF_DUPLEX_BRIDGE */
+
+/* This is the Native bridge code, which Asterisk will try before using its own bridging code */
+static enum ast_bridge_result ast_vpb_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms)
+{
+ struct vpb_pvt *p0 = (struct vpb_pvt *)c0->tech_pvt;
+ struct vpb_pvt *p1 = (struct vpb_pvt *)c1->tech_pvt;
+ int i;
+ int res;
+ struct ast_channel *cs[3];
+ struct ast_channel *who;
+ struct ast_frame *f;
+
+ cs[0] = c0;
+ cs[1] = c1;
+
+ #ifdef BAD_V4PCI_BRIDGE
+ if(p0->vpb_model==vpb_model_v4pci)
+ return AST_BRIDGE_FAILED_NOWARN;
+ #endif
+ if ( UseNativeBridge != 1){
+ return AST_BRIDGE_FAILED_NOWARN;
+ }
+
+/*
+ ast_mutex_lock(&p0->lock);
+ ast_mutex_lock(&p1->lock);
+*/
+
+ /* Bridge channels, check if we can. I believe we always can, so find a slot.*/
+
+ ast_mutex_lock(&bridge_lock); {
+ for (i = 0; i < max_bridges; i++)
+ if (!bridges[i].inuse)
+ break;
+ if (i < max_bridges) {
+ bridges[i].inuse = 1;
+ bridges[i].endbridge = 0;
+ bridges[i].flags = flags;
+ bridges[i].rc = rc;
+ bridges[i].fo = fo;
+ bridges[i].c0 = c0;
+ bridges[i].c1 = c1;
+ }
+ } ast_mutex_unlock(&bridge_lock);
+
+ if (i == max_bridges) {
+ ast_log(LOG_WARNING, "%s: vpb_bridge: Failed to bridge %s and %s!\n", p0->dev, c0->name, c1->name);
+ ast_mutex_unlock(&p0->lock);
+ ast_mutex_unlock(&p1->lock);
+ return AST_BRIDGE_FAILED_NOWARN;
+ } else {
+ /* Set bridge pointers. You don't want to take these locks while holding bridge lock.*/
+ ast_mutex_lock(&p0->lock); {
+ p0->bridge = &bridges[i];
+ } ast_mutex_unlock(&p0->lock);
+
+ ast_mutex_lock(&p1->lock); {
+ p1->bridge = &bridges[i];
+ } ast_mutex_unlock(&p1->lock);
+
+ ast_verb(2, "%s: vpb_bridge: Bridging call entered with [%s, %s]\n",p0->dev, c0->name, c1->name);
+ }
+
+ ast_verb(3, "Native bridging %s and %s\n", c0->name, c1->name);
+
+ #ifdef HALF_DUPLEX_BRIDGE
+
+ ast_verb(2, "%s: vpb_bridge: Starting half-duplex bridge [%s, %s]\n",p0->dev, c0->name, c1->name);
+
+ int dir = 0;
+
+ memset(p0->buf, 0, sizeof p0->buf);
+ memset(p1->buf, 0, sizeof p1->buf);
+
+ vpb_record_buf_start(p0->handle, VPB_ALAW);
+ vpb_record_buf_start(p1->handle, VPB_ALAW);
+
+ vpb_play_buf_start(p0->handle, VPB_ALAW);
+ vpb_play_buf_start(p1->handle, VPB_ALAW);
+
+ while( !bridges[i].endbridge ) {
+ struct vpb_pvt *from, *to;
+ if(++dir%2) {
+ from = p0;
+ to = p1;
+ } else {
+ from = p1;
+ to = p0;
+ }
+ vpb_record_buf_sync(from->handle, from->buf, VPB_SAMPLES);
+ vpb_play_buf_sync(to->handle, from->buf, VPB_SAMPLES);
+ }
+
+ vpb_record_buf_finish(p0->handle);
+ vpb_record_buf_finish(p1->handle);
+
+ vpb_play_buf_finish(p0->handle);
+ vpb_play_buf_finish(p1->handle);
+
+ ast_verb(2, "%s: vpb_bridge: Finished half-duplex bridge [%s, %s]\n",p0->dev, c0->name, c1->name);
+
+ res = VPB_OK;
+
+ #else
+
+ res = vpb_bridge(p0->handle, p1->handle, VPB_BRIDGE_ON, i+1 /* resource 1 & 2 only for V4PCI*/ );
+ if (res == VPB_OK) {
+ /* pthread_cond_wait(&bridges[i].cond, &bridges[i].lock);*/ /* Wait for condition signal. */
+ while( !bridges[i].endbridge ) {
+ /* Are we really ment to be doing nothing ?!?! */
+ who = ast_waitfor_n(cs, 2, &timeoutms);
+ if (!who) {
+ if (!timeoutms) {
+ res = AST_BRIDGE_RETRY;
+ break;
+ }
+ ast_debug(1, "%s: vpb_bridge: Empty frame read...\n",p0->dev);
+ /* check for hangup / whentohangup */
+ if (ast_check_hangup(c0) || ast_check_hangup(c1))
+ break;
+ continue;
+ }
+ f = ast_read(who);
+ if (!f || ((f->frametype == AST_FRAME_DTMF) &&
+ (((who == c0) && (flags & AST_BRIDGE_DTMF_CHANNEL_0)) ||
+ ((who == c1) && (flags & AST_BRIDGE_DTMF_CHANNEL_1))))) {
+ *fo = f;
+ *rc = who;
+ ast_debug(1, "%s: vpb_bridge: Got a [%s]\n",p0->dev, f ? "digit" : "hangup");
+/*
+ if ((c0->tech_pvt == pvt0) && (!ast_check_hangup(c0))) {
+ if (pr0->set_rtp_peer(c0, NULL, NULL, 0))
+ ast_log(LOG_WARNING, "Channel '%s' failed to revert\n", c0->name);
+ }
+ if ((c1->tech_pvt == pvt1) && (!ast_check_hangup(c1))) {
+ if (pr1->set_rtp_peer(c1, NULL, NULL, 0))
+ ast_log(LOG_WARNING, "Channel '%s' failed to revert back\n", c1->name);
+ }
+*/
+ /* That's all we needed */
+ /*return 0; */
+ /* Check if we need to break */
+ if (break_for_dtmf){
+ break;
+ }
+ else if ((f->frametype == AST_FRAME_DTMF) && ((f->subclass == '#')||(f->subclass == '*'))){
+ break;
+ }
+ } else {
+ if ((f->frametype == AST_FRAME_DTMF) ||
+ (f->frametype == AST_FRAME_VOICE) ||
+ (f->frametype == AST_FRAME_VIDEO))
+ {
+ /* Forward voice or DTMF frames if they happen upon us */
+ /* Actually I dont think we want to forward on any frames!
+ if (who == c0) {
+ ast_write(c1, f);
+ } else if (who == c1) {
+ ast_write(c0, f);
+ }
+ */
+ }
+ ast_frfree(f);
+ }
+ /* Swap priority not that it's a big deal at this point */
+ cs[2] = cs[0];
+ cs[0] = cs[1];
+ cs[1] = cs[2];
+ };
+ vpb_bridge(p0->handle, p1->handle, VPB_BRIDGE_OFF, i+1 /* resource 1 & 2 only for V4PCI*/ );
+ }
+
+ #endif
+
+ ast_mutex_lock(&bridge_lock); {
+ bridges[i].inuse = 0;
+ } ast_mutex_unlock(&bridge_lock);
+
+ p0->bridge = NULL;
+ p1->bridge = NULL;
+
+
+ ast_verb(2, "Bridging call done with [%s, %s] => %d\n", c0->name, c1->name, res);
+
+/*
+ ast_mutex_unlock(&p0->lock);
+ ast_mutex_unlock(&p1->lock);
+*/
+ return (res==VPB_OK) ? AST_BRIDGE_COMPLETE : AST_BRIDGE_FAILED;
+}
+
+/* Caller ID can be located in different positions between the rings depending on your Telco
+ * Australian (Telstra) callerid starts 700ms after 1st ring and finishes 1.5s after first ring
+ * Use ANALYSE_CID to record rings and determine location of callerid
+ */
+/* #define ANALYSE_CID */
+#define RING_SKIP 300
+#define CID_MSECS 2000
+
+static void get_callerid(struct vpb_pvt *p)
+{
+ short buf[CID_MSECS*8]; /* 8kHz sampling rate */
+ struct timeval cid_record_time;
+ int rc;
+ struct ast_channel *owner = p->owner;
+/*
+ char callerid[AST_MAX_EXTENSION] = "";
+*/
+#ifdef ANALYSE_CID
+ void * ws;
+ char * file="cidsams.wav";
+#endif
+
+
+ if( ast_mutex_trylock(&p->record_lock) == 0 ) {
+
+ cid_record_time = ast_tvnow();
+ ast_verb(4, "CID record - start\n");
+
+ /* Skip any trailing ringtone */
+ if (UsePolarityCID != 1){
+ vpb_sleep(RING_SKIP);
+ }
+
+ ast_verb(4, "CID record - skipped %dms trailing ring\n",
+ ast_tvdiff_ms(ast_tvnow(), cid_record_time));
+ cid_record_time = ast_tvnow();
+
+ /* Record bit between the rings which contains the callerid */
+ vpb_record_buf_start(p->handle, VPB_LINEAR);
+ rc = vpb_record_buf_sync(p->handle, (char*)buf, sizeof(buf));
+ vpb_record_buf_finish(p->handle);
+#ifdef ANALYSE_CID
+ vpb_wave_open_write(&ws, file, VPB_LINEAR);
+ vpb_wave_write(ws,(char*)buf,sizeof(buf));
+ vpb_wave_close_write(ws);
+#endif
+
+ ast_verb(4, "CID record - recorded %dms between rings\n",
+ ast_tvdiff_ms(ast_tvnow(), cid_record_time));
+
+ ast_mutex_unlock(&p->record_lock);
+
+ if( rc != VPB_OK ) {
+ ast_log(LOG_ERROR, "Failed to record caller id sample on %s\n", p->dev );
+ return;
+ }
+
+ VPB_CID *cli_struct = new VPB_CID;
+ cli_struct->ra_cldn[0]=0;
+ cli_struct->ra_cn[0]=0;
+ /* This decodes FSK 1200baud type callerid */
+ if ((rc=vpb_cid_decode2(cli_struct, buf, CID_MSECS*8)) == VPB_OK ) {
+ /*
+ if (owner->cid.cid_num)
+ ast_free(owner->cid.cid_num);
+ owner->cid.cid_num=NULL;
+ if (owner->cid.cid_name)
+ ast_free(owner->cid.cid_name);
+ owner->cid.cid_name=NULL;
+ */
+
+ if (cli_struct->ra_cldn[0]=='\0'){
+ /*
+ owner->cid.cid_num = ast_strdup(cli_struct->cldn);
+ owner->cid.cid_name = ast_strdup(cli_struct->cn);
+ */
+ if (owner){
+ ast_set_callerid(owner, cli_struct->cldn, cli_struct->cn, cli_struct->cldn);
+ } else {
+ strcpy(p->cid_num, cli_struct->cldn);
+ strcpy(p->cid_name, cli_struct->cn);
+
+ }
+ ast_verb(4, "CID record - got [%s] [%s]\n",owner->cid.cid_num,owner->cid.cid_name );
+ snprintf(p->callerid,sizeof(p->callerid)-1,"%s %s",cli_struct->cldn,cli_struct->cn);
+ }
+ else {
+ ast_log(LOG_ERROR,"CID record - No caller id avalable on %s \n", p->dev);
+ }
+
+ } else {
+ ast_log(LOG_ERROR, "CID record - Failed to decode caller id on %s - %s\n", p->dev, vpb_strerror(rc) );
+ strncpy(p->callerid,"unknown", sizeof(p->callerid) - 1);
+ }
+ delete cli_struct;
+
+ } else
+ ast_log(LOG_ERROR, "CID record - Failed to set record mode for caller id on %s\n", p->dev );
+}
+
+static void get_callerid_ast(struct vpb_pvt *p)
+{
+ struct callerid_state *cs;
+ char buf[1024];
+ char *name=NULL, *number=NULL;
+ int flags;
+ int rc=0,vrc;
+ int sam_count=0;
+ struct ast_channel *owner = p->owner;
+ int which_cid;
+/*
+ float old_gain;
+*/
+#ifdef ANALYSE_CID
+ void * ws;
+ char * file="cidsams.wav";
+#endif
+
+ if(p->callerid_type == 1) {
+ ast_verb(4, "Collected caller ID already\n");
+ return;
+ }
+ else if(p->callerid_type == 2 ) {
+ which_cid=CID_SIG_V23;
+ ast_verb(4, "Collecting Caller ID v23...\n");
+ }
+ else if(p->callerid_type == 3) {
+ which_cid=CID_SIG_BELL;
+ ast_verb(4, "Collecting Caller ID bell...\n");
+ }
+ else {
+ ast_verb(4, "Caller ID disabled\n");
+ return;
+ }
+/* vpb_sleep(RING_SKIP); */
+/* vpb_record_get_gain(p->handle, &old_gain); */
+ cs = callerid_new(which_cid);
+ if (cs){
+#ifdef ANALYSE_CID
+ vpb_wave_open_write(&ws, file, VPB_MULAW);
+ vpb_record_set_gain(p->handle, 3.0);
+ vpb_record_set_hw_gain(p->handle,12.0);
+#endif
+ vpb_record_buf_start(p->handle, VPB_MULAW);
+ while((rc == 0)&&(sam_count<8000*3)){
+ vrc = vpb_record_buf_sync(p->handle, (char*)buf, sizeof(buf));
+ if (vrc != VPB_OK)
+ ast_log(LOG_ERROR, "%s: Caller ID couldnt read audio buffer!\n",p->dev);
+ rc = callerid_feed(cs,(unsigned char *)buf,sizeof(buf),AST_FORMAT_ULAW);
+#ifdef ANALYSE_CID
+ vpb_wave_write(ws,(char*)buf,sizeof(buf));
+#endif
+ sam_count+=sizeof(buf);
+ ast_verb(4, "Collecting Caller ID samples [%d][%d]...\n",sam_count,rc);
+ }
+ vpb_record_buf_finish(p->handle);
+#ifdef ANALYSE_CID
+ vpb_wave_close_write(ws);
+#endif
+ if (rc == 1){
+ callerid_get(cs, &name, &number, &flags);
+ ast_verb(1, "%s: Caller ID name [%s] number [%s] flags [%d]\n",p->dev,name, number,flags);
+ }
+ else {
+ ast_log(LOG_ERROR, "%s: Failed to decode Caller ID \n", p->dev );
+ }
+/* vpb_record_set_gain(p->handle, old_gain); */
+/* vpb_record_set_hw_gain(p->handle,6.0); */
+ }
+ else {
+ ast_log(LOG_ERROR, "%s: Failed to create Caller ID struct\n", p->dev );
+ }
+ if (owner->cid.cid_num) {
+ ast_free(owner->cid.cid_num);
+ owner->cid.cid_num = NULL;
+ }
+ if (owner->cid.cid_name) {
+ ast_free(owner->cid.cid_name);
+ owner->cid.cid_name = NULL;
+ }
+ if (number)
+ ast_shrink_phone_number(number);
+ ast_set_callerid(owner,
+ number, name,
+ owner->cid.cid_ani ? NULL : number);
+ if (!ast_strlen_zero(name)){
+ snprintf(p->callerid,(sizeof(p->callerid)-1),"%s %s",number,name);
+ } else {
+ snprintf(p->callerid,(sizeof(p->callerid)-1),"%s",number);
+ }
+ if (cs)
+ callerid_free(cs);
+}
+
+/* Terminate any tones we are presently playing */
+static void stoptone( int handle)
+{
+ int ret;
+ VPB_EVENT je;
+ while(vpb_playtone_state(handle)!=VPB_OK){
+ vpb_tone_terminate(handle);
+ ret = vpb_get_event_ch_async(handle,&je);
+ if ((ret == VPB_OK)&&(je.type != VPB_DIALEND)){
+ ast_verb(4, "Stop tone collected a wrong event!![%d]\n",je.type);
+/* vpb_put_event(&je); */
+ }
+ vpb_sleep(10);
+ }
+}
+
+/* Safe vpb_playtone_async */
+static int playtone( int handle, VPB_TONE *tone)
+{
+ int ret=VPB_OK;
+ stoptone(handle);
+ ast_verb(4, "[%02d]: Playing tone\n", handle);
+ ret = vpb_playtone_async(handle, tone);
+ return ret;
+}
+
+static inline int monitor_handle_owned(struct vpb_pvt *p, VPB_EVENT *e)
+{
+ struct ast_frame f = {AST_FRAME_CONTROL}; /* default is control, Clear rest. */
+ int endbridge = 0;
+ int res=0;
+
+ ast_verb(4, "%s: handle_owned: got event: [%d=>%d]\n", p->dev, e->type, e->data);
+
+ f.src = "vpb";
+ switch (e->type) {
+ case VPB_RING:
+ if (p->mode == MODE_FXO) {
+ f.subclass = AST_CONTROL_RING;
+ vpb_timer_stop(p->ring_timer);
+ vpb_timer_start(p->ring_timer);
+ } else
+ f.frametype = AST_FRAME_NULL; /* ignore ring on station port. */
+ break;
+
+ case VPB_RING_OFF:
+ f.frametype = AST_FRAME_NULL;
+ break;
+
+ case VPB_TIMEREXP:
+ if (e->data == p->busy_timer_id) {
+ playtone(p->handle,&Busytone);
+ p->state = VPB_STATE_PLAYBUSY;
+ vpb_timer_stop(p->busy_timer);
+ vpb_timer_start(p->busy_timer);
+ f.frametype = AST_FRAME_NULL;
+ } else if (e->data == p->ringback_timer_id) {
+ playtone(p->handle, &Ringbacktone);
+ vpb_timer_stop(p->ringback_timer);
+ vpb_timer_start(p->ringback_timer);
+ f.frametype = AST_FRAME_NULL;
+ } else if (e->data == p->ring_timer_id) {
+ /* We didnt get another ring in time! */
+ if (p->owner->_state != AST_STATE_UP) {
+ /* Assume caller has hung up */
+ vpb_timer_stop(p->ring_timer);
+ f.subclass = AST_CONTROL_HANGUP;
+ } else {
+ vpb_timer_stop(p->ring_timer);
+ f.frametype = AST_FRAME_NULL;
+ }
+
+ } else {
+ f.frametype = AST_FRAME_NULL; /* Ignore. */
+ }
+ break;
+
+ case VPB_DTMF_DOWN:
+ case VPB_DTMF:
+ if (use_ast_dtmfdet){
+ f.frametype = AST_FRAME_NULL;
+ } else if (p->owner->_state == AST_STATE_UP) {
+ f.frametype = AST_FRAME_DTMF;
+ f.subclass = e->data;
+ } else
+ f.frametype = AST_FRAME_NULL;
+ break;
+
+ case VPB_TONEDETECT:
+ if (e->data == VPB_BUSY || e->data == VPB_BUSY_308 || e->data == VPB_BUSY_AUST ) {
+ ast_verb(4, "%s: handle_owned: got event: BUSY\n", p->dev);
+ if (p->owner->_state == AST_STATE_UP) {
+ f.subclass = AST_CONTROL_HANGUP;
+ }
+ else {
+ f.subclass = AST_CONTROL_BUSY;
+ }
+ }
+ else if (e->data == VPB_FAX){
+ if (!p->faxhandled){
+ if (strcmp(p->owner->exten, "fax")) {
+ const char *target_context = S_OR(p->owner->macrocontext, p->owner->context);
+
+ if (ast_exists_extension(p->owner, target_context, "fax", 1, p->owner->cid.cid_num)) {
+ ast_verb(3, "Redirecting %s to fax extension\n", p->owner->name);
+ /* Save the DID/DNIS when we transfer the fax call to a "fax" extension */
+ pbx_builtin_setvar_helper(p->owner, "FAXEXTEN", p->owner->exten);
+ if (ast_async_goto(p->owner, target_context, "fax", 1))
+ ast_log(LOG_WARNING, "Failed to async goto '%s' into fax of '%s'\n", p->owner->name, target_context);
+ } else
+ ast_log(LOG_NOTICE, "Fax detected, but no fax extension\n");
+ } else
+ ast_debug(1, "Already in a fax extension, not redirecting\n");
+ } else
+ ast_debug(1, "Fax already handled\n");
+
+ }
+ else if (e->data == VPB_GRUNT) {
+ if ( ast_tvdiff_ms(ast_tvnow(), p->lastgrunt) > gruntdetect_timeout ) {
+ /* Nothing heard on line for a very long time
+ * Timeout connection */
+ ast_verb(3, "grunt timeout\n");
+ ast_log(LOG_NOTICE,"%s: Line hangup due of lack of conversation\n",p->dev);
+ f.subclass = AST_CONTROL_HANGUP;
+ } else {
+ p->lastgrunt = ast_tvnow();
+ f.frametype = AST_FRAME_NULL;
+ }
+ }
+ else {
+ f.frametype = AST_FRAME_NULL;
+ }
+ break;
+
+ case VPB_CALLEND:
+ #ifdef DIAL_WITH_CALL_PROGRESS
+ if (e->data == VPB_CALL_CONNECTED)
+ f.subclass = AST_CONTROL_ANSWER;
+ else if (e->data == VPB_CALL_NO_DIAL_TONE || e->data == VPB_CALL_NO_RING_BACK)
+ f.subclass = AST_CONTROL_CONGESTION;
+ else if (e->data == VPB_CALL_NO_ANSWER || e->data == VPB_CALL_BUSY)
+ f.subclass = AST_CONTROL_BUSY;
+ else if (e->data == VPB_CALL_DISCONNECTED)
+ f.subclass = AST_CONTROL_HANGUP;
+ #else
+ ast_log(LOG_NOTICE,"%s: Got call progress callback but blind dialing \n", p->dev);
+ f.frametype = AST_FRAME_NULL;
+ #endif
+ break;
+
+ case VPB_STATION_OFFHOOK:
+ f.subclass = AST_CONTROL_ANSWER;
+ break;
+
+ case VPB_DROP:
+ if ((p->mode == MODE_FXO)&&(UseLoopDrop)){ /* ignore loop drop on stations */
+ if (p->owner->_state == AST_STATE_UP)
+ f.subclass = AST_CONTROL_HANGUP;
+ else
+ f.frametype = AST_FRAME_NULL;
+ }
+ break;
+ case VPB_LOOP_ONHOOK:
+ if (p->owner->_state == AST_STATE_UP)
+ f.subclass = AST_CONTROL_HANGUP;
+ else
+ f.frametype = AST_FRAME_NULL;
+ break;
+ case VPB_STATION_ONHOOK:
+ f.subclass = AST_CONTROL_HANGUP;
+ break;
+
+ case VPB_STATION_FLASH:
+ f.subclass = AST_CONTROL_FLASH;
+ break;
+
+ /* Called when dialing has finished and ringing starts
+ * No indication that call has really been answered when using blind dialing
+ */
+ case VPB_DIALEND:
+ if (p->state < 5){
+ f.subclass = AST_CONTROL_ANSWER;
+ ast_verb(2, "%s: Dialend\n", p->dev);
+ } else {
+ f.frametype = AST_FRAME_NULL;
+ }
+ break;
+
+ case VPB_PLAY_UNDERFLOW:
+ f.frametype = AST_FRAME_NULL;
+ vpb_reset_play_fifo_alarm(p->handle);
+ break;
+
+ case VPB_RECORD_OVERFLOW:
+ f.frametype = AST_FRAME_NULL;
+ vpb_reset_record_fifo_alarm(p->handle);
+ break;
+
+ default:
+ f.frametype = AST_FRAME_NULL;
+ break;
+ }
+
+/*
+ ast_verb(4, "%s: LOCKING in handle_owned [%d]\n", p->dev,res);
+ res = ast_mutex_lock(&p->lock);
+ ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
+*/
+ {
+ if (p->bridge) { /* Check what happened, see if we need to report it. */
+ switch (f.frametype) {
+ case AST_FRAME_DTMF:
+ if ( !(p->bridge->c0 == p->owner &&
+ (p->bridge->flags & AST_BRIDGE_DTMF_CHANNEL_0) ) &&
+ !(p->bridge->c1 == p->owner &&
+ (p->bridge->flags & AST_BRIDGE_DTMF_CHANNEL_1) ))
+ /* Kill bridge, this is interesting. */
+ endbridge = 1;
+ break;
+
+ case AST_FRAME_CONTROL:
+ if (!(p->bridge->flags & AST_BRIDGE_IGNORE_SIGS))
+ #if 0
+ if (f.subclass == AST_CONTROL_BUSY ||
+ f.subclass == AST_CONTROL_CONGESTION ||
+ f.subclass == AST_CONTROL_HANGUP ||
+ f.subclass == AST_CONTROL_FLASH)
+ #endif
+ endbridge = 1;
+ break;
+
+ default:
+ break;
+ }
+ if (endbridge) {
+ if (p->bridge->fo)
+ *p->bridge->fo = ast_frisolate(&f);
+ if (p->bridge->rc)
+ *p->bridge->rc = p->owner;
+
+ ast_mutex_lock(&p->bridge->lock); {
+ p->bridge->endbridge = 1;
+ ast_cond_signal(&p->bridge->cond);
+ } ast_mutex_unlock(&p->bridge->lock);
+ }
+ }
+ }
+
+ if (endbridge){
+ res = ast_mutex_unlock(&p->lock);
+/*
+ ast_verb(4, "%s: unLOCKING in handle_owned [%d]\n", p->dev,res);
+*/
+ return 0;
+ }
+
+ ast_verb(4, "%s: handle_owned: Prepared frame type[%d]subclass[%d], bridge=%p owner=[%s]\n",
+ p->dev, f.frametype, f.subclass, (void *)p->bridge, p->owner->name);
+
+ /* Trylock used here to avoid deadlock that can occur if we
+ * happen to be in here handling an event when hangup is called
+ * Problem is that hangup holds p->owner->lock
+ */
+ if ((f.frametype >= 0)&& (f.frametype != AST_FRAME_NULL)&&(p->owner)) {
+ if (ast_mutex_trylock(&p->owner->lock)==0) {
+ ast_queue_frame(p->owner, &f);
+ ast_mutex_unlock(&p->owner->lock);
+ ast_verb(4, "%s: handled_owned: Queued Frame to [%s]\n", p->dev,p->owner->name);
+ } else {
+ ast_verbose("%s: handled_owned: Missed event %d/%d \n",
+ p->dev,f.frametype, f.subclass);
+ }
+ }
+ res = ast_mutex_unlock(&p->lock);
+/*
+ ast_verb(4, "%s: unLOCKING in handle_owned [%d]\n", p->dev,res);
+*/
+
+ return 0;
+}
+
+static inline int monitor_handle_notowned(struct vpb_pvt *p, VPB_EVENT *e)
+{
+ char s[2] = {0};
+ struct ast_channel *owner = p->owner;
+ char cid_num[256];
+ char cid_name[256];
+/*
+ struct ast_channel *c;
+*/
+
+ char str[VPB_MAX_STR];
+ vpb_translate_event(e, str);
+ ast_verb(4, "%s: handle_notowned: mode=%d, event[%d][%s]=[%d]\n", p->dev, p->mode, e->type,str, e->data);
+
+ switch(e->type) {
+ case VPB_LOOP_ONHOOK:
+ case VPB_LOOP_POLARITY:
+ if (UsePolarityCID == 1){
+ ast_verb(4, "Polarity reversal\n");
+ if(p->callerid_type == 1) {
+ ast_verb(4, "Using VPB Caller ID\n");
+ get_callerid(p); /* UK CID before 1st ring*/
+ }
+/* get_callerid_ast(p); */ /* Caller ID using the ast functions */
+ }
+ break;
+ case VPB_RING:
+ if (p->mode == MODE_FXO) /* FXO port ring, start * */ {
+ vpb_new(p, AST_STATE_RING, p->context);
+ if (UsePolarityCID != 1){
+ if(p->callerid_type == 1) {
+ ast_verb(4, "Using VPB Caller ID\n");
+ get_callerid(p); /* Australian CID only between 1st and 2nd ring */
+ }
+ get_callerid_ast(p); /* Caller ID using the ast functions */
+ }
+ else {
+ ast_log(LOG_ERROR, "Setting caller ID: %s %s\n",p->cid_num, p->cid_name);
+ ast_set_callerid(p->owner, p->cid_num, p->cid_name, p->cid_num);
+ p->cid_num[0]=0;
+ p->cid_name[0]=0;
+ }
+
+ vpb_timer_stop(p->ring_timer);
+ vpb_timer_start(p->ring_timer);
+ }
+ break;
+
+ case VPB_RING_OFF:
+ break;
+
+ case VPB_STATION_OFFHOOK:
+ if (p->mode == MODE_IMMEDIATE)
+ vpb_new(p,AST_STATE_RING, p->context);
+ else {
+ ast_verb(4, "%s: handle_notowned: playing dialtone\n",p->dev);
+ playtone(p->handle, &Dialtone);
+ p->state=VPB_STATE_PLAYDIAL;
+ p->wantdtmf = 1;
+ p->ext[0] = 0; /* Just to be sure & paranoid.*/
+ }
+ break;
+
+ case VPB_DIALEND:
+ if (p->mode == MODE_DIALTONE){
+ if (p->state == VPB_STATE_PLAYDIAL) {
+ playtone(p->handle, &Dialtone);
+ p->wantdtmf = 1;
+ p->ext[0] = 0; /* Just to be sure & paranoid. */
+ }
+ /* These are not needed as they have timers to restart them
+ else if (p->state == VPB_STATE_PLAYBUSY) {
+ playtone(p->handle, &Busytone);
+ p->wantdtmf = 1;
+ p->ext[0] = 0;
+ }
+ else if (p->state == VPB_STATE_PLAYRING) {
+ playtone(p->handle, &Ringbacktone);
+ p->wantdtmf = 1;
+ p->ext[0] = 0;
+ }
+ */
+ } else {
+ ast_verb(4, "%s: handle_notowned: Got a DIALEND when not really expected\n",p->dev);
+ }
+ break;
+
+ case VPB_STATION_ONHOOK: /* clear ext */
+ stoptone(p->handle);
+ p->wantdtmf = 1 ;
+ p->ext[0] = 0;
+ p->state=VPB_STATE_ONHOOK;
+ break;
+ case VPB_TIMEREXP:
+ if (e->data == p->dtmfidd_timer_id) {
+ if (ast_exists_extension(NULL, p->context, p->ext, 1, p->callerid)){
+ ast_verb(4, "%s: handle_notowned: DTMF IDD timer out, matching on [%s] in [%s]\n", p->dev,p->ext , p->context);
+
+ vpb_new(p,AST_STATE_RING, p->context);
+ }
+ } else if (e->data == p->ring_timer_id) {
+ /* We didnt get another ring in time! */
+ if (p->owner){
+ if (p->owner->_state != AST_STATE_UP) {
+ /* Assume caller has hung up */
+ vpb_timer_stop(p->ring_timer);
+ }
+ } else {
+ /* No owner any more, Assume caller has hung up */
+ vpb_timer_stop(p->ring_timer);
+ }
+ }
+ break;
+
+ case VPB_DTMF:
+ if (p->state == VPB_STATE_ONHOOK){
+ /* DTMF's being passed while on-hook maybe Caller ID */
+ if ( p->mode == MODE_FXO ) {
+ if ( e->data == DTMF_CID_START ) { /* CallerID Start signal */
+ p->dtmf_caller_pos = 0; /* Leaves the first digit out */
+ memset(p->callerid,0,AST_MAX_EXTENSION);
+ }
+ else if ( e->data == DTMF_CID_STOP ) { /* CallerID End signal */
+ p->callerid[p->dtmf_caller_pos] = '\0';
+ ast_verb(3, " %s: DTMF CallerID %s\n",p->dev,p->callerid);
+ if (owner){
+ /*
+ if (owner->cid.cid_num)
+ ast_free(owner->cid.cid_num);
+ owner->cid.cid_num=NULL;
+ if (owner->cid.cid_name)
+ ast_free(owner->cid.cid_name);
+ owner->cid.cid_name=NULL;
+ owner->cid.cid_num = strdup(p->callerid);
+ */
+ cid_name[0] = '\0';
+ cid_num[0] = '\0';
+ ast_callerid_split(p->callerid, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num));
+ ast_set_callerid(owner, cid_num, cid_name, cid_num);
+
+ } else
+ ast_verb(3, " %s: DTMF CallerID: no owner to assign CID \n",p->dev);
+ } else if ( p->dtmf_caller_pos < AST_MAX_EXTENSION ) {
+ if ( p->dtmf_caller_pos >= 0 )
+ p->callerid[p->dtmf_caller_pos] = e->data;
+ p->dtmf_caller_pos++;
+ }
+ }
+ break;
+ }
+ if (p->wantdtmf == 1) {
+ stoptone(p->handle);
+ p->wantdtmf = 0;
+ }
+ p->state=VPB_STATE_GETDTMF;
+ s[0] = e->data;
+ strncat(p->ext, s, sizeof(p->ext) - strlen(p->ext) - 1);
+ #if 0
+ if (!strcmp(p->ext,ast_pickup_ext())) {
+ /* Call pickup has been dialled! */
+ if (ast_pickup_call(c)) {
+ /* Call pickup wasnt possible */
+ }
+ }
+ else
+ #endif
+ if (ast_exists_extension(NULL, p->context, p->ext, 1, p->callerid)){
+ if ( ast_canmatch_extension(NULL, p->context, p->ext, 1, p->callerid)){
+ ast_verb(4, "%s: handle_notowned: Multiple matches on [%s] in [%s]\n", p->dev,p->ext , p->context);
+ /* Start DTMF IDD timer */
+ vpb_timer_stop(p->dtmfidd_timer);
+ vpb_timer_start(p->dtmfidd_timer);
+ }
+ else {
+ ast_verb(4, "%s: handle_notowned: Matched on [%s] in [%s]\n", p->dev,p->ext , p->context);
+ vpb_new(p,AST_STATE_UP, p->context);
+ }
+ } else if (!ast_canmatch_extension(NULL, p->context, p->ext, 1, p->callerid)){
+ if (ast_exists_extension(NULL, "default", p->ext, 1, p->callerid)) {
+ vpb_new(p,AST_STATE_UP, "default");
+ } else if (!ast_canmatch_extension(NULL, "default", p->ext, 1, p->callerid)) {
+ ast_verb(4, "%s: handle_notowned: can't match anything in %s or default\n", p->dev, p->context);
+ playtone(p->handle, &Busytone);
+ vpb_timer_stop(p->busy_timer);
+ vpb_timer_start(p->busy_timer);
+ p->state = VPB_STATE_PLAYBUSY;
+ }
+ }
+ break;
+
+ default:
+ /* Ignore.*/
+ break;
+ }
+
+ ast_verb(4, "%s: handle_notowned: mode=%d, [%d=>%d]\n", p->dev, p->mode, e->type, e->data);
+
+ return 0;
+}
+
+static void *do_monitor(void *unused)
+{
+
+ /* Monitor thread, doesn't die until explicitly killed. */
+
+ ast_verb(2, "Starting vpb monitor thread[%ld]\n",
+ pthread_self());
+
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+
+ for(;;) {
+ VPB_EVENT e;
+ VPB_EVENT je;
+ char str[VPB_MAX_STR];
+ struct vpb_pvt *p;
+
+ /*
+ ast_verb(4, "Monitor waiting for event\n");
+ */
+
+ int res = vpb_get_event_sync(&e, VPB_WAIT_TIMEOUT);
+ if( (res==VPB_NO_EVENTS) || (res==VPB_TIME_OUT) ){
+ /*
+ if (res == VPB_NO_EVENTS){
+ ast_verb(4, "No events....\n");
+ } else {
+ ast_verb(4, "No events, timed out....\n");
+ }
+ */
+ continue;
+ }
+
+ if (res != VPB_OK) {
+ ast_log(LOG_ERROR,"Monitor get event error %s\n", vpb_strerror(res) );
+ ast_verbose("Monitor get event error %s\n", vpb_strerror(res) );
+ continue;
+ }
+
+ str[0] = 0;
+
+ p = NULL;
+
+ ast_mutex_lock(&monlock); {
+ //XXX useless braces, remove them and fix indenting
+ if (e.type == VPB_NULL_EVENT)
+ ast_verb(4, "Monitor got null event\n");
+ else {
+ vpb_translate_event(&e, str);
+ if (strlen(str)>1){
+ str[(strlen(str)-1)]='\0';
+ }
+
+ ast_mutex_lock(&iflock); {
+ p = iflist;
+ while (p && p->handle != e.handle)
+ p = p->next;
+ } ast_mutex_unlock(&iflock);
+
+ if (p)
+ ast_verb(4, "%s: Event [%d=>%s] \n",
+ p ? p->dev : "null", e.type, str );
+ }
+
+ } ast_mutex_unlock(&monlock);
+
+ if (!p) {
+ if (e.type != VPB_NULL_EVENT){
+ ast_log(LOG_WARNING, "Got event [%s][%d], no matching iface!\n", str,e.type);
+ ast_verb(4, "vpb/ERR: No interface for Event [%d=>%s] \n",e.type,str );
+ }
+ continue;
+ }
+
+ /* flush the event from the channel event Q */
+ vpb_get_event_ch_async(e.handle,&je);
+ vpb_translate_event(&je, str);
+ ast_verb(5, "%s: Flushing event [%d]=>%s\n",p->dev,je.type,str);
+
+ /* Check for ownership and locks */
+ if ((p->owner)&&(!p->golock)){
+ /* Need to get owner lock */
+ /* Safely grab both p->lock and p->owner->lock so that there
+ cannot be a race with something from the other side */
+ /*
+ ast_mutex_lock(&p->lock);
+ while(ast_mutex_trylock(&p->owner->lock)) {
+ ast_mutex_unlock(&p->lock);
+ usleep(1);
+ ast_mutex_lock(&p->lock);
+ if (!p->owner)
+ break;
+ }
+ if (p->owner)
+ p->golock=1;
+ */
+ }
+ /* Two scenarios: Are you owned or not. */
+ if (p->owner) {
+ monitor_handle_owned(p, &e);
+ } else {
+ monitor_handle_notowned(p, &e);
+ }
+ /* if ((!p->owner)&&(p->golock)){
+ ast_mutex_unlock(&p->owner->lock);
+ ast_mutex_unlock(&p->lock);
+ }
+ */
+
+ }
+
+ return NULL;
+}
+
+static int restart_monitor(void)
+{
+ int error = 0;
+
+ /* If we're supposed to be stopped -- stay stopped */
+ if (mthreadactive == -2)
+ return 0;
+
+ ast_verb(4, "Restarting monitor\n");
+
+ ast_mutex_lock(&monlock); {
+ if (monitor_thread == pthread_self()) {
+ ast_log(LOG_WARNING, "Cannot kill myself\n");
+ error = -1;
+ ast_verb(4, "Monitor trying to kill monitor\n");
+ }
+ else {
+ if (mthreadactive != -1) {
+ /* Why do other drivers kill the thread? No need says I, simply awake thread with event. */
+ VPB_EVENT e;
+ e.handle = 0;
+ e.type = VPB_NULL_EVENT;
+ e.data = 0;
+
+ ast_verb(4, "Trying to reawake monitor\n");
+
+ vpb_put_event(&e);
+ } else {
+ /* Start a new monitor */
+ int pid = ast_pthread_create(&monitor_thread, NULL, do_monitor, NULL);
+ ast_verb(4, "Created new monitor thread %d\n",pid);
+ if (pid < 0) {
+ ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
+ error = -1;
+ } else
+ mthreadactive = 0; /* Started the thread!*/
+ }
+ }
+ } ast_mutex_unlock(&monlock);
+
+ ast_verb(4, "Monitor restarted\n");
+
+ return error;
+}
+
+/* Per board config that must be called after vpb_open() */
+static void mkbrd(vpb_model_t model, int echo_cancel)
+{
+ if(!bridges) {
+ if(model==vpb_model_v4pci)
+ max_bridges = MAX_BRIDGES_V4PCI;
+ bridges = ast_calloc(1, max_bridges * sizeof(vpb_bridge_t));
+ if(!bridges)
+ ast_log(LOG_ERROR, "Failed to initialize bridges\n");
+ else {
+ for(int i = 0; i < max_bridges; i++ ) {
+ ast_mutex_init(&bridges[i].lock);
+ ast_cond_init(&bridges[i].cond, NULL);
+ }
+ }
+ }
+ if(!echo_cancel) {
+ if (model==vpb_model_v4pci) {
+ vpb_echo_canc_disable();
+ ast_log(LOG_NOTICE, "Voicetronix echo cancellation OFF\n");
+ }
+ else {
+ /* need to it port by port for OpenSwitch*/
+ }
+ } else {
+ if (model==vpb_model_v4pci) {
+ vpb_echo_canc_enable();
+ ast_log(LOG_NOTICE, "Voicetronix echo cancellation ON\n");
+ if (ec_supp_threshold > -1){
+ #ifdef VPB_PRI
+ vpb_echo_canc_set_sup_thresh(0,(short *)&ec_supp_threshold);
+ #else
+ vpb_echo_canc_set_sup_thresh((short *)&ec_supp_threshold);
+ #endif
+ ast_log(LOG_NOTICE, "Voicetronix EC Sup Thres set\n");
+ }
+ }
+ else {
+ /* need to it port by port for OpenSwitch*/
+ }
+ }
+}
+
+static struct vpb_pvt *mkif(int board, int channel, int mode, int gains, float txgain, float rxgain,
+ float txswgain, float rxswgain, int bal1, int bal2, int bal3,
+ char * callerid, int echo_cancel, int group, ast_group_t callgroup, ast_group_t pickupgroup )
+{
+ struct vpb_pvt *tmp;
+ char buf[64];
+
+ tmp = ast_calloc(1, sizeof(*tmp));
+
+ if (!tmp)
+ return NULL;
+
+ tmp->handle = vpb_open(board, channel);
+
+ if (tmp->handle < 0) {
+ ast_log(LOG_WARNING, "Unable to create channel vpb/%d-%d: %s\n",
+ board, channel, strerror(errno));
+ ast_free(tmp);
+ return NULL;
+ }
+
+ snprintf(tmp->dev, sizeof(tmp->dev), "vpb/%d-%d", board, channel);
+
+ tmp->mode = mode;
+
+ tmp->group = group;
+ tmp->callgroup = callgroup;
+ tmp->pickupgroup = pickupgroup;
+
+ /* Initilize dtmf caller ID position variable */
+ tmp->dtmf_caller_pos=0;
+
+ strncpy(tmp->language, language, sizeof(tmp->language) - 1);
+ strncpy(tmp->context, context, sizeof(tmp->context) - 1);
+
+ tmp->callerid_type=0;
+ if(callerid) {
+ if (strcasecmp(callerid,"on")==0){
+ tmp->callerid_type =1;
+ strncpy(tmp->callerid, "unknown", sizeof(tmp->callerid) - 1);
+ }
+ else if (strcasecmp(callerid,"v23")==0){
+ tmp->callerid_type =2;
+ strncpy(tmp->callerid, "unknown", sizeof(tmp->callerid) - 1);
+ }
+ else if (strcasecmp(callerid,"bell")==0){
+ tmp->callerid_type =3;
+ strncpy(tmp->callerid, "unknown", sizeof(tmp->callerid) - 1);
+ }
+ else {
+ strncpy(tmp->callerid, callerid, sizeof(tmp->callerid) - 1);
+ }
+ } else {
+ strncpy(tmp->callerid, "unknown", sizeof(tmp->callerid) - 1);
+ }
+
+ /* check if codec balances have been set in the config file */
+ if (bal3>=0) {
+ if ((bal1>=0) && !(bal1 & 32)) bal1 |= 32;
+ vpb_set_codec_reg(tmp->handle, 0x42, bal3);
+ }
+ if(bal1>=0) vpb_set_codec_reg(tmp->handle, 0x32, bal1);
+ if(bal2>=0) vpb_set_codec_reg(tmp->handle, 0x3a, bal2);
+
+ if (gains & VPB_GOT_TXHWG){
+ if (txgain > MAX_VPB_GAIN){
+ tmp->txgain = MAX_VPB_GAIN;
+ }
+ else if (txgain < MIN_VPB_GAIN){
+ tmp->txgain = MIN_VPB_GAIN;
+ }
+ else {
+ tmp->txgain = txgain;
+ }
+
+ ast_log(LOG_NOTICE,"VPB setting Tx Hw gain to [%f]\n",tmp->txgain);
+ vpb_play_set_hw_gain(tmp->handle, tmp->txgain);
+ }
+
+ if (gains & VPB_GOT_RXHWG){
+ if (rxgain > MAX_VPB_GAIN){
+ tmp->rxgain = MAX_VPB_GAIN;
+ }
+ else if (rxgain < MIN_VPB_GAIN){
+ tmp->rxgain = MIN_VPB_GAIN;
+ }
+ else {
+ tmp->rxgain = rxgain;
+ }
+ ast_log(LOG_NOTICE,"VPB setting Rx Hw gain to [%f]\n",tmp->rxgain);
+ vpb_record_set_hw_gain(tmp->handle,tmp->rxgain);
+ }
+
+ if (gains & VPB_GOT_TXSWG){
+ tmp->txswgain = txswgain;
+ ast_log(LOG_NOTICE,"VPB setting Tx Sw gain to [%f]\n",tmp->txswgain);
+ vpb_play_set_gain(tmp->handle, tmp->txswgain);
+ }
+
+ if (gains & VPB_GOT_RXSWG){
+ tmp->rxswgain = rxswgain;
+ ast_log(LOG_NOTICE,"VPB setting Rx Sw gain to [%f]\n",tmp->rxswgain);
+ vpb_record_set_gain(tmp->handle, tmp->rxswgain);
+ }
+
+ tmp->vpb_model = vpb_model_unknown;
+ if( vpb_get_model(buf) == VPB_OK ) {
+ if(strcmp(buf,"V12PCI")==0)
+ tmp->vpb_model = vpb_model_v12pci;
+ else if(strcmp(buf,"VPB4")==0)
+ tmp->vpb_model = vpb_model_v4pci;
+ }
+
+ ast_mutex_init(&tmp->owner_lock);
+ ast_mutex_init(&tmp->lock);
+ ast_mutex_init(&tmp->record_lock);
+ ast_mutex_init(&tmp->play_lock);
+ ast_mutex_init(&tmp->play_dtmf_lock);
+
+ /* set default read state */
+ tmp->read_state = 0;
+
+ tmp->golock=0;
+
+ tmp->busy_timer_id = vpb_timer_get_unique_timer_id();
+ vpb_timer_open(&tmp->busy_timer, tmp->handle, tmp->busy_timer_id, TIMER_PERIOD_BUSY);
+
+ tmp->ringback_timer_id = vpb_timer_get_unique_timer_id();
+ vpb_timer_open(&tmp->ringback_timer, tmp->handle, tmp->ringback_timer_id, TIMER_PERIOD_RINGBACK);
+
+ tmp->ring_timer_id = vpb_timer_get_unique_timer_id();
+ vpb_timer_open(&tmp->ring_timer, tmp->handle, tmp->ring_timer_id, timer_period_ring);
+
+ tmp->dtmfidd_timer_id = vpb_timer_get_unique_timer_id();
+ vpb_timer_open(&tmp->dtmfidd_timer, tmp->handle, tmp->dtmfidd_timer_id, dtmf_idd);
+
+ if (mode == MODE_FXO){
+ if (use_ast_dtmfdet)
+ vpb_set_event_mask(tmp->handle, VPB_EVENTS_NODTMF );
+ else
+ vpb_set_event_mask(tmp->handle, VPB_EVENTS_ALL );
+ }
+ else {
+/*
+ if (use_ast_dtmfdet)
+ vpb_set_event_mask(tmp->handle, VPB_EVENTS_NODTMF );
+ else
+*/
+ vpb_set_event_mask(tmp->handle, VPB_EVENTS_STAT );
+ }
+
+ if ((tmp->vpb_model == vpb_model_v12pci) && (echo_cancel)){
+ vpb_hostecho_on(tmp->handle);
+ }
+ if (use_ast_dtmfdet) {
+ tmp->vad = ast_dsp_new();
+ ast_dsp_set_features(tmp->vad, DSP_FEATURE_DTMF_DETECT);
+ ast_dsp_digitmode(tmp->vad, DSP_DIGITMODE_DTMF);
+ if (relaxdtmf)
+ ast_dsp_digitmode(tmp->vad, DSP_DIGITMODE_DTMF|DSP_DIGITMODE_RELAXDTMF);
+ }
+ else {
+ tmp->vad = NULL;
+ }
+
+ /* define grunt tone */
+ vpb_settonedet(tmp->handle,&toned_ungrunt);
+
+ ast_log(LOG_NOTICE,"Voicetronix %s channel %s initialized (rxsg=%f/txsg=%f/rxhg=%f/txhg=%f)(0x%x/0x%x/0x%x)\n",
+ (tmp->vpb_model==vpb_model_v4pci)?"V4PCI": (tmp->vpb_model==vpb_model_v12pci)?"V12PCI":"[Unknown model]",
+ tmp->dev, tmp->rxswgain, tmp->txswgain, tmp->rxgain, tmp->txgain, bal1, bal2, bal3 );
+
+ return tmp;
+}
+
+static int vpb_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+ struct vpb_pvt *p = (struct vpb_pvt *)ast->tech_pvt;
+ int res = 0;
+ int tmp = 0;
+
+ if (use_ast_ind == 1) {
+ ast_verb(4, "%s: vpb_indicate called when using Ast Indications !?!\n", p->dev);
+ return 0;
+ }
+
+ ast_verb(4, "%s: vpb_indicate [%d] state[%d]\n", p->dev, condition,ast->_state);
+/*
+ if (ast->_state != AST_STATE_UP) {
+ ast_verb(4, "%s: vpb_indicate Not in AST_STATE_UP\n", p->dev, condition,ast->_state);
+ return res;
+ }
+*/
+
+/*
+ ast_verb(4, "%s: LOCKING in indicate \n", p->dev);
+ ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
+*/
+ ast_mutex_lock(&p->lock);
+ switch(condition) {
+ case AST_CONTROL_BUSY:
+ case AST_CONTROL_CONGESTION:
+ if (ast->_state == AST_STATE_UP) {
+ playtone(p->handle, &Busytone);
+ p->state = VPB_STATE_PLAYBUSY;
+ vpb_timer_stop(p->busy_timer);
+ vpb_timer_start(p->busy_timer);
+ }
+ break;
+ case AST_CONTROL_RINGING:
+ if (ast->_state == AST_STATE_UP) {
+ playtone(p->handle, &Ringbacktone);
+ p->state = VPB_STATE_PLAYRING;
+ ast_verb(4, "%s: vpb indicate: setting ringback timer [%d]\n", p->dev,p->ringback_timer_id);
+
+ vpb_timer_stop(p->ringback_timer);
+ vpb_timer_start(p->ringback_timer);
+ }
+ break;
+ case AST_CONTROL_ANSWER:
+ case -1: /* -1 means stop playing? */
+ vpb_timer_stop(p->ringback_timer);
+ vpb_timer_stop(p->busy_timer);
+ stoptone(p->handle);
+ break;
+ case AST_CONTROL_HANGUP:
+ if (ast->_state == AST_STATE_UP) {
+ playtone(p->handle, &Busytone);
+ p->state = VPB_STATE_PLAYBUSY;
+ vpb_timer_stop(p->busy_timer);
+ vpb_timer_start(p->busy_timer);
+ }
+ break;
+ case AST_CONTROL_HOLD:
+ ast_moh_start(ast, (const char *) data, NULL);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_moh_stop(ast);
+ break;
+ default:
+ res = 0;
+ break;
+ }
+ tmp = ast_mutex_unlock(&p->lock);
+/*
+ ast_verb(4, "%s: unLOCKING in indicate [%d]\n", p->dev,tmp);
+*/
+ return res;
+}
+
+static int vpb_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct vpb_pvt *p = (struct vpb_pvt *)newchan->tech_pvt;
+ int res = 0;
+
+/*
+ ast_verb(4, "%s: LOCKING in fixup \n", p->dev);
+ ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
+*/
+ ast_mutex_lock(&p->lock);
+ ast_debug(1, "New owner for channel %s is %s\n", p->dev, newchan->name);
+
+ if (p->owner == oldchan) {
+ p->owner = newchan;
+ }
+
+ if (newchan->_state == AST_STATE_RINGING){
+ if (use_ast_ind == 1) {
+ ast_verb(4, "%s: vpb_fixup Calling ast_indicate\n", p->dev);
+ ast_indicate(newchan, AST_CONTROL_RINGING);
+ }
+ else {
+ ast_verb(4, "%s: vpb_fixup Calling vpb_indicate\n", p->dev);
+ vpb_indicate(newchan, AST_CONTROL_RINGING, NULL, 0);
+ }
+ }
+
+ res= ast_mutex_unlock(&p->lock);
+/*
+ ast_verb(4, "%s: unLOCKING in fixup [%d]\n", p->dev,res);
+*/
+ return 0;
+}
+
+static int vpb_digit_begin(struct ast_channel *ast, char digit)
+{
+ /* XXX Modify this callback to let Asterisk control the length of DTMF */
+ return 0;
+}
+static int vpb_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct vpb_pvt *p = (struct vpb_pvt *)ast->tech_pvt;
+ char s[2];
+ int res = 0;
+
+ if (use_ast_dtmf){
+ ast_verb(4, "%s: vpb_digit: asked to play digit[%c] but we are using asterisk dtmf play back?!\n", p->dev, digit);
+ return 0;
+ }
+
+/*
+ ast_verb(4, "%s: LOCKING in digit \n", p->dev);
+ ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
+*/
+ ast_mutex_lock(&p->lock);
+
+
+ s[0] = digit;
+ s[1] = '\0';
+
+ ast_verb(4, "%s: vpb_digit: asked to play digit[%s]\n", p->dev, s);
+
+ ast_mutex_lock(&p->play_dtmf_lock);
+ strncat(p->play_dtmf,s,sizeof(*p->play_dtmf));
+ ast_mutex_unlock(&p->play_dtmf_lock);
+
+ res = ast_mutex_unlock(&p->lock);
+/*
+ ast_verb(4, "%s: unLOCKING in digit [%d]\n", p->dev,res);
+*/
+ return 0;
+}
+
+/* Places a call out of a VPB channel */
+static int vpb_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ struct vpb_pvt *p = (struct vpb_pvt *)ast->tech_pvt;
+ int res = 0,i;
+ char *s = strrchr(dest, '/');
+ char dialstring[254] = "";
+ int tmp = 0;
+
+/*
+ ast_verb(4, "%s: LOCKING in call \n", p->dev);
+ ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
+*/
+ ast_mutex_lock(&p->lock);
+ ast_verb(4, "%s: starting call to [%s]\n", p->dev,dest);
+
+ if (s)
+ s = s + 1;
+ else
+ s = dest;
+ strncpy(dialstring, s, sizeof(dialstring) - 1);
+ for (i=0; dialstring[i] != '\0' ; i++) {
+ if ((dialstring[i] == 'w') || (dialstring[i] == 'W'))
+ dialstring[i] = ',';
+ else if ((dialstring[i] == 'f') || (dialstring[i] == 'F'))
+ dialstring[i] = '&';
+ }
+
+ if (ast->_state != AST_STATE_DOWN && ast->_state != AST_STATE_RESERVED) {
+ ast_log(LOG_WARNING, "vpb_call on %s neither down nor reserved!\n", ast->name);
+ tmp = ast_mutex_unlock(&p->lock);
+/*
+ ast_verb(4, "%s: unLOCKING in call [%d]\n", p->dev,tmp);
+*/
+ return -1;
+ }
+ if (p->mode != MODE_FXO) /* Station port, ring it. */
+ res = vpb_ring_station_async(p->handle, VPB_RING_STATION_ON,0);
+ else {
+ VPB_CALL call;
+
+ /* Dial must timeout or it can leave channels unuseable */
+ if( timeout == 0 )
+ timeout = TIMER_PERIOD_NOANSWER;
+ else
+ timeout = timeout * 1000; /* convert from secs to ms. */
+
+ /* These timeouts are only used with call progress dialing */
+ call.dialtones = 1; /* Number of dialtones to get outside line */
+ call.dialtone_timeout = VPB_DIALTONE_WAIT; /* Wait this long for dialtone (ms) */
+ call.ringback_timeout = VPB_RINGWAIT; /* Wait this long for ringing after dialing (ms) */
+ call.inter_ringback_timeout = VPB_CONNECTED_WAIT; /* If ringing stops for this long consider it connected (ms) */
+ call.answer_timeout = timeout; /* Time to wait for answer after ringing starts (ms) */
+ memcpy( &call.tone_map, DialToneMap, sizeof(DialToneMap) );
+ vpb_set_call(p->handle, &call);
+
+ ast_verb(2, "%s: Calling %s on %s \n",p->dev, dialstring, ast->name);
+
+ int j;
+ ast_verb(2, "%s: Dial parms for %s %d/%dms/%dms/%dms/%dms\n", p->dev
+ , ast->name, call.dialtones, call.dialtone_timeout
+ , call.ringback_timeout, call.inter_ringback_timeout
+ , call.answer_timeout );
+ for( j=0; !call.tone_map[j].terminate; j++ )
+ ast_verb(2, "%s: Dial parms for %s tone %d->%d\n", p->dev,
+ ast->name, call.tone_map[j].tone_id, call.tone_map[j].call_id);
+
+ ast_verb(4, "%s: Disabling Loop Drop detection\n",p->dev);
+ vpb_disable_event(p->handle, VPB_MDROP);
+ vpb_sethook_sync(p->handle,VPB_OFFHOOK);
+ p->state=VPB_STATE_OFFHOOK;
+
+ #ifndef DIAL_WITH_CALL_PROGRESS
+ vpb_sleep(300);
+ ast_verb(4, "%s: Enabling Loop Drop detection\n",p->dev);
+ vpb_enable_event(p->handle, VPB_MDROP);
+ res = vpb_dial_async(p->handle, dialstring);
+ #else
+ ast_verb(4, "%s: Enabling Loop Drop detection\n",p->dev);
+ vpb_enable_event(p->handle, VPB_MDROP);
+ res = vpb_call_async(p->handle, dialstring);
+ #endif
+
+ if (res != VPB_OK) {
+ ast_debug(1, "Call on %s to %s failed: %s\n", ast->name, s, vpb_strerror(res));
+ res = -1;
+ } else
+ res = 0;
+ }
+
+ ast_verb(3, "%s: VPB Calling %s [t=%d] on %s returned %d\n",p->dev , s, timeout, ast->name, res);
+ if (res == 0) {
+ ast_setstate(ast, AST_STATE_RINGING);
+ ast_queue_control(ast,AST_CONTROL_RINGING);
+ }
+
+ if (!p->readthread){
+ ast_pthread_create(&p->readthread, NULL, do_chanreads, (void *)p);
+ }
+
+ tmp = ast_mutex_unlock(&p->lock);
+/*
+ ast_verb(4, "%s: unLOCKING in call [%d]\n", p->dev,tmp);
+*/
+ return res;
+}
+
+static int vpb_hangup(struct ast_channel *ast)
+{
+ struct vpb_pvt *p = (struct vpb_pvt *)ast->tech_pvt;
+ VPB_EVENT je;
+ char str[VPB_MAX_STR];
+ int res =0 ;
+
+/*
+ ast_verb(4, "%s: LOCKING in hangup \n", p->dev);
+ ast_verb(4, "%s: LOCKING in hangup count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
+ ast_verb(4, "%s: LOCKING pthread_self(%d)\n", p->dev,pthread_self());
+ ast_mutex_lock(&p->lock);
+*/
+ ast_verb(2, "%s: Hangup requested\n", ast->name);
+
+ if (!ast->tech || !ast->tech_pvt) {
+ ast_log(LOG_WARNING, "%s: channel not connected?\n", ast->name);
+ res = ast_mutex_unlock(&p->lock);
+/*
+ ast_verb(4, "%s: unLOCKING in hangup [%d]\n", p->dev,res);
+*/
+ /* Free up ast dsp if we have one */
+ if ((use_ast_dtmfdet)&&(p->vad)) {
+ ast_dsp_free(p->vad);
+ p->vad = NULL;
+ }
+ return 0;
+ }
+
+
+
+ /* Stop record */
+ p->stopreads = 1;
+ if( p->readthread ){
+ pthread_join(p->readthread, NULL);
+ ast_verb(4, "%s: stopped record thread \n",ast->name);
+ }
+
+ /* Stop play */
+ if (p->lastoutput != -1) {
+ ast_verb(2, "%s: Ending play mode \n",ast->name);
+ vpb_play_terminate(p->handle);
+ ast_mutex_lock(&p->play_lock); {
+ vpb_play_buf_finish(p->handle);
+ } ast_mutex_unlock(&p->play_lock);
+ }
+
+ ast_verb(4, "%s: Setting state down\n",ast->name);
+ ast_setstate(ast,AST_STATE_DOWN);
+
+
+/*
+ ast_verb(4, "%s: LOCKING in hangup \n", p->dev);
+ ast_verb(4, "%s: LOCKING in hangup count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
+ ast_verb(4, "%s: LOCKING pthread_self(%d)\n", p->dev,pthread_self());
+*/
+ ast_mutex_lock(&p->lock);
+
+ if (p->mode != MODE_FXO) {
+ /* station port. */
+ vpb_ring_station_async(p->handle, VPB_RING_STATION_OFF,0);
+ if(p->state!=VPB_STATE_ONHOOK){
+ /* This is causing a "dial end" "play tone" loop
+ playtone(p->handle, &Busytone);
+ p->state = VPB_STATE_PLAYBUSY;
+ ast_verb(5, "%s: Station offhook[%d], playing busy tone\n",
+ ast->name,p->state);
+ */
+ }
+ else {
+ stoptone(p->handle);
+ }
+ #ifdef VPB_PRI
+ vpb_setloop_async(p->handle, VPB_OFFHOOK);
+ vpb_sleep(100);
+ vpb_setloop_async(p->handle, VPB_ONHOOK);
+ #endif
+ } else {
+ stoptone(p->handle); /* Terminates any dialing */
+ vpb_sethook_sync(p->handle, VPB_ONHOOK);
+ p->state=VPB_STATE_ONHOOK;
+ }
+ while (VPB_OK==vpb_get_event_ch_async(p->handle,&je)){
+ vpb_translate_event(&je, str);
+ ast_verb(4, "%s: Flushing event [%d]=>%s\n",ast->name,je.type,str);
+ }
+
+ p->readthread = 0;
+ p->lastoutput = -1;
+ p->lastinput = -1;
+ p->last_ignore_dtmf = 1;
+ p->ext[0] = 0;
+ p->dialtone = 0;
+
+ p->owner = NULL;
+ ast->tech_pvt=NULL;
+
+ /* Free up ast dsp if we have one */
+ if ((use_ast_dtmfdet)&&(p->vad)) {
+ ast_dsp_free(p->vad);
+ p->vad = NULL;
+ }
+
+ ast_verb(2, "%s: Hangup complete\n", ast->name);
+
+ restart_monitor();
+/*
+ ast_verb(4, "%s: LOCKING in hangup count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
+*/
+ res = ast_mutex_unlock(&p->lock);
+/*
+ ast_verb(4, "%s: unLOCKING in hangup [%d]\n", p->dev,res);
+ ast_verb(4, "%s: LOCKING in hangup count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
+*/
+ return 0;
+}
+
+static int vpb_answer(struct ast_channel *ast)
+{
+ struct vpb_pvt *p = (struct vpb_pvt *)ast->tech_pvt;
+ int res = 0;
+/*
+ VPB_EVENT je;
+ int ret;
+ ast_verb(4, "%s: LOCKING in answer \n", p->dev);
+ ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
+*/
+ ast_mutex_lock(&p->lock);
+
+ ast_verb(4, "%s: Answering channel\n",p->dev);
+
+ if (p->mode == MODE_FXO){
+ ast_verb(4, "%s: Disabling Loop Drop detection\n",p->dev);
+ vpb_disable_event(p->handle, VPB_MDROP);
+ }
+
+ if (ast->_state != AST_STATE_UP) {
+ if (p->mode == MODE_FXO){
+ vpb_sethook_sync(p->handle, VPB_OFFHOOK);
+ p->state=VPB_STATE_OFFHOOK;
+/* vpb_sleep(500);
+ ret = vpb_get_event_ch_async(p->handle,&je);
+ if ((ret == VPB_OK)&&((je.type != VPB_DROP)&&(je.type != VPB_RING))){
+ ast_verb(4, "%s: Answer collected a wrong event!!\n",p->dev);
+ vpb_put_event(&je);
+ }
+*/
+ }
+ ast_setstate(ast, AST_STATE_UP);
+
+ ast_verb(2, "%s: Answered call on %s [%s]\n", p->dev,
+ ast->name,(p->mode == MODE_FXO)?"FXO":"FXS");
+
+ ast->rings = 0;
+ if( !p->readthread ){
+ /* res = ast_mutex_unlock(&p->lock); */
+ /* ast_verbose("%s: unLOCKING in answer [%d]\n", p->dev,res); */
+ ast_pthread_create(&p->readthread, NULL, do_chanreads, (void *)p);
+ } else {
+ ast_verb(4, "%s: Record thread already running!!\n",p->dev);
+ }
+ } else {
+ ast_verb(4, "%s: Answered state is up\n",p->dev);
+ /* res = ast_mutex_unlock(&p->lock); */
+ /* ast_verbose("%s: unLOCKING in answer [%d]\n", p->dev,res); */
+ }
+ vpb_sleep(500);
+ if (p->mode == MODE_FXO){
+ ast_verb(4, "%s: Re-enabling Loop Drop detection\n",p->dev);
+ vpb_enable_event(p->handle,VPB_MDROP);
+ }
+ res = ast_mutex_unlock(&p->lock);
+/*
+ ast_verb(4, "%s: unLOCKING in answer [%d]\n", p->dev,res);
+*/
+ return 0;
+}
+
+static struct ast_frame *vpb_read(struct ast_channel *ast)
+{
+ struct vpb_pvt *p = (struct vpb_pvt *)ast->tech_pvt;
+ static struct ast_frame f = {AST_FRAME_NULL};
+
+ f.src = "vpb";
+ ast_log(LOG_NOTICE, "%s: vpb_read: should never be called!\n", p->dev);
+ ast_verbose("%s: vpb_read: should never be called!\n", p->dev);
+
+ return &f;
+}
+
+static inline int ast2vpbformat(int ast_format)
+{
+ switch(ast_format) {
+ case AST_FORMAT_ALAW:
+ return VPB_ALAW;
+ case AST_FORMAT_SLINEAR:
+ return VPB_LINEAR;
+ case AST_FORMAT_ULAW:
+ return VPB_MULAW;
+ case AST_FORMAT_ADPCM:
+ return VPB_OKIADPCM;
+ default:
+ return -1;
+ }
+}
+
+static inline char * ast2vpbformatname(int ast_format)
+{
+ switch(ast_format) {
+ case AST_FORMAT_ALAW:
+ return "AST_FORMAT_ALAW:VPB_ALAW";
+ case AST_FORMAT_SLINEAR:
+ return "AST_FORMAT_SLINEAR:VPB_LINEAR";
+ case AST_FORMAT_ULAW:
+ return "AST_FORMAT_ULAW:VPB_MULAW";
+ case AST_FORMAT_ADPCM:
+ return "AST_FORMAT_ADPCM:VPB_OKIADPCM";
+ default:
+ return "UNKN:UNKN";
+ }
+}
+
+static inline int astformatbits(int ast_format)
+{
+ switch(ast_format) {
+ case AST_FORMAT_ALAW:
+ case AST_FORMAT_ULAW:
+ return 8;
+ case AST_FORMAT_SLINEAR:
+ return 16;
+ case AST_FORMAT_ADPCM:
+ return 4;
+ default:
+ return 8;
+ }
+}
+
+int a_gain_vector(float g, short *v, int n)
+{
+ int i;
+ float tmp;
+ for ( i = 0; i<n; i++) {
+ tmp = g*v[i];
+ if (tmp > 32767.0)
+ tmp = 32767.0;
+ if (tmp < -32768.0)
+ tmp = -32768.0;
+ v[i] = (short)tmp;
+ }
+ return(i);
+}
+
+/* Writes a frame of voice data to a VPB channel */
+static int vpb_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct vpb_pvt *p = (struct vpb_pvt *)ast->tech_pvt;
+ int res = 0, fmt = 0;
+ struct timeval play_buf_time_start;
+ int tdiff;
+
+/* ast_mutex_lock(&p->lock); */
+ ast_verb(6, "%s: vpb_write: Writing to channel\n", p->dev);
+
+ if (frame->frametype != AST_FRAME_VOICE) {
+ ast_verb(4, "%s: vpb_write: Don't know how to handle from type %d\n", ast->name, frame->frametype);
+/* ast_mutex_unlock(&p->lock); */
+ return 0;
+ } else if (ast->_state != AST_STATE_UP) {
+ ast_verb(4, "%s: vpb_write: Attempt to Write frame type[%d]subclass[%d] on not up chan(state[%d])\n",ast->name, frame->frametype, frame->subclass,ast->_state);
+ p->lastoutput = -1;
+/* ast_mutex_unlock(&p->lock); */
+ return 0;
+ }
+/* ast_debug(1, "%s: vpb_write: Checked frame type..\n", p->dev); */
+
+
+ fmt = ast2vpbformat(frame->subclass);
+ if (fmt < 0) {
+ ast_log(LOG_WARNING, "%s: vpb_write: Cannot handle frames of %d format!\n",ast->name, frame->subclass);
+ return -1;
+ }
+
+ tdiff = ast_tvdiff_ms(ast_tvnow(), p->lastplay);
+ ast_debug(1, "%s: vpb_write: time since last play(%d) \n", p->dev, tdiff);
+ if (tdiff < (VPB_SAMPLES/8 - 1)){
+ ast_debug(1, "%s: vpb_write: Asked to play too often (%d) (%d)\n", p->dev, tdiff,frame->datalen);
+// return 0;
+ }
+ p->lastplay = ast_tvnow();
+/*
+ ast_debug(1, "%s: vpb_write: Checked frame format..\n", p->dev);
+*/
+
+ ast_mutex_lock(&p->play_lock);
+
+/*
+ ast_debug(1, "%s: vpb_write: Got play lock..\n", p->dev);
+*/
+
+ /* Check if we have set up the play_buf */
+ if (p->lastoutput == -1) {
+ vpb_play_buf_start(p->handle, fmt);
+ ast_verb(2, "%s: vpb_write: Starting play mode (codec=%d)[%s]\n",p->dev,fmt,ast2vpbformatname(frame->subclass));
+ p->lastoutput = fmt;
+ ast_mutex_unlock(&p->play_lock);
+ return 0;
+ } else if (p->lastoutput != fmt) {
+ vpb_play_buf_finish(p->handle);
+ vpb_play_buf_start(p->handle, fmt);
+ ast_verb(2, "%s: vpb_write: Changed play format (%d=>%d)\n",p->dev,p->lastoutput,fmt);
+ ast_mutex_unlock(&p->play_lock);
+ return 0;
+ }
+ p->lastoutput = fmt;
+
+
+
+ /* Apply extra gain ! */
+ if( p->txswgain > MAX_VPB_GAIN )
+ a_gain_vector(p->txswgain - MAX_VPB_GAIN , (short*)frame->data, frame->datalen/sizeof(short));
+
+/* ast_debug(1, "%s: vpb_write: Applied gain..\n", p->dev); */
+/* ast_debug(1, "%s: vpb_write: play_buf_time %d\n", p->dev, p->play_buf_time); */
+
+ if ((p->read_state == 1)&&(p->play_buf_time<5)){
+ play_buf_time_start = ast_tvnow();
+/* res = vpb_play_buf_sync(p->handle, (char*)frame->data, tdiff*8*2); */
+ res = vpb_play_buf_sync(p->handle, (char*)frame->data, frame->datalen);
+ if(res == VPB_OK) {
+ short * data = (short*)frame->data;
+ ast_verb(6, "%s: vpb_write: Wrote chan (codec=%d) %d %d\n", p->dev, fmt, data[0],data[1]);
+ }
+ p->play_buf_time = ast_tvdiff_ms(ast_tvnow(), play_buf_time_start);
+ }
+ else {
+ p->chuck_count++;
+ ast_debug(1, "%s: vpb_write: Tossed data away, tooooo much data!![%d]\n", p->dev,p->chuck_count);
+ p->play_buf_time=0;
+ }
+
+ ast_mutex_unlock(&p->play_lock);
+/* ast_mutex_unlock(&p->lock); */
+ ast_verb(6, "%s: vpb_write: Done Writing to channel\n", p->dev);
+ return 0;
+}
+
+/* Read monitor thread function. */
+static void *do_chanreads(void *pvt)
+{
+ struct vpb_pvt *p = (struct vpb_pvt *)pvt;
+ struct ast_frame *fr = &p->fr;
+ char *readbuf = ((char *)p->buf) + AST_FRIENDLY_OFFSET;
+ int bridgerec = 0;
+ int afmt, readlen, res, fmt, trycnt=0;
+ int ignore_dtmf;
+ const char * getdtmf_var = NULL;
+
+ fr->frametype = AST_FRAME_VOICE;
+ fr->src = "vpb";
+ fr->mallocd = 0;
+ fr->delivery.tv_sec = 0;
+ fr->delivery.tv_usec = 0;
+ fr->samples = VPB_SAMPLES;
+ fr->offset = AST_FRIENDLY_OFFSET;
+ memset(p->buf, 0, sizeof p->buf);
+
+ ast_verb(3, "%s: chanreads: starting thread\n", p->dev);
+ ast_mutex_lock(&p->record_lock);
+
+ p->stopreads = 0;
+ p->read_state = 1;
+ while (!p->stopreads && p->owner) {
+
+ ast_verb(5, "%s: chanreads: Starting cycle ...\n", p->dev);
+ ast_verb(5, "%s: chanreads: Checking bridge \n", p->dev);
+ if (p->bridge) {
+ if (p->bridge->c0 == p->owner && (p->bridge->flags & AST_BRIDGE_REC_CHANNEL_0))
+ bridgerec = 1;
+ else if (p->bridge->c1 == p->owner && (p->bridge->flags & AST_BRIDGE_REC_CHANNEL_1))
+ bridgerec = 1;
+ else
+ bridgerec = 0;
+ } else {
+ ast_verb(5, "%s: chanreads: No native bridge.\n", p->dev);
+ if (p->owner->_bridge){
+ ast_verb(5, "%s: chanreads: Got Asterisk bridge with [%s].\n", p->dev,p->owner->_bridge->name);
+ bridgerec = 1;
+ }
+ else {
+ bridgerec = 0;
+ }
+ }
+
+/* if ( (p->owner->_state != AST_STATE_UP) || !bridgerec) */
+ if ( (p->owner->_state != AST_STATE_UP) )
+ {
+ if (p->owner->_state != AST_STATE_UP)
+ ast_verb(5, "%s: chanreads: Im not up[%d]\n", p->dev,p->owner->_state);
+ else
+ ast_verb(5, "%s: chanreads: No bridgerec[%d]\n", p->dev,bridgerec);
+ vpb_sleep(10);
+ continue;
+ }
+
+ /* Voicetronix DTMF detection can be triggered off ordinary speech
+ * This leads to annoying beeps during the conversation
+ * Avoid this problem by just setting VPB_GETDTMF when you want to listen for DTMF
+ */
+ /* ignore_dtmf = 1; */
+ ignore_dtmf = 0; /* set this to 1 to turn this feature on */
+ getdtmf_var = pbx_builtin_getvar_helper(p->owner,"VPB_GETDTMF");
+ if( getdtmf_var && ( strcasecmp( getdtmf_var, "yes" ) == 0 ) )
+ ignore_dtmf = 0;
+
+ if(( ignore_dtmf != p->last_ignore_dtmf ) &&(!use_ast_dtmfdet)){
+ ast_verb(2, "%s:Now %s DTMF \n",
+ p->dev, ignore_dtmf ? "ignoring" : "listening for");
+ vpb_set_event_mask(p->handle, ignore_dtmf ? VPB_EVENTS_NODTMF : VPB_EVENTS_ALL );
+ }
+ p->last_ignore_dtmf = ignore_dtmf;
+
+ /* Play DTMF digits here to avoid problem you get if playing a digit during
+ * a record operation
+ */
+ ast_verb(6, "%s: chanreads: Checking dtmf's \n", p->dev);
+ ast_mutex_lock(&p->play_dtmf_lock);
+ if( p->play_dtmf[0] ) {
+ /* Try to ignore DTMF event we get after playing digit */
+ /* This DTMF is played by asterisk and leads to an annoying trailing beep on CISCO phones */
+ if( !ignore_dtmf)
+ vpb_set_event_mask(p->handle, VPB_EVENTS_NODTMF );
+ if (p->bridge == NULL){
+ vpb_dial_sync(p->handle,p->play_dtmf);
+ ast_verb(2, "%s: chanreads: Played DTMF %s\n",p->dev,p->play_dtmf);
+ }
+ else {
+ ast_verb(2, "%s: chanreads: Not playing DTMF frame on native bridge\n", p->dev);
+ }
+ p->play_dtmf[0] = '\0';
+ ast_mutex_unlock(&p->play_dtmf_lock);
+ vpb_sleep(700); /* Long enough to miss echo and DTMF event */
+ if( !ignore_dtmf)
+ vpb_set_event_mask(p->handle, VPB_EVENTS_ALL );
+ continue;
+ }
+ ast_mutex_unlock(&p->play_dtmf_lock);
+
+/* afmt = (p->owner) ? p->owner->rawreadformat : AST_FORMAT_SLINEAR; */
+ if (p->owner){
+ afmt = p->owner->rawreadformat;
+/* ast_debug(1,"%s: Record using owner format [%s]\n", p->dev, ast2vpbformatname(afmt)); */
+ }
+ else {
+ afmt = AST_FORMAT_SLINEAR;
+/* ast_debug(1,"%s: Record using default format [%s]\n", p->dev, ast2vpbformatname(afmt)); */
+ }
+ fmt = ast2vpbformat(afmt);
+ if (fmt < 0) {
+ ast_log(LOG_WARNING,"%s: Record failure (unsupported format %d)\n", p->dev, afmt);
+ return NULL;
+ }
+ readlen = VPB_SAMPLES * astformatbits(afmt) / 8;
+
+ if (p->lastinput == -1) {
+ vpb_record_buf_start(p->handle, fmt);
+ vpb_reset_record_fifo_alarm(p->handle);
+ p->lastinput = fmt;
+ ast_verb(2, "%s: Starting record mode (codec=%d)[%s]\n",p->dev,fmt,ast2vpbformatname(afmt));
+ continue;
+ } else if (p->lastinput != fmt) {
+ vpb_record_buf_finish(p->handle);
+ vpb_record_buf_start(p->handle, fmt);
+ p->lastinput = fmt;
+ ast_verb(2, "%s: Changed record format (%d=>%d)\n",p->dev,p->lastinput,fmt);
+ continue;
+ }
+
+ /* Read only if up and not bridged, or a bridge for which we can read. */
+ ast_verb(6, "%s: chanreads: getting buffer!\n", p->dev);
+ if( (res = vpb_record_buf_sync(p->handle, readbuf, readlen) ) == VPB_OK ) {
+ ast_verb(6, "%s: chanreads: got buffer!\n", p->dev);
+ /* Apply extra gain ! */
+ if( p->rxswgain > MAX_VPB_GAIN )
+ a_gain_vector(p->rxswgain - MAX_VPB_GAIN , (short*)readbuf, readlen/sizeof(short));
+ ast_verb(6, "%s: chanreads: applied gain\n", p->dev);
+
+ fr->subclass = afmt;
+ fr->data = readbuf;
+ fr->datalen = readlen;
+ fr->frametype = AST_FRAME_VOICE;
+
+ if ((use_ast_dtmfdet)&&(p->vad)){
+ fr = ast_dsp_process(p->owner,p->vad,fr);
+ if (fr && (fr->frametype == AST_FRAME_DTMF))
+ ast_debug(1, "%s: chanreads: Detected DTMF '%c'\n",p->dev, fr->subclass);
+ if (fr->subclass == 'm'){
+ /* conf mute request */
+ fr->frametype = AST_FRAME_NULL;
+ fr->subclass = 0;
+ }
+ else if (fr->subclass == 'u'){
+ /* Unmute */
+ fr->frametype = AST_FRAME_NULL;
+ fr->subclass = 0;
+ }
+ else if (fr->subclass == 'f'){
+ }
+ }
+ /* Using trylock here to prevent deadlock when channel is hungup
+ * (ast_hangup() immediately gets lock)
+ */
+ if (p->owner && !p->stopreads ) {
+ ast_verb(6, "%s: chanreads: queueing buffer on read frame q (state[%d])\n", p->dev,p->owner->_state);
+ do {
+ res = ast_mutex_trylock(&p->owner->lock);
+ trycnt++;
+ } while((res !=0)&&(trycnt<300));
+ if (res==0) {
+ ast_queue_frame(p->owner, fr);
+ ast_mutex_unlock(&p->owner->lock);
+ } else {
+ ast_verb(5, "%s: chanreads: Couldnt get lock after %d tries!\n", p->dev,trycnt);
+ }
+ trycnt=0;
+
+/*
+ res = ast_mutex_trylock(&p->owner->lock);
+ if (res==0) {
+ ast_queue_frame(p->owner, fr);
+ ast_mutex_unlock(&p->owner->lock);
+ } else {
+ if (res == EINVAL )
+ ast_verb(5, "%s: chanreads: try owner->lock gave me EINVAL[%d]\n", p->dev,res);
+ else if (res == EBUSY )
+ ast_verb(5, "%s: chanreads: try owner->lock gave me EBUSY[%d]\n", p->dev,res);
+ while(res !=0){
+ res = ast_mutex_trylock(&p->owner->lock);
+ }
+ if (res==0) {
+ ast_queue_frame(p->owner, fr);
+ ast_mutex_unlock(&p->owner->lock);
+ }
+ else {
+ if (res == EINVAL )
+ ast_verb(5, "%s: chanreads: try owner->lock gave me EINVAL[%d]\n", p->dev,res);
+ else if (res == EBUSY )
+ ast_verb(5, "%s: chanreads: try owner->lock gave me EBUSY[%d]\n", p->dev,res);
+ ast_verb(5, "%s: chanreads: Couldnt get lock on owner[%s][%d][%d] channel to send frame!\n", p->dev,p->owner->name,(int)p->owner->lock.__m_owner,(int)p->owner->lock.__m_count);
+ }
+ }
+*/
+ short * data = (short*)readbuf;
+ ast_verb(7, "%s: Read channel (codec=%d) %d %d\n", p->dev, fmt, data[0], data[1] );
+ }
+ else {
+ ast_verb(5, "%s: p->stopreads[%d] p->owner[%p]\n", p->dev, p->stopreads,(void *)p->owner);
+ }
+ }
+ ast_verb(5, "%s: chanreads: Finished cycle...\n", p->dev);
+ }
+ p->read_state=0;
+
+ /* When stopreads seen, go away! */
+ vpb_record_buf_finish(p->handle);
+ p->read_state=0;
+ ast_mutex_unlock(&p->record_lock);
+
+ ast_verb(2, "%s: Ending record mode (%d/%s)\n",
+ p->dev, p->stopreads, p->owner? "yes" : "no");
+ return NULL;
+}
+
+static struct ast_channel *vpb_new(struct vpb_pvt *me, enum ast_channel_state state, char *context)
+{
+ struct ast_channel *tmp;
+ char cid_num[256];
+ char cid_name[256];
+
+ if (me->owner) {
+ ast_log(LOG_WARNING, "Called vpb_new on owned channel (%s) ?!\n", me->dev);
+ return NULL;
+ }
+ ast_verb(4, "%s: New call for context [%s]\n",me->dev,context);
+
+ tmp = ast_channel_alloc(1, state, 0, 0, "", me->ext, me->context, 0, me->dev);
+ if (tmp) {
+ if (use_ast_ind == 1){
+ tmp->tech = &vpb_tech_indicate;
+ }
+ else {
+ tmp->tech = &vpb_tech;
+ }
+
+ tmp->callgroup = me->callgroup;
+ tmp->pickupgroup = me->pickupgroup;
+
+ /* Linear is the preferred format. Although Voicetronix supports other formats
+ * they are all converted to/from linear in the vpb code. Best for us to use
+ * linear since we can then adjust volume in this modules.
+ */
+ tmp->nativeformats = prefformat;
+ tmp->rawreadformat = AST_FORMAT_SLINEAR;
+ tmp->rawwriteformat = AST_FORMAT_SLINEAR;
+ if (state == AST_STATE_RING) {
+ tmp->rings = 1;
+ cid_name[0] = '\0';
+ cid_num[0] = '\0';
+ ast_callerid_split(me->callerid, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num));
+ ast_set_callerid(tmp, cid_num, cid_name, cid_num);
+ }
+ tmp->tech_pvt = me;
+
+ strncpy(tmp->context, context, sizeof(tmp->context)-1);
+ if (strlen(me->ext))
+ strncpy(tmp->exten, me->ext, sizeof(tmp->exten)-1);
+ else
+ strncpy(tmp->exten, "s", sizeof(tmp->exten) - 1);
+ if (strlen(me->language))
+ ast_string_field_set(tmp, language, me->language);
+
+ me->owner = tmp;
+
+ me->bridge = NULL;
+ me->lastoutput = -1;
+ me->lastinput = -1;
+ me->last_ignore_dtmf = 1;
+ me->readthread = 0;
+ me->play_dtmf[0] = '\0';
+ me->faxhandled =0;
+
+ me->lastgrunt = ast_tvnow(); /* Assume at least one grunt tone seen now. */
+ me->lastplay = ast_tvnow(); /* Assume at least one grunt tone seen now. */
+
+ if (state != AST_STATE_DOWN) {
+ if ((me->mode != MODE_FXO)&&(state != AST_STATE_UP)){
+ vpb_answer(tmp);
+ }
+ if (ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ ast_hangup(tmp);
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+ }
+ return tmp;
+}
+
+static struct ast_channel *vpb_request(const char *type, int format, void *data, int *cause)
+{
+ int oldformat;
+ struct vpb_pvt *p;
+ struct ast_channel *tmp = NULL;
+ char *name = ast_strdup(data ? (char *)data : "");
+ char *s, *sepstr;
+ int group=-1;
+
+ oldformat = format;
+ format &= prefformat;
+ if (!format) {
+ ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", oldformat);
+ return NULL;
+ }
+
+ sepstr = name;
+ s = strsep(&sepstr, "/"); /* Handle / issues */
+ if (!s)
+ s = "";
+ /* Check if we are looking for a group */
+ if (toupper(name[0]) == 'G' || toupper(name[0])=='R') {
+ group=atoi(name+1);
+ }
+ /* Search for an unowned channel */
+ ast_mutex_lock(&iflock); {
+ p = iflist;
+ while(p) {
+ if (group == -1){
+ if (strncmp(s, p->dev + 4, sizeof p->dev) == 0) {
+ if (!p->owner) {
+ tmp = vpb_new(p, AST_STATE_DOWN, p->context);
+ break;
+ }
+ }
+ }
+ else {
+ if ((p->group == group) && (!p->owner)) {
+ tmp = vpb_new(p, AST_STATE_DOWN, p->context);
+ break;
+ }
+ }
+ p = p->next;
+ }
+ } ast_mutex_unlock(&iflock);
+
+
+ ast_verb(2, " %s requested, got: [%s]\n", name, tmp ? tmp->name : "None");
+
+ ast_free(name);
+
+ restart_monitor();
+ return tmp;
+}
+
+static float parse_gain_value(char *gain_type, char *value)
+{
+ float gain;
+
+ /* try to scan number */
+ if (sscanf(value, "%f", &gain) != 1)
+ {
+ ast_log(LOG_ERROR, "Invalid %s value '%s' in '%s' config\n", value, gain_type, config);
+ return DEFAULT_GAIN;
+ }
+
+
+ /* percentage? */
+ /*if (value[strlen(value) - 1] == '%') */
+ /* return gain / (float)100; */
+
+ return gain;
+}
+
+
+static int unload_module()
+{
+ struct vpb_pvt *p;
+ /* First, take us out of the channel loop */
+ if (use_ast_ind == 1){
+ ast_channel_unregister(&vpb_tech_indicate);
+ }
+ else {
+ ast_channel_unregister(&vpb_tech);
+ }
+
+ ast_mutex_lock(&iflock); {
+ /* Hangup all interfaces if they have an owner */
+ p = iflist;
+ while(p) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ p = p->next;
+ }
+ iflist = NULL;
+ } ast_mutex_unlock(&iflock);
+
+ ast_mutex_lock(&monlock); {
+ if (mthreadactive > -1) {
+ pthread_cancel(monitor_thread);
+ pthread_join(monitor_thread, NULL);
+ }
+ mthreadactive = -2;
+ } ast_mutex_unlock(&monlock);
+
+ ast_mutex_lock(&iflock); {
+ /* Destroy all the interfaces and free their memory */
+
+ while(iflist) {
+ p = iflist;
+ ast_mutex_destroy(&p->lock);
+ pthread_cancel(p->readthread);
+ ast_mutex_destroy(&p->owner_lock);
+ ast_mutex_destroy(&p->record_lock);
+ ast_mutex_destroy(&p->play_lock);
+ ast_mutex_destroy(&p->play_dtmf_lock);
+ p->readthread = 0;
+
+ vpb_close(p->handle);
+
+ iflist = iflist->next;
+
+ ast_free(p);
+ }
+ iflist = NULL;
+ } ast_mutex_unlock(&iflock);
+
+ ast_mutex_lock(&bridge_lock); {
+ memset(bridges, 0, sizeof bridges);
+ } ast_mutex_unlock(&bridge_lock);
+ ast_mutex_destroy(&bridge_lock);
+ for(int i = 0; i < max_bridges; i++ ) {
+ ast_mutex_destroy(&bridges[i].lock);
+ ast_cond_destroy(&bridges[i].cond);
+ }
+ ast_free(bridges);
+
+ return 0;
+}
+
+static int load_module()
+{
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ struct ast_flags config_flags = { 0 };
+ struct vpb_pvt *tmp;
+ int board = 0, group = 0;
+ ast_group_t callgroup = 0;
+ ast_group_t pickupgroup = 0;
+ int mode = MODE_IMMEDIATE;
+ float txgain = DEFAULT_GAIN, rxgain = DEFAULT_GAIN;
+ float txswgain = 0, rxswgain = 0;
+ int got_gain=0;
+ int first_channel = 1;
+ int echo_cancel = DEFAULT_ECHO_CANCEL;
+ int error = 0; /* Error flag */
+ int bal1 = -1; /* Special value - means do not set */
+ int bal2 = -1;
+ int bal3 = -1;
+ char * callerid = NULL;
+
+ cfg = ast_config_load(config, config_flags);
+
+ /* We *must* have a config file otherwise stop immediately */
+ if (!cfg) {
+ ast_log(LOG_ERROR, "Unable to load config %s\n", config);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ vpb_seterrormode(VPB_ERROR_CODE);
+
+ ast_mutex_lock(&iflock); {
+ v = ast_variable_browse(cfg, "general");
+ while (v){
+ if (strcasecmp(v->name, "cards") == 0) {
+ ast_log(LOG_NOTICE,"VPB Driver configured to use [%d] cards\n",atoi(v->value));
+ }
+ else if (strcasecmp(v->name, "indication") == 0) {
+ use_ast_ind = 1;
+ ast_log(LOG_NOTICE,"VPB driver using Asterisk Indication functions!\n");
+ }
+ else if (strcasecmp(v->name, "break-for-dtmf") == 0) {
+ if (ast_true(v->value)){
+ break_for_dtmf = 1;
+ }
+ else {
+ break_for_dtmf = 0;
+ ast_log(LOG_NOTICE,"VPB driver not stopping for DTMF's in native bridge\n");
+ }
+ }
+ else if (strcasecmp(v->name, "ast-dtmf") == 0) {
+ use_ast_dtmf = 1;
+ ast_log(LOG_NOTICE,"VPB driver using Asterisk DTMF play functions!\n");
+ }
+ else if (strcasecmp(v->name, "ast-dtmf-det") == 0) {
+ use_ast_dtmfdet = 1;
+ ast_log(LOG_NOTICE,"VPB driver using Asterisk DTMF detection functions!\n");
+ }
+ else if (strcasecmp(v->name, "relaxdtmf") == 0) {
+ relaxdtmf = 1;
+ ast_log(LOG_NOTICE,"VPB driver using Relaxed DTMF with Asterisk DTMF detections functions!\n");
+ }
+ else if (strcasecmp(v->name, "timer_period_ring") ==0) {
+ timer_period_ring = atoi(v->value);
+ }
+ else if (strcasecmp(v->name, "ecsuppthres") ==0) {
+ ec_supp_threshold = atoi(v->value);
+ }
+ else if (strcasecmp(v->name, "dtmfidd") ==0) {
+ dtmf_idd = atoi(v->value);
+ ast_log(LOG_NOTICE,"VPB Driver setting DTMF IDD to [%d]ms\n",dtmf_idd);
+ }
+ v = v->next;
+ }
+
+ v = ast_variable_browse(cfg, "interfaces");
+ while(v) {
+ /* Create the interface list */
+ if (strcasecmp(v->name, "board") == 0) {
+ board = atoi(v->value);
+ } else if (strcasecmp(v->name, "group") == 0){
+ group = atoi(v->value);
+ } else if (strcasecmp(v->name, "callgroup") == 0){
+ callgroup = ast_get_group(v->value);
+ } else if (strcasecmp(v->name, "pickupgroup") == 0){
+ pickupgroup = ast_get_group(v->value);
+ } else if (strcasecmp(v->name, "usepolaritycid") == 0){
+ UsePolarityCID = atoi(v->value);
+ } else if (strcasecmp(v->name, "useloopdrop") == 0){
+ UseLoopDrop = atoi(v->value);
+ } else if (strcasecmp(v->name, "usenativebridge") == 0){
+ UseNativeBridge = atoi(v->value);
+ } else if (strcasecmp(v->name, "channel") == 0) {
+ int channel = atoi(v->value);
+ tmp = mkif(board, channel, mode, got_gain, txgain, rxgain, txswgain, rxswgain, bal1, bal2, bal3, callerid, echo_cancel,group,callgroup,pickupgroup);
+ if (tmp) {
+ if(first_channel) {
+ mkbrd( tmp->vpb_model, echo_cancel );
+ first_channel = 0;
+ }
+ tmp->next = iflist;
+ iflist = tmp;
+ } else {
+ ast_log(LOG_ERROR, "Unable to register channel '%s'\n", v->value);
+ error = -1;
+ goto done;
+ }
+ } else if (strcasecmp(v->name, "language") == 0) {
+ strncpy(language, v->value, sizeof(language)-1);
+ } else if (strcasecmp(v->name, "callerid") == 0) {
+ callerid = ast_strdup(v->value);
+ } else if (strcasecmp(v->name, "mode") == 0) {
+ if (strncasecmp(v->value, "di", 2) == 0)
+ mode = MODE_DIALTONE;
+ else if (strncasecmp(v->value, "im", 2) == 0)
+ mode = MODE_IMMEDIATE;
+ else if (strncasecmp(v->value, "fx", 2) == 0)
+ mode = MODE_FXO;
+ else
+ ast_log(LOG_WARNING, "Unknown mode: %s\n", v->value);
+ } else if (!strcasecmp(v->name, "context")) {
+ strncpy(context, v->value, sizeof(context)-1);
+ } else if (!strcasecmp(v->name, "echocancel")) {
+ if (!strcasecmp(v->value, "off"))
+ echo_cancel = 0;
+ } else if (strcasecmp(v->name, "txgain") == 0) {
+ txswgain = parse_gain_value(v->name, v->value);
+ got_gain |=VPB_GOT_TXSWG;
+ } else if (strcasecmp(v->name, "rxgain") == 0) {
+ rxswgain = parse_gain_value(v->name, v->value);
+ got_gain |=VPB_GOT_RXSWG;
+ } else if (strcasecmp(v->name, "txhwgain") == 0) {
+ txgain = parse_gain_value(v->name, v->value);
+ got_gain |=VPB_GOT_TXHWG;
+ } else if (strcasecmp(v->name, "rxhwgain") == 0) {
+ rxgain = parse_gain_value(v->name, v->value);
+ got_gain |=VPB_GOT_RXHWG;
+ } else if (strcasecmp(v->name, "bal1") == 0) {
+ bal1 = strtol(v->value, NULL, 16);
+ if(bal1<0 || bal1>255) {
+ ast_log(LOG_WARNING, "Bad bal1 value: %d\n", bal1);
+ bal1 = -1;
+ }
+ } else if (strcasecmp(v->name, "bal2") == 0) {
+ bal2 = strtol(v->value, NULL, 16);
+ if(bal2<0 || bal2>255) {
+ ast_log(LOG_WARNING, "Bad bal2 value: %d\n", bal2);
+ bal2 = -1;
+ }
+ } else if (strcasecmp(v->name, "bal3") == 0) {
+ bal3 = strtol(v->value, NULL, 16);
+ if(bal3<0 || bal3>255) {
+ ast_log(LOG_WARNING, "Bad bal3 value: %d\n", bal3);
+ bal3 = -1;
+ }
+ } else if (strcasecmp(v->name, "grunttimeout") == 0) {
+ gruntdetect_timeout = 1000*atoi(v->value);
+ }
+ v = v->next;
+ }
+
+ if (gruntdetect_timeout < 1000)
+ gruntdetect_timeout = 1000;
+
+ done: (void)0;
+ } ast_mutex_unlock(&iflock);
+
+ ast_config_destroy(cfg);
+
+ if (use_ast_ind == 1){
+ if (!error && ast_channel_register(&vpb_tech_indicate) != 0) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'vpb'\n");
+ error = -1;
+ }
+ else {
+ ast_log(LOG_NOTICE,"VPB driver Registered (w/AstIndication)\n");
+ }
+ }
+ else {
+ if (!error && ast_channel_register(&vpb_tech) != 0) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'vpb'\n");
+ error = -1;
+ }
+ else {
+ ast_log(LOG_NOTICE,"VPB driver Registered )\n");
+ }
+ }
+
+
+ if (error)
+ unload_module();
+ else
+ restart_monitor(); /* And start the monitor for the first time */
+
+ return error;
+}
+
+/**/
+#if defined(__cplusplus) || defined(c_plusplus)
+ }
+#endif
+/**/
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "VoiceTronix API driver");
diff --git a/trunk/channels/chan_zap.c b/trunk/channels/chan_zap.c
new file mode 100644
index 000000000..08dc2aead
--- /dev/null
+++ b/trunk/channels/chan_zap.c
@@ -0,0 +1,14253 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Zaptel Pseudo TDM interface
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * Connects to the Zaptel telephony library as well as
+ * libpri. Libpri is optional and needed only if you are
+ * going to use ISDN connections.
+ *
+ * You need to install libraries before you attempt to compile
+ * and install the Zaptel channel.
+ *
+ * \par See also
+ * \arg \ref Config_zap
+ *
+ * \ingroup channel_drivers
+ *
+ * \todo Deprecate the "musiconhold" configuration option post 1.4
+ */
+
+/*** MODULEINFO
+ <depend>res_smdi</depend>
+ <depend>zaptel_vldtmf</depend>
+ <depend>zaptel</depend>
+ <depend>tonezone</depend>
+ <depend>res_features</depend>
+ <use>pri</use>
+ <use>ss7</use>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#ifdef __NetBSD__
+#include <pthread.h>
+#include <signal.h>
+#else
+#include <sys/signal.h>
+#endif
+#include <sys/ioctl.h>
+#include <math.h>
+#include <ctype.h>
+#include "asterisk/zapata.h"
+
+#ifdef HAVE_PRI
+#include <libpri.h>
+#endif
+
+#ifdef HAVE_SS7
+#include <libss7.h>
+#endif
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/file.h"
+#include "asterisk/ulaw.h"
+#include "asterisk/alaw.h"
+#include "asterisk/callerid.h"
+#include "asterisk/adsi.h"
+#include "asterisk/cli.h"
+#include "asterisk/cdr.h"
+#include "asterisk/features.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/say.h"
+#include "asterisk/tdd.h"
+#include "asterisk/app.h"
+#include "asterisk/dsp.h"
+#include "asterisk/astdb.h"
+#include "asterisk/manager.h"
+#include "asterisk/causes.h"
+#include "asterisk/term.h"
+#include "asterisk/utils.h"
+#include "asterisk/transcap.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/smdi.h"
+#include "asterisk/astobj.h"
+#include "asterisk/event.h"
+
+#define SMDI_MD_WAIT_TIMEOUT 1500 /* 1.5 seconds */
+
+#ifdef ZT_SPANINFO_HAS_LINECONFIG
+static const char *lbostr[] = {
+"0 db (CSU)/0-133 feet (DSX-1)",
+"133-266 feet (DSX-1)",
+"266-399 feet (DSX-1)",
+"399-533 feet (DSX-1)",
+"533-655 feet (DSX-1)",
+"-7.5db (CSU)",
+"-15db (CSU)",
+"-22.5db (CSU)"
+};
+#endif
+
+/*! Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = ""
+};
+static struct ast_jb_conf global_jbconf;
+
+#if !defined(ZT_SIG_EM_E1) || (defined(HAVE_PRI) && !defined(ZT_SIG_HARDHDLC))
+#error "Your Zaptel is too old. Please update"
+#endif
+
+#ifndef ZT_TONEDETECT
+/* Work around older code with no tone detect */
+#define ZT_EVENT_DTMFDOWN 0
+#define ZT_EVENT_DTMFUP 0
+#endif
+
+/* define this to send PRI user-user information elements */
+#undef SUPPORT_USERUSER
+
+/*!
+ * \note Define ZHONE_HACK to cause us to go off hook and then back on hook when
+ * the user hangs up to reset the state machine so ring works properly.
+ * This is used to be able to support kewlstart by putting the zhone in
+ * groundstart mode since their forward disconnect supervision is entirely
+ * broken even though their documentation says it isn't and their support
+ * is entirely unwilling to provide any assistance with their channel banks
+ * even though their web site says they support their products for life.
+ */
+/* #define ZHONE_HACK */
+
+/*! \note
+ * Define if you want to check the hook state for an FXO (FXS signalled) interface
+ * before dialing on it. Certain FXO interfaces always think they're out of
+ * service with this method however.
+ */
+/* #define ZAP_CHECK_HOOKSTATE */
+
+/*! \brief Typically, how many rings before we should send Caller*ID */
+#define DEFAULT_CIDRINGS 1
+
+#define CHANNEL_PSEUDO -12
+
+#define AST_LAW(p) (((p)->law == ZT_LAW_ALAW) ? AST_FORMAT_ALAW : AST_FORMAT_ULAW)
+
+
+/*! \brief Signaling types that need to use MF detection should be placed in this macro */
+#define NEED_MFDETECT(p) (((p)->sig == SIG_FEATDMF) || ((p)->sig == SIG_FEATDMF_TA) || ((p)->sig == SIG_E911) || ((p)->sig == SIG_FGC_CAMA) || ((p)->sig == SIG_FGC_CAMAMF) || ((p)->sig == SIG_FEATB))
+
+static const char tdesc[] = "Zapata Telephony Driver"
+#ifdef HAVE_PRI
+ " w/PRI"
+#endif
+#ifdef HAVE_SS7
+ " w/SS7"
+#endif
+;
+
+static const char config[] = "zapata.conf";
+
+#define SIG_EM ZT_SIG_EM
+#define SIG_EMWINK (0x0100000 | ZT_SIG_EM)
+#define SIG_FEATD (0x0200000 | ZT_SIG_EM)
+#define SIG_FEATDMF (0x0400000 | ZT_SIG_EM)
+#define SIG_FEATB (0x0800000 | ZT_SIG_EM)
+#define SIG_E911 (0x1000000 | ZT_SIG_EM)
+#define SIG_FEATDMF_TA (0x2000000 | ZT_SIG_EM)
+#define SIG_FGC_CAMA (0x4000000 | ZT_SIG_EM)
+#define SIG_FGC_CAMAMF (0x8000000 | ZT_SIG_EM)
+#define SIG_FXSLS ZT_SIG_FXSLS
+#define SIG_FXSGS ZT_SIG_FXSGS
+#define SIG_FXSKS ZT_SIG_FXSKS
+#define SIG_FXOLS ZT_SIG_FXOLS
+#define SIG_FXOGS ZT_SIG_FXOGS
+#define SIG_FXOKS ZT_SIG_FXOKS
+#define SIG_PRI ZT_SIG_CLEAR
+#define SIG_BRI (0x2000000 | ZT_SIG_CLEAR)
+#define SIG_BRI_PTMP (0X4000000 | ZT_SIG_CLEAR)
+#define SIG_SS7 (0x1000000 | ZT_SIG_CLEAR)
+#define SIG_SF ZT_SIG_SF
+#define SIG_SFWINK (0x0100000 | ZT_SIG_SF)
+#define SIG_SF_FEATD (0x0200000 | ZT_SIG_SF)
+#define SIG_SF_FEATDMF (0x0400000 | ZT_SIG_SF)
+#define SIG_SF_FEATB (0x0800000 | ZT_SIG_SF)
+#define SIG_EM_E1 ZT_SIG_EM_E1
+#define SIG_GR303FXOKS (0x0100000 | ZT_SIG_FXOKS)
+#define SIG_GR303FXSKS (0x0100000 | ZT_SIG_FXSKS)
+
+#ifdef LOTS_OF_SPANS
+#define NUM_SPANS ZT_MAX_SPANS
+#else
+#define NUM_SPANS 32
+#endif
+#define NUM_DCHANS 4 /*!< No more than 4 d-channels */
+#define MAX_CHANNELS 672 /*!< No more than a DS3 per trunk group */
+
+#define CHAN_PSEUDO -2
+
+#define DCHAN_PROVISIONED (1 << 0)
+#define DCHAN_NOTINALARM (1 << 1)
+#define DCHAN_UP (1 << 2)
+
+#define DCHAN_AVAILABLE (DCHAN_PROVISIONED | DCHAN_NOTINALARM | DCHAN_UP)
+
+/* Overlap dialing option types */
+#define ZAP_OVERLAPDIAL_NONE 0
+#define ZAP_OVERLAPDIAL_OUTGOING 1
+#define ZAP_OVERLAPDIAL_INCOMING 2
+#define ZAP_OVERLAPDIAL_BOTH (ZAP_OVERLAPDIAL_INCOMING|ZAP_OVERLAPDIAL_OUTGOING)
+
+
+#define CALLPROGRESS_PROGRESS 1
+#define CALLPROGRESS_FAX_OUTGOING 2
+#define CALLPROGRESS_FAX_INCOMING 4
+#define CALLPROGRESS_FAX (CALLPROGRESS_FAX_INCOMING | CALLPROGRESS_FAX_OUTGOING)
+
+static char defaultcic[64] = "";
+static char defaultozz[64] = "";
+
+/*! Run this script when the MWI state changes on an FXO line, if mwimonitor is enabled */
+static char mwimonitornotify[PATH_MAX] = "";
+
+static char progzone[10] = "";
+
+static int usedistinctiveringdetection = 0;
+static int distinctiveringaftercid = 0;
+
+static int numbufs = 4;
+
+static int mwilevel = 512;
+
+#ifdef HAVE_PRI
+static struct ast_channel inuse;
+#ifdef PRI_GETSET_TIMERS
+static int pritimers[PRI_MAX_TIMERS];
+#endif
+static int pridebugfd = -1;
+static char pridebugfilename[1024] = "";
+#endif
+
+/*! \brief Wait up to 16 seconds for first digit (FXO logic) */
+static int firstdigittimeout = 16000;
+
+/*! \brief How long to wait for following digits (FXO logic) */
+static int gendigittimeout = 8000;
+
+/*! \brief How long to wait for an extra digit, if there is an ambiguous match */
+static int matchdigittimeout = 3000;
+
+/*! \brief Protect the interface list (of zt_pvt's) */
+AST_MUTEX_DEFINE_STATIC(iflock);
+
+
+static int ifcount = 0;
+
+#ifdef HAVE_PRI
+AST_MUTEX_DEFINE_STATIC(pridebugfdlock);
+#endif
+
+/*! \brief Protect the monitoring thread, so only one process can kill or start it, and not
+ when it's doing something critical. */
+AST_MUTEX_DEFINE_STATIC(monlock);
+
+/*! \brief This is the thread for the monitor which checks for input on the channels
+ which are not currently in use. */
+static pthread_t monitor_thread = AST_PTHREADT_NULL;
+
+static int restart_monitor(void);
+
+static enum ast_bridge_result zt_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
+
+static int zt_sendtext(struct ast_channel *c, const char *text);
+
+static void mwi_event_cb(const struct ast_event *event, void *userdata)
+{
+ /* This module does not handle MWI in an event-based manner. However, it
+ * subscribes to MWI for each mailbox that is configured so that the core
+ * knows that we care about it. Then, chan_zap will get the MWI from the
+ * event cache instead of checking the mailbox directly. */
+}
+
+/*! \brief Avoid the silly zt_getevent which ignores a bunch of events */
+static inline int zt_get_event(int fd)
+{
+ int j;
+ if (ioctl(fd, ZT_GETEVENT, &j) == -1)
+ return -1;
+ return j;
+}
+
+/*! \brief Avoid the silly zt_waitevent which ignores a bunch of events */
+static inline int zt_wait_event(int fd)
+{
+ int i, j = 0;
+ i = ZT_IOMUX_SIGEVENT;
+ if (ioctl(fd, ZT_IOMUX, &i) == -1)
+ return -1;
+ if (ioctl(fd, ZT_GETEVENT, &j) == -1)
+ return -1;
+ return j;
+}
+
+/*! Chunk size to read -- we use 20ms chunks to make things happy. */
+#define READ_SIZE 160
+
+#define MASK_AVAIL (1 << 0) /*!< Channel available for PRI use */
+#define MASK_INUSE (1 << 1) /*!< Channel currently in use */
+
+#define CALLWAITING_SILENT_SAMPLES ( (300 * 8) / READ_SIZE) /*!< 300 ms */
+#define CALLWAITING_REPEAT_SAMPLES ( (10000 * 8) / READ_SIZE) /*!< 300 ms */
+#define CIDCW_EXPIRE_SAMPLES ( (500 * 8) / READ_SIZE) /*!< 500 ms */
+#define MIN_MS_SINCE_FLASH ( (2000) ) /*!< 2000 ms */
+#define DEFAULT_RINGT ( (8000 * 8) / READ_SIZE)
+
+struct zt_pvt;
+
+static int ringt_base = DEFAULT_RINGT;
+
+#ifdef HAVE_SS7
+
+#define LINKSTATE_INALARM (1 << 0)
+#define LINKSTATE_STARTING (1 << 1)
+#define LINKSTATE_UP (1 << 2)
+#define LINKSTATE_DOWN (1 << 3)
+
+#define SS7_NAI_DYNAMIC -1
+
+struct zt_ss7 {
+ pthread_t master; /*!< Thread of master */
+ ast_mutex_t lock;
+ int fds[NUM_DCHANS];
+ int numsigchans;
+ int linkstate[NUM_DCHANS];
+ int numchans;
+ int type;
+ enum {
+ LINKSET_STATE_DOWN = 0,
+ LINKSET_STATE_UP
+ } state;
+ char called_nai; /*!< Called Nature of Address Indicator */
+ char calling_nai; /*!< Calling Nature of Address Indicator */
+ char internationalprefix[10]; /*!< country access code ('00' for european dialplans) */
+ char nationalprefix[10]; /*!< area access code ('0' for european dialplans) */
+ char subscriberprefix[20]; /*!< area access code + area code ('0'+area code for european dialplans) */
+ char unknownprefix[20]; /*!< for unknown dialplans */
+ struct ss7 *ss7;
+ struct zt_pvt *pvts[MAX_CHANNELS]; /*!< Member channel pvt structs */
+};
+
+static struct zt_ss7 linksets[NUM_SPANS];
+
+static int cur_ss7type = -1;
+static int cur_linkset = -1;
+static int cur_pointcode = -1;
+static int cur_cicbeginswith = -1;
+static int cur_adjpointcode = -1;
+static int cur_networkindicator = -1;
+static int cur_defaultdpc = -1;
+#endif /* HAVE_SS7 */
+
+#ifdef HAVE_PRI
+
+#define PVT_TO_CHANNEL(p) (((p)->prioffset) | ((p)->logicalspan << 8) | (p->pri->mastertrunkgroup ? 0x10000 : 0))
+#define PRI_CHANNEL(p) ((p) & 0xff)
+#define PRI_SPAN(p) (((p) >> 8) & 0xff)
+#define PRI_EXPLICIT(p) (((p) >> 16) & 0x01)
+
+struct zt_pri {
+ pthread_t master; /*!< Thread of master */
+ ast_mutex_t lock; /*!< Mutex */
+ char idleext[AST_MAX_EXTENSION]; /*!< Where to idle extra calls */
+ char idlecontext[AST_MAX_CONTEXT]; /*!< What context to use for idle */
+ char idledial[AST_MAX_EXTENSION]; /*!< What to dial before dumping */
+ int minunused; /*!< Min # of channels to keep empty */
+ int minidle; /*!< Min # of "idling" calls to keep active */
+ int nodetype; /*!< Node type */
+ int switchtype; /*!< Type of switch to emulate */
+ int nsf; /*!< Network-Specific Facilities */
+ int dialplan; /*!< Dialing plan */
+ int localdialplan; /*!< Local dialing plan */
+ char internationalprefix[10]; /*!< country access code ('00' for european dialplans) */
+ char nationalprefix[10]; /*!< area access code ('0' for european dialplans) */
+ char localprefix[20]; /*!< area access code + area code ('0'+area code for european dialplans) */
+ char privateprefix[20]; /*!< for private dialplans */
+ char unknownprefix[20]; /*!< for unknown dialplans */
+ int dchannels[NUM_DCHANS]; /*!< What channel are the dchannels on */
+ int trunkgroup; /*!< What our trunkgroup is */
+ int mastertrunkgroup; /*!< What trunk group is our master */
+ int prilogicalspan; /*!< Logical span number within trunk group */
+ int numchans; /*!< Num of channels we represent */
+ int overlapdial; /*!< In overlap dialing mode */
+ int facilityenable; /*!< Enable facility IEs */
+ struct pri *dchans[NUM_DCHANS]; /*!< Actual d-channels */
+ int dchanavail[NUM_DCHANS]; /*!< Whether each channel is available */
+ struct pri *pri; /*!< Currently active D-channel */
+ int debug;
+ int fds[NUM_DCHANS]; /*!< FD's for d-channels */
+ int offset;
+ int span;
+ int resetting;
+ int resetpos;
+ time_t lastreset; /*!< time when unused channels were last reset */
+ long resetinterval; /*!< Interval (in seconds) for resetting unused channels */
+ int sig;
+ struct zt_pvt *pvts[MAX_CHANNELS]; /*!< Member channel pvt structs */
+ struct zt_pvt *crvs; /*!< Member CRV structs */
+ struct zt_pvt *crvend; /*!< Pointer to end of CRV structs */
+};
+
+
+static struct zt_pri pris[NUM_SPANS];
+
+#if 0
+#define DEFAULT_PRI_DEBUG (PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q921_DUMP | PRI_DEBUG_Q921_RAW | PRI_DEBUG_Q921_STATE)
+#else
+#define DEFAULT_PRI_DEBUG 0
+#endif
+
+static inline void pri_rel(struct zt_pri *pri)
+{
+ ast_mutex_unlock(&pri->lock);
+}
+
+#else
+/*! Shut up the compiler */
+struct zt_pri;
+#endif
+
+#define SUB_REAL 0 /*!< Active call */
+#define SUB_CALLWAIT 1 /*!< Call-Waiting call on hold */
+#define SUB_THREEWAY 2 /*!< Three-way call */
+
+/* Polarity states */
+#define POLARITY_IDLE 0
+#define POLARITY_REV 1
+
+
+static struct zt_distRings drings;
+
+struct distRingData {
+ int ring[3];
+ int range;
+};
+struct ringContextData {
+ char contextData[AST_MAX_CONTEXT];
+};
+struct zt_distRings {
+ struct distRingData ringnum[3];
+ struct ringContextData ringContext[3];
+};
+
+static char *subnames[] = {
+ "Real",
+ "Callwait",
+ "Threeway"
+};
+
+struct zt_subchannel {
+ int zfd;
+ struct ast_channel *owner;
+ int chan;
+ short buffer[AST_FRIENDLY_OFFSET/2 + READ_SIZE];
+ struct ast_frame f; /*!< One frame for each channel. How did this ever work before? */
+ unsigned int needringing:1;
+ unsigned int needbusy:1;
+ unsigned int needcongestion:1;
+ unsigned int needcallerid:1;
+ unsigned int needanswer:1;
+ unsigned int needflash:1;
+ unsigned int needhold:1;
+ unsigned int needunhold:1;
+ unsigned int linear:1;
+ unsigned int inthreeway:1;
+ ZT_CONFINFO curconf;
+};
+
+#define CONF_USER_REAL (1 << 0)
+#define CONF_USER_THIRDCALL (1 << 1)
+
+#define MAX_SLAVES 4
+
+static struct zt_pvt {
+ ast_mutex_t lock;
+ struct ast_channel *owner; /*!< Our current active owner (if applicable) */
+ /*!< Up to three channels can be associated with this call */
+
+ struct zt_subchannel sub_unused; /*!< Just a safety precaution */
+ struct zt_subchannel subs[3]; /*!< Sub-channels */
+ struct zt_confinfo saveconf; /*!< Saved conference info */
+
+ struct zt_pvt *slaves[MAX_SLAVES]; /*!< Slave to us (follows our conferencing) */
+ struct zt_pvt *master; /*!< Master to us (we follow their conferencing) */
+ int inconference; /*!< If our real should be in the conference */
+
+ int sig; /*!< Signalling style */
+ int radio; /*!< radio type */
+ int outsigmod; /*!< Outbound Signalling style (modifier) */
+ int oprmode; /*!< "Operator Services" mode */
+ struct zt_pvt *oprpeer; /*!< "Operator Services" peer tech_pvt ptr */
+ float cid_rxgain; /*!< "Gain to apply during caller id */
+ float rxgain;
+ float txgain;
+ int tonezone; /*!< tone zone for this chan, or -1 for default */
+ struct zt_pvt *next; /*!< Next channel in list */
+ struct zt_pvt *prev; /*!< Prev channel in list */
+
+ /* flags */
+ unsigned int adsi:1;
+ unsigned int answeronpolarityswitch:1;
+ unsigned int busydetect:1;
+ unsigned int callreturn:1;
+ unsigned int callwaiting:1;
+ unsigned int callwaitingcallerid:1;
+ unsigned int cancallforward:1;
+ unsigned int canpark:1;
+ unsigned int confirmanswer:1; /*!< Wait for '#' to confirm answer */
+ unsigned int destroy:1;
+ unsigned int didtdd:1; /*!< flag to say its done it once */
+ unsigned int dialednone:1;
+ unsigned int dialing:1;
+ unsigned int digital:1;
+ unsigned int dnd:1;
+ unsigned int echobreak:1;
+ unsigned int echocanbridged:1;
+ unsigned int echocanon:1;
+ unsigned int faxhandled:1; /*!< Has a fax tone already been handled? */
+ unsigned int firstradio:1;
+ unsigned int hanguponpolarityswitch:1;
+ unsigned int hardwaredtmf:1;
+ unsigned int hidecallerid:1;
+ unsigned int hidecalleridname:1; /*!< Hide just the name not the number for legacy PBX use */
+ unsigned int ignoredtmf:1;
+ unsigned int immediate:1; /*!< Answer before getting digits? */
+ unsigned int inalarm:1;
+ unsigned int mate:1; /*!< flag to say its in MATE mode */
+ unsigned int outgoing:1;
+ /* unsigned int overlapdial:1; unused and potentially confusing */
+ unsigned int permcallwaiting:1;
+ unsigned int permhidecallerid:1; /*!< Whether to hide our outgoing caller ID or not */
+ unsigned int priindication_oob:1;
+ unsigned int priexclusive:1;
+ unsigned int pulse:1;
+ unsigned int pulsedial:1; /*!< whether a pulse dial phone is detected */
+ unsigned int restrictcid:1; /*!< Whether restrict the callerid -> only send ANI */
+ unsigned int threewaycalling:1;
+ unsigned int transfer:1;
+ unsigned int use_callerid:1; /*!< Whether or not to use caller id on this channel */
+ unsigned int use_callingpres:1; /*!< Whether to use the callingpres the calling switch sends */
+ unsigned int usedistinctiveringdetection:1;
+ unsigned int zaptrcallerid:1; /*!< should we use the callerid from incoming call on zap transfer or not */
+ unsigned int transfertobusy:1; /*!< allow flash-transfers to busy channels */
+ unsigned int mwimonitor:1; /*!< monitor this FXO port for MWI indication from other end */
+ unsigned int mwimonitoractive:1; /*!< an MWI monitor thread is currently active */
+ /* Channel state or unavilability flags */
+ unsigned int inservice:1;
+ unsigned int locallyblocked:1;
+ unsigned int remotelyblocked:1;
+#if defined(HAVE_PRI) || defined(HAVE_SS7)
+ unsigned int rlt:1;
+ unsigned int alerting:1;
+ unsigned int alreadyhungup:1;
+ unsigned int isidlecall:1;
+ unsigned int proceeding:1;
+ unsigned int progress:1;
+ unsigned int resetting:1;
+ unsigned int setup_ack:1;
+#endif
+ unsigned int use_smdi:1; /* Whether to use SMDI on this channel */
+ struct ast_smdi_interface *smdi_iface; /* The serial port to listen for SMDI data on */
+
+ struct zt_distRings drings;
+
+ char context[AST_MAX_CONTEXT];
+ char defcontext[AST_MAX_CONTEXT];
+ char exten[AST_MAX_EXTENSION];
+ char language[MAX_LANGUAGE];
+ char mohinterpret[MAX_MUSICCLASS];
+ char mohsuggest[MAX_MUSICCLASS];
+#if defined(PRI_ANI) || defined(HAVE_SS7)
+ char cid_ani[AST_MAX_EXTENSION];
+#endif
+ int cid_ani2;
+ char cid_num[AST_MAX_EXTENSION];
+ int cid_ton; /*!< Type Of Number (TON) */
+ char cid_name[AST_MAX_EXTENSION];
+ char lastcid_num[AST_MAX_EXTENSION];
+ char lastcid_name[AST_MAX_EXTENSION];
+ char *origcid_num; /*!< malloced original callerid */
+ char *origcid_name; /*!< malloced original callerid */
+ char callwait_num[AST_MAX_EXTENSION];
+ char callwait_name[AST_MAX_EXTENSION];
+ char rdnis[AST_MAX_EXTENSION];
+ char dnid[AST_MAX_EXTENSION];
+ ast_group_t group;
+ int law;
+ int confno; /*!< Our conference */
+ int confusers; /*!< Who is using our conference */
+ int propconfno; /*!< Propagated conference number */
+ ast_group_t callgroup;
+ ast_group_t pickupgroup;
+ struct ast_variable *vars;
+ int channel; /*!< Channel Number or CRV */
+ int span; /*!< Span number */
+ time_t guardtime; /*!< Must wait this much time before using for new call */
+ int cid_signalling; /*!< CID signalling type bell202 or v23 */
+ int cid_start; /*!< CID start indicator, polarity or ring */
+ int callingpres; /*!< The value of callling presentation that we're going to use when placing a PRI call */
+ int callwaitingrepeat; /*!< How many samples to wait before repeating call waiting */
+ int cidcwexpire; /*!< When to expire our muting for CID/CW */
+ unsigned char *cidspill;
+ int cidpos;
+ int cidlen;
+ int ringt;
+ int ringt_base;
+ int stripmsd;
+ int callwaitcas;
+ int callwaitrings;
+#if defined(HAVE_ZAPTEL_ECHOCANPARAMS)
+ struct {
+ struct zt_echocanparams head;
+ struct zt_echocanparam params[ZT_MAX_ECHOCANPARAMS];
+ } echocancel;
+#else
+ int echocancel;
+#endif
+ int echotraining;
+ char echorest[20];
+ int busycount;
+ int busy_tonelength;
+ int busy_quietlength;
+ int callprogress;
+ struct timeval flashtime; /*!< Last flash-hook time */
+ struct ast_dsp *dsp;
+ int cref; /*!< Call reference number */
+ ZT_DIAL_OPERATION dop;
+ int whichwink; /*!< SIG_FEATDMF_TA Which wink are we on? */
+ char finaldial[64];
+ char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Account code */
+ int amaflags; /*!< AMA Flags */
+ struct tdd_state *tdd; /*!< TDD flag */
+ char call_forward[AST_MAX_EXTENSION];
+ char mailbox[AST_MAX_EXTENSION];
+ struct ast_event_sub *mwi_event_sub;
+ char dialdest[256];
+ int onhooktime;
+ int msgstate;
+ int distinctivering; /*!< Which distinctivering to use */
+ int cidrings; /*!< Which ring to deliver CID on */
+ int dtmfrelax; /*!< whether to run in relaxed DTMF mode */
+ int fake_event;
+ int polarityonanswerdelay;
+ struct timeval polaritydelaytv;
+ int sendcalleridafter;
+#ifdef HAVE_PRI
+ struct zt_pri *pri;
+ struct zt_pvt *bearer;
+ struct zt_pvt *realcall;
+ q931_call *call;
+ int prioffset;
+ int logicalspan;
+#endif
+ int polarity;
+ int dsp_features;
+#ifdef HAVE_SS7
+ struct zt_ss7 *ss7;
+ struct isup_call *ss7call;
+ char charge_number[50];
+ char gen_add_number[50];
+ char gen_dig_number[50];
+ unsigned char gen_add_num_plan;
+ unsigned char gen_add_nai;
+ unsigned char gen_add_pres_ind;
+ unsigned char gen_add_type;
+ unsigned char gen_dig_type;
+ unsigned char gen_dig_scheme;
+ char jip_number[50];
+ unsigned char lspi_type;
+ unsigned char lspi_scheme;
+ unsigned char lspi_context;
+ char lspi_ident[50];
+ unsigned int call_ref_ident;
+ unsigned int call_ref_pc;
+ int transcap;
+ int cic; /*!< CIC associated with channel */
+ unsigned int dpc; /*!< CIC's DPC */
+ unsigned int loopedback:1;
+#endif
+ char begindigit;
+} *iflist = NULL, *ifend = NULL;
+
+/*! \brief Channel configuration from zapata.conf .
+ * This struct is used for parsing the [channels] section of zapata.conf.
+ * Generally there is a field here for every possible configuration item.
+ *
+ * The state of fields is saved along the parsing and whenever a 'channel'
+ * statement is reached, the current zt_chan_conf is used to configure the
+ * channel (struct zt_pvt)
+ *
+ * \see zt_chan_init for the default values.
+ */
+struct zt_chan_conf {
+ struct zt_pvt chan;
+#ifdef HAVE_PRI
+ struct zt_pri pri;
+#endif
+
+#ifdef HAVE_SS7
+ struct zt_ss7 ss7;
+#endif
+ ZT_PARAMS timing;
+ int is_sig_auto; /*!< Use channel signalling from Zaptel? */
+
+ char smdi_port[SMDI_MAX_FILENAME_LEN];
+};
+
+/** returns a new zt_chan_conf with default values (by-value) */
+static struct zt_chan_conf zt_chan_conf_default(void) {
+ /* recall that if a field is not included here it is initialized
+ * to 0 or equivalent
+ */
+ struct zt_chan_conf conf = {
+#ifdef HAVE_PRI
+ .pri = {
+ .nsf = PRI_NSF_NONE,
+ .switchtype = PRI_SWITCH_NI2,
+ .dialplan = PRI_NATIONAL_ISDN + 1,
+ .localdialplan = PRI_NATIONAL_ISDN + 1,
+ .nodetype = PRI_CPE,
+
+ .minunused = 2,
+ .idleext = "",
+ .idledial = "",
+ .internationalprefix = "",
+ .nationalprefix = "",
+ .localprefix = "",
+ .privateprefix = "",
+ .unknownprefix = "",
+ .resetinterval = -1,
+ },
+#endif
+#ifdef HAVE_SS7
+ .ss7 = {
+ .called_nai = SS7_NAI_NATIONAL,
+ .calling_nai = SS7_NAI_NATIONAL,
+ .internationalprefix = "",
+ .nationalprefix = "",
+ .subscriberprefix = "",
+ .unknownprefix = ""
+ },
+#endif
+ .chan = {
+ .context = "default",
+ .cid_num = "",
+ .cid_name = "",
+ .mohinterpret = "default",
+ .mohsuggest = "",
+ .transfertobusy = 1,
+
+ .cid_signalling = CID_SIG_BELL,
+ .cid_start = CID_START_RING,
+ .zaptrcallerid = 0,
+ .use_callerid = 1,
+ .sig = -1,
+ .outsigmod = -1,
+
+ .cid_rxgain = +5.0,
+
+ .tonezone = -1,
+
+#if defined(HAVE_ZAPTEL_ECHOCANPARAMS)
+ .echocancel.head.tap_length = 1,
+#else
+ .echocancel = 1,
+#endif
+
+ .busycount = 3,
+
+ .accountcode = "",
+
+ .mailbox = "",
+
+
+ .polarityonanswerdelay = 600,
+
+ .sendcalleridafter = DEFAULT_CIDRINGS
+ },
+ .timing = {
+ .prewinktime = -1,
+ .preflashtime = -1,
+ .winktime = -1,
+ .flashtime = -1,
+ .starttime = -1,
+ .rxwinktime = -1,
+ .rxflashtime = -1,
+ .debouncetime = -1
+ },
+ .is_sig_auto = 1,
+ .smdi_port = "/dev/ttyS0",
+ };
+
+ return conf;
+}
+
+
+static struct ast_channel *zt_request(const char *type, int format, void *data, int *cause);
+static int zt_digit_begin(struct ast_channel *ast, char digit);
+static int zt_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int zt_sendtext(struct ast_channel *c, const char *text);
+static int zt_call(struct ast_channel *ast, char *rdest, int timeout);
+static int zt_hangup(struct ast_channel *ast);
+static int zt_answer(struct ast_channel *ast);
+static struct ast_frame *zt_read(struct ast_channel *ast);
+static int zt_write(struct ast_channel *ast, struct ast_frame *frame);
+static struct ast_frame *zt_exception(struct ast_channel *ast);
+static int zt_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen);
+static int zt_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static int zt_setoption(struct ast_channel *chan, int option, void *data, int datalen);
+static int zt_func_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len);
+
+static const struct ast_channel_tech zap_tech = {
+ .type = "Zap",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_SLINEAR | AST_FORMAT_ULAW | AST_FORMAT_ALAW,
+ .requester = zt_request,
+ .send_digit_begin = zt_digit_begin,
+ .send_digit_end = zt_digit_end,
+ .send_text = zt_sendtext,
+ .call = zt_call,
+ .hangup = zt_hangup,
+ .answer = zt_answer,
+ .read = zt_read,
+ .write = zt_write,
+ .bridge = zt_bridge,
+ .exception = zt_exception,
+ .indicate = zt_indicate,
+ .fixup = zt_fixup,
+ .setoption = zt_setoption,
+ .func_channel_read = zt_func_read,
+};
+
+#ifdef HAVE_PRI
+#define GET_CHANNEL(p) ((p)->bearer ? (p)->bearer->channel : p->channel)
+#else
+#define GET_CHANNEL(p) ((p)->channel)
+#endif
+
+struct zt_pvt *round_robin[32];
+
+#ifdef HAVE_PRI
+static inline int pri_grab(struct zt_pvt *pvt, struct zt_pri *pri)
+{
+ int res;
+ /* Grab the lock first */
+ do {
+ res = ast_mutex_trylock(&pri->lock);
+ if (res) {
+ ast_mutex_unlock(&pvt->lock);
+ /* Release the lock and try again */
+ usleep(1);
+ ast_mutex_lock(&pvt->lock);
+ }
+ } while (res);
+ /* Then break the poll */
+ pthread_kill(pri->master, SIGURG);
+ return 0;
+}
+#endif
+
+#ifdef HAVE_SS7
+static inline void ss7_rel(struct zt_ss7 *ss7)
+{
+ ast_mutex_unlock(&ss7->lock);
+}
+
+static inline int ss7_grab(struct zt_pvt *pvt, struct zt_ss7 *pri)
+{
+ int res;
+ /* Grab the lock first */
+ do {
+ res = ast_mutex_trylock(&pri->lock);
+ if (res) {
+ ast_mutex_unlock(&pvt->lock);
+ /* Release the lock and try again */
+ usleep(1);
+ ast_mutex_lock(&pvt->lock);
+ }
+ } while (res);
+ /* Then break the poll */
+ pthread_kill(pri->master, SIGURG);
+ return 0;
+}
+#endif
+#define NUM_CADENCE_MAX 25
+static int num_cadence = 4;
+static int user_has_defined_cadences = 0;
+
+static struct zt_ring_cadence cadences[NUM_CADENCE_MAX] = {
+ { { 125, 125, 2000, 4000 } }, /*!< Quick chirp followed by normal ring */
+ { { 250, 250, 500, 1000, 250, 250, 500, 4000 } }, /*!< British style ring */
+ { { 125, 125, 125, 125, 125, 4000 } }, /*!< Three short bursts */
+ { { 1000, 500, 2500, 5000 } }, /*!< Long ring */
+};
+
+/*! \brief cidrings says in which pause to transmit the cid information, where the first pause
+ * is 1, the second pause is 2 and so on.
+ */
+
+static int cidrings[NUM_CADENCE_MAX] = {
+ 2, /*!< Right after first long ring */
+ 4, /*!< Right after long part */
+ 3, /*!< After third chirp */
+ 2, /*!< Second spell */
+};
+
+#define ISTRUNK(p) ((p->sig == SIG_FXSLS) || (p->sig == SIG_FXSKS) || \
+ (p->sig == SIG_FXSGS) || (p->sig == SIG_PRI))
+
+#define CANBUSYDETECT(p) (ISTRUNK(p) || (p->sig & (SIG_EM | SIG_EM_E1 | SIG_SF)) /* || (p->sig & __ZT_SIG_FXO) */)
+#define CANPROGRESSDETECT(p) (ISTRUNK(p) || (p->sig & (SIG_EM | SIG_EM_E1 | SIG_SF)) /* || (p->sig & __ZT_SIG_FXO) */)
+
+static int zt_get_index(struct ast_channel *ast, struct zt_pvt *p, int nullok)
+{
+ int res;
+ if (p->subs[0].owner == ast)
+ res = 0;
+ else if (p->subs[1].owner == ast)
+ res = 1;
+ else if (p->subs[2].owner == ast)
+ res = 2;
+ else {
+ res = -1;
+ if (!nullok)
+ ast_log(LOG_WARNING, "Unable to get index, and nullok is not asserted\n");
+ }
+ return res;
+}
+
+#ifdef HAVE_PRI
+static void wakeup_sub(struct zt_pvt *p, int a, struct zt_pri *pri)
+#else
+static void wakeup_sub(struct zt_pvt *p, int a, void *pri)
+#endif
+{
+#ifdef HAVE_PRI
+ if (pri)
+ ast_mutex_unlock(&pri->lock);
+#endif
+ for (;;) {
+ if (p->subs[a].owner) {
+ if (ast_channel_trylock(p->subs[a].owner)) {
+ ast_mutex_unlock(&p->lock);
+ usleep(1);
+ ast_mutex_lock(&p->lock);
+ } else {
+ ast_queue_frame(p->subs[a].owner, &ast_null_frame);
+ ast_channel_unlock(p->subs[a].owner);
+ break;
+ }
+ } else
+ break;
+ }
+#ifdef HAVE_PRI
+ if (pri)
+ ast_mutex_lock(&pri->lock);
+#endif
+}
+
+static void zap_queue_frame(struct zt_pvt *p, struct ast_frame *f, void *data)
+{
+#ifdef HAVE_PRI
+ struct zt_pri *pri = (struct zt_pri*) data;
+#endif
+#ifdef HAVE_SS7
+ struct zt_ss7 *ss7 = (struct zt_ss7*) data;
+#endif
+ /* We must unlock the PRI to avoid the possibility of a deadlock */
+#if defined(HAVE_PRI) || defined(HAVE_SS7)
+ if (data) {
+ switch (p->sig) {
+#ifdef HAVE_PRI
+ case SIG_BRI:
+ case SIG_BRI_PTMP:
+ case SIG_PRI:
+ ast_mutex_unlock(&pri->lock);
+ break;
+#endif
+#ifdef HAVE_SS7
+ case SIG_SS7:
+ ast_mutex_unlock(&ss7->lock);
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+#endif
+ for (;;) {
+ if (p->owner) {
+ if (ast_channel_trylock(p->owner)) {
+ ast_mutex_unlock(&p->lock);
+ usleep(1);
+ ast_mutex_lock(&p->lock);
+ } else {
+ ast_queue_frame(p->owner, f);
+ ast_channel_unlock(p->owner);
+ break;
+ }
+ } else
+ break;
+ }
+#if defined(HAVE_PRI) || defined(HAVE_SS7)
+ if (data) {
+ switch (p->sig) {
+#ifdef HAVE_PRI
+ case SIG_BRI:
+ case SIG_BRI_PTMP:
+ case SIG_PRI:
+ ast_mutex_lock(&pri->lock);
+ break;
+#endif
+#ifdef HAVE_SS7
+ case SIG_SS7:
+ ast_mutex_lock(&ss7->lock);
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+
+#endif
+}
+
+static int restore_gains(struct zt_pvt *p);
+
+static void swap_subs(struct zt_pvt *p, int a, int b)
+{
+ int tchan;
+ int tinthreeway;
+ struct ast_channel *towner;
+
+ ast_debug(1, "Swapping %d and %d\n", a, b);
+
+ tchan = p->subs[a].chan;
+ towner = p->subs[a].owner;
+ tinthreeway = p->subs[a].inthreeway;
+
+ p->subs[a].chan = p->subs[b].chan;
+ p->subs[a].owner = p->subs[b].owner;
+ p->subs[a].inthreeway = p->subs[b].inthreeway;
+
+ p->subs[b].chan = tchan;
+ p->subs[b].owner = towner;
+ p->subs[b].inthreeway = tinthreeway;
+
+ if (p->subs[a].owner)
+ ast_channel_set_fd(p->subs[a].owner, 0, p->subs[a].zfd);
+ if (p->subs[b].owner)
+ ast_channel_set_fd(p->subs[b].owner, 0, p->subs[b].zfd);
+ wakeup_sub(p, a, NULL);
+ wakeup_sub(p, b, NULL);
+}
+
+static int zt_open(char *fn)
+{
+ int fd;
+ int isnum;
+ int chan = 0;
+ int bs;
+ int x;
+ isnum = 1;
+ for (x = 0; x < strlen(fn); x++) {
+ if (!isdigit(fn[x])) {
+ isnum = 0;
+ break;
+ }
+ }
+ if (isnum) {
+ chan = atoi(fn);
+ if (chan < 1) {
+ ast_log(LOG_WARNING, "Invalid channel number '%s'\n", fn);
+ return -1;
+ }
+ fn = "/dev/zap/channel";
+ }
+ fd = open(fn, O_RDWR | O_NONBLOCK);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Unable to open '%s': %s\n", fn, strerror(errno));
+ return -1;
+ }
+ if (chan) {
+ if (ioctl(fd, ZT_SPECIFY, &chan)) {
+ x = errno;
+ close(fd);
+ errno = x;
+ ast_log(LOG_WARNING, "Unable to specify channel %d: %s\n", chan, strerror(errno));
+ return -1;
+ }
+ }
+ bs = READ_SIZE;
+ if (ioctl(fd, ZT_SET_BLOCKSIZE, &bs) == -1) {
+ ast_log(LOG_WARNING, "Unable to set blocksize '%d': %s\n", bs, strerror(errno));
+ x = errno;
+ close(fd);
+ errno = x;
+ return -1;
+ }
+ return fd;
+}
+
+static void zt_close(int fd)
+{
+ if (fd > 0)
+ close(fd);
+}
+
+static int zt_setlinear(int zfd, int linear)
+{
+ int res;
+ res = ioctl(zfd, ZT_SETLINEAR, &linear);
+ if (res)
+ return res;
+ return 0;
+}
+
+
+static int alloc_sub(struct zt_pvt *p, int x)
+{
+ ZT_BUFFERINFO bi;
+ int res;
+ if (p->subs[x].zfd < 0) {
+ p->subs[x].zfd = zt_open("/dev/zap/pseudo");
+ if (p->subs[x].zfd > -1) {
+ res = ioctl(p->subs[x].zfd, ZT_GET_BUFINFO, &bi);
+ if (!res) {
+ bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
+ bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
+ bi.numbufs = numbufs;
+ res = ioctl(p->subs[x].zfd, ZT_SET_BUFINFO, &bi);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set buffer policy on channel %d\n", x);
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to check buffer policy on channel %d\n", x);
+ if (ioctl(p->subs[x].zfd, ZT_CHANNO, &p->subs[x].chan) == 1) {
+ ast_log(LOG_WARNING, "Unable to get channel number for pseudo channel on FD %d\n", p->subs[x].zfd);
+ zt_close(p->subs[x].zfd);
+ p->subs[x].zfd = -1;
+ return -1;
+ }
+ ast_debug(1, "Allocated %s subchannel on FD %d channel %d\n", subnames[x], p->subs[x].zfd, p->subs[x].chan);
+ return 0;
+ } else
+ ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
+ return -1;
+ }
+ ast_log(LOG_WARNING, "%s subchannel of %d already in use\n", subnames[x], p->channel);
+ return -1;
+}
+
+static int unalloc_sub(struct zt_pvt *p, int x)
+{
+ if (!x) {
+ ast_log(LOG_WARNING, "Trying to unalloc the real channel %d?!?\n", p->channel);
+ return -1;
+ }
+ ast_debug(1, "Released sub %d of channel %d\n", x, p->channel);
+ if (p->subs[x].zfd > -1) {
+ zt_close(p->subs[x].zfd);
+ }
+ p->subs[x].zfd = -1;
+ p->subs[x].linear = 0;
+ p->subs[x].chan = 0;
+ p->subs[x].owner = NULL;
+ p->subs[x].inthreeway = 0;
+ p->polarity = POLARITY_IDLE;
+ memset(&p->subs[x].curconf, 0, sizeof(p->subs[x].curconf));
+ return 0;
+}
+
+static int digit_to_dtmfindex(char digit)
+{
+ if (isdigit(digit))
+ return ZT_TONE_DTMF_BASE + (digit - '0');
+ else if (digit >= 'A' && digit <= 'D')
+ return ZT_TONE_DTMF_A + (digit - 'A');
+ else if (digit >= 'a' && digit <= 'd')
+ return ZT_TONE_DTMF_A + (digit - 'a');
+ else if (digit == '*')
+ return ZT_TONE_DTMF_s;
+ else if (digit == '#')
+ return ZT_TONE_DTMF_p;
+ else
+ return -1;
+}
+
+static int zt_digit_begin(struct ast_channel *chan, char digit)
+{
+ struct zt_pvt *pvt;
+ int index;
+ int dtmf = -1;
+
+ pvt = chan->tech_pvt;
+
+ ast_mutex_lock(&pvt->lock);
+
+ index = zt_get_index(chan, pvt, 0);
+
+ if ((index != SUB_REAL) || !pvt->owner)
+ goto out;
+
+#ifdef HAVE_PRI
+ if (((pvt->sig == SIG_PRI) || (pvt->sig == SIG_BRI) || (pvt->sig == SIG_BRI_PTMP))
+ && (chan->_state == AST_STATE_DIALING) && !pvt->proceeding) {
+ if (pvt->setup_ack) {
+ if (!pri_grab(pvt, pvt->pri)) {
+ pri_information(pvt->pri->pri, pvt->call, digit);
+ pri_rel(pvt->pri);
+ } else
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", pvt->span);
+ } else if (strlen(pvt->dialdest) < sizeof(pvt->dialdest) - 1) {
+ int res;
+ ast_debug(1, "Queueing digit '%c' since setup_ack not yet received\n", digit);
+ res = strlen(pvt->dialdest);
+ pvt->dialdest[res++] = digit;
+ pvt->dialdest[res] = '\0';
+ }
+ goto out;
+ }
+#endif
+ if ((dtmf = digit_to_dtmfindex(digit)) == -1)
+ goto out;
+
+ if (pvt->pulse || ioctl(pvt->subs[SUB_REAL].zfd, ZT_SENDTONE, &dtmf)) {
+ int res;
+ ZT_DIAL_OPERATION zo = {
+ .op = ZT_DIAL_OP_APPEND,
+ };
+
+ zo.dialstr[0] = 'T';
+ zo.dialstr[1] = digit;
+ zo.dialstr[2] = '\0';
+ if ((res = ioctl(pvt->subs[SUB_REAL].zfd, ZT_DIAL, &zo)))
+ ast_log(LOG_WARNING, "Couldn't dial digit %c\n", digit);
+ else
+ pvt->dialing = 1;
+ } else {
+ ast_debug(1, "Started VLDTMF digit '%c'\n", digit);
+ pvt->dialing = 1;
+ pvt->begindigit = digit;
+ }
+
+out:
+ ast_mutex_unlock(&pvt->lock);
+
+ return 0;
+}
+
+static int zt_digit_end(struct ast_channel *chan, char digit, unsigned int duration)
+{
+ struct zt_pvt *pvt;
+ int res = 0;
+ int index;
+ int x;
+
+ pvt = chan->tech_pvt;
+
+ ast_mutex_lock(&pvt->lock);
+
+ index = zt_get_index(chan, pvt, 0);
+
+ if ((index != SUB_REAL) || !pvt->owner || pvt->pulse)
+ goto out;
+
+#ifdef HAVE_PRI
+ /* This means that the digit was already sent via PRI signalling */
+ if (((pvt->sig == SIG_PRI) || (pvt->sig == SIG_BRI) || (pvt->sig == SIG_BRI_PTMP))
+ && !pvt->begindigit)
+ goto out;
+#endif
+
+ if (pvt->begindigit) {
+ x = -1;
+ ast_debug(1, "Ending VLDTMF digit '%c'\n", digit);
+ res = ioctl(pvt->subs[SUB_REAL].zfd, ZT_SENDTONE, &x);
+ pvt->dialing = 0;
+ pvt->begindigit = 0;
+ }
+
+out:
+ ast_mutex_unlock(&pvt->lock);
+
+ return res;
+}
+
+static char *events[] = {
+ "No event",
+ "On hook",
+ "Ring/Answered",
+ "Wink/Flash",
+ "Alarm",
+ "No more alarm",
+ "HDLC Abort",
+ "HDLC Overrun",
+ "HDLC Bad FCS",
+ "Dial Complete",
+ "Ringer On",
+ "Ringer Off",
+ "Hook Transition Complete",
+ "Bits Changed",
+ "Pulse Start",
+ "Timer Expired",
+ "Timer Ping",
+ "Polarity Reversal",
+ "Ring Begin",
+};
+
+static struct {
+ int alarm;
+ char *name;
+} alarms[] = {
+ { ZT_ALARM_RED, "Red Alarm" },
+ { ZT_ALARM_YELLOW, "Yellow Alarm" },
+ { ZT_ALARM_BLUE, "Blue Alarm" },
+ { ZT_ALARM_RECOVER, "Recovering" },
+ { ZT_ALARM_LOOPBACK, "Loopback" },
+ { ZT_ALARM_NOTOPEN, "Not Open" },
+ { ZT_ALARM_NONE, "None" },
+};
+
+static char *alarm2str(int alarm)
+{
+ int x;
+ for (x = 0; x < sizeof(alarms) / sizeof(alarms[0]); x++) {
+ if (alarms[x].alarm & alarm)
+ return alarms[x].name;
+ }
+ return alarm ? "Unknown Alarm" : "No Alarm";
+}
+
+static char *event2str(int event)
+{
+ static char buf[256];
+ if ((event < (sizeof(events) / sizeof(events[0]))) && (event > -1))
+ return events[event];
+ sprintf(buf, "Event %d", event); /* safe */
+ return buf;
+}
+
+#ifdef HAVE_PRI
+static char *dialplan2str(int dialplan)
+{
+ if (dialplan == -1 || dialplan == -2) {
+ return("Dynamically set dialplan in ISDN");
+ }
+ return (pri_plan2str(dialplan));
+}
+#endif
+
+static char *zap_sig2str(int sig)
+{
+ static char buf[256];
+ switch (sig) {
+ case SIG_EM:
+ return "E & M Immediate";
+ case SIG_EMWINK:
+ return "E & M Wink";
+ case SIG_EM_E1:
+ return "E & M E1";
+ case SIG_FEATD:
+ return "Feature Group D (DTMF)";
+ case SIG_FEATDMF:
+ return "Feature Group D (MF)";
+ case SIG_FEATDMF_TA:
+ return "Feature Groud D (MF) Tandem Access";
+ case SIG_FEATB:
+ return "Feature Group B (MF)";
+ case SIG_E911:
+ return "E911 (MF)";
+ case SIG_FGC_CAMA:
+ return "FGC/CAMA (Dialpulse)";
+ case SIG_FGC_CAMAMF:
+ return "FGC/CAMA (MF)";
+ case SIG_FXSLS:
+ return "FXS Loopstart";
+ case SIG_FXSGS:
+ return "FXS Groundstart";
+ case SIG_FXSKS:
+ return "FXS Kewlstart";
+ case SIG_FXOLS:
+ return "FXO Loopstart";
+ case SIG_FXOGS:
+ return "FXO Groundstart";
+ case SIG_FXOKS:
+ return "FXO Kewlstart";
+ case SIG_PRI:
+ return "ISDN PRI";
+ case SIG_BRI:
+ return "ISDN BRI Point to Point";
+ case SIG_BRI_PTMP:
+ return "ISDN BRI Point to MultiPoint";
+ case SIG_SS7:
+ return "SS7";
+ case SIG_SF:
+ return "SF (Tone) Immediate";
+ case SIG_SFWINK:
+ return "SF (Tone) Wink";
+ case SIG_SF_FEATD:
+ return "SF (Tone) with Feature Group D (DTMF)";
+ case SIG_SF_FEATDMF:
+ return "SF (Tone) with Feature Group D (MF)";
+ case SIG_SF_FEATB:
+ return "SF (Tone) with Feature Group B (MF)";
+ case SIG_GR303FXOKS:
+ return "GR-303 with FXOKS";
+ case SIG_GR303FXSKS:
+ return "GR-303 with FXSKS";
+ case 0:
+ return "Pseudo";
+ default:
+ snprintf(buf, sizeof(buf), "Unknown signalling %d", sig);
+ return buf;
+ }
+}
+
+#define sig2str zap_sig2str
+
+static int conf_add(struct zt_pvt *p, struct zt_subchannel *c, int index, int slavechannel)
+{
+ /* If the conference already exists, and we're already in it
+ don't bother doing anything */
+ ZT_CONFINFO zi;
+
+ memset(&zi, 0, sizeof(zi));
+ zi.chan = 0;
+
+ if (slavechannel > 0) {
+ /* If we have only one slave, do a digital mon */
+ zi.confmode = ZT_CONF_DIGITALMON;
+ zi.confno = slavechannel;
+ } else {
+ if (!index) {
+ /* Real-side and pseudo-side both participate in conference */
+ zi.confmode = ZT_CONF_REALANDPSEUDO | ZT_CONF_TALKER | ZT_CONF_LISTENER |
+ ZT_CONF_PSEUDO_TALKER | ZT_CONF_PSEUDO_LISTENER;
+ } else
+ zi.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
+ zi.confno = p->confno;
+ }
+ if ((zi.confno == c->curconf.confno) && (zi.confmode == c->curconf.confmode))
+ return 0;
+ if (c->zfd < 0)
+ return 0;
+ if (ioctl(c->zfd, ZT_SETCONF, &zi)) {
+ ast_log(LOG_WARNING, "Failed to add %d to conference %d/%d\n", c->zfd, zi.confmode, zi.confno);
+ return -1;
+ }
+ if (slavechannel < 1) {
+ p->confno = zi.confno;
+ }
+ memcpy(&c->curconf, &zi, sizeof(c->curconf));
+ ast_debug(1, "Added %d to conference %d/%d\n", c->zfd, c->curconf.confmode, c->curconf.confno);
+ return 0;
+}
+
+static int isourconf(struct zt_pvt *p, struct zt_subchannel *c)
+{
+ /* If they're listening to our channel, they're ours */
+ if ((p->channel == c->curconf.confno) && (c->curconf.confmode == ZT_CONF_DIGITALMON))
+ return 1;
+ /* If they're a talker on our (allocated) conference, they're ours */
+ if ((p->confno > 0) && (p->confno == c->curconf.confno) && (c->curconf.confmode & ZT_CONF_TALKER))
+ return 1;
+ return 0;
+}
+
+static int conf_del(struct zt_pvt *p, struct zt_subchannel *c, int index)
+{
+ ZT_CONFINFO zi;
+ if (/* Can't delete if there's no zfd */
+ (c->zfd < 0) ||
+ /* Don't delete from the conference if it's not our conference */
+ !isourconf(p, c)
+ /* Don't delete if we don't think it's conferenced at all (implied) */
+ ) return 0;
+ memset(&zi, 0, sizeof(zi));
+ zi.chan = 0;
+ zi.confno = 0;
+ zi.confmode = 0;
+ if (ioctl(c->zfd, ZT_SETCONF, &zi)) {
+ ast_log(LOG_WARNING, "Failed to drop %d from conference %d/%d\n", c->zfd, c->curconf.confmode, c->curconf.confno);
+ return -1;
+ }
+ ast_debug(1, "Removed %d from conference %d/%d\n", c->zfd, c->curconf.confmode, c->curconf.confno);
+ memcpy(&c->curconf, &zi, sizeof(c->curconf));
+ return 0;
+}
+
+static int isslavenative(struct zt_pvt *p, struct zt_pvt **out)
+{
+ int x;
+ int useslavenative;
+ struct zt_pvt *slave = NULL;
+ /* Start out optimistic */
+ useslavenative = 1;
+ /* Update conference state in a stateless fashion */
+ for (x = 0; x < 3; x++) {
+ /* Any three-way calling makes slave native mode *definitely* out
+ of the question */
+ if ((p->subs[x].zfd > -1) && p->subs[x].inthreeway)
+ useslavenative = 0;
+ }
+ /* If we don't have any 3-way calls, check to see if we have
+ precisely one slave */
+ if (useslavenative) {
+ for (x = 0; x < MAX_SLAVES; x++) {
+ if (p->slaves[x]) {
+ if (slave) {
+ /* Whoops already have a slave! No
+ slave native and stop right away */
+ slave = NULL;
+ useslavenative = 0;
+ break;
+ } else {
+ /* We have one slave so far */
+ slave = p->slaves[x];
+ }
+ }
+ }
+ }
+ /* If no slave, slave native definitely out */
+ if (!slave)
+ useslavenative = 0;
+ else if (slave->law != p->law) {
+ useslavenative = 0;
+ slave = NULL;
+ }
+ if (out)
+ *out = slave;
+ return useslavenative;
+}
+
+static int reset_conf(struct zt_pvt *p)
+{
+ ZT_CONFINFO zi;
+ memset(&zi, 0, sizeof(zi));
+ p->confno = -1;
+ memset(&p->subs[SUB_REAL].curconf, 0, sizeof(p->subs[SUB_REAL].curconf));
+ if (p->subs[SUB_REAL].zfd > -1) {
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_SETCONF, &zi))
+ ast_log(LOG_WARNING, "Failed to reset conferencing on channel %d!\n", p->channel);
+ }
+ return 0;
+}
+
+static int update_conf(struct zt_pvt *p)
+{
+ int needconf = 0;
+ int x;
+ int useslavenative;
+ struct zt_pvt *slave = NULL;
+
+ useslavenative = isslavenative(p, &slave);
+ /* Start with the obvious, general stuff */
+ for (x = 0; x < 3; x++) {
+ /* Look for three way calls */
+ if ((p->subs[x].zfd > -1) && p->subs[x].inthreeway) {
+ conf_add(p, &p->subs[x], x, 0);
+ needconf++;
+ } else {
+ conf_del(p, &p->subs[x], x);
+ }
+ }
+ /* If we have a slave, add him to our conference now. or DAX
+ if this is slave native */
+ for (x = 0; x < MAX_SLAVES; x++) {
+ if (p->slaves[x]) {
+ if (useslavenative)
+ conf_add(p, &p->slaves[x]->subs[SUB_REAL], SUB_REAL, GET_CHANNEL(p));
+ else {
+ conf_add(p, &p->slaves[x]->subs[SUB_REAL], SUB_REAL, 0);
+ needconf++;
+ }
+ }
+ }
+ /* If we're supposed to be in there, do so now */
+ if (p->inconference && !p->subs[SUB_REAL].inthreeway) {
+ if (useslavenative)
+ conf_add(p, &p->subs[SUB_REAL], SUB_REAL, GET_CHANNEL(slave));
+ else {
+ conf_add(p, &p->subs[SUB_REAL], SUB_REAL, 0);
+ needconf++;
+ }
+ }
+ /* If we have a master, add ourselves to his conference */
+ if (p->master) {
+ if (isslavenative(p->master, NULL)) {
+ conf_add(p->master, &p->subs[SUB_REAL], SUB_REAL, GET_CHANNEL(p->master));
+ } else {
+ conf_add(p->master, &p->subs[SUB_REAL], SUB_REAL, 0);
+ }
+ }
+ if (!needconf) {
+ /* Nobody is left (or should be left) in our conference.
+ Kill it. */
+ p->confno = -1;
+ }
+ ast_debug(1, "Updated conferencing on %d, with %d conference users\n", p->channel, needconf);
+ return 0;
+}
+
+static void zt_enable_ec(struct zt_pvt *p)
+{
+ int x;
+ int res;
+ if (!p)
+ return;
+ if (p->echocanon) {
+ ast_debug(1, "Echo cancellation already on\n");
+ return;
+ }
+ if (p->digital) {
+ ast_debug(1, "Echo cancellation isn't required on digital connection\n");
+ return;
+ }
+#if defined(HAVE_ZAPTEL_ECHOCANPARAMS)
+ if (p->echocancel.head.tap_length) {
+#else
+ if (p->echocancel) {
+#endif
+ if ((p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP) || (p->sig == SIG_PRI) || (p->sig == SIG_SS7)) {
+ x = 1;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_AUDIOMODE, &x);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to enable audio mode on channel %d (%s)\n", p->channel, strerror(errno));
+ }
+#if defined(HAVE_ZAPTEL_ECHOCANPARAMS)
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_ECHOCANCEL_PARAMS, &p->echocancel);
+#else
+ x = p->echocancel;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_ECHOCANCEL, &x);
+#endif
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to enable echo cancellation on channel %d (%s)\n", p->channel, strerror(errno));
+ } else {
+ p->echocanon = 1;
+ ast_debug(1, "Enabled echo cancellation on channel %d\n", p->channel);
+ }
+ } else
+ ast_debug(1, "No echo cancellation requested\n");
+}
+
+static void zt_train_ec(struct zt_pvt *p)
+{
+ int x;
+ int res;
+
+ if (p && p->echocanon && p->echotraining) {
+ x = p->echotraining;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_ECHOTRAIN, &x);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to request echo training on channel %d\n", p->channel);
+ else
+ ast_debug(1, "Engaged echo training on channel %d\n", p->channel);
+ } else {
+ ast_debug(1, "No echo training requested\n");
+ }
+}
+
+static void zt_disable_ec(struct zt_pvt *p)
+{
+ int res;
+
+ if (p->echocanon) {
+#if defined(HAVE_ZAPTEL_ECHOCANPARAMS)
+ struct zt_echocanparams ecp = { .tap_length = 0 };
+
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_ECHOCANCEL_PARAMS, &ecp);
+#else
+ int x = 0;
+
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_ECHOCANCEL, &x);
+#endif
+
+ if (res)
+ ast_log(LOG_WARNING, "Unable to disable echo cancellation on channel %d\n", p->channel);
+ else
+ ast_debug(1, "Disabled echo cancellation on channel %d\n", p->channel);
+ }
+
+ p->echocanon = 0;
+}
+
+static void fill_txgain(struct zt_gains *g, float gain, int law)
+{
+ int j;
+ int k;
+ float linear_gain = pow(10.0, gain / 20.0);
+
+ switch (law) {
+ case ZT_LAW_ALAW:
+ for (j = 0; j < (sizeof(g->txgain) / sizeof(g->txgain[0])); j++) {
+ if (gain) {
+ k = (int) (((float) AST_ALAW(j)) * linear_gain);
+ if (k > 32767) k = 32767;
+ if (k < -32767) k = -32767;
+ g->txgain[j] = AST_LIN2A(k);
+ } else {
+ g->txgain[j] = j;
+ }
+ }
+ break;
+ case ZT_LAW_MULAW:
+ for (j = 0; j < (sizeof(g->txgain) / sizeof(g->txgain[0])); j++) {
+ if (gain) {
+ k = (int) (((float) AST_MULAW(j)) * linear_gain);
+ if (k > 32767) k = 32767;
+ if (k < -32767) k = -32767;
+ g->txgain[j] = AST_LIN2MU(k);
+ } else {
+ g->txgain[j] = j;
+ }
+ }
+ break;
+ }
+}
+
+static void fill_rxgain(struct zt_gains *g, float gain, int law)
+{
+ int j;
+ int k;
+ float linear_gain = pow(10.0, gain / 20.0);
+
+ switch (law) {
+ case ZT_LAW_ALAW:
+ for (j = 0; j < (sizeof(g->rxgain) / sizeof(g->rxgain[0])); j++) {
+ if (gain) {
+ k = (int) (((float) AST_ALAW(j)) * linear_gain);
+ if (k > 32767) k = 32767;
+ if (k < -32767) k = -32767;
+ g->rxgain[j] = AST_LIN2A(k);
+ } else {
+ g->rxgain[j] = j;
+ }
+ }
+ break;
+ case ZT_LAW_MULAW:
+ for (j = 0; j < (sizeof(g->rxgain) / sizeof(g->rxgain[0])); j++) {
+ if (gain) {
+ k = (int) (((float) AST_MULAW(j)) * linear_gain);
+ if (k > 32767) k = 32767;
+ if (k < -32767) k = -32767;
+ g->rxgain[j] = AST_LIN2MU(k);
+ } else {
+ g->rxgain[j] = j;
+ }
+ }
+ break;
+ }
+}
+
+static int set_actual_txgain(int fd, int chan, float gain, int law)
+{
+ struct zt_gains g;
+ int res;
+
+ memset(&g, 0, sizeof(g));
+ g.chan = chan;
+ res = ioctl(fd, ZT_GETGAINS, &g);
+ if (res) {
+ ast_debug(1, "Failed to read gains: %s\n", strerror(errno));
+ return res;
+ }
+
+ fill_txgain(&g, gain, law);
+
+ return ioctl(fd, ZT_SETGAINS, &g);
+}
+
+static int set_actual_rxgain(int fd, int chan, float gain, int law)
+{
+ struct zt_gains g;
+ int res;
+
+ memset(&g, 0, sizeof(g));
+ g.chan = chan;
+ res = ioctl(fd, ZT_GETGAINS, &g);
+ if (res) {
+ ast_debug(1, "Failed to read gains: %s\n", strerror(errno));
+ return res;
+ }
+
+ fill_rxgain(&g, gain, law);
+
+ return ioctl(fd, ZT_SETGAINS, &g);
+}
+
+static int set_actual_gain(int fd, int chan, float rxgain, float txgain, int law)
+{
+ return set_actual_txgain(fd, chan, txgain, law) | set_actual_rxgain(fd, chan, rxgain, law);
+}
+
+static int bump_gains(struct zt_pvt *p)
+{
+ int res;
+
+ /* Bump receive gain by value stored in cid_rxgain */
+ res = set_actual_gain(p->subs[SUB_REAL].zfd, 0, p->rxgain + p->cid_rxgain, p->txgain, p->law);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to bump gain: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int restore_gains(struct zt_pvt *p)
+{
+ int res;
+
+ res = set_actual_gain(p->subs[SUB_REAL].zfd, 0, p->rxgain, p->txgain, p->law);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to restore gains: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static inline int zt_set_hook(int fd, int hs)
+{
+ int x, res;
+
+ x = hs;
+ res = ioctl(fd, ZT_HOOK, &x);
+
+ if (res < 0) {
+ if (errno == EINPROGRESS)
+ return 0;
+ ast_log(LOG_WARNING, "zt hook failed: %s\n", strerror(errno));
+ }
+
+ return res;
+}
+
+static inline int zt_confmute(struct zt_pvt *p, int muted)
+{
+ int x, y, res;
+ x = muted;
+ if ((p->sig == SIG_PRI) || (p->sig == SIG_SS7) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP)) {
+ y = 1;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_AUDIOMODE, &y);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set audio mode on '%d'\n", p->channel);
+ }
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_CONFMUTE, &x);
+ if (res < 0)
+ ast_log(LOG_WARNING, "zt confmute(%d) failed on channel %d: %s\n", muted, p->channel, strerror(errno));
+ return res;
+}
+
+static int save_conference(struct zt_pvt *p)
+{
+ struct zt_confinfo c;
+ int res;
+ if (p->saveconf.confmode) {
+ ast_log(LOG_WARNING, "Can't save conference -- already in use\n");
+ return -1;
+ }
+ p->saveconf.chan = 0;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_GETCONF, &p->saveconf);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to get conference info: %s\n", strerror(errno));
+ p->saveconf.confmode = 0;
+ return -1;
+ }
+ c.chan = 0;
+ c.confno = 0;
+ c.confmode = ZT_CONF_NORMAL;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_SETCONF, &c);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to set conference info: %s\n", strerror(errno));
+ return -1;
+ }
+ ast_debug(1, "Disabled conferencing\n");
+ return 0;
+}
+
+/*!
+ * \brief Send MWI state change
+ *
+ * \arg mailbox_full This is the mailbox associated with the FXO line that the
+ * MWI state has changed on.
+ * \arg thereornot This argument should simply be set to 1 or 0, to indicate
+ * whether there are messages waiting or not.
+ *
+ * \return nothing
+ *
+ * This function does two things:
+ *
+ * 1) It generates an internal Asterisk event notifying any other module that
+ * cares about MWI that the state of a mailbox has changed.
+ *
+ * 2) It runs the script specified by the mwimonitornotify option to allow
+ * some custom handling of the state change.
+ */
+static void notify_message(char *mailbox_full, int thereornot)
+{
+ char s[sizeof(mwimonitornotify) + 80];
+ struct ast_event *event;
+ char *mailbox, *context;
+
+ /* Strip off @default */
+ context = mailbox = ast_strdupa(mailbox_full);
+ strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+
+ if (!(event = ast_event_new(AST_EVENT_MWI,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
+ AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, thereornot,
+ AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, thereornot,
+ AST_EVENT_IE_END))) {
+ return;
+ }
+
+ ast_event_queue_and_cache(event,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR,
+ AST_EVENT_IE_END);
+
+ if (!ast_strlen_zero(mailbox) && !ast_strlen_zero(mwimonitornotify)) {
+ snprintf(s, sizeof(s), "%s %s %d", mwimonitornotify, mailbox, thereornot);
+ ast_safe_system(s);
+ }
+}
+
+static int restore_conference(struct zt_pvt *p)
+{
+ int res;
+ if (p->saveconf.confmode) {
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_SETCONF, &p->saveconf);
+ p->saveconf.confmode = 0;
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to restore conference info: %s\n", strerror(errno));
+ return -1;
+ }
+ }
+ ast_debug(1, "Restored conferencing\n");
+ return 0;
+}
+
+static int send_callerid(struct zt_pvt *p);
+
+static int send_cwcidspill(struct zt_pvt *p)
+{
+ p->callwaitcas = 0;
+ p->cidcwexpire = 0;
+ if (!(p->cidspill = ast_malloc(MAX_CALLERID_SIZE)))
+ return -1;
+ p->cidlen = ast_callerid_callwaiting_generate(p->cidspill, p->callwait_name, p->callwait_num, AST_LAW(p));
+ /* Make sure we account for the end */
+ p->cidlen += READ_SIZE * 4;
+ p->cidpos = 0;
+ send_callerid(p);
+ ast_verb(3, "CPE supports Call Waiting Caller*ID. Sending '%s/%s'\n", p->callwait_name, p->callwait_num);
+ return 0;
+}
+
+static int has_voicemail(struct zt_pvt *p)
+{
+ int new_msgs;
+ struct ast_event *event;
+ char *mailbox, *context;
+
+ mailbox = context = ast_strdupa(p->mailbox);
+ strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+
+ event = ast_event_get_cached(AST_EVENT_MWI,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
+ AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_EXISTS,
+ AST_EVENT_IE_END);
+
+ if (event) {
+ new_msgs = ast_event_get_ie_uint(event, AST_EVENT_IE_NEWMSGS);
+ ast_event_destroy(event);
+ } else
+ new_msgs = ast_app_has_voicemail(p->mailbox, NULL);
+
+ return new_msgs;
+}
+
+static int send_callerid(struct zt_pvt *p)
+{
+ /* Assumes spill in p->cidspill, p->cidlen in length and we're p->cidpos into it */
+ int res;
+ /* Take out of linear mode if necessary */
+ if (p->subs[SUB_REAL].linear) {
+ p->subs[SUB_REAL].linear = 0;
+ zt_setlinear(p->subs[SUB_REAL].zfd, 0);
+ }
+ while (p->cidpos < p->cidlen) {
+ res = write(p->subs[SUB_REAL].zfd, p->cidspill + p->cidpos, p->cidlen - p->cidpos);
+ if (res < 0) {
+ if (errno == EAGAIN)
+ return 0;
+ else {
+ ast_log(LOG_WARNING, "write failed: %s\n", strerror(errno));
+ return -1;
+ }
+ }
+ if (!res)
+ return 0;
+ p->cidpos += res;
+ }
+ ast_free(p->cidspill);
+ p->cidspill = NULL;
+ if (p->callwaitcas) {
+ /* Wait for CID/CW to expire */
+ p->cidcwexpire = CIDCW_EXPIRE_SAMPLES;
+ } else
+ restore_conference(p);
+ return 0;
+}
+
+static int zt_callwait(struct ast_channel *ast)
+{
+ struct zt_pvt *p = ast->tech_pvt;
+ p->callwaitingrepeat = CALLWAITING_REPEAT_SAMPLES;
+ if (p->cidspill) {
+ ast_log(LOG_WARNING, "Spill already exists?!?\n");
+ ast_free(p->cidspill);
+ }
+ if (!(p->cidspill = ast_malloc(2400 /* SAS */ + 680 /* CAS */ + READ_SIZE * 4)))
+ return -1;
+ save_conference(p);
+ /* Silence */
+ memset(p->cidspill, 0x7f, 2400 + 600 + READ_SIZE * 4);
+ if (!p->callwaitrings && p->callwaitingcallerid) {
+ ast_gen_cas(p->cidspill, 1, 2400 + 680, AST_LAW(p));
+ p->callwaitcas = 1;
+ p->cidlen = 2400 + 680 + READ_SIZE * 4;
+ } else {
+ ast_gen_cas(p->cidspill, 1, 2400, AST_LAW(p));
+ p->callwaitcas = 0;
+ p->cidlen = 2400 + READ_SIZE * 4;
+ }
+ p->cidpos = 0;
+ send_callerid(p);
+
+ return 0;
+}
+
+#ifdef HAVE_SS7
+static unsigned char cid_pres2ss7pres(int cid_pres)
+{
+ return (cid_pres >> 5) & 0x03;
+}
+
+static unsigned char cid_pres2ss7screen(int cid_pres)
+{
+ return cid_pres & 0x03;
+}
+#endif
+
+static int zt_call(struct ast_channel *ast, char *rdest, int timeout)
+{
+ struct zt_pvt *p = ast->tech_pvt;
+ int x, res, index,mysig;
+ char *c, *n, *l;
+#ifdef HAVE_PRI
+ char *s = NULL;
+#endif
+ char dest[256]; /* must be same length as p->dialdest */
+ ast_mutex_lock(&p->lock);
+ ast_copy_string(dest, rdest, sizeof(dest));
+ ast_copy_string(p->dialdest, rdest, sizeof(p->dialdest));
+ if ((ast->_state == AST_STATE_BUSY)) {
+ p->subs[SUB_REAL].needbusy = 1;
+ ast_mutex_unlock(&p->lock);
+ return 0;
+ }
+ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "zt_call called on %s, neither down nor reserved\n", ast->name);
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ p->dialednone = 0;
+ if ((p->radio || (p->oprmode < 0))) /* if a radio channel, up immediately */
+ {
+ /* Special pseudo -- automatically up */
+ ast_setstate(ast, AST_STATE_UP);
+ ast_mutex_unlock(&p->lock);
+ return 0;
+ }
+ x = ZT_FLUSH_READ | ZT_FLUSH_WRITE;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_FLUSH, &x);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to flush input on channel %d\n", p->channel);
+ p->outgoing = 1;
+
+ set_actual_gain(p->subs[SUB_REAL].zfd, 0, p->rxgain, p->txgain, p->law);
+
+ mysig = p->sig;
+ if (p->outsigmod > -1)
+ mysig = p->outsigmod;
+
+ switch (mysig) {
+ case SIG_FXOLS:
+ case SIG_FXOGS:
+ case SIG_FXOKS:
+ if (p->owner == ast) {
+ /* Normal ring, on hook */
+
+ /* Don't send audio while on hook, until the call is answered */
+ p->dialing = 1;
+ if (p->use_callerid) {
+ /* Generate the Caller-ID spill if desired */
+ if (p->cidspill) {
+ ast_log(LOG_WARNING, "cidspill already exists??\n");
+ ast_free(p->cidspill);
+ }
+ p->callwaitcas = 0;
+ if ((p->cidspill = ast_malloc(MAX_CALLERID_SIZE))) {
+ p->cidlen = ast_callerid_generate(p->cidspill, ast->cid.cid_name, ast->cid.cid_num, AST_LAW(p));
+ p->cidpos = 0;
+ send_callerid(p);
+ }
+ }
+ /* Choose proper cadence */
+ if ((p->distinctivering > 0) && (p->distinctivering <= num_cadence)) {
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_SETCADENCE, &cadences[p->distinctivering - 1]))
+ ast_log(LOG_WARNING, "Unable to set distinctive ring cadence %d on '%s'\n", p->distinctivering, ast->name);
+ p->cidrings = cidrings[p->distinctivering - 1];
+ } else {
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_SETCADENCE, NULL))
+ ast_log(LOG_WARNING, "Unable to reset default ring on '%s'\n", ast->name);
+ p->cidrings = p->sendcalleridafter;
+ }
+
+ /* nick@dccinc.com 4/3/03 mods to allow for deferred dialing */
+ c = strchr(dest, '/');
+ if (c)
+ c++;
+ if (c && (strlen(c) < p->stripmsd)) {
+ ast_log(LOG_WARNING, "Number '%s' is shorter than stripmsd (%d)\n", c, p->stripmsd);
+ c = NULL;
+ }
+ if (c) {
+ p->dop.op = ZT_DIAL_OP_REPLACE;
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "Tw%s", c);
+ ast_debug(1, "FXO: setup deferred dialstring: %s\n", c);
+ } else {
+ p->dop.dialstr[0] = '\0';
+ }
+ x = ZT_RING;
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_HOOK, &x) && (errno != EINPROGRESS)) {
+ ast_log(LOG_WARNING, "Unable to ring phone: %s\n", strerror(errno));
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ p->dialing = 1;
+ } else {
+ /* Call waiting call */
+ p->callwaitrings = 0;
+ if (ast->cid.cid_num)
+ ast_copy_string(p->callwait_num, ast->cid.cid_num, sizeof(p->callwait_num));
+ else
+ p->callwait_num[0] = '\0';
+ if (ast->cid.cid_name)
+ ast_copy_string(p->callwait_name, ast->cid.cid_name, sizeof(p->callwait_name));
+ else
+ p->callwait_name[0] = '\0';
+ /* Call waiting tone instead */
+ if (zt_callwait(ast)) {
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ /* Make ring-back */
+ if (tone_zone_play_tone(p->subs[SUB_CALLWAIT].zfd, ZT_TONE_RINGTONE))
+ ast_log(LOG_WARNING, "Unable to generate call-wait ring-back on channel %s\n", ast->name);
+
+ }
+ n = ast->cid.cid_name;
+ l = ast->cid.cid_num;
+ if (l)
+ ast_copy_string(p->lastcid_num, l, sizeof(p->lastcid_num));
+ else
+ p->lastcid_num[0] = '\0';
+ if (n)
+ ast_copy_string(p->lastcid_name, n, sizeof(p->lastcid_name));
+ else
+ p->lastcid_name[0] = '\0';
+ ast_setstate(ast, AST_STATE_RINGING);
+ index = zt_get_index(ast, p, 0);
+ if (index > -1) {
+ p->subs[index].needringing = 1;
+ }
+ break;
+ case SIG_FXSLS:
+ case SIG_FXSGS:
+ case SIG_FXSKS:
+ case SIG_EMWINK:
+ case SIG_EM:
+ case SIG_EM_E1:
+ case SIG_FEATD:
+ case SIG_FEATDMF:
+ case SIG_E911:
+ case SIG_FGC_CAMA:
+ case SIG_FGC_CAMAMF:
+ case SIG_FEATB:
+ case SIG_SFWINK:
+ case SIG_SF:
+ case SIG_SF_FEATD:
+ case SIG_SF_FEATDMF:
+ case SIG_FEATDMF_TA:
+ case SIG_SF_FEATB:
+ c = strchr(dest, '/');
+ if (c)
+ c++;
+ else
+ c = "";
+ if (strlen(c) < p->stripmsd) {
+ ast_log(LOG_WARNING, "Number '%s' is shorter than stripmsd (%d)\n", c, p->stripmsd);
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+#ifdef HAVE_PRI
+ /* Start the trunk, if not GR-303 */
+ if (!p->pri) {
+#endif
+ x = ZT_START;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_HOOK, &x);
+ if (res < 0) {
+ if (errno != EINPROGRESS) {
+ ast_log(LOG_WARNING, "Unable to start channel: %s\n", strerror(errno));
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ }
+#ifdef HAVE_PRI
+ }
+#endif
+ ast_debug(1, "Dialing '%s'\n", c);
+ p->dop.op = ZT_DIAL_OP_REPLACE;
+
+ c += p->stripmsd;
+
+ switch (mysig) {
+ case SIG_FEATD:
+ l = ast->cid.cid_num;
+ if (l)
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "T*%s*%s*", l, c);
+ else
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "T**%s*", c);
+ break;
+ case SIG_FEATDMF:
+ l = ast->cid.cid_num;
+ if (l)
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*00%s#*%s#", l, c);
+ else
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*02#*%s#", c);
+ break;
+ case SIG_FEATDMF_TA:
+ {
+ const char *cic, *ozz;
+
+ /* If you have to go through a Tandem Access point you need to use this */
+ ozz = pbx_builtin_getvar_helper(p->owner, "FEATDMF_OZZ");
+ if (!ozz)
+ ozz = defaultozz;
+ cic = pbx_builtin_getvar_helper(p->owner, "FEATDMF_CIC");
+ if (!cic)
+ cic = defaultcic;
+ if (!ozz || !cic) {
+ ast_log(LOG_WARNING, "Unable to dial channel of type feature group D MF tandem access without CIC or OZZ set\n");
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*%s%s#", ozz, cic);
+ snprintf(p->finaldial, sizeof(p->finaldial), "M*%s#", c);
+ p->whichwink = 0;
+ }
+ break;
+ case SIG_E911:
+ ast_copy_string(p->dop.dialstr, "M*911#", sizeof(p->dop.dialstr));
+ break;
+ case SIG_FGC_CAMA:
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "P%s", c);
+ break;
+ case SIG_FGC_CAMAMF:
+ case SIG_FEATB:
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*%s#", c);
+ break;
+ default:
+ if (p->pulse)
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "P%sw", c);
+ else
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "T%sw", c);
+ break;
+ }
+
+ if (p->echotraining && (strlen(p->dop.dialstr) > 4)) {
+ memset(p->echorest, 'w', sizeof(p->echorest) - 1);
+ strcpy(p->echorest + (p->echotraining / 400) + 1, p->dop.dialstr + strlen(p->dop.dialstr) - 2);
+ p->echorest[sizeof(p->echorest) - 1] = '\0';
+ p->echobreak = 1;
+ p->dop.dialstr[strlen(p->dop.dialstr)-2] = '\0';
+ } else
+ p->echobreak = 0;
+ if (!res) {
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_DIAL, &p->dop)) {
+ x = ZT_ONHOOK;
+ ioctl(p->subs[SUB_REAL].zfd, ZT_HOOK, &x);
+ ast_log(LOG_WARNING, "Dialing failed on channel %d: %s\n", p->channel, strerror(errno));
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ } else
+ ast_debug(1, "Deferring dialing...\n");
+
+ p->dialing = 1;
+ if (ast_strlen_zero(c))
+ p->dialednone = 1;
+ ast_setstate(ast, AST_STATE_DIALING);
+ break;
+ case 0:
+ /* Special pseudo -- automatically up*/
+ ast_setstate(ast, AST_STATE_UP);
+ break;
+ case SIG_PRI:
+ case SIG_BRI:
+ case SIG_BRI_PTMP:
+ case SIG_SS7:
+ /* We'll get it in a moment -- but use dialdest to store pre-setup_ack digits */
+ p->dialdest[0] = '\0';
+ break;
+ default:
+ ast_debug(1, "not yet implemented\n");
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+#ifdef HAVE_SS7
+ if (p->ss7) {
+ char ss7_called_nai;
+ int called_nai_strip;
+ char ss7_calling_nai;
+ int calling_nai_strip;
+ const char *charge_str = NULL;
+ const char *gen_address = NULL;
+ const char *gen_digits = NULL;
+ const char *gen_dig_type = NULL;
+ const char *gen_dig_scheme = NULL;
+ const char *jip_digits = NULL;
+ const char *lspi_ident = NULL;
+ const char *rlt_flag = NULL;
+ const char *call_ref_id = NULL;
+ const char *call_ref_pc = NULL;
+
+ c = strchr(dest, '/');
+ if (c)
+ c++;
+ else
+ c = dest;
+
+ if (!p->hidecallerid) {
+ l = ast->cid.cid_num;
+ } else {
+ l = NULL;
+ }
+
+ if (ss7_grab(p, p->ss7)) {
+ ast_log(LOG_WARNING, "Failed to grab SS7!\n");
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ p->digital = IS_DIGITAL(ast->transfercapability);
+ p->ss7call = isup_new_call(p->ss7->ss7);
+
+ if (!p->ss7call) {
+ ss7_rel(p->ss7);
+ ast_mutex_unlock(&p->lock);
+ ast_log(LOG_ERROR, "Unable to allocate new SS7 call!\n");
+ return -1;
+ }
+
+ called_nai_strip = 0;
+ ss7_called_nai = p->ss7->called_nai;
+ if (ss7_called_nai == SS7_NAI_DYNAMIC) { /* compute dynamically */
+ if (strncmp(c + p->stripmsd, p->ss7->internationalprefix, strlen(p->ss7->internationalprefix)) == 0) {
+ called_nai_strip = strlen(p->ss7->internationalprefix);
+ ss7_called_nai = SS7_NAI_INTERNATIONAL;
+ } else if (strncmp(c + p->stripmsd, p->ss7->nationalprefix, strlen(p->ss7->nationalprefix)) == 0) {
+ called_nai_strip = strlen(p->ss7->nationalprefix);
+ ss7_called_nai = SS7_NAI_NATIONAL;
+ } else {
+ ss7_called_nai = SS7_NAI_SUBSCRIBER;
+ }
+ }
+ isup_set_called(p->ss7call, c + p->stripmsd + called_nai_strip, ss7_called_nai, p->ss7->ss7);
+
+ calling_nai_strip = 0;
+ ss7_calling_nai = p->ss7->calling_nai;
+ if ((l != NULL) && (ss7_calling_nai == SS7_NAI_DYNAMIC)) { /* compute dynamically */
+ if (strncmp(l, p->ss7->internationalprefix, strlen(p->ss7->internationalprefix)) == 0) {
+ calling_nai_strip = strlen(p->ss7->internationalprefix);
+ ss7_calling_nai = SS7_NAI_INTERNATIONAL;
+ } else if (strncmp(l, p->ss7->nationalprefix, strlen(p->ss7->nationalprefix)) == 0) {
+ calling_nai_strip = strlen(p->ss7->nationalprefix);
+ ss7_calling_nai = SS7_NAI_NATIONAL;
+ } else {
+ ss7_calling_nai = SS7_NAI_SUBSCRIBER;
+ }
+ }
+ isup_set_calling(p->ss7call, l ? (l + calling_nai_strip) : NULL, ss7_calling_nai,
+ p->use_callingpres ? cid_pres2ss7pres(ast->cid.cid_pres) : (l ? SS7_PRESENTATION_ALLOWED : SS7_PRESENTATION_RESTRICTED),
+ p->use_callingpres ? cid_pres2ss7screen(ast->cid.cid_pres) : SS7_SCREENING_USER_PROVIDED );
+
+ isup_set_oli(p->ss7call, ast->cid.cid_ani2);
+ isup_init_call(p->ss7->ss7, p->ss7call, p->cic, p->dpc);
+
+ /* Set the charge number if it is set */
+ charge_str = pbx_builtin_getvar_helper(ast, "SS7_CHARGE_NUMBER");
+ if (charge_str)
+ isup_set_charge(p->ss7call, charge_str, SS7_ANI_CALLING_PARTY_SUB_NUMBER, 0x10);
+
+ gen_address = pbx_builtin_getvar_helper(ast, "SS7_GENERIC_ADDRESS");
+ if (gen_address)
+ isup_set_gen_address(p->ss7call, gen_address, p->gen_add_nai,p->gen_add_pres_ind, p->gen_add_num_plan,p->gen_add_type); /* need to add some types here for NAI,PRES,TYPE */
+
+ gen_digits = pbx_builtin_getvar_helper(ast, "SS7_GENERIC_DIGITS");
+ gen_dig_type = pbx_builtin_getvar_helper(ast, "SS7_GENERIC_DIGTYPE");
+ gen_dig_scheme = pbx_builtin_getvar_helper(ast, "SS7_GENERIC_DIGSCHEME");
+ if (gen_digits)
+ isup_set_gen_digits(p->ss7call, gen_digits, atoi(gen_dig_type), atoi(gen_dig_scheme));
+
+ jip_digits = pbx_builtin_getvar_helper(ast, "SS7_JIP");
+ if (jip_digits)
+ isup_set_jip_digits(p->ss7call, jip_digits);
+
+ lspi_ident = pbx_builtin_getvar_helper(ast, "SS7_LSPI_IDENT");
+ if (lspi_ident)
+ isup_set_lspi(p->ss7call, lspi_ident, 0x18, 0x7, 0x00);
+
+ rlt_flag = pbx_builtin_getvar_helper(ast, "SS7_RLT_ON");
+ if ((rlt_flag) && ((strncmp("NO", rlt_flag, strlen(rlt_flag))) != 0 ))
+ isup_set_lspi(p->ss7call, rlt_flag, 0x18, 0x7, 0x00); /* Setting for Nortel DMS-250/500 */
+
+ call_ref_id = pbx_builtin_getvar_helper(ast, "SS7_CALLREF_IDENT");
+ call_ref_pc = pbx_builtin_getvar_helper(ast, "SS7_CALLREF_PC");
+ if (call_ref_id) {
+ isup_set_callref(p->ss7call, atoi(call_ref_id),
+ call_ref_pc ? atoi(call_ref_pc) : 0);
+ }
+
+ isup_iam(p->ss7->ss7, p->ss7call);
+ ast_setstate(ast, AST_STATE_DIALING);
+ ss7_rel(p->ss7);
+ }
+#endif /* HAVE_SS7 */
+#ifdef HAVE_PRI
+ if (p->pri) {
+ struct pri_sr *sr;
+#ifdef SUPPORT_USERUSER
+ const char *useruser;
+#endif
+ int pridialplan;
+ int dp_strip;
+ int prilocaldialplan;
+ int ldp_strip;
+ int exclusive;
+ const char *rr_str;
+ int redirect_reason;
+
+ c = strchr(dest, '/');
+ if (c)
+ c++;
+ else
+ c = dest;
+
+ l = NULL;
+ n = NULL;
+
+ if (!p->hidecallerid) {
+ l = ast->cid.cid_num;
+ if (!p->hidecalleridname) {
+ n = ast->cid.cid_name;
+ }
+ }
+
+ if (strlen(c) < p->stripmsd) {
+ ast_log(LOG_WARNING, "Number '%s' is shorter than stripmsd (%d)\n", c, p->stripmsd);
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ if (mysig != SIG_FXSKS) {
+ p->dop.op = ZT_DIAL_OP_REPLACE;
+ s = strchr(c + p->stripmsd, 'w');
+ if (s) {
+ if (strlen(s) > 1)
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "T%s", s);
+ else
+ p->dop.dialstr[0] = '\0';
+ *s = '\0';
+ } else {
+ p->dop.dialstr[0] = '\0';
+ }
+ }
+ if (pri_grab(p, p->pri)) {
+ ast_log(LOG_WARNING, "Failed to grab PRI!\n");
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ if (!(p->call = pri_new_call(p->pri->pri))) {
+ ast_log(LOG_WARNING, "Unable to create call on channel %d\n", p->channel);
+ pri_rel(p->pri);
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ if (!(sr = pri_sr_new())) {
+ ast_log(LOG_WARNING, "Failed to allocate setup request channel %d\n", p->channel);
+ pri_rel(p->pri);
+ ast_mutex_unlock(&p->lock);
+ }
+ if (p->bearer || (mysig == SIG_FXSKS)) {
+ if (p->bearer) {
+ ast_debug(1, "Oooh, I have a bearer on %d (%d:%d)\n", PVT_TO_CHANNEL(p->bearer), p->bearer->logicalspan, p->bearer->channel);
+ p->bearer->call = p->call;
+ } else
+ ast_debug(1, "I'm being setup with no bearer right now...\n");
+
+ pri_set_crv(p->pri->pri, p->call, p->channel, 0);
+ }
+ p->digital = IS_DIGITAL(ast->transfercapability);
+ /* Add support for exclusive override */
+ if (p->priexclusive)
+ exclusive = 1;
+ else {
+ /* otherwise, traditional behavior */
+ if (p->pri->nodetype == PRI_NETWORK)
+ exclusive = 0;
+ else
+ exclusive = 1;
+ }
+
+ pri_sr_set_channel(sr, p->bearer ? PVT_TO_CHANNEL(p->bearer) : PVT_TO_CHANNEL(p), exclusive, 1);
+ pri_sr_set_bearer(sr, p->digital ? PRI_TRANS_CAP_DIGITAL : ast->transfercapability,
+ (p->digital ? -1 :
+ ((p->law == ZT_LAW_ALAW) ? PRI_LAYER_1_ALAW : PRI_LAYER_1_ULAW)));
+ if (p->pri->facilityenable)
+ pri_facility_enable(p->pri->pri);
+
+ ast_verb(3, "Requested transfer capability: 0x%.2x - %s\n", ast->transfercapability, ast_transfercapability2str(ast->transfercapability));
+ dp_strip = 0;
+ pridialplan = p->pri->dialplan - 1;
+ if (pridialplan == -2 || pridialplan == -3) { /* compute dynamically */
+ if (strncmp(c + p->stripmsd, p->pri->internationalprefix, strlen(p->pri->internationalprefix)) == 0) {
+ if (pridialplan == -2) {
+ dp_strip = strlen(p->pri->internationalprefix);
+ }
+ pridialplan = PRI_INTERNATIONAL_ISDN;
+ } else if (strncmp(c + p->stripmsd, p->pri->nationalprefix, strlen(p->pri->nationalprefix)) == 0) {
+ if (pridialplan == -2) {
+ dp_strip = strlen(p->pri->nationalprefix);
+ }
+ pridialplan = PRI_NATIONAL_ISDN;
+ } else {
+ pridialplan = PRI_LOCAL_ISDN;
+ }
+ }
+ while (c[p->stripmsd] > '9' && c[p->stripmsd] != '*' && c[p->stripmsd] != '#') {
+ switch (c[p->stripmsd]) {
+ case 'U':
+ pridialplan = (PRI_TON_UNKNOWN << 4) | (pridialplan & 0xf);
+ break;
+ case 'I':
+ pridialplan = (PRI_TON_INTERNATIONAL << 4) | (pridialplan & 0xf);
+ break;
+ case 'N':
+ pridialplan = (PRI_TON_NATIONAL << 4) | (pridialplan & 0xf);
+ break;
+ case 'L':
+ pridialplan = (PRI_TON_NET_SPECIFIC << 4) | (pridialplan & 0xf);
+ break;
+ case 'S':
+ pridialplan = (PRI_TON_SUBSCRIBER << 4) | (pridialplan & 0xf);
+ break;
+ case 'V':
+ pridialplan = (PRI_TON_ABBREVIATED << 4) | (pridialplan & 0xf);
+ break;
+ case 'R':
+ pridialplan = (PRI_TON_RESERVED << 4) | (pridialplan & 0xf);
+ break;
+ case 'u':
+ pridialplan = PRI_NPI_UNKNOWN | (pridialplan & 0xf0);
+ break;
+ case 'e':
+ pridialplan = PRI_NPI_E163_E164 | (pridialplan & 0xf0);
+ break;
+ case 'x':
+ pridialplan = PRI_NPI_X121 | (pridialplan & 0xf0);
+ break;
+ case 'f':
+ pridialplan = PRI_NPI_F69 | (pridialplan & 0xf0);
+ break;
+ case 'n':
+ pridialplan = PRI_NPI_NATIONAL | (pridialplan & 0xf0);
+ break;
+ case 'p':
+ pridialplan = PRI_NPI_PRIVATE | (pridialplan & 0xf0);
+ break;
+ case 'r':
+ pridialplan = PRI_NPI_RESERVED | (pridialplan & 0xf0);
+ break;
+ default:
+ if (isalpha(*c))
+ ast_log(LOG_WARNING, "Unrecognized pridialplan %s modifier: %c\n", *c > 'Z' ? "NPI" : "TON", *c);
+ }
+ c++;
+ }
+ pri_sr_set_called(sr, c + p->stripmsd + dp_strip, pridialplan, s ? 1 : 0);
+
+ ldp_strip = 0;
+ prilocaldialplan = p->pri->localdialplan - 1;
+ if ((l != NULL) && (prilocaldialplan == -2 || prilocaldialplan == -3)) { /* compute dynamically */
+ if (strncmp(l, p->pri->internationalprefix, strlen(p->pri->internationalprefix)) == 0) {
+ if (prilocaldialplan == -2) {
+ ldp_strip = strlen(p->pri->internationalprefix);
+ }
+ prilocaldialplan = PRI_INTERNATIONAL_ISDN;
+ } else if (strncmp(l, p->pri->nationalprefix, strlen(p->pri->nationalprefix)) == 0) {
+ if (prilocaldialplan == -2) {
+ ldp_strip = strlen(p->pri->nationalprefix);
+ }
+ prilocaldialplan = PRI_NATIONAL_ISDN;
+ } else {
+ prilocaldialplan = PRI_LOCAL_ISDN;
+ }
+ }
+ if (l != NULL) {
+ while (*l > '9' && *l != '*' && *l != '#') {
+ switch (*l) {
+ case 'U':
+ prilocaldialplan = (PRI_TON_UNKNOWN << 4) | (prilocaldialplan & 0xf);
+ break;
+ case 'I':
+ prilocaldialplan = (PRI_TON_INTERNATIONAL << 4) | (prilocaldialplan & 0xf);
+ break;
+ case 'N':
+ prilocaldialplan = (PRI_TON_NATIONAL << 4) | (prilocaldialplan & 0xf);
+ break;
+ case 'L':
+ prilocaldialplan = (PRI_TON_NET_SPECIFIC << 4) | (prilocaldialplan & 0xf);
+ break;
+ case 'S':
+ prilocaldialplan = (PRI_TON_SUBSCRIBER << 4) | (prilocaldialplan & 0xf);
+ break;
+ case 'V':
+ prilocaldialplan = (PRI_TON_ABBREVIATED << 4) | (prilocaldialplan & 0xf);
+ break;
+ case 'R':
+ prilocaldialplan = (PRI_TON_RESERVED << 4) | (prilocaldialplan & 0xf);
+ break;
+ case 'u':
+ prilocaldialplan = PRI_NPI_UNKNOWN | (prilocaldialplan & 0xf0);
+ break;
+ case 'e':
+ prilocaldialplan = PRI_NPI_E163_E164 | (prilocaldialplan & 0xf0);
+ break;
+ case 'x':
+ prilocaldialplan = PRI_NPI_X121 | (prilocaldialplan & 0xf0);
+ break;
+ case 'f':
+ prilocaldialplan = PRI_NPI_F69 | (prilocaldialplan & 0xf0);
+ break;
+ case 'n':
+ prilocaldialplan = PRI_NPI_NATIONAL | (prilocaldialplan & 0xf0);
+ break;
+ case 'p':
+ prilocaldialplan = PRI_NPI_PRIVATE | (prilocaldialplan & 0xf0);
+ break;
+ case 'r':
+ prilocaldialplan = PRI_NPI_RESERVED | (prilocaldialplan & 0xf0);
+ break;
+ default:
+ if (isalpha(*l))
+ ast_log(LOG_WARNING, "Unrecognized prilocaldialplan %s modifier: %c\n", *c > 'Z' ? "NPI" : "TON", *c);
+ }
+ l++;
+ }
+ }
+ pri_sr_set_caller(sr, l ? (l + ldp_strip) : NULL, n, prilocaldialplan,
+ p->use_callingpres ? ast->cid.cid_pres : (l ? PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN : PRES_NUMBER_NOT_AVAILABLE));
+ if ((rr_str = pbx_builtin_getvar_helper(ast, "PRIREDIRECTREASON"))) {
+ if (!strcasecmp(rr_str, "UNKNOWN"))
+ redirect_reason = 0;
+ else if (!strcasecmp(rr_str, "BUSY"))
+ redirect_reason = 1;
+ else if (!strcasecmp(rr_str, "NO_REPLY"))
+ redirect_reason = 2;
+ else if (!strcasecmp(rr_str, "UNCONDITIONAL"))
+ redirect_reason = 15;
+ else
+ redirect_reason = PRI_REDIR_UNCONDITIONAL;
+ } else
+ redirect_reason = PRI_REDIR_UNCONDITIONAL;
+ pri_sr_set_redirecting(sr, ast->cid.cid_rdnis, p->pri->localdialplan - 1, PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN, redirect_reason);
+
+#ifdef SUPPORT_USERUSER
+ /* User-user info */
+ useruser = pbx_builtin_getvar_helper(p->owner, "USERUSERINFO");
+
+ if (useruser)
+ pri_sr_set_useruser(sr, useruser);
+#endif
+
+ if (pri_setup(p->pri->pri, p->call, sr)) {
+ ast_log(LOG_WARNING, "Unable to setup call to %s (using %s)\n",
+ c + p->stripmsd + dp_strip, dialplan2str(p->pri->dialplan));
+ pri_rel(p->pri);
+ ast_mutex_unlock(&p->lock);
+ pri_sr_free(sr);
+ return -1;
+ }
+ pri_sr_free(sr);
+ ast_setstate(ast, AST_STATE_DIALING);
+ pri_rel(p->pri);
+ }
+#endif
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static void destroy_zt_pvt(struct zt_pvt **pvt)
+{
+ struct zt_pvt *p = *pvt;
+ /* Remove channel from the list */
+ if (p->prev)
+ p->prev->next = p->next;
+ if (p->next)
+ p->next->prev = p->prev;
+ if (p->use_smdi)
+ ASTOBJ_UNREF(p->smdi_iface, ast_smdi_interface_destroy);
+ if (p->mwi_event_sub)
+ ast_event_unsubscribe(p->mwi_event_sub);
+ if (p->vars)
+ ast_variables_destroy(p->vars);
+ ast_mutex_destroy(&p->lock);
+ ast_free(p);
+ *pvt = NULL;
+}
+
+static int destroy_channel(struct zt_pvt *prev, struct zt_pvt *cur, int now)
+{
+ int owned = 0;
+ int i = 0;
+
+ if (!now) {
+ if (cur->owner) {
+ owned = 1;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (cur->subs[i].owner) {
+ owned = 1;
+ }
+ }
+ if (!owned) {
+ if (prev) {
+ prev->next = cur->next;
+ if (prev->next)
+ prev->next->prev = prev;
+ else
+ ifend = prev;
+ } else {
+ iflist = cur->next;
+ if (iflist)
+ iflist->prev = NULL;
+ else
+ ifend = NULL;
+ }
+ if (cur->subs[SUB_REAL].zfd > -1) {
+ zt_close(cur->subs[SUB_REAL].zfd);
+ }
+ destroy_zt_pvt(&cur);
+ }
+ } else {
+ if (prev) {
+ prev->next = cur->next;
+ if (prev->next)
+ prev->next->prev = prev;
+ else
+ ifend = prev;
+ } else {
+ iflist = cur->next;
+ if (iflist)
+ iflist->prev = NULL;
+ else
+ ifend = NULL;
+ }
+ if (cur->subs[SUB_REAL].zfd > -1) {
+ zt_close(cur->subs[SUB_REAL].zfd);
+ }
+ destroy_zt_pvt(&cur);
+ }
+ return 0;
+}
+
+#ifdef HAVE_PRI
+static char *zap_send_keypad_facility_app = "ZapSendKeypadFacility";
+
+static char *zap_send_keypad_facility_synopsis = "Send digits out of band over a PRI";
+
+static char *zap_send_keypad_facility_descrip =
+" ZapSendKeypadFacility(): This application will send the given string of digits in a Keypad Facility\n"
+" IE over the current channel.\n";
+
+static int zap_send_keypad_facility_exec(struct ast_channel *chan, void *data)
+{
+ /* Data will be our digit string */
+ struct zt_pvt *p;
+ char *digits = (char *) data;
+
+ if (ast_strlen_zero(digits)) {
+ ast_debug(1, "No digit string sent to application!\n");
+ return -1;
+ }
+
+ p = (struct zt_pvt *)chan->tech_pvt;
+
+ if (!p) {
+ ast_debug(1, "Unable to find technology private\n");
+ return -1;
+ }
+
+ ast_mutex_lock(&p->lock);
+
+ if (!p->pri || !p->call) {
+ ast_debug(1, "Unable to find pri or call on channel!\n");
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+
+ if (!pri_grab(p, p->pri)) {
+ pri_keypad_facility(p->pri->pri, p->call, digits);
+ pri_rel(p->pri);
+ } else {
+ ast_debug(1, "Unable to grab pri to send keypad facility!\n");
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+
+ ast_mutex_unlock(&p->lock);
+
+ return 0;
+}
+
+static int pri_is_up(struct zt_pri *pri)
+{
+ int x;
+ for (x = 0; x < NUM_DCHANS; x++) {
+ if (pri->dchanavail[x] == DCHAN_AVAILABLE)
+ return 1;
+ }
+ return 0;
+}
+
+static int pri_assign_bearer(struct zt_pvt *crv, struct zt_pri *pri, struct zt_pvt *bearer)
+{
+ bearer->owner = &inuse;
+ bearer->realcall = crv;
+ crv->subs[SUB_REAL].zfd = bearer->subs[SUB_REAL].zfd;
+ if (crv->subs[SUB_REAL].owner)
+ ast_channel_set_fd(crv->subs[SUB_REAL].owner, 0, crv->subs[SUB_REAL].zfd);
+ crv->bearer = bearer;
+ crv->call = bearer->call;
+ crv->pri = pri;
+ return 0;
+}
+
+static char *pri_order(int level)
+{
+ switch (level) {
+ case 0:
+ return "Primary";
+ case 1:
+ return "Secondary";
+ case 2:
+ return "Tertiary";
+ case 3:
+ return "Quaternary";
+ default:
+ return "<Unknown>";
+ }
+}
+
+/* Returns fd of the active dchan */
+static int pri_active_dchan_fd(struct zt_pri *pri)
+{
+ int x = -1;
+
+ for (x = 0; x < NUM_DCHANS; x++) {
+ if ((pri->dchans[x] == pri->pri))
+ break;
+ }
+
+ return pri->fds[x];
+}
+
+static int pri_find_dchan(struct zt_pri *pri)
+{
+ int oldslot = -1;
+ struct pri *old;
+ int newslot = -1;
+ int x;
+ old = pri->pri;
+ for (x = 0; x < NUM_DCHANS; x++) {
+ if ((pri->dchanavail[x] == DCHAN_AVAILABLE) && (newslot < 0))
+ newslot = x;
+ if (pri->dchans[x] == old) {
+ oldslot = x;
+ }
+ }
+ if (newslot < 0) {
+ newslot = 0;
+ ast_log(LOG_WARNING, "No D-channels available! Using Primary channel %d as D-channel anyway!\n",
+ pri->dchannels[newslot]);
+ }
+ if (old && (oldslot != newslot))
+ ast_log(LOG_NOTICE, "Switching from from d-channel %d to channel %d!\n",
+ pri->dchannels[oldslot], pri->dchannels[newslot]);
+ pri->pri = pri->dchans[newslot];
+ return 0;
+}
+#endif
+
+static int zt_hangup(struct ast_channel *ast)
+{
+ int res;
+ int index,x, law;
+ /*static int restore_gains(struct zt_pvt *p);*/
+ struct zt_pvt *p = ast->tech_pvt;
+ struct zt_pvt *tmp = NULL;
+ struct zt_pvt *prev = NULL;
+ ZT_PARAMS par;
+
+ ast_debug(1, "zt_hangup(%s)\n", ast->name);
+ if (!ast->tech_pvt) {
+ ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
+ return 0;
+ }
+
+ ast_mutex_lock(&p->lock);
+
+ index = zt_get_index(ast, p, 1);
+
+ if ((p->sig == SIG_PRI) || (p->sig == SIG_SS7) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP)) {
+ x = 1;
+ ast_channel_setoption(ast,AST_OPTION_AUDIO_MODE,&x,sizeof(char),0);
+ }
+
+ x = 0;
+ zt_confmute(p, 0);
+ restore_gains(p);
+ if (p->origcid_num) {
+ ast_copy_string(p->cid_num, p->origcid_num, sizeof(p->cid_num));
+ ast_free(p->origcid_num);
+ p->origcid_num = NULL;
+ }
+ if (p->origcid_name) {
+ ast_copy_string(p->cid_name, p->origcid_name, sizeof(p->cid_name));
+ ast_free(p->origcid_name);
+ p->origcid_name = NULL;
+ }
+ if (p->dsp)
+ ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_DTMF | p->dtmfrelax);
+ if (p->exten)
+ p->exten[0] = '\0';
+
+ ast_debug(1, "Hangup: channel: %d index = %d, normal = %d, callwait = %d, thirdcall = %d\n",
+ p->channel, index, p->subs[SUB_REAL].zfd, p->subs[SUB_CALLWAIT].zfd, p->subs[SUB_THREEWAY].zfd);
+ p->ignoredtmf = 0;
+
+ if (index > -1) {
+ /* Real channel, do some fixup */
+ p->subs[index].owner = NULL;
+ p->subs[index].needanswer = 0;
+ p->subs[index].needflash = 0;
+ p->subs[index].needringing = 0;
+ p->subs[index].needbusy = 0;
+ p->subs[index].needcongestion = 0;
+ p->subs[index].linear = 0;
+ p->subs[index].needcallerid = 0;
+ p->polarity = POLARITY_IDLE;
+ zt_setlinear(p->subs[index].zfd, 0);
+ if (index == SUB_REAL) {
+ if ((p->subs[SUB_CALLWAIT].zfd > -1) && (p->subs[SUB_THREEWAY].zfd > -1)) {
+ ast_debug(1, "Normal call hung up with both three way call and a call waiting call in place?\n");
+ if (p->subs[SUB_CALLWAIT].inthreeway) {
+ /* We had flipped over to answer a callwait and now it's gone */
+ ast_debug(1, "We were flipped over to the callwait, moving back and unowning.\n");
+ /* Move to the call-wait, but un-own us until they flip back. */
+ swap_subs(p, SUB_CALLWAIT, SUB_REAL);
+ unalloc_sub(p, SUB_CALLWAIT);
+ p->owner = NULL;
+ } else {
+ /* The three way hung up, but we still have a call wait */
+ ast_debug(1, "We were in the threeway and have a callwait still. Ditching the threeway.\n");
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ unalloc_sub(p, SUB_THREEWAY);
+ if (p->subs[SUB_REAL].inthreeway) {
+ /* This was part of a three way call. Immediately make way for
+ another call */
+ ast_debug(1, "Call was complete, setting owner to former third call\n");
+ p->owner = p->subs[SUB_REAL].owner;
+ } else {
+ /* This call hasn't been completed yet... Set owner to NULL */
+ ast_debug(1, "Call was incomplete, setting owner to NULL\n");
+ p->owner = NULL;
+ }
+ p->subs[SUB_REAL].inthreeway = 0;
+ }
+ } else if (p->subs[SUB_CALLWAIT].zfd > -1) {
+ /* Move to the call-wait and switch back to them. */
+ swap_subs(p, SUB_CALLWAIT, SUB_REAL);
+ unalloc_sub(p, SUB_CALLWAIT);
+ p->owner = p->subs[SUB_REAL].owner;
+ if (p->owner->_state != AST_STATE_UP)
+ p->subs[SUB_REAL].needanswer = 1;
+ if (ast_bridged_channel(p->subs[SUB_REAL].owner))
+ ast_queue_control(p->subs[SUB_REAL].owner, AST_CONTROL_UNHOLD);
+ } else if (p->subs[SUB_THREEWAY].zfd > -1) {
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ unalloc_sub(p, SUB_THREEWAY);
+ if (p->subs[SUB_REAL].inthreeway) {
+ /* This was part of a three way call. Immediately make way for
+ another call */
+ ast_debug(1, "Call was complete, setting owner to former third call\n");
+ p->owner = p->subs[SUB_REAL].owner;
+ } else {
+ /* This call hasn't been completed yet... Set owner to NULL */
+ ast_debug(1, "Call was incomplete, setting owner to NULL\n");
+ p->owner = NULL;
+ }
+ p->subs[SUB_REAL].inthreeway = 0;
+ }
+ } else if (index == SUB_CALLWAIT) {
+ /* Ditch the holding callwait call, and immediately make it availabe */
+ if (p->subs[SUB_CALLWAIT].inthreeway) {
+ /* This is actually part of a three way, placed on hold. Place the third part
+ on music on hold now */
+ if (p->subs[SUB_THREEWAY].owner && ast_bridged_channel(p->subs[SUB_THREEWAY].owner)) {
+ ast_queue_control_data(p->subs[SUB_THREEWAY].owner, AST_CONTROL_HOLD,
+ S_OR(p->mohsuggest, NULL),
+ !ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
+ }
+ p->subs[SUB_THREEWAY].inthreeway = 0;
+ /* Make it the call wait now */
+ swap_subs(p, SUB_CALLWAIT, SUB_THREEWAY);
+ unalloc_sub(p, SUB_THREEWAY);
+ } else
+ unalloc_sub(p, SUB_CALLWAIT);
+ } else if (index == SUB_THREEWAY) {
+ if (p->subs[SUB_CALLWAIT].inthreeway) {
+ /* The other party of the three way call is currently in a call-wait state.
+ Start music on hold for them, and take the main guy out of the third call */
+ if (p->subs[SUB_CALLWAIT].owner && ast_bridged_channel(p->subs[SUB_CALLWAIT].owner)) {
+ ast_queue_control_data(p->subs[SUB_CALLWAIT].owner, AST_CONTROL_HOLD,
+ S_OR(p->mohsuggest, NULL),
+ !ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
+ }
+ p->subs[SUB_CALLWAIT].inthreeway = 0;
+ }
+ p->subs[SUB_REAL].inthreeway = 0;
+ /* If this was part of a three way call index, let us make
+ another three way call */
+ unalloc_sub(p, SUB_THREEWAY);
+ } else {
+ /* This wasn't any sort of call, but how are we an index? */
+ ast_log(LOG_WARNING, "Index found but not any type of call?\n");
+ }
+ }
+
+ if (!p->subs[SUB_REAL].owner && !p->subs[SUB_CALLWAIT].owner && !p->subs[SUB_THREEWAY].owner) {
+ p->owner = NULL;
+ p->ringt = 0;
+ p->distinctivering = 0;
+ p->confirmanswer = 0;
+ p->cidrings = 1;
+ p->outgoing = 0;
+ p->digital = 0;
+ p->faxhandled = 0;
+ p->pulsedial = 0;
+ p->onhooktime = time(NULL);
+#if defined(HAVE_PRI) || defined(HAVE_SS7)
+ p->proceeding = 0;
+ p->progress = 0;
+ p->alerting = 0;
+ p->setup_ack = 0;
+ p->rlt = 0;
+#endif
+ if (p->dsp) {
+ ast_dsp_free(p->dsp);
+ p->dsp = NULL;
+ }
+
+ law = ZT_LAW_DEFAULT;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_SETLAW, &law);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to set law on channel %d to default\n", p->channel);
+ /* Perform low level hangup if no owner left */
+#ifdef HAVE_SS7
+ if (p->ss7) {
+ if (p->ss7call) {
+ if (!ss7_grab(p, p->ss7)) {
+ if (!p->alreadyhungup) {
+ const char *cause = pbx_builtin_getvar_helper(ast,"SS7_CAUSE");
+ int icause = ast->hangupcause ? ast->hangupcause : -1;
+
+ if (cause) {
+ if (atoi(cause))
+ icause = atoi(cause);
+ }
+ isup_rel(p->ss7->ss7, p->ss7call, icause);
+ ss7_rel(p->ss7);
+ p->alreadyhungup = 1;
+ } else
+ ast_log(LOG_WARNING, "Trying to hangup twice!\n");
+ } else {
+ ast_log(LOG_WARNING, "Unable to grab SS7 on CIC %d\n", p->cic);
+ res = -1;
+ }
+ }
+ }
+#endif
+#ifdef HAVE_PRI
+ if (p->pri) {
+#ifdef SUPPORT_USERUSER
+ const char *useruser = pbx_builtin_getvar_helper(ast,"USERUSERINFO");
+#endif
+
+ /* Make sure we have a call (or REALLY have a call in the case of a PRI) */
+ if (p->call && (!p->bearer || (p->bearer->call == p->call))) {
+ if (!pri_grab(p, p->pri)) {
+ if (p->alreadyhungup) {
+ ast_debug(1, "Already hungup... Calling hangup once, and clearing call\n");
+
+#ifdef SUPPORT_USERUSER
+ pri_call_set_useruser(p->call, useruser);
+#endif
+
+ pri_hangup(p->pri->pri, p->call, -1);
+ p->call = NULL;
+ if (p->bearer)
+ p->bearer->call = NULL;
+ } else {
+ const char *cause = pbx_builtin_getvar_helper(ast,"PRI_CAUSE");
+ int icause = ast->hangupcause ? ast->hangupcause : -1;
+ ast_debug(1, "Not yet hungup... Calling hangup once with icause, and clearing call\n");
+
+#ifdef SUPPORT_USERUSER
+ pri_call_set_useruser(p->call, useruser);
+#endif
+
+ p->alreadyhungup = 1;
+ if (p->bearer)
+ p->bearer->alreadyhungup = 1;
+ if (cause) {
+ if (atoi(cause))
+ icause = atoi(cause);
+ }
+ pri_hangup(p->pri->pri, p->call, icause);
+ }
+ if (res < 0)
+ ast_log(LOG_WARNING, "pri_disconnect failed\n");
+ pri_rel(p->pri);
+ } else {
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->span);
+ res = -1;
+ }
+ } else {
+ if (p->bearer)
+ ast_debug(1, "Bearer call is %p, while ours is still %p\n", p->bearer->call, p->call);
+ p->call = NULL;
+ res = 0;
+ }
+ }
+#endif
+ if (p->sig && ((p->sig != SIG_PRI) && (p->sig != SIG_SS7) && (p->sig != SIG_BRI) && (p->sig != SIG_BRI_PTMP)))
+ res = zt_set_hook(p->subs[SUB_REAL].zfd, ZT_ONHOOK);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to hangup line %s\n", ast->name);
+ }
+ switch (p->sig) {
+ case SIG_FXOGS:
+ case SIG_FXOLS:
+ case SIG_FXOKS:
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_GET_PARAMS, &par);
+ if (!res) {
+#if 0
+ ast_debug(1, "Hanging up channel %d, offhook = %d\n", p->channel, par.rxisoffhook);
+#endif
+ /* If they're off hook, try playing congestion */
+ if ((par.rxisoffhook) && (!(p->radio || (p->oprmode < 0))))
+ tone_zone_play_tone(p->subs[SUB_REAL].zfd, ZT_TONE_CONGESTION);
+ else
+ tone_zone_play_tone(p->subs[SUB_REAL].zfd, -1);
+ }
+ break;
+ case SIG_FXSGS:
+ case SIG_FXSLS:
+ case SIG_FXSKS:
+ /* Make sure we're not made available for at least two seconds assuming
+ we were actually used for an inbound or outbound call. */
+ if (ast->_state != AST_STATE_RESERVED) {
+ time(&p->guardtime);
+ p->guardtime += 2;
+ }
+ break;
+ default:
+ tone_zone_play_tone(p->subs[SUB_REAL].zfd, -1);
+ }
+ if (p->cidspill)
+ ast_free(p->cidspill);
+ if (p->sig)
+ zt_disable_ec(p);
+ x = 0;
+ ast_channel_setoption(ast,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
+ ast_channel_setoption(ast,AST_OPTION_TDD,&x,sizeof(char),0);
+ p->didtdd = 0;
+ p->cidspill = NULL;
+ p->callwaitcas = 0;
+ p->callwaiting = p->permcallwaiting;
+ p->hidecallerid = p->permhidecallerid;
+ p->dialing = 0;
+ p->rdnis[0] = '\0';
+ update_conf(p);
+ reset_conf(p);
+ /* Restore data mode */
+ if ((p->sig == SIG_PRI) || (p->sig == SIG_SS7) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP)) {
+ x = 0;
+ ast_channel_setoption(ast,AST_OPTION_AUDIO_MODE,&x,sizeof(char),0);
+ }
+#ifdef HAVE_PRI
+ if (p->bearer) {
+ ast_debug(1, "Freeing up bearer channel %d\n", p->bearer->channel);
+ /* Free up the bearer channel as well, and
+ don't use its file descriptor anymore */
+ update_conf(p->bearer);
+ reset_conf(p->bearer);
+ p->bearer->owner = NULL;
+ p->bearer->realcall = NULL;
+ p->bearer = NULL;
+ p->subs[SUB_REAL].zfd = -1;
+ p->pri = NULL;
+ }
+#endif
+ restart_monitor();
+ }
+
+ p->callwaitingrepeat = 0;
+ p->cidcwexpire = 0;
+ p->oprmode = 0;
+ ast->tech_pvt = NULL;
+ ast_mutex_unlock(&p->lock);
+ ast_module_unref(ast_module_info->self);
+ ast_verb(3, "Hungup '%s'\n", ast->name);
+
+ ast_mutex_lock(&iflock);
+ tmp = iflist;
+ prev = NULL;
+ if (p->destroy) {
+ while (tmp) {
+ if (tmp == p) {
+ destroy_channel(prev, tmp, 0);
+ break;
+ } else {
+ prev = tmp;
+ tmp = tmp->next;
+ }
+ }
+ }
+ ast_mutex_unlock(&iflock);
+ return 0;
+}
+
+static int zt_answer(struct ast_channel *ast)
+{
+ struct zt_pvt *p = ast->tech_pvt;
+ int res = 0;
+ int index;
+ int oldstate = ast->_state;
+ ast_setstate(ast, AST_STATE_UP);
+ ast_mutex_lock(&p->lock);
+ index = zt_get_index(ast, p, 0);
+ if (index < 0)
+ index = SUB_REAL;
+ /* nothing to do if a radio channel */
+ if ((p->radio || (p->oprmode < 0))) {
+ ast_mutex_unlock(&p->lock);
+ return 0;
+ }
+ switch (p->sig) {
+ case SIG_FXSLS:
+ case SIG_FXSGS:
+ case SIG_FXSKS:
+ p->ringt = 0;
+ /* Fall through */
+ case SIG_EM:
+ case SIG_EM_E1:
+ case SIG_EMWINK:
+ case SIG_FEATD:
+ case SIG_FEATDMF:
+ case SIG_FEATDMF_TA:
+ case SIG_E911:
+ case SIG_FGC_CAMA:
+ case SIG_FGC_CAMAMF:
+ case SIG_FEATB:
+ case SIG_SF:
+ case SIG_SFWINK:
+ case SIG_SF_FEATD:
+ case SIG_SF_FEATDMF:
+ case SIG_SF_FEATB:
+ case SIG_FXOLS:
+ case SIG_FXOGS:
+ case SIG_FXOKS:
+ /* Pick up the line */
+ ast_debug(1, "Took %s off hook\n", ast->name);
+ if (p->hanguponpolarityswitch) {
+ p->polaritydelaytv = ast_tvnow();
+ }
+ res = zt_set_hook(p->subs[SUB_REAL].zfd, ZT_OFFHOOK);
+ tone_zone_play_tone(p->subs[index].zfd, -1);
+ p->dialing = 0;
+ if ((index == SUB_REAL) && p->subs[SUB_THREEWAY].inthreeway) {
+ if (oldstate == AST_STATE_RINGING) {
+ ast_debug(1, "Finally swapping real and threeway\n");
+ tone_zone_play_tone(p->subs[SUB_THREEWAY].zfd, -1);
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ p->owner = p->subs[SUB_REAL].owner;
+ }
+ }
+ if (p->sig & __ZT_SIG_FXS) {
+ zt_enable_ec(p);
+ zt_train_ec(p);
+ }
+ break;
+#ifdef HAVE_PRI
+ case SIG_BRI:
+ case SIG_BRI_PTMP:
+ case SIG_PRI:
+ /* Send a pri acknowledge */
+ if (!pri_grab(p, p->pri)) {
+ p->proceeding = 1;
+ res = pri_answer(p->pri->pri, p->call, 0, !p->digital);
+ pri_rel(p->pri);
+ } else {
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->span);
+ res = -1;
+ }
+ break;
+#endif
+#ifdef HAVE_SS7
+ case SIG_SS7:
+ if (!ss7_grab(p, p->ss7)) {
+ p->proceeding = 1;
+ res = isup_anm(p->ss7->ss7, p->ss7call);
+ ss7_rel(p->ss7);
+ } else {
+ ast_log(LOG_WARNING, "Unable to grab SS7 on span %d\n", p->span);
+ res = -1;
+ }
+ break;
+#endif
+ case 0:
+ ast_mutex_unlock(&p->lock);
+ return 0;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to answer signalling %d (channel %d)\n", p->sig, p->channel);
+ res = -1;
+ }
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static int zt_setoption(struct ast_channel *chan, int option, void *data, int datalen)
+{
+ char *cp;
+ signed char *scp;
+ int x;
+ int index;
+ struct zt_pvt *p = chan->tech_pvt, *pp;
+ struct oprmode *oprmode;
+
+
+ /* all supported options require data */
+ if (!data || (datalen < 1)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ switch (option) {
+ case AST_OPTION_TXGAIN:
+ scp = (signed char *) data;
+ index = zt_get_index(chan, p, 0);
+ if (index < 0) {
+ ast_log(LOG_WARNING, "No index in TXGAIN?\n");
+ return -1;
+ }
+ ast_debug(1, "Setting actual tx gain on %s to %f\n", chan->name, p->txgain + (float) *scp);
+ return set_actual_txgain(p->subs[index].zfd, 0, p->txgain + (float) *scp, p->law);
+ case AST_OPTION_RXGAIN:
+ scp = (signed char *) data;
+ index = zt_get_index(chan, p, 0);
+ if (index < 0) {
+ ast_log(LOG_WARNING, "No index in RXGAIN?\n");
+ return -1;
+ }
+ ast_debug(1, "Setting actual rx gain on %s to %f\n", chan->name, p->rxgain + (float) *scp);
+ return set_actual_rxgain(p->subs[index].zfd, 0, p->rxgain + (float) *scp, p->law);
+ case AST_OPTION_TONE_VERIFY:
+ if (!p->dsp)
+ break;
+ cp = (char *) data;
+ switch (*cp) {
+ case 1:
+ ast_debug(1, "Set option TONE VERIFY, mode: MUTECONF(1) on %s\n",chan->name);
+ ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_MUTECONF | p->dtmfrelax); /* set mute mode if desired */
+ break;
+ case 2:
+ ast_debug(1, "Set option TONE VERIFY, mode: MUTECONF/MAX(2) on %s\n",chan->name);
+ ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX | p->dtmfrelax); /* set mute mode if desired */
+ break;
+ default:
+ ast_debug(1, "Set option TONE VERIFY, mode: OFF(0) on %s\n",chan->name);
+ ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_DTMF | p->dtmfrelax); /* set mute mode if desired */
+ break;
+ }
+ break;
+ case AST_OPTION_TDD:
+ /* turn on or off TDD */
+ cp = (char *) data;
+ p->mate = 0;
+ if (!*cp) { /* turn it off */
+ ast_debug(1, "Set option TDD MODE, value: OFF(0) on %s\n",chan->name);
+ if (p->tdd)
+ tdd_free(p->tdd);
+ p->tdd = 0;
+ break;
+ }
+ ast_debug(1, "Set option TDD MODE, value: %s(%d) on %s\n",
+ (*cp == 2) ? "MATE" : "ON", (int) *cp, chan->name);
+ zt_disable_ec(p);
+ /* otherwise, turn it on */
+ if (!p->didtdd) { /* if havent done it yet */
+ unsigned char mybuf[41000], *buf;
+ int size, res, fd, len;
+ struct pollfd fds[1];
+
+ buf = mybuf;
+ memset(buf, 0x7f, sizeof(mybuf)); /* set to silence */
+ ast_tdd_gen_ecdisa(buf + 16000, 16000); /* put in tone */
+ len = 40000;
+ index = zt_get_index(chan, p, 0);
+ if (index < 0) {
+ ast_log(LOG_WARNING, "No index in TDD?\n");
+ return -1;
+ }
+ fd = p->subs[index].zfd;
+ while (len) {
+ if (ast_check_hangup(chan))
+ return -1;
+ size = len;
+ if (size > READ_SIZE)
+ size = READ_SIZE;
+ fds[0].fd = fd;
+ fds[0].events = POLLPRI | POLLOUT;
+ fds[0].revents = 0;
+ res = poll(fds, 1, -1);
+ if (!res) {
+ ast_debug(1, "poll (for write) ret. 0 on channel %d\n", p->channel);
+ continue;
+ }
+ /* if got exception */
+ if (fds[0].revents & POLLPRI)
+ return -1;
+ if (!(fds[0].revents & POLLOUT)) {
+ ast_debug(1, "write fd not ready on channel %d\n", p->channel);
+ continue;
+ }
+ res = write(fd, buf, size);
+ if (res != size) {
+ if (res == -1) return -1;
+ ast_debug(1, "Write returned %d (%s) on channel %d\n", res, strerror(errno), p->channel);
+ break;
+ }
+ len -= size;
+ buf += size;
+ }
+ p->didtdd = 1; /* set to have done it now */
+ }
+ if (*cp == 2) { /* Mate mode */
+ if (p->tdd)
+ tdd_free(p->tdd);
+ p->tdd = 0;
+ p->mate = 1;
+ break;
+ }
+ if (!p->tdd) { /* if we dont have one yet */
+ p->tdd = tdd_new(); /* allocate one */
+ }
+ break;
+ case AST_OPTION_RELAXDTMF: /* Relax DTMF decoding (or not) */
+ if (!p->dsp)
+ break;
+ cp = (char *) data;
+ ast_debug(1, "Set option RELAX DTMF, value: %s(%d) on %s\n",
+ *cp ? "ON" : "OFF", (int) *cp, chan->name);
+ ast_dsp_digitmode(p->dsp, ((*cp) ? DSP_DIGITMODE_RELAXDTMF : DSP_DIGITMODE_DTMF) | p->dtmfrelax);
+ break;
+ case AST_OPTION_AUDIO_MODE: /* Set AUDIO mode (or not) */
+ cp = (char *) data;
+ if (!*cp) {
+ ast_debug(1, "Set option AUDIO MODE, value: OFF(0) on %s\n", chan->name);
+ x = 0;
+ zt_disable_ec(p);
+ } else {
+ ast_debug(1, "Set option AUDIO MODE, value: ON(1) on %s\n", chan->name);
+ x = 1;
+ }
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_AUDIOMODE, &x) == -1)
+ ast_log(LOG_WARNING, "Unable to set audio mode on channel %d to %d\n", p->channel, x);
+ break;
+ case AST_OPTION_OPRMODE: /* Operator services mode */
+ oprmode = (struct oprmode *) data;
+ pp = oprmode->peer->tech_pvt;
+ p->oprmode = pp->oprmode = 0;
+ /* setup peers */
+ p->oprpeer = pp;
+ pp->oprpeer = p;
+ /* setup modes, if any */
+ if (oprmode->mode)
+ {
+ pp->oprmode = oprmode->mode;
+ p->oprmode = -oprmode->mode;
+ }
+ ast_debug(1, "Set Operator Services mode, value: %d on %s/%s\n",
+ oprmode->mode, chan->name,oprmode->peer->name);
+ break;
+ case AST_OPTION_ECHOCAN:
+ cp = (char *) data;
+ if (*cp) {
+ ast_debug(1, "Enabling echo cancelation on %s\n", chan->name);
+ zt_enable_ec(p);
+ } else {
+ ast_debug(1, "Disabling echo cancelation on %s\n", chan->name);
+ zt_disable_ec(p);
+ }
+ break;
+ }
+ errno = 0;
+
+ return 0;
+}
+
+static int zt_func_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len)
+{
+ struct zt_pvt *p = chan->tech_pvt;
+
+ if (!strcasecmp(data, "rxgain")) {
+ ast_mutex_lock(&p->lock);
+ snprintf(buf, len, "%f", p->rxgain);
+ ast_mutex_unlock(&p->lock);
+ } else if (!strcasecmp(data, "txgain")) {
+ ast_mutex_lock(&p->lock);
+ snprintf(buf, len, "%f", p->txgain);
+ ast_mutex_unlock(&p->lock);
+ } else {
+ ast_copy_string(buf, "", len);
+ }
+ return 0;
+}
+
+
+static void zt_unlink(struct zt_pvt *slave, struct zt_pvt *master, int needlock)
+{
+ /* Unlink a specific slave or all slaves/masters from a given master */
+ int x;
+ int hasslaves;
+ if (!master)
+ return;
+ if (needlock) {
+ ast_mutex_lock(&master->lock);
+ if (slave) {
+ while (ast_mutex_trylock(&slave->lock)) {
+ ast_mutex_unlock(&master->lock);
+ usleep(1);
+ ast_mutex_lock(&master->lock);
+ }
+ }
+ }
+ hasslaves = 0;
+ for (x = 0; x < MAX_SLAVES; x++) {
+ if (master->slaves[x]) {
+ if (!slave || (master->slaves[x] == slave)) {
+ /* Take slave out of the conference */
+ ast_debug(1, "Unlinking slave %d from %d\n", master->slaves[x]->channel, master->channel);
+ conf_del(master, &master->slaves[x]->subs[SUB_REAL], SUB_REAL);
+ conf_del(master->slaves[x], &master->subs[SUB_REAL], SUB_REAL);
+ master->slaves[x]->master = NULL;
+ master->slaves[x] = NULL;
+ } else
+ hasslaves = 1;
+ }
+ if (!hasslaves)
+ master->inconference = 0;
+ }
+ if (!slave) {
+ if (master->master) {
+ /* Take master out of the conference */
+ conf_del(master->master, &master->subs[SUB_REAL], SUB_REAL);
+ conf_del(master, &master->master->subs[SUB_REAL], SUB_REAL);
+ hasslaves = 0;
+ for (x = 0; x < MAX_SLAVES; x++) {
+ if (master->master->slaves[x] == master)
+ master->master->slaves[x] = NULL;
+ else if (master->master->slaves[x])
+ hasslaves = 1;
+ }
+ if (!hasslaves)
+ master->master->inconference = 0;
+ }
+ master->master = NULL;
+ }
+ update_conf(master);
+ if (needlock) {
+ if (slave)
+ ast_mutex_unlock(&slave->lock);
+ ast_mutex_unlock(&master->lock);
+ }
+}
+
+static void zt_link(struct zt_pvt *slave, struct zt_pvt *master) {
+ int x;
+ if (!slave || !master) {
+ ast_log(LOG_WARNING, "Tried to link to/from NULL??\n");
+ return;
+ }
+ for (x = 0; x < MAX_SLAVES; x++) {
+ if (!master->slaves[x]) {
+ master->slaves[x] = slave;
+ break;
+ }
+ }
+ if (x >= MAX_SLAVES) {
+ ast_log(LOG_WARNING, "Replacing slave %d with new slave, %d\n", master->slaves[MAX_SLAVES - 1]->channel, slave->channel);
+ master->slaves[MAX_SLAVES - 1] = slave;
+ }
+ if (slave->master)
+ ast_log(LOG_WARNING, "Replacing master %d with new master, %d\n", slave->master->channel, master->channel);
+ slave->master = master;
+
+ ast_debug(1, "Making %d slave to master %d at %d\n", slave->channel, master->channel, x);
+}
+
+static void disable_dtmf_detect(struct zt_pvt *p)
+{
+#ifdef ZT_TONEDETECT
+ int val;
+#endif
+
+ p->ignoredtmf = 1;
+
+#ifdef ZT_TONEDETECT
+ val = 0;
+ ioctl(p->subs[SUB_REAL].zfd, ZT_TONEDETECT, &val);
+#endif
+ if (!p->hardwaredtmf && p->dsp) {
+ p->dsp_features &= ~DSP_FEATURE_DTMF_DETECT;
+ ast_dsp_set_features(p->dsp, p->dsp_features);
+ }
+}
+
+static void enable_dtmf_detect(struct zt_pvt *p)
+{
+#ifdef ZT_TONEDETECT
+ int val;
+#endif
+
+ if (p->channel == CHAN_PSEUDO)
+ return;
+
+ p->ignoredtmf = 0;
+
+#ifdef ZT_TONEDETECT
+ val = ZT_TONEDETECT_ON | ZT_TONEDETECT_MUTE;
+ ioctl(p->subs[SUB_REAL].zfd, ZT_TONEDETECT, &val);
+#endif
+ if (!p->hardwaredtmf && p->dsp) {
+ p->dsp_features |= DSP_FEATURE_DTMF_DETECT;
+ ast_dsp_set_features(p->dsp, p->dsp_features);
+ }
+}
+
+static enum ast_bridge_result zt_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms)
+{
+ struct ast_channel *who;
+ struct zt_pvt *p0, *p1, *op0, *op1;
+ struct zt_pvt *master = NULL, *slave = NULL;
+ struct ast_frame *f;
+ int inconf = 0;
+ int nothingok = 1;
+ int ofd0, ofd1;
+ int oi0, oi1, i0 = -1, i1 = -1, t0, t1;
+ int os0 = -1, os1 = -1;
+ int priority = 0;
+ struct ast_channel *oc0, *oc1;
+ enum ast_bridge_result res;
+
+#ifdef PRI_2BCT
+ int triedtopribridge = 0;
+ q931_call *q931c0 = NULL, *q931c1 = NULL;
+#endif
+
+ /* For now, don't attempt to native bridge if either channel needs DTMF detection.
+ There is code below to handle it properly until DTMF is actually seen,
+ but due to currently unresolved issues it's ignored...
+ */
+
+ if (flags & (AST_BRIDGE_DTMF_CHANNEL_0 | AST_BRIDGE_DTMF_CHANNEL_1))
+ return AST_BRIDGE_FAILED_NOWARN;
+
+ ast_channel_lock(c0);
+ while (ast_channel_trylock(c1)) {
+ ast_channel_unlock(c0);
+ usleep(1);
+ ast_channel_lock(c0);
+ }
+
+ p0 = c0->tech_pvt;
+ p1 = c1->tech_pvt;
+ /* cant do pseudo-channels here */
+ if (!p0 || (!p0->sig) || !p1 || (!p1->sig)) {
+ ast_channel_unlock(c0);
+ ast_channel_unlock(c1);
+ return AST_BRIDGE_FAILED_NOWARN;
+ }
+
+ oi0 = zt_get_index(c0, p0, 0);
+ oi1 = zt_get_index(c1, p1, 0);
+ if ((oi0 < 0) || (oi1 < 0)) {
+ ast_channel_unlock(c0);
+ ast_channel_unlock(c1);
+ return AST_BRIDGE_FAILED;
+ }
+
+ op0 = p0 = c0->tech_pvt;
+ op1 = p1 = c1->tech_pvt;
+ ofd0 = c0->fds[0];
+ ofd1 = c1->fds[0];
+ oc0 = p0->owner;
+ oc1 = p1->owner;
+
+ if (ast_mutex_trylock(&p0->lock)) {
+ /* Don't block, due to potential for deadlock */
+ ast_channel_unlock(c0);
+ ast_channel_unlock(c1);
+ ast_log(LOG_NOTICE, "Avoiding deadlock...\n");
+ return AST_BRIDGE_RETRY;
+ }
+ if (ast_mutex_trylock(&p1->lock)) {
+ /* Don't block, due to potential for deadlock */
+ ast_mutex_unlock(&p0->lock);
+ ast_channel_unlock(c0);
+ ast_channel_unlock(c1);
+ ast_log(LOG_NOTICE, "Avoiding deadlock...\n");
+ return AST_BRIDGE_RETRY;
+ }
+
+ if ((oi0 == SUB_REAL) && (oi1 == SUB_REAL)) {
+ if (p0->owner && p1->owner) {
+ /* If we don't have a call-wait in a 3-way, and we aren't in a 3-way, we can be master */
+ if (!p0->subs[SUB_CALLWAIT].inthreeway && !p1->subs[SUB_REAL].inthreeway) {
+ master = p0;
+ slave = p1;
+ inconf = 1;
+ } else if (!p1->subs[SUB_CALLWAIT].inthreeway && !p0->subs[SUB_REAL].inthreeway) {
+ master = p1;
+ slave = p0;
+ inconf = 1;
+ } else {
+ ast_log(LOG_WARNING, "Huh? Both calls are callwaits or 3-ways? That's clever...?\n");
+ ast_log(LOG_WARNING, "p0: chan %d/%d/CW%d/3W%d, p1: chan %d/%d/CW%d/3W%d\n",
+ p0->channel,
+ oi0, (p0->subs[SUB_CALLWAIT].zfd > -1) ? 1 : 0,
+ p0->subs[SUB_REAL].inthreeway, p0->channel,
+ oi0, (p1->subs[SUB_CALLWAIT].zfd > -1) ? 1 : 0,
+ p1->subs[SUB_REAL].inthreeway);
+ }
+ nothingok = 0;
+ }
+ } else if ((oi0 == SUB_REAL) && (oi1 == SUB_THREEWAY)) {
+ if (p1->subs[SUB_THREEWAY].inthreeway) {
+ master = p1;
+ slave = p0;
+ nothingok = 0;
+ }
+ } else if ((oi0 == SUB_THREEWAY) && (oi1 == SUB_REAL)) {
+ if (p0->subs[SUB_THREEWAY].inthreeway) {
+ master = p0;
+ slave = p1;
+ nothingok = 0;
+ }
+ } else if ((oi0 == SUB_REAL) && (oi1 == SUB_CALLWAIT)) {
+ /* We have a real and a call wait. If we're in a three way call, put us in it, otherwise,
+ don't put us in anything */
+ if (p1->subs[SUB_CALLWAIT].inthreeway) {
+ master = p1;
+ slave = p0;
+ nothingok = 0;
+ }
+ } else if ((oi0 == SUB_CALLWAIT) && (oi1 == SUB_REAL)) {
+ /* Same as previous */
+ if (p0->subs[SUB_CALLWAIT].inthreeway) {
+ master = p0;
+ slave = p1;
+ nothingok = 0;
+ }
+ }
+ ast_debug(1, "master: %d, slave: %d, nothingok: %d\n",
+ master ? master->channel : 0, slave ? slave->channel : 0, nothingok);
+ if (master && slave) {
+ /* Stop any tones, or play ringtone as appropriate. If they're bridged
+ in an active threeway call with a channel that is ringing, we should
+ indicate ringing. */
+ if ((oi1 == SUB_THREEWAY) &&
+ p1->subs[SUB_THREEWAY].inthreeway &&
+ p1->subs[SUB_REAL].owner &&
+ p1->subs[SUB_REAL].inthreeway &&
+ (p1->subs[SUB_REAL].owner->_state == AST_STATE_RINGING)) {
+ ast_debug(1, "Playing ringback on %s since %s is in a ringing three-way\n", c0->name, c1->name);
+ tone_zone_play_tone(p0->subs[oi0].zfd, ZT_TONE_RINGTONE);
+ os1 = p1->subs[SUB_REAL].owner->_state;
+ } else {
+ ast_debug(1, "Stopping tones on %d/%d talking to %d/%d\n", p0->channel, oi0, p1->channel, oi1);
+ tone_zone_play_tone(p0->subs[oi0].zfd, -1);
+ }
+ if ((oi0 == SUB_THREEWAY) &&
+ p0->subs[SUB_THREEWAY].inthreeway &&
+ p0->subs[SUB_REAL].owner &&
+ p0->subs[SUB_REAL].inthreeway &&
+ (p0->subs[SUB_REAL].owner->_state == AST_STATE_RINGING)) {
+ ast_debug(1, "Playing ringback on %s since %s is in a ringing three-way\n", c1->name, c0->name);
+ tone_zone_play_tone(p1->subs[oi1].zfd, ZT_TONE_RINGTONE);
+ os0 = p0->subs[SUB_REAL].owner->_state;
+ } else {
+ ast_debug(1, "Stopping tones on %d/%d talking to %d/%d\n", p1->channel, oi1, p0->channel, oi0);
+ tone_zone_play_tone(p1->subs[oi0].zfd, -1);
+ }
+ if ((oi0 == SUB_REAL) && (oi1 == SUB_REAL)) {
+ if (!p0->echocanbridged || !p1->echocanbridged) {
+ /* Disable echo cancellation if appropriate */
+ zt_disable_ec(p0);
+ zt_disable_ec(p1);
+ }
+ }
+ zt_link(slave, master);
+ master->inconference = inconf;
+ } else if (!nothingok)
+ ast_log(LOG_WARNING, "Can't link %d/%s with %d/%s\n", p0->channel, subnames[oi0], p1->channel, subnames[oi1]);
+
+ update_conf(p0);
+ update_conf(p1);
+ t0 = p0->subs[SUB_REAL].inthreeway;
+ t1 = p1->subs[SUB_REAL].inthreeway;
+
+ ast_mutex_unlock(&p0->lock);
+ ast_mutex_unlock(&p1->lock);
+
+ ast_channel_unlock(c0);
+ ast_channel_unlock(c1);
+
+ /* Native bridge failed */
+ if ((!master || !slave) && !nothingok) {
+ zt_enable_ec(p0);
+ zt_enable_ec(p1);
+ return AST_BRIDGE_FAILED;
+ }
+
+ ast_verb(3, "Native bridging %s and %s\n", c0->name, c1->name);
+
+ if (!(flags & AST_BRIDGE_DTMF_CHANNEL_0) && (oi0 == SUB_REAL))
+ disable_dtmf_detect(op0);
+
+ if (!(flags & AST_BRIDGE_DTMF_CHANNEL_1) && (oi1 == SUB_REAL))
+ disable_dtmf_detect(op1);
+
+ for (;;) {
+ struct ast_channel *c0_priority[2] = {c0, c1};
+ struct ast_channel *c1_priority[2] = {c1, c0};
+
+ /* Here's our main loop... Start by locking things, looking for private parts,
+ and then balking if anything is wrong */
+
+ ast_channel_lock(c0);
+ while (ast_channel_trylock(c1)) {
+ ast_channel_unlock(c0);
+ usleep(1);
+ ast_channel_lock(c0);
+ }
+
+ p0 = c0->tech_pvt;
+ p1 = c1->tech_pvt;
+
+ if (op0 == p0)
+ i0 = zt_get_index(c0, p0, 1);
+ if (op1 == p1)
+ i1 = zt_get_index(c1, p1, 1);
+
+ ast_channel_unlock(c0);
+ ast_channel_unlock(c1);
+
+ if (!timeoutms ||
+ (op0 != p0) ||
+ (op1 != p1) ||
+ (ofd0 != c0->fds[0]) ||
+ (ofd1 != c1->fds[0]) ||
+ (p0->subs[SUB_REAL].owner && (os0 > -1) && (os0 != p0->subs[SUB_REAL].owner->_state)) ||
+ (p1->subs[SUB_REAL].owner && (os1 > -1) && (os1 != p1->subs[SUB_REAL].owner->_state)) ||
+ (oc0 != p0->owner) ||
+ (oc1 != p1->owner) ||
+ (t0 != p0->subs[SUB_REAL].inthreeway) ||
+ (t1 != p1->subs[SUB_REAL].inthreeway) ||
+ (oi0 != i0) ||
+ (oi1 != i1)) {
+ ast_debug(1, "Something changed out on %d/%d to %d/%d, returning -3 to restart\n",
+ op0->channel, oi0, op1->channel, oi1);
+ res = AST_BRIDGE_RETRY;
+ goto return_from_bridge;
+ }
+
+#ifdef PRI_2BCT
+ q931c0 = p0->call;
+ q931c1 = p1->call;
+ if (p0->transfer && p1->transfer
+ && q931c0 && q931c1
+ && !triedtopribridge) {
+ pri_channel_bridge(q931c0, q931c1);
+ triedtopribridge = 1;
+ }
+#endif
+
+ who = ast_waitfor_n(priority ? c0_priority : c1_priority, 2, &timeoutms);
+ if (!who) {
+ ast_debug(1, "Ooh, empty read...\n");
+ continue;
+ }
+ f = ast_read(who);
+ if (!f || (f->frametype == AST_FRAME_CONTROL)) {
+ *fo = f;
+ *rc = who;
+ res = AST_BRIDGE_COMPLETE;
+ goto return_from_bridge;
+ }
+ if (f->frametype == AST_FRAME_DTMF) {
+ if ((who == c0) && p0->pulsedial) {
+ ast_write(c1, f);
+ } else if ((who == c1) && p1->pulsedial) {
+ ast_write(c0, f);
+ } else {
+ *fo = f;
+ *rc = who;
+ res = AST_BRIDGE_COMPLETE;
+ goto return_from_bridge;
+ }
+ }
+ ast_frfree(f);
+
+ /* Swap who gets priority */
+ priority = !priority;
+ }
+
+return_from_bridge:
+ if (op0 == p0)
+ zt_enable_ec(p0);
+
+ if (op1 == p1)
+ zt_enable_ec(p1);
+
+ if (!(flags & AST_BRIDGE_DTMF_CHANNEL_0) && (oi0 == SUB_REAL))
+ enable_dtmf_detect(op0);
+
+ if (!(flags & AST_BRIDGE_DTMF_CHANNEL_1) && (oi1 == SUB_REAL))
+ enable_dtmf_detect(op1);
+
+ zt_unlink(slave, master, 1);
+
+ return res;
+}
+
+static int zt_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct zt_pvt *p = newchan->tech_pvt;
+ int x;
+ ast_mutex_lock(&p->lock);
+ ast_debug(1, "New owner for channel %d is %s\n", p->channel, newchan->name);
+ if (p->owner == oldchan) {
+ p->owner = newchan;
+ }
+ for (x = 0; x < 3; x++)
+ if (p->subs[x].owner == oldchan) {
+ if (!x)
+ zt_unlink(NULL, p, 0);
+ p->subs[x].owner = newchan;
+ }
+ if (newchan->_state == AST_STATE_RINGING)
+ zt_indicate(newchan, AST_CONTROL_RINGING, NULL, 0);
+ update_conf(p);
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int zt_ring_phone(struct zt_pvt *p)
+{
+ int x;
+ int res;
+ /* Make sure our transmit state is on hook */
+ x = 0;
+ x = ZT_ONHOOK;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_HOOK, &x);
+ do {
+ x = ZT_RING;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_HOOK, &x);
+ if (res) {
+ switch (errno) {
+ case EBUSY:
+ case EINTR:
+ /* Wait just in case */
+ usleep(10000);
+ continue;
+ case EINPROGRESS:
+ res = 0;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Couldn't ring the phone: %s\n", strerror(errno));
+ res = 0;
+ }
+ }
+ } while (res);
+ return res;
+}
+
+static void *ss_thread(void *data);
+
+static struct ast_channel *zt_new(struct zt_pvt *, int, int, int, int, int);
+
+static int attempt_transfer(struct zt_pvt *p)
+{
+ /* In order to transfer, we need at least one of the channels to
+ actually be in a call bridge. We can't conference two applications
+ together (but then, why would we want to?) */
+ if (ast_bridged_channel(p->subs[SUB_REAL].owner)) {
+ /* The three-way person we're about to transfer to could still be in MOH, so
+ stop if now if appropriate */
+ if (ast_bridged_channel(p->subs[SUB_THREEWAY].owner))
+ ast_queue_control(p->subs[SUB_THREEWAY].owner, AST_CONTROL_UNHOLD);
+ if (p->subs[SUB_REAL].owner->_state == AST_STATE_RINGING) {
+ ast_indicate(ast_bridged_channel(p->subs[SUB_REAL].owner), AST_CONTROL_RINGING);
+ }
+ if (p->subs[SUB_THREEWAY].owner->_state == AST_STATE_RING) {
+ tone_zone_play_tone(p->subs[SUB_THREEWAY].zfd, ZT_TONE_RINGTONE);
+ }
+ if (p->subs[SUB_REAL].owner->cdr) {
+ /* Move CDR from second channel to current one */
+ p->subs[SUB_THREEWAY].owner->cdr =
+ ast_cdr_append(p->subs[SUB_THREEWAY].owner->cdr, p->subs[SUB_REAL].owner->cdr);
+ p->subs[SUB_REAL].owner->cdr = NULL;
+ }
+ if (ast_bridged_channel(p->subs[SUB_REAL].owner)->cdr) {
+ /* Move CDR from second channel's bridge to current one */
+ p->subs[SUB_THREEWAY].owner->cdr =
+ ast_cdr_append(p->subs[SUB_THREEWAY].owner->cdr, ast_bridged_channel(p->subs[SUB_REAL].owner)->cdr);
+ ast_bridged_channel(p->subs[SUB_REAL].owner)->cdr = NULL;
+ }
+ if (ast_channel_masquerade(p->subs[SUB_THREEWAY].owner, ast_bridged_channel(p->subs[SUB_REAL].owner))) {
+ ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n",
+ ast_bridged_channel(p->subs[SUB_REAL].owner)->name, p->subs[SUB_THREEWAY].owner->name);
+ return -1;
+ }
+ /* Orphan the channel after releasing the lock */
+ ast_channel_unlock(p->subs[SUB_THREEWAY].owner);
+ unalloc_sub(p, SUB_THREEWAY);
+ } else if (ast_bridged_channel(p->subs[SUB_THREEWAY].owner)) {
+ ast_queue_control(p->subs[SUB_REAL].owner, AST_CONTROL_UNHOLD);
+ if (p->subs[SUB_THREEWAY].owner->_state == AST_STATE_RINGING) {
+ ast_indicate(ast_bridged_channel(p->subs[SUB_THREEWAY].owner), AST_CONTROL_RINGING);
+ }
+ if (p->subs[SUB_REAL].owner->_state == AST_STATE_RING) {
+ tone_zone_play_tone(p->subs[SUB_REAL].zfd, ZT_TONE_RINGTONE);
+ }
+ if (p->subs[SUB_THREEWAY].owner->cdr) {
+ /* Move CDR from second channel to current one */
+ p->subs[SUB_REAL].owner->cdr =
+ ast_cdr_append(p->subs[SUB_REAL].owner->cdr, p->subs[SUB_THREEWAY].owner->cdr);
+ p->subs[SUB_THREEWAY].owner->cdr = NULL;
+ }
+ if (ast_bridged_channel(p->subs[SUB_THREEWAY].owner)->cdr) {
+ /* Move CDR from second channel's bridge to current one */
+ p->subs[SUB_REAL].owner->cdr =
+ ast_cdr_append(p->subs[SUB_REAL].owner->cdr, ast_bridged_channel(p->subs[SUB_THREEWAY].owner)->cdr);
+ ast_bridged_channel(p->subs[SUB_THREEWAY].owner)->cdr = NULL;
+ }
+ if (ast_channel_masquerade(p->subs[SUB_REAL].owner, ast_bridged_channel(p->subs[SUB_THREEWAY].owner))) {
+ ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n",
+ ast_bridged_channel(p->subs[SUB_THREEWAY].owner)->name, p->subs[SUB_REAL].owner->name);
+ return -1;
+ }
+ /* Three-way is now the REAL */
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ ast_channel_unlock(p->subs[SUB_REAL].owner);
+ unalloc_sub(p, SUB_THREEWAY);
+ /* Tell the caller not to hangup */
+ return 1;
+ } else {
+ ast_debug(1, "Neither %s nor %s are in a bridge, nothing to transfer\n",
+ p->subs[SUB_REAL].owner->name, p->subs[SUB_THREEWAY].owner->name);
+ p->subs[SUB_THREEWAY].owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ return -1;
+ }
+ return 0;
+}
+
+static int check_for_conference(struct zt_pvt *p)
+{
+ ZT_CONFINFO ci;
+ /* Fine if we already have a master, etc */
+ if (p->master || (p->confno > -1))
+ return 0;
+ memset(&ci, 0, sizeof(ci));
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_GETCONF, &ci)) {
+ ast_log(LOG_WARNING, "Failed to get conference info on channel %d\n", p->channel);
+ return 0;
+ }
+ /* If we have no master and don't have a confno, then
+ if we're in a conference, it's probably a MeetMe room or
+ some such, so don't let us 3-way out! */
+ if ((p->subs[SUB_REAL].curconf.confno != ci.confno) || (p->subs[SUB_REAL].curconf.confmode != ci.confmode)) {
+ ast_verb(3, "Avoiding 3-way call when in an external conference\n");
+ return 1;
+ }
+ return 0;
+}
+
+/*! Checks channel for alarms
+ * \param p a channel to check for alarms.
+ * \returns the alarms on the span to which the channel belongs, or alarms on
+ * the channel if no span alarms.
+ */
+static int get_alarms(struct zt_pvt *p)
+{
+ int res;
+ ZT_SPANINFO zi;
+#if defined(HAVE_ZAPTEL_CHANALARMS)
+ struct zt_params params;
+#endif
+
+ memset(&zi, 0, sizeof(zi));
+ zi.spanno = p->span;
+ if ((res = ioctl(p->subs[SUB_REAL].zfd, ZT_SPANSTAT, &zi)) >= 0) {
+ if (zi.alarms != ZT_ALARM_NONE)
+ return zi.alarms;
+ }
+
+#if defined(HAVE_ZAPTEL_CHANALARMS)
+ /* No alarms on the span. Check for channel alarms. */
+ if ((res = ioctl(p->subs[SUB_REAL].zfd, ZT_GET_PARAMS, &params)) >= 0)
+ return params.chan_alarms;
+#endif
+
+ ast_log(LOG_WARNING, "Unable to determine alarm on channel %d\n", p->channel);
+
+ return ZT_ALARM_NONE;
+}
+
+static void zt_handle_dtmfup(struct ast_channel *ast, int index, struct ast_frame **dest)
+{
+ struct zt_pvt *p = ast->tech_pvt;
+ struct ast_frame *f = *dest;
+
+ ast_debug(1, "DTMF digit: %c on %s\n", f->subclass, ast->name);
+
+ if (p->confirmanswer) {
+ ast_debug(1, "Confirm answer on %s!\n", ast->name);
+ /* Upon receiving a DTMF digit, consider this an answer confirmation instead
+ of a DTMF digit */
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_ANSWER;
+ *dest = &p->subs[index].f;
+ /* Reset confirmanswer so DTMF's will behave properly for the duration of the call */
+ p->confirmanswer = 0;
+ } else if (p->callwaitcas) {
+ if ((f->subclass == 'A') || (f->subclass == 'D')) {
+ ast_debug(1, "Got some DTMF, but it's for the CAS\n");
+ if (p->cidspill)
+ ast_free(p->cidspill);
+ send_cwcidspill(p);
+ }
+ if ((f->subclass != 'm') && (f->subclass != 'u'))
+ p->callwaitcas = 0;
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.subclass = 0;
+ *dest = &p->subs[index].f;
+ } else if (f->subclass == 'f') {
+ /* Fax tone -- Handle and return NULL */
+ if ((p->callprogress & CALLPROGRESS_FAX) && !p->faxhandled) {
+ p->faxhandled++;
+ if (strcmp(ast->exten, "fax")) {
+ const char *target_context = S_OR(ast->macrocontext, ast->context);
+
+ if (ast_exists_extension(ast, target_context, "fax", 1, ast->cid.cid_num)) {
+ ast_verb(3, "Redirecting %s to fax extension\n", ast->name);
+ /* Save the DID/DNIS when we transfer the fax call to a "fax" extension */
+ pbx_builtin_setvar_helper(ast, "FAXEXTEN", ast->exten);
+ if (ast_async_goto(ast, target_context, "fax", 1))
+ ast_log(LOG_WARNING, "Failed to async goto '%s' into fax of '%s'\n", ast->name, target_context);
+ } else
+ ast_log(LOG_NOTICE, "Fax detected, but no fax extension\n");
+ } else
+ ast_debug(1, "Already in a fax extension, not redirecting\n");
+ } else
+ ast_debug(1, "Fax already handled\n");
+ zt_confmute(p, 0);
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.subclass = 0;
+ *dest = &p->subs[index].f;
+ } else if (f->subclass == 'm') {
+ /* Confmute request */
+ zt_confmute(p, 1);
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.subclass = 0;
+ *dest = &p->subs[index].f;
+ } else if (f->subclass == 'u') {
+ /* Unmute */
+ zt_confmute(p, 0);
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.subclass = 0;
+ *dest = &p->subs[index].f;
+ } else
+ zt_confmute(p, 0);
+}
+
+static struct ast_frame *zt_handle_event(struct ast_channel *ast)
+{
+ int res, x;
+ int index, mysig;
+ char *c;
+ struct zt_pvt *p = ast->tech_pvt;
+ pthread_t threadid;
+ struct ast_channel *chan;
+ struct ast_frame *f;
+
+ index = zt_get_index(ast, p, 0);
+ mysig = p->sig;
+ if (p->outsigmod > -1)
+ mysig = p->outsigmod;
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.subclass = 0;
+ p->subs[index].f.datalen = 0;
+ p->subs[index].f.samples = 0;
+ p->subs[index].f.mallocd = 0;
+ p->subs[index].f.offset = 0;
+ p->subs[index].f.src = "zt_handle_event";
+ p->subs[index].f.data = NULL;
+ f = &p->subs[index].f;
+
+ if (index < 0)
+ return &p->subs[index].f;
+ if (p->fake_event) {
+ res = p->fake_event;
+ p->fake_event = 0;
+ } else
+ res = zt_get_event(p->subs[index].zfd);
+
+ ast_debug(1, "Got event %s(%d) on channel %d (index %d)\n", event2str(res), res, p->channel, index);
+
+ if (res & (ZT_EVENT_PULSEDIGIT | ZT_EVENT_DTMFUP)) {
+ p->pulsedial = (res & ZT_EVENT_PULSEDIGIT) ? 1 : 0;
+ ast_debug(1, "Detected %sdigit '%c'\n", p->pulsedial ? "pulse ": "", res & 0xff);
+#ifdef HAVE_PRI
+ if (!p->proceeding && ((p->sig == SIG_PRI) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP)) && p->pri && (p->pri->overlapdial & ZAP_OVERLAPDIAL_INCOMING)) {
+ /* absorb event */
+ } else {
+#endif
+ p->subs[index].f.frametype = AST_FRAME_DTMF_END;
+ p->subs[index].f.subclass = res & 0xff;
+#ifdef HAVE_PRI
+ }
+#endif
+ zt_handle_dtmfup(ast, index, &f);
+ return f;
+ }
+
+ if (res & ZT_EVENT_DTMFDOWN) {
+ ast_debug(1, "DTMF Down '%c'\n", res & 0xff);
+ /* Mute conference */
+ zt_confmute(p, 1);
+ p->subs[index].f.frametype = AST_FRAME_DTMF_BEGIN;
+ p->subs[index].f.subclass = res & 0xff;
+ return &p->subs[index].f;
+ }
+
+ switch (res) {
+#ifdef ZT_EVENT_EC_DISABLED
+ case ZT_EVENT_EC_DISABLED:
+ ast_verb(3, "Channel %d echo canceler disabled due to CED detection\n", p->channel);
+ p->echocanon = 0;
+ break;
+#endif
+ case ZT_EVENT_BITSCHANGED:
+ ast_log(LOG_WARNING, "Recieved bits changed on %s signalling?\n", sig2str(p->sig));
+ case ZT_EVENT_PULSE_START:
+ /* Stop tone if there's a pulse start and the PBX isn't started */
+ if (!ast->pbx)
+ tone_zone_play_tone(p->subs[index].zfd, -1);
+ break;
+ case ZT_EVENT_DIALCOMPLETE:
+ if (p->inalarm) break;
+ if ((p->radio || (p->oprmode < 0))) break;
+ if (ioctl(p->subs[index].zfd,ZT_DIALING,&x) == -1) {
+ ast_debug(1, "ZT_DIALING ioctl failed on %s\n",ast->name);
+ return NULL;
+ }
+ if (!x) { /* if not still dialing in driver */
+ zt_enable_ec(p);
+ if (p->echobreak) {
+ zt_train_ec(p);
+ ast_copy_string(p->dop.dialstr, p->echorest, sizeof(p->dop.dialstr));
+ p->dop.op = ZT_DIAL_OP_REPLACE;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_DIAL, &p->dop);
+ p->echobreak = 0;
+ } else {
+ p->dialing = 0;
+ if ((mysig == SIG_E911) || (mysig == SIG_FGC_CAMA) || (mysig == SIG_FGC_CAMAMF)) {
+ /* if thru with dialing after offhook */
+ if (ast->_state == AST_STATE_DIALING_OFFHOOK) {
+ ast_setstate(ast, AST_STATE_UP);
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_ANSWER;
+ break;
+ } else { /* if to state wait for offhook to dial rest */
+ /* we now wait for off hook */
+ ast_setstate(ast,AST_STATE_DIALING_OFFHOOK);
+ }
+ }
+ if (ast->_state == AST_STATE_DIALING) {
+ if ((p->callprogress & CALLPROGRESS_PROGRESS) && CANPROGRESSDETECT(p) && p->dsp && p->outgoing) {
+ ast_debug(1, "Done dialing, but waiting for progress detection before doing more...\n");
+ } else if (p->confirmanswer || (!p->dialednone && ((mysig == SIG_EM) || (mysig == SIG_EM_E1) || (mysig == SIG_EMWINK) || (mysig == SIG_FEATD) || (mysig == SIG_FEATDMF_TA) || (mysig == SIG_FEATDMF) || (mysig == SIG_E911) || (mysig == SIG_FGC_CAMA) || (mysig == SIG_FGC_CAMAMF) || (mysig == SIG_FEATB) || (mysig == SIG_SF) || (mysig == SIG_SFWINK) || (mysig == SIG_SF_FEATD) || (mysig == SIG_SF_FEATDMF) || (mysig == SIG_SF_FEATB)))) {
+ ast_setstate(ast, AST_STATE_RINGING);
+ } else if (!p->answeronpolarityswitch) {
+ ast_setstate(ast, AST_STATE_UP);
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_ANSWER;
+ /* If aops=0 and hops=1, this is necessary */
+ p->polarity = POLARITY_REV;
+ } else {
+ /* Start clean, so we can catch the change to REV polarity when party answers */
+ p->polarity = POLARITY_IDLE;
+ }
+ }
+ }
+ }
+ break;
+ case ZT_EVENT_ALARM:
+#ifdef HAVE_PRI
+ if ((p->sig == SIG_PRI) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP)) {
+ if (!p->pri || !p->pri->pri || (pri_get_timer(p->pri->pri, PRI_TIMER_T309) < 0)) {
+ /* T309 is not enabled : hangup calls when alarm occurs */
+ if (p->call) {
+ if (p->pri && p->pri->pri) {
+ if (!pri_grab(p, p->pri)) {
+ pri_hangup(p->pri->pri, p->call, -1);
+ pri_destroycall(p->pri->pri, p->call);
+ p->call = NULL;
+ pri_rel(p->pri);
+ } else
+ ast_log(LOG_WARNING, "Failed to grab PRI!\n");
+ } else
+ ast_log(LOG_WARNING, "The PRI Call has not been destroyed\n");
+ }
+ if (p->owner)
+ p->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ }
+ }
+ if (p->bearer)
+ p->bearer->inalarm = 1;
+ else
+#endif
+ p->inalarm = 1;
+ res = get_alarms(p);
+ ast_log(LOG_WARNING, "Detected alarm on channel %d: %s\n", p->channel, alarm2str(res));
+ manager_event(EVENT_FLAG_SYSTEM, "Alarm",
+ "Alarm: %s\r\n"
+ "Channel: %d\r\n",
+ alarm2str(res), p->channel);
+#ifdef HAVE_PRI
+ if (!p->pri || !p->pri->pri || pri_get_timer(p->pri->pri, PRI_TIMER_T309) < 0) {
+ /* fall through intentionally */
+ } else {
+ break;
+ }
+#endif
+#ifdef HAVE_SS7
+ if (p->sig == SIG_SS7)
+ break;
+#endif
+ case ZT_EVENT_ONHOOK:
+ if (p->radio) {
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_RADIO_UNKEY;
+ break;
+ }
+ if (p->oprmode < 0)
+ {
+ if (p->oprmode != -1) break;
+ if ((p->sig == SIG_FXOLS) || (p->sig == SIG_FXOKS) || (p->sig == SIG_FXOGS))
+ {
+ /* Make sure it starts ringing */
+ zt_set_hook(p->subs[SUB_REAL].zfd, ZT_RINGOFF);
+ zt_set_hook(p->subs[SUB_REAL].zfd, ZT_RING);
+ save_conference(p->oprpeer);
+ tone_zone_play_tone(p->oprpeer->subs[SUB_REAL].zfd, ZT_TONE_RINGTONE);
+ }
+ break;
+ }
+ switch (p->sig) {
+ case SIG_FXOLS:
+ case SIG_FXOGS:
+ case SIG_FXOKS:
+ p->onhooktime = time(NULL);
+ p->msgstate = -1;
+ /* Check for some special conditions regarding call waiting */
+ if (index == SUB_REAL) {
+ /* The normal line was hung up */
+ if (p->subs[SUB_CALLWAIT].owner) {
+ /* There's a call waiting call, so ring the phone, but make it unowned in the mean time */
+ swap_subs(p, SUB_CALLWAIT, SUB_REAL);
+ ast_verb(3, "Channel %d still has (callwait) call, ringing phone\n", p->channel);
+ unalloc_sub(p, SUB_CALLWAIT);
+#if 0
+ p->subs[index].needanswer = 0;
+ p->subs[index].needringing = 0;
+#endif
+ p->callwaitingrepeat = 0;
+ p->cidcwexpire = 0;
+ p->owner = NULL;
+ /* Don't start streaming audio yet if the incoming call isn't up yet */
+ if (p->subs[SUB_REAL].owner->_state != AST_STATE_UP)
+ p->dialing = 1;
+ zt_ring_phone(p);
+ } else if (p->subs[SUB_THREEWAY].owner) {
+ unsigned int mssinceflash;
+ /* Here we have to retain the lock on both the main channel, the 3-way channel, and
+ the private structure -- not especially easy or clean */
+ while (p->subs[SUB_THREEWAY].owner && ast_channel_trylock(p->subs[SUB_THREEWAY].owner)) {
+ /* Yuck, didn't get the lock on the 3-way, gotta release everything and re-grab! */
+ ast_mutex_unlock(&p->lock);
+ ast_channel_unlock(ast);
+ usleep(1);
+ /* We can grab ast and p in that order, without worry. We should make sure
+ nothing seriously bad has happened though like some sort of bizarre double
+ masquerade! */
+ ast_channel_lock(ast);
+ ast_mutex_lock(&p->lock);
+ if (p->owner != ast) {
+ ast_log(LOG_WARNING, "This isn't good...\n");
+ return NULL;
+ }
+ }
+ if (!p->subs[SUB_THREEWAY].owner) {
+ ast_log(LOG_NOTICE, "Whoa, threeway disappeared kinda randomly.\n");
+ return NULL;
+ }
+ mssinceflash = ast_tvdiff_ms(ast_tvnow(), p->flashtime);
+ ast_debug(1, "Last flash was %d ms ago\n", mssinceflash);
+ if (mssinceflash < MIN_MS_SINCE_FLASH) {
+ /* It hasn't been long enough since the last flashook. This is probably a bounce on
+ hanging up. Hangup both channels now */
+ if (p->subs[SUB_THREEWAY].owner)
+ ast_queue_hangup(p->subs[SUB_THREEWAY].owner);
+ p->subs[SUB_THREEWAY].owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ ast_debug(1, "Looks like a bounced flash, hanging up both calls on %d\n", p->channel);
+ ast_channel_unlock(p->subs[SUB_THREEWAY].owner);
+ } else if ((ast->pbx) || (ast->_state == AST_STATE_UP)) {
+ if (p->transfer) {
+ /* In any case this isn't a threeway call anymore */
+ p->subs[SUB_REAL].inthreeway = 0;
+ p->subs[SUB_THREEWAY].inthreeway = 0;
+ /* Only attempt transfer if the phone is ringing; why transfer to busy tone eh? */
+ if (!p->transfertobusy && ast->_state == AST_STATE_BUSY) {
+ ast_channel_unlock(p->subs[SUB_THREEWAY].owner);
+ /* Swap subs and dis-own channel */
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ p->owner = NULL;
+ /* Ring the phone */
+ zt_ring_phone(p);
+ } else {
+ if ((res = attempt_transfer(p)) < 0) {
+ p->subs[SUB_THREEWAY].owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ if (p->subs[SUB_THREEWAY].owner)
+ ast_channel_unlock(p->subs[SUB_THREEWAY].owner);
+ } else if (res) {
+ /* Don't actually hang up at this point */
+ if (p->subs[SUB_THREEWAY].owner)
+ ast_channel_unlock(p->subs[SUB_THREEWAY].owner);
+ break;
+ }
+ }
+ } else {
+ p->subs[SUB_THREEWAY].owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ if (p->subs[SUB_THREEWAY].owner)
+ ast_channel_unlock(p->subs[SUB_THREEWAY].owner);
+ }
+ } else {
+ ast_channel_unlock(p->subs[SUB_THREEWAY].owner);
+ /* Swap subs and dis-own channel */
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ p->owner = NULL;
+ /* Ring the phone */
+ zt_ring_phone(p);
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Got a hangup and my index is %d?\n", index);
+ }
+ /* Fall through */
+ default:
+ zt_disable_ec(p);
+ return NULL;
+ }
+ break;
+ case ZT_EVENT_RINGOFFHOOK:
+ if (p->inalarm) break;
+ if (p->oprmode < 0)
+ {
+ if ((p->sig == SIG_FXOLS) || (p->sig == SIG_FXOKS) || (p->sig == SIG_FXOGS))
+ {
+ /* Make sure it stops ringing */
+ zt_set_hook(p->subs[SUB_REAL].zfd, ZT_RINGOFF);
+ tone_zone_play_tone(p->oprpeer->subs[SUB_REAL].zfd, -1);
+ restore_conference(p->oprpeer);
+ }
+ break;
+ }
+ if (p->radio)
+ {
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_RADIO_KEY;
+ break;
+ }
+ /* for E911, its supposed to wait for offhook then dial
+ the second half of the dial string */
+ if (((mysig == SIG_E911) || (mysig == SIG_FGC_CAMA) || (mysig == SIG_FGC_CAMAMF)) && (ast->_state == AST_STATE_DIALING_OFFHOOK)) {
+ c = strchr(p->dialdest, '/');
+ if (c)
+ c++;
+ else
+ c = p->dialdest;
+ if (*c) snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*0%s#", c);
+ else ast_copy_string(p->dop.dialstr,"M*2#", sizeof(p->dop.dialstr));
+ if (strlen(p->dop.dialstr) > 4) {
+ memset(p->echorest, 'w', sizeof(p->echorest) - 1);
+ strcpy(p->echorest + (p->echotraining / 401) + 1, p->dop.dialstr + strlen(p->dop.dialstr) - 2);
+ p->echorest[sizeof(p->echorest) - 1] = '\0';
+ p->echobreak = 1;
+ p->dop.dialstr[strlen(p->dop.dialstr)-2] = '\0';
+ } else
+ p->echobreak = 0;
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_DIAL, &p->dop)) {
+ x = ZT_ONHOOK;
+ ioctl(p->subs[SUB_REAL].zfd, ZT_HOOK, &x);
+ ast_log(LOG_WARNING, "Dialing failed on channel %d: %s\n", p->channel, strerror(errno));
+ return NULL;
+ }
+ p->dialing = 1;
+ return &p->subs[index].f;
+ }
+ switch (p->sig) {
+ case SIG_FXOLS:
+ case SIG_FXOGS:
+ case SIG_FXOKS:
+ switch (ast->_state) {
+ case AST_STATE_RINGING:
+ zt_enable_ec(p);
+ zt_train_ec(p);
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_ANSWER;
+ /* Make sure it stops ringing */
+ zt_set_hook(p->subs[index].zfd, ZT_OFFHOOK);
+ ast_debug(1, "channel %d answered\n", p->channel);
+ if (p->cidspill) {
+ /* Cancel any running CallerID spill */
+ ast_free(p->cidspill);
+ p->cidspill = NULL;
+ }
+ p->dialing = 0;
+ p->callwaitcas = 0;
+ if (p->confirmanswer) {
+ /* Ignore answer if "confirm answer" is enabled */
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.subclass = 0;
+ } else if (!ast_strlen_zero(p->dop.dialstr)) {
+ /* nick@dccinc.com 4/3/03 - fxo should be able to do deferred dialing */
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_DIAL, &p->dop);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to initiate dialing on trunk channel %d\n", p->channel);
+ p->dop.dialstr[0] = '\0';
+ return NULL;
+ } else {
+ ast_debug(1, "Sent FXO deferred digit string: %s\n", p->dop.dialstr);
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.subclass = 0;
+ p->dialing = 1;
+ }
+ p->dop.dialstr[0] = '\0';
+ ast_setstate(ast, AST_STATE_DIALING);
+ } else
+ ast_setstate(ast, AST_STATE_UP);
+ return &p->subs[index].f;
+ case AST_STATE_DOWN:
+ ast_setstate(ast, AST_STATE_RING);
+ ast->rings = 1;
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_OFFHOOK;
+ ast_debug(1, "channel %d picked up\n", p->channel);
+ return &p->subs[index].f;
+ case AST_STATE_UP:
+ /* Make sure it stops ringing */
+ zt_set_hook(p->subs[index].zfd, ZT_OFFHOOK);
+ /* Okay -- probably call waiting*/
+ if (ast_bridged_channel(p->owner))
+ ast_queue_control(p->owner, AST_CONTROL_UNHOLD);
+ p->subs[index].needunhold = 1;
+ break;
+ case AST_STATE_RESERVED:
+ /* Start up dialtone */
+ if (has_voicemail(p))
+ res = tone_zone_play_tone(p->subs[SUB_REAL].zfd, ZT_TONE_STUTTER);
+ else
+ res = tone_zone_play_tone(p->subs[SUB_REAL].zfd, ZT_TONE_DIALTONE);
+ break;
+ default:
+ ast_log(LOG_WARNING, "FXO phone off hook in weird state %d??\n", ast->_state);
+ }
+ break;
+ case SIG_FXSLS:
+ case SIG_FXSGS:
+ case SIG_FXSKS:
+ if (ast->_state == AST_STATE_RING) {
+ p->ringt = p->ringt_base;
+ }
+
+ /* If we get a ring then we cannot be in
+ * reversed polarity. So we reset to idle */
+ ast_debug(1, "Setting IDLE polarity due "
+ "to ring. Old polarity was %d\n",
+ p->polarity);
+ p->polarity = POLARITY_IDLE;
+
+ /* Fall through */
+ case SIG_EM:
+ case SIG_EM_E1:
+ case SIG_EMWINK:
+ case SIG_FEATD:
+ case SIG_FEATDMF:
+ case SIG_FEATDMF_TA:
+ case SIG_E911:
+ case SIG_FGC_CAMA:
+ case SIG_FGC_CAMAMF:
+ case SIG_FEATB:
+ case SIG_SF:
+ case SIG_SFWINK:
+ case SIG_SF_FEATD:
+ case SIG_SF_FEATDMF:
+ case SIG_SF_FEATB:
+ if (ast->_state == AST_STATE_PRERING)
+ ast_setstate(ast, AST_STATE_RING);
+ if ((ast->_state == AST_STATE_DOWN) || (ast->_state == AST_STATE_RING)) {
+ ast_debug(1, "Ring detected\n");
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_RING;
+ } else if (p->outgoing && ((ast->_state == AST_STATE_RINGING) || (ast->_state == AST_STATE_DIALING))) {
+ ast_debug(1, "Line answered\n");
+ if (p->confirmanswer) {
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.subclass = 0;
+ } else {
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_ANSWER;
+ ast_setstate(ast, AST_STATE_UP);
+ }
+ } else if (ast->_state != AST_STATE_RING)
+ ast_log(LOG_WARNING, "Ring/Off-hook in strange state %d on channel %d\n", ast->_state, p->channel);
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to handle ring/off hook for signalling %d\n", p->sig);
+ }
+ break;
+#ifdef ZT_EVENT_RINGBEGIN
+ case ZT_EVENT_RINGBEGIN:
+ switch (p->sig) {
+ case SIG_FXSLS:
+ case SIG_FXSGS:
+ case SIG_FXSKS:
+ if (ast->_state == AST_STATE_RING) {
+ p->ringt = p->ringt_base;
+ }
+ break;
+ }
+ break;
+#endif
+ case ZT_EVENT_RINGEROFF:
+ if (p->inalarm) break;
+ if ((p->radio || (p->oprmode < 0))) break;
+ ast->rings++;
+ if ((ast->rings > p->cidrings) && (p->cidspill)) {
+ ast_log(LOG_WARNING, "Didn't finish Caller-ID spill. Cancelling.\n");
+ ast_free(p->cidspill);
+ p->cidspill = NULL;
+ p->callwaitcas = 0;
+ }
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_RINGING;
+ break;
+ case ZT_EVENT_RINGERON:
+ break;
+ case ZT_EVENT_NOALARM:
+ p->inalarm = 0;
+#ifdef HAVE_PRI
+ /* Extremely unlikely but just in case */
+ if (p->bearer)
+ p->bearer->inalarm = 0;
+#endif
+ ast_log(LOG_NOTICE, "Alarm cleared on channel %d\n", p->channel);
+ manager_event(EVENT_FLAG_SYSTEM, "AlarmClear",
+ "Channel: %d\r\n", p->channel);
+ break;
+ case ZT_EVENT_WINKFLASH:
+ if (p->inalarm) break;
+ if (p->radio) break;
+ if (p->oprmode < 0) break;
+ if (p->oprmode > 1)
+ {
+ struct zt_params par;
+
+ if (ioctl(p->oprpeer->subs[SUB_REAL].zfd, ZT_GET_PARAMS, &par) != -1)
+ {
+ if (!par.rxisoffhook)
+ {
+ /* Make sure it stops ringing */
+ zt_set_hook(p->oprpeer->subs[SUB_REAL].zfd, ZT_RINGOFF);
+ zt_set_hook(p->oprpeer->subs[SUB_REAL].zfd, ZT_RING);
+ save_conference(p);
+ tone_zone_play_tone(p->subs[SUB_REAL].zfd, ZT_TONE_RINGTONE);
+ }
+ }
+ break;
+ }
+ /* Remember last time we got a flash-hook */
+ p->flashtime = ast_tvnow();
+ switch (mysig) {
+ case SIG_FXOLS:
+ case SIG_FXOGS:
+ case SIG_FXOKS:
+ ast_debug(1, "Winkflash, index: %d, normal: %d, callwait: %d, thirdcall: %d\n",
+ index, p->subs[SUB_REAL].zfd, p->subs[SUB_CALLWAIT].zfd, p->subs[SUB_THREEWAY].zfd);
+ p->callwaitcas = 0;
+
+ if (index != SUB_REAL) {
+ ast_log(LOG_WARNING, "Got flash hook with index %d on channel %d?!?\n", index, p->channel);
+ goto winkflashdone;
+ }
+
+ if (p->subs[SUB_CALLWAIT].owner) {
+ /* Swap to call-wait */
+ swap_subs(p, SUB_REAL, SUB_CALLWAIT);
+ tone_zone_play_tone(p->subs[SUB_REAL].zfd, -1);
+ p->owner = p->subs[SUB_REAL].owner;
+ ast_debug(1, "Making %s the new owner\n", p->owner->name);
+ if (p->owner->_state == AST_STATE_RINGING) {
+ ast_setstate(p->owner, AST_STATE_UP);
+ p->subs[SUB_REAL].needanswer = 1;
+ }
+ p->callwaitingrepeat = 0;
+ p->cidcwexpire = 0;
+ /* Start music on hold if appropriate */
+ if (!p->subs[SUB_CALLWAIT].inthreeway && ast_bridged_channel(p->subs[SUB_CALLWAIT].owner)) {
+ ast_queue_control_data(p->subs[SUB_CALLWAIT].owner, AST_CONTROL_HOLD,
+ S_OR(p->mohsuggest, NULL),
+ !ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
+ }
+ p->subs[SUB_CALLWAIT].needhold = 1;
+ if (ast_bridged_channel(p->subs[SUB_REAL].owner)) {
+ ast_queue_control_data(p->subs[SUB_REAL].owner, AST_CONTROL_HOLD,
+ S_OR(p->mohsuggest, NULL),
+ !ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
+ }
+ p->subs[SUB_REAL].needunhold = 1;
+ } else if (!p->subs[SUB_THREEWAY].owner) {
+ char cid_num[256];
+ char cid_name[256];
+
+ if (!p->threewaycalling) {
+ /* Just send a flash if no 3-way calling */
+ p->subs[SUB_REAL].needflash = 1;
+ goto winkflashdone;
+ } else if (!check_for_conference(p)) {
+ if (p->zaptrcallerid && p->owner) {
+ if (p->owner->cid.cid_num)
+ ast_copy_string(cid_num, p->owner->cid.cid_num, sizeof(cid_num));
+ if (p->owner->cid.cid_name)
+ ast_copy_string(cid_name, p->owner->cid.cid_name, sizeof(cid_name));
+ }
+ /* XXX This section needs much more error checking!!! XXX */
+ /* Start a 3-way call if feasible */
+ if (!((ast->pbx) ||
+ (ast->_state == AST_STATE_UP) ||
+ (ast->_state == AST_STATE_RING))) {
+ ast_debug(1, "Flash when call not up or ringing\n");
+ goto winkflashdone;
+ }
+ if (alloc_sub(p, SUB_THREEWAY)) {
+ ast_log(LOG_WARNING, "Unable to allocate three-way subchannel\n");
+ goto winkflashdone;
+ }
+ /* Make new channel */
+ chan = zt_new(p, AST_STATE_RESERVED, 0, SUB_THREEWAY, 0, 0);
+ if (p->zaptrcallerid) {
+ if (!p->origcid_num)
+ p->origcid_num = ast_strdup(p->cid_num);
+ if (!p->origcid_name)
+ p->origcid_name = ast_strdup(p->cid_name);
+ ast_copy_string(p->cid_num, cid_num, sizeof(p->cid_num));
+ ast_copy_string(p->cid_name, cid_name, sizeof(p->cid_name));
+ }
+ /* Swap things around between the three-way and real call */
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ /* Disable echo canceller for better dialing */
+ zt_disable_ec(p);
+ res = tone_zone_play_tone(p->subs[SUB_REAL].zfd, ZT_TONE_DIALRECALL);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to start dial recall tone on channel %d\n", p->channel);
+ p->owner = chan;
+ if (!chan) {
+ ast_log(LOG_WARNING, "Cannot allocate new structure on channel %d\n", p->channel);
+ } else if (ast_pthread_create_detached(&threadid, NULL, ss_thread, chan)) {
+ ast_log(LOG_WARNING, "Unable to start simple switch on channel %d\n", p->channel);
+ res = tone_zone_play_tone(p->subs[SUB_REAL].zfd, ZT_TONE_CONGESTION);
+ zt_enable_ec(p);
+ ast_hangup(chan);
+ } else {
+ ast_verb(3, "Started three way call on channel %d\n", p->channel);
+ /* Start music on hold if appropriate */
+ if (ast_bridged_channel(p->subs[SUB_THREEWAY].owner)) {
+ ast_queue_control_data(p->subs[SUB_THREEWAY].owner, AST_CONTROL_HOLD,
+ S_OR(p->mohsuggest, NULL),
+ !ast_strlen_zero(p->mohsuggest) ? strlen(p->mohsuggest) + 1 : 0);
+ }
+ p->subs[SUB_THREEWAY].needhold = 1;
+ }
+ }
+ } else {
+ /* Already have a 3 way call */
+ if (p->subs[SUB_THREEWAY].inthreeway) {
+ /* Call is already up, drop the last person */
+ ast_debug(1, "Got flash with three way call up, dropping last call on %d\n", p->channel);
+ /* If the primary call isn't answered yet, use it */
+ if ((p->subs[SUB_REAL].owner->_state != AST_STATE_UP) && (p->subs[SUB_THREEWAY].owner->_state == AST_STATE_UP)) {
+ /* Swap back -- we're dropping the real 3-way that isn't finished yet*/
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ p->owner = p->subs[SUB_REAL].owner;
+ }
+ /* Drop the last call and stop the conference */
+ ast_verb(3, "Dropping three-way call on %s\n", p->subs[SUB_THREEWAY].owner->name);
+ p->subs[SUB_THREEWAY].owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ p->subs[SUB_REAL].inthreeway = 0;
+ p->subs[SUB_THREEWAY].inthreeway = 0;
+ } else {
+ /* Lets see what we're up to */
+ if (((ast->pbx) || (ast->_state == AST_STATE_UP)) &&
+ (p->transfertobusy || (ast->_state != AST_STATE_BUSY))) {
+ int otherindex = SUB_THREEWAY;
+
+ ast_verb(3, "Building conference on call on %s and %s\n", p->subs[SUB_THREEWAY].owner->name, p->subs[SUB_REAL].owner->name);
+ /* Put them in the threeway, and flip */
+ p->subs[SUB_THREEWAY].inthreeway = 1;
+ p->subs[SUB_REAL].inthreeway = 1;
+ if (ast->_state == AST_STATE_UP) {
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ otherindex = SUB_REAL;
+ }
+ if (p->subs[otherindex].owner && ast_bridged_channel(p->subs[otherindex].owner))
+ ast_queue_control(p->subs[otherindex].owner, AST_CONTROL_UNHOLD);
+ p->subs[otherindex].needunhold = 1;
+ p->owner = p->subs[SUB_REAL].owner;
+ if (ast->_state == AST_STATE_RINGING) {
+ ast_debug(1, "Enabling ringtone on real and threeway\n");
+ res = tone_zone_play_tone(p->subs[SUB_REAL].zfd, ZT_TONE_RINGTONE);
+ res = tone_zone_play_tone(p->subs[SUB_THREEWAY].zfd, ZT_TONE_RINGTONE);
+ }
+ } else {
+ ast_verb(3, "Dumping incomplete call on on %s\n", p->subs[SUB_THREEWAY].owner->name);
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ p->subs[SUB_THREEWAY].owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ p->owner = p->subs[SUB_REAL].owner;
+ if (p->subs[SUB_REAL].owner && ast_bridged_channel(p->subs[SUB_REAL].owner))
+ ast_queue_control(p->subs[SUB_REAL].owner, AST_CONTROL_UNHOLD);
+ p->subs[SUB_REAL].needunhold = 1;
+ zt_enable_ec(p);
+ }
+
+ }
+ }
+ winkflashdone:
+ update_conf(p);
+ break;
+ case SIG_EM:
+ case SIG_EM_E1:
+ case SIG_EMWINK:
+ case SIG_FEATD:
+ case SIG_SF:
+ case SIG_SFWINK:
+ case SIG_SF_FEATD:
+ case SIG_FXSLS:
+ case SIG_FXSGS:
+ if (option_debug) {
+ if (p->dialing)
+ ast_debug(1, "Ignoring wink on channel %d\n", p->channel);
+ else
+ ast_debug(1, "Got wink in weird state %d on channel %d\n", ast->_state, p->channel);
+ }
+ break;
+ case SIG_FEATDMF_TA:
+ switch (p->whichwink) {
+ case 0:
+ ast_debug(1, "ANI2 set to '%d' and ANI is '%s'\n", p->owner->cid.cid_ani2, p->owner->cid.cid_ani);
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*%d%s#", p->owner->cid.cid_ani2, p->owner->cid.cid_ani);
+ break;
+ case 1:
+ ast_copy_string(p->dop.dialstr, p->finaldial, sizeof(p->dop.dialstr));
+ break;
+ case 2:
+ ast_log(LOG_WARNING, "Received unexpected wink on channel of type SIG_FEATDMF_TA\n");
+ return NULL;
+ }
+ p->whichwink++;
+ /* Fall through */
+ case SIG_FEATDMF:
+ case SIG_E911:
+ case SIG_FGC_CAMAMF:
+ case SIG_FGC_CAMA:
+ case SIG_FEATB:
+ case SIG_SF_FEATDMF:
+ case SIG_SF_FEATB:
+ /* FGD MF *Must* wait for wink */
+ if (!ast_strlen_zero(p->dop.dialstr))
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_DIAL, &p->dop);
+ else if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to initiate dialing on trunk channel %d\n", p->channel);
+ p->dop.dialstr[0] = '\0';
+ return NULL;
+ } else
+ ast_debug(1, "Sent deferred digit string: %s\n", p->dop.dialstr);
+
+ p->dop.dialstr[0] = '\0';
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to handle ring/off hoook for signalling %d\n", p->sig);
+ }
+ break;
+ case ZT_EVENT_HOOKCOMPLETE:
+ if (p->inalarm) break;
+ if ((p->radio || (p->oprmode < 0))) break;
+ switch (mysig) {
+ case SIG_FXSLS: /* only interesting for FXS */
+ case SIG_FXSGS:
+ case SIG_FXSKS:
+ case SIG_EM:
+ case SIG_EM_E1:
+ case SIG_EMWINK:
+ case SIG_FEATD:
+ case SIG_SF:
+ case SIG_SFWINK:
+ case SIG_SF_FEATD:
+ if (!ast_strlen_zero(p->dop.dialstr))
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_DIAL, &p->dop);
+ else if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to initiate dialing on trunk channel %d\n", p->channel);
+ p->dop.dialstr[0] = '\0';
+ return NULL;
+ } else
+ ast_debug(1, "Sent deferred digit string: %s\n", p->dop.dialstr);
+
+ p->dop.dialstr[0] = '\0';
+ p->dop.op = ZT_DIAL_OP_REPLACE;
+ break;
+ case SIG_FEATDMF:
+ case SIG_FEATDMF_TA:
+ case SIG_E911:
+ case SIG_FGC_CAMA:
+ case SIG_FGC_CAMAMF:
+ case SIG_FEATB:
+ case SIG_SF_FEATDMF:
+ case SIG_SF_FEATB:
+ ast_debug(1, "Got hook complete in MF FGD, waiting for wink now on channel %d\n",p->channel);
+ break;
+ default:
+ break;
+ }
+ break;
+ case ZT_EVENT_POLARITY:
+ /*
+ * If we get a Polarity Switch event, check to see
+ * if we should change the polarity state and
+ * mark the channel as UP or if this is an indication
+ * of remote end disconnect.
+ */
+ if (p->polarity == POLARITY_IDLE) {
+ p->polarity = POLARITY_REV;
+ if (p->answeronpolarityswitch &&
+ ((ast->_state == AST_STATE_DIALING) ||
+ (ast->_state == AST_STATE_RINGING))) {
+ ast_debug(1, "Answering on polarity switch!\n");
+ ast_setstate(p->owner, AST_STATE_UP);
+ if (p->hanguponpolarityswitch) {
+ p->polaritydelaytv = ast_tvnow();
+ }
+ } else
+ ast_debug(1, "Ignore switch to REVERSED Polarity on channel %d, state %d\n", p->channel, ast->_state);
+
+ }
+ /* Removed else statement from here as it was preventing hangups from ever happening*/
+ /* Added AST_STATE_RING in if statement below to deal with calling party hangups that take place when ringing */
+ if (p->hanguponpolarityswitch &&
+ (p->polarityonanswerdelay > 0) &&
+ (p->polarity == POLARITY_REV) &&
+ ((ast->_state == AST_STATE_UP) || (ast->_state == AST_STATE_RING)) ) {
+ /* Added log_debug information below to provide a better indication of what is going on */
+ ast_debug(1, "Polarity Reversal event occured - DEBUG 1: channel %d, state %d, pol= %d, aonp= %d, honp= %d, pdelay= %d, tv= %d\n", p->channel, ast->_state, p->polarity, p->answeronpolarityswitch, p->hanguponpolarityswitch, p->polarityonanswerdelay, ast_tvdiff_ms(ast_tvnow(), p->polaritydelaytv) );
+
+ if (ast_tvdiff_ms(ast_tvnow(), p->polaritydelaytv) > p->polarityonanswerdelay) {
+ ast_debug(1, "Polarity Reversal detected and now Hanging up on channel %d\n", p->channel);
+ ast_softhangup(p->owner, AST_SOFTHANGUP_EXPLICIT);
+ p->polarity = POLARITY_IDLE;
+ } else
+ ast_debug(1, "Polarity Reversal detected but NOT hanging up (too close to answer event) on channel %d, state %d\n", p->channel, ast->_state);
+
+ } else {
+ p->polarity = POLARITY_IDLE;
+ ast_debug(1, "Ignoring Polarity switch to IDLE on channel %d, state %d\n", p->channel, ast->_state);
+ }
+ /* Added more log_debug information below to provide a better indication of what is going on */
+ ast_debug(1, "Polarity Reversal event occured - DEBUG 2: channel %d, state %d, pol= %d, aonp= %d, honp= %d, pdelay= %d, tv= %d\n", p->channel, ast->_state, p->polarity, p->answeronpolarityswitch, p->hanguponpolarityswitch, p->polarityonanswerdelay, ast_tvdiff_ms(ast_tvnow(), p->polaritydelaytv) );
+ break;
+ default:
+ ast_debug(1, "Dunno what to do with event %d on channel %d\n", res, p->channel);
+ }
+ return &p->subs[index].f;
+}
+
+static struct ast_frame *__zt_exception(struct ast_channel *ast)
+{
+ struct zt_pvt *p = ast->tech_pvt;
+ int res;
+ int usedindex=-1;
+ int index;
+ struct ast_frame *f;
+
+
+ index = zt_get_index(ast, p, 1);
+
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.datalen = 0;
+ p->subs[index].f.samples = 0;
+ p->subs[index].f.mallocd = 0;
+ p->subs[index].f.offset = 0;
+ p->subs[index].f.subclass = 0;
+ p->subs[index].f.delivery = ast_tv(0,0);
+ p->subs[index].f.src = "zt_exception";
+ p->subs[index].f.data = NULL;
+
+
+ if ((!p->owner) && (!(p->radio || (p->oprmode < 0)))) {
+ /* If nobody owns us, absorb the event appropriately, otherwise
+ we loop indefinitely. This occurs when, during call waiting, the
+ other end hangs up our channel so that it no longer exists, but we
+ have neither FLASH'd nor ONHOOK'd to signify our desire to
+ change to the other channel. */
+ if (p->fake_event) {
+ res = p->fake_event;
+ p->fake_event = 0;
+ } else
+ res = zt_get_event(p->subs[SUB_REAL].zfd);
+ /* Switch to real if there is one and this isn't something really silly... */
+ if ((res != ZT_EVENT_RINGEROFF) && (res != ZT_EVENT_RINGERON) &&
+ (res != ZT_EVENT_HOOKCOMPLETE)) {
+ ast_debug(1, "Restoring owner of channel %d on event %d\n", p->channel, res);
+ p->owner = p->subs[SUB_REAL].owner;
+ if (p->owner && ast_bridged_channel(p->owner))
+ ast_queue_control(p->owner, AST_CONTROL_UNHOLD);
+ p->subs[SUB_REAL].needunhold = 1;
+ }
+ switch (res) {
+ case ZT_EVENT_ONHOOK:
+ zt_disable_ec(p);
+ if (p->owner) {
+ ast_verb(3, "Channel %s still has call, ringing phone\n", p->owner->name);
+ zt_ring_phone(p);
+ p->callwaitingrepeat = 0;
+ p->cidcwexpire = 0;
+ } else
+ ast_log(LOG_WARNING, "Absorbed on hook, but nobody is left!?!?\n");
+ update_conf(p);
+ break;
+ case ZT_EVENT_RINGOFFHOOK:
+ zt_set_hook(p->subs[SUB_REAL].zfd, ZT_OFFHOOK);
+ if (p->owner && (p->owner->_state == AST_STATE_RINGING)) {
+ p->subs[SUB_REAL].needanswer = 1;
+ p->dialing = 0;
+ }
+ break;
+ case ZT_EVENT_HOOKCOMPLETE:
+ case ZT_EVENT_RINGERON:
+ case ZT_EVENT_RINGEROFF:
+ /* Do nothing */
+ break;
+ case ZT_EVENT_WINKFLASH:
+ p->flashtime = ast_tvnow();
+ if (p->owner) {
+ ast_verb(3, "Channel %d flashed to other channel %s\n", p->channel, p->owner->name);
+ if (p->owner->_state != AST_STATE_UP) {
+ /* Answer if necessary */
+ usedindex = zt_get_index(p->owner, p, 0);
+ if (usedindex > -1) {
+ p->subs[usedindex].needanswer = 1;
+ }
+ ast_setstate(p->owner, AST_STATE_UP);
+ }
+ p->callwaitingrepeat = 0;
+ p->cidcwexpire = 0;
+ if (ast_bridged_channel(p->owner))
+ ast_queue_control(p->owner, AST_CONTROL_UNHOLD);
+ p->subs[SUB_REAL].needunhold = 1;
+ } else
+ ast_log(LOG_WARNING, "Absorbed on hook, but nobody is left!?!?\n");
+ update_conf(p);
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to absorb event %s\n", event2str(res));
+ }
+ f = &p->subs[index].f;
+ return f;
+ }
+ if (!(p->radio || (p->oprmode < 0)))
+ ast_debug(1, "Exception on %d, channel %d\n", ast->fds[0],p->channel);
+ /* If it's not us, return NULL immediately */
+ if (ast != p->owner) {
+ ast_log(LOG_WARNING, "We're %s, not %s\n", ast->name, p->owner->name);
+ f = &p->subs[index].f;
+ return f;
+ }
+ f = zt_handle_event(ast);
+ return f;
+}
+
+static struct ast_frame *zt_exception(struct ast_channel *ast)
+{
+ struct zt_pvt *p = ast->tech_pvt;
+ struct ast_frame *f;
+ ast_mutex_lock(&p->lock);
+ f = __zt_exception(ast);
+ ast_mutex_unlock(&p->lock);
+ return f;
+}
+
+static struct ast_frame *zt_read(struct ast_channel *ast)
+{
+ struct zt_pvt *p = ast->tech_pvt;
+ int res;
+ int index;
+ void *readbuf;
+ struct ast_frame *f;
+
+
+ ast_mutex_lock(&p->lock);
+
+ index = zt_get_index(ast, p, 0);
+
+ /* Hang up if we don't really exist */
+ if (index < 0) {
+ ast_log(LOG_WARNING, "We dont exist?\n");
+ ast_mutex_unlock(&p->lock);
+ return NULL;
+ }
+
+ if ((p->radio || (p->oprmode < 0)) && p->inalarm) return NULL;
+
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.datalen = 0;
+ p->subs[index].f.samples = 0;
+ p->subs[index].f.mallocd = 0;
+ p->subs[index].f.offset = 0;
+ p->subs[index].f.subclass = 0;
+ p->subs[index].f.delivery = ast_tv(0,0);
+ p->subs[index].f.src = "zt_read";
+ p->subs[index].f.data = NULL;
+
+ /* make sure it sends initial key state as first frame */
+ if ((p->radio || (p->oprmode < 0)) && (!p->firstradio))
+ {
+ ZT_PARAMS ps;
+
+ ps.channo = p->channel;
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_GET_PARAMS, &ps) < 0) {
+ ast_mutex_unlock(&p->lock);
+ return NULL;
+ }
+ p->firstradio = 1;
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ if (ps.rxisoffhook)
+ {
+ p->subs[index].f.subclass = AST_CONTROL_RADIO_KEY;
+ }
+ else
+ {
+ p->subs[index].f.subclass = AST_CONTROL_RADIO_UNKEY;
+ }
+ ast_mutex_unlock(&p->lock);
+ return &p->subs[index].f;
+ }
+ if (p->ringt == 1) {
+ ast_mutex_unlock(&p->lock);
+ return NULL;
+ }
+ else if (p->ringt > 0)
+ p->ringt--;
+
+ if (p->subs[index].needringing) {
+ /* Send ringing frame if requested */
+ p->subs[index].needringing = 0;
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_RINGING;
+ ast_setstate(ast, AST_STATE_RINGING);
+ ast_mutex_unlock(&p->lock);
+ return &p->subs[index].f;
+ }
+
+ if (p->subs[index].needbusy) {
+ /* Send busy frame if requested */
+ p->subs[index].needbusy = 0;
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_BUSY;
+ ast_mutex_unlock(&p->lock);
+ return &p->subs[index].f;
+ }
+
+ if (p->subs[index].needcongestion) {
+ /* Send congestion frame if requested */
+ p->subs[index].needcongestion = 0;
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_CONGESTION;
+ ast_mutex_unlock(&p->lock);
+ return &p->subs[index].f;
+ }
+
+ if (p->subs[index].needcallerid) {
+ ast_set_callerid(ast, S_OR(p->lastcid_num, NULL),
+ S_OR(p->lastcid_name, NULL),
+ S_OR(p->lastcid_num, NULL)
+ );
+ p->subs[index].needcallerid = 0;
+ }
+
+ if (p->subs[index].needanswer) {
+ /* Send answer frame if requested */
+ p->subs[index].needanswer = 0;
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_ANSWER;
+ ast_mutex_unlock(&p->lock);
+ return &p->subs[index].f;
+ }
+
+ if (p->subs[index].needflash) {
+ /* Send answer frame if requested */
+ p->subs[index].needflash = 0;
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_FLASH;
+ ast_mutex_unlock(&p->lock);
+ return &p->subs[index].f;
+ }
+
+ if (p->subs[index].needhold) {
+ /* Send answer frame if requested */
+ p->subs[index].needhold = 0;
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_HOLD;
+ ast_mutex_unlock(&p->lock);
+ ast_debug(1, "Sending hold on '%s'\n", ast->name);
+ return &p->subs[index].f;
+ }
+
+ if (p->subs[index].needunhold) {
+ /* Send answer frame if requested */
+ p->subs[index].needunhold = 0;
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_UNHOLD;
+ ast_mutex_unlock(&p->lock);
+ ast_debug(1, "Sending unhold on '%s'\n", ast->name);
+ return &p->subs[index].f;
+ }
+
+ if (ast->rawreadformat == AST_FORMAT_SLINEAR) {
+ if (!p->subs[index].linear) {
+ p->subs[index].linear = 1;
+ res = zt_setlinear(p->subs[index].zfd, p->subs[index].linear);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set channel %d (index %d) to linear mode.\n", p->channel, index);
+ }
+ } else if ((ast->rawreadformat == AST_FORMAT_ULAW) ||
+ (ast->rawreadformat == AST_FORMAT_ALAW)) {
+ if (p->subs[index].linear) {
+ p->subs[index].linear = 0;
+ res = zt_setlinear(p->subs[index].zfd, p->subs[index].linear);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set channel %d (index %d) to companded mode.\n", p->channel, index);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Don't know how to read frames in format %s\n", ast_getformatname(ast->rawreadformat));
+ ast_mutex_unlock(&p->lock);
+ return NULL;
+ }
+ readbuf = ((unsigned char *)p->subs[index].buffer) + AST_FRIENDLY_OFFSET;
+ CHECK_BLOCKING(ast);
+ res = read(p->subs[index].zfd, readbuf, p->subs[index].linear ? READ_SIZE * 2 : READ_SIZE);
+ ast_clear_flag(ast, AST_FLAG_BLOCKING);
+ /* Check for hangup */
+ if (res < 0) {
+ f = NULL;
+ if (res == -1) {
+ if (errno == EAGAIN) {
+ /* Return "NULL" frame if there is nobody there */
+ ast_mutex_unlock(&p->lock);
+ return &p->subs[index].f;
+ } else if (errno == ELAST) {
+ f = __zt_exception(ast);
+ } else
+ ast_log(LOG_WARNING, "zt_rec: %s\n", strerror(errno));
+ }
+ ast_mutex_unlock(&p->lock);
+ return f;
+ }
+ if (res != (p->subs[index].linear ? READ_SIZE * 2 : READ_SIZE)) {
+ ast_debug(1, "Short read (%d/%d), must be an event...\n", res, p->subs[index].linear ? READ_SIZE * 2 : READ_SIZE);
+ f = __zt_exception(ast);
+ ast_mutex_unlock(&p->lock);
+ return f;
+ }
+ if (p->tdd) { /* if in TDD mode, see if we receive that */
+ int c;
+
+ c = tdd_feed(p->tdd,readbuf,READ_SIZE);
+ if (c < 0) {
+ ast_debug(1,"tdd_feed failed\n");
+ ast_mutex_unlock(&p->lock);
+ return NULL;
+ }
+ if (c) { /* if a char to return */
+ p->subs[index].f.subclass = 0;
+ p->subs[index].f.frametype = AST_FRAME_TEXT;
+ p->subs[index].f.mallocd = 0;
+ p->subs[index].f.offset = AST_FRIENDLY_OFFSET;
+ p->subs[index].f.data = p->subs[index].buffer + AST_FRIENDLY_OFFSET;
+ p->subs[index].f.datalen = 1;
+ *((char *) p->subs[index].f.data) = c;
+ ast_mutex_unlock(&p->lock);
+ return &p->subs[index].f;
+ }
+ }
+ if (p->callwaitingrepeat)
+ p->callwaitingrepeat--;
+ if (p->cidcwexpire)
+ p->cidcwexpire--;
+ /* Repeat callwaiting */
+ if (p->callwaitingrepeat == 1) {
+ p->callwaitrings++;
+ zt_callwait(ast);
+ }
+ /* Expire CID/CW */
+ if (p->cidcwexpire == 1) {
+ ast_verb(3, "CPE does not support Call Waiting Caller*ID.\n");
+ restore_conference(p);
+ }
+ if (p->subs[index].linear) {
+ p->subs[index].f.datalen = READ_SIZE * 2;
+ } else
+ p->subs[index].f.datalen = READ_SIZE;
+
+ /* Handle CallerID Transmission */
+ if ((p->owner == ast) && p->cidspill &&((ast->_state == AST_STATE_UP) || (ast->rings == p->cidrings))) {
+ send_callerid(p);
+ }
+
+ p->subs[index].f.frametype = AST_FRAME_VOICE;
+ p->subs[index].f.subclass = ast->rawreadformat;
+ p->subs[index].f.samples = READ_SIZE;
+ p->subs[index].f.mallocd = 0;
+ p->subs[index].f.offset = AST_FRIENDLY_OFFSET;
+ p->subs[index].f.data = p->subs[index].buffer + AST_FRIENDLY_OFFSET / sizeof(p->subs[index].buffer[0]);
+#if 0
+ ast_debug(1, "Read %d of voice on %s\n", p->subs[index].f.datalen, ast->name);
+#endif
+ if (p->dialing || /* Transmitting something */
+ (index && (ast->_state != AST_STATE_UP)) || /* Three-way or callwait that isn't up */
+ ((index == SUB_CALLWAIT) && !p->subs[SUB_CALLWAIT].inthreeway) /* Inactive and non-confed call-wait */
+ ) {
+ /* Whoops, we're still dialing, or in a state where we shouldn't transmit....
+ don't send anything */
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.subclass = 0;
+ p->subs[index].f.samples = 0;
+ p->subs[index].f.mallocd = 0;
+ p->subs[index].f.offset = 0;
+ p->subs[index].f.data = NULL;
+ p->subs[index].f.datalen= 0;
+ }
+ if (p->dsp && (!p->ignoredtmf || p->callwaitcas || p->busydetect || p->callprogress) && !index) {
+ /* Perform busy detection. etc on the zap line */
+ f = ast_dsp_process(ast, p->dsp, &p->subs[index].f);
+ if (f) {
+ if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_BUSY)) {
+ if ((ast->_state == AST_STATE_UP) && !p->outgoing) {
+ /* Treat this as a "hangup" instead of a "busy" on the assumption that
+ a busy */
+ f = NULL;
+ }
+ } else if (f->frametype == AST_FRAME_DTMF) {
+#ifdef HAVE_PRI
+ if (!p->proceeding && ((p->sig == SIG_PRI) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP)) && p->pri &&
+ ((!p->outgoing && (p->pri->overlapdial & ZAP_OVERLAPDIAL_INCOMING)) ||
+ (p->outgoing && (p->pri->overlapdial & ZAP_OVERLAPDIAL_OUTGOING)))) {
+ /* Don't accept in-band DTMF when in overlap dial mode */
+ f->frametype = AST_FRAME_NULL;
+ f->subclass = 0;
+ }
+#endif
+ /* DSP clears us of being pulse */
+ p->pulsedial = 0;
+ }
+ }
+ } else
+ f = &p->subs[index].f;
+
+ if (f && (f->frametype == AST_FRAME_DTMF))
+ zt_handle_dtmfup(ast, index, &f);
+
+ /* If we have a fake_event, trigger exception to handle it */
+ if (p->fake_event)
+ ast_set_flag(ast, AST_FLAG_EXCEPTION);
+
+ ast_mutex_unlock(&p->lock);
+ return f;
+}
+
+static int my_zt_write(struct zt_pvt *p, unsigned char *buf, int len, int index, int linear)
+{
+ int sent=0;
+ int size;
+ int res;
+ int fd;
+ fd = p->subs[index].zfd;
+ while (len) {
+ size = len;
+ if (size > (linear ? READ_SIZE * 2 : READ_SIZE))
+ size = (linear ? READ_SIZE * 2 : READ_SIZE);
+ res = write(fd, buf, size);
+ if (res != size) {
+ ast_debug(1, "Write returned %d (%s) on channel %d\n", res, strerror(errno), p->channel);
+ return sent;
+ }
+ len -= size;
+ buf += size;
+ }
+ return sent;
+}
+
+static int zt_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct zt_pvt *p = ast->tech_pvt;
+ int res;
+ int index;
+ index = zt_get_index(ast, p, 0);
+ if (index < 0) {
+ ast_log(LOG_WARNING, "%s doesn't really exist?\n", ast->name);
+ return -1;
+ }
+
+#if 0
+#ifdef HAVE_PRI
+ ast_mutex_lock(&p->lock);
+ if (!p->proceeding && p->sig==SIG_PRI && p->pri && !p->outgoing) {
+ if (p->pri->pri) {
+ if (!pri_grab(p, p->pri)) {
+ pri_progress(p->pri->pri,p->call, PVT_TO_CHANNEL(p), !p->digital);
+ pri_rel(p->pri);
+ } else
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->span);
+ }
+ p->proceeding=1;
+ }
+ ast_mutex_unlock(&p->lock);
+#endif
+#endif
+ /* Write a frame of (presumably voice) data */
+ if (frame->frametype != AST_FRAME_VOICE) {
+ if (frame->frametype != AST_FRAME_IMAGE)
+ ast_log(LOG_WARNING, "Don't know what to do with frame type '%d'\n", frame->frametype);
+ return 0;
+ }
+ if ((frame->subclass != AST_FORMAT_SLINEAR) &&
+ (frame->subclass != AST_FORMAT_ULAW) &&
+ (frame->subclass != AST_FORMAT_ALAW)) {
+ ast_log(LOG_WARNING, "Cannot handle frames in %d format\n", frame->subclass);
+ return -1;
+ }
+ if (p->dialing) {
+ ast_debug(1, "Dropping frame since I'm still dialing on %s...\n",ast->name);
+ return 0;
+ }
+ if (!p->owner) {
+ ast_debug(1, "Dropping frame since there is no active owner on %s...\n",ast->name);
+ return 0;
+ }
+ if (p->cidspill) {
+ ast_debug(1, "Dropping frame since I've still got a callerid spill\n");
+ return 0;
+ }
+ /* Return if it's not valid data */
+ if (!frame->data || !frame->datalen)
+ return 0;
+
+ if (frame->subclass == AST_FORMAT_SLINEAR) {
+ if (!p->subs[index].linear) {
+ p->subs[index].linear = 1;
+ res = zt_setlinear(p->subs[index].zfd, p->subs[index].linear);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set linear mode on channel %d\n", p->channel);
+ }
+ res = my_zt_write(p, (unsigned char *)frame->data, frame->datalen, index, 1);
+ } else {
+ /* x-law already */
+ if (p->subs[index].linear) {
+ p->subs[index].linear = 0;
+ res = zt_setlinear(p->subs[index].zfd, p->subs[index].linear);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set companded mode on channel %d\n", p->channel);
+ }
+ res = my_zt_write(p, (unsigned char *)frame->data, frame->datalen, index, 0);
+ }
+ if (res < 0) {
+ ast_log(LOG_WARNING, "write failed: %s\n", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static int zt_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen)
+{
+ struct zt_pvt *p = chan->tech_pvt;
+ int res=-1;
+ int index;
+ int func = ZT_FLASH;
+ ast_mutex_lock(&p->lock);
+ index = zt_get_index(chan, p, 0);
+ ast_debug(1, "Requested indication %d on channel %s\n", condition, chan->name);
+ if (index == SUB_REAL) {
+ switch (condition) {
+ case AST_CONTROL_BUSY:
+#ifdef HAVE_PRI
+ if (p->priindication_oob && ((p->sig == SIG_PRI) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP))) {
+ chan->hangupcause = AST_CAUSE_USER_BUSY;
+ chan->_softhangup |= AST_SOFTHANGUP_DEV;
+ res = 0;
+ } else if (!p->progress &&
+ ((p->sig == SIG_PRI) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP))
+ && p->pri && !p->outgoing) {
+ if (p->pri->pri) {
+ if (!pri_grab(p, p->pri)) {
+ pri_progress(p->pri->pri,p->call, PVT_TO_CHANNEL(p), 1);
+ pri_rel(p->pri);
+ }
+ else
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->span);
+ }
+ p->progress = 1;
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_BUSY);
+ } else
+#endif
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_BUSY);
+ break;
+ case AST_CONTROL_RINGING:
+#ifdef HAVE_PRI
+ if ((!p->alerting) && ((p->sig == SIG_PRI) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP))
+ && p->pri && !p->outgoing && (chan->_state != AST_STATE_UP)) {
+ if (p->pri->pri) {
+ if (!pri_grab(p, p->pri)) {
+ pri_acknowledge(p->pri->pri,p->call, PVT_TO_CHANNEL(p), !p->digital);
+ pri_rel(p->pri);
+ }
+ else
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->span);
+ }
+ p->alerting = 1;
+ }
+
+#endif
+#ifdef HAVE_SS7
+ if ((!p->alerting) && (p->sig == SIG_SS7) && p->ss7 && !p->outgoing && (chan->_state != AST_STATE_UP)) {
+ if (p->ss7->ss7) {
+ ss7_grab(p, p->ss7);
+
+ if ((isup_far(p->ss7->ss7, p->ss7call)) != -1)
+ p->rlt = 1;
+ if (p->rlt != 1) /* No need to send CPG if call will be RELEASE */
+ isup_cpg(p->ss7->ss7, p->ss7call, CPG_EVENT_ALERTING);
+ p->alerting = 1;
+ ss7_rel(p->ss7);
+ }
+ }
+#endif
+
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_RINGTONE);
+
+ if (chan->_state != AST_STATE_UP) {
+ if ((chan->_state != AST_STATE_RING) ||
+ ((p->sig != SIG_FXSKS) &&
+ (p->sig != SIG_FXSLS) &&
+ (p->sig != SIG_FXSGS)))
+ ast_setstate(chan, AST_STATE_RINGING);
+ }
+ break;
+ case AST_CONTROL_PROCEEDING:
+ ast_debug(1,"Received AST_CONTROL_PROCEEDING on %s\n",chan->name);
+#ifdef HAVE_PRI
+ if (!p->proceeding && ((p->sig == SIG_PRI) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP))
+ && p->pri && !p->outgoing) {
+ if (p->pri->pri) {
+ if (!pri_grab(p, p->pri)) {
+ pri_proceeding(p->pri->pri,p->call, PVT_TO_CHANNEL(p), !p->digital);
+ pri_rel(p->pri);
+ }
+ else
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->span);
+ }
+ p->proceeding = 1;
+ }
+#endif
+#ifdef HAVE_SS7
+ /* This IF sends the FAR for an answered ALEG call */
+ if (chan->_state == AST_STATE_UP && (p->rlt != 1) && (p->sig == SIG_SS7)){
+ if ((isup_far(p->ss7->ss7, p->ss7call)) != -1)
+ p->rlt = 1;
+ }
+
+ if (!p->proceeding && p->sig == SIG_SS7 && p->ss7 && !p->outgoing) {
+ if (p->ss7->ss7) {
+ ss7_grab(p, p->ss7);
+ isup_acm(p->ss7->ss7, p->ss7call);
+ p->proceeding = 1;
+ ss7_rel(p->ss7);
+
+ }
+ }
+#endif
+ /* don't continue in ast_indicate */
+ res = 0;
+ break;
+ case AST_CONTROL_PROGRESS:
+ ast_debug(1,"Received AST_CONTROL_PROGRESS on %s\n",chan->name);
+#ifdef HAVE_PRI
+ p->digital = 0; /* Digital-only calls isn't allows any inband progress messages */
+ if (!p->progress && ((p->sig == SIG_PRI) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP))
+ && p->pri && !p->outgoing) {
+ if (p->pri->pri) {
+ if (!pri_grab(p, p->pri)) {
+ pri_progress(p->pri->pri,p->call, PVT_TO_CHANNEL(p), 1);
+ pri_rel(p->pri);
+ }
+ else
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->span);
+ }
+ p->progress = 1;
+ }
+#endif
+#ifdef HAVE_SS7
+ if (!p->progress && p->sig==SIG_SS7 && p->ss7 && !p->outgoing) {
+ if (p->ss7->ss7) {
+ ss7_grab(p, p->ss7);
+ isup_cpg(p->ss7->ss7, p->ss7call, CPG_EVENT_INBANDINFO);
+ p->progress = 1;
+ ss7_rel(p->ss7);
+
+ }
+ }
+#endif
+ /* don't continue in ast_indicate */
+ res = 0;
+ break;
+ case AST_CONTROL_CONGESTION:
+ chan->hangupcause = AST_CAUSE_CONGESTION;
+#ifdef HAVE_PRI
+ if (p->priindication_oob && ((p->sig == SIG_PRI) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP))) {
+ chan->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ chan->_softhangup |= AST_SOFTHANGUP_DEV;
+ res = 0;
+ } else if (!p->progress && ((p->sig == SIG_PRI) || (p->sig == SIG_BRI) || (p->sig == SIG_BRI_PTMP))
+ && p->pri && !p->outgoing) {
+ if (p->pri) {
+ if (!pri_grab(p, p->pri)) {
+ pri_progress(p->pri->pri,p->call, PVT_TO_CHANNEL(p), 1);
+ pri_rel(p->pri);
+ } else
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->span);
+ }
+ p->progress = 1;
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);
+ } else
+#endif
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);
+ break;
+ case AST_CONTROL_HOLD:
+#ifdef HAVE_PRI
+ if (p->pri && !strcasecmp(p->mohinterpret, "passthrough")) {
+ if (!pri_grab(p, p->pri)) {
+ res = pri_notify(p->pri->pri, p->call, p->prioffset, PRI_NOTIFY_REMOTE_HOLD);
+ pri_rel(p->pri);
+ } else
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->span);
+ } else
+#endif
+ ast_moh_start(chan, data, p->mohinterpret);
+ break;
+ case AST_CONTROL_UNHOLD:
+#ifdef HAVE_PRI
+ if (p->pri && !strcasecmp(p->mohinterpret, "passthrough")) {
+ if (!pri_grab(p, p->pri)) {
+ res = pri_notify(p->pri->pri, p->call, p->prioffset, PRI_NOTIFY_REMOTE_RETRIEVAL);
+ pri_rel(p->pri);
+ } else
+ ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->span);
+ } else
+#endif
+ ast_moh_stop(chan);
+ break;
+ case AST_CONTROL_RADIO_KEY:
+ if (p->radio)
+ res = zt_set_hook(p->subs[index].zfd, ZT_OFFHOOK);
+ res = 0;
+ break;
+ case AST_CONTROL_RADIO_UNKEY:
+ if (p->radio)
+ res = zt_set_hook(p->subs[index].zfd, ZT_RINGOFF);
+ res = 0;
+ break;
+ case AST_CONTROL_FLASH:
+ /* flash hookswitch */
+ if (ISTRUNK(p) && (p->sig != SIG_PRI)) {
+ /* Clear out the dial buffer */
+ p->dop.dialstr[0] = '\0';
+ if ((ioctl(p->subs[SUB_REAL].zfd,ZT_HOOK,&func) == -1) && (errno != EINPROGRESS)) {
+ ast_log(LOG_WARNING, "Unable to flash external trunk on channel %s: %s\n",
+ chan->name, strerror(errno));
+ } else
+ res = 0;
+ } else
+ res = 0;
+ break;
+ case -1:
+ res = tone_zone_play_tone(p->subs[index].zfd, -1);
+ break;
+ }
+ } else
+ res = 0;
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static struct ast_channel *zt_new(struct zt_pvt *i, int state, int startpbx, int index, int law, int transfercapability)
+{
+ struct ast_channel *tmp;
+ int deflaw;
+ int res;
+ int x,y;
+ int features;
+ struct ast_str *chan_name;
+ struct ast_variable *v;
+ ZT_PARAMS ps;
+ if (i->subs[index].owner) {
+ ast_log(LOG_WARNING, "Channel %d already has a %s call\n", i->channel,subnames[index]);
+ return NULL;
+ }
+ y = 1;
+ chan_name = ast_str_alloca(32);
+ do {
+#ifdef HAVE_PRI
+ if (i->bearer || (i->pri && (i->sig == SIG_FXSKS)))
+ ast_str_set(&chan_name, 0, "%d:%d-%d", i->pri->trunkgroup, i->channel, y);
+ else
+#endif
+ if (i->channel == CHAN_PSEUDO)
+ ast_str_set(&chan_name, 0, "pseudo-%ld", ast_random());
+ else
+ ast_str_set(&chan_name, 0, "%d-%d", i->channel, y);
+ for (x = 0; x < 3; x++) {
+ if ((index != x) && i->subs[x].owner && !strcasecmp(chan_name->str, i->subs[x].owner->name))
+ break;
+ }
+ y++;
+ } while (x < 3);
+ tmp = ast_channel_alloc(0, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, i->amaflags, "Zap/%s", chan_name->str);
+ if (!tmp)
+ return NULL;
+ tmp->tech = &zap_tech;
+ ps.channo = i->channel;
+ res = ioctl(i->subs[SUB_REAL].zfd, ZT_GET_PARAMS, &ps);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to get parameters, assuming MULAW\n");
+ ps.curlaw = ZT_LAW_MULAW;
+ }
+ if (ps.curlaw == ZT_LAW_ALAW)
+ deflaw = AST_FORMAT_ALAW;
+ else
+ deflaw = AST_FORMAT_ULAW;
+ if (law) {
+ if (law == ZT_LAW_ALAW)
+ deflaw = AST_FORMAT_ALAW;
+ else
+ deflaw = AST_FORMAT_ULAW;
+ }
+ ast_channel_set_fd(tmp, 0, i->subs[index].zfd);
+ tmp->nativeformats = AST_FORMAT_SLINEAR | deflaw;
+ /* Start out assuming ulaw since it's smaller :) */
+ tmp->rawreadformat = deflaw;
+ tmp->readformat = deflaw;
+ tmp->rawwriteformat = deflaw;
+ tmp->writeformat = deflaw;
+ i->subs[index].linear = 0;
+ zt_setlinear(i->subs[index].zfd, i->subs[index].linear);
+ features = 0;
+ if (index == SUB_REAL) {
+ if (i->busydetect && CANBUSYDETECT(i))
+ features |= DSP_FEATURE_BUSY_DETECT;
+ if ((i->callprogress & CALLPROGRESS_PROGRESS) && CANPROGRESSDETECT(i))
+ features |= DSP_FEATURE_CALL_PROGRESS;
+ if ((!i->outgoing && (i->callprogress & CALLPROGRESS_FAX_INCOMING)) ||
+ (i->outgoing && (i->callprogress & CALLPROGRESS_FAX_OUTGOING))) {
+ features |= DSP_FEATURE_FAX_DETECT;
+ }
+#ifdef ZT_TONEDETECT
+ x = ZT_TONEDETECT_ON | ZT_TONEDETECT_MUTE;
+ if (ioctl(i->subs[index].zfd, ZT_TONEDETECT, &x)) {
+#endif
+ i->hardwaredtmf = 0;
+ features |= DSP_FEATURE_DTMF_DETECT;
+#ifdef ZT_TONEDETECT
+ } else if (NEED_MFDETECT(i)) {
+ i->hardwaredtmf = 1;
+ features |= DSP_FEATURE_DTMF_DETECT;
+ }
+#endif
+ }
+ if (features) {
+ if (i->dsp) {
+ ast_debug(1, "Already have a dsp on %s?\n", tmp->name);
+ } else {
+ if (i->channel != CHAN_PSEUDO)
+ i->dsp = ast_dsp_new();
+ else
+ i->dsp = NULL;
+ if (i->dsp) {
+ i->dsp_features = features & ~DSP_PROGRESS_TALK;
+#if defined(HAVE_PRI) || defined(HAVE_SS7)
+ /* We cannot do progress detection until receives PROGRESS message */
+ if (i->outgoing && ((i->sig == SIG_PRI) || (i->sig == SIG_BRI) || (i->sig == SIG_BRI_PTMP) || (i->sig == SIG_SS7))) {
+ /* Remember requested DSP features, don't treat
+ talking as ANSWER */
+ features = 0;
+ }
+#endif
+ ast_dsp_set_features(i->dsp, features);
+ ast_dsp_digitmode(i->dsp, DSP_DIGITMODE_DTMF | i->dtmfrelax);
+ if (!ast_strlen_zero(progzone))
+ ast_dsp_set_call_progress_zone(i->dsp, progzone);
+ if (i->busydetect && CANBUSYDETECT(i)) {
+ ast_dsp_set_busy_count(i->dsp, i->busycount);
+ ast_dsp_set_busy_pattern(i->dsp, i->busy_tonelength, i->busy_quietlength);
+ }
+ }
+ }
+ }
+
+ if (state == AST_STATE_RING)
+ tmp->rings = 1;
+ tmp->tech_pvt = i;
+ if ((i->sig == SIG_FXOKS) || (i->sig == SIG_FXOGS) || (i->sig == SIG_FXOLS)) {
+ /* Only FXO signalled stuff can be picked up */
+ tmp->callgroup = i->callgroup;
+ tmp->pickupgroup = i->pickupgroup;
+ }
+ if (!ast_strlen_zero(i->language))
+ ast_string_field_set(tmp, language, i->language);
+ if (!i->owner)
+ i->owner = tmp;
+ if (!ast_strlen_zero(i->accountcode))
+ ast_string_field_set(tmp, accountcode, i->accountcode);
+ if (i->amaflags)
+ tmp->amaflags = i->amaflags;
+ i->subs[index].owner = tmp;
+ ast_copy_string(tmp->context, i->context, sizeof(tmp->context));
+ ast_string_field_set(tmp, call_forward, i->call_forward);
+ /* If we've been told "no ADSI" then enforce it */
+ if (!i->adsi)
+ tmp->adsicpe = AST_ADSI_UNAVAILABLE;
+ if (!ast_strlen_zero(i->exten))
+ ast_copy_string(tmp->exten, i->exten, sizeof(tmp->exten));
+ if (!ast_strlen_zero(i->rdnis))
+ tmp->cid.cid_rdnis = ast_strdup(i->rdnis);
+ if (!ast_strlen_zero(i->dnid))
+ tmp->cid.cid_dnid = ast_strdup(i->dnid);
+
+ /* Don't use ast_set_callerid() here because it will
+ * generate a needless NewCallerID event */
+#ifdef PRI_ANI
+ if (!ast_strlen_zero(i->cid_ani))
+ tmp->cid.cid_ani = ast_strdup(i->cid_ani);
+ else
+ tmp->cid.cid_ani = ast_strdup(i->cid_num);
+#else
+ tmp->cid.cid_ani = ast_strdup(i->cid_num);
+#endif
+ tmp->cid.cid_pres = i->callingpres;
+ tmp->cid.cid_ton = i->cid_ton;
+ tmp->cid.cid_ani2 = i->cid_ani2;
+#if defined(HAVE_PRI) || defined(HAVE_SS7)
+ tmp->transfercapability = transfercapability;
+ pbx_builtin_setvar_helper(tmp, "TRANSFERCAPABILITY", ast_transfercapability2str(transfercapability));
+ if (transfercapability & AST_TRANS_CAP_DIGITAL)
+ i->digital = 1;
+ /* Assume calls are not idle calls unless we're told differently */
+ i->isidlecall = 0;
+ i->alreadyhungup = 0;
+#endif
+ /* clear the fake event in case we posted one before we had ast_channel */
+ i->fake_event = 0;
+ /* Assure there is no confmute on this channel */
+ zt_confmute(i, 0);
+ /* Configure the new channel jb */
+ ast_jb_configure(tmp, &global_jbconf);
+
+ for (v = i->vars ; v ; v = v->next)
+ pbx_builtin_setvar_helper(tmp, v->name, v->value);
+
+ if (startpbx) {
+ if (ast_pbx_start(tmp)) {
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
+ ast_hangup(tmp);
+ i->owner = NULL;
+ return NULL;
+ }
+ }
+
+ ast_module_ref(ast_module_info->self);
+ return tmp;
+}
+
+
+static int my_getsigstr(struct ast_channel *chan, char *str, const char *term, int ms)
+{
+ char c;
+
+ *str = 0; /* start with empty output buffer */
+ for (;;)
+ {
+ /* Wait for the first digit (up to specified ms). */
+ c = ast_waitfordigit(chan, ms);
+ /* if timeout, hangup or error, return as such */
+ if (c < 1)
+ return c;
+ *str++ = c;
+ *str = 0;
+ if (strchr(term, c))
+ return 1;
+ }
+}
+
+static int zt_wink(struct zt_pvt *p, int index)
+{
+ int j;
+ zt_set_hook(p->subs[index].zfd, ZT_WINK);
+ for (;;)
+ {
+ /* set bits of interest */
+ j = ZT_IOMUX_SIGEVENT;
+ /* wait for some happening */
+ if (ioctl(p->subs[index].zfd,ZT_IOMUX,&j) == -1) return(-1);
+ /* exit loop if we have it */
+ if (j & ZT_IOMUX_SIGEVENT) break;
+ }
+ /* get the event info */
+ if (ioctl(p->subs[index].zfd,ZT_GETEVENT,&j) == -1) return(-1);
+ return 0;
+}
+
+/*! enable or disable the chan_zap Do-Not-Disturb mode for a Zaptel channel
+ * @zapchan "Physical" Zaptel channel (e.g: Zap/5)
+ * @on: 1 to enable, 0 to disable
+ *
+ * chan_zap has a DND (Do Not Disturb) mode for each zapchan (physical
+ * zaptel channel). Use this to enable or disable it.
+ *
+ * \fixme the use of the word "channel" for those zapchans is really
+ * confusing.
+ */
+static void zap_dnd(struct zt_pvt *zapchan, int on)
+{
+ /* Do not disturb */
+ zapchan->dnd = on;
+ ast_verb(3, "%s DND on channel %d\n",
+ on? "Enabled" : "Disabled",
+ zapchan->channel);
+ manager_event(EVENT_FLAG_SYSTEM, "DNDState",
+ "Channel: Zap/%d\r\n"
+ "Status: %s\r\n", zapchan->channel,
+ on? "enabled" : "disabled");
+}
+
+static void *ss_thread(void *data)
+{
+ struct ast_channel *chan = data;
+ struct zt_pvt *p = chan->tech_pvt;
+ char exten[AST_MAX_EXTENSION] = "";
+ char exten2[AST_MAX_EXTENSION] = "";
+ unsigned char buf[256];
+ char dtmfcid[300];
+ char dtmfbuf[300];
+ struct callerid_state *cs = NULL;
+ char *name = NULL, *number = NULL;
+ int distMatches;
+ int curRingData[3];
+ int receivedRingT;
+ int counter1;
+ int counter;
+ int samples = 0;
+ struct ast_smdi_md_message *smdi_msg = NULL;
+ int flags;
+ int i;
+ int timeout;
+ int getforward = 0;
+ char *s1, *s2;
+ int len = 0;
+ int res;
+ int index;
+
+ /* in the bizarre case where the channel has become a zombie before we
+ even get started here, abort safely
+ */
+ if (!p) {
+ ast_log(LOG_WARNING, "Channel became a zombie before simple switch could be started (%s)\n", chan->name);
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ ast_verb(3, "Starting simple switch on '%s'\n", chan->name);
+ index = zt_get_index(chan, p, 1);
+ if (index < 0) {
+ ast_log(LOG_WARNING, "Huh?\n");
+ ast_hangup(chan);
+ return NULL;
+ }
+ if (p->dsp)
+ ast_dsp_digitreset(p->dsp);
+ switch (p->sig) {
+#ifdef HAVE_PRI
+ case SIG_PRI:
+ case SIG_BRI:
+ case SIG_BRI_PTMP:
+ /* Now loop looking for an extension */
+ ast_copy_string(exten, p->exten, sizeof(exten));
+ len = strlen(exten);
+ res = 0;
+ while ((len < AST_MAX_EXTENSION-1) && ast_matchmore_extension(chan, chan->context, exten, 1, p->cid_num)) {
+ if (len && !ast_ignore_pattern(chan->context, exten))
+ tone_zone_play_tone(p->subs[index].zfd, -1);
+ else
+ tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALTONE);
+ if (ast_exists_extension(chan, chan->context, exten, 1, p->cid_num))
+ timeout = matchdigittimeout;
+ else
+ timeout = gendigittimeout;
+ res = ast_waitfordigit(chan, timeout);
+ if (res < 0) {
+ ast_debug(1, "waitfordigit returned < 0...\n");
+ ast_hangup(chan);
+ return NULL;
+ } else if (res) {
+ exten[len++] = res;
+ exten[len] = '\0';
+ } else
+ break;
+ }
+ /* if no extension was received ('unspecified') on overlap call, use the 's' extension */
+ if (ast_strlen_zero(exten)) {
+ ast_verb(3, "Going to extension s|1 because of empty extension received on overlap call\n");
+ exten[0] = 's';
+ exten[1] = '\0';
+ }
+ tone_zone_play_tone(p->subs[index].zfd, -1);
+ if (ast_exists_extension(chan, chan->context, exten, 1, p->cid_num)) {
+ /* Start the real PBX */
+ ast_copy_string(chan->exten, exten, sizeof(chan->exten));
+ if (p->dsp) ast_dsp_digitreset(p->dsp);
+ zt_enable_ec(p);
+ ast_setstate(chan, AST_STATE_RING);
+ res = ast_pbx_run(chan);
+ if (res) {
+ ast_log(LOG_WARNING, "PBX exited non-zero!\n");
+ }
+ } else {
+ ast_debug(1, "No such possible extension '%s' in context '%s'\n", exten, chan->context);
+ chan->hangupcause = AST_CAUSE_UNALLOCATED;
+ ast_hangup(chan);
+ p->exten[0] = '\0';
+ /* Since we send release complete here, we won't get one */
+ p->call = NULL;
+ }
+ return NULL;
+ break;
+#endif
+ case SIG_FEATD:
+ case SIG_FEATDMF:
+ case SIG_FEATDMF_TA:
+ case SIG_E911:
+ case SIG_FGC_CAMAMF:
+ case SIG_FEATB:
+ case SIG_EMWINK:
+ case SIG_SF_FEATD:
+ case SIG_SF_FEATDMF:
+ case SIG_SF_FEATB:
+ case SIG_SFWINK:
+ if (zt_wink(p, index))
+ return NULL;
+ /* Fall through */
+ case SIG_EM:
+ case SIG_EM_E1:
+ case SIG_SF:
+ case SIG_FGC_CAMA:
+ res = tone_zone_play_tone(p->subs[index].zfd, -1);
+ if (p->dsp)
+ ast_dsp_digitreset(p->dsp);
+ /* set digit mode appropriately */
+ if (p->dsp) {
+ if (NEED_MFDETECT(p))
+ ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_MF | p->dtmfrelax);
+ else
+ ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_DTMF | p->dtmfrelax);
+ }
+ memset(dtmfbuf, 0, sizeof(dtmfbuf));
+ /* Wait for the first digit only if immediate=no */
+ if (!p->immediate)
+ /* Wait for the first digit (up to 5 seconds). */
+ res = ast_waitfordigit(chan, 5000);
+ else
+ res = 0;
+ if (res > 0) {
+ /* save first char */
+ dtmfbuf[0] = res;
+ switch (p->sig) {
+ case SIG_FEATD:
+ case SIG_SF_FEATD:
+ res = my_getsigstr(chan, dtmfbuf + 1, "*", 3000);
+ if (res > 0)
+ res = my_getsigstr(chan, dtmfbuf + strlen(dtmfbuf), "*", 3000);
+ if ((res < 1) && (p->dsp)) ast_dsp_digitreset(p->dsp);
+ break;
+ case SIG_FEATDMF_TA:
+ res = my_getsigstr(chan, dtmfbuf + 1, "#", 3000);
+ if ((res < 1) && (p->dsp)) ast_dsp_digitreset(p->dsp);
+ if (zt_wink(p, index)) return NULL;
+ dtmfbuf[0] = 0;
+ /* Wait for the first digit (up to 5 seconds). */
+ res = ast_waitfordigit(chan, 5000);
+ if (res <= 0) break;
+ dtmfbuf[0] = res;
+ /* fall through intentionally */
+ case SIG_FEATDMF:
+ case SIG_E911:
+ case SIG_FGC_CAMAMF:
+ case SIG_SF_FEATDMF:
+ res = my_getsigstr(chan, dtmfbuf + 1, "#", 3000);
+ /* if international caca, do it again to get real ANO */
+ if ((p->sig == SIG_FEATDMF) && (dtmfbuf[1] != '0') && (strlen(dtmfbuf) != 14))
+ {
+ if (zt_wink(p, index)) return NULL;
+ dtmfbuf[0] = 0;
+ /* Wait for the first digit (up to 5 seconds). */
+ res = ast_waitfordigit(chan, 5000);
+ if (res <= 0) break;
+ dtmfbuf[0] = res;
+ res = my_getsigstr(chan, dtmfbuf + 1, "#", 3000);
+ }
+ if (res > 0) {
+ /* if E911, take off hook */
+ if (p->sig == SIG_E911)
+ zt_set_hook(p->subs[SUB_REAL].zfd, ZT_OFFHOOK);
+ res = my_getsigstr(chan, dtmfbuf + strlen(dtmfbuf), "#", 3000);
+ }
+ if ((res < 1) && (p->dsp)) ast_dsp_digitreset(p->dsp);
+ break;
+ case SIG_FEATB:
+ case SIG_SF_FEATB:
+ res = my_getsigstr(chan, dtmfbuf + 1, "#", 3000);
+ if ((res < 1) && (p->dsp)) ast_dsp_digitreset(p->dsp);
+ break;
+ case SIG_EMWINK:
+ /* if we received a '*', we are actually receiving Feature Group D
+ dial syntax, so use that mode; otherwise, fall through to normal
+ mode
+ */
+ if (res == '*') {
+ res = my_getsigstr(chan, dtmfbuf + 1, "*", 3000);
+ if (res > 0)
+ res = my_getsigstr(chan, dtmfbuf + strlen(dtmfbuf), "*", 3000);
+ if ((res < 1) && (p->dsp)) ast_dsp_digitreset(p->dsp);
+ break;
+ }
+ default:
+ /* If we got the first digit, get the rest */
+ len = 1;
+ dtmfbuf[len] = '\0';
+ while ((len < AST_MAX_EXTENSION-1) && ast_matchmore_extension(chan, chan->context, dtmfbuf, 1, p->cid_num)) {
+ if (ast_exists_extension(chan, chan->context, dtmfbuf, 1, p->cid_num)) {
+ timeout = matchdigittimeout;
+ } else {
+ timeout = gendigittimeout;
+ }
+ res = ast_waitfordigit(chan, timeout);
+ if (res < 0) {
+ ast_debug(1, "waitfordigit returned < 0...\n");
+ ast_hangup(chan);
+ return NULL;
+ } else if (res) {
+ dtmfbuf[len++] = res;
+ dtmfbuf[len] = '\0';
+ } else {
+ break;
+ }
+ }
+ break;
+ }
+ }
+ if (res == -1) {
+ ast_log(LOG_WARNING, "getdtmf on channel %d: %s\n", p->channel, strerror(errno));
+ ast_hangup(chan);
+ return NULL;
+ } else if (res < 0) {
+ ast_debug(1, "Got hung up before digits finished\n");
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ if (p->sig == SIG_FGC_CAMA) {
+ char anibuf[100];
+
+ if (ast_safe_sleep(chan,1000) == -1) {
+ ast_hangup(chan);
+ return NULL;
+ }
+ zt_set_hook(p->subs[SUB_REAL].zfd, ZT_OFFHOOK);
+ ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_MF | p->dtmfrelax);
+ res = my_getsigstr(chan, anibuf, "#", 10000);
+ if ((res > 0) && (strlen(anibuf) > 2)) {
+ if (anibuf[strlen(anibuf) - 1] == '#')
+ anibuf[strlen(anibuf) - 1] = 0;
+ ast_set_callerid(chan, anibuf + 2, NULL, anibuf + 2);
+ }
+ ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_DTMF | p->dtmfrelax);
+ }
+
+ ast_copy_string(exten, dtmfbuf, sizeof(exten));
+ if (ast_strlen_zero(exten))
+ ast_copy_string(exten, "s", sizeof(exten));
+ if (p->sig == SIG_FEATD || p->sig == SIG_EMWINK) {
+ /* Look for Feature Group D on all E&M Wink and Feature Group D trunks */
+ if (exten[0] == '*') {
+ char *stringp=NULL;
+ ast_copy_string(exten2, exten, sizeof(exten2));
+ /* Parse out extension and callerid */
+ stringp=exten2 +1;
+ s1 = strsep(&stringp, "*");
+ s2 = strsep(&stringp, "*");
+ if (s2) {
+ if (!ast_strlen_zero(p->cid_num))
+ ast_set_callerid(chan, p->cid_num, NULL, p->cid_num);
+ else
+ ast_set_callerid(chan, s1, NULL, s1);
+ ast_copy_string(exten, s2, sizeof(exten));
+ } else
+ ast_copy_string(exten, s1, sizeof(exten));
+ } else if (p->sig == SIG_FEATD)
+ ast_log(LOG_WARNING, "Got a non-Feature Group D input on channel %d. Assuming E&M Wink instead\n", p->channel);
+ }
+ if ((p->sig == SIG_FEATDMF) || (p->sig == SIG_FEATDMF_TA)) {
+ if (exten[0] == '*') {
+ char *stringp=NULL;
+ ast_copy_string(exten2, exten, sizeof(exten2));
+ /* Parse out extension and callerid */
+ stringp=exten2 +1;
+ s1 = strsep(&stringp, "#");
+ s2 = strsep(&stringp, "#");
+ if (s2) {
+ if (!ast_strlen_zero(p->cid_num))
+ ast_set_callerid(chan, p->cid_num, NULL, p->cid_num);
+ else
+ if (*(s1 + 2))
+ ast_set_callerid(chan, s1 + 2, NULL, s1 + 2);
+ ast_copy_string(exten, s2 + 1, sizeof(exten));
+ } else
+ ast_copy_string(exten, s1 + 2, sizeof(exten));
+ } else
+ ast_log(LOG_WARNING, "Got a non-Feature Group D input on channel %d. Assuming E&M Wink instead\n", p->channel);
+ }
+ if ((p->sig == SIG_E911) || (p->sig == SIG_FGC_CAMAMF)) {
+ if (exten[0] == '*') {
+ char *stringp=NULL;
+ ast_copy_string(exten2, exten, sizeof(exten2));
+ /* Parse out extension and callerid */
+ stringp=exten2 +1;
+ s1 = strsep(&stringp, "#");
+ s2 = strsep(&stringp, "#");
+ if (s2 && (*(s2 + 1) == '0')) {
+ if (*(s2 + 2))
+ ast_set_callerid(chan, s2 + 2, NULL, s2 + 2);
+ }
+ if (s1) ast_copy_string(exten, s1, sizeof(exten));
+ else ast_copy_string(exten, "911", sizeof(exten));
+ } else
+ ast_log(LOG_WARNING, "Got a non-E911/FGC CAMA input on channel %d. Assuming E&M Wink instead\n", p->channel);
+ }
+ if (p->sig == SIG_FEATB) {
+ if (exten[0] == '*') {
+ char *stringp=NULL;
+ ast_copy_string(exten2, exten, sizeof(exten2));
+ /* Parse out extension and callerid */
+ stringp=exten2 +1;
+ s1 = strsep(&stringp, "#");
+ ast_copy_string(exten, exten2 + 1, sizeof(exten));
+ } else
+ ast_log(LOG_WARNING, "Got a non-Feature Group B input on channel %d. Assuming E&M Wink instead\n", p->channel);
+ }
+ if ((p->sig == SIG_FEATDMF) || (p->sig == SIG_FEATDMF_TA)) {
+ zt_wink(p, index);
+ /* some switches require a minimum guard time between
+ the last FGD wink and something that answers
+ immediately. This ensures it */
+ if (ast_safe_sleep(chan,100)) return NULL;
+ }
+ zt_enable_ec(p);
+ if (NEED_MFDETECT(p)) {
+ if (p->dsp) {
+ if (!p->hardwaredtmf)
+ ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_DTMF | p->dtmfrelax);
+ else {
+ ast_dsp_free(p->dsp);
+ p->dsp = NULL;
+ }
+ }
+ }
+
+ if (ast_exists_extension(chan, chan->context, exten, 1, chan->cid.cid_num)) {
+ ast_copy_string(chan->exten, exten, sizeof(chan->exten));
+ if (p->dsp) ast_dsp_digitreset(p->dsp);
+ res = ast_pbx_run(chan);
+ if (res) {
+ ast_log(LOG_WARNING, "PBX exited non-zero\n");
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);
+ }
+ return NULL;
+ } else {
+ ast_verb(2, "Unknown extension '%s' in context '%s' requested\n", exten, chan->context);
+ sleep(2);
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_INFO);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to start special tone on %d\n", p->channel);
+ else
+ sleep(1);
+ res = ast_streamfile(chan, "ss-noservice", chan->language);
+ if (res >= 0)
+ ast_waitstream(chan, "");
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);
+ ast_hangup(chan);
+ return NULL;
+ }
+ break;
+ case SIG_FXOLS:
+ case SIG_FXOGS:
+ case SIG_FXOKS:
+ /* Read the first digit */
+ timeout = firstdigittimeout;
+ /* If starting a threeway call, never timeout on the first digit so someone
+ can use flash-hook as a "hold" feature */
+ if (p->subs[SUB_THREEWAY].owner)
+ timeout = 999999;
+ while (len < AST_MAX_EXTENSION-1) {
+ /* Read digit unless it's supposed to be immediate, in which case the
+ only answer is 's' */
+ if (p->immediate)
+ res = 's';
+ else
+ res = ast_waitfordigit(chan, timeout);
+ timeout = 0;
+ if (res < 0) {
+ ast_debug(1, "waitfordigit returned < 0...\n");
+ res = tone_zone_play_tone(p->subs[index].zfd, -1);
+ ast_hangup(chan);
+ return NULL;
+ } else if (res) {
+ ast_debug(1,"waitfordigit returned '%c' (%d), timeout = %d\n", res, res, timeout);
+ exten[len++]=res;
+ exten[len] = '\0';
+ }
+ if (!ast_ignore_pattern(chan->context, exten))
+ tone_zone_play_tone(p->subs[index].zfd, -1);
+ else
+ tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALTONE);
+ if (ast_exists_extension(chan, chan->context, exten, 1, p->cid_num) && strcmp(exten, ast_parking_ext())) {
+ if (!res || !ast_matchmore_extension(chan, chan->context, exten, 1, p->cid_num)) {
+ if (getforward) {
+ /* Record this as the forwarding extension */
+ ast_copy_string(p->call_forward, exten, sizeof(p->call_forward));
+ ast_verb(3, "Setting call forward to '%s' on channel %d\n", p->call_forward, p->channel);
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);
+ if (res)
+ break;
+ usleep(500000);
+ res = tone_zone_play_tone(p->subs[index].zfd, -1);
+ sleep(1);
+ memset(exten, 0, sizeof(exten));
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALTONE);
+ len = 0;
+ getforward = 0;
+ } else {
+ res = tone_zone_play_tone(p->subs[index].zfd, -1);
+ ast_copy_string(chan->exten, exten, sizeof(chan->exten));
+ if (!ast_strlen_zero(p->cid_num)) {
+ if (!p->hidecallerid)
+ ast_set_callerid(chan, p->cid_num, NULL, p->cid_num);
+ else
+ ast_set_callerid(chan, NULL, NULL, p->cid_num);
+ }
+ if (!ast_strlen_zero(p->cid_name)) {
+ if (!p->hidecallerid)
+ ast_set_callerid(chan, NULL, p->cid_name, NULL);
+ }
+ ast_setstate(chan, AST_STATE_RING);
+ zt_enable_ec(p);
+ res = ast_pbx_run(chan);
+ if (res) {
+ ast_log(LOG_WARNING, "PBX exited non-zero\n");
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);
+ }
+ return NULL;
+ }
+ } else {
+ /* It's a match, but they just typed a digit, and there is an ambiguous match,
+ so just set the timeout to matchdigittimeout and wait some more */
+ timeout = matchdigittimeout;
+ }
+ } else if (res == 0) {
+ ast_debug(1, "not enough digits (and no ambiguous match)...\n");
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);
+ zt_wait_event(p->subs[index].zfd);
+ ast_hangup(chan);
+ return NULL;
+ } else if (p->callwaiting && !strcmp(exten, "*70")) {
+ ast_verb(3, "Disabling call waiting on %s\n", chan->name);
+ /* Disable call waiting if enabled */
+ p->callwaiting = 0;
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n",
+ chan->name, strerror(errno));
+ }
+ len = 0;
+ ioctl(p->subs[index].zfd,ZT_CONFDIAG,&len);
+ memset(exten, 0, sizeof(exten));
+ timeout = firstdigittimeout;
+
+ } else if (!strcmp(exten,ast_pickup_ext())) {
+ /* Scan all channels and see if any there
+ * ringing channqels with that have call groups
+ * that equal this channels pickup group
+ */
+ if (index == SUB_REAL) {
+ /* Switch us from Third call to Call Wait */
+ if (p->subs[SUB_THREEWAY].owner) {
+ /* If you make a threeway call and the *8# a call, it should actually
+ look like a callwait */
+ alloc_sub(p, SUB_CALLWAIT);
+ swap_subs(p, SUB_CALLWAIT, SUB_THREEWAY);
+ unalloc_sub(p, SUB_THREEWAY);
+ }
+ zt_enable_ec(p);
+ if (ast_pickup_call(chan)) {
+ ast_debug(1, "No call pickup possible...\n");
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);
+ zt_wait_event(p->subs[index].zfd);
+ }
+ ast_hangup(chan);
+ return NULL;
+ } else {
+ ast_log(LOG_WARNING, "Huh? Got *8# on call not on real\n");
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ } else if (!p->hidecallerid && !strcmp(exten, "*67")) {
+ ast_verb(3, "Disabling Caller*ID on %s\n", chan->name);
+ /* Disable Caller*ID if enabled */
+ p->hidecallerid = 1;
+ if (chan->cid.cid_num)
+ ast_free(chan->cid.cid_num);
+ chan->cid.cid_num = NULL;
+ if (chan->cid.cid_name)
+ ast_free(chan->cid.cid_name);
+ chan->cid.cid_name = NULL;
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n",
+ chan->name, strerror(errno));
+ }
+ len = 0;
+ memset(exten, 0, sizeof(exten));
+ timeout = firstdigittimeout;
+ } else if (p->callreturn && !strcmp(exten, "*69")) {
+ res = 0;
+ if (!ast_strlen_zero(p->lastcid_num)) {
+ res = ast_say_digit_str(chan, p->lastcid_num, "", chan->language);
+ }
+ if (!res)
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);
+ break;
+ } else if (!strcmp(exten, "*78")) {
+ zap_dnd(p, 1);
+ /* Do not disturb */
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);
+ getforward = 0;
+ memset(exten, 0, sizeof(exten));
+ len = 0;
+ } else if (!strcmp(exten, "*79")) {
+ zap_dnd(p, 0);
+ /* Do not disturb */
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);
+ getforward = 0;
+ memset(exten, 0, sizeof(exten));
+ len = 0;
+ } else if (p->cancallforward && !strcmp(exten, "*72")) {
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);
+ getforward = 1;
+ memset(exten, 0, sizeof(exten));
+ len = 0;
+ } else if (p->cancallforward && !strcmp(exten, "*73")) {
+ ast_verb(3, "Cancelling call forwarding on channel %d\n", p->channel);
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);
+ memset(p->call_forward, 0, sizeof(p->call_forward));
+ getforward = 0;
+ memset(exten, 0, sizeof(exten));
+ len = 0;
+ } else if ((p->transfer || p->canpark) && !strcmp(exten, ast_parking_ext()) &&
+ p->subs[SUB_THREEWAY].owner &&
+ ast_bridged_channel(p->subs[SUB_THREEWAY].owner)) {
+ /* This is a three way call, the main call being a real channel,
+ and we're parking the first call. */
+ ast_masq_park_call(ast_bridged_channel(p->subs[SUB_THREEWAY].owner), chan, 0, NULL);
+ ast_verb(3, "Parking call to '%s'\n", chan->name);
+ break;
+ } else if (!ast_strlen_zero(p->lastcid_num) && !strcmp(exten, "*60")) {
+ ast_verb(3, "Blacklisting number %s\n", p->lastcid_num);
+ res = ast_db_put("blacklist", p->lastcid_num, "1");
+ if (!res) {
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);
+ memset(exten, 0, sizeof(exten));
+ len = 0;
+ }
+ } else if (p->hidecallerid && !strcmp(exten, "*82")) {
+ ast_verb(3, "Enabling Caller*ID on %s\n", chan->name);
+ /* Enable Caller*ID if enabled */
+ p->hidecallerid = 0;
+ if (chan->cid.cid_num)
+ ast_free(chan->cid.cid_num);
+ chan->cid.cid_num = NULL;
+ if (chan->cid.cid_name)
+ ast_free(chan->cid.cid_name);
+ chan->cid.cid_name = NULL;
+ ast_set_callerid(chan, p->cid_num, p->cid_name, NULL);
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_DIALRECALL);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n",
+ chan->name, strerror(errno));
+ }
+ len = 0;
+ memset(exten, 0, sizeof(exten));
+ timeout = firstdigittimeout;
+ } else if (!strcmp(exten, "*0")) {
+ struct ast_channel *nbridge =
+ p->subs[SUB_THREEWAY].owner;
+ struct zt_pvt *pbridge = NULL;
+ /* set up the private struct of the bridged one, if any */
+ if (nbridge && ast_bridged_channel(nbridge))
+ pbridge = ast_bridged_channel(nbridge)->tech_pvt;
+ if (nbridge && pbridge &&
+ (nbridge->tech == &zap_tech) &&
+ (ast_bridged_channel(nbridge)->tech == &zap_tech) &&
+ ISTRUNK(pbridge)) {
+ int func = ZT_FLASH;
+ /* Clear out the dial buffer */
+ p->dop.dialstr[0] = '\0';
+ /* flash hookswitch */
+ if ((ioctl(pbridge->subs[SUB_REAL].zfd,ZT_HOOK,&func) == -1) && (errno != EINPROGRESS)) {
+ ast_log(LOG_WARNING, "Unable to flash external trunk on channel %s: %s\n",
+ nbridge->name, strerror(errno));
+ }
+ swap_subs(p, SUB_REAL, SUB_THREEWAY);
+ unalloc_sub(p, SUB_THREEWAY);
+ p->owner = p->subs[SUB_REAL].owner;
+ if (ast_bridged_channel(p->subs[SUB_REAL].owner))
+ ast_queue_control(p->subs[SUB_REAL].owner, AST_CONTROL_UNHOLD);
+ ast_hangup(chan);
+ return NULL;
+ } else {
+ tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);
+ zt_wait_event(p->subs[index].zfd);
+ tone_zone_play_tone(p->subs[index].zfd, -1);
+ swap_subs(p, SUB_REAL, SUB_THREEWAY);
+ unalloc_sub(p, SUB_THREEWAY);
+ p->owner = p->subs[SUB_REAL].owner;
+ ast_hangup(chan);
+ return NULL;
+ }
+ } else if (!ast_canmatch_extension(chan, chan->context, exten, 1, chan->cid.cid_num) &&
+ ((exten[0] != '*') || (strlen(exten) > 2))) {
+ ast_debug(1, "Can't match %s from '%s' in context %s\n", exten, chan->cid.cid_num ? chan->cid.cid_num : "<Unknown Caller>", chan->context);
+ break;
+ }
+ if (!timeout)
+ timeout = gendigittimeout;
+ if (len && !ast_ignore_pattern(chan->context, exten))
+ tone_zone_play_tone(p->subs[index].zfd, -1);
+ }
+ break;
+ case SIG_FXSLS:
+ case SIG_FXSGS:
+ case SIG_FXSKS:
+#ifdef HAVE_PRI
+ if (p->pri) {
+ /* This is a GR-303 trunk actually. Wait for the first ring... */
+ struct ast_frame *f;
+ int res;
+ time_t start;
+
+ time(&start);
+ ast_setstate(chan, AST_STATE_RING);
+ while (time(NULL) < start + 3) {
+ res = ast_waitfor(chan, 1000);
+ if (res) {
+ f = ast_read(chan);
+ if (!f) {
+ ast_log(LOG_WARNING, "Whoa, hangup while waiting for first ring!\n");
+ ast_hangup(chan);
+ return NULL;
+ } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_RING)) {
+ res = 1;
+ } else
+ res = 0;
+ ast_frfree(f);
+ if (res) {
+ ast_debug(1, "Got ring!\n");
+ res = 0;
+ break;
+ }
+ }
+ }
+ }
+#endif
+ /* check for SMDI messages */
+ if (p->use_smdi && p->smdi_iface) {
+ smdi_msg = ast_smdi_md_message_wait(p->smdi_iface, SMDI_MD_WAIT_TIMEOUT);
+
+ if (smdi_msg != NULL) {
+ ast_copy_string(chan->exten, smdi_msg->fwd_st, sizeof(chan->exten));
+
+ if (smdi_msg->type == 'B')
+ pbx_builtin_setvar_helper(chan, "_SMDI_VM_TYPE", "b");
+ else if (smdi_msg->type == 'N')
+ pbx_builtin_setvar_helper(chan, "_SMDI_VM_TYPE", "u");
+
+ ast_debug(1, "Recieved SMDI message on %s\n", chan->name);
+ } else {
+ ast_log(LOG_WARNING, "SMDI enabled but no SMDI message present\n");
+ }
+ }
+
+ if (p->use_callerid && (p->cid_signalling == CID_SIG_SMDI && smdi_msg)) {
+ number = smdi_msg->calling_st;
+
+ /* If we want caller id, we're in a prering state due to a polarity reversal
+ * and we're set to use a polarity reversal to trigger the start of caller id,
+ * grab the caller id and wait for ringing to start... */
+ } else if (p->use_callerid && (chan->_state == AST_STATE_PRERING && (p->cid_start == CID_START_POLARITY || p->cid_start == CID_START_POLARITY_IN))) {
+ /* If set to use DTMF CID signalling, listen for DTMF */
+ if (p->cid_signalling == CID_SIG_DTMF) {
+ int i = 0;
+ cs = NULL;
+ ast_debug(1, "Receiving DTMF cid on "
+ "channel %s\n", chan->name);
+ zt_setlinear(p->subs[index].zfd, 0);
+ res = 2000;
+ for (;;) {
+ struct ast_frame *f;
+ res = ast_waitfor(chan, res);
+ if (res <= 0) {
+ ast_log(LOG_WARNING, "DTMFCID timed out waiting for ring. "
+ "Exiting simple switch\n");
+ ast_hangup(chan);
+ return NULL;
+ }
+ f = ast_read(chan);
+ if (!f)
+ break;
+ if (f->frametype == AST_FRAME_DTMF) {
+ dtmfbuf[i++] = f->subclass;
+ ast_debug(1, "CID got digit '%c'\n", f->subclass);
+ res = 2000;
+ }
+ ast_frfree(f);
+ if (chan->_state == AST_STATE_RING ||
+ chan->_state == AST_STATE_RINGING)
+ break; /* Got ring */
+ }
+ dtmfbuf[i] = '\0';
+ zt_setlinear(p->subs[index].zfd, p->subs[index].linear);
+ /* Got cid and ring. */
+ ast_debug(1, "CID got string '%s'\n", dtmfbuf);
+ callerid_get_dtmf(dtmfbuf, dtmfcid, &flags);
+ ast_debug(1, "CID is '%s', flags %d\n",
+ dtmfcid, flags);
+ /* If first byte is NULL, we have no cid */
+ if (!ast_strlen_zero(dtmfcid))
+ number = dtmfcid;
+ else
+ number = NULL;
+ /* If set to use V23 Signalling, launch our FSK gubbins and listen for it */
+ } else if ((p->cid_signalling == CID_SIG_V23) || (p->cid_signalling == CID_SIG_V23_JP)) {
+ cs = callerid_new(p->cid_signalling);
+ if (cs) {
+ samples = 0;
+#if 1
+ bump_gains(p);
+#endif
+ /* Take out of linear mode for Caller*ID processing */
+ zt_setlinear(p->subs[index].zfd, 0);
+
+ /* First we wait and listen for the Caller*ID */
+ for (;;) {
+ i = ZT_IOMUX_READ | ZT_IOMUX_SIGEVENT;
+ if ((res = ioctl(p->subs[index].zfd, ZT_IOMUX, &i))) {
+ ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ return NULL;
+ }
+ if (i & ZT_IOMUX_SIGEVENT) {
+ res = zt_get_event(p->subs[index].zfd);
+ ast_log(LOG_NOTICE, "Got event %d (%s)...\n", res, event2str(res));
+
+ if (p->cid_signalling == CID_SIG_V23_JP) {
+#ifdef ZT_EVENT_RINGBEGIN
+ if (res == ZT_EVENT_RINGBEGIN) {
+ res = zt_set_hook(p->subs[SUB_REAL].zfd, ZT_OFFHOOK);
+ usleep(1);
+ }
+#endif
+ } else {
+ res = 0;
+ break;
+ }
+ } else if (i & ZT_IOMUX_READ) {
+ res = read(p->subs[index].zfd, buf, sizeof(buf));
+ if (res < 0) {
+ if (errno != ELAST) {
+ ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ return NULL;
+ }
+ break;
+ }
+ samples += res;
+
+ if (p->cid_signalling == CID_SIG_V23_JP) {
+ res = callerid_feed_jp(cs, buf, res, AST_LAW(p));
+ } else {
+ res = callerid_feed(cs, buf, res, AST_LAW(p));
+ }
+
+ if (res < 0) {
+ ast_log(LOG_WARNING, "CallerID feed failed: %s\n", strerror(errno));
+ break;
+ } else if (res)
+ break;
+ else if (samples > (8000 * 10))
+ break;
+ }
+ }
+ if (res == 1) {
+ callerid_get(cs, &name, &number, &flags);
+ ast_log(LOG_NOTICE, "CallerID number: %s, name: %s, flags=%d\n", number, name, flags);
+ }
+ if (res < 0) {
+ ast_log(LOG_WARNING, "CallerID returned with error on channel '%s'\n", chan->name);
+ }
+
+ if (p->cid_signalling == CID_SIG_V23_JP) {
+ res = zt_set_hook(p->subs[SUB_REAL].zfd, ZT_ONHOOK);
+ usleep(1);
+ res = 4000;
+ } else {
+
+ /* Finished with Caller*ID, now wait for a ring to make sure there really is a call coming */
+ res = 2000;
+ }
+
+ for (;;) {
+ struct ast_frame *f;
+ res = ast_waitfor(chan, res);
+ if (res <= 0) {
+ ast_log(LOG_WARNING, "CID timed out waiting for ring. "
+ "Exiting simple switch\n");
+ ast_hangup(chan);
+ return NULL;
+ }
+ f = ast_read(chan);
+ ast_frfree(f);
+ if (chan->_state == AST_STATE_RING ||
+ chan->_state == AST_STATE_RINGING)
+ break; /* Got ring */
+ }
+
+ /* We must have a ring by now, so, if configured, lets try to listen for
+ * distinctive ringing */
+ if (p->usedistinctiveringdetection == 1) {
+ len = 0;
+ distMatches = 0;
+ /* Clear the current ring data array so we dont have old data in it. */
+ for (receivedRingT = 0; receivedRingT < (sizeof(curRingData) / sizeof(curRingData[0])); receivedRingT++)
+ curRingData[receivedRingT] = 0;
+ receivedRingT = 0;
+ counter = 0;
+ counter1 = 0;
+ /* Check to see if context is what it should be, if not set to be. */
+ if (strcmp(p->context,p->defcontext) != 0) {
+ ast_copy_string(p->context, p->defcontext, sizeof(p->context));
+ ast_copy_string(chan->context,p->defcontext,sizeof(chan->context));
+ }
+
+ for (;;) {
+ i = ZT_IOMUX_READ | ZT_IOMUX_SIGEVENT;
+ if ((res = ioctl(p->subs[index].zfd, ZT_IOMUX, &i))) {
+ ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ return NULL;
+ }
+ if (i & ZT_IOMUX_SIGEVENT) {
+ res = zt_get_event(p->subs[index].zfd);
+ ast_log(LOG_NOTICE, "Got event %d (%s)...\n", res, event2str(res));
+ res = 0;
+ /* Let us detect distinctive ring */
+
+ curRingData[receivedRingT] = p->ringt;
+
+ if (p->ringt < p->ringt_base/2)
+ break;
+ /* Increment the ringT counter so we can match it against
+ values in zapata.conf for distinctive ring */
+ if (++receivedRingT == (sizeof(curRingData) / sizeof(curRingData[0])))
+ break;
+ } else if (i & ZT_IOMUX_READ) {
+ res = read(p->subs[index].zfd, buf, sizeof(buf));
+ if (res < 0) {
+ if (errno != ELAST) {
+ ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ return NULL;
+ }
+ break;
+ }
+ if (p->ringt)
+ p->ringt--;
+ if (p->ringt == 1) {
+ res = -1;
+ break;
+ }
+ }
+ }
+ /* this only shows up if you have n of the dring patterns filled in */
+ ast_verb(3, "Detected ring pattern: %d,%d,%d\n",curRingData[0],curRingData[1],curRingData[2]);
+ for (counter = 0; counter < 3; counter++) {
+ /* Check to see if the rings we received match any of the ones in zapata.conf for this
+ channel */
+ distMatches = 0;
+ for (counter1 = 0; counter1 < 3; counter1++) {
+ ast_verb(3, "Ring pattern check range: %d\n", p->drings.ringnum[counter].range);
+ if (p->drings.ringnum[counter].ring[counter1] == -1) {
+ ast_verb(3, "Pattern ignore (-1) detected, so matching pattern %d regardless.\n",
+ curRingData[counter1]);
+ distMatches++;
+ }
+ else if (curRingData[counter1] <= (p->drings.ringnum[counter].ring[counter1] + p->drings.ringnum[counter].range) &&
+ curRingData[counter1] >= (p->drings.ringnum[counter].ring[counter1] - p->drings.ringnum[counter].range)) {
+ ast_verb(3, "Ring pattern matched in range: %d to %d\n",
+ (p->drings.ringnum[counter].ring[counter1] - p->drings.ringnum[counter].range),
+ (p->drings.ringnum[counter].ring[counter1] + p->drings.ringnum[counter].range));
+ distMatches++;
+ }
+ }
+
+ if (distMatches == 3) {
+ /* The ring matches, set the context to whatever is for distinctive ring.. */
+ ast_copy_string(p->context, p->drings.ringContext[counter].contextData, sizeof(p->context));
+ ast_copy_string(chan->context, p->drings.ringContext[counter].contextData, sizeof(chan->context));
+ ast_verb(3, "Distinctive Ring matched context %s\n",p->context);
+ break;
+ }
+ }
+ }
+ /* Restore linear mode (if appropriate) for Caller*ID processing */
+ zt_setlinear(p->subs[index].zfd, p->subs[index].linear);
+#if 1
+ restore_gains(p);
+#endif
+ } else
+ ast_log(LOG_WARNING, "Unable to get caller ID space\n");
+ } else {
+ ast_log(LOG_WARNING, "Channel %s in prering "
+ "state, but I have nothing to do. "
+ "Terminating simple switch, should be "
+ "restarted by the actual ring.\n",
+ chan->name);
+ ast_hangup(chan);
+ return NULL;
+ }
+ } else if (p->use_callerid && p->cid_start == CID_START_RING) {
+ if (p->cid_signalling == CID_SIG_DTMF) {
+ int i = 0;
+ cs = NULL;
+ zt_setlinear(p->subs[index].zfd, 0);
+ res = 2000;
+ for (;;) {
+ struct ast_frame *f;
+ res = ast_waitfor(chan, res);
+ if (res <= 0) {
+ ast_log(LOG_WARNING, "DTMFCID timed out waiting for ring. "
+ "Exiting simple switch\n");
+ ast_hangup(chan);
+ return NULL;
+ }
+ f = ast_read(chan);
+ if (f->frametype == AST_FRAME_DTMF) {
+ dtmfbuf[i++] = f->subclass;
+ ast_log(LOG_DEBUG, "CID got digit '%c'\n", f->subclass);
+ res = 2000;
+ }
+ ast_frfree(f);
+
+ if (p->ringt_base == p->ringt)
+ break;
+
+ }
+ dtmfbuf[i] = '\0';
+ zt_setlinear(p->subs[index].zfd, p->subs[index].linear);
+ /* Got cid and ring. */
+ callerid_get_dtmf(dtmfbuf, dtmfcid, &flags);
+ ast_log(LOG_DEBUG, "CID is '%s', flags %d\n",
+ dtmfcid, flags);
+ /* If first byte is NULL, we have no cid */
+ if (!ast_strlen_zero(dtmfcid))
+ number = dtmfcid;
+ else
+ number = NULL;
+ /* If set to use V23 Signalling, launch our FSK gubbins and listen for it */
+ } else {
+ /* FSK Bell202 callerID */
+ cs = callerid_new(p->cid_signalling);
+ if (cs) {
+#if 1
+ bump_gains(p);
+#endif
+ samples = 0;
+ len = 0;
+ distMatches = 0;
+ /* Clear the current ring data array so we dont have old data in it. */
+ for (receivedRingT = 0; receivedRingT < (sizeof(curRingData) / sizeof(curRingData[0])); receivedRingT++)
+ curRingData[receivedRingT] = 0;
+ receivedRingT = 0;
+ counter = 0;
+ counter1 = 0;
+ /* Check to see if context is what it should be, if not set to be. */
+ if (strcmp(p->context,p->defcontext) != 0) {
+ ast_copy_string(p->context, p->defcontext, sizeof(p->context));
+ ast_copy_string(chan->context,p->defcontext,sizeof(chan->context));
+ }
+
+ /* Take out of linear mode for Caller*ID processing */
+ zt_setlinear(p->subs[index].zfd, 0);
+ for (;;) {
+ i = ZT_IOMUX_READ | ZT_IOMUX_SIGEVENT;
+ if ((res = ioctl(p->subs[index].zfd, ZT_IOMUX, &i))) {
+ ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ return NULL;
+ }
+ if (i & ZT_IOMUX_SIGEVENT) {
+ res = zt_get_event(p->subs[index].zfd);
+ ast_log(LOG_NOTICE, "Got event %d (%s)...\n", res, event2str(res));
+ /* If we get a PR event, they hung up while processing calerid */
+ if ( res == ZT_EVENT_POLARITY && p->hanguponpolarityswitch && p->polarity == POLARITY_REV) {
+ ast_log(LOG_DEBUG, "Hanging up due to polarity reversal on channel %d while detecting callerid\n", p->channel);
+ p->polarity = POLARITY_IDLE;
+ callerid_free(cs);
+ ast_hangup(chan);
+ return NULL;
+ }
+ res = 0;
+ /* Let us detect callerid when the telco uses distinctive ring */
+
+ curRingData[receivedRingT] = p->ringt;
+
+ if (p->ringt < p->ringt_base/2)
+ break;
+ /* Increment the ringT counter so we can match it against
+ values in zapata.conf for distinctive ring */
+ if (++receivedRingT == (sizeof(curRingData) / sizeof(curRingData[0])))
+ break;
+ } else if (i & ZT_IOMUX_READ) {
+ res = read(p->subs[index].zfd, buf, sizeof(buf));
+ if (res < 0) {
+ if (errno != ELAST) {
+ ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ return NULL;
+ }
+ break;
+ }
+ if (p->ringt)
+ p->ringt--;
+ if (p->ringt == 1) {
+ res = -1;
+ break;
+ }
+ samples += res;
+ res = callerid_feed(cs, buf, res, AST_LAW(p));
+ if (res < 0) {
+ ast_log(LOG_WARNING, "CallerID feed failed: %s\n", strerror(errno));
+ break;
+ } else if (res)
+ break;
+ else if (samples > (8000 * 10))
+ break;
+ }
+ }
+ if (res == 1) {
+ callerid_get(cs, &name, &number, &flags);
+ ast_debug(1, "CallerID number: %s, name: %s, flags=%d\n", number, name, flags);
+ }
+ if (distinctiveringaftercid == 1) {
+ /* Clear the current ring data array so we dont have old data in it. */
+ for (receivedRingT = 0; receivedRingT < 3; receivedRingT++) {
+ curRingData[receivedRingT] = 0;
+ }
+ receivedRingT = 0;
+ ast_verb(3, "Detecting post-CID distinctive ring\n");
+ for (;;) {
+ i = ZT_IOMUX_READ | ZT_IOMUX_SIGEVENT;
+ if ((res = ioctl(p->subs[index].zfd, ZT_IOMUX, &i))) {
+ ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ return NULL;
+ }
+ if (i & ZT_IOMUX_SIGEVENT) {
+ res = zt_get_event(p->subs[index].zfd);
+ ast_log(LOG_NOTICE, "Got event %d (%s)...\n", res, event2str(res));
+ res = 0;
+ /* Let us detect callerid when the telco uses distinctive ring */
+
+ curRingData[receivedRingT] = p->ringt;
+
+ if (p->ringt < p->ringt_base/2)
+ break;
+ /* Increment the ringT counter so we can match it against
+ values in zapata.conf for distinctive ring */
+ if (++receivedRingT == (sizeof(curRingData) / sizeof(curRingData[0])))
+ break;
+ } else if (i & ZT_IOMUX_READ) {
+ res = read(p->subs[index].zfd, buf, sizeof(buf));
+ if (res < 0) {
+ if (errno != ELAST) {
+ ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ return NULL;
+ }
+ break;
+ }
+ if (p->ringt)
+ p->ringt--;
+ if (p->ringt == 1) {
+ res = -1;
+ break;
+ }
+ }
+ }
+ }
+ if (p->usedistinctiveringdetection == 1) {
+ /* this only shows up if you have n of the dring patterns filled in */
+ ast_verb(3, "Detected ring pattern: %d,%d,%d\n",curRingData[0],curRingData[1],curRingData[2]);
+
+ for (counter = 0; counter < 3; counter++) {
+ /* Check to see if the rings we received match any of the ones in zapata.conf for this
+ channel */
+ /* this only shows up if you have n of the dring patterns filled in */
+ ast_verb(3, "Checking %d,%d,%d\n",
+ p->drings.ringnum[counter].ring[0],
+ p->drings.ringnum[counter].ring[1],
+ p->drings.ringnum[counter].ring[2]);
+ distMatches = 0;
+ for (counter1 = 0; counter1 < 3; counter1++) {
+ ast_verb(3, "Ring pattern check range: %d\n", p->drings.ringnum[counter].range);
+ if (p->drings.ringnum[counter].ring[counter1] == -1) {
+ ast_verb(3, "Pattern ignore (-1) detected, so matching pattern %d regardless.\n",
+ curRingData[counter1]);
+ distMatches++;
+ }
+ else if (curRingData[counter1] <= (p->drings.ringnum[counter].ring[counter1] + p->drings.ringnum[counter].range) &&
+ curRingData[counter1] >= (p->drings.ringnum[counter].ring[counter1] - p->drings.ringnum[counter].range)) {
+ ast_verb(3, "Ring pattern matched in range: %d to %d\n",
+ (p->drings.ringnum[counter].ring[counter1] - p->drings.ringnum[counter].range),
+ (p->drings.ringnum[counter].ring[counter1] + p->drings.ringnum[counter].range));
+ distMatches++;
+ }
+ }
+ if (distMatches == 3) {
+ /* The ring matches, set the context to whatever is for distinctive ring.. */
+ ast_copy_string(p->context, p->drings.ringContext[counter].contextData, sizeof(p->context));
+ ast_copy_string(chan->context, p->drings.ringContext[counter].contextData, sizeof(chan->context));
+ ast_verb(3, "Distinctive Ring matched context %s\n",p->context);
+ break;
+ }
+ }
+ }
+ /* Restore linear mode (if appropriate) for Caller*ID processing */
+ zt_setlinear(p->subs[index].zfd, p->subs[index].linear);
+#if 1
+ restore_gains(p);
+#endif
+ if (res < 0) {
+ ast_log(LOG_WARNING, "CallerID returned with error on channel '%s'\n", chan->name);
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to get caller ID space\n");
+ }
+ }
+ else
+ cs = NULL;
+
+ if (number)
+ ast_shrink_phone_number(number);
+ ast_set_callerid(chan, number, name, number);
+
+ if (smdi_msg)
+ ASTOBJ_UNREF(smdi_msg, ast_smdi_md_message_destroy);
+
+ if (cs)
+ callerid_free(cs);
+
+ ast_setstate(chan, AST_STATE_RING);
+ chan->rings = 1;
+ p->ringt = p->ringt_base;
+ res = ast_pbx_run(chan);
+ if (res) {
+ ast_hangup(chan);
+ ast_log(LOG_WARNING, "PBX exited non-zero\n");
+ }
+ return NULL;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to handle simple switch with signalling %s on channel %d\n", sig2str(p->sig), p->channel);
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", p->channel);
+ }
+ res = tone_zone_play_tone(p->subs[index].zfd, ZT_TONE_CONGESTION);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", p->channel);
+ ast_hangup(chan);
+ return NULL;
+}
+
+struct mwi_thread_data {
+ struct zt_pvt *pvt;
+ unsigned char buf[READ_SIZE];
+ size_t len;
+};
+
+static int calc_energy(const unsigned char *buf, int len, int law)
+{
+ int x;
+ int sum = 0;
+
+ if (!len)
+ return 0;
+
+ for (x = 0; x < len; x++)
+ sum += abs(law == AST_FORMAT_ULAW ? AST_MULAW(buf[x]) : AST_ALAW(buf[x]));
+
+ return sum / len;
+}
+
+static void *mwi_thread(void *data)
+{
+ struct mwi_thread_data *mtd = data;
+ struct callerid_state *cs;
+ pthread_attr_t attr;
+ pthread_t threadid;
+ int samples = 0;
+ char *name, *number;
+ int flags;
+ int i, res;
+ unsigned int spill_done = 0;
+ int spill_result = -1;
+
+ if (!(cs = callerid_new(mtd->pvt->cid_signalling))) {
+ mtd->pvt->mwimonitoractive = 0;
+
+ return NULL;
+ }
+
+ callerid_feed(cs, mtd->buf, mtd->len, AST_LAW(mtd->pvt));
+
+ bump_gains(mtd->pvt);
+
+ for (;;) {
+ i = ZT_IOMUX_READ | ZT_IOMUX_SIGEVENT;
+ if ((res = ioctl(mtd->pvt->subs[SUB_REAL].zfd, ZT_IOMUX, &i))) {
+ ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno));
+ goto quit;
+ }
+
+ if (i & ZT_IOMUX_SIGEVENT) {
+ struct ast_channel *chan;
+
+ /* If we get an event, cancel and go to the simple switch to let it deal with it */
+ res = zt_get_event(mtd->pvt->subs[SUB_REAL].zfd);
+ ast_log(LOG_NOTICE, "Got event %d (%s)... Passing along to ss_thread\n", res, event2str(res));
+ callerid_free(cs);
+
+ restore_gains(mtd->pvt);
+ mtd->pvt->ringt = mtd->pvt->ringt_base;
+
+ if ((chan = zt_new(mtd->pvt, AST_STATE_RING, 0, SUB_REAL, 0, 0))) {
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ if (ast_pthread_create(&threadid, &attr, ss_thread, chan)) {
+ ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", mtd->pvt->channel);
+ res = tone_zone_play_tone(mtd->pvt->subs[SUB_REAL].zfd, ZT_TONE_CONGESTION);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", mtd->pvt->channel);
+ ast_hangup(chan);
+ goto quit;
+ }
+ goto quit_no_clean;
+
+ } else {
+ ast_log(LOG_WARNING, "Could not create channel to handle call\n");
+ }
+ } else if (i & ZT_IOMUX_READ) {
+ if ((res = read(mtd->pvt->subs[SUB_REAL].zfd, mtd->buf, sizeof(mtd->buf))) < 0) {
+ if (errno != ELAST) {
+ ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno));
+ goto quit;
+ }
+ break;
+ }
+ samples += res;
+ if (!spill_done) {
+ if ((spill_result = callerid_feed(cs, mtd->buf, res, AST_LAW(mtd->pvt))) < 0) {
+ ast_log(LOG_WARNING, "CallerID feed failed: %s\n", strerror(errno));
+ break;
+ } else if (spill_result) {
+ spill_done = 1;
+ }
+ } else {
+ /* keep reading data until the energy level drops below the threshold
+ so we don't get another 'trigger' on the remaining carrier signal
+ */
+ if (calc_energy(mtd->buf, res, AST_LAW(mtd->pvt)) <= mwilevel)
+ break;
+ }
+ if (samples > (8000 * 4)) /*Termination case - time to give up*/
+ break;
+ }
+ }
+
+ if (spill_result == 1) {
+ callerid_get(cs, &name, &number, &flags);
+ if (flags & CID_MSGWAITING) {
+ ast_log(LOG_NOTICE, "mwi: Have Messages on channel %d\n", mtd->pvt->channel);
+ notify_message(mtd->pvt->mailbox, 1);
+ } else if (flags & CID_NOMSGWAITING) {
+ ast_log(LOG_NOTICE, "mwi: No Messages on channel %d\n", mtd->pvt->channel);
+ notify_message(mtd->pvt->mailbox, 0);
+ } else {
+ ast_log(LOG_NOTICE, "mwi: Status unknown on channel %d\n", mtd->pvt->channel);
+ }
+ }
+
+
+quit:
+ callerid_free(cs);
+
+ restore_gains(mtd->pvt);
+
+quit_no_clean:
+ mtd->pvt->mwimonitoractive = 0;
+
+ ast_free(mtd);
+
+ return NULL;
+}
+
+/* destroy a Zaptel channel, identified by its number */
+static int zap_destroy_channel_bynum(int channel)
+{
+ struct zt_pvt *tmp = NULL;
+ struct zt_pvt *prev = NULL;
+
+ tmp = iflist;
+ while (tmp) {
+ if (tmp->channel == channel) {
+ destroy_channel(prev, tmp, 1);
+ return RESULT_SUCCESS;
+ }
+ prev = tmp;
+ tmp = tmp->next;
+ }
+ return RESULT_FAILURE;
+}
+
+static int handle_init_event(struct zt_pvt *i, int event)
+{
+ int res;
+ pthread_t threadid;
+ struct ast_channel *chan;
+
+ /* Handle an event on a given channel for the monitor thread. */
+
+ switch (event) {
+ case ZT_EVENT_NONE:
+ case ZT_EVENT_BITSCHANGED:
+ break;
+ case ZT_EVENT_WINKFLASH:
+ case ZT_EVENT_RINGOFFHOOK:
+ if (i->inalarm) break;
+ if (i->radio) break;
+ /* Got a ring/answer. What kind of channel are we? */
+ switch (i->sig) {
+ case SIG_FXOLS:
+ case SIG_FXOGS:
+ case SIG_FXOKS:
+ res = zt_set_hook(i->subs[SUB_REAL].zfd, ZT_OFFHOOK);
+ if (res && (errno == EBUSY))
+ break;
+ if (i->cidspill) {
+ /* Cancel VMWI spill */
+ ast_free(i->cidspill);
+ i->cidspill = NULL;
+ }
+ if (i->immediate) {
+ zt_enable_ec(i);
+ /* The channel is immediately up. Start right away */
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, ZT_TONE_RINGTONE);
+ chan = zt_new(i, AST_STATE_RING, 1, SUB_REAL, 0, 0);
+ if (!chan) {
+ ast_log(LOG_WARNING, "Unable to start PBX on channel %d\n", i->channel);
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, ZT_TONE_CONGESTION);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel);
+ }
+ } else {
+ /* Check for callerid, digits, etc */
+ chan = zt_new(i, AST_STATE_RESERVED, 0, SUB_REAL, 0, 0);
+ if (chan) {
+ if (has_voicemail(i))
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, ZT_TONE_STUTTER);
+ else
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, ZT_TONE_DIALTONE);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to play dialtone on channel %d, do you have defaultzone and loadzone defined?\n", i->channel);
+ if (ast_pthread_create_detached(&threadid, NULL, ss_thread, chan)) {
+ ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel);
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, ZT_TONE_CONGESTION);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel);
+ ast_hangup(chan);
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to create channel\n");
+ }
+ break;
+ case SIG_FXSLS:
+ case SIG_FXSGS:
+ case SIG_FXSKS:
+ i->ringt = i->ringt_base;
+ /* Fall through */
+ case SIG_EMWINK:
+ case SIG_FEATD:
+ case SIG_FEATDMF:
+ case SIG_FEATDMF_TA:
+ case SIG_E911:
+ case SIG_FGC_CAMA:
+ case SIG_FGC_CAMAMF:
+ case SIG_FEATB:
+ case SIG_EM:
+ case SIG_EM_E1:
+ case SIG_SFWINK:
+ case SIG_SF_FEATD:
+ case SIG_SF_FEATDMF:
+ case SIG_SF_FEATB:
+ case SIG_SF:
+ /* Check for callerid, digits, etc */
+ if (i->cid_start == CID_START_POLARITY_IN) {
+ chan = zt_new(i, AST_STATE_PRERING, 0, SUB_REAL, 0, 0);
+ } else {
+ chan = zt_new(i, AST_STATE_RING, 0, SUB_REAL, 0, 0);
+ }
+ if (chan && ast_pthread_create_detached(&threadid, NULL, ss_thread, chan)) {
+ ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel);
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, ZT_TONE_CONGESTION);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel);
+ ast_hangup(chan);
+ } else if (!chan) {
+ ast_log(LOG_WARNING, "Cannot allocate new structure on channel %d\n", i->channel);
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to handle ring/answer with signalling %s on channel %d\n", sig2str(i->sig), i->channel);
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, ZT_TONE_CONGESTION);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel);
+ return -1;
+ }
+ break;
+ case ZT_EVENT_NOALARM:
+ i->inalarm = 0;
+ ast_log(LOG_NOTICE, "Alarm cleared on channel %d\n", i->channel);
+ manager_event(EVENT_FLAG_SYSTEM, "AlarmClear",
+ "Channel: %d\r\n", i->channel);
+ break;
+ case ZT_EVENT_ALARM:
+ i->inalarm = 1;
+ res = get_alarms(i);
+ ast_log(LOG_WARNING, "Detected alarm on channel %d: %s\n", i->channel, alarm2str(res));
+ manager_event(EVENT_FLAG_SYSTEM, "Alarm",
+ "Alarm: %s\r\n"
+ "Channel: %d\r\n",
+ alarm2str(res), i->channel);
+ /* fall thru intentionally */
+ case ZT_EVENT_ONHOOK:
+ if (i->radio)
+ break;
+ /* Back on hook. Hang up. */
+ switch (i->sig) {
+ case SIG_FXOLS:
+ case SIG_FXOGS:
+ case SIG_FEATD:
+ case SIG_FEATDMF:
+ case SIG_FEATDMF_TA:
+ case SIG_E911:
+ case SIG_FGC_CAMA:
+ case SIG_FGC_CAMAMF:
+ case SIG_FEATB:
+ case SIG_EM:
+ case SIG_EM_E1:
+ case SIG_EMWINK:
+ case SIG_SF_FEATD:
+ case SIG_SF_FEATDMF:
+ case SIG_SF_FEATB:
+ case SIG_SF:
+ case SIG_SFWINK:
+ case SIG_FXSLS:
+ case SIG_FXSGS:
+ case SIG_FXSKS:
+ case SIG_GR303FXSKS:
+ zt_disable_ec(i);
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, -1);
+ zt_set_hook(i->subs[SUB_REAL].zfd, ZT_ONHOOK);
+ break;
+ case SIG_GR303FXOKS:
+ case SIG_FXOKS:
+ zt_disable_ec(i);
+ /* Diddle the battery for the zhone */
+#ifdef ZHONE_HACK
+ zt_set_hook(i->subs[SUB_REAL].zfd, ZT_OFFHOOK);
+ usleep(1);
+#endif
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, -1);
+ zt_set_hook(i->subs[SUB_REAL].zfd, ZT_ONHOOK);
+ break;
+ case SIG_PRI:
+ case SIG_SS7:
+ case SIG_BRI:
+ case SIG_BRI_PTMP:
+ zt_disable_ec(i);
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, -1);
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to handle on hook with signalling %s on channel %d\n", sig2str(i->sig), i->channel);
+ res = tone_zone_play_tone(i->subs[SUB_REAL].zfd, -1);
+ return -1;
+ }
+ break;
+ case ZT_EVENT_POLARITY:
+ switch (i->sig) {
+ case SIG_FXSLS:
+ case SIG_FXSKS:
+ case SIG_FXSGS:
+ /* We have already got a PR before the channel was
+ created, but it wasn't handled. We need polarity
+ to be REV for remote hangup detection to work.
+ At least in Spain */
+ if (i->hanguponpolarityswitch)
+ i->polarity = POLARITY_REV;
+ if (i->cid_start == CID_START_POLARITY || i->cid_start == CID_START_POLARITY_IN) {
+ i->polarity = POLARITY_REV;
+ ast_verb(2, "Starting post polarity "
+ "CID detection on channel %d\n",
+ i->channel);
+ chan = zt_new(i, AST_STATE_PRERING, 0, SUB_REAL, 0, 0);
+ if (chan && ast_pthread_create_detached(&threadid, NULL, ss_thread, chan)) {
+ ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel);
+ }
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "handle_init_event detected "
+ "polarity reversal on non-FXO (SIG_FXS) "
+ "interface %d\n", i->channel);
+ }
+ break;
+ case ZT_EVENT_REMOVED: /* destroy channel */
+ ast_log(LOG_NOTICE,
+ "Got ZT_EVENT_REMOVED. Destroying channel %d\n",
+ i->channel);
+ zap_destroy_channel_bynum(i->channel);
+ break;
+ }
+ return 0;
+}
+
+static void *do_monitor(void *data)
+{
+ int count, res, res2, spoint, pollres=0;
+ struct zt_pvt *i;
+ struct zt_pvt *last = NULL;
+ time_t thispass = 0, lastpass = 0;
+ int found;
+ char buf[1024];
+ struct pollfd *pfds=NULL;
+ int lastalloc = -1;
+ /* This thread monitors all the frame relay interfaces which are not yet in use
+ (and thus do not have a separate thread) indefinitely */
+ /* From here on out, we die whenever asked */
+#if 0
+ if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL)) {
+ ast_log(LOG_WARNING, "Unable to set cancel type to asynchronous\n");
+ return NULL;
+ }
+ ast_debug(1, "Monitor starting...\n");
+#endif
+ for (;;) {
+ /* Lock the interface list */
+ ast_mutex_lock(&iflock);
+ if (!pfds || (lastalloc != ifcount)) {
+ if (pfds) {
+ ast_free(pfds);
+ pfds = NULL;
+ }
+ if (ifcount) {
+ if (!(pfds = ast_calloc(1, ifcount * sizeof(*pfds)))) {
+ ast_mutex_unlock(&iflock);
+ return NULL;
+ }
+ }
+ lastalloc = ifcount;
+ }
+ /* Build the stuff we're going to poll on, that is the socket of every
+ zt_pvt that does not have an associated owner channel */
+ count = 0;
+ i = iflist;
+ while (i) {
+ if ((i->subs[SUB_REAL].zfd > -1) && i->sig && (!i->radio)) {
+ if (!i->owner && !i->subs[SUB_REAL].owner && !i->mwimonitoractive) {
+ /* This needs to be watched, as it lacks an owner */
+ pfds[count].fd = i->subs[SUB_REAL].zfd;
+ pfds[count].events = POLLPRI;
+ pfds[count].revents = 0;
+ /* If we are monitoring for VMWI or sending CID, we need to
+ read from the channel as well */
+ if (i->cidspill || i->mwimonitor)
+ pfds[count].events |= POLLIN;
+ count++;
+ }
+ }
+ i = i->next;
+ }
+ /* Okay, now that we know what to do, release the interface lock */
+ ast_mutex_unlock(&iflock);
+
+ pthread_testcancel();
+ /* Wait at least a second for something to happen */
+ res = poll(pfds, count, 1000);
+ pthread_testcancel();
+ /* Okay, poll has finished. Let's see what happened. */
+ if (res < 0) {
+ if ((errno != EAGAIN) && (errno != EINTR))
+ ast_log(LOG_WARNING, "poll return %d: %s\n", res, strerror(errno));
+ continue;
+ }
+ /* Alright, lock the interface list again, and let's look and see what has
+ happened */
+ ast_mutex_lock(&iflock);
+ found = 0;
+ spoint = 0;
+ lastpass = thispass;
+ thispass = time(NULL);
+ i = iflist;
+ while (i) {
+ if (thispass != lastpass) {
+ if (!found && ((i == last) || ((i == iflist) && !last))) {
+ last = i;
+ if (last) {
+ if (!last->cidspill && !last->owner && !ast_strlen_zero(last->mailbox) && (thispass - last->onhooktime > 3) &&
+ (last->sig & __ZT_SIG_FXO)) {
+ res = has_voicemail(last);
+ if (last->msgstate != res) {
+ int x;
+ ast_debug(1, "Message status for %s changed from %d to %d on %d\n", last->mailbox, last->msgstate, res, last->channel);
+#ifdef ZT_VMWI
+ res2 = ioctl(last->subs[SUB_REAL].zfd, ZT_VMWI, res);
+ if (res2)
+ ast_log(LOG_DEBUG, "Unable to control message waiting led on channel %d\n", last->channel);
+#endif
+ x = ZT_FLUSH_BOTH;
+ res2 = ioctl(last->subs[SUB_REAL].zfd, ZT_FLUSH, &x);
+ if (res2)
+ ast_log(LOG_WARNING, "Unable to flush input on channel %d\n", last->channel);
+ if ((last->cidspill = ast_calloc(1, MAX_CALLERID_SIZE))) {
+ /* Turn on on hook transfer for 4 seconds */
+ x = 4000;
+ ioctl(last->subs[SUB_REAL].zfd, ZT_ONHOOKTRANSFER, &x);
+ last->cidlen = vmwi_generate(last->cidspill, res, 1, AST_LAW(last));
+ last->cidpos = 0;
+ last->msgstate = res;
+ last->onhooktime = thispass;
+ }
+ found ++;
+ }
+ }
+ last = last->next;
+ }
+ }
+ }
+ if ((i->subs[SUB_REAL].zfd > -1) && i->sig) {
+ if (i->radio && !i->owner)
+ {
+ res = zt_get_event(i->subs[SUB_REAL].zfd);
+ if (res)
+ {
+ ast_debug(1, "Monitor doohicky got event %s on radio channel %d\n", event2str(res), i->channel);
+ /* Don't hold iflock while handling init events */
+ ast_mutex_unlock(&iflock);
+ handle_init_event(i, res);
+ ast_mutex_lock(&iflock);
+ }
+ i = i->next;
+ continue;
+ }
+ pollres = ast_fdisset(pfds, i->subs[SUB_REAL].zfd, count, &spoint);
+ if (pollres & POLLIN) {
+ if (i->owner || i->subs[SUB_REAL].owner) {
+#ifdef HAVE_PRI
+ if (!i->pri)
+#endif
+ ast_log(LOG_WARNING, "Whoa.... I'm owned but found (%d) in read...\n", i->subs[SUB_REAL].zfd);
+ i = i->next;
+ continue;
+ }
+ if (!i->cidspill && !i->mwimonitor) {
+ ast_log(LOG_WARNING, "Whoa.... I'm reading but have no cidspill (%d)...\n", i->subs[SUB_REAL].zfd);
+ i = i->next;
+ continue;
+ }
+ res = read(i->subs[SUB_REAL].zfd, buf, sizeof(buf));
+ if (res > 0) {
+ if (i->mwimonitor) {
+ if (calc_energy((unsigned char *) buf, res, AST_LAW(i)) > mwilevel) {
+ pthread_attr_t attr;
+ pthread_t threadid;
+ struct mwi_thread_data *mtd;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ ast_log(LOG_DEBUG, "Maybe some MWI on port %d!\n", i->channel);
+ if ((mtd = ast_calloc(1, sizeof(*mtd)))) {
+ mtd->pvt = i;
+ memcpy(mtd->buf, buf, res);
+ mtd->len = res;
+ if (ast_pthread_create_background(&threadid, &attr, mwi_thread, mtd)) {
+ ast_log(LOG_WARNING, "Unable to start mwi thread on channel %d\n", i->channel);
+ ast_free(mtd);
+ }
+ i->mwimonitoractive = 1;
+ }
+ }
+ } else if (i->cidspill) {
+ /* We read some number of bytes. Write an equal amount of data */
+ if (res > i->cidlen - i->cidpos)
+ res = i->cidlen - i->cidpos;
+ res2 = write(i->subs[SUB_REAL].zfd, i->cidspill + i->cidpos, res);
+ if (res2 > 0) {
+ i->cidpos += res2;
+ if (i->cidpos >= i->cidlen) {
+ free(i->cidspill);
+ i->cidspill = 0;
+ i->cidpos = 0;
+ i->cidlen = 0;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Write failed: %s\n", strerror(errno));
+ i->msgstate = -1;
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Read failed with %d: %s\n", res, strerror(errno));
+ }
+ ast_debug(1, "Monitor doohicky got event %s on channel %d\n", event2str(res), i->channel);
+ /* Don't hold iflock while handling init events -- race with chlock */
+ ast_mutex_unlock(&iflock);
+ handle_init_event(i, res);
+ ast_mutex_lock(&iflock);
+ }
+ if (pollres & POLLPRI) {
+ if (i->owner || i->subs[SUB_REAL].owner) {
+#ifdef HAVE_PRI
+ if (!i->pri)
+#endif
+ ast_log(LOG_WARNING, "Whoa.... I'm owned but found (%d)...\n", i->subs[SUB_REAL].zfd);
+ i = i->next;
+ continue;
+ }
+ res = zt_get_event(i->subs[SUB_REAL].zfd);
+ ast_debug(1, "Monitor doohicky got event %s on channel %d\n", event2str(res), i->channel);
+ /* Don't hold iflock while handling init events */
+ ast_mutex_unlock(&iflock);
+ handle_init_event(i, res);
+ ast_mutex_lock(&iflock);
+ }
+ }
+ i=i->next;
+ }
+ ast_mutex_unlock(&iflock);
+ }
+ /* Never reached */
+ return NULL;
+
+}
+
+static int restart_monitor(void)
+{
+ /* If we're supposed to be stopped -- stay stopped */
+ if (monitor_thread == AST_PTHREADT_STOP)
+ return 0;
+ ast_mutex_lock(&monlock);
+ if (monitor_thread == pthread_self()) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_WARNING, "Cannot kill myself\n");
+ return -1;
+ }
+ if (monitor_thread != AST_PTHREADT_NULL) {
+ /* Wake up the thread */
+ pthread_kill(monitor_thread, SIGURG);
+ } else {
+ /* Start a new monitor */
+ if (ast_pthread_create_detached_background(&monitor_thread, NULL, do_monitor, NULL) < 0) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
+ return -1;
+ }
+ }
+ ast_mutex_unlock(&monlock);
+ return 0;
+}
+
+#ifdef HAVE_PRI
+static int pri_resolve_span(int *span, int channel, int offset, struct zt_spaninfo *si)
+{
+ int x;
+ int trunkgroup;
+ /* Get appropriate trunk group if there is one */
+ trunkgroup = pris[*span].mastertrunkgroup;
+ if (trunkgroup) {
+ /* Select a specific trunk group */
+ for (x = 0; x < NUM_SPANS; x++) {
+ if (pris[x].trunkgroup == trunkgroup) {
+ *span = x;
+ return 0;
+ }
+ }
+ ast_log(LOG_WARNING, "Channel %d on span %d configured to use nonexistent trunk group %d\n", channel, *span, trunkgroup);
+ *span = -1;
+ } else {
+ if (pris[*span].trunkgroup) {
+ ast_log(LOG_WARNING, "Unable to use span %d implicitly since it is trunk group %d (please use spanmap)\n", *span, pris[*span].trunkgroup);
+ *span = -1;
+ } else if (pris[*span].mastertrunkgroup) {
+ ast_log(LOG_WARNING, "Unable to use span %d implicitly since it is already part of trunk group %d\n", *span, pris[*span].mastertrunkgroup);
+ *span = -1;
+ } else {
+ if (si->totalchans == 31) { /* if it's an E1 */
+ pris[*span].dchannels[0] = 16 + offset;
+ } else { /* T1 or BRI: D Channel is the last Channel */
+ pris[*span].dchannels[0] =
+ si->totalchans + offset;
+ }
+ pris[*span].dchanavail[0] |= DCHAN_PROVISIONED;
+ pris[*span].offset = offset;
+ pris[*span].span = *span + 1;
+ }
+ }
+ return 0;
+}
+
+static int pri_create_trunkgroup(int trunkgroup, int *channels)
+{
+ struct zt_spaninfo si;
+ ZT_PARAMS p;
+ int fd;
+ int span;
+ int ospan=0;
+ int x,y;
+ for (x = 0; x < NUM_SPANS; x++) {
+ if (pris[x].trunkgroup == trunkgroup) {
+ ast_log(LOG_WARNING, "Trunk group %d already exists on span %d, Primary d-channel %d\n", trunkgroup, x + 1, pris[x].dchannels[0]);
+ return -1;
+ }
+ }
+ for (y = 0; y < NUM_DCHANS; y++) {
+ if (!channels[y])
+ break;
+ memset(&si, 0, sizeof(si));
+ memset(&p, 0, sizeof(p));
+ fd = open("/dev/zap/channel", O_RDWR);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Failed to open channel: %s\n", strerror(errno));
+ return -1;
+ }
+ x = channels[y];
+ if (ioctl(fd, ZT_SPECIFY, &x)) {
+ ast_log(LOG_WARNING, "Failed to specify channel %d: %s\n", channels[y], strerror(errno));
+ zt_close(fd);
+ return -1;
+ }
+ if (ioctl(fd, ZT_GET_PARAMS, &p)) {
+ ast_log(LOG_WARNING, "Failed to get channel parameters for channel %d: %s\n", channels[y], strerror(errno));
+ return -1;
+ }
+ if (ioctl(fd, ZT_SPANSTAT, &si)) {
+ ast_log(LOG_WARNING, "Failed go get span information on channel %d (span %d)\n", channels[y], p.spanno);
+ zt_close(fd);
+ return -1;
+ }
+ span = p.spanno - 1;
+ if (pris[span].trunkgroup) {
+ ast_log(LOG_WARNING, "Span %d is already provisioned for trunk group %d\n", span + 1, pris[span].trunkgroup);
+ zt_close(fd);
+ return -1;
+ }
+ if (pris[span].pvts[0]) {
+ ast_log(LOG_WARNING, "Span %d is already provisioned with channels (implicit PRI maybe?)\n", span + 1);
+ zt_close(fd);
+ return -1;
+ }
+ if (!y) {
+ pris[span].trunkgroup = trunkgroup;
+ pris[span].offset = channels[y] - p.chanpos;
+ ospan = span;
+ }
+ pris[ospan].dchannels[y] = channels[y];
+ pris[ospan].dchanavail[y] |= DCHAN_PROVISIONED;
+ pris[span].span = span + 1;
+ zt_close(fd);
+ }
+ return 0;
+}
+
+static int pri_create_spanmap(int span, int trunkgroup, int logicalspan)
+{
+ if (pris[span].mastertrunkgroup) {
+ ast_log(LOG_WARNING, "Span %d is already part of trunk group %d, cannot add to trunk group %d\n", span + 1, pris[span].mastertrunkgroup, trunkgroup);
+ return -1;
+ }
+ pris[span].mastertrunkgroup = trunkgroup;
+ pris[span].prilogicalspan = logicalspan;
+ return 0;
+}
+
+#endif
+
+#ifdef HAVE_SS7
+
+static unsigned int parse_pointcode(const char *pcstring)
+{
+ unsigned int code1, code2, code3;
+ int numvals;
+
+ numvals = sscanf(pcstring, "%d-%d-%d", &code1, &code2, &code3);
+ if (numvals == 1)
+ return code1;
+ if (numvals == 3)
+ return (code1 << 16) | (code2 << 8) | code3;
+
+ return 0;
+}
+
+static struct zt_ss7 * ss7_resolve_linkset(int linkset)
+{
+ if ((linkset < 0) || (linkset >= NUM_SPANS))
+ return NULL;
+ else
+ return &linksets[linkset - 1];
+}
+#endif /* HAVE_SS7 */
+
+/* converts a Zaptel sigtype to signalling as can be configured from
+ * zapata.conf.
+ * While both have basically the same values, this will later be the
+ * place to add filters and sanity checks
+ */
+static int sigtype_to_signalling(int sigtype)
+{
+ return sigtype;
+}
+
+static struct zt_pvt *mkintf(int channel, struct zt_chan_conf conf, struct zt_pri *pri, int reloading)
+{
+ /* Make a zt_pvt structure for this interface (or CRV if "pri" is specified) */
+ struct zt_pvt *tmp = NULL, *tmp2, *prev = NULL;
+ char fn[80];
+#if 1
+ struct zt_bufferinfo bi;
+#endif
+ struct zt_spaninfo si;
+ int res;
+ int span=0;
+ int here = 0;
+ int x;
+ struct zt_pvt **wlist;
+ struct zt_pvt **wend;
+ ZT_PARAMS p;
+
+ wlist = &iflist;
+ wend = &ifend;
+
+#ifdef HAVE_PRI
+ if (pri) {
+ wlist = &pri->crvs;
+ wend = &pri->crvend;
+ }
+#endif
+
+ tmp2 = *wlist;
+ prev = NULL;
+
+ while (tmp2) {
+ if (!tmp2->destroy) {
+ if (tmp2->channel == channel) {
+ tmp = tmp2;
+ here = 1;
+ break;
+ }
+ if (tmp2->channel > channel) {
+ break;
+ }
+ }
+ prev = tmp2;
+ tmp2 = tmp2->next;
+ }
+
+ if (!here && !reloading) {
+ if (!(tmp = ast_calloc(1, sizeof(*tmp)))) {
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ ast_mutex_init(&tmp->lock);
+ ifcount++;
+ for (x = 0; x < 3; x++)
+ tmp->subs[x].zfd = -1;
+ tmp->channel = channel;
+ }
+
+ if (tmp) {
+ if (!here) {
+ if ((channel != CHAN_PSEUDO) && !pri) {
+ snprintf(fn, sizeof(fn), "%d", channel);
+ /* Open non-blocking */
+ if (!here)
+ tmp->subs[SUB_REAL].zfd = zt_open(fn);
+ /* Allocate a zapata structure */
+ if (tmp->subs[SUB_REAL].zfd < 0) {
+ ast_log(LOG_ERROR, "Unable to open channel %d: %s\nhere = %d, tmp->channel = %d, channel = %d\n", channel, strerror(errno), here, tmp->channel, channel);
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ memset(&p, 0, sizeof(p));
+ res = ioctl(tmp->subs[SUB_REAL].zfd, ZT_GET_PARAMS, &p);
+ if (res < 0) {
+ ast_log(LOG_ERROR, "Unable to get parameters\n");
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if (conf.is_sig_auto)
+ conf.chan.sig = sigtype_to_signalling(p.sigtype);
+ if (p.sigtype != (conf.chan.sig & 0x3ffff)) {
+ ast_log(LOG_ERROR, "Signalling requested on channel %d is %s but line is in %s signalling\n", channel, sig2str(conf.chan.sig), sig2str(p.sigtype));
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ tmp->law = p.curlaw;
+ tmp->span = p.spanno;
+ span = p.spanno - 1;
+ } else {
+ if (channel == CHAN_PSEUDO)
+ conf.chan.sig = 0;
+ else if ((conf.chan.sig != SIG_FXOKS) && (conf.chan.sig != SIG_FXSKS)) {
+ ast_log(LOG_ERROR, "CRV's must use FXO/FXS Kewl Start (fxo_ks/fxs_ks) signalling only.\n");
+ return NULL;
+ }
+ }
+#ifdef HAVE_SS7
+ if (conf.chan.sig == SIG_SS7) {
+ struct zt_ss7 *ss7;
+ int clear = 0;
+ if (ioctl(tmp->subs[SUB_REAL].zfd, ZT_AUDIOMODE, &clear)) {
+ ast_log(LOG_ERROR, "Unable to set clear mode on clear channel %d of span %d: %s\n", channel, p.spanno, strerror(errno));
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+
+ ss7 = ss7_resolve_linkset(cur_linkset);
+ if (!ss7) {
+ ast_log(LOG_ERROR, "Unable to find linkset %d\n", cur_linkset);
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if (cur_cicbeginswith < 0) {
+ ast_log(LOG_ERROR, "Need to set cicbeginswith for the channels!\n");
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+
+ tmp->cic = cur_cicbeginswith++;
+
+ /* DB: Add CIC's DPC information */
+ tmp->dpc = cur_defaultdpc;
+
+ tmp->ss7 = ss7;
+ tmp->ss7call = NULL;
+ ss7->pvts[ss7->numchans++] = tmp;
+
+ ast_copy_string(linksets[span].internationalprefix, conf.ss7.internationalprefix, sizeof(linksets[span].internationalprefix));
+ ast_copy_string(linksets[span].nationalprefix, conf.ss7.nationalprefix, sizeof(linksets[span].nationalprefix));
+ ast_copy_string(linksets[span].subscriberprefix, conf.ss7.subscriberprefix, sizeof(linksets[span].subscriberprefix));
+ ast_copy_string(linksets[span].unknownprefix, conf.ss7.unknownprefix, sizeof(linksets[span].unknownprefix));
+
+ linksets[span].called_nai = conf.ss7.called_nai;
+ linksets[span].calling_nai = conf.ss7.calling_nai;
+ }
+#endif
+#ifdef HAVE_PRI
+ if ((conf.chan.sig == SIG_PRI) || (conf.chan.sig == SIG_BRI) || (conf.chan.sig == SIG_BRI_PTMP) || (conf.chan.sig == SIG_GR303FXOKS) || (conf.chan.sig == SIG_GR303FXSKS)) {
+ int offset;
+ int myswitchtype;
+ int matchesdchan;
+ int x,y;
+ offset = 0;
+ if (((conf.chan.sig == SIG_PRI) || (conf.chan.sig == SIG_BRI) || (conf.chan.sig == SIG_BRI_PTMP))
+ && ioctl(tmp->subs[SUB_REAL].zfd, ZT_AUDIOMODE, &offset)) {
+ ast_log(LOG_ERROR, "Unable to set clear mode on clear channel %d of span %d: %s\n", channel, p.spanno, strerror(errno));
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if (span >= NUM_SPANS) {
+ ast_log(LOG_ERROR, "Channel %d does not lie on a span I know of (%d)\n", channel, span);
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ } else {
+ si.spanno = 0;
+ if (ioctl(tmp->subs[SUB_REAL].zfd,ZT_SPANSTAT,&si) == -1) {
+ ast_log(LOG_ERROR, "Unable to get span status: %s\n", strerror(errno));
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ /* Store the logical span first based upon the real span */
+ tmp->logicalspan = pris[span].prilogicalspan;
+ pri_resolve_span(&span, channel, (channel - p.chanpos), &si);
+ if (span < 0) {
+ ast_log(LOG_WARNING, "Channel %d: Unable to find locate channel/trunk group!\n", channel);
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if ((conf.chan.sig == SIG_PRI) ||
+ (conf.chan.sig == SIG_BRI) ||
+ (conf.chan.sig == SIG_BRI_PTMP))
+ myswitchtype = conf.pri.switchtype;
+ else
+ myswitchtype = PRI_SWITCH_GR303_TMC;
+ /* Make sure this isn't a d-channel */
+ matchesdchan=0;
+ for (x = 0; x < NUM_SPANS; x++) {
+ for (y = 0; y < NUM_DCHANS; y++) {
+ if (pris[x].dchannels[y] == tmp->channel) {
+ matchesdchan = 1;
+ break;
+ }
+ }
+ }
+ offset = p.chanpos;
+ if (!matchesdchan) {
+ if (pris[span].nodetype && (pris[span].nodetype != conf.pri.nodetype)) {
+ ast_log(LOG_ERROR, "Span %d is already a %s node\n", span + 1, pri_node2str(pris[span].nodetype));
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if (pris[span].switchtype && (pris[span].switchtype != myswitchtype)) {
+ ast_log(LOG_ERROR, "Span %d is already a %s switch\n", span + 1, pri_switch2str(pris[span].switchtype));
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if ((pris[span].dialplan) && (pris[span].dialplan != conf.pri.dialplan)) {
+ ast_log(LOG_ERROR, "Span %d is already a %s dialing plan\n", span + 1, dialplan2str(pris[span].dialplan));
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if (!ast_strlen_zero(pris[span].idledial) && strcmp(pris[span].idledial, conf.pri.idledial)) {
+ ast_log(LOG_ERROR, "Span %d already has idledial '%s'.\n", span + 1, conf.pri.idledial);
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if (!ast_strlen_zero(pris[span].idleext) && strcmp(pris[span].idleext, conf.pri.idleext)) {
+ ast_log(LOG_ERROR, "Span %d already has idleext '%s'.\n", span + 1, conf.pri.idleext);
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if (pris[span].minunused && (pris[span].minunused != conf.pri.minunused)) {
+ ast_log(LOG_ERROR, "Span %d already has minunused of %d.\n", span + 1, conf.pri.minunused);
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if (pris[span].minidle && (pris[span].minidle != conf.pri.minidle)) {
+ ast_log(LOG_ERROR, "Span %d already has minidle of %d.\n", span + 1, conf.pri.minidle);
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if (pris[span].numchans >= MAX_CHANNELS) {
+ ast_log(LOG_ERROR, "Unable to add channel %d: Too many channels in trunk group %d!\n", channel,
+ pris[span].trunkgroup);
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+
+ pris[span].sig = conf.chan.sig;
+ pris[span].nodetype = conf.pri.nodetype;
+ pris[span].switchtype = myswitchtype;
+ pris[span].nsf = conf.pri.nsf;
+ pris[span].dialplan = conf.pri.dialplan;
+ pris[span].localdialplan = conf.pri.localdialplan;
+ pris[span].pvts[pris[span].numchans++] = tmp;
+ pris[span].minunused = conf.pri.minunused;
+ pris[span].minidle = conf.pri.minidle;
+ pris[span].overlapdial = conf.pri.overlapdial;
+ pris[span].facilityenable = conf.pri.facilityenable;
+ ast_copy_string(pris[span].idledial, conf.pri.idledial, sizeof(pris[span].idledial));
+ ast_copy_string(pris[span].idleext, conf.pri.idleext, sizeof(pris[span].idleext));
+ ast_copy_string(pris[span].internationalprefix, conf.pri.internationalprefix, sizeof(pris[span].internationalprefix));
+ ast_copy_string(pris[span].nationalprefix, conf.pri.nationalprefix, sizeof(pris[span].nationalprefix));
+ ast_copy_string(pris[span].localprefix, conf.pri.localprefix, sizeof(pris[span].localprefix));
+ ast_copy_string(pris[span].privateprefix, conf.pri.privateprefix, sizeof(pris[span].privateprefix));
+ ast_copy_string(pris[span].unknownprefix, conf.pri.unknownprefix, sizeof(pris[span].unknownprefix));
+ pris[span].resetinterval = conf.pri.resetinterval;
+
+ tmp->pri = &pris[span];
+ tmp->prioffset = offset;
+ tmp->call = NULL;
+ } else {
+ ast_log(LOG_ERROR, "Channel %d is reserved for D-channel.\n", offset);
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ }
+ } else {
+ tmp->prioffset = 0;
+ }
+#endif
+ } else {
+ conf.chan.sig = tmp->sig;
+ conf.chan.radio = tmp->radio;
+ memset(&p, 0, sizeof(p));
+ if (tmp->subs[SUB_REAL].zfd > -1)
+ res = ioctl(tmp->subs[SUB_REAL].zfd, ZT_GET_PARAMS, &p);
+ }
+ /* Adjust starttime on loopstart and kewlstart trunks to reasonable values */
+ if ((conf.chan.sig == SIG_FXSKS) || (conf.chan.sig == SIG_FXSLS) ||
+ (conf.chan.sig == SIG_EM) || (conf.chan.sig == SIG_EM_E1) || (conf.chan.sig == SIG_EMWINK) ||
+ (conf.chan.sig == SIG_FEATD) || (conf.chan.sig == SIG_FEATDMF) || (conf.chan.sig == SIG_FEATDMF_TA) ||
+ (conf.chan.sig == SIG_FEATB) || (conf.chan.sig == SIG_E911) ||
+ (conf.chan.sig == SIG_SF) || (conf.chan.sig == SIG_SFWINK) || (conf.chan.sig == SIG_FGC_CAMA) || (conf.chan.sig == SIG_FGC_CAMAMF) ||
+ (conf.chan.sig == SIG_SF_FEATD) || (conf.chan.sig == SIG_SF_FEATDMF) ||
+ (conf.chan.sig == SIG_SF_FEATB)) {
+ p.starttime = 250;
+ }
+ if (conf.chan.radio) {
+ /* XXX Waiting to hear back from Jim if these should be adjustable XXX */
+ p.channo = channel;
+ p.rxwinktime = 1;
+ p.rxflashtime = 1;
+ p.starttime = 1;
+ p.debouncetime = 5;
+ }
+ if (!conf.chan.radio) {
+ p.channo = channel;
+ /* Override timing settings based on config file */
+ if (conf.timing.prewinktime >= 0)
+ p.prewinktime = conf.timing.prewinktime;
+ if (conf.timing.preflashtime >= 0)
+ p.preflashtime = conf.timing.preflashtime;
+ if (conf.timing.winktime >= 0)
+ p.winktime = conf.timing.winktime;
+ if (conf.timing.flashtime >= 0)
+ p.flashtime = conf.timing.flashtime;
+ if (conf.timing.starttime >= 0)
+ p.starttime = conf.timing.starttime;
+ if (conf.timing.rxwinktime >= 0)
+ p.rxwinktime = conf.timing.rxwinktime;
+ if (conf.timing.rxflashtime >= 0)
+ p.rxflashtime = conf.timing.rxflashtime;
+ if (conf.timing.debouncetime >= 0)
+ p.debouncetime = conf.timing.debouncetime;
+ }
+
+ /* dont set parms on a pseudo-channel (or CRV) */
+ if (tmp->subs[SUB_REAL].zfd >= 0)
+ {
+ res = ioctl(tmp->subs[SUB_REAL].zfd, ZT_SET_PARAMS, &p);
+ if (res < 0) {
+ ast_log(LOG_ERROR, "Unable to set parameters\n");
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ }
+#if 1
+ if (!here && (tmp->subs[SUB_REAL].zfd > -1)) {
+ memset(&bi, 0, sizeof(bi));
+ res = ioctl(tmp->subs[SUB_REAL].zfd, ZT_GET_BUFINFO, &bi);
+ if (!res) {
+ bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
+ bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
+ bi.numbufs = numbufs;
+ res = ioctl(tmp->subs[SUB_REAL].zfd, ZT_SET_BUFINFO, &bi);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set buffer policy on channel %d\n", channel);
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to check buffer policy on channel %d\n", channel);
+ }
+#endif
+ tmp->immediate = conf.chan.immediate;
+ tmp->transfertobusy = conf.chan.transfertobusy;
+ if (conf.chan.sig & __ZT_SIG_FXS)
+ tmp->mwimonitor = conf.chan.mwimonitor;
+ tmp->sig = conf.chan.sig;
+ tmp->outsigmod = conf.chan.outsigmod;
+ tmp->radio = conf.chan.radio;
+ tmp->ringt_base = ringt_base;
+ tmp->firstradio = 0;
+ if ((conf.chan.sig == SIG_FXOKS) || (conf.chan.sig == SIG_FXOLS) || (conf.chan.sig == SIG_FXOGS))
+ tmp->permcallwaiting = conf.chan.callwaiting;
+ else
+ tmp->permcallwaiting = 0;
+ /* Flag to destroy the channel must be cleared on new mkif. Part of changes for reload to work */
+ tmp->destroy = 0;
+ tmp->drings = drings;
+ tmp->usedistinctiveringdetection = usedistinctiveringdetection;
+ tmp->callwaitingcallerid = conf.chan.callwaitingcallerid;
+ tmp->threewaycalling = conf.chan.threewaycalling;
+ tmp->adsi = conf.chan.adsi;
+ tmp->use_smdi = conf.chan.use_smdi;
+ tmp->permhidecallerid = conf.chan.hidecallerid;
+ tmp->callreturn = conf.chan.callreturn;
+ tmp->echocancel = conf.chan.echocancel;
+ tmp->echotraining = conf.chan.echotraining;
+ tmp->pulse = conf.chan.pulse;
+ tmp->echocanbridged = conf.chan.echocanbridged;
+ tmp->busydetect = conf.chan.busydetect;
+ tmp->busycount = conf.chan.busycount;
+ tmp->busy_tonelength = conf.chan.busy_tonelength;
+ tmp->busy_quietlength = conf.chan.busy_quietlength;
+ tmp->callprogress = conf.chan.callprogress;
+ tmp->cancallforward = conf.chan.cancallforward;
+ tmp->dtmfrelax = conf.chan.dtmfrelax;
+ tmp->callwaiting = tmp->permcallwaiting;
+ tmp->hidecallerid = tmp->permhidecallerid;
+ tmp->channel = channel;
+ tmp->stripmsd = conf.chan.stripmsd;
+ tmp->use_callerid = conf.chan.use_callerid;
+ tmp->cid_signalling = conf.chan.cid_signalling;
+ tmp->cid_start = conf.chan.cid_start;
+ tmp->zaptrcallerid = conf.chan.zaptrcallerid;
+ tmp->restrictcid = conf.chan.restrictcid;
+ tmp->use_callingpres = conf.chan.use_callingpres;
+ tmp->priindication_oob = conf.chan.priindication_oob;
+ tmp->priexclusive = conf.chan.priexclusive;
+ if (tmp->usedistinctiveringdetection) {
+ if (!tmp->use_callerid) {
+ ast_log(LOG_NOTICE, "Distinctive Ring detect requires 'usecallerid' be on\n");
+ tmp->use_callerid = 1;
+ }
+ }
+
+ if (tmp->cid_signalling == CID_SIG_SMDI) {
+ if (!tmp->use_smdi) {
+ ast_log(LOG_WARNING, "SMDI callerid requires SMDI to be enabled, enabling...\n");
+ tmp->use_smdi = 1;
+ }
+ }
+ if (tmp->use_smdi) {
+ tmp->smdi_iface = ast_smdi_interface_find(conf.smdi_port);
+ if (!(tmp->smdi_iface)) {
+ ast_log(LOG_ERROR, "Invalid SMDI port specfied, disabling SMDI support\n");
+ tmp->use_smdi = 0;
+ }
+ }
+
+ ast_copy_string(tmp->accountcode, conf.chan.accountcode, sizeof(tmp->accountcode));
+ tmp->amaflags = conf.chan.amaflags;
+ if (!here) {
+ tmp->confno = -1;
+ tmp->propconfno = -1;
+ }
+ tmp->canpark = conf.chan.canpark;
+ tmp->transfer = conf.chan.transfer;
+ ast_copy_string(tmp->defcontext,conf.chan.context,sizeof(tmp->defcontext));
+ ast_copy_string(tmp->language, conf.chan.language, sizeof(tmp->language));
+ ast_copy_string(tmp->mohinterpret, conf.chan.mohinterpret, sizeof(tmp->mohinterpret));
+ ast_copy_string(tmp->mohsuggest, conf.chan.mohsuggest, sizeof(tmp->mohsuggest));
+ ast_copy_string(tmp->context, conf.chan.context, sizeof(tmp->context));
+ ast_copy_string(tmp->cid_num, conf.chan.cid_num, sizeof(tmp->cid_num));
+ tmp->cid_ton = 0;
+ ast_copy_string(tmp->cid_name, conf.chan.cid_name, sizeof(tmp->cid_name));
+ ast_copy_string(tmp->mailbox, conf.chan.mailbox, sizeof(tmp->mailbox));
+ if (!ast_strlen_zero(tmp->mailbox)) {
+ char *mailbox, *context;
+ mailbox = context = ast_strdupa(tmp->mailbox);
+ strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+ tmp->mwi_event_sub = ast_event_subscribe(AST_EVENT_MWI, mwi_event_cb, NULL,
+ AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
+ AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
+ AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_EXISTS,
+ AST_EVENT_IE_END);
+ }
+ tmp->msgstate = -1;
+ tmp->group = conf.chan.group;
+ tmp->callgroup = conf.chan.callgroup;
+ tmp->pickupgroup= conf.chan.pickupgroup;
+ if (conf.chan.vars) {
+ tmp->vars = conf.chan.vars;
+ conf.chan.vars = NULL;
+ }
+ tmp->cid_rxgain = conf.chan.cid_rxgain;
+ tmp->rxgain = conf.chan.rxgain;
+ tmp->txgain = conf.chan.txgain;
+ tmp->tonezone = conf.chan.tonezone;
+ tmp->onhooktime = time(NULL);
+ if (tmp->subs[SUB_REAL].zfd > -1) {
+ set_actual_gain(tmp->subs[SUB_REAL].zfd, 0, tmp->rxgain, tmp->txgain, tmp->law);
+ if (tmp->dsp)
+ ast_dsp_digitmode(tmp->dsp, DSP_DIGITMODE_DTMF | tmp->dtmfrelax);
+ update_conf(tmp);
+ if (!here) {
+ if ((conf.chan.sig != SIG_BRI) && (conf.chan.sig != SIG_BRI_PTMP) && (conf.chan.sig != SIG_PRI) && (conf.chan.sig != SIG_SS7))
+ /* Hang it up to be sure it's good */
+ zt_set_hook(tmp->subs[SUB_REAL].zfd, ZT_ONHOOK);
+ }
+ ioctl(tmp->subs[SUB_REAL].zfd,ZT_SETTONEZONE,&tmp->tonezone);
+#ifdef HAVE_PRI
+ /* the dchannel is down so put the channel in alarm */
+ if (tmp->pri && !pri_is_up(tmp->pri))
+ tmp->inalarm = 1;
+ else
+ tmp->inalarm = 0;
+#endif
+ memset(&si, 0, sizeof(si));
+ if (ioctl(tmp->subs[SUB_REAL].zfd,ZT_SPANSTAT,&si) == -1) {
+ ast_log(LOG_ERROR, "Unable to get span status: %s\n", strerror(errno));
+ destroy_zt_pvt(&tmp);
+ return NULL;
+ }
+ if (si.alarms) tmp->inalarm = 1;
+ }
+
+ tmp->polarityonanswerdelay = conf.chan.polarityonanswerdelay;
+ tmp->answeronpolarityswitch = conf.chan.answeronpolarityswitch;
+ tmp->hanguponpolarityswitch = conf.chan.hanguponpolarityswitch;
+ tmp->sendcalleridafter = conf.chan.sendcalleridafter;
+ if (!here) {
+ tmp->locallyblocked = tmp->remotelyblocked = 0;
+ if ((conf.chan.sig == SIG_PRI) || (conf.chan.sig == SIG_BRI) || (conf.chan.sig == SIG_BRI_PTMP) || (conf.chan.sig == SIG_SS7))
+ tmp->inservice = 0;
+ else /* We default to in service on protocols that don't have a reset */
+ tmp->inservice = 1;
+ }
+ }
+ if (tmp && !here) {
+ /* nothing on the iflist */
+ if (!*wlist) {
+ *wlist = tmp;
+ tmp->prev = NULL;
+ tmp->next = NULL;
+ *wend = tmp;
+ } else {
+ /* at least one member on the iflist */
+ struct zt_pvt *working = *wlist;
+
+ /* check if we maybe have to put it on the begining */
+ if (working->channel > tmp->channel) {
+ tmp->next = *wlist;
+ tmp->prev = NULL;
+ (*wlist)->prev = tmp;
+ *wlist = tmp;
+ } else {
+ /* go through all the members and put the member in the right place */
+ while (working) {
+ /* in the middle */
+ if (working->next) {
+ if (working->channel < tmp->channel && working->next->channel > tmp->channel) {
+ tmp->next = working->next;
+ tmp->prev = working;
+ working->next->prev = tmp;
+ working->next = tmp;
+ break;
+ }
+ } else {
+ /* the last */
+ if (working->channel < tmp->channel) {
+ working->next = tmp;
+ tmp->next = NULL;
+ tmp->prev = working;
+ *wend = tmp;
+ break;
+ }
+ }
+ working = working->next;
+ }
+ }
+ }
+ }
+ return tmp;
+}
+
+static inline int available(struct zt_pvt *p, int channelmatch, int groupmatch, int *busy, int *channelmatched, int *groupmatched)
+{
+ int res;
+ ZT_PARAMS par;
+
+ /* First, check group matching */
+ if (groupmatch) {
+ if ((p->group & groupmatch) != groupmatch)
+ return 0;
+ *groupmatched = 1;
+ }
+ /* Check to see if we have a channel match */
+ if (channelmatch != -1) {
+ if (p->channel != channelmatch)
+ return 0;
+ *channelmatched = 1;
+ }
+ /* We're at least busy at this point */
+ if (busy) {
+ if ((p->sig == SIG_FXOKS) || (p->sig == SIG_FXOLS) || (p->sig == SIG_FXOGS))
+ *busy = 1;
+ }
+ /* If do not disturb, definitely not */
+ if (p->dnd)
+ return 0;
+ /* If guard time, definitely not */
+ if (p->guardtime && (time(NULL) < p->guardtime))
+ return 0;
+
+ if (p->locallyblocked || p->remotelyblocked)
+ return 0;
+
+ /* If no owner definitely available */
+ if (!p->owner) {
+#ifdef HAVE_PRI
+ /* Trust PRI */
+ if (p->pri) {
+ if (p->resetting || p->call)
+ return 0;
+ else
+ return 1;
+ }
+#endif
+#ifdef HAVE_SS7
+ /* Trust SS7 */
+ if (p->ss7) {
+ if (p->ss7call)
+ return 0;
+ else
+ return 1;
+ }
+#endif
+ if (!(p->radio || (p->oprmode < 0)))
+ {
+ if (!p->sig || (p->sig == SIG_FXSLS))
+ return 1;
+ /* Check hook state */
+ if (p->subs[SUB_REAL].zfd > -1)
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_GET_PARAMS, &par);
+ else {
+ /* Assume not off hook on CVRS */
+ res = 0;
+ par.rxisoffhook = 0;
+ }
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to check hook state on channel %d\n", p->channel);
+ } else if ((p->sig == SIG_FXSKS) || (p->sig == SIG_FXSGS)) {
+ /* When "onhook" that means no battery on the line, and thus
+ it is out of service..., if it's on a TDM card... If it's a channel
+ bank, there is no telling... */
+ if (par.rxbits > -1)
+ return 1;
+ if (par.rxisoffhook)
+ return 1;
+ else
+#ifdef ZAP_CHECK_HOOKSTATE
+ return 0;
+#else
+ return 1;
+#endif
+ } else if (par.rxisoffhook) {
+ ast_debug(1, "Channel %d off hook, can't use\n", p->channel);
+ /* Not available when the other end is off hook */
+ return 0;
+ }
+ }
+ return 1;
+ }
+
+ /* If it's not an FXO, forget about call wait */
+ if ((p->sig != SIG_FXOKS) && (p->sig != SIG_FXOLS) && (p->sig != SIG_FXOGS))
+ return 0;
+
+ if (!p->callwaiting) {
+ /* If they don't have call waiting enabled, then for sure they're unavailable at this point */
+ return 0;
+ }
+
+ if (p->subs[SUB_CALLWAIT].zfd > -1) {
+ /* If there is already a call waiting call, then we can't take a second one */
+ return 0;
+ }
+
+ if ((p->owner->_state != AST_STATE_UP) &&
+ ((p->owner->_state != AST_STATE_RINGING) || p->outgoing)) {
+ /* If the current call is not up, then don't allow the call */
+ return 0;
+ }
+ if ((p->subs[SUB_THREEWAY].owner) && (!p->subs[SUB_THREEWAY].inthreeway)) {
+ /* Can't take a call wait when the three way calling hasn't been merged yet. */
+ return 0;
+ }
+ /* We're cool */
+ return 1;
+}
+
+static struct zt_pvt *chandup(struct zt_pvt *src)
+{
+ struct zt_pvt *p;
+ ZT_BUFFERINFO bi;
+ int res;
+
+ if ((p = ast_malloc(sizeof(*p)))) {
+ memcpy(p, src, sizeof(struct zt_pvt));
+ ast_mutex_init(&p->lock);
+ p->subs[SUB_REAL].zfd = zt_open("/dev/zap/pseudo");
+ /* Allocate a zapata structure */
+ if (p->subs[SUB_REAL].zfd < 0) {
+ ast_log(LOG_ERROR, "Unable to dup channel: %s\n", strerror(errno));
+ destroy_zt_pvt(&p);
+ return NULL;
+ }
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_GET_BUFINFO, &bi);
+ if (!res) {
+ bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
+ bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
+ bi.numbufs = numbufs;
+ res = ioctl(p->subs[SUB_REAL].zfd, ZT_SET_BUFINFO, &bi);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set buffer policy on dup channel\n");
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to check buffer policy on dup channel\n");
+ }
+ p->destroy = 1;
+ p->next = iflist;
+ iflist = p;
+ return p;
+}
+
+
+#ifdef HAVE_PRI
+static int pri_find_empty_chan(struct zt_pri *pri, int backwards)
+{
+ int x;
+ if (backwards)
+ x = pri->numchans;
+ else
+ x = 0;
+ for (;;) {
+ if (backwards && (x < 0))
+ break;
+ if (!backwards && (x >= pri->numchans))
+ break;
+ if (pri->pvts[x] && !pri->pvts[x]->inalarm && !pri->pvts[x]->owner) {
+ ast_debug(1, "Found empty available channel %d/%d\n",
+ pri->pvts[x]->logicalspan, pri->pvts[x]->prioffset);
+ return x;
+ }
+ if (backwards)
+ x--;
+ else
+ x++;
+ }
+ return -1;
+}
+#endif
+
+static struct ast_channel *zt_request(const char *type, int format, void *data, int *cause)
+{
+ int groupmatch = 0;
+ int channelmatch = -1;
+ int roundrobin = 0;
+ int callwait = 0;
+ int busy = 0;
+ struct zt_pvt *p;
+ struct ast_channel *tmp = NULL;
+ char *dest=NULL;
+ int x;
+ char *s;
+ char opt=0;
+ int res=0, y=0;
+ int backwards = 0;
+#ifdef HAVE_PRI
+ int crv;
+ int bearer = -1;
+ int trunkgroup;
+ struct zt_pri *pri=NULL;
+#endif
+ struct zt_pvt *exit, *start, *end;
+ ast_mutex_t *lock;
+ int channelmatched = 0;
+ int groupmatched = 0;
+
+ /* Assume we're locking the iflock */
+ lock = &iflock;
+ start = iflist;
+ end = ifend;
+ if (data) {
+ dest = ast_strdupa((char *)data);
+ } else {
+ ast_log(LOG_WARNING, "Channel requested with no data\n");
+ return NULL;
+ }
+ if (toupper(dest[0]) == 'G' || toupper(dest[0])=='R') {
+ /* Retrieve the group number */
+ char *stringp=NULL;
+ stringp=dest + 1;
+ s = strsep(&stringp, "/");
+ if ((res = sscanf(s, "%d%c%d", &x, &opt, &y)) < 1) {
+ ast_log(LOG_WARNING, "Unable to determine group for data %s\n", (char *)data);
+ return NULL;
+ }
+ groupmatch = 1 << x;
+ if (toupper(dest[0]) == 'G') {
+ if (dest[0] == 'G') {
+ backwards = 1;
+ p = ifend;
+ } else
+ p = iflist;
+ } else {
+ if (dest[0] == 'R') {
+ backwards = 1;
+ p = round_robin[x]?round_robin[x]->prev:ifend;
+ if (!p)
+ p = ifend;
+ } else {
+ p = round_robin[x]?round_robin[x]->next:iflist;
+ if (!p)
+ p = iflist;
+ }
+ roundrobin = 1;
+ }
+ } else {
+ char *stringp=NULL;
+ stringp=dest;
+ s = strsep(&stringp, "/");
+ p = iflist;
+ if (!strcasecmp(s, "pseudo")) {
+ /* Special case for pseudo */
+ x = CHAN_PSEUDO;
+ channelmatch = x;
+ }
+#ifdef HAVE_PRI
+ else if ((res = sscanf(s, "%d:%d%c%d", &trunkgroup, &crv, &opt, &y)) > 1) {
+ if ((trunkgroup < 1) || (crv < 1)) {
+ ast_log(LOG_WARNING, "Unable to determine trunk group and CRV for data %s\n", (char *)data);
+ return NULL;
+ }
+ res--;
+ for (x = 0; x < NUM_SPANS; x++) {
+ if (pris[x].trunkgroup == trunkgroup) {
+ pri = pris + x;
+ lock = &pri->lock;
+ start = pri->crvs;
+ end = pri->crvend;
+ break;
+ }
+ }
+ if (!pri) {
+ ast_log(LOG_WARNING, "Unable to find trunk group %d\n", trunkgroup);
+ return NULL;
+ }
+ channelmatch = crv;
+ p = pris[x].crvs;
+ }
+#endif
+ else if ((res = sscanf(s, "%d%c%d", &x, &opt, &y)) < 1) {
+ ast_log(LOG_WARNING, "Unable to determine channel for data %s\n", (char *)data);
+ return NULL;
+ } else {
+ channelmatch = x;
+ }
+ }
+ /* Search for an unowned channel */
+ ast_mutex_lock(lock);
+ exit = p;
+ while (p && !tmp) {
+ if (roundrobin)
+ round_robin[x] = p;
+#if 0
+ ast_verbose("name = %s, %d, %d, %d\n",p->owner ? p->owner->name : "<none>", p->channel, channelmatch, groupmatch);
+#endif
+
+ if (p && available(p, channelmatch, groupmatch, &busy, &channelmatched, &groupmatched)) {
+ ast_debug(1, "Using channel %d\n", p->channel);
+ if (p->inalarm)
+ goto next;
+
+ callwait = (p->owner != NULL);
+#ifdef HAVE_PRI
+ if (pri && (p->subs[SUB_REAL].zfd < 0)) {
+ if (p->sig != SIG_FXSKS) {
+ /* Gotta find an actual channel to use for this
+ CRV if this isn't a callwait */
+ bearer = pri_find_empty_chan(pri, 0);
+ if (bearer < 0) {
+ ast_log(LOG_NOTICE, "Out of bearer channels on span %d for call to CRV %d:%d\n", pri->span, trunkgroup, crv);
+ p = NULL;
+ break;
+ }
+ pri_assign_bearer(p, pri, pri->pvts[bearer]);
+ } else {
+ if (alloc_sub(p, 0)) {
+ ast_log(LOG_NOTICE, "Failed to allocate place holder pseudo channel!\n");
+ p = NULL;
+ break;
+ } else
+ ast_debug(1, "Allocated placeholder pseudo channel\n");
+
+ p->pri = pri;
+ }
+ }
+#endif
+ if (p->channel == CHAN_PSEUDO) {
+ p = chandup(p);
+ if (!p) {
+ break;
+ }
+ }
+ if (p->owner) {
+ if (alloc_sub(p, SUB_CALLWAIT)) {
+ p = NULL;
+ break;
+ }
+ }
+ p->outgoing = 1;
+ tmp = zt_new(p, AST_STATE_RESERVED, 0, p->owner ? SUB_CALLWAIT : SUB_REAL, 0, 0);
+#ifdef HAVE_PRI
+ if (p->bearer) {
+ /* Log owner to bearer channel, too */
+ p->bearer->owner = tmp;
+ }
+#endif
+ /* Make special notes */
+ if (res > 1) {
+ if (opt == 'c') {
+ /* Confirm answer */
+ p->confirmanswer = 1;
+ } else if (opt == 'r') {
+ /* Distinctive ring */
+ if (res < 3)
+ ast_log(LOG_WARNING, "Distinctive ring missing identifier in '%s'\n", (char *)data);
+ else
+ p->distinctivering = y;
+ } else if (opt == 'd') {
+ /* If this is an ISDN call, make it digital */
+ p->digital = 1;
+ if (tmp)
+ tmp->transfercapability = AST_TRANS_CAP_DIGITAL;
+ } else {
+ ast_log(LOG_WARNING, "Unknown option '%c' in '%s'\n", opt, (char *)data);
+ }
+ }
+ /* Note if the call is a call waiting call */
+ if (tmp && callwait)
+ tmp->cdrflags |= AST_CDR_CALLWAIT;
+ break;
+ }
+next:
+ if (backwards) {
+ p = p->prev;
+ if (!p)
+ p = end;
+ } else {
+ p = p->next;
+ if (!p)
+ p = start;
+ }
+ /* stop when you roll to the one that we started from */
+ if (p == exit)
+ break;
+ }
+ ast_mutex_unlock(lock);
+ restart_monitor();
+ if (callwait)
+ *cause = AST_CAUSE_BUSY;
+ else if (!tmp) {
+ if (channelmatched) {
+ if (busy)
+ *cause = AST_CAUSE_BUSY;
+ } else if (groupmatched) {
+ *cause = AST_CAUSE_CONGESTION;
+ }
+ }
+
+ return tmp;
+}
+
+#if defined(HAVE_PRI) || defined(HAVE_SS7)
+static int zt_setlaw(int zfd, int law)
+{
+ return ioctl(zfd, ZT_SETLAW, &law);
+}
+#endif
+
+#ifdef HAVE_SS7
+
+static int ss7_find_cic(struct zt_ss7 *linkset, int cic)
+{
+ int i;
+ int winner = -1;
+ for (i = 0; i < linkset->numchans; i++) {
+ if (linkset->pvts[i] && (linkset->pvts[i]->cic == cic)) {
+ winner = i;
+ }
+ }
+ return winner;
+}
+
+static void ss7_handle_cqm(struct zt_ss7 *linkset, int startcic, int endcic)
+{
+ unsigned char status[32];
+ struct zt_pvt *p = NULL;
+ int i, offset;
+
+ for (i = 0; i < linkset->numchans; i++) {
+ if (linkset->pvts[i] && ((linkset->pvts[i]->cic >= startcic) && (linkset->pvts[i]->cic <= endcic))) {
+ p = linkset->pvts[i];
+ offset = p->cic - startcic;
+ status[offset] = 0;
+ if (p->locallyblocked)
+ status[offset] |= (1 << 0) | (1 << 4);
+ if (p->remotelyblocked)
+ status[offset] |= (1 << 1) | (1 << 5);
+ if (p->ss7call) {
+ if (p->outgoing)
+ status[offset] |= (1 << 3);
+ else
+ status[offset] |= (1 << 2);
+ } else
+ status[offset] |= 0x3 << 2;
+ }
+ }
+
+ if (p)
+ isup_cqr(linkset->ss7, startcic, endcic, p->dpc, status);
+ else
+ ast_log(LOG_WARNING, "Could not find any equipped circuits within CQM CICs\n");
+
+}
+
+static inline void ss7_block_cics(struct zt_ss7 *linkset, int startcic, int endcic, unsigned char state[], int block)
+{
+ int i;
+
+ for (i = 0; i < linkset->numchans; i++) {
+ if (linkset->pvts[i] && ((linkset->pvts[i]->cic >= startcic) && (linkset->pvts[i]->cic <= endcic))) {
+ if (state) {
+ if (state[i])
+ linkset->pvts[i]->remotelyblocked = block;
+ } else
+ linkset->pvts[i]->remotelyblocked = block;
+ }
+ }
+}
+
+static void ss7_inservice(struct zt_ss7 *linkset, int startcic, int endcic)
+{
+ int i;
+
+ for (i = 0; i < linkset->numchans; i++) {
+ if (linkset->pvts[i] && (linkset->pvts[i]->cic >= startcic) && (linkset->pvts[i]->cic <= endcic))
+ linkset->pvts[i]->inservice = 1;
+ }
+}
+
+static void ss7_reset_linkset(struct zt_ss7 *linkset)
+{
+ int i, startcic = -1, endcic, dpc;
+
+ if (linkset->numchans <= 0)
+ return;
+
+ startcic = linkset->pvts[0]->cic;
+ /* DB: CIC's DPC fix */
+ dpc = linkset->pvts[0]->dpc;
+
+ for (i = 0; i < linkset->numchans; i++) {
+ if (linkset->pvts[i+1] && linkset->pvts[i+1]->dpc == dpc && ((linkset->pvts[i+1]->cic - linkset->pvts[i]->cic) == 1) && (linkset->pvts[i]->cic - startcic < 31)) {
+ continue;
+ } else {
+ endcic = linkset->pvts[i]->cic;
+ ast_verbose("Resetting CICs %d to %d\n", startcic, endcic);
+ isup_grs(linkset->ss7, startcic, endcic, dpc);
+
+ /* DB: CIC's DPC fix */
+ if (linkset->pvts[i+1]) {
+ startcic = linkset->pvts[i+1]->cic;
+ dpc = linkset->pvts[i+1]->dpc;
+ }
+ }
+ }
+}
+
+static void zt_loopback(struct zt_pvt *p, int enable)
+{
+ if (p->loopedback != enable) {
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_LOOPBACK, &enable)) {
+ ast_log(LOG_WARNING, "Unable to set loopback on channel %d\n", p->channel);
+ return;
+ }
+ p->loopedback = enable;
+ }
+}
+
+static void ss7_start_call(struct zt_pvt *p, struct zt_ss7 *linkset)
+{
+ struct ss7 *ss7 = linkset->ss7;
+ int res;
+ int law = 1;
+ struct ast_channel *c;
+ char tmp[256];
+
+ if (ioctl(p->subs[SUB_REAL].zfd, ZT_AUDIOMODE, &law) == -1)
+ ast_log(LOG_WARNING, "Unable to set audio mode on channel %d to %d\n", p->channel, law);
+
+ if (linkset->type == SS7_ITU)
+ law = ZT_LAW_ALAW;
+ else
+ law = ZT_LAW_MULAW;
+
+ res = zt_setlaw(p->subs[SUB_REAL].zfd, law);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to set law on channel %d\n", p->channel);
+
+ p->proceeding = 1;
+ isup_acm(ss7, p->ss7call);
+
+ ast_mutex_unlock(&linkset->lock);
+ c = zt_new(p, AST_STATE_RING, 1, SUB_REAL, law, 0);
+ ast_mutex_lock(&linkset->lock);
+ if (c)
+ ast_verb(3, "Accepting call to '%s' on CIC %d\n", p->exten, p->cic);
+ else
+ ast_log(LOG_WARNING, "Unable to start PBX on CIC %d\n", p->cic);
+
+ if (!ast_strlen_zero(p->charge_number)) {
+ pbx_builtin_setvar_helper(c, "SS7_CHARGE_NUMBER", p->charge_number);
+ /* Clear this after we set it */
+ p->charge_number[0] = 0;
+ }
+ if (!ast_strlen_zero(p->gen_add_number)) {
+ pbx_builtin_setvar_helper(c, "SS7_GENERIC_ADDRESS", p->gen_add_number);
+ /* Clear this after we set it */
+ p->gen_add_number[0] = 0;
+ }
+ if (!ast_strlen_zero(p->jip_number)) {
+ pbx_builtin_setvar_helper(c, "SS7_JIP", p->jip_number);
+ /* Clear this after we set it */
+ p->jip_number[0] = 0;
+ }
+ if (!ast_strlen_zero(p->gen_dig_number)) {
+ pbx_builtin_setvar_helper(c, "SS7_GENERIC_DIGITS", p->gen_dig_number);
+ /* Clear this after we set it */
+ p->gen_dig_number[0] = 0;
+ }
+
+ snprintf(tmp, sizeof(tmp), "%d", p->gen_dig_type);
+ pbx_builtin_setvar_helper(c, "SS7_GENERIC_DIGTYPE", tmp);
+ /* Clear this after we set it */
+ p->gen_dig_type = 0;
+
+ snprintf(tmp, sizeof(tmp), "%d", p->gen_dig_scheme);
+ pbx_builtin_setvar_helper(c, "SS7_GENERIC_DIGSCHEME", tmp);
+ /* Clear this after we set it */
+ p->gen_dig_scheme = 0;
+
+ if (!ast_strlen_zero(p->lspi_ident)) {
+ pbx_builtin_setvar_helper(c, "SS7_LSPI_IDENT", p->lspi_ident);
+ /* Clear this after we set it */
+ p->lspi_ident[0] = 0;
+ }
+
+ snprintf(tmp, sizeof(tmp), "%d", p->call_ref_ident);
+ pbx_builtin_setvar_helper(c, "SS7_CALLREF_IDENT", tmp);
+ /* Clear this after we set it */
+ p->call_ref_ident = 0;
+
+ snprintf(tmp, sizeof(tmp), "%d", p->call_ref_pc);
+ pbx_builtin_setvar_helper(c, "SS7_CALLREF_PC", tmp);
+ /* Clear this after we set it */
+ p->call_ref_pc = 0;
+
+}
+
+static void ss7_apply_plan_to_number(char *buf, size_t size, const struct zt_ss7 *ss7, const char *number, const unsigned nai)
+{
+ switch (nai) {
+ case SS7_NAI_INTERNATIONAL:
+ snprintf(buf, size, "%s%s", ss7->internationalprefix, number);
+ break;
+ case SS7_NAI_NATIONAL:
+ snprintf(buf, size, "%s%s", ss7->nationalprefix, number);
+ break;
+ case SS7_NAI_SUBSCRIBER:
+ snprintf(buf, size, "%s%s", ss7->subscriberprefix, number);
+ break;
+ case SS7_NAI_UNKNOWN:
+ snprintf(buf, size, "%s%s", ss7->unknownprefix, number);
+ break;
+ default:
+ snprintf(buf, size, "%s", number);
+ break;
+ }
+}
+static int ss7_pres_scr2cid_pres(char presentation_ind, char screening_ind)
+{
+ return ((presentation_ind & 0x3) << 5) | (screening_ind & 0x3);
+}
+
+static void *ss7_linkset(void *data)
+{
+ int res, i;
+ struct timeval *next = NULL, tv;
+ struct zt_ss7 *linkset = (struct zt_ss7 *) data;
+ struct ss7 *ss7 = linkset->ss7;
+ ss7_event *e = NULL;
+ struct zt_pvt *p;
+ int chanpos;
+ struct pollfd pollers[NUM_DCHANS];
+ int cic;
+ unsigned int dpc;
+ int nextms = 0;
+
+ ss7_start(ss7);
+
+ while(1) {
+ ast_mutex_lock(&linkset->lock);
+ if ((next = ss7_schedule_next(ss7))) {
+ tv = ast_tvnow();
+ tv.tv_sec = next->tv_sec - tv.tv_sec;
+ tv.tv_usec = next->tv_usec - tv.tv_usec;
+ if (tv.tv_usec < 0) {
+ tv.tv_usec += 1000000;
+ tv.tv_sec -= 1;
+ }
+ if (tv.tv_sec < 0) {
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ }
+ nextms = tv.tv_sec * 1000;
+ nextms += tv.tv_usec / 1000;
+ }
+ ast_mutex_unlock(&linkset->lock);
+
+ for (i = 0; i < linkset->numsigchans; i++) {
+ pollers[i].fd = linkset->fds[i];
+ pollers[i].events = POLLIN | POLLOUT | POLLPRI;
+ pollers[i].revents = 0;
+ }
+
+ res = poll(pollers, linkset->numsigchans, nextms);
+ if ((res < 0) && (errno != EINTR)) {
+ ast_log(LOG_ERROR, "poll(%s)\n", strerror(errno));
+ } else if (!res) {
+ ast_mutex_lock(&linkset->lock);
+ ss7_schedule_run(ss7);
+ ast_mutex_unlock(&linkset->lock);
+ continue;
+ }
+
+ ast_mutex_lock(&linkset->lock);
+ for (i = 0; i < linkset->numsigchans; i++) {
+ if (pollers[i].revents & POLLPRI) {
+ int x;
+ if (ioctl(pollers[i].fd, ZT_GETEVENT, &x)) {
+ ast_log(LOG_ERROR, "Error in exception retrieval!\n");
+ }
+ switch (x) {
+ case ZT_EVENT_OVERRUN:
+ ast_debug(1, "Overrun detected!\n");
+ break;
+ case ZT_EVENT_BADFCS:
+ ast_debug(1, "Bad FCS\n");
+ break;
+ case ZT_EVENT_ABORT:
+ ast_debug(1, "HDLC Abort\n");
+ break;
+ case ZT_EVENT_ALARM:
+ ast_log(LOG_ERROR, "Alarm on link!\n");
+ linkset->linkstate[i] |= (LINKSTATE_DOWN | LINKSTATE_INALARM);
+ linkset->linkstate[i] &= ~LINKSTATE_UP;
+ ss7_link_alarm(ss7, pollers[i].fd);
+ break;
+ case ZT_EVENT_NOALARM:
+ ast_log(LOG_ERROR, "Alarm cleared on link\n");
+ linkset->linkstate[i] &= ~(LINKSTATE_INALARM | LINKSTATE_DOWN);
+ linkset->linkstate[i] |= LINKSTATE_STARTING;
+ ss7_link_noalarm(ss7, pollers[i].fd);
+ break;
+ default:
+ ast_log(LOG_ERROR, "Got exception %d!\n", x);
+ break;
+ }
+ }
+
+ if (pollers[i].revents & POLLIN) {
+ ast_mutex_lock(&linkset->lock);
+ res = ss7_read(ss7, pollers[i].fd);
+ ast_mutex_unlock(&linkset->lock);
+ }
+ if (pollers[i].revents & POLLOUT) {
+ ast_mutex_lock(&linkset->lock);
+ res = ss7_write(ss7, pollers[i].fd);
+ ast_mutex_unlock(&linkset->lock);
+ if (res < 0) {
+ ast_log(LOG_ERROR, "Error in write %s", strerror(errno));
+ }
+ }
+ }
+
+#if 0
+ if (res < 0)
+ exit(-1);
+#endif
+
+ while ((e = ss7_check_event(ss7))) {
+ switch (e->e) {
+ case SS7_EVENT_UP:
+ if (linkset->state != LINKSET_STATE_UP) {
+ ast_verbose("--- SS7 Up ---\n");
+ ss7_reset_linkset(linkset);
+ }
+ linkset->state = LINKSET_STATE_UP;
+ break;
+ case SS7_EVENT_DOWN:
+ ast_verbose("--- SS7 Down ---\n");
+ linkset->state = LINKSET_STATE_DOWN;
+ for (i = 0; i < linkset->numchans; i++) {
+ struct zt_pvt *p = linkset->pvts[i];
+ if (p)
+ p->inalarm = 1;
+ }
+ break;
+ case MTP2_LINK_UP:
+ ast_debug(1, "MTP2 link up\n");
+ break;
+ case ISUP_EVENT_CPG:
+ chanpos = ss7_find_cic(linkset, e->cpg.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "CPG on unconfigured CIC %d\n", e->cpg.cic);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ ast_mutex_lock(&p->lock);
+ switch (e->cpg.event) {
+ case CPG_EVENT_ALERTING:
+ p->alerting = 1;
+ p->subs[SUB_REAL].needringing = 1;
+ break;
+ case CPG_EVENT_PROGRESS:
+ case CPG_EVENT_INBANDINFO:
+ {
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_PROGRESS, };
+ ast_debug(1, "Queuing frame PROGRESS on CIC %d\n", p->cic);
+ zap_queue_frame(p, &f, linkset);
+ p->progress = 1;
+ if (p->dsp && p->dsp_features) {
+ ast_dsp_set_features(p->dsp, p->dsp_features);
+ p->dsp_features = 0;
+ }
+ }
+ break;
+ default:
+ ast_debug(1, "Do not handle CPG with event type 0x%x\n", e->cpg.event);
+ }
+
+ ast_mutex_unlock(&p->lock);
+ break;
+ case ISUP_EVENT_RSC:
+ ast_verbose("Resetting CIC %d\n", e->rsc.cic);
+ chanpos = ss7_find_cic(linkset, e->rsc.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "RSC on unconfigured CIC %d\n", e->rsc.cic);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ ast_mutex_lock(&p->lock);
+ p->inservice = 1;
+ p->remotelyblocked = 0;
+ dpc = p->dpc;
+ isup_set_call_dpc(e->rsc.call, dpc);
+ ast_mutex_unlock(&p->lock);
+ isup_rlc(ss7, e->rsc.call);
+ break;
+ case ISUP_EVENT_GRS:
+ ast_debug(1, "Got Reset for CICs %d to %d: Acknowledging\n", e->grs.startcic, e->grs.endcic);
+ chanpos = ss7_find_cic(linkset, e->grs.startcic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "GRS on unconfigured CIC %d\n", e->grs.startcic);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ isup_gra(ss7, e->grs.startcic, e->grs.endcic, p->dpc);
+ ss7_block_cics(linkset, e->grs.startcic, e->grs.endcic, NULL, 0);
+ break;
+ case ISUP_EVENT_CQM:
+ ast_debug(1, "Got Circuit group query message from CICs %d to %d\n", e->cqm.startcic, e->cqm.endcic);
+ ss7_handle_cqm(linkset, e->cqm.startcic, e->cqm.endcic);
+ break;
+ case ISUP_EVENT_GRA:
+ ast_verbose("Got reset acknowledgement from CIC %d to %d.\n", e->gra.startcic, e->gra.endcic);
+ ss7_inservice(linkset, e->gra.startcic, e->gra.endcic);
+ ss7_block_cics(linkset, e->gra.startcic, e->gra.endcic, e->gra.status, 1);
+ break;
+ case ISUP_EVENT_IAM:
+ ast_debug(1, "Got IAM for CIC %d and called number %s, calling number %s\n", e->iam.cic, e->iam.called_party_num, e->iam.calling_party_num);
+ chanpos = ss7_find_cic(linkset, e->iam.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "IAM on unconfigured CIC %d\n", e->iam.cic);
+ isup_rel(ss7, e->iam.call, -1);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ ast_mutex_lock(&p->lock);
+ if (p->owner) {
+ if (p->ss7call == e->iam.call) {
+ ast_mutex_unlock(&p->lock);
+ ast_log(LOG_WARNING, "Duplicate IAM requested on CIC %d\n", e->iam.cic);
+ break;
+ } else {
+ ast_mutex_unlock(&p->lock);
+ ast_log(LOG_WARNING, "Ring requested on CIC %d already in use!\n", e->iam.cic);
+ break;
+ }
+ }
+
+ dpc = p->dpc;
+ p->ss7call = e->iam.call;
+ isup_set_call_dpc(p->ss7call, dpc);
+
+ if ( (p->use_callerid) && (!ast_strlen_zero(e->iam.calling_party_num)) )
+ {
+ ss7_apply_plan_to_number(p->cid_num, sizeof(p->cid_num), linkset, e->iam.calling_party_num, e->iam.calling_nai);
+ p->callingpres = ss7_pres_scr2cid_pres(e->iam.presentation_ind, e->iam.screening_ind);
+ }
+ else
+ p->cid_num[0] = 0;
+
+ if (p->immediate) {
+ p->exten[0] = 's';
+ p->exten[1] = '\0';
+ } else if (!ast_strlen_zero(e->iam.called_party_num)) {
+ char *st;
+ ss7_apply_plan_to_number(p->exten, sizeof(p->exten), linkset, e->iam.called_party_num, e->iam.called_nai);
+ st = strchr(p->exten, '#');
+ if (st)
+ *st = '\0';
+ } else
+ p->exten[0] = '\0';
+
+ p->cid_ani[0] = '\0';
+ p->cid_name[0] = '\0';
+ p->cid_ani2 = e->iam.oli_ani2;
+ p->cid_ton = 0;
+ ast_copy_string(p->charge_number, e->iam.charge_number, sizeof(p->charge_number));
+ ast_copy_string(p->gen_add_number, e->iam.gen_add_number, sizeof(p->gen_add_number));
+ p->gen_add_type = e->iam.gen_add_type;
+ p->gen_add_nai = e->iam.gen_add_nai;
+ p->gen_add_pres_ind = e->iam.gen_add_pres_ind;
+ p->gen_add_num_plan = e->iam.gen_add_num_plan;
+ ast_copy_string(p->gen_dig_number, e->iam.gen_dig_number, sizeof(p->gen_dig_number));
+ p->gen_dig_type = e->iam.gen_dig_type;
+ p->gen_dig_scheme = e->iam.gen_dig_scheme;
+ ast_copy_string(p->jip_number, e->iam.jip_number, sizeof(p->jip_number));
+
+ /* Set DNID */
+ if (!ast_strlen_zero(e->iam.called_party_num))
+ ss7_apply_plan_to_number(p->dnid, sizeof(p->dnid), linkset, e->iam.called_party_num, e->iam.called_nai);
+
+ if (ast_exists_extension(NULL, p->context, p->exten, 1, p->cid_num)) {
+
+ if (e->iam.cot_check_required) {
+ zt_loopback(p, 1);
+ } else
+ ss7_start_call(p, linkset);
+ } else {
+ ast_debug(1, "Call on CIC for unconfigured extension %s\n", p->exten);
+ isup_rel(ss7, e->iam.call, -1);
+ }
+ ast_mutex_unlock(&p->lock);
+ break;
+ case ISUP_EVENT_COT:
+ chanpos = ss7_find_cic(linkset, e->cot.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "COT on unconfigured CIC %d\n", e->cot.cic);
+ isup_rel(ss7, e->cot.call, -1);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+
+ zt_loopback(p, 0);
+
+ ss7_start_call(p, linkset);
+ break;
+ case ISUP_EVENT_CCR:
+ ast_debug(1, "Got CCR request on CIC %d\n", e->ccr.cic);
+ chanpos = ss7_find_cic(linkset, e->ccr.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "CCR on unconfigured CIC %d\n", e->ccr.cic);
+ break;
+ }
+
+ p = linkset->pvts[chanpos];
+
+ ast_mutex_lock(&p->lock);
+ zt_loopback(p, 1);
+ ast_mutex_unlock(&p->lock);
+
+ isup_lpa(linkset->ss7, e->ccr.cic, p->dpc);
+ break;
+ case ISUP_EVENT_REL:
+ chanpos = ss7_find_cic(linkset, e->rel.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "REL on unconfigured CIC %d\n", e->rel.cic);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ ast_mutex_lock(&p->lock);
+ if (p->owner) {
+ p->owner->hangupcause = e->rel.cause;
+ p->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ } else
+ ast_log(LOG_WARNING, "REL on channel (CIC %d) without owner!\n", p->cic);
+
+ /* End the loopback if we have one */
+ zt_loopback(p, 0);
+
+ isup_rlc(ss7, e->rel.call);
+ p->ss7call = NULL;
+
+ ast_mutex_unlock(&p->lock);
+ break;
+ case ISUP_EVENT_ACM:
+ chanpos = ss7_find_cic(linkset, e->acm.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "ACM on unconfigured CIC %d\n", e->acm.cic);
+ isup_rel(ss7, e->acm.call, -1);
+ break;
+ } else {
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_PROCEEDING, };
+
+ p = linkset->pvts[chanpos];
+
+ ast_debug(1, "Queueing frame from SS7_EVENT_ACM on CIC %d\n", p->cic);
+
+ if (e->acm.call_ref_ident > 0) {
+ p->rlt = 1; /* Setting it but not using it here*/
+ }
+
+ ast_mutex_lock(&p->lock);
+ zap_queue_frame(p, &f, linkset);
+ p->proceeding = 1;
+
+ ast_mutex_unlock(&p->lock);
+ }
+ break;
+ case ISUP_EVENT_CGB:
+ chanpos = ss7_find_cic(linkset, e->cgb.startcic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "CGB on unconfigured CIC %d\n", e->cgb.startcic);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ ss7_block_cics(linkset, e->cgb.startcic, e->cgb.endcic, e->cgb.status, 1);
+ isup_cgba(linkset->ss7, e->cgb.startcic, e->cgb.endcic, p->dpc, e->cgb.status, e->cgb.type);
+ break;
+ case ISUP_EVENT_CGU:
+ chanpos = ss7_find_cic(linkset, e->cgu.startcic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "CGU on unconfigured CIC %d\n", e->cgu.startcic);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ ss7_block_cics(linkset, e->cgu.startcic, e->cgu.endcic, e->cgu.status, 0);
+ isup_cgua(linkset->ss7, e->cgu.startcic, e->cgu.endcic, p->dpc, e->cgu.status, e->cgu.type);
+ break;
+ case ISUP_EVENT_UCIC:
+ chanpos = ss7_find_cic(linkset, e->ucic.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "UCIC on unconfigured CIC %d\n", e->ucic.cic);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ ast_debug(1, "Unequiped Circuit Id Code on CIC %d\n", e->ucic.cic);
+ ast_mutex_lock(&p->lock);
+ p->remotelyblocked = 1;
+ p->inservice = 0;
+ ast_mutex_unlock(&p->lock); //doesn't require a SS7 acknowledgement
+ break;
+ case ISUP_EVENT_BLO:
+ chanpos = ss7_find_cic(linkset, e->blo.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "BLO on unconfigured CIC %d\n", e->blo.cic);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ ast_debug(1, "Blocking CIC %d\n", e->blo.cic);
+ ast_mutex_lock(&p->lock);
+ p->remotelyblocked = 1;
+ ast_mutex_unlock(&p->lock);
+ isup_bla(linkset->ss7, e->blo.cic, p->dpc);
+ break;
+ case ISUP_EVENT_BLA:
+ chanpos = ss7_find_cic(linkset, e->bla.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "BLA on unconfigured CIC %d\n", e->bla.cic);
+ break;
+ }
+ ast_debug(1, "Blocking CIC %d\n", e->bla.cic);
+ p = linkset->pvts[chanpos];
+ ast_mutex_lock(&p->lock);
+ p->locallyblocked = 1;
+ ast_mutex_unlock(&p->lock);
+ break;
+ case ISUP_EVENT_UBL:
+ chanpos = ss7_find_cic(linkset, e->ubl.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "UBL on unconfigured CIC %d\n", e->ubl.cic);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ ast_debug(1, "Unblocking CIC %d\n", e->ubl.cic);
+ ast_mutex_lock(&p->lock);
+ p->remotelyblocked = 0;
+ ast_mutex_unlock(&p->lock);
+ isup_uba(linkset->ss7, e->ubl.cic, p->dpc);
+ break;
+ case ISUP_EVENT_UBA:
+ chanpos = ss7_find_cic(linkset, e->uba.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "UBA on unconfigured CIC %d\n", e->uba.cic);
+ break;
+ }
+ p = linkset->pvts[chanpos];
+ ast_debug(1, "Unblocking CIC %d\n", e->uba.cic);
+ ast_mutex_lock(&p->lock);
+ p->locallyblocked = 0;
+ ast_mutex_unlock(&p->lock);
+ break;
+ case ISUP_EVENT_CON:
+ case ISUP_EVENT_ANM:
+ if (e->e == ISUP_EVENT_CON)
+ cic = e->con.cic;
+ else
+ cic = e->anm.cic;
+
+ chanpos = ss7_find_cic(linkset, cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "ANM/CON on unconfigured CIC %d\n", cic);
+ isup_rel(ss7, (e->e == ISUP_EVENT_ANM) ? e->anm.call : e->con.call, -1);
+ break;
+ } else {
+ p = linkset->pvts[chanpos];
+ ast_mutex_lock(&p->lock);
+ p->subs[SUB_REAL].needanswer = 1;
+ if (p->dsp && p->dsp_features) {
+ ast_dsp_set_features(p->dsp, p->dsp_features);
+ p->dsp_features = 0;
+ }
+ zt_enable_ec(p);
+ ast_mutex_unlock(&p->lock);
+ }
+ break;
+ case ISUP_EVENT_RLC:
+ chanpos = ss7_find_cic(linkset, e->rlc.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "RLC on unconfigured CIC %d\n", e->rlc.cic);
+ break;
+ } else {
+ p = linkset->pvts[chanpos];
+ ast_mutex_lock(&p->lock);
+ if (p->alreadyhungup)
+ p->ss7call = NULL;
+ else
+ ast_log(LOG_NOTICE, "Received RLC out and we haven't sent REL. Ignoring.\n");
+ ast_mutex_unlock(&p->lock);
+ }
+ break;
+ case ISUP_EVENT_FAA:
+ chanpos = ss7_find_cic(linkset, e->faa.cic);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "FAA on unconfigured CIC %d\n", e->faa.cic);
+ break;
+ } else {
+ p = linkset->pvts[chanpos];
+ ast_debug(1, "FAA received on CIC %d\n", e->faa.cic);
+ ast_mutex_lock(&p->lock);
+ if (p->alreadyhungup){
+ p->ss7call = NULL;
+ ast_log(LOG_NOTICE, "Received FAA and we haven't sent FAR. Ignoring.\n");
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ break;
+ default:
+ ast_debug(1, "Unknown event %s\n", ss7_event2str(e->e));
+ break;
+ }
+ }
+ ast_mutex_unlock(&linkset->lock);
+ }
+
+ return 0;
+}
+
+static void zt_ss7_message(struct ss7 *ss7, char *s)
+{
+#if 0
+ int i;
+
+ for (i = 0; i < NUM_SPANS; i++)
+ if (linksets[i].ss7 == ss7)
+ break;
+
+ ast_verbose("[%d] %s", i+1, s);
+#else
+ ast_verbose("%s", s);
+#endif
+}
+
+static void zt_ss7_error(struct ss7 *ss7, char *s)
+{
+#if 0
+ int i;
+
+ for (i = 0; i < NUM_SPANS; i++)
+ if (linksets[i].ss7 == ss7)
+ break;
+
+#else
+ ast_log(LOG_ERROR, "%s", s);
+#endif
+}
+
+#endif /* HAVE_SS7 */
+
+#ifdef HAVE_PRI
+static struct zt_pvt *pri_find_crv(struct zt_pri *pri, int crv)
+{
+ struct zt_pvt *p;
+ p = pri->crvs;
+ while (p) {
+ if (p->channel == crv)
+ return p;
+ p = p->next;
+ }
+ return NULL;
+}
+
+
+static int pri_find_principle(struct zt_pri *pri, int channel)
+{
+ int x;
+ int span = PRI_SPAN(channel);
+ int spanfd;
+ ZT_PARAMS param;
+ int principle = -1;
+ int explicit = PRI_EXPLICIT(channel);
+ channel = PRI_CHANNEL(channel);
+
+ if (!explicit) {
+ spanfd = pri_active_dchan_fd(pri);
+ if (ioctl(spanfd, ZT_GET_PARAMS, &param))
+ return -1;
+ span = pris[param.spanno - 1].prilogicalspan;
+ }
+
+ for (x = 0; x < pri->numchans; x++) {
+ if (pri->pvts[x] && (pri->pvts[x]->prioffset == channel) && (pri->pvts[x]->logicalspan == span)) {
+ principle = x;
+ break;
+ }
+ }
+
+ return principle;
+}
+
+static int pri_fixup_principle(struct zt_pri *pri, int principle, q931_call *c)
+{
+ int x;
+ struct zt_pvt *crv;
+ if (!c) {
+ if (principle < 0)
+ return -1;
+ return principle;
+ }
+ if ((principle > -1) &&
+ (principle < pri->numchans) &&
+ (pri->pvts[principle]) &&
+ (pri->pvts[principle]->call == c))
+ return principle;
+ /* First, check for other bearers */
+ for (x = 0; x < pri->numchans; x++) {
+ if (!pri->pvts[x])
+ continue;
+ if (pri->pvts[x]->call == c) {
+ /* Found our call */
+ if (principle != x) {
+ ast_verb(3, "Moving call from channel %d to channel %d\n",
+ pri->pvts[x]->channel, pri->pvts[principle]->channel);
+ if (pri->pvts[principle]->owner) {
+ ast_log(LOG_WARNING, "Can't fix up channel from %d to %d because %d is already in use\n",
+ pri->pvts[x]->channel, pri->pvts[principle]->channel, pri->pvts[principle]->channel);
+ return -1;
+ }
+ /* Fix it all up now */
+ pri->pvts[principle]->owner = pri->pvts[x]->owner;
+ if (pri->pvts[principle]->owner) {
+ ast_string_field_build(pri->pvts[principle]->owner, name,
+ "Zap/%d:%d-%d", pri->trunkgroup,
+ pri->pvts[principle]->channel, 1);
+ pri->pvts[principle]->owner->tech_pvt = pri->pvts[principle];
+ ast_channel_set_fd(pri->pvts[principle]->owner, 0, pri->pvts[principle]->subs[SUB_REAL].zfd);
+ pri->pvts[principle]->subs[SUB_REAL].owner = pri->pvts[x]->subs[SUB_REAL].owner;
+ } else
+ ast_log(LOG_WARNING, "Whoa, there's no owner, and we're having to fix up channel %d to channel %d\n", pri->pvts[x]->channel, pri->pvts[principle]->channel);
+ pri->pvts[principle]->call = pri->pvts[x]->call;
+ /* Free up the old channel, now not in use */
+ pri->pvts[x]->subs[SUB_REAL].owner = NULL;
+ pri->pvts[x]->owner = NULL;
+ pri->pvts[x]->call = NULL;
+ }
+ return principle;
+ }
+ }
+ /* Now check for a CRV with no bearer */
+ crv = pri->crvs;
+ while (crv) {
+ if (crv->call == c) {
+ /* This is our match... Perform some basic checks */
+ if (crv->bearer)
+ ast_log(LOG_WARNING, "Trying to fix up call which already has a bearer which isn't the one we think it is\n");
+ else if (pri->pvts[principle]->owner)
+ ast_log(LOG_WARNING, "Tring to fix up a call to a bearer which already has an owner!\n");
+ else {
+ /* Looks good. Drop the pseudo channel now, clear up the assignment, and
+ wakeup the potential sleeper */
+ zt_close(crv->subs[SUB_REAL].zfd);
+ pri->pvts[principle]->call = crv->call;
+ pri_assign_bearer(crv, pri, pri->pvts[principle]);
+ ast_debug(1, "Assigning bearer %d/%d to CRV %d:%d\n",
+ pri->pvts[principle]->logicalspan, pri->pvts[principle]->prioffset,
+ pri->trunkgroup, crv->channel);
+ wakeup_sub(crv, SUB_REAL, pri);
+ }
+ return principle;
+ }
+ crv = crv->next;
+ }
+ ast_log(LOG_WARNING, "Call specified, but not found?\n");
+ return -1;
+}
+
+static void *do_idle_thread(void *vchan)
+{
+ struct ast_channel *chan = vchan;
+ struct zt_pvt *pvt = chan->tech_pvt;
+ struct ast_frame *f;
+ char ex[80];
+ /* Wait up to 30 seconds for an answer */
+ int newms, ms = 30000;
+ ast_verb(3, "Initiating idle call on channel %s\n", chan->name);
+ snprintf(ex, sizeof(ex), "%d/%s", pvt->channel, pvt->pri->idledial);
+ if (ast_call(chan, ex, 0)) {
+ ast_log(LOG_WARNING, "Idle dial failed on '%s' to '%s'\n", chan->name, ex);
+ ast_hangup(chan);
+ return NULL;
+ }
+ while ((newms = ast_waitfor(chan, ms)) > 0) {
+ f = ast_read(chan);
+ if (!f) {
+ /* Got hangup */
+ break;
+ }
+ if (f->frametype == AST_FRAME_CONTROL) {
+ switch (f->subclass) {
+ case AST_CONTROL_ANSWER:
+ /* Launch the PBX */
+ ast_copy_string(chan->exten, pvt->pri->idleext, sizeof(chan->exten));
+ ast_copy_string(chan->context, pvt->pri->idlecontext, sizeof(chan->context));
+ chan->priority = 1;
+ ast_verb(4, "Idle channel '%s' answered, sending to %s@%s\n", chan->name, chan->exten, chan->context);
+ ast_pbx_run(chan);
+ /* It's already hungup, return immediately */
+ return NULL;
+ case AST_CONTROL_BUSY:
+ ast_verb(4, "Idle channel '%s' busy, waiting...\n", chan->name);
+ break;
+ case AST_CONTROL_CONGESTION:
+ ast_verb(4, "Idle channel '%s' congested, waiting...\n", chan->name);
+ break;
+ };
+ }
+ ast_frfree(f);
+ ms = newms;
+ }
+ /* Hangup the channel since nothing happend */
+ ast_hangup(chan);
+ return NULL;
+}
+
+#ifndef PRI_RESTART
+#error "Upgrade your libpri"
+#endif
+static void zt_pri_message(struct pri *pri, char *s)
+{
+ int x, y;
+ int dchan = -1, span = -1;
+ int dchancount = 0;
+
+ if (pri) {
+ for (x = 0; x < NUM_SPANS; x++) {
+ for (y = 0; y < NUM_DCHANS; y++) {
+ if (pris[x].dchans[y])
+ dchancount++;
+
+ if (pris[x].dchans[y] == pri)
+ dchan = y;
+ }
+ if (dchan >= 0) {
+ span = x;
+ break;
+ }
+ dchancount = 0;
+ }
+ if (dchancount > 1 && (span > -1))
+ ast_verbose("[Span %d D-Channel %d]%s", span, dchan, s);
+ else
+ ast_verbose("%s", s);
+ } else
+ ast_verbose("%s", s);
+
+ ast_mutex_lock(&pridebugfdlock);
+
+ if (pridebugfd >= 0)
+ write(pridebugfd, s, strlen(s));
+
+ ast_mutex_unlock(&pridebugfdlock);
+}
+
+static void zt_pri_error(struct pri *pri, char *s)
+{
+ int x, y;
+ int dchan = -1, span = -1;
+ int dchancount = 0;
+
+ if (pri) {
+ for (x = 0; x < NUM_SPANS; x++) {
+ for (y = 0; y < NUM_DCHANS; y++) {
+ if (pris[x].dchans[y])
+ dchancount++;
+
+ if (pris[x].dchans[y] == pri)
+ dchan = y;
+ }
+ if (dchan >= 0) {
+ span = x;
+ break;
+ }
+ dchancount = 0;
+ }
+ if ((dchancount > 1) && (span > -1))
+ ast_log(LOG_ERROR, "[Span %d D-Channel %d] PRI: %s", span, dchan, s);
+ else
+ ast_log(LOG_ERROR, "%s", s);
+ } else
+ ast_log(LOG_ERROR, "%s", s);
+
+ ast_mutex_lock(&pridebugfdlock);
+
+ if (pridebugfd >= 0)
+ write(pridebugfd, s, strlen(s));
+
+ ast_mutex_unlock(&pridebugfdlock);
+}
+
+static int pri_check_restart(struct zt_pri *pri)
+{
+ do {
+ pri->resetpos++;
+ } while ((pri->resetpos < pri->numchans) &&
+ (!pri->pvts[pri->resetpos] ||
+ pri->pvts[pri->resetpos]->call ||
+ pri->pvts[pri->resetpos]->resetting));
+ if (pri->resetpos < pri->numchans) {
+ /* Mark the channel as resetting and restart it */
+ pri->pvts[pri->resetpos]->resetting = 1;
+ pri_reset(pri->pri, PVT_TO_CHANNEL(pri->pvts[pri->resetpos]));
+ } else {
+ pri->resetting = 0;
+ time(&pri->lastreset);
+ }
+ return 0;
+}
+
+static int pri_hangup_all(struct zt_pvt *p, struct zt_pri *pri)
+{
+ int x;
+ int redo;
+ ast_mutex_unlock(&pri->lock);
+ ast_mutex_lock(&p->lock);
+ do {
+ redo = 0;
+ for (x = 0; x < 3; x++) {
+ while (p->subs[x].owner && ast_channel_trylock(p->subs[x].owner)) {
+ redo++;
+ ast_mutex_unlock(&p->lock);
+ usleep(1);
+ ast_mutex_lock(&p->lock);
+ }
+ if (p->subs[x].owner) {
+ ast_queue_hangup(p->subs[x].owner);
+ ast_channel_unlock(p->subs[x].owner);
+ }
+ }
+ } while (redo);
+ ast_mutex_unlock(&p->lock);
+ ast_mutex_lock(&pri->lock);
+ return 0;
+}
+
+static char * redirectingreason2str(int redirectingreason)
+{
+ switch (redirectingreason) {
+ case 0:
+ return "UNKNOWN";
+ case 1:
+ return "BUSY";
+ case 2:
+ return "NO_REPLY";
+ case 0xF:
+ return "UNCONDITIONAL";
+ default:
+ return "NOREDIRECT";
+ }
+}
+
+static void apply_plan_to_number(char *buf, size_t size, const struct zt_pri *pri, const char *number, const int plan)
+{
+ if (pri->dialplan == -2) { /* autodetect the TON but leave the number untouched */
+ snprintf(buf, size, "%s", number);
+ return;
+ }
+ switch (plan) {
+ case PRI_INTERNATIONAL_ISDN: /* Q.931 dialplan == 0x11 international dialplan => prepend international prefix digits */
+ snprintf(buf, size, "%s%s", pri->internationalprefix, number);
+ break;
+ case PRI_NATIONAL_ISDN: /* Q.931 dialplan == 0x21 national dialplan => prepend national prefix digits */
+ snprintf(buf, size, "%s%s", pri->nationalprefix, number);
+ break;
+ case PRI_LOCAL_ISDN: /* Q.931 dialplan == 0x41 local dialplan => prepend local prefix digits */
+ snprintf(buf, size, "%s%s", pri->localprefix, number);
+ break;
+ case PRI_PRIVATE: /* Q.931 dialplan == 0x49 private dialplan => prepend private prefix digits */
+ snprintf(buf, size, "%s%s", pri->privateprefix, number);
+ break;
+ case PRI_UNKNOWN: /* Q.931 dialplan == 0x00 unknown dialplan => prepend unknown prefix digits */
+ snprintf(buf, size, "%s%s", pri->unknownprefix, number);
+ break;
+ default: /* other Q.931 dialplan => don't twiddle with callingnum */
+ snprintf(buf, size, "%s", number);
+ break;
+ }
+}
+
+
+static void *pri_dchannel(void *vpri)
+{
+ struct zt_pri *pri = vpri;
+ pri_event *e;
+ struct pollfd fds[NUM_DCHANS];
+ int res;
+ int chanpos = 0;
+ int x;
+ int haveidles;
+ int activeidles;
+ int nextidle = -1;
+ struct ast_channel *c;
+ struct timeval tv, lowest, *next;
+ struct timeval lastidle = ast_tvnow();
+ int doidling=0;
+ char *cc;
+ char idlen[80];
+ struct ast_channel *idle;
+ pthread_t p;
+ time_t t;
+ int i, which=-1;
+ int numdchans;
+ int cause=0;
+ struct zt_pvt *crv;
+ pthread_t threadid;
+ char ani2str[6];
+ char plancallingnum[256];
+ char plancallingani[256];
+ char calledtonstr[10];
+
+ if (!ast_strlen_zero(pri->idledial) && !ast_strlen_zero(pri->idleext)) {
+ /* Need to do idle dialing, check to be sure though */
+ cc = strchr(pri->idleext, '@');
+ if (cc) {
+ *cc = '\0';
+ cc++;
+ ast_copy_string(pri->idlecontext, cc, sizeof(pri->idlecontext));
+#if 0
+ /* Extensions may not be loaded yet */
+ if (!ast_exists_extension(NULL, pri->idlecontext, pri->idleext, 1, NULL))
+ ast_log(LOG_WARNING, "Extension '%s @ %s' does not exist\n", pri->idleext, pri->idlecontext);
+ else
+#endif
+ doidling = 1;
+ } else
+ ast_log(LOG_WARNING, "Idle dial string '%s' lacks '@context'\n", pri->idleext);
+ }
+ for (;;) {
+ for (i = 0; i < NUM_DCHANS; i++) {
+ if (!pri->dchannels[i])
+ break;
+ fds[i].fd = pri->fds[i];
+ fds[i].events = POLLIN | POLLPRI;
+ fds[i].revents = 0;
+ }
+ numdchans = i;
+ time(&t);
+ ast_mutex_lock(&pri->lock);
+ if (pri->switchtype != PRI_SWITCH_GR303_TMC && (pri->resetinterval > 0)) {
+ if (pri->resetting && pri_is_up(pri)) {
+ if (pri->resetpos < 0)
+ pri_check_restart(pri);
+ } else {
+ if (!pri->resetting && (t - pri->lastreset) >= pri->resetinterval) {
+ pri->resetting = 1;
+ pri->resetpos = -1;
+ }
+ }
+ }
+ /* Look for any idle channels if appropriate */
+ if (doidling && pri_is_up(pri)) {
+ nextidle = -1;
+ haveidles = 0;
+ activeidles = 0;
+ for (x = pri->numchans; x >= 0; x--) {
+ if (pri->pvts[x] && !pri->pvts[x]->owner &&
+ !pri->pvts[x]->call) {
+ if (haveidles < pri->minunused) {
+ haveidles++;
+ } else if (!pri->pvts[x]->resetting) {
+ nextidle = x;
+ break;
+ }
+ } else if (pri->pvts[x] && pri->pvts[x]->owner && pri->pvts[x]->isidlecall)
+ activeidles++;
+ }
+ if (nextidle > -1) {
+ if (ast_tvdiff_ms(ast_tvnow(), lastidle) > 1000) {
+ /* Don't create a new idle call more than once per second */
+ snprintf(idlen, sizeof(idlen), "%d/%s", pri->pvts[nextidle]->channel, pri->idledial);
+ idle = zt_request("Zap", AST_FORMAT_ULAW, idlen, &cause);
+ if (idle) {
+ pri->pvts[nextidle]->isidlecall = 1;
+ if (ast_pthread_create_background(&p, NULL, do_idle_thread, idle)) {
+ ast_log(LOG_WARNING, "Unable to start new thread for idle channel '%s'\n", idle->name);
+ zt_hangup(idle);
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to request channel 'Zap/%s' for idle call\n", idlen);
+ lastidle = ast_tvnow();
+ }
+ } else if ((haveidles < pri->minunused) &&
+ (activeidles > pri->minidle)) {
+ /* Mark something for hangup if there is something
+ that can be hungup */
+ for (x = pri->numchans; x >= 0; x--) {
+ /* find a candidate channel */
+ if (pri->pvts[x] && pri->pvts[x]->owner && pri->pvts[x]->isidlecall) {
+ pri->pvts[x]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ haveidles++;
+ /* Stop if we have enough idle channels or
+ can't spare any more active idle ones */
+ if ((haveidles >= pri->minunused) ||
+ (activeidles <= pri->minidle))
+ break;
+ }
+ }
+ }
+ }
+ /* Start with reasonable max */
+ lowest = ast_tv(60, 0);
+ for (i = 0; i < NUM_DCHANS; i++) {
+ /* Find lowest available d-channel */
+ if (!pri->dchannels[i])
+ break;
+ if ((next = pri_schedule_next(pri->dchans[i]))) {
+ /* We need relative time here */
+ tv = ast_tvsub(*next, ast_tvnow());
+ if (tv.tv_sec < 0) {
+ tv = ast_tv(0,0);
+ }
+ if (doidling || pri->resetting) {
+ if (tv.tv_sec > 1) {
+ tv = ast_tv(1, 0);
+ }
+ } else {
+ if (tv.tv_sec > 60) {
+ tv = ast_tv(60, 0);
+ }
+ }
+ } else if (doidling || pri->resetting) {
+ /* Make sure we stop at least once per second if we're
+ monitoring idle channels */
+ tv = ast_tv(1,0);
+ } else {
+ /* Don't poll for more than 60 seconds */
+ tv = ast_tv(60, 0);
+ }
+ if (!i || ast_tvcmp(tv, lowest) < 0) {
+ lowest = tv;
+ }
+ }
+ ast_mutex_unlock(&pri->lock);
+
+ e = NULL;
+ res = poll(fds, numdchans, lowest.tv_sec * 1000 + lowest.tv_usec / 1000);
+
+ ast_mutex_lock(&pri->lock);
+ if (!res) {
+ for (which = 0; which < NUM_DCHANS; which++) {
+ if (!pri->dchans[which])
+ break;
+ /* Just a timeout, run the scheduler */
+ e = pri_schedule_run(pri->dchans[which]);
+ if (e)
+ break;
+ }
+ } else if (res > -1) {
+ for (which = 0; which < NUM_DCHANS; which++) {
+ if (!pri->dchans[which])
+ break;
+ if (fds[which].revents & POLLPRI) {
+ /* Check for an event */
+ x = 0;
+ res = ioctl(pri->fds[which], ZT_GETEVENT, &x);
+ if (x) {
+ ast_log(LOG_NOTICE, "PRI got event: %s (%d) on %s D-channel of span %d\n", event2str(x), x, pri_order(which), pri->span);
+ manager_event(EVENT_FLAG_SYSTEM, "PRIEvent",
+ "PRIEvent: %s\r\n"
+ "PRIEventCode: %d\r\n"
+ "D-channel: %s\r\n"
+ "Span: %d\r\n",
+ event2str(x),
+ x,
+ pri_order(which),
+ pri->span
+ );
+ }
+ /* Keep track of alarm state */
+ if (x == ZT_EVENT_ALARM) {
+ pri->dchanavail[which] &= ~(DCHAN_NOTINALARM | DCHAN_UP);
+ pri_find_dchan(pri);
+ } else if (x == ZT_EVENT_NOALARM) {
+ pri->dchanavail[which] |= DCHAN_NOTINALARM;
+ pri_restart(pri->dchans[which]);
+ }
+
+ ast_debug(1, "Got event %s (%d) on D-channel for span %d\n", event2str(x), x, pri->span);
+ } else if (fds[which].revents & POLLIN) {
+ e = pri_check_event(pri->dchans[which]);
+ }
+ if (e)
+ break;
+ }
+ } else if (errno != EINTR)
+ ast_log(LOG_WARNING, "pri_event returned error %d (%s)\n", errno, strerror(errno));
+
+ if (e) {
+ if (pri->debug)
+ pri_dump_event(pri->dchans[which], e);
+ if (e->e != PRI_EVENT_DCHAN_DOWN)
+ pri->dchanavail[which] |= DCHAN_UP;
+
+ if ((e->e != PRI_EVENT_DCHAN_UP) && (e->e != PRI_EVENT_DCHAN_DOWN) && (pri->pri != pri->dchans[which]))
+ /* Must be an NFAS group that has the secondary dchan active */
+ pri->pri = pri->dchans[which];
+
+ switch (e->e) {
+ case PRI_EVENT_DCHAN_UP:
+ ast_verb(2, "%s D-Channel on span %d up\n", pri_order(which), pri->span);
+ pri->dchanavail[which] |= DCHAN_UP;
+ if (!pri->pri) pri_find_dchan(pri);
+
+ /* Note presense of D-channel */
+ time(&pri->lastreset);
+
+ /* Restart in 5 seconds */
+ if (pri->resetinterval > -1) {
+ pri->lastreset -= pri->resetinterval;
+ pri->lastreset += 5;
+ }
+ pri->resetting = 0;
+ /* Take the channels from inalarm condition */
+ for (i = 0; i < pri->numchans; i++)
+ if (pri->pvts[i]) {
+ pri->pvts[i]->inalarm = 0;
+ }
+ break;
+ case PRI_EVENT_DCHAN_DOWN:
+ ast_verb(2, "%s D-Channel on span %d down\n", pri_order(which), pri->span);
+ pri->dchanavail[which] &= ~DCHAN_UP;
+ pri_find_dchan(pri);
+ if (!pri_is_up(pri)) {
+ pri->resetting = 0;
+ /* Hangup active channels and put them in alarm mode */
+ for (i = 0; i < pri->numchans; i++) {
+ struct zt_pvt *p = pri->pvts[i];
+ if (p) {
+ if (!p->pri || !p->pri->pri || pri_get_timer(p->pri->pri, PRI_TIMER_T309) < 0) {
+ /* T309 is not enabled : hangup calls when alarm occurs */
+ if (p->call) {
+ if (p->pri && p->pri->pri) {
+ pri_hangup(p->pri->pri, p->call, -1);
+ pri_destroycall(p->pri->pri, p->call);
+ p->call = NULL;
+ } else
+ ast_log(LOG_WARNING, "The PRI Call have not been destroyed\n");
+ }
+ if (p->realcall) {
+ pri_hangup_all(p->realcall, pri);
+ } else if (p->owner)
+ p->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ }
+ p->inalarm = 1;
+ }
+ }
+ }
+ break;
+ case PRI_EVENT_RESTART:
+ if (e->restart.channel > -1) {
+ chanpos = pri_find_principle(pri, e->restart.channel);
+ if (chanpos < 0)
+ ast_log(LOG_WARNING, "Restart requested on odd/unavailable channel number %d/%d on span %d\n",
+ PRI_SPAN(e->restart.channel), PRI_CHANNEL(e->restart.channel), pri->span);
+ else {
+ ast_verb(3, "B-channel %d/%d restarted on span %d\n",
+ PRI_SPAN(e->restart.channel), PRI_CHANNEL(e->restart.channel), pri->span);
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ if (pri->pvts[chanpos]->call) {
+ pri_destroycall(pri->pri, pri->pvts[chanpos]->call);
+ pri->pvts[chanpos]->call = NULL;
+ }
+ /* Force soft hangup if appropriate */
+ if (pri->pvts[chanpos]->realcall)
+ pri_hangup_all(pri->pvts[chanpos]->realcall, pri);
+ else if (pri->pvts[chanpos]->owner)
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ } else {
+ ast_verb(3, "Restart on requested on entire span %d\n", pri->span);
+ for (x = 0; x < pri->numchans; x++)
+ if (pri->pvts[x]) {
+ ast_mutex_lock(&pri->pvts[x]->lock);
+ if (pri->pvts[x]->call) {
+ pri_destroycall(pri->pri, pri->pvts[x]->call);
+ pri->pvts[x]->call = NULL;
+ }
+ if (pri->pvts[chanpos]->realcall)
+ pri_hangup_all(pri->pvts[chanpos]->realcall, pri);
+ else if (pri->pvts[x]->owner)
+ pri->pvts[x]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ ast_mutex_unlock(&pri->pvts[x]->lock);
+ }
+ }
+ break;
+ case PRI_EVENT_KEYPAD_DIGIT:
+ chanpos = pri_find_principle(pri, e->digit.channel);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "KEYPAD_DIGITs received on unconfigured channel %d/%d span %d\n",
+ PRI_SPAN(e->digit.channel), PRI_CHANNEL(e->digit.channel), pri->span);
+ } else {
+ chanpos = pri_fixup_principle(pri, chanpos, e->digit.call);
+ if (chanpos > -1) {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ /* queue DTMF frame if the PBX for this call was already started (we're forwarding KEYPAD_DIGITs further on */
+ if ((pri->overlapdial & ZAP_OVERLAPDIAL_INCOMING) && pri->pvts[chanpos]->call==e->digit.call && pri->pvts[chanpos]->owner) {
+ /* how to do that */
+ int digitlen = strlen(e->digit.digits);
+ char digit;
+ int i;
+ for (i = 0; i < digitlen; i++) {
+ digit = e->digit.digits[i];
+ {
+ struct ast_frame f = { AST_FRAME_DTMF, digit, };
+ zap_queue_frame(pri->pvts[chanpos], &f, pri);
+ }
+ }
+ }
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ }
+ break;
+
+ case PRI_EVENT_INFO_RECEIVED:
+ chanpos = pri_find_principle(pri, e->ring.channel);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "INFO received on unconfigured channel %d/%d span %d\n",
+ PRI_SPAN(e->ring.channel), PRI_CHANNEL(e->ring.channel), pri->span);
+ } else {
+ chanpos = pri_fixup_principle(pri, chanpos, e->ring.call);
+ if (chanpos > -1) {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ /* queue DTMF frame if the PBX for this call was already started (we're forwarding INFORMATION further on */
+ if ((pri->overlapdial & ZAP_OVERLAPDIAL_INCOMING) && pri->pvts[chanpos]->call==e->ring.call && pri->pvts[chanpos]->owner) {
+ /* how to do that */
+ int digitlen = strlen(e->ring.callednum);
+ char digit;
+ int i;
+ for (i = 0; i < digitlen; i++) {
+ digit = e->ring.callednum[i];
+ {
+ struct ast_frame f = { AST_FRAME_DTMF, digit, };
+ zap_queue_frame(pri->pvts[chanpos], &f, pri);
+ }
+ }
+ }
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ }
+ break;
+ case PRI_EVENT_RING:
+ crv = NULL;
+ if (e->ring.channel == -1)
+ chanpos = pri_find_empty_chan(pri, 1);
+ else
+ chanpos = pri_find_principle(pri, e->ring.channel);
+ /* if no channel specified find one empty */
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Ring requested on unconfigured channel %d/%d span %d\n",
+ PRI_SPAN(e->ring.channel), PRI_CHANNEL(e->ring.channel), pri->span);
+ } else {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ if (pri->pvts[chanpos]->owner) {
+ if (pri->pvts[chanpos]->call == e->ring.call) {
+ ast_log(LOG_WARNING, "Duplicate setup requested on channel %d/%d already in use on span %d\n",
+ PRI_SPAN(e->ring.channel), PRI_CHANNEL(e->ring.channel), pri->span);
+ break;
+ } else {
+ /* This is where we handle initial glare */
+ ast_debug(1, "Ring requested on channel %d/%d already in use or previously requested on span %d. Attempting to renegotiate channel.\n",
+ PRI_SPAN(e->ring.channel), PRI_CHANNEL(e->ring.channel), pri->span);
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ chanpos = -1;
+ }
+ }
+ if (chanpos > -1)
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ if ((chanpos < 0) && (e->ring.flexible))
+ chanpos = pri_find_empty_chan(pri, 1);
+ if (chanpos > -1) {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ if (pri->switchtype == PRI_SWITCH_GR303_TMC) {
+ /* Should be safe to lock CRV AFAIK while bearer is still locked */
+ crv = pri_find_crv(pri, pri_get_crv(pri->pri, e->ring.call, NULL));
+ if (crv)
+ ast_mutex_lock(&crv->lock);
+ if (!crv || crv->owner) {
+ pri->pvts[chanpos]->call = NULL;
+ if (crv) {
+ if (crv->owner)
+ crv->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ ast_log(LOG_WARNING, "Call received for busy CRV %d on span %d\n", pri_get_crv(pri->pri, e->ring.call, NULL), pri->span);
+ } else
+ ast_log(LOG_NOTICE, "Call received for unconfigured CRV %d on span %d\n", pri_get_crv(pri->pri, e->ring.call, NULL), pri->span);
+ pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_INVALID_CALL_REFERENCE);
+ if (crv)
+ ast_mutex_unlock(&crv->lock);
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ break;
+ }
+ }
+ pri->pvts[chanpos]->call = e->ring.call;
+ apply_plan_to_number(plancallingnum, sizeof(plancallingnum), pri, e->ring.callingnum, e->ring.callingplan);
+ if (pri->pvts[chanpos]->use_callerid) {
+ ast_shrink_phone_number(plancallingnum);
+ ast_copy_string(pri->pvts[chanpos]->cid_num, plancallingnum, sizeof(pri->pvts[chanpos]->cid_num));
+#ifdef PRI_ANI
+ if (!ast_strlen_zero(e->ring.callingani)) {
+ apply_plan_to_number(plancallingani, sizeof(plancallingani), pri, e->ring.callingani, e->ring.callingplanani);
+ ast_shrink_phone_number(plancallingani);
+ ast_copy_string(pri->pvts[chanpos]->cid_ani, plancallingani, sizeof(pri->pvts[chanpos]->cid_ani));
+ } else {
+ pri->pvts[chanpos]->cid_ani[0] = '\0';
+ }
+#endif
+ ast_copy_string(pri->pvts[chanpos]->cid_name, e->ring.callingname, sizeof(pri->pvts[chanpos]->cid_name));
+ pri->pvts[chanpos]->cid_ton = e->ring.callingplan; /* this is the callingplan (TON/NPI), e->ring.callingplan>>4 would be the TON */
+ } else {
+ pri->pvts[chanpos]->cid_num[0] = '\0';
+ pri->pvts[chanpos]->cid_ani[0] = '\0';
+ pri->pvts[chanpos]->cid_name[0] = '\0';
+ pri->pvts[chanpos]->cid_ton = 0;
+ }
+ apply_plan_to_number(pri->pvts[chanpos]->rdnis, sizeof(pri->pvts[chanpos]->rdnis), pri,
+ e->ring.redirectingnum, e->ring.callingplanrdnis);
+ /* If immediate=yes go to s|1 */
+ if (pri->pvts[chanpos]->immediate) {
+ ast_verb(3, "Going to extension s|1 because of immediate=yes\n");
+ pri->pvts[chanpos]->exten[0] = 's';
+ pri->pvts[chanpos]->exten[1] = '\0';
+ }
+ /* Get called number */
+ else if (!ast_strlen_zero(e->ring.callednum)) {
+ ast_copy_string(pri->pvts[chanpos]->exten, e->ring.callednum, sizeof(pri->pvts[chanpos]->exten));
+ ast_copy_string(pri->pvts[chanpos]->dnid, e->ring.callednum, sizeof(pri->pvts[chanpos]->dnid));
+ } else if (pri->overlapdial)
+ pri->pvts[chanpos]->exten[0] = '\0';
+ else {
+ /* Some PRI circuits are set up to send _no_ digits. Handle them as 's'. */
+ pri->pvts[chanpos]->exten[0] = 's';
+ pri->pvts[chanpos]->exten[1] = '\0';
+ }
+ /* Set DNID on all incoming calls -- even immediate */
+ if (!ast_strlen_zero(e->ring.callednum))
+ ast_copy_string(pri->pvts[chanpos]->dnid, e->ring.callednum, sizeof(pri->pvts[chanpos]->dnid));
+ /* No number yet, but received "sending complete"? */
+ if (e->ring.complete && (ast_strlen_zero(e->ring.callednum))) {
+ ast_verb(3, "Going to extension s|1 because of Complete received\n");
+ pri->pvts[chanpos]->exten[0] = 's';
+ pri->pvts[chanpos]->exten[1] = '\0';
+ }
+ /* Make sure extension exists (or in overlap dial mode, can exist) */
+ if (((pri->overlapdial & ZAP_OVERLAPDIAL_INCOMING) && ast_canmatch_extension(NULL, pri->pvts[chanpos]->context, pri->pvts[chanpos]->exten, 1, pri->pvts[chanpos]->cid_num)) ||
+ ast_exists_extension(NULL, pri->pvts[chanpos]->context, pri->pvts[chanpos]->exten, 1, pri->pvts[chanpos]->cid_num)) {
+ /* Setup law */
+ int law;
+ if (pri->switchtype != PRI_SWITCH_GR303_TMC) {
+ /* Set to audio mode at this point */
+ law = 1;
+ if (ioctl(pri->pvts[chanpos]->subs[SUB_REAL].zfd, ZT_AUDIOMODE, &law) == -1)
+ ast_log(LOG_WARNING, "Unable to set audio mode on channel %d to %d\n", pri->pvts[chanpos]->channel, law);
+ }
+ if (e->ring.layer1 == PRI_LAYER_1_ALAW)
+ law = ZT_LAW_ALAW;
+ else
+ law = ZT_LAW_MULAW;
+ res = zt_setlaw(pri->pvts[chanpos]->subs[SUB_REAL].zfd, law);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to set law on channel %d\n", pri->pvts[chanpos]->channel);
+ res = set_actual_gain(pri->pvts[chanpos]->subs[SUB_REAL].zfd, 0, pri->pvts[chanpos]->rxgain, pri->pvts[chanpos]->txgain, law);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to set gains on channel %d\n", pri->pvts[chanpos]->channel);
+ if (e->ring.complete || !(pri->overlapdial & ZAP_OVERLAPDIAL_INCOMING)) {
+ /* Just announce proceeding */
+ pri->pvts[chanpos]->proceeding = 1;
+ pri_proceeding(pri->pri, e->ring.call, PVT_TO_CHANNEL(pri->pvts[chanpos]), 0);
+ } else {
+ if (pri->switchtype != PRI_SWITCH_GR303_TMC)
+ pri_need_more_info(pri->pri, e->ring.call, PVT_TO_CHANNEL(pri->pvts[chanpos]), 1);
+ else
+ pri_answer(pri->pri, e->ring.call, PVT_TO_CHANNEL(pri->pvts[chanpos]), 1);
+ }
+ /* Get the use_callingpres state */
+ pri->pvts[chanpos]->callingpres = e->ring.callingpres;
+
+ /* Start PBX */
+ if ((pri->overlapdial & ZAP_OVERLAPDIAL_INCOMING) && ast_matchmore_extension(NULL, pri->pvts[chanpos]->context, pri->pvts[chanpos]->exten, 1, pri->pvts[chanpos]->cid_num)) {
+ /* Release the PRI lock while we create the channel */
+ ast_mutex_unlock(&pri->lock);
+ if (crv) {
+ /* Set bearer and such */
+ pri_assign_bearer(crv, pri, pri->pvts[chanpos]);
+ c = zt_new(crv, AST_STATE_RESERVED, 0, SUB_REAL, law, e->ring.ctype);
+ pri->pvts[chanpos]->owner = &inuse;
+ ast_debug(1, "Started up crv %d:%d on bearer channel %d\n", pri->trunkgroup, crv->channel, crv->bearer->channel);
+ } else {
+ c = zt_new(pri->pvts[chanpos], AST_STATE_RESERVED, 0, SUB_REAL, law, e->ring.ctype);
+ }
+
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+
+ if (!ast_strlen_zero(e->ring.callingsubaddr)) {
+ pbx_builtin_setvar_helper(c, "CALLINGSUBADDR", e->ring.callingsubaddr);
+ }
+ if (e->ring.ani2 >= 0) {
+ snprintf(ani2str, 5, "%.2d", e->ring.ani2);
+ pbx_builtin_setvar_helper(c, "ANI2", ani2str);
+ pri->pvts[chanpos]->cid_ani2 = e->ring.ani2;
+ }
+
+#ifdef SUPPORT_USERUSER
+ if (!ast_strlen_zero(e->ring.useruserinfo)) {
+ pbx_builtin_setvar_helper(c, "USERUSERINFO", e->ring.useruserinfo);
+ }
+#endif
+
+ snprintf(calledtonstr, sizeof(calledtonstr)-1, "%d", e->ring.calledplan);
+ pbx_builtin_setvar_helper(c, "CALLEDTON", calledtonstr);
+ if (e->ring.redirectingreason >= 0)
+ pbx_builtin_setvar_helper(c, "PRIREDIRECTREASON", redirectingreason2str(e->ring.redirectingreason));
+
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ ast_mutex_lock(&pri->lock);
+ if (c && !ast_pthread_create_detached(&threadid, NULL, ss_thread, c)) {
+ ast_verb(3, "Accepting overlap call from '%s' to '%s' on channel %d/%d, span %d\n",
+ plancallingnum, S_OR(pri->pvts[chanpos]->exten, "<unspecified>"),
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span);
+ } else {
+ ast_log(LOG_WARNING, "Unable to start PBX on channel %d/%d, span %d\n",
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span);
+ if (c)
+ ast_hangup(c);
+ else {
+ pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_SWITCH_CONGESTION);
+ pri->pvts[chanpos]->call = NULL;
+ }
+ }
+ } else {
+ ast_mutex_unlock(&pri->lock);
+ /* Release PRI lock while we create the channel */
+ c = zt_new(pri->pvts[chanpos], AST_STATE_RING, 1, SUB_REAL, law, e->ring.ctype);
+ if (c) {
+ char calledtonstr[10];
+
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+
+ if (e->ring.ani2 >= 0) {
+ snprintf(ani2str, 5, "%d", e->ring.ani2);
+ pbx_builtin_setvar_helper(c, "ANI2", ani2str);
+ pri->pvts[chanpos]->cid_ani2 = e->ring.ani2;
+ }
+
+#ifdef SUPPORT_USERUSER
+ if (!ast_strlen_zero(e->ring.useruserinfo)) {
+ pbx_builtin_setvar_helper(c, "USERUSERINFO", e->ring.useruserinfo);
+ }
+#endif
+
+ if (e->ring.redirectingreason >= 0)
+ pbx_builtin_setvar_helper(c, "PRIREDIRECTREASON", redirectingreason2str(e->ring.redirectingreason));
+
+ snprintf(calledtonstr, sizeof(calledtonstr)-1, "%d", e->ring.calledplan);
+ pbx_builtin_setvar_helper(c, "CALLEDTON", calledtonstr);
+
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ ast_mutex_lock(&pri->lock);
+
+ ast_verb(3, "Accepting call from '%s' to '%s' on channel %d/%d, span %d\n",
+ plancallingnum, pri->pvts[chanpos]->exten,
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span);
+
+ zt_enable_ec(pri->pvts[chanpos]);
+ } else {
+
+ ast_mutex_lock(&pri->lock);
+
+ ast_log(LOG_WARNING, "Unable to start PBX on channel %d/%d, span %d\n",
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span);
+ pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_SWITCH_CONGESTION);
+ pri->pvts[chanpos]->call = NULL;
+ }
+ }
+ } else {
+ ast_verb(3, "Extension '%s' in context '%s' from '%s' does not exist. Rejecting call on channel %d/%d, span %d\n",
+ pri->pvts[chanpos]->exten, pri->pvts[chanpos]->context, pri->pvts[chanpos]->cid_num, pri->pvts[chanpos]->logicalspan,
+ pri->pvts[chanpos]->prioffset, pri->span);
+ pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_UNALLOCATED);
+ pri->pvts[chanpos]->call = NULL;
+ pri->pvts[chanpos]->exten[0] = '\0';
+ }
+ if (crv)
+ ast_mutex_unlock(&crv->lock);
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ } else {
+ if (e->ring.flexible)
+ pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION);
+ else
+ pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_REQUESTED_CHAN_UNAVAIL);
+ }
+ break;
+ case PRI_EVENT_RINGING:
+ chanpos = pri_find_principle(pri, e->ringing.channel);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Ringing requested on unconfigured channel %d/%d span %d\n",
+ PRI_SPAN(e->ringing.channel), PRI_CHANNEL(e->ringing.channel), pri->span);
+ } else {
+ chanpos = pri_fixup_principle(pri, chanpos, e->ringing.call);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Ringing requested on channel %d/%d not in use on span %d\n",
+ PRI_SPAN(e->ringing.channel), PRI_CHANNEL(e->ringing.channel), pri->span);
+ } else {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ if (ast_strlen_zero(pri->pvts[chanpos]->dop.dialstr)) {
+ zt_enable_ec(pri->pvts[chanpos]);
+ pri->pvts[chanpos]->subs[SUB_REAL].needringing = 1;
+ pri->pvts[chanpos]->alerting = 1;
+ } else
+ ast_debug(1, "Deferring ringing notification because of extra digits to dial...\n");
+
+#ifdef PRI_PROGRESS_MASK
+ if (e->ringing.progressmask & PRI_PROG_INBAND_AVAILABLE) {
+#else
+ if (e->ringing.progress == 8) {
+#endif
+ /* Now we can do call progress detection */
+ if (pri->pvts[chanpos]->dsp && pri->pvts[chanpos]->dsp_features) {
+ /* RINGING detection isn't required because we got ALERTING signal */
+ ast_dsp_set_features(pri->pvts[chanpos]->dsp, pri->pvts[chanpos]->dsp_features & ~DSP_PROGRESS_RINGING);
+ pri->pvts[chanpos]->dsp_features = 0;
+ }
+ }
+
+#ifdef SUPPORT_USERUSER
+ if (!ast_strlen_zero(e->ringing.useruserinfo)) {
+ struct ast_channel *owner = pri->pvts[chanpos]->owner;
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ pbx_builtin_setvar_helper(owner, "USERUSERINFO", e->ringing.useruserinfo);
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ }
+#endif
+
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ }
+ break;
+ case PRI_EVENT_PROGRESS:
+ /* Get chan value if e->e is not PRI_EVNT_RINGING */
+ chanpos = pri_find_principle(pri, e->proceeding.channel);
+ if (chanpos > -1) {
+#ifdef PRI_PROGRESS_MASK
+ if ((!pri->pvts[chanpos]->progress) || (e->proceeding.progressmask & PRI_PROG_INBAND_AVAILABLE)) {
+#else
+ if ((!pri->pvts[chanpos]->progress) || (e->proceeding.progress == 8)) {
+#endif
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_PROGRESS, };
+
+ if (e->proceeding.cause > -1) {
+ ast_verb(3, "PROGRESS with cause code %d received\n", e->proceeding.cause);
+
+ /* Work around broken, out of spec USER_BUSY cause in a progress message */
+ if (e->proceeding.cause == AST_CAUSE_USER_BUSY) {
+ if (pri->pvts[chanpos]->owner) {
+ ast_verb(3, "PROGRESS with 'user busy' received, signalling AST_CONTROL_BUSY instead of AST_CONTROL_PROGRESS\n");
+
+ pri->pvts[chanpos]->owner->hangupcause = e->proceeding.cause;
+ f.subclass = AST_CONTROL_BUSY;
+ }
+ }
+ }
+
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ ast_debug(1, "Queuing frame from PRI_EVENT_PROGRESS on channel %d/%d span %d\n",
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset,pri->span);
+ zap_queue_frame(pri->pvts[chanpos], &f, pri);
+#ifdef PRI_PROGRESS_MASK
+ if (e->proceeding.progressmask & PRI_PROG_INBAND_AVAILABLE) {
+#else
+ if (e->proceeding.progress == 8) {
+#endif
+ /* Now we can do call progress detection */
+ if (pri->pvts[chanpos]->dsp && pri->pvts[chanpos]->dsp_features) {
+ ast_dsp_set_features(pri->pvts[chanpos]->dsp, pri->pvts[chanpos]->dsp_features);
+ pri->pvts[chanpos]->dsp_features = 0;
+ }
+ }
+ pri->pvts[chanpos]->progress = 1;
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ }
+ break;
+ case PRI_EVENT_PROCEEDING:
+ chanpos = pri_find_principle(pri, e->proceeding.channel);
+ if (chanpos > -1) {
+ if (!pri->pvts[chanpos]->proceeding) {
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_PROCEEDING, };
+
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ ast_debug(1, "Queuing frame from PRI_EVENT_PROCEEDING on channel %d/%d span %d\n",
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset,pri->span);
+ zap_queue_frame(pri->pvts[chanpos], &f, pri);
+#ifdef PRI_PROGRESS_MASK
+ if (e->proceeding.progressmask & PRI_PROG_INBAND_AVAILABLE) {
+#else
+ if (e->proceeding.progress == 8) {
+#endif
+ /* Now we can do call progress detection */
+ if (pri->pvts[chanpos]->dsp && pri->pvts[chanpos]->dsp_features) {
+ ast_dsp_set_features(pri->pvts[chanpos]->dsp, pri->pvts[chanpos]->dsp_features);
+ pri->pvts[chanpos]->dsp_features = 0;
+ }
+ /* Bring voice path up */
+ f.subclass = AST_CONTROL_PROGRESS;
+ zap_queue_frame(pri->pvts[chanpos], &f, pri);
+ }
+ pri->pvts[chanpos]->proceeding = 1;
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ }
+ break;
+ case PRI_EVENT_FACNAME:
+ chanpos = pri_find_principle(pri, e->facname.channel);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Facility Name requested on unconfigured channel %d/%d span %d\n",
+ PRI_SPAN(e->facname.channel), PRI_CHANNEL(e->facname.channel), pri->span);
+ } else {
+ chanpos = pri_fixup_principle(pri, chanpos, e->facname.call);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Facility Name requested on channel %d/%d not in use on span %d\n",
+ PRI_SPAN(e->facname.channel), PRI_CHANNEL(e->facname.channel), pri->span);
+ } else {
+ /* Re-use *69 field for PRI */
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ ast_copy_string(pri->pvts[chanpos]->lastcid_num, e->facname.callingnum, sizeof(pri->pvts[chanpos]->lastcid_num));
+ ast_copy_string(pri->pvts[chanpos]->lastcid_name, e->facname.callingname, sizeof(pri->pvts[chanpos]->lastcid_name));
+ pri->pvts[chanpos]->subs[SUB_REAL].needcallerid =1;
+ zt_enable_ec(pri->pvts[chanpos]);
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ }
+ break;
+ case PRI_EVENT_ANSWER:
+ chanpos = pri_find_principle(pri, e->answer.channel);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Answer on unconfigured channel %d/%d span %d\n",
+ PRI_SPAN(e->answer.channel), PRI_CHANNEL(e->answer.channel), pri->span);
+ } else {
+ chanpos = pri_fixup_principle(pri, chanpos, e->answer.call);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Answer requested on channel %d/%d not in use on span %d\n",
+ PRI_SPAN(e->answer.channel), PRI_CHANNEL(e->answer.channel), pri->span);
+ } else {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ /* Now we can do call progress detection */
+
+ /* We changed this so it turns on the DSP no matter what... progress or no progress.
+ * By this time, we need DTMF detection and other features that were previously disabled
+ * -- Matt F */
+ if (pri->pvts[chanpos]->dsp && pri->pvts[chanpos]->dsp_features) {
+ ast_dsp_set_features(pri->pvts[chanpos]->dsp, pri->pvts[chanpos]->dsp_features);
+ pri->pvts[chanpos]->dsp_features = 0;
+ }
+ if (pri->pvts[chanpos]->realcall && (pri->pvts[chanpos]->realcall->sig == SIG_FXSKS)) {
+ ast_debug(1, "Starting up GR-303 trunk now that we got CONNECT...\n");
+ x = ZT_START;
+ res = ioctl(pri->pvts[chanpos]->subs[SUB_REAL].zfd, ZT_HOOK, &x);
+ if (res < 0) {
+ if (errno != EINPROGRESS) {
+ ast_log(LOG_WARNING, "Unable to start channel: %s\n", strerror(errno));
+ }
+ }
+ } else if (!ast_strlen_zero(pri->pvts[chanpos]->dop.dialstr)) {
+ pri->pvts[chanpos]->dialing = 1;
+ /* Send any "w" waited stuff */
+ res = ioctl(pri->pvts[chanpos]->subs[SUB_REAL].zfd, ZT_DIAL, &pri->pvts[chanpos]->dop);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to initiate dialing on trunk channel %d\n", pri->pvts[chanpos]->channel);
+ pri->pvts[chanpos]->dop.dialstr[0] = '\0';
+ } else
+ ast_debug(1, "Sent deferred digit string: %s\n", pri->pvts[chanpos]->dop.dialstr);
+
+ pri->pvts[chanpos]->dop.dialstr[0] = '\0';
+ } else if (pri->pvts[chanpos]->confirmanswer) {
+ ast_debug(1, "Waiting on answer confirmation on channel %d!\n", pri->pvts[chanpos]->channel);
+ } else {
+ pri->pvts[chanpos]->subs[SUB_REAL].needanswer =1;
+ /* Enable echo cancellation if it's not on already */
+ zt_enable_ec(pri->pvts[chanpos]);
+ }
+
+#ifdef SUPPORT_USERUSER
+ if (!ast_strlen_zero(e->answer.useruserinfo)) {
+ struct ast_channel *owner = pri->pvts[chanpos]->owner;
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ pbx_builtin_setvar_helper(owner, "USERUSERINFO", e->answer.useruserinfo);
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ }
+#endif
+
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ }
+ break;
+ case PRI_EVENT_HANGUP:
+ chanpos = pri_find_principle(pri, e->hangup.channel);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Hangup requested on unconfigured channel %d/%d span %d\n",
+ PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span);
+ } else {
+ chanpos = pri_fixup_principle(pri, chanpos, e->hangup.call);
+ if (chanpos > -1) {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ if (!pri->pvts[chanpos]->alreadyhungup) {
+ /* we're calling here zt_hangup so once we get there we need to clear p->call after calling pri_hangup */
+ pri->pvts[chanpos]->alreadyhungup = 1;
+ if (pri->pvts[chanpos]->realcall)
+ pri_hangup_all(pri->pvts[chanpos]->realcall, pri);
+ else if (pri->pvts[chanpos]->owner) {
+ /* Queue a BUSY instead of a hangup if our cause is appropriate */
+ pri->pvts[chanpos]->owner->hangupcause = e->hangup.cause;
+ if (pri->pvts[chanpos]->owner->_state == AST_STATE_UP)
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ else {
+ switch (e->hangup.cause) {
+ case PRI_CAUSE_USER_BUSY:
+ pri->pvts[chanpos]->subs[SUB_REAL].needbusy =1;
+ break;
+ case PRI_CAUSE_CALL_REJECTED:
+ case PRI_CAUSE_NETWORK_OUT_OF_ORDER:
+ case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION:
+ case PRI_CAUSE_SWITCH_CONGESTION:
+ case PRI_CAUSE_DESTINATION_OUT_OF_ORDER:
+ case PRI_CAUSE_NORMAL_TEMPORARY_FAILURE:
+ pri->pvts[chanpos]->subs[SUB_REAL].needcongestion =1;
+ break;
+ default:
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ }
+ }
+ }
+ ast_verb(3, "Channel %d/%d, span %d got hangup, cause %d\n",
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span, e->hangup.cause);
+ } else {
+ pri_hangup(pri->pri, pri->pvts[chanpos]->call, e->hangup.cause);
+ pri->pvts[chanpos]->call = NULL;
+ }
+ if (e->hangup.cause == PRI_CAUSE_REQUESTED_CHAN_UNAVAIL) {
+ ast_verb(3, "Forcing restart of channel %d/%d on span %d since channel reported in use\n",
+ PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span);
+ pri_reset(pri->pri, PVT_TO_CHANNEL(pri->pvts[chanpos]));
+ pri->pvts[chanpos]->resetting = 1;
+ }
+ if (e->hangup.aoc_units > -1)
+ ast_verb(3, "Channel %d/%d, span %d received AOC-E charging %d unit%s\n",
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span, (int)e->hangup.aoc_units, (e->hangup.aoc_units == 1) ? "" : "s");
+
+#ifdef SUPPORT_USERUSER
+ if (pri->pvts[chanpos]->owner && !ast_strlen_zero(e->hangup.useruserinfo)) {
+ struct ast_channel *owner = pri->pvts[chanpos]->owner;
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ pbx_builtin_setvar_helper(owner, "USERUSERINFO", e->hangup.useruserinfo);
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ }
+#endif
+
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ } else {
+ ast_log(LOG_WARNING, "Hangup on bad channel %d/%d on span %d\n",
+ PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span);
+ }
+ }
+ break;
+#ifndef PRI_EVENT_HANGUP_REQ
+#error please update libpri
+#endif
+ case PRI_EVENT_HANGUP_REQ:
+ chanpos = pri_find_principle(pri, e->hangup.channel);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Hangup REQ requested on unconfigured channel %d/%d span %d\n",
+ PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span);
+ } else {
+ chanpos = pri_fixup_principle(pri, chanpos, e->hangup.call);
+ if (chanpos > -1) {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ if (pri->pvts[chanpos]->realcall)
+ pri_hangup_all(pri->pvts[chanpos]->realcall, pri);
+ else if (pri->pvts[chanpos]->owner) {
+ pri->pvts[chanpos]->owner->hangupcause = e->hangup.cause;
+ if (pri->pvts[chanpos]->owner->_state == AST_STATE_UP)
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ else {
+ switch (e->hangup.cause) {
+ case PRI_CAUSE_USER_BUSY:
+ pri->pvts[chanpos]->subs[SUB_REAL].needbusy =1;
+ break;
+ case PRI_CAUSE_CALL_REJECTED:
+ case PRI_CAUSE_NETWORK_OUT_OF_ORDER:
+ case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION:
+ case PRI_CAUSE_SWITCH_CONGESTION:
+ case PRI_CAUSE_DESTINATION_OUT_OF_ORDER:
+ case PRI_CAUSE_NORMAL_TEMPORARY_FAILURE:
+ pri->pvts[chanpos]->subs[SUB_REAL].needcongestion =1;
+ break;
+ default:
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ }
+ }
+ ast_verb(3, "Channel %d/%d, span %d got hangup request, cause %d\n", PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span, e->hangup.cause);
+ if (e->hangup.aoc_units > -1)
+ ast_verb(3, "Channel %d/%d, span %d received AOC-E charging %d unit%s\n",
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span, (int)e->hangup.aoc_units, (e->hangup.aoc_units == 1) ? "" : "s");
+ } else {
+ pri_hangup(pri->pri, pri->pvts[chanpos]->call, e->hangup.cause);
+ pri->pvts[chanpos]->call = NULL;
+ }
+ if (e->hangup.cause == PRI_CAUSE_REQUESTED_CHAN_UNAVAIL) {
+ ast_verb(3, "Forcing restart of channel %d/%d span %d since channel reported in use\n",
+ PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span);
+ pri_reset(pri->pri, PVT_TO_CHANNEL(pri->pvts[chanpos]));
+ pri->pvts[chanpos]->resetting = 1;
+ }
+
+#ifdef SUPPORT_USERUSER
+ if (!ast_strlen_zero(e->hangup.useruserinfo)) {
+ struct ast_channel *owner = pri->pvts[chanpos]->owner;
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ pbx_builtin_setvar_helper(owner, "USERUSERINFO", e->hangup.useruserinfo);
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ }
+#endif
+
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ } else {
+ ast_log(LOG_WARNING, "Hangup REQ on bad channel %d/%d on span %d\n", PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span);
+ }
+ }
+ break;
+ case PRI_EVENT_HANGUP_ACK:
+ chanpos = pri_find_principle(pri, e->hangup.channel);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Hangup ACK requested on unconfigured channel number %d/%d span %d\n",
+ PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span);
+ } else {
+ chanpos = pri_fixup_principle(pri, chanpos, e->hangup.call);
+ if (chanpos > -1) {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ pri->pvts[chanpos]->call = NULL;
+ pri->pvts[chanpos]->resetting = 0;
+ if (pri->pvts[chanpos]->owner) {
+ ast_verb(3, "Channel %d/%d, span %d got hangup ACK\n", PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span);
+ }
+
+#ifdef SUPPORT_USERUSER
+ if (!ast_strlen_zero(e->hangup.useruserinfo)) {
+ struct ast_channel *owner = pri->pvts[chanpos]->owner;
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ pbx_builtin_setvar_helper(owner, "USERUSERINFO", e->hangup.useruserinfo);
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ }
+#endif
+
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ }
+ break;
+ case PRI_EVENT_CONFIG_ERR:
+ ast_log(LOG_WARNING, "PRI Error on span %d: %s\n", pri->trunkgroup, e->err.err);
+ break;
+ case PRI_EVENT_RESTART_ACK:
+ chanpos = pri_find_principle(pri, e->restartack.channel);
+ if (chanpos < 0) {
+ /* Sometime switches (e.g. I421 / British Telecom) don't give us the
+ channel number, so we have to figure it out... This must be why
+ everybody resets exactly a channel at a time. */
+ for (x = 0; x < pri->numchans; x++) {
+ if (pri->pvts[x] && pri->pvts[x]->resetting) {
+ chanpos = x;
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ ast_debug(1, "Assuming restart ack is really for channel %d/%d span %d\n", pri->pvts[chanpos]->logicalspan,
+ pri->pvts[chanpos]->prioffset, pri->span);
+ if (pri->pvts[chanpos]->realcall)
+ pri_hangup_all(pri->pvts[chanpos]->realcall, pri);
+ else if (pri->pvts[chanpos]->owner) {
+ ast_log(LOG_WARNING, "Got restart ack on channel %d/%d with owner on span %d\n", pri->pvts[chanpos]->logicalspan,
+ pri->pvts[chanpos]->prioffset, pri->span);
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ }
+ pri->pvts[chanpos]->resetting = 0;
+ ast_verb(3, "B-channel %d/%d successfully restarted on span %d\n", pri->pvts[chanpos]->logicalspan,
+ pri->pvts[chanpos]->prioffset, pri->span);
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ if (pri->resetting)
+ pri_check_restart(pri);
+ break;
+ }
+ }
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Restart ACK requested on strange channel %d/%d span %d\n",
+ PRI_SPAN(e->restartack.channel), PRI_CHANNEL(e->restartack.channel), pri->span);
+ }
+ } else {
+ if (pri->pvts[chanpos]) {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ if (pri->pvts[chanpos]->realcall)
+ pri_hangup_all(pri->pvts[chanpos]->realcall, pri);
+ else if (pri->pvts[chanpos]->owner) {
+ ast_log(LOG_WARNING, "Got restart ack on channel %d/%d span %d with owner\n",
+ PRI_SPAN(e->restartack.channel), PRI_CHANNEL(e->restartack.channel), pri->span);
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ }
+ pri->pvts[chanpos]->resetting = 0;
+ pri->pvts[chanpos]->inservice = 1;
+ ast_verb(3, "B-channel %d/%d successfully restarted on span %d\n", pri->pvts[chanpos]->logicalspan,
+ pri->pvts[chanpos]->prioffset, pri->span);
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ if (pri->resetting)
+ pri_check_restart(pri);
+ }
+ }
+ break;
+ case PRI_EVENT_SETUP_ACK:
+ chanpos = pri_find_principle(pri, e->setup_ack.channel);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Received SETUP_ACKNOWLEDGE on unconfigured channel %d/%d span %d\n",
+ PRI_SPAN(e->setup_ack.channel), PRI_CHANNEL(e->setup_ack.channel), pri->span);
+ } else {
+ chanpos = pri_fixup_principle(pri, chanpos, e->setup_ack.call);
+ if (chanpos > -1) {
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ pri->pvts[chanpos]->setup_ack = 1;
+ /* Send any queued digits */
+ for (x = 0;x < strlen(pri->pvts[chanpos]->dialdest); x++) {
+ ast_debug(1, "Sending pending digit '%c'\n", pri->pvts[chanpos]->dialdest[x]);
+ pri_information(pri->pri, pri->pvts[chanpos]->call,
+ pri->pvts[chanpos]->dialdest[x]);
+ }
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ } else
+ ast_log(LOG_WARNING, "Unable to move channel %d!\n", e->setup_ack.channel);
+ }
+ break;
+ case PRI_EVENT_NOTIFY:
+ chanpos = pri_find_principle(pri, e->notify.channel);
+ if (chanpos < 0) {
+ ast_log(LOG_WARNING, "Received NOTIFY on unconfigured channel %d/%d span %d\n",
+ PRI_SPAN(e->notify.channel), PRI_CHANNEL(e->notify.channel), pri->span);
+ } else {
+ struct ast_frame f = { AST_FRAME_CONTROL, };
+ ast_mutex_lock(&pri->pvts[chanpos]->lock);
+ switch (e->notify.info) {
+ case PRI_NOTIFY_REMOTE_HOLD:
+ f.subclass = AST_CONTROL_HOLD;
+ zap_queue_frame(pri->pvts[chanpos], &f, pri);
+ break;
+ case PRI_NOTIFY_REMOTE_RETRIEVAL:
+ f.subclass = AST_CONTROL_UNHOLD;
+ zap_queue_frame(pri->pvts[chanpos], &f, pri);
+ break;
+ }
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ break;
+ default:
+ ast_debug(1, "Event: %d\n", e->e);
+ }
+ }
+ ast_mutex_unlock(&pri->lock);
+ }
+ /* Never reached */
+ return NULL;
+}
+
+static int start_pri(struct zt_pri *pri)
+{
+ int res, x;
+ ZT_PARAMS p;
+ ZT_BUFFERINFO bi;
+ struct zt_spaninfo si;
+ int i;
+
+ for (i = 0; i < NUM_DCHANS; i++) {
+ if (!pri->dchannels[i])
+ break;
+ pri->fds[i] = open("/dev/zap/channel", O_RDWR);
+ x = pri->dchannels[i];
+ if ((pri->fds[i] < 0) || (ioctl(pri->fds[i],ZT_SPECIFY,&x) == -1)) {
+ ast_log(LOG_ERROR, "Unable to open D-channel %d (%s)\n", x, strerror(errno));
+ return -1;
+ }
+ res = ioctl(pri->fds[i], ZT_GET_PARAMS, &p);
+ if (res) {
+ zt_close(pri->fds[i]);
+ pri->fds[i] = -1;
+ ast_log(LOG_ERROR, "Unable to get parameters for D-channel %d (%s)\n", x, strerror(errno));
+ return -1;
+ }
+ if ((p.sigtype != ZT_SIG_HDLCFCS) && (p.sigtype != ZT_SIG_HARDHDLC)) {
+ zt_close(pri->fds[i]);
+ pri->fds[i] = -1;
+ ast_log(LOG_ERROR, "D-channel %d is not in HDLC/FCS mode. See /etc/zaptel.conf\n", x);
+ return -1;
+ }
+ memset(&si, 0, sizeof(si));
+ res = ioctl(pri->fds[i], ZT_SPANSTAT, &si);
+ if (res) {
+ zt_close(pri->fds[i]);
+ pri->fds[i] = -1;
+ ast_log(LOG_ERROR, "Unable to get span state for D-channel %d (%s)\n", x, strerror(errno));
+ }
+ if (!si.alarms)
+ pri->dchanavail[i] |= DCHAN_NOTINALARM;
+ else
+ pri->dchanavail[i] &= ~DCHAN_NOTINALARM;
+ bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
+ bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
+ bi.numbufs = 32;
+ bi.bufsize = 1024;
+ if (ioctl(pri->fds[i], ZT_SET_BUFINFO, &bi)) {
+ ast_log(LOG_ERROR, "Unable to set appropriate buffering on channel %d\n", x);
+ zt_close(pri->fds[i]);
+ pri->fds[i] = -1;
+ return -1;
+ }
+ switch (pri->sig) {
+ case SIG_BRI:
+ pri->dchans[i] = pri_new_bri(pri->fds[i], 1, pri->nodetype, pri->switchtype);
+ break;
+ case SIG_BRI_PTMP:
+ pri->dchans[i] = pri_new_bri(pri->fds[i], 0, pri->nodetype, pri->switchtype);
+ break;
+ default:
+ pri->dchans[i] = pri_new(pri->fds[i], pri->nodetype, pri->switchtype);
+ }
+ /* Force overlap dial if we're doing GR-303! */
+ if (pri->switchtype == PRI_SWITCH_GR303_TMC)
+ pri->overlapdial |= ZAP_OVERLAPDIAL_BOTH;
+ pri_set_overlapdial(pri->dchans[i],(pri->overlapdial & ZAP_OVERLAPDIAL_OUTGOING)?1:0);
+ /* Enslave to master if appropriate */
+ if (i)
+ pri_enslave(pri->dchans[0], pri->dchans[i]);
+ if (!pri->dchans[i]) {
+ zt_close(pri->fds[i]);
+ pri->fds[i] = -1;
+ ast_log(LOG_ERROR, "Unable to create PRI structure\n");
+ return -1;
+ }
+ pri_set_debug(pri->dchans[i], DEFAULT_PRI_DEBUG);
+ pri_set_nsf(pri->dchans[i], pri->nsf);
+#ifdef PRI_GETSET_TIMERS
+ for (x = 0; x < PRI_MAX_TIMERS; x++) {
+ if (pritimers[x] != 0)
+ pri_set_timer(pri->dchans[i], x, pritimers[x]);
+ }
+#endif
+ }
+ /* Assume primary is the one we use */
+ pri->pri = pri->dchans[0];
+ pri->resetpos = -1;
+ if (ast_pthread_create_background(&pri->master, NULL, pri_dchannel, pri)) {
+ for (i = 0; i < NUM_DCHANS; i++) {
+ if (!pri->dchannels[i])
+ break;
+ zt_close(pri->fds[i]);
+ pri->fds[i] = -1;
+ }
+ ast_log(LOG_ERROR, "Unable to spawn D-channel: %s\n", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static char *complete_span_helper(const char *line, const char *word, int pos, int state, int rpos)
+{
+ int which, span;
+ char *ret = NULL;
+
+ if (pos != rpos)
+ return ret;
+
+ for (which = span = 0; span < NUM_SPANS; span++) {
+ if (pris[span].pri && ++which > state) {
+ asprintf(&ret, "%d", span + 1); /* user indexes start from 1 */
+ break;
+ }
+ }
+ return ret;
+}
+
+static char *complete_span_4(const char *line, const char *word, int pos, int state)
+{
+ return complete_span_helper(line,word,pos,state,3);
+}
+
+static char *complete_span_5(const char *line, const char *word, int pos, int state)
+{
+ return complete_span_helper(line,word,pos,state,4);
+}
+
+static char *handle_pri_unset_debug_file(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "pri unset debug file";
+ e->usage = "Usage: pri unset debug file\n"
+ " Stop sending debug output to the previously \n"
+ " specified file\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ /* Assume it is unset */
+ ast_mutex_lock(&pridebugfdlock);
+ close(pridebugfd);
+ pridebugfd = -1;
+ ast_cli(a->fd, "PRI debug output to file disabled\n");
+ ast_mutex_unlock(&pridebugfdlock);
+ return CLI_SUCCESS;
+}
+
+static char *handle_pri_set_debug_file(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int myfd;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "pri set debug file";
+ e->usage = "Usage: pri set debug file [output-file]\n"
+ " Sends PRI debug output to the specified output file\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc < 5)
+ return CLI_SHOWUSAGE;
+
+ if (ast_strlen_zero(a->argv[4]))
+ return CLI_SHOWUSAGE;
+
+ myfd = open(a->argv[4], O_CREAT|O_WRONLY, AST_FILE_MODE);
+ if (myfd < 0) {
+ ast_cli(a->fd, "Unable to open '%s' for writing\n", a->argv[4]);
+ return CLI_SUCCESS;
+ }
+
+ ast_mutex_lock(&pridebugfdlock);
+
+ if (pridebugfd >= 0)
+ close(pridebugfd);
+
+ pridebugfd = myfd;
+ ast_copy_string(pridebugfilename,a->argv[4],sizeof(pridebugfilename));
+ ast_mutex_unlock(&pridebugfdlock);
+ ast_cli(a->fd, "PRI debug output will be sent to '%s'\n", a->argv[4]);
+ return CLI_SUCCESS;
+}
+
+static char *handle_pri_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int span;
+ int x;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "pri debug span";
+ e->usage =
+ "Usage: pri debug span <span>\n"
+ " Enables debugging on a given PRI span\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_span_4(a->line, a->word, a->pos, a->n);
+ }
+ if (a->argc < 4) {
+ return CLI_SHOWUSAGE;
+ }
+ span = atoi(a->argv[3]);
+ if ((span < 1) || (span > NUM_SPANS)) {
+ ast_cli(a->fd, "Invalid span %s. Should be a number %d to %d\n", a->argv[3], 1, NUM_SPANS);
+ return CLI_SUCCESS;
+ }
+ if (!pris[span-1].pri) {
+ ast_cli(a->fd, "No PRI running on span %d\n", span);
+ return CLI_SUCCESS;
+ }
+ for (x = 0; x < NUM_DCHANS; x++) {
+ if (pris[span-1].dchans[x])
+ pri_set_debug(pris[span-1].dchans[x], PRI_DEBUG_APDU |
+ PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE |
+ PRI_DEBUG_Q921_STATE);
+ }
+ ast_cli(a->fd, "Enabled debugging on span %d\n", span);
+ return CLI_SUCCESS;
+}
+
+
+
+static char *handle_pri_no_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int span;
+ int x;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "pri no debug span";
+ e->usage =
+ "Usage: pri no debug span <span>\n"
+ " Disables debugging on a given PRI span\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_span_5(a->line, a->word, a->pos, a->n);
+ }
+ if (a->argc < 5)
+ return CLI_SHOWUSAGE;
+
+ span = atoi(a->argv[4]);
+ if ((span < 1) || (span > NUM_SPANS)) {
+ ast_cli(a->fd, "Invalid span %s. Should be a number %d to %d\n", a->argv[4], 1, NUM_SPANS);
+ return CLI_SUCCESS;
+ }
+ if (!pris[span-1].pri) {
+ ast_cli(a->fd, "No PRI running on span %d\n", span);
+ return CLI_SUCCESS;
+ }
+ for (x = 0; x < NUM_DCHANS; x++) {
+ if (pris[span-1].dchans[x])
+ pri_set_debug(pris[span-1].dchans[x], 0);
+ }
+ ast_cli(a->fd, "Disabled debugging on span %d\n", span);
+ return CLI_SUCCESS;
+}
+
+static char *handle_pri_really_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int span;
+ int x;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "pri intensive debug span";
+ e->usage =
+ "Usage: pri intensive debug span <span>\n"
+ " Enables debugging down to the Q.921 level\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_span_5(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc < 5)
+ return CLI_SHOWUSAGE;
+ span = atoi(a->argv[4]);
+ if ((span < 1) || (span > NUM_SPANS)) {
+ ast_cli(a->fd, "Invalid span %s. Should be a number %d to %d\n", a->argv[4], 1, NUM_SPANS);
+ return CLI_SUCCESS;
+ }
+ if (!pris[span-1].pri) {
+ ast_cli(a->fd, "No PRI running on span %d\n", span);
+ return CLI_SUCCESS;
+ }
+ for (x = 0; x < NUM_DCHANS; x++) {
+ if (pris[span-1].dchans[x])
+ pri_set_debug(pris[span-1].dchans[x], PRI_DEBUG_APDU |
+ PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE |
+ PRI_DEBUG_Q921_RAW | PRI_DEBUG_Q921_DUMP | PRI_DEBUG_Q921_STATE);
+ }
+ ast_cli(a->fd, "Enabled EXTENSIVE debugging on span %d\n", span);
+ return CLI_SUCCESS;
+}
+
+static void build_status(char *s, size_t len, int status, int active)
+{
+ if (!s || len < 1) {
+ return;
+ }
+ s[0] = '\0';
+ if (status & DCHAN_PROVISIONED)
+ strncat(s, "Provisioned, ", len - strlen(s) - 1);
+ if (!(status & DCHAN_NOTINALARM))
+ strncat(s, "In Alarm, ", len - strlen(s) - 1);
+ if (status & DCHAN_UP)
+ strncat(s, "Up", len - strlen(s) - 1);
+ else
+ strncat(s, "Down", len - strlen(s) - 1);
+ if (active)
+ strncat(s, ", Active", len - strlen(s) - 1);
+ else
+ strncat(s, ", Standby", len - strlen(s) - 1);
+ s[len - 1] = '\0';
+}
+
+static char *handle_pri_show_spans(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int span;
+ int x;
+ char status[256];
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "pri show spans";
+ e->usage =
+ "Usage: pri show spans\n"
+ " Displays PRI Information\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3)
+ return CLI_SHOWUSAGE;
+
+ for (span = 0; span < NUM_SPANS; span++) {
+ if (pris[span].pri) {
+ for (x = 0; x < NUM_DCHANS; x++) {
+ if (pris[span].dchannels[x]) {
+ build_status(status, sizeof(status), pris[span].dchanavail[x], pris[span].dchans[x] == pris[span].pri);
+ ast_cli(a->fd, "PRI span %d/%d: %s\n", span + 1, x, status);
+ }
+ }
+ }
+ }
+ return CLI_SUCCESS;
+}
+
+static char *handle_pri_show_span(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int span;
+ int x;
+ char status[256];
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "pri show span";
+ e->usage =
+ "Usage: pri show span <span>\n"
+ " Displays PRI Information on a given PRI span\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_span_4(a->line, a->word, a->pos, a->n);
+ }
+
+ if (a->argc < 4)
+ return CLI_SHOWUSAGE;
+ span = atoi(a->argv[3]);
+ if ((span < 1) || (span > NUM_SPANS)) {
+ ast_cli(a->fd, "Invalid span '%s'. Should be a number from %d to %d\n", a->argv[3], 1, NUM_SPANS);
+ return CLI_SUCCESS;
+ }
+ if (!pris[span-1].pri) {
+ ast_cli(a->fd, "No PRI running on span %d\n", span);
+ return CLI_SUCCESS;
+ }
+ for (x = 0; x < NUM_DCHANS; x++) {
+ if (pris[span-1].dchannels[x]) {
+#ifdef PRI_DUMP_INFO_STR
+ char *info_str = NULL;
+#endif
+ ast_cli(a->fd, "%s D-channel: %d\n", pri_order(x), pris[span-1].dchannels[x]);
+ build_status(status, sizeof(status), pris[span-1].dchanavail[x], pris[span-1].dchans[x] == pris[span-1].pri);
+ ast_cli(a->fd, "Status: %s\n", status);
+#ifdef PRI_DUMP_INFO_STR
+ info_str = pri_dump_info_str(pris[span-1].pri);
+ if (info_str) {
+ ast_cli(a->fd, "%s", info_str);
+ ast_free(info_str);
+ }
+#else
+ pri_dump_info(pris[span-1].pri);
+#endif
+ ast_cli(a->fd, "Overlap Recv: %s\n\n", (pris[span-1].overlapdial & ZAP_OVERLAPDIAL_INCOMING)?"Yes":"No");
+ }
+ }
+ return CLI_SUCCESS;
+}
+
+static char *handle_pri_show_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int x;
+ int span;
+ int count=0;
+ int debug=0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "pri show debug";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ for (span = 0; span < NUM_SPANS; span++) {
+ if (pris[span].pri) {
+ for (x = 0; x < NUM_DCHANS; x++) {
+ debug = 0;
+ if (pris[span].dchans[x]) {
+ debug = pri_get_debug(pris[span].dchans[x]);
+ ast_cli(a->fd, "Span %d: Debug: %s\tIntense: %s\n", span+1, (debug&PRI_DEBUG_Q931_STATE)? "Yes" : "No" ,(debug&PRI_DEBUG_Q921_RAW)? "Yes" : "No" );
+ count++;
+ }
+ }
+ }
+
+ }
+ ast_mutex_lock(&pridebugfdlock);
+ if (pridebugfd >= 0)
+ ast_cli(a->fd, "Logging PRI debug to file %s\n", pridebugfilename);
+ ast_mutex_unlock(&pridebugfdlock);
+
+ if (!count)
+ ast_cli(a->fd, "No debug set or no PRI running\n");
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry zap_pri_cli[] = {
+ AST_CLI_DEFINE(handle_pri_debug, "Enables PRI debugging on a span"),
+ AST_CLI_DEFINE(handle_pri_no_debug, "Disables PRI debugging on a span"),
+ AST_CLI_DEFINE(handle_pri_really_debug, "Enables REALLY INTENSE PRI debugging"),
+ AST_CLI_DEFINE(handle_pri_show_spans, "Displays PRI Information"),
+ AST_CLI_DEFINE(handle_pri_show_span, "Displays PRI Information"),
+ AST_CLI_DEFINE(handle_pri_show_debug, "Displays current PRI debug settings"),
+ AST_CLI_DEFINE(handle_pri_set_debug_file, "Sends PRI debug output to the specified file"),
+ AST_CLI_DEFINE(handle_pri_unset_debug_file, "Ends PRI debug output to file"),
+};
+
+#endif /* HAVE_PRI */
+
+static char *zap_destroy_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int channel;
+ int ret;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "zap destroy channel";
+ e->usage =
+ "Usage: zap destroy channel <chan num>\n"
+ " DON'T USE THIS UNLESS YOU KNOW WHAT YOU ARE DOING. Immediately removes a given channel, whether it is in use or not\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+
+ channel = atoi(a->argv[3]);
+ ret = zap_destroy_channel_bynum(channel);
+ return ( RESULT_SUCCESS == ret ) ? CLI_SUCCESS : CLI_FAILURE;
+}
+
+static int setup_zap(int reload);
+static int zap_restart(void)
+{
+ ast_verb(1, "Destroying channels and reloading Zaptel configuration.\n");
+ while (iflist) {
+ ast_debug(1, "Destroying Zaptel channel no. %d\n", iflist->channel);
+ /* Also updates iflist: */
+ destroy_channel(NULL, iflist, 1);
+ }
+ ast_debug(1, "Channels destroyed. Now re-reading config.\n");
+ if (setup_zap(2) != 0) {
+ ast_log(LOG_WARNING, "Reload channels from zap config failed!\n");
+ return 1;
+ }
+ return 0;
+}
+
+static char *zap_restart_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "zap restart";
+ e->usage =
+ "Usage: zap restart\n"
+ " Restarts the Zaptel channels: destroys them all and then\n"
+ " re-reads them from zapata.conf.\n"
+ " Note that this will STOP any running CALL on Zaptel channels.\n"
+ "";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc != 2)
+ return CLI_SHOWUSAGE;
+
+ if (zap_restart() != 0)
+ return CLI_FAILURE;
+ return CLI_SUCCESS;
+}
+
+static int action_zaprestart(struct mansession *s, const struct message *m)
+{
+ if (zap_restart() != 0) {
+ astman_send_error(s, m, "Failed rereading Zaptel configuration");
+ return 1;
+ }
+ astman_send_ack(s, m, "ZapRestart: Success");
+ return 0;
+}
+
+static char *zap_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT "%7s %-10.10s %-15.15s %-10.10s %-20.20s %-10.10s %-10.10s\n"
+#define FORMAT2 "%7s %-10.10s %-15.15s %-10.10s %-20.20s %-10.10s %-10.10s\n"
+ unsigned int targetnum = 0;
+ int filtertype = 0;
+ struct zt_pvt *tmp = NULL;
+ char tmps[20] = "";
+ char statestr[20] = "";
+ char blockstr[20] = "";
+ ast_mutex_t *lock;
+ struct zt_pvt *start;
+#ifdef HAVE_PRI
+ int trunkgroup;
+ struct zt_pri *pri = NULL;
+ int x;
+#endif
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "zap show channels [trunkgroup|group|context]";
+ e->usage =
+ "Usage: zap show channels [ trunkgroup <trunkgroup> | group <group> | context <context> ]\n"
+ " Shows a list of available channels with optional filtering\n"
+ " <group> must be a number between 0 and 63\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ lock = &iflock;
+ start = iflist;
+
+ /* syntax: zap show channels [ group <group> | context <context> | trunkgroup <trunkgroup> ] */
+
+ if (!((a->argc == 3) || (a->argc == 5)))
+ return CLI_SHOWUSAGE;
+
+ if (a->argc == 5) {
+#ifdef HAVE_PRI
+ if (!strcasecmp(a->argv[3], "trunkgroup")) {
+ /* this option requires no special handling, so leave filtertype to zero */
+ if ((trunkgroup = atoi(a->argv[4])) < 1)
+ return CLI_SHOWUSAGE;
+ for (x = 0; x < NUM_SPANS; x++) {
+ if (pris[x].trunkgroup == trunkgroup) {
+ pri = pris + x;
+ break;
+ }
+ }
+ if (pri) {
+ start = pri->crvs;
+ lock = &pri->lock;
+ } else {
+ ast_cli(a->fd, "No such trunk group %d\n", trunkgroup);
+ return CLI_FAILURE;
+ }
+ } else
+#endif
+ if (!strcasecmp(a->argv[3], "group")) {
+ targetnum = atoi(a->argv[4]);
+ if ((targetnum < 0) || (targetnum > 63))
+ return CLI_SHOWUSAGE;
+ targetnum = 1 << targetnum;
+ filtertype = 1;
+ } else if (!strcasecmp(a->argv[3], "context")) {
+ filtertype = 2;
+ }
+ }
+
+ ast_mutex_lock(lock);
+#ifdef HAVE_PRI
+ ast_cli(a->fd, FORMAT2, pri ? "CRV" : "Chan", "Extension", "Context", "Language", "MOH Interpret", "Blocked", "State");
+#else
+ ast_cli(a->fd, FORMAT2, "Chan", "Extension", "Context", "Language", "MOH Interpret", "Blocked", "State");
+#endif
+
+ tmp = start;
+ while (tmp) {
+ if (filtertype) {
+ switch(filtertype) {
+ case 1: /* zap show channels group <group> */
+ if (tmp->group != targetnum) {
+ tmp = tmp->next;
+ continue;
+ }
+ break;
+ case 2: /* zap show channels context <context> */
+ if (strcasecmp(tmp->context, a->argv[4])) {
+ tmp = tmp->next;
+ continue;
+ }
+ break;
+ default:
+ ;
+ }
+ }
+ if (tmp->channel > 0) {
+ snprintf(tmps, sizeof(tmps), "%d", tmp->channel);
+ } else
+ ast_copy_string(tmps, "pseudo", sizeof(tmps));
+
+ if (tmp->locallyblocked)
+ blockstr[0] = 'L';
+ else
+ blockstr[0] = ' ';
+
+ if (tmp->remotelyblocked)
+ blockstr[1] = 'R';
+ else
+ blockstr[1] = ' ';
+
+ blockstr[2] = '\0';
+
+ snprintf(statestr, sizeof(statestr), "%s", "In Service");
+
+ ast_cli(a->fd, FORMAT, tmps, tmp->exten, tmp->context, tmp->language, tmp->mohinterpret, blockstr, statestr);
+ tmp = tmp->next;
+ }
+ ast_mutex_unlock(lock);
+ return CLI_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static char *zap_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int channel;
+ struct zt_pvt *tmp = NULL;
+ ZT_CONFINFO ci;
+ ZT_PARAMS ps;
+ int x;
+ ast_mutex_t *lock;
+ struct zt_pvt *start;
+#ifdef HAVE_PRI
+ char *c;
+ int trunkgroup;
+ struct zt_pri *pri=NULL;
+#endif
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "zap show channel";
+ e->usage =
+ "Usage: zap show channel <chan num>\n"
+ " Detailed information about a given channel\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ lock = &iflock;
+ start = iflist;
+
+ if (a->argc != 4)
+ return CLI_SHOWUSAGE;
+#ifdef HAVE_PRI
+ if ((c = strchr(a->argv[3], ':'))) {
+ if (sscanf(a->argv[3], "%d:%d", &trunkgroup, &channel) != 2)
+ return CLI_SHOWUSAGE;
+ if ((trunkgroup < 1) || (channel < 1))
+ return CLI_SHOWUSAGE;
+ for (x = 0; x < NUM_SPANS; x++) {
+ if (pris[x].trunkgroup == trunkgroup) {
+ pri = pris + x;
+ break;
+ }
+ }
+ if (pri) {
+ start = pri->crvs;
+ lock = &pri->lock;
+ } else {
+ ast_cli(a->fd, "No such trunk group %d\n", trunkgroup);
+ return CLI_FAILURE;
+ }
+ } else
+#endif
+ channel = atoi(a->argv[3]);
+
+ ast_mutex_lock(lock);
+ tmp = start;
+ while (tmp) {
+ if (tmp->channel == channel) {
+#ifdef HAVE_PRI
+ if (pri)
+ ast_cli(a->fd, "Trunk/CRV: %d/%d\n", trunkgroup, tmp->channel);
+ else
+#endif
+ ast_cli(a->fd, "Channel: %d\n", tmp->channel);
+ ast_cli(a->fd, "File Descriptor: %d\n", tmp->subs[SUB_REAL].zfd);
+ ast_cli(a->fd, "Span: %d\n", tmp->span);
+ ast_cli(a->fd, "Extension: %s\n", tmp->exten);
+ ast_cli(a->fd, "Dialing: %s\n", tmp->dialing ? "yes" : "no");
+ ast_cli(a->fd, "Context: %s\n", tmp->context);
+ ast_cli(a->fd, "Caller ID: %s\n", tmp->cid_num);
+ ast_cli(a->fd, "Calling TON: %d\n", tmp->cid_ton);
+ ast_cli(a->fd, "Caller ID name: %s\n", tmp->cid_name);
+ ast_cli(a->fd, "Mailbox: %s\n", S_OR(tmp->mailbox, "none"));
+ if (tmp->vars) {
+ struct ast_variable *v;
+ ast_cli(a->fd, "Variables:\n");
+ for (v = tmp->vars ; v ; v = v->next)
+ ast_cli(a->fd, " %s = %s\n", v->name, v->value);
+ }
+ ast_cli(a->fd, "Destroy: %d\n", tmp->destroy);
+ ast_cli(a->fd, "InAlarm: %d\n", tmp->inalarm);
+ ast_cli(a->fd, "Signalling Type: %s\n", sig2str(tmp->sig));
+ ast_cli(a->fd, "Radio: %d\n", tmp->radio);
+ ast_cli(a->fd, "Owner: %s\n", tmp->owner ? tmp->owner->name : "<None>");
+ ast_cli(a->fd, "Real: %s%s%s\n", tmp->subs[SUB_REAL].owner ? tmp->subs[SUB_REAL].owner->name : "<None>", tmp->subs[SUB_REAL].inthreeway ? " (Confed)" : "", tmp->subs[SUB_REAL].linear ? " (Linear)" : "");
+ ast_cli(a->fd, "Callwait: %s%s%s\n", tmp->subs[SUB_CALLWAIT].owner ? tmp->subs[SUB_CALLWAIT].owner->name : "<None>", tmp->subs[SUB_CALLWAIT].inthreeway ? " (Confed)" : "", tmp->subs[SUB_CALLWAIT].linear ? " (Linear)" : "");
+ ast_cli(a->fd, "Threeway: %s%s%s\n", tmp->subs[SUB_THREEWAY].owner ? tmp->subs[SUB_THREEWAY].owner->name : "<None>", tmp->subs[SUB_THREEWAY].inthreeway ? " (Confed)" : "", tmp->subs[SUB_THREEWAY].linear ? " (Linear)" : "");
+ ast_cli(a->fd, "Confno: %d\n", tmp->confno);
+ ast_cli(a->fd, "Propagated Conference: %d\n", tmp->propconfno);
+ ast_cli(a->fd, "Real in conference: %d\n", tmp->inconference);
+ ast_cli(a->fd, "DSP: %s\n", tmp->dsp ? "yes" : "no");
+ ast_cli(a->fd, "Busy Detection: %s\n", tmp->busydetect ? "yes" : "no");
+ if (tmp->busydetect) {
+#if defined(BUSYDETECT_TONEONLY)
+ ast_cli(a->fd, " Busy Detector Helper: BUSYDETECT_TONEONLY\n");
+#elif defined(BUSYDETECT_COMPARE_TONE_AND_SILENCE)
+ ast_cli(a->fd, " Busy Detector Helper: BUSYDETECT_COMPARE_TONE_AND_SILENCE\n");
+#endif
+#ifdef BUSYDETECT_DEBUG
+ ast_cli(a->fd, " Busy Detector Debug: Enabled\n");
+#endif
+ ast_cli(a->fd, " Busy Count: %d\n", tmp->busycount);
+ ast_cli(a->fd, " Busy Pattern: %d,%d\n", tmp->busy_tonelength, tmp->busy_quietlength);
+ }
+ ast_cli(a->fd, "TDD: %s\n", tmp->tdd ? "yes" : "no");
+ ast_cli(a->fd, "Relax DTMF: %s\n", tmp->dtmfrelax ? "yes" : "no");
+ ast_cli(a->fd, "Dialing/CallwaitCAS: %d/%d\n", tmp->dialing, tmp->callwaitcas);
+ ast_cli(a->fd, "Default law: %s\n", tmp->law == ZT_LAW_MULAW ? "ulaw" : tmp->law == ZT_LAW_ALAW ? "alaw" : "unknown");
+ ast_cli(a->fd, "Fax Handled: %s\n", tmp->faxhandled ? "yes" : "no");
+ ast_cli(a->fd, "Pulse phone: %s\n", tmp->pulsedial ? "yes" : "no");
+ ast_cli(a->fd, "DND: %s\n", tmp->dnd ? "yes" : "no");
+ ast_cli(a->fd, "Echo Cancellation:\n");
+#if defined(HAVE_ZAPTEL_ECHOCANPARAMS)
+ if (tmp->echocancel.head.tap_length) {
+ ast_cli(a->fd, "\t%d taps\n", tmp->echocancel.head.tap_length);
+ for (x = 0; x < tmp->echocancel.head.param_count; x++) {
+ ast_cli(a->fd, "\t\t%s: %ud\n", tmp->echocancel.params[x].name, tmp->echocancel.params[x].value);
+ }
+ ast_cli(a->fd, "\t%scurrently %s\n", tmp->echocanbridged ? "" : "(unless TDM bridged) ", tmp->echocanon ? "ON" : "OFF");
+ } else {
+ ast_cli(a->fd, "\tnone\n");
+ }
+#else
+ if (tmp->echocancel) {
+ ast_cli(a->fd, "\t%d taps\n", tmp->echocancel);
+ ast_cli(a->fd, "\t%scurrently %s\n", tmp->echocanbridged ? "" : "(unless TDM bridged) ", tmp->echocanon ? "ON" : "OFF");
+ }
+ else
+ ast_cli(a->fd, "\tnone\n");
+#endif
+ if (tmp->master)
+ ast_cli(a->fd, "Master Channel: %d\n", tmp->master->channel);
+ for (x = 0; x < MAX_SLAVES; x++) {
+ if (tmp->slaves[x])
+ ast_cli(a->fd, "Slave Channel: %d\n", tmp->slaves[x]->channel);
+ }
+#ifdef HAVE_SS7
+ if (tmp->ss7) {
+ ast_cli(a->fd, "CIC: %d\n", tmp->cic);
+ }
+#endif
+#ifdef HAVE_PRI
+ if (tmp->pri) {
+ ast_cli(a->fd, "PRI Flags: ");
+ if (tmp->resetting)
+ ast_cli(a->fd, "Resetting ");
+ if (tmp->call)
+ ast_cli(a->fd, "Call ");
+ if (tmp->bearer)
+ ast_cli(a->fd, "Bearer ");
+ ast_cli(a->fd, "\n");
+ if (tmp->logicalspan)
+ ast_cli(a->fd, "PRI Logical Span: %d\n", tmp->logicalspan);
+ else
+ ast_cli(a->fd, "PRI Logical Span: Implicit\n");
+ }
+
+#endif
+ memset(&ci, 0, sizeof(ci));
+ ps.channo = tmp->channel;
+ if (tmp->subs[SUB_REAL].zfd > -1) {
+ if (!ioctl(tmp->subs[SUB_REAL].zfd, ZT_GETCONF, &ci)) {
+ ast_cli(a->fd, "Actual Confinfo: Num/%d, Mode/0x%04x\n", ci.confno, ci.confmode);
+ }
+#ifdef ZT_GETCONFMUTE
+ if (!ioctl(tmp->subs[SUB_REAL].zfd, ZT_GETCONFMUTE, &x)) {
+ ast_cli(a->fd, "Actual Confmute: %s\n", x ? "Yes" : "No");
+ }
+#endif
+ if (ioctl(tmp->subs[SUB_REAL].zfd, ZT_GET_PARAMS, &ps) < 0) {
+ ast_log(LOG_WARNING, "Failed to get parameters on channel %d\n", tmp->channel);
+ } else {
+ ast_cli(a->fd, "Hookstate (FXS only): %s\n", ps.rxisoffhook ? "Offhook" : "Onhook");
+ }
+ }
+ ast_mutex_unlock(lock);
+ return CLI_SUCCESS;
+ }
+ tmp = tmp->next;
+ }
+
+ ast_cli(a->fd, "Unable to find given channel %d\n", channel);
+ ast_mutex_unlock(lock);
+ return CLI_FAILURE;
+}
+
+static char *handle_zap_show_cadences(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int i, j;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "zap show cadences";
+ e->usage =
+ "Usage: zap show cadences\n"
+ " Shows all cadences currently defined\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ for (i = 0; i < num_cadence; i++) {
+ char output[1024];
+ char tmp[16], tmp2[64];
+ snprintf(tmp, sizeof(tmp), "r%d: ", i + 1);
+ term_color(output, tmp, COLOR_GREEN, COLOR_BLACK, sizeof(output));
+
+ for (j = 0; j < 16; j++) {
+ if (cadences[i].ringcadence[j] == 0)
+ break;
+ snprintf(tmp, sizeof(tmp), "%d", cadences[i].ringcadence[j]);
+ if (cidrings[i] * 2 - 1 == j)
+ term_color(tmp2, tmp, COLOR_MAGENTA, COLOR_BLACK, sizeof(tmp2) - 1);
+ else
+ term_color(tmp2, tmp, COLOR_GREEN, COLOR_BLACK, sizeof(tmp2) - 1);
+ if (j != 0)
+ strncat(output, ",", sizeof(output) - strlen(output) - 1);
+ strncat(output, tmp2, sizeof(output) - strlen(output) - 1);
+ }
+ ast_cli(a->fd,"%s\n",output);
+ }
+ return CLI_SUCCESS;
+}
+
+/* Based on irqmiss.c */
+static char *zap_show_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#ifdef ZT_SPANINFO_HAS_LINECONFIG
+ #define FORMAT "%-40.40s %-7.7s %-6d %-6d %-6d %-3.3s %-4.4s %-8.8s %s\n"
+ #define FORMAT2 "%-40.40s %-7.7s %-6.6s %-6.6s %-6.6s %-3.3s %-4.4s %-8.8s %s\n"
+#else
+ #define FORMAT "%-40.40s %-10.10s %-10d %-10d %-10d\n"
+ #define FORMAT2 "%-40.40s %-10.10s %-10.10s %-10.10s %-10.10s\n"
+#endif
+ int span;
+ int res;
+ char alarms[50];
+
+ int ctl;
+ ZT_SPANINFO s;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "zap show status";
+ e->usage =
+ "Usage: zap show status\n"
+ " Shows a list of Zaptel cards with status\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ ctl = open("/dev/zap/ctl", O_RDWR);
+ if (ctl < 0) {
+ ast_cli(a->fd, "No Zaptel interface found. Unable to open /dev/zap/ctl: %s\n", strerror(errno));
+ return CLI_FAILURE;
+ }
+ ast_cli(a->fd, FORMAT2, "Description", "Alarms", "IRQ", "bpviol", "CRC4"
+#ifdef ZT_SPANINFO_HAS_LINECONFIG
+ , "Framing", "Coding", "Options", "LBO"
+#endif
+ );
+
+ for (span = 1; span < ZT_MAX_SPANS; ++span) {
+ s.spanno = span;
+ res = ioctl(ctl, ZT_SPANSTAT, &s);
+ if (res) {
+ continue;
+ }
+ alarms[0] = '\0';
+ if (s.alarms > 0) {
+ if (s.alarms & ZT_ALARM_BLUE)
+ strcat(alarms, "BLU/");
+ if (s.alarms & ZT_ALARM_YELLOW)
+ strcat(alarms, "YEL/");
+ if (s.alarms & ZT_ALARM_RED)
+ strcat(alarms, "RED/");
+ if (s.alarms & ZT_ALARM_LOOPBACK)
+ strcat(alarms, "LB/");
+ if (s.alarms & ZT_ALARM_RECOVER)
+ strcat(alarms, "REC/");
+ if (s.alarms & ZT_ALARM_NOTOPEN)
+ strcat(alarms, "NOP/");
+ if (!strlen(alarms))
+ strcat(alarms, "UUU/");
+ if (strlen(alarms)) {
+ /* Strip trailing / */
+ alarms[strlen(alarms) - 1] = '\0';
+ }
+ } else {
+ if (s.numchans)
+ strcpy(alarms, "OK");
+ else
+ strcpy(alarms, "UNCONFIGURED");
+ }
+
+ ast_cli(a->fd, FORMAT, s.desc, alarms, s.irqmisses, s.bpvcount, s.crc4count
+#ifdef ZT_SPANINFO_HAS_LINECONFIG
+ , s.lineconfig & ZT_CONFIG_D4 ? "D4" :
+ s.lineconfig & ZT_CONFIG_ESF ? "ESF" :
+ s.lineconfig & ZT_CONFIG_CCS ? "CCS" :
+ "CAS"
+ , s.lineconfig & ZT_CONFIG_B8ZS ? "B8ZS" :
+ s.lineconfig & ZT_CONFIG_HDB3 ? "HDB3" :
+ s.lineconfig & ZT_CONFIG_AMI ? "AMI" :
+ "Unk"
+ , s.lineconfig & ZT_CONFIG_CRC4 ?
+ s.lineconfig & ZT_CONFIG_NOTOPEN ? "CRC4/YEL" : "CRC4" : "YEL"
+ , lbostr[s.lbo]
+#endif
+ );
+ }
+ close(ctl);
+
+ return CLI_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static char *zap_show_version(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int pseudo_fd = -1;
+ struct zt_versioninfo vi;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "zap show version";
+ e->usage =
+ "Usage: zap show version\n"
+ " Shows the Zaptel version in use\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if ((pseudo_fd = open("/dev/zap/ctl", O_RDONLY)) < 0) {
+ ast_cli(a->fd, "Failed to open control file to get version.\n");
+ return CLI_SUCCESS;
+ }
+
+ strcpy(vi.version, "Unknown");
+ strcpy(vi.echo_canceller, "Unknown");
+
+ if (ioctl(pseudo_fd, ZT_GETVERSION, &vi))
+ ast_cli(a->fd, "Failed to get version from control file.\n");
+ else
+ ast_cli(a->fd, "Zaptel Version: %s Echo Canceller: %s\n", vi.version, vi.echo_canceller);
+
+ close(pseudo_fd);
+
+ return CLI_SUCCESS;
+}
+
+#if defined(HAVE_ZAPTEL_HWGAIN)
+static char *zap_set_hwgain(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int channel;
+ int gain;
+ int tx;
+ struct zt_hwgain hwgain;
+ struct zt_pvt *tmp = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "zap set hwgain";
+ e->usage =
+ "Usage: zap set hwgain <rx|tx> <chan#> <gain>\n"
+ " Sets the hardware gain on a a given channel, overriding the\n"
+ " value provided at module loadtime, whether the channel is in\n"
+ " use or not. Changes take effect immediately.\n"
+ " <rx|tx> which direction do you want to change (relative to our module)\n"
+ " <chan num> is the channel number relative to the device\n"
+ " <gain> is the gain in dB (e.g. -3.5 for -3.5dB)\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 6)
+ return CLI_SHOWUSAGE;
+
+ if (!strcasecmp("rx", a->argv[3]))
+ tx = 0; /* rx */
+ else if (!strcasecmp("tx", a->argv[3]))
+ tx = 1; /* tx */
+ else
+ return CLI_SHOWUSAGE;
+
+ channel = atoi(a->argv[4]);
+ gain = atof(a->argv[5])*10.0;
+
+ ast_mutex_lock(&iflock);
+
+ for (tmp = iflist; tmp; tmp = tmp->next) {
+
+ if (tmp->channel != channel)
+ continue;
+
+ if (tmp->subs[SUB_REAL].zfd == -1)
+ break;
+
+ hwgain.newgain = gain;
+ hwgain.tx = tx;
+ if (ioctl(tmp->subs[SUB_REAL].zfd, ZT_SET_HWGAIN, &hwgain) < 0) {
+ ast_cli(a->fd, "Unable to set the hardware gain for channel %d\n", channel);
+ ast_mutex_unlock(&iflock);
+ return CLI_FAILURE;
+ }
+ ast_cli(a->fd, "hardware %s gain set to %d (%.1f dB) on channel %d\n",
+ tx ? "tx" : "rx", gain, (float)gain/10.0, channel);
+ break;
+ }
+
+ ast_mutex_unlock(&iflock);
+
+ if (tmp)
+ return CLI_SUCCESS;
+
+ ast_cli(a->fd, "Unable to find given channel %d\n", channel);
+ return CLI_FAILURE;
+
+}
+#endif
+
+static char *zap_set_swgain(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int channel;
+ float gain;
+ int tx;
+ int res;
+ ast_mutex_t *lock;
+ struct zt_pvt *tmp = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "zap set swgain";
+ e->usage =
+ "Usage: zap set swgain <rx|tx> <chan#> <gain>\n"
+ " Sets the software gain on a a given channel, overriding the\n"
+ " value provided at module loadtime, whether the channel is in\n"
+ " use or not. Changes take effect immediately.\n"
+ " <rx|tx> which direction do you want to change (relative to our module)\n"
+ " <chan num> is the channel number relative to the device\n"
+ " <gain> is the gain in dB (e.g. -3.5 for -3.5dB)\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ lock = &iflock;
+
+ if (a->argc != 6)
+ return CLI_SHOWUSAGE;
+
+ if (!strcasecmp("rx", a->argv[3]))
+ tx = 0; /* rx */
+ else if (!strcasecmp("tx", a->argv[3]))
+ tx = 1; /* tx */
+ else
+ return CLI_SHOWUSAGE;
+
+ channel = atoi(a->argv[4]);
+ gain = atof(a->argv[5]);
+
+ ast_mutex_lock(lock);
+ for (tmp = iflist; tmp; tmp = tmp->next) {
+
+ if (tmp->channel != channel)
+ continue;
+
+ if (tmp->subs[SUB_REAL].zfd == -1)
+ break;
+
+ if (tx)
+ res = set_actual_txgain(tmp->subs[SUB_REAL].zfd, channel, gain, tmp->law);
+ else
+ res = set_actual_rxgain(tmp->subs[SUB_REAL].zfd, channel, gain, tmp->law);
+
+ if (res) {
+ ast_cli(a->fd, "Unable to set the software gain for channel %d\n", channel);
+ ast_mutex_unlock(lock);
+ return CLI_FAILURE;
+ }
+
+ ast_cli(a->fd, "software %s gain set to %.1f on channel %d\n",
+ tx ? "tx" : "rx", gain, channel);
+ break;
+ }
+ ast_mutex_unlock(lock);
+
+ if (tmp)
+ return CLI_SUCCESS;
+
+ ast_cli(a->fd, "Unable to find given channel %d\n", channel);
+ return CLI_FAILURE;
+
+}
+
+static char *zap_set_dnd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int channel;
+ int on;
+ struct zt_pvt *zt_chan = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "zap set dnd";
+ e->usage =
+ "Usage: zap set dnd <chan#> <on|off>\n"
+ " Sets/resets DND (Do Not Disturb) mode on a channel.\n"
+ " Changes take effect immediately.\n"
+ " <chan num> is the channel number\n"
+ " <on|off> Enable or disable DND mode?\n"
+ ;
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 5)
+ return CLI_SHOWUSAGE;
+
+ if ((channel = atoi(a->argv[3])) <= 0) {
+ ast_cli(a->fd, "Expected channel number, got '%s'\n", a->argv[3]);
+ return CLI_SHOWUSAGE;
+ }
+
+ if (ast_true(a->argv[4]))
+ on = 1;
+ else if (ast_false(a->argv[4]))
+ on = 0;
+ else {
+ ast_cli(a->fd, "Expected 'on' or 'off', got '%s'\n", a->argv[4]);
+ return CLI_SHOWUSAGE;
+ }
+
+ ast_mutex_lock(&iflock);
+ for (zt_chan = iflist; zt_chan; zt_chan = zt_chan->next) {
+ if (zt_chan->channel != channel)
+ continue;
+
+ /* Found the channel. Actually set it */
+ zap_dnd(zt_chan, on);
+ break;
+ }
+ ast_mutex_unlock(&iflock);
+
+ if (!zt_chan) {
+ ast_cli(a->fd, "Unable to find given channel %d\n", channel);
+ return CLI_FAILURE;
+ }
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry zap_cli[] = {
+ AST_CLI_DEFINE(handle_zap_show_cadences, "List cadences"),
+ AST_CLI_DEFINE(zap_show_channels, "Show active zapata channels"),
+ AST_CLI_DEFINE(zap_show_channel, "Show information on a channel"),
+ AST_CLI_DEFINE(zap_destroy_channel, "Destroy a channel"),
+ AST_CLI_DEFINE(zap_restart_cmd, "Fully restart Zaptel channels"),
+ AST_CLI_DEFINE(zap_show_status, "Show all Zaptel cards status"),
+ AST_CLI_DEFINE(zap_show_version, "Show the Zaptel version in use"),
+#if defined(HAVE_ZAPTEL_HWGAIN)
+ AST_CLI_DEFINE(zap_set_hwgain, "Set hardware gain on a channel"),
+#endif
+ AST_CLI_DEFINE(zap_set_swgain, "Set software gain on a channel"),
+ AST_CLI_DEFINE(zap_set_dnd, "Set software gain on a channel"),
+};
+
+#define TRANSFER 0
+#define HANGUP 1
+
+static int zap_fake_event(struct zt_pvt *p, int mode)
+{
+ if (p) {
+ switch (mode) {
+ case TRANSFER:
+ p->fake_event = ZT_EVENT_WINKFLASH;
+ break;
+ case HANGUP:
+ p->fake_event = ZT_EVENT_ONHOOK;
+ break;
+ default:
+ ast_log(LOG_WARNING, "I don't know how to handle transfer event with this: %d on channel %s\n",mode, p->owner->name);
+ }
+ }
+ return 0;
+}
+static struct zt_pvt *find_channel(int channel)
+{
+ struct zt_pvt *p = iflist;
+ while (p) {
+ if (p->channel == channel) {
+ break;
+ }
+ p = p->next;
+ }
+ return p;
+}
+
+static int action_zapdndon(struct mansession *s, const struct message *m)
+{
+ struct zt_pvt *p = NULL;
+ const char *channel = astman_get_header(m, "ZapChannel");
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ p = find_channel(atoi(channel));
+ if (!p) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ p->dnd = 1;
+ astman_send_ack(s, m, "DND Enabled");
+ return 0;
+}
+
+static int action_zapdndoff(struct mansession *s, const struct message *m)
+{
+ struct zt_pvt *p = NULL;
+ const char *channel = astman_get_header(m, "ZapChannel");
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ p = find_channel(atoi(channel));
+ if (!p) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ p->dnd = 0;
+ astman_send_ack(s, m, "DND Disabled");
+ return 0;
+}
+
+static int action_transfer(struct mansession *s, const struct message *m)
+{
+ struct zt_pvt *p = NULL;
+ const char *channel = astman_get_header(m, "ZapChannel");
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ p = find_channel(atoi(channel));
+ if (!p) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ zap_fake_event(p,TRANSFER);
+ astman_send_ack(s, m, "ZapTransfer");
+ return 0;
+}
+
+static int action_transferhangup(struct mansession *s, const struct message *m)
+{
+ struct zt_pvt *p = NULL;
+ const char *channel = astman_get_header(m, "ZapChannel");
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ p = find_channel(atoi(channel));
+ if (!p) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ zap_fake_event(p,HANGUP);
+ astman_send_ack(s, m, "ZapHangup");
+ return 0;
+}
+
+static int action_zapdialoffhook(struct mansession *s, const struct message *m)
+{
+ struct zt_pvt *p = NULL;
+ const char *channel = astman_get_header(m, "ZapChannel");
+ const char *number = astman_get_header(m, "Number");
+ int i;
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ if (ast_strlen_zero(number)) {
+ astman_send_error(s, m, "No number specified");
+ return 0;
+ }
+ p = find_channel(atoi(channel));
+ if (!p) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ if (!p->owner) {
+ astman_send_error(s, m, "Channel does not have it's owner");
+ return 0;
+ }
+ for (i = 0; i < strlen(number); i++) {
+ struct ast_frame f = { AST_FRAME_DTMF, number[i] };
+ zap_queue_frame(p, &f, NULL);
+ }
+ astman_send_ack(s, m, "ZapDialOffhook");
+ return 0;
+}
+
+static int action_zapshowchannels(struct mansession *s, const struct message *m)
+{
+ struct zt_pvt *tmp = NULL;
+ const char *id = astman_get_header(m, "ActionID");
+ const char *zapchannel = astman_get_header(m, "ZapChannel");
+ char idText[256] = "";
+ int channels = 0;
+ int zapchanquery = -1;
+ if (!ast_strlen_zero(zapchannel)) {
+ zapchanquery = atoi(zapchannel);
+ }
+
+ astman_send_ack(s, m, "Zapata channel status will follow");
+ if (!ast_strlen_zero(id))
+ snprintf(idText, sizeof(idText) - 1, "ActionID: %s\r\n", id);
+
+ ast_mutex_lock(&iflock);
+
+ tmp = iflist;
+ while (tmp) {
+ if (tmp->channel > 0) {
+ int alarm = get_alarms(tmp);
+
+ /* If a specific channel is queried for, only deliver status for that channel */
+ if (zapchanquery > 0 && tmp->channel != zapchanquery)
+ continue;
+
+ channels++;
+ if (tmp->owner) {
+ /* Add data if we have a current call */
+ astman_append(s,
+ "Event: ZapShowChannels\r\n"
+ "ZapChannel: %d\r\n"
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "AccountCode: %s\r\n"
+ "Signalling: %s\r\n"
+ "SignallingCode: %d\r\n"
+ "Context: %s\r\n"
+ "DND: %s\r\n"
+ "Alarm: %s\r\n"
+ "%s"
+ "\r\n",
+ tmp->channel,
+ tmp->owner->name,
+ tmp->owner->uniqueid,
+ tmp->owner->accountcode,
+ sig2str(tmp->sig),
+ tmp->sig,
+ tmp->context,
+ tmp->dnd ? "Enabled" : "Disabled",
+ alarm2str(alarm), idText);
+ } else {
+ astman_append(s,
+ "Event: ZapShowChannels\r\n"
+ "ZapChannel: %d\r\n"
+ "Signalling: %s\r\n"
+ "SignallingCode: %d\r\n"
+ "Context: %s\r\n"
+ "DND: %s\r\n"
+ "Alarm: %s\r\n"
+ "%s"
+ "\r\n",
+ tmp->channel, sig2str(tmp->sig), tmp->sig,
+ tmp->context,
+ tmp->dnd ? "Enabled" : "Disabled",
+ alarm2str(alarm), idText);
+ }
+ }
+
+ tmp = tmp->next;
+ }
+
+ ast_mutex_unlock(&iflock);
+
+ astman_append(s,
+ "Event: ZapShowChannelsComplete\r\n"
+ "%s"
+ "Items: %d\r\n"
+ "\r\n",
+ idText,
+ channels);
+ return 0;
+}
+
+static int __unload_module(void)
+{
+ int x;
+ struct zt_pvt *p, *pl;
+#if defined(HAVE_PRI) || defined(HAVE_SS7)
+ int i;
+#endif
+
+#if defined(HAVE_PRI)
+ for (i = 0; i < NUM_SPANS; i++) {
+ if (pris[i].master != AST_PTHREADT_NULL)
+ pthread_cancel(pris[i].master);
+ }
+ ast_cli_unregister_multiple(zap_pri_cli, sizeof(zap_pri_cli) / sizeof(struct ast_cli_entry));
+ ast_unregister_application(zap_send_keypad_facility_app);
+#endif
+
+ ast_cli_unregister_multiple(zap_cli, sizeof(zap_cli) / sizeof(struct ast_cli_entry));
+ ast_manager_unregister( "ZapDialOffhook" );
+ ast_manager_unregister( "ZapHangup" );
+ ast_manager_unregister( "ZapTransfer" );
+ ast_manager_unregister( "ZapDNDoff" );
+ ast_manager_unregister( "ZapDNDon" );
+ ast_manager_unregister("ZapShowChannels");
+ ast_manager_unregister("ZapRestart");
+ ast_channel_unregister(&zap_tech);
+ ast_mutex_lock(&iflock);
+ /* Hangup all interfaces if they have an owner */
+ p = iflist;
+ while (p) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ p = p->next;
+ }
+ ast_mutex_unlock(&iflock);
+ ast_mutex_lock(&monlock);
+ if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) {
+ pthread_cancel(monitor_thread);
+ pthread_kill(monitor_thread, SIGURG);
+ pthread_join(monitor_thread, NULL);
+ }
+ monitor_thread = AST_PTHREADT_STOP;
+ ast_mutex_unlock(&monlock);
+
+ ast_mutex_lock(&iflock);
+ /* Destroy all the interfaces and free their memory */
+ p = iflist;
+ while (p) {
+ /* Free any callerid */
+ if (p->cidspill)
+ ast_free(p->cidspill);
+ /* Close the zapata thingy */
+ if (p->subs[SUB_REAL].zfd > -1)
+ zt_close(p->subs[SUB_REAL].zfd);
+ pl = p;
+ p = p->next;
+ x = pl->channel;
+ /* Free associated memory */
+ if (pl)
+ destroy_zt_pvt(&pl);
+ ast_verb(3, "Unregistered channel %d\n", x);
+ }
+ iflist = NULL;
+ ifcount = 0;
+ ast_mutex_unlock(&iflock);
+
+#if defined(HAVE_PRI)
+ for (i = 0; i < NUM_SPANS; i++) {
+ if (pris[i].master && (pris[i].master != AST_PTHREADT_NULL))
+ pthread_join(pris[i].master, NULL);
+ zt_close(pris[i].fds[i]);
+ }
+#endif /* HAVE_PRI */
+
+#if defined(HAVE_SS7)
+ for (i = 0; i < NUM_SPANS; i++) {
+ if (linksets[i].master && (linksets[i].master != AST_PTHREADT_NULL))
+ pthread_join(linksets[i].master, NULL);
+ zt_close(linksets[i].fds[i]);
+ }
+#endif /* HAVE_SS7 */
+
+ return 0;
+}
+
+#ifdef HAVE_SS7
+static int linkset_addsigchan(int sigchan)
+{
+ struct zt_ss7 *link;
+ int res;
+ int curfd;
+ ZT_PARAMS p;
+ ZT_BUFFERINFO bi;
+ struct zt_spaninfo si;
+
+
+ link = ss7_resolve_linkset(cur_linkset);
+ if (!link) {
+ ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1);
+ return -1;
+ }
+
+ if (cur_ss7type < 0) {
+ ast_log(LOG_ERROR, "Unspecified or invalid ss7type\n");
+ return -1;
+ }
+
+ if (!link->ss7)
+ link->ss7 = ss7_new(cur_ss7type);
+
+ if (!link->ss7) {
+ ast_log(LOG_ERROR, "Can't create new SS7!\n");
+ return -1;
+ }
+
+ link->type = cur_ss7type;
+
+ if (cur_pointcode < 0) {
+ ast_log(LOG_ERROR, "Unspecified pointcode!\n");
+ return -1;
+ } else
+ ss7_set_pc(link->ss7, cur_pointcode);
+
+ if (sigchan < 0) {
+ ast_log(LOG_ERROR, "Invalid sigchan!\n");
+ return -1;
+ } else {
+ if (link->numsigchans >= NUM_DCHANS) {
+ ast_log(LOG_ERROR, "Too many sigchans on linkset %d\n", cur_linkset);
+ return -1;
+ }
+ curfd = link->numsigchans;
+
+ link->fds[curfd] = open("/dev/zap/channel", O_RDWR, 0600);
+ if ((link->fds[curfd] < 0) || (ioctl(link->fds[curfd],ZT_SPECIFY,&sigchan) == -1)) {
+ ast_log(LOG_ERROR, "Unable to open SS7 sigchan %d (%s)\n", sigchan, strerror(errno));
+ return -1;
+ }
+ res = ioctl(link->fds[curfd], ZT_GET_PARAMS, &p);
+ if (res) {
+ zt_close(link->fds[curfd]);
+ link->fds[curfd] = -1;
+ ast_log(LOG_ERROR, "Unable to get parameters for sigchan %d (%s)\n", sigchan, strerror(errno));
+ return -1;
+ }
+ if ((p.sigtype != ZT_SIG_HDLCFCS) && (p.sigtype != ZT_SIG_HARDHDLC)) {
+ zt_close(link->fds[curfd]);
+ link->fds[curfd] = -1;
+ ast_log(LOG_ERROR, "sigchan %d is not in HDLC/FCS mode. See /etc/zaptel.conf\n", sigchan);
+ return -1;
+ }
+
+ bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
+ bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
+ bi.numbufs = 32;
+ bi.bufsize = 512;
+
+ if (ioctl(link->fds[curfd], ZT_SET_BUFINFO, &bi)) {
+ ast_log(LOG_ERROR, "Unable to set appropriate buffering on channel %d\n", sigchan);
+ zt_close(link->fds[curfd]);
+ link->fds[curfd] = -1;
+ return -1;
+ }
+
+ ss7_add_link(link->ss7, SS7_TRANSPORT_ZAP, link->fds[curfd]);
+ link->numsigchans++;
+
+ memset(&si, 0, sizeof(si));
+ res = ioctl(link->fds[curfd], ZT_SPANSTAT, &si);
+ if (res) {
+ zt_close(link->fds[curfd]);
+ link->fds[curfd] = -1;
+ ast_log(LOG_ERROR, "Unable to get span state for sigchan %d (%s)\n", sigchan, strerror(errno));
+ }
+
+ if (!si.alarms) {
+ link->linkstate[curfd] = LINKSTATE_DOWN;
+ ss7_link_noalarm(link->ss7, link->fds[curfd]);
+ } else {
+ link->linkstate[curfd] = LINKSTATE_DOWN | LINKSTATE_INALARM;
+ ss7_link_alarm(link->ss7, link->fds[curfd]);
+ }
+ }
+
+ if (cur_adjpointcode < 0) {
+ ast_log(LOG_ERROR, "Unspecified adjpointcode!\n");
+ return -1;
+ } else {
+ ss7_set_adjpc(link->ss7, link->fds[curfd], cur_adjpointcode);
+ }
+
+ if (cur_defaultdpc < 0) {
+ ast_log(LOG_ERROR, "Unspecified defaultdpc!\n");
+ return -1;
+ }
+
+ if (cur_networkindicator < 0) {
+ ast_log(LOG_ERROR, "Invalid networkindicator!\n");
+ return -1;
+ } else
+ ss7_set_network_ind(link->ss7, cur_networkindicator);
+
+ return 0;
+}
+
+static char *handle_ss7_no_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int span;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "ss7 no debug linkset";
+ e->usage =
+ "Usage: ss7 no debug linkset <span>\n"
+ " Disables debugging on a given SS7 linkset\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc < 5)
+ return CLI_SHOWUSAGE;
+ span = atoi(a->argv[4]);
+ if ((span < 1) || (span > NUM_SPANS)) {
+ ast_cli(a->fd, "Invalid linkset %s. Should be a number from %d to %d\n", a->argv[4], 1, NUM_SPANS);
+ return CLI_SUCCESS;
+ }
+ if (!linksets[span-1].ss7) {
+ ast_cli(a->fd, "No SS7 running on linkset %d\n", span);
+ return CLI_SUCCESS;
+ }
+ if (linksets[span-1].ss7)
+ ss7_set_debug(linksets[span-1].ss7, 0);
+
+ ast_cli(a->fd, "Disabled debugging on linkset %d\n", span);
+ return CLI_SUCCESS;
+}
+
+static char *handle_ss7_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int span;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "ss7 debug linkset";
+ e->usage =
+ "Usage: ss7 debug linkset <linkset>\n"
+ " Enables debugging on a given SS7 linkset\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc < 4)
+ return CLI_SHOWUSAGE;
+ span = atoi(a->argv[3]);
+ if ((span < 1) || (span > NUM_SPANS)) {
+ ast_cli(a->fd, "Invalid linkset %s. Should be a number from %d to %d\n", a->argv[3], 1, NUM_SPANS);
+ return CLI_SUCCESS;
+ }
+ if (!linksets[span-1].ss7) {
+ ast_cli(a->fd, "No SS7 running on linkset %d\n", span);
+ return CLI_SUCCESS;
+ }
+ if (linksets[span-1].ss7)
+ ss7_set_debug(linksets[span-1].ss7, SS7_DEBUG_MTP2 | SS7_DEBUG_MTP3 | SS7_DEBUG_ISUP);
+
+ ast_cli(a->fd, "Enabled debugging on linkset %d\n", span);
+ return CLI_SUCCESS;
+}
+
+static char *handle_ss7_block_cic(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int linkset, cic;
+ int blocked = -1, i;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "ss7 block cic";
+ e->usage =
+ "Usage: ss7 block cic <linkset> <CIC>\n"
+ " Sends a remote blocking request for the given CIC on the specified linkset\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+ if (a->argc == 5)
+ linkset = atoi(a->argv[3]);
+ else
+ return CLI_SHOWUSAGE;
+
+ if ((linkset < 1) || (linkset > NUM_SPANS)) {
+ ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[3], 1, NUM_SPANS);
+ return CLI_SUCCESS;
+ }
+
+ if (!linksets[linkset-1].ss7) {
+ ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset);
+ return CLI_SUCCESS;
+ }
+
+ cic = atoi(a->argv[4]);
+
+ if (cic < 1) {
+ ast_cli(a->fd, "Invalid CIC specified!\n");
+ return CLI_SUCCESS;
+ }
+
+ for (i = 0; i < linksets[linkset-1].numchans; i++) {
+ if (linksets[linkset-1].pvts[i]->cic == cic) {
+ blocked = linksets[linkset-1].pvts[i]->locallyblocked;
+ if (!blocked) {
+ ast_mutex_lock(&linksets[linkset-1].lock);
+ isup_blo(linksets[linkset-1].ss7, cic, linksets[linkset-1].pvts[i]->dpc);
+ ast_mutex_unlock(&linksets[linkset-1].lock);
+ }
+ }
+ }
+
+ if (blocked < 0) {
+ ast_cli(a->fd, "Invalid CIC specified!\n");
+ return CLI_SUCCESS;
+ }
+
+ if (!blocked)
+ ast_cli(a->fd, "Sent blocking request for linkset %d on CIC %d\n", linkset, cic);
+ else
+ ast_cli(a->fd, "CIC %d already locally blocked\n", cic);
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_ss7_unblock_cic(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int linkset, cic;
+ int i, blocked = -1;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "ss7 unblock cic";
+ e->usage =
+ "Usage: ss7 unblock cic <linkset> <CIC>\n"
+ " Sends a remote unblocking request for the given CIC on the specified linkset\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc == 5)
+ linkset = atoi(a->argv[3]);
+ else
+ return CLI_SHOWUSAGE;
+
+ if ((linkset < 1) || (linkset > NUM_SPANS)) {
+ ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[3], 1, NUM_SPANS);
+ return CLI_SUCCESS;
+ }
+
+ if (!linksets[linkset-1].ss7) {
+ ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset);
+ return CLI_SUCCESS;
+ }
+
+ cic = atoi(a->argv[4]);
+
+ if (cic < 1) {
+ ast_cli(a->fd, "Invalid CIC specified!\n");
+ return CLI_SUCCESS;
+ }
+
+ for (i = 0; i < linksets[linkset-1].numchans; i++) {
+ if (linksets[linkset-1].pvts[i]->cic == cic) {
+ blocked = linksets[linkset-1].pvts[i]->locallyblocked;
+ if (blocked) {
+ ast_mutex_lock(&linksets[linkset-1].lock);
+ isup_ubl(linksets[linkset-1].ss7, cic, linksets[linkset-1].pvts[i]->dpc);
+ ast_mutex_unlock(&linksets[linkset-1].lock);
+ }
+ }
+ }
+
+ if (blocked > 0)
+ ast_cli(a->fd, "Sent unblocking request for linkset %d on CIC %d\n", linkset, cic);
+ return CLI_SUCCESS;
+}
+
+static char *handle_ss7_show_linkset(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int linkset;
+ struct zt_ss7 *ss7;
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "ss7 show linkset";
+ e->usage =
+ "Usage: ss7 show linkset <span>\n"
+ " Shows the status of an SS7 linkset.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc < 4)
+ return CLI_SHOWUSAGE;
+ linkset = atoi(a->argv[3]);
+ if ((linkset < 1) || (linkset > NUM_SPANS)) {
+ ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[3], 1, NUM_SPANS);
+ return CLI_SUCCESS;
+ }
+ if (!linksets[linkset-1].ss7) {
+ ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset);
+ return CLI_SUCCESS;
+ }
+ if (linksets[linkset-1].ss7)
+ ss7 = &linksets[linkset-1];
+
+ ast_cli(a->fd, "SS7 linkset %d status: %s\n", linkset, (ss7->state == LINKSET_STATE_UP) ? "Up" : "Down");
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry zap_ss7_cli[] = {
+ AST_CLI_DEFINE(handle_ss7_debug, "Enables SS7 debugging on a linkset"),
+ AST_CLI_DEFINE(handle_ss7_no_debug, "Disables SS7 debugging on a linkset"),
+ AST_CLI_DEFINE(handle_ss7_block_cic, "Disables SS7 debugging on a linkset"),
+ AST_CLI_DEFINE(handle_ss7_unblock_cic, "Disables SS7 debugging on a linkset"),
+ AST_CLI_DEFINE(handle_ss7_show_linkset, "Shows the status of a linkset"),
+};
+#endif /* HAVE_SS7 */
+
+static int unload_module(void)
+{
+#if defined(HAVE_PRI) || defined(HAVE_SS7)
+ int y;
+#endif
+#ifdef HAVE_PRI
+ for (y = 0; y < NUM_SPANS; y++)
+ ast_mutex_destroy(&pris[y].lock);
+#endif
+#ifdef HAVE_SS7
+ for (y = 0; y < NUM_SPANS; y++)
+ ast_mutex_destroy(&linksets[y].lock);
+#endif /* HAVE_SS7 */
+ return __unload_module();
+}
+
+static int build_channels(struct zt_chan_conf conf, int iscrv, const char *value, int reload, int lineno, int *found_pseudo)
+{
+ char *c, *chan;
+ int x, start, finish;
+ struct zt_pvt *tmp;
+#ifdef HAVE_PRI
+ struct zt_pri *pri;
+ int trunkgroup, y;
+#endif
+
+ if ((reload == 0) && (conf.chan.sig < 0) && !conf.is_sig_auto) {
+ ast_log(LOG_ERROR, "Signalling must be specified before any channels are.\n");
+ return -1;
+ }
+
+ c = ast_strdupa(value);
+
+#ifdef HAVE_PRI
+ pri = NULL;
+ if (iscrv) {
+ if (sscanf(c, "%d:%n", &trunkgroup, &y) != 1) {
+ ast_log(LOG_WARNING, "CRV must begin with trunkgroup followed by a colon at line %d\n", lineno);
+ return -1;
+ }
+ if (trunkgroup < 1) {
+ ast_log(LOG_WARNING, "CRV trunk group must be a positive number at line %d\n", lineno);
+ return -1;
+ }
+ c += y;
+ for (y = 0; y < NUM_SPANS; y++) {
+ if (pris[y].trunkgroup == trunkgroup) {
+ pri = pris + y;
+ break;
+ }
+ }
+ if (!pri) {
+ ast_log(LOG_WARNING, "No such trunk group %d at CRV declaration at line %d\n", trunkgroup, lineno);
+ return -1;
+ }
+ }
+#endif
+
+ while ((chan = strsep(&c, ","))) {
+ if (sscanf(chan, "%d-%d", &start, &finish) == 2) {
+ /* Range */
+ } else if (sscanf(chan, "%d", &start)) {
+ /* Just one */
+ finish = start;
+ } else if (!strcasecmp(chan, "pseudo")) {
+ finish = start = CHAN_PSEUDO;
+ if (found_pseudo)
+ *found_pseudo = 1;
+ } else {
+ ast_log(LOG_ERROR, "Syntax error parsing '%s' at '%s'\n", value, chan);
+ return -1;
+ }
+ if (finish < start) {
+ ast_log(LOG_WARNING, "Sillyness: %d < %d\n", start, finish);
+ x = finish;
+ finish = start;
+ start = x;
+ }
+
+ for (x = start; x <= finish; x++) {
+#ifdef HAVE_PRI
+ tmp = mkintf(x, conf, pri, reload);
+#else
+ tmp = mkintf(x, conf, NULL, reload);
+#endif
+
+ if (tmp) {
+#ifdef HAVE_PRI
+ if (pri)
+ ast_verb(3, "%s CRV %d:%d, %s signalling\n", reload ? "Reconfigured" : "Registered", trunkgroup, x, sig2str(tmp->sig));
+ else
+#endif
+ ast_verb(3, "%s channel %d, %s signalling\n", reload ? "Reconfigured" : "Registered", x, sig2str(tmp->sig));
+ } else {
+ ast_log(LOG_ERROR, "Unable to %s channel '%s'\n",
+ (reload == 1) ? "reconfigure" : "register", value);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/** The length of the parameters list of 'zapchan'.
+ * \todo Move definition of MAX_CHANLIST_LEN to a proper place. */
+#define MAX_CHANLIST_LEN 80
+
+#if defined(HAVE_ZAPTEL_ECHOCANPARAMS)
+static void process_echocancel(struct zt_chan_conf *confp, const char *data, unsigned int line)
+{
+ char *parse = ast_strdupa(data);
+ char *params[ZT_MAX_ECHOCANPARAMS + 1];
+ unsigned int param_count;
+ unsigned int x;
+
+ if (!(param_count = ast_app_separate_args(parse, ',', params, sizeof(params) / sizeof(params[0]))))
+ return;
+
+ memset(&confp->chan.echocancel, 0, sizeof(confp->chan.echocancel));
+
+ /* first parameter is tap length, process it here */
+
+ x = ast_strlen_zero(params[0]) ? 0 : atoi(params[0]);
+
+ if ((x == 32) || (x == 64) || (x == 128) || (x == 256) || (x == 512) || (x == 1024))
+ confp->chan.echocancel.head.tap_length = x;
+ else if ((confp->chan.echocancel.head.tap_length = ast_true(params[0])))
+ confp->chan.echocancel.head.tap_length = 128;
+
+ /* now process any remaining parameters */
+
+ for (x = 1; x < param_count; x++) {
+ struct {
+ char *name;
+ char *value;
+ } param;
+
+ if (ast_app_separate_args(params[x], '=', (char **) &param, 2) < 1) {
+ ast_log(LOG_WARNING, "Invalid echocancel parameter supplied at line %d: '%s'\n", line, params[x]);
+ continue;
+ }
+
+ if (ast_strlen_zero(param.name) || (strlen(param.name) > sizeof(confp->chan.echocancel.params[0].name)-1)) {
+ ast_log(LOG_WARNING, "Invalid echocancel parameter supplied at line %d: '%s'\n", line, param.name);
+ continue;
+ }
+
+ strcpy(confp->chan.echocancel.params[confp->chan.echocancel.head.param_count].name, param.name);
+
+ if (param.value) {
+ if (sscanf(param.value, "%ud", &confp->chan.echocancel.params[confp->chan.echocancel.head.param_count].value) != 1) {
+ ast_log(LOG_WARNING, "Invalid echocancel parameter value supplied at line %d: '%s'\n", line, param.value);
+ continue;
+ }
+ }
+ confp->chan.echocancel.head.param_count++;
+ }
+}
+#endif /* defined(HAVE_ZAPTEL_ECHOCANPARAMS) */
+
+static int process_zap(struct zt_chan_conf *confp, struct ast_variable *v, int reload, int skipchannels)
+{
+ struct zt_pvt *tmp;
+ const char *ringc; /* temporary string for parsing the dring number. */
+ int y;
+ int found_pseudo = 0;
+ char zapchan[MAX_CHANLIST_LEN] = {};
+
+ for (; v; v = v->next) {
+ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
+ continue;
+
+ /* Create the interface list */
+ if (!strcasecmp(v->name, "channel")
+#ifdef HAVE_PRI
+ || !strcasecmp(v->name, "crv")
+#endif
+ ) {
+ int iscrv;
+ if (skipchannels)
+ continue;
+ iscrv = !strcasecmp(v->name, "crv");
+ if (build_channels(*confp, iscrv, v->value, reload, v->lineno, &found_pseudo))
+ return -1;
+ } else if (!strcasecmp(v->name, "zapchan")) {
+ ast_copy_string(zapchan, v->value, sizeof(zapchan));
+ } else if (!strcasecmp(v->name, "usedistinctiveringdetection")) {
+ usedistinctiveringdetection = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "distinctiveringaftercid")) {
+ distinctiveringaftercid = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "dring1context")) {
+ ast_copy_string(confp->chan.drings.ringContext[0].contextData,v->value,sizeof(confp->chan.drings.ringContext[0].contextData));
+ } else if (!strcasecmp(v->name, "dring2context")) {
+ ast_copy_string(confp->chan.drings.ringContext[1].contextData,v->value,sizeof(confp->chan.drings.ringContext[1].contextData));
+ } else if (!strcasecmp(v->name, "dring3context")) {
+ ast_copy_string(confp->chan.drings.ringContext[2].contextData,v->value,sizeof(confp->chan.drings.ringContext[2].contextData));
+ } else if (!strcasecmp(v->name, "dring1range")) {
+ drings.ringnum[0].range = atoi(v->value);
+ /* 10 is a nice default. */
+ if (confp->chan.drings.ringnum[0].range == 0)
+ confp->chan.drings.ringnum[0].range = 10;
+ } else if (!strcasecmp(v->name, "dring2range")) {
+ confp->chan.drings.ringnum[1].range = atoi(v->value);
+ /* 10 is a nice default. */
+ if (confp->chan.drings.ringnum[1].range == 0)
+ confp->chan.drings.ringnum[1].range = 10;
+ } else if (!strcasecmp(v->name, "dring3range")) {
+ confp->chan.drings.ringnum[2].range = atoi(v->value);
+ /* 10 is a nice default. */
+ if (confp->chan.drings.ringnum[2].range == 0)
+ confp->chan.drings.ringnum[2].range = 10;
+ } else if (!strcasecmp(v->name, "dring1")) {
+ ringc = v->value;
+ sscanf(ringc, "%d,%d,%d", &confp->chan.drings.ringnum[0].ring[0], &confp->chan.drings.ringnum[0].ring[1], &confp->chan.drings.ringnum[0].ring[2]);
+ } else if (!strcasecmp(v->name, "dring2")) {
+ ringc = v->value;
+ sscanf(ringc,"%d,%d,%d", &confp->chan.drings.ringnum[1].ring[0], &confp->chan.drings.ringnum[1].ring[1], &confp->chan.drings.ringnum[1].ring[2]);
+ } else if (!strcasecmp(v->name, "dring3")) {
+ ringc = v->value;
+ sscanf(ringc, "%d,%d,%d", &confp->chan.drings.ringnum[2].ring[0], &confp->chan.drings.ringnum[2].ring[1], &confp->chan.drings.ringnum[2].ring[2]);
+ } else if (!strcasecmp(v->name, "usecallerid")) {
+ confp->chan.use_callerid = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "cidsignalling")) {
+ if (!strcasecmp(v->value, "bell"))
+ confp->chan.cid_signalling = CID_SIG_BELL;
+ else if (!strcasecmp(v->value, "v23"))
+ confp->chan.cid_signalling = CID_SIG_V23;
+ else if (!strcasecmp(v->value, "dtmf"))
+ confp->chan.cid_signalling = CID_SIG_DTMF;
+ else if (!strcasecmp(v->value, "smdi"))
+ confp->chan.cid_signalling = CID_SIG_SMDI;
+ else if (!strcasecmp(v->value, "v23_jp"))
+ confp->chan.cid_signalling = CID_SIG_V23_JP;
+ else if (ast_true(v->value))
+ confp->chan.cid_signalling = CID_SIG_BELL;
+ } else if (!strcasecmp(v->name, "cidstart")) {
+ if (!strcasecmp(v->value, "ring"))
+ confp->chan.cid_start = CID_START_RING;
+ else if (!strcasecmp(v->value, "polarity_in"))
+ confp->chan.cid_start = CID_START_POLARITY_IN;
+ else if (!strcasecmp(v->value, "polarity"))
+ confp->chan.cid_start = CID_START_POLARITY;
+ else if (ast_true(v->value))
+ confp->chan.cid_start = CID_START_RING;
+ } else if (!strcasecmp(v->name, "threewaycalling")) {
+ confp->chan.threewaycalling = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "cancallforward")) {
+ confp->chan.cancallforward = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "relaxdtmf")) {
+ if (ast_true(v->value))
+ confp->chan.dtmfrelax = DSP_DIGITMODE_RELAXDTMF;
+ else
+ confp->chan.dtmfrelax = 0;
+ } else if (!strcasecmp(v->name, "mailbox")) {
+ ast_copy_string(confp->chan.mailbox, v->value, sizeof(confp->chan.mailbox));
+ } else if (!strcasecmp(v->name, "adsi")) {
+ confp->chan.adsi = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "usesmdi")) {
+ confp->chan.use_smdi = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "smdiport")) {
+ ast_copy_string(confp->smdi_port, v->value, sizeof(confp->smdi_port));
+ } else if (!strcasecmp(v->name, "transfer")) {
+ confp->chan.transfer = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "canpark")) {
+ confp->chan.canpark = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "echocancelwhenbridged")) {
+ confp->chan.echocanbridged = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "busydetect")) {
+ confp->chan.busydetect = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "busycount")) {
+ confp->chan.busycount = atoi(v->value);
+ } else if (!strcasecmp(v->name, "busypattern")) {
+ if (sscanf(v->value, "%d,%d", &confp->chan.busy_tonelength, &confp->chan.busy_quietlength) != 2) {
+ ast_log(LOG_ERROR, "busypattern= expects busypattern=tonelength,quietlength\n");
+ }
+ } else if (!strcasecmp(v->name, "callprogress")) {
+ confp->chan.callprogress &= ~CALLPROGRESS_PROGRESS;
+ if (ast_true(v->value))
+ confp->chan.callprogress |= CALLPROGRESS_PROGRESS;
+ } else if (!strcasecmp(v->name, "faxdetect")) {
+ confp->chan.callprogress &= ~CALLPROGRESS_FAX;
+ if (!strcasecmp(v->value, "incoming")) {
+ confp->chan.callprogress |= CALLPROGRESS_FAX_INCOMING;
+ } else if (!strcasecmp(v->value, "outgoing")) {
+ confp->chan.callprogress |= CALLPROGRESS_FAX_OUTGOING;
+ } else if (!strcasecmp(v->value, "both") || ast_true(v->value))
+ confp->chan.callprogress |= CALLPROGRESS_FAX_INCOMING | CALLPROGRESS_FAX_OUTGOING;
+ } else if (!strcasecmp(v->name, "echocancel")) {
+#if defined(HAVE_ZAPTEL_ECHOCANPARAMS)
+ process_echocancel(confp, v->value, v->lineno);
+#else
+ y = ast_strlen_zero(v->value) ? 0 : atoi(v->value);
+
+ if ((y == 32) || (y == 64) || (y == 128) || (y == 256) || (y == 512) || (y == 1024))
+ confp->chan.echocancel = y;
+ else if ((confp->chan.echocancel = ast_true(v->value)))
+ confp->chan.echocancel = 128;
+#endif
+ } else if (!strcasecmp(v->name, "echotraining")) {
+ if (sscanf(v->value, "%d", &y) == 1) {
+ if ((y < 10) || (y > 4000)) {
+ ast_log(LOG_WARNING, "Echo training time must be within the range of 10 to 4000 ms at line %d\n", v->lineno);
+ } else {
+ confp->chan.echotraining = y;
+ }
+ } else if (ast_true(v->value)) {
+ confp->chan.echotraining = 400;
+ } else
+ confp->chan.echotraining = 0;
+ } else if (!strcasecmp(v->name, "hidecallerid")) {
+ confp->chan.hidecallerid = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "hidecalleridname")) {
+ confp->chan.hidecalleridname = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "pulsedial")) {
+ confp->chan.pulse = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callreturn")) {
+ confp->chan.callreturn = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callwaiting")) {
+ confp->chan.callwaiting = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "callwaitingcallerid")) {
+ confp->chan.callwaitingcallerid = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(confp->chan.context, v->value, sizeof(confp->chan.context));
+ } else if (!strcasecmp(v->name, "language")) {
+ ast_copy_string(confp->chan.language, v->value, sizeof(confp->chan.language));
+ } else if (!strcasecmp(v->name, "progzone")) {
+ ast_copy_string(progzone, v->value, sizeof(progzone));
+ } else if (!strcasecmp(v->name, "mohinterpret")
+ ||!strcasecmp(v->name, "musiconhold") || !strcasecmp(v->name, "musicclass")) {
+ ast_copy_string(confp->chan.mohinterpret, v->value, sizeof(confp->chan.mohinterpret));
+ } else if (!strcasecmp(v->name, "mohsuggest")) {
+ ast_copy_string(confp->chan.mohsuggest, v->value, sizeof(confp->chan.mohsuggest));
+ } else if (!strcasecmp(v->name, "stripmsd")) {
+ confp->chan.stripmsd = atoi(v->value);
+ } else if (!strcasecmp(v->name, "jitterbuffers")) {
+ numbufs = atoi(v->value);
+ } else if (!strcasecmp(v->name, "group")) {
+ confp->chan.group = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "callgroup")) {
+ if (!strcasecmp(v->value, "none"))
+ confp->chan.callgroup = 0;
+ else
+ confp->chan.callgroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "pickupgroup")) {
+ if (!strcasecmp(v->value, "none"))
+ confp->chan.pickupgroup = 0;
+ else
+ confp->chan.pickupgroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "setvar")) {
+ char *varname = ast_strdupa(v->value), *varval = NULL;
+ struct ast_variable *tmpvar;
+ if (varname && (varval = strchr(varname, '='))) {
+ *varval++ = '\0';
+ if ((tmpvar = ast_variable_new(varname, varval, ""))) {
+ tmpvar->next = confp->chan.vars;
+ confp->chan.vars = tmpvar;
+ }
+ }
+ } else if (!strcasecmp(v->name, "immediate")) {
+ confp->chan.immediate = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "transfertobusy")) {
+ confp->chan.transfertobusy = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "mwimonitor")) {
+ confp->chan.mwimonitor = ast_true(v->value) ? 1 : 0;
+ } else if (!strcasecmp(v->name, "cid_rxgain")) {
+ if (sscanf(v->value, "%f", &confp->chan.cid_rxgain) != 1) {
+ ast_log(LOG_WARNING, "Invalid cid_rxgain: %s\n", v->value);
+ }
+ } else if (!strcasecmp(v->name, "rxgain")) {
+ if (sscanf(v->value, "%f", &confp->chan.rxgain) != 1) {
+ ast_log(LOG_WARNING, "Invalid rxgain: %s\n", v->value);
+ }
+ } else if (!strcasecmp(v->name, "txgain")) {
+ if (sscanf(v->value, "%f", &confp->chan.txgain) != 1) {
+ ast_log(LOG_WARNING, "Invalid txgain: %s\n", v->value);
+ }
+ } else if (!strcasecmp(v->name, "tonezone")) {
+ if (sscanf(v->value, "%d", &confp->chan.tonezone) != 1) {
+ ast_log(LOG_WARNING, "Invalid tonezone: %s\n", v->value);
+ }
+ } else if (!strcasecmp(v->name, "callerid")) {
+ if (!strcasecmp(v->value, "asreceived")) {
+ confp->chan.cid_num[0] = '\0';
+ confp->chan.cid_name[0] = '\0';
+ } else {
+ ast_callerid_split(v->value, confp->chan.cid_name, sizeof(confp->chan.cid_name), confp->chan.cid_num, sizeof(confp->chan.cid_num));
+ }
+ } else if (!strcasecmp(v->name, "fullname")) {
+ ast_copy_string(confp->chan.cid_name, v->value, sizeof(confp->chan.cid_name));
+ } else if (!strcasecmp(v->name, "cid_number")) {
+ ast_copy_string(confp->chan.cid_num, v->value, sizeof(confp->chan.cid_num));
+ } else if (!strcasecmp(v->name, "useincomingcalleridonzaptransfer")) {
+ confp->chan.zaptrcallerid = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "restrictcid")) {
+ confp->chan.restrictcid = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "usecallingpres")) {
+ confp->chan.use_callingpres = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "accountcode")) {
+ ast_copy_string(confp->chan.accountcode, v->value, sizeof(confp->chan.accountcode));
+ } else if (!strcasecmp(v->name, "amaflags")) {
+ y = ast_cdr_amaflags2int(v->value);
+ if (y < 0)
+ ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno);
+ else
+ confp->chan.amaflags = y;
+ } else if (!strcasecmp(v->name, "polarityonanswerdelay")) {
+ confp->chan.polarityonanswerdelay = atoi(v->value);
+ } else if (!strcasecmp(v->name, "answeronpolarityswitch")) {
+ confp->chan.answeronpolarityswitch = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "hanguponpolarityswitch")) {
+ confp->chan.hanguponpolarityswitch = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "sendcalleridafter")) {
+ confp->chan.sendcalleridafter = atoi(v->value);
+ } else if (!strcasecmp(v->name, "mwimonitornotify")) {
+ ast_copy_string(mwimonitornotify, v->value, sizeof(mwimonitornotify));
+ } else if (!reload){
+ if (!strcasecmp(v->name, "signalling") || !strcasecmp(v->name, "signaling")) {
+ int orig_radio = confp->chan.radio;
+ int orig_outsigmod = confp->chan.outsigmod;
+ int orig_auto = confp->is_sig_auto;
+
+ confp->chan.radio = 0;
+ confp->chan.outsigmod = -1;
+ confp->is_sig_auto = 0;
+ if (!strcasecmp(v->value, "em")) {
+ confp->chan.sig = SIG_EM;
+ } else if (!strcasecmp(v->value, "em_e1")) {
+ confp->chan.sig = SIG_EM_E1;
+ } else if (!strcasecmp(v->value, "em_w")) {
+ confp->chan.sig = SIG_EMWINK;
+ } else if (!strcasecmp(v->value, "fxs_ls")) {
+ confp->chan.sig = SIG_FXSLS;
+ } else if (!strcasecmp(v->value, "fxs_gs")) {
+ confp->chan.sig = SIG_FXSGS;
+ } else if (!strcasecmp(v->value, "fxs_ks")) {
+ confp->chan.sig = SIG_FXSKS;
+ } else if (!strcasecmp(v->value, "fxo_ls")) {
+ confp->chan.sig = SIG_FXOLS;
+ } else if (!strcasecmp(v->value, "fxo_gs")) {
+ confp->chan.sig = SIG_FXOGS;
+ } else if (!strcasecmp(v->value, "fxo_ks")) {
+ confp->chan.sig = SIG_FXOKS;
+ } else if (!strcasecmp(v->value, "fxs_rx")) {
+ confp->chan.sig = SIG_FXSKS;
+ confp->chan.radio = 1;
+ } else if (!strcasecmp(v->value, "fxo_rx")) {
+ confp->chan.sig = SIG_FXOLS;
+ confp->chan.radio = 1;
+ } else if (!strcasecmp(v->value, "fxs_tx")) {
+ confp->chan.sig = SIG_FXSLS;
+ confp->chan.radio = 1;
+ } else if (!strcasecmp(v->value, "fxo_tx")) {
+ confp->chan.sig = SIG_FXOGS;
+ confp->chan.radio = 1;
+ } else if (!strcasecmp(v->value, "em_rx")) {
+ confp->chan.sig = SIG_EM;
+ confp->chan.radio = 1;
+ } else if (!strcasecmp(v->value, "em_tx")) {
+ confp->chan.sig = SIG_EM;
+ confp->chan.radio = 1;
+ } else if (!strcasecmp(v->value, "em_rxtx")) {
+ confp->chan.sig = SIG_EM;
+ confp->chan.radio = 2;
+ } else if (!strcasecmp(v->value, "em_txrx")) {
+ confp->chan.sig = SIG_EM;
+ confp->chan.radio = 2;
+ } else if (!strcasecmp(v->value, "sf")) {
+ confp->chan.sig = SIG_SF;
+ } else if (!strcasecmp(v->value, "sf_w")) {
+ confp->chan.sig = SIG_SFWINK;
+ } else if (!strcasecmp(v->value, "sf_featd")) {
+ confp->chan.sig = SIG_FEATD;
+ } else if (!strcasecmp(v->value, "sf_featdmf")) {
+ confp->chan.sig = SIG_FEATDMF;
+ } else if (!strcasecmp(v->value, "sf_featb")) {
+ confp->chan.sig = SIG_SF_FEATB;
+ } else if (!strcasecmp(v->value, "sf")) {
+ confp->chan.sig = SIG_SF;
+ } else if (!strcasecmp(v->value, "sf_rx")) {
+ confp->chan.sig = SIG_SF;
+ confp->chan.radio = 1;
+ } else if (!strcasecmp(v->value, "sf_tx")) {
+ confp->chan.sig = SIG_SF;
+ confp->chan.radio = 1;
+ } else if (!strcasecmp(v->value, "sf_rxtx")) {
+ confp->chan.sig = SIG_SF;
+ confp->chan.radio = 2;
+ } else if (!strcasecmp(v->value, "sf_txrx")) {
+ confp->chan.sig = SIG_SF;
+ confp->chan.radio = 2;
+ } else if (!strcasecmp(v->value, "featd")) {
+ confp->chan.sig = SIG_FEATD;
+ } else if (!strcasecmp(v->value, "featdmf")) {
+ confp->chan.sig = SIG_FEATDMF;
+ } else if (!strcasecmp(v->value, "featdmf_ta")) {
+ confp->chan.sig = SIG_FEATDMF_TA;
+ } else if (!strcasecmp(v->value, "e911")) {
+ confp->chan.sig = SIG_E911;
+ } else if (!strcasecmp(v->value, "fgccama")) {
+ confp->chan.sig = SIG_FGC_CAMA;
+ } else if (!strcasecmp(v->value, "fgccamamf")) {
+ confp->chan.sig = SIG_FGC_CAMAMF;
+ } else if (!strcasecmp(v->value, "featb")) {
+ confp->chan.sig = SIG_FEATB;
+#ifdef HAVE_PRI
+ } else if (!strcasecmp(v->value, "pri_net")) {
+ confp->chan.sig = SIG_PRI;
+ confp->pri.nodetype = PRI_NETWORK;
+ } else if (!strcasecmp(v->value, "pri_cpe")) {
+ confp->chan.sig = SIG_PRI;
+ confp->pri.nodetype = PRI_CPE;
+ } else if (!strcasecmp(v->value, "bri_cpe")) {
+ confp->chan.sig = SIG_BRI;
+ confp->pri.nodetype = PRI_CPE;
+ } else if (!strcasecmp(v->value, "bri_net")) {
+ confp->chan.sig = SIG_BRI;
+ confp->pri.nodetype = PRI_NETWORK;
+ } else if (!strcasecmp(v->value, "bri_cpe_ptmp")) {
+ confp->chan.sig = SIG_BRI_PTMP;
+ confp->pri.nodetype = PRI_CPE;
+ } else if (!strcasecmp(v->value, "bri_net_ptmp")) {
+ ast_log(LOG_WARNING, "How cool would it be if someone implemented this mode! For now, sucks for you.\n");
+ } else if (!strcasecmp(v->value, "gr303fxoks_net")) {
+ confp->chan.sig = SIG_GR303FXOKS;
+ confp->pri.nodetype = PRI_NETWORK;
+ } else if (!strcasecmp(v->value, "gr303fxsks_cpe")) {
+ confp->chan.sig = SIG_GR303FXSKS;
+ confp->pri.nodetype = PRI_CPE;
+#endif
+#ifdef HAVE_SS7
+ } else if (!strcasecmp(v->value, "ss7")) {
+ confp->chan.sig = SIG_SS7;
+#endif
+ } else if (!strcasecmp(v->value, "auto")) {
+ confp->is_sig_auto = 1;
+ } else {
+ confp->chan.outsigmod = orig_outsigmod;
+ confp->chan.radio = orig_radio;
+ confp->is_sig_auto = orig_auto;
+ ast_log(LOG_ERROR, "Unknown signalling method '%s'\n", v->value);
+ }
+ } else if (!strcasecmp(v->name, "outsignalling") || !strcasecmp(v->name, "outsignaling")) {
+ if (!strcasecmp(v->value, "em")) {
+ confp->chan.outsigmod = SIG_EM;
+ } else if (!strcasecmp(v->value, "em_e1")) {
+ confp->chan.outsigmod = SIG_EM_E1;
+ } else if (!strcasecmp(v->value, "em_w")) {
+ confp->chan.outsigmod = SIG_EMWINK;
+ } else if (!strcasecmp(v->value, "sf")) {
+ confp->chan.outsigmod = SIG_SF;
+ } else if (!strcasecmp(v->value, "sf_w")) {
+ confp->chan.outsigmod = SIG_SFWINK;
+ } else if (!strcasecmp(v->value, "sf_featd")) {
+ confp->chan.outsigmod = SIG_FEATD;
+ } else if (!strcasecmp(v->value, "sf_featdmf")) {
+ confp->chan.outsigmod = SIG_FEATDMF;
+ } else if (!strcasecmp(v->value, "sf_featb")) {
+ confp->chan.outsigmod = SIG_SF_FEATB;
+ } else if (!strcasecmp(v->value, "sf")) {
+ confp->chan.outsigmod = SIG_SF;
+ } else if (!strcasecmp(v->value, "featd")) {
+ confp->chan.outsigmod = SIG_FEATD;
+ } else if (!strcasecmp(v->value, "featdmf")) {
+ confp->chan.outsigmod = SIG_FEATDMF;
+ } else if (!strcasecmp(v->value, "featdmf_ta")) {
+ confp->chan.outsigmod = SIG_FEATDMF_TA;
+ } else if (!strcasecmp(v->value, "e911")) {
+ confp->chan.outsigmod = SIG_E911;
+ } else if (!strcasecmp(v->value, "fgccama")) {
+ confp->chan.outsigmod = SIG_FGC_CAMA;
+ } else if (!strcasecmp(v->value, "fgccamamf")) {
+ confp->chan.outsigmod = SIG_FGC_CAMAMF;
+ } else if (!strcasecmp(v->value, "featb")) {
+ confp->chan.outsigmod = SIG_FEATB;
+ } else {
+ ast_log(LOG_ERROR, "Unknown signalling method '%s'\n", v->value);
+ }
+#ifdef HAVE_PRI
+ } else if (!strcasecmp(v->name, "pridialplan")) {
+ if (!strcasecmp(v->value, "national")) {
+ confp->pri.dialplan = PRI_NATIONAL_ISDN + 1;
+ } else if (!strcasecmp(v->value, "unknown")) {
+ confp->pri.dialplan = PRI_UNKNOWN + 1;
+ } else if (!strcasecmp(v->value, "private")) {
+ confp->pri.dialplan = PRI_PRIVATE + 1;
+ } else if (!strcasecmp(v->value, "international")) {
+ confp->pri.dialplan = PRI_INTERNATIONAL_ISDN + 1;
+ } else if (!strcasecmp(v->value, "local")) {
+ confp->pri.dialplan = PRI_LOCAL_ISDN + 1;
+ } else if (!strcasecmp(v->value, "dynamic")) {
+ confp->pri.dialplan = -1;
+ } else if (!strcasecmp(v->value, "redundant")) {
+ confp->pri.dialplan = -2;
+ } else {
+ ast_log(LOG_WARNING, "Unknown PRI dialplan '%s' at line %d.\n", v->value, v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "prilocaldialplan")) {
+ if (!strcasecmp(v->value, "national")) {
+ confp->pri.localdialplan = PRI_NATIONAL_ISDN + 1;
+ } else if (!strcasecmp(v->value, "unknown")) {
+ confp->pri.localdialplan = PRI_UNKNOWN + 1;
+ } else if (!strcasecmp(v->value, "private")) {
+ confp->pri.localdialplan = PRI_PRIVATE + 1;
+ } else if (!strcasecmp(v->value, "international")) {
+ confp->pri.localdialplan = PRI_INTERNATIONAL_ISDN + 1;
+ } else if (!strcasecmp(v->value, "local")) {
+ confp->pri.localdialplan = PRI_LOCAL_ISDN + 1;
+ } else if (!strcasecmp(v->value, "dynamic")) {
+ confp->pri.localdialplan = -1;
+ } else if (!strcasecmp(v->value, "redundant")) {
+ confp->pri.localdialplan = -2;
+ } else {
+ ast_log(LOG_WARNING, "Unknown PRI localdialplan '%s' at line %d.\n", v->value, v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "switchtype")) {
+ if (!strcasecmp(v->value, "national"))
+ confp->pri.switchtype = PRI_SWITCH_NI2;
+ else if (!strcasecmp(v->value, "ni1"))
+ confp->pri.switchtype = PRI_SWITCH_NI1;
+ else if (!strcasecmp(v->value, "dms100"))
+ confp->pri.switchtype = PRI_SWITCH_DMS100;
+ else if (!strcasecmp(v->value, "4ess"))
+ confp->pri.switchtype = PRI_SWITCH_ATT4ESS;
+ else if (!strcasecmp(v->value, "5ess"))
+ confp->pri.switchtype = PRI_SWITCH_LUCENT5E;
+ else if (!strcasecmp(v->value, "euroisdn"))
+ confp->pri.switchtype = PRI_SWITCH_EUROISDN_E1;
+ else if (!strcasecmp(v->value, "qsig"))
+ confp->pri.switchtype = PRI_SWITCH_QSIG;
+ else {
+ ast_log(LOG_ERROR, "Unknown switchtype '%s'\n", v->value);
+ return -1;
+ }
+ } else if (!strcasecmp(v->name, "nsf")) {
+ if (!strcasecmp(v->value, "sdn"))
+ confp->pri.nsf = PRI_NSF_SDN;
+ else if (!strcasecmp(v->value, "megacom"))
+ confp->pri.nsf = PRI_NSF_MEGACOM;
+ else if (!strcasecmp(v->value, "tollfreemegacom"))
+ confp->pri.nsf = PRI_NSF_TOLL_FREE_MEGACOM;
+ else if (!strcasecmp(v->value, "accunet"))
+ confp->pri.nsf = PRI_NSF_ACCUNET;
+ else if (!strcasecmp(v->value, "none"))
+ confp->pri.nsf = PRI_NSF_NONE;
+ else {
+ ast_log(LOG_WARNING, "Unknown network-specific facility '%s'\n", v->value);
+ confp->pri.nsf = PRI_NSF_NONE;
+ }
+ } else if (!strcasecmp(v->name, "priindication")) {
+ if (!strcasecmp(v->value, "outofband"))
+ confp->chan.priindication_oob = 1;
+ else if (!strcasecmp(v->value, "inband"))
+ confp->chan.priindication_oob = 0;
+ else
+ ast_log(LOG_WARNING, "'%s' is not a valid pri indication value, should be 'inband' or 'outofband' at line %d\n",
+ v->value, v->lineno);
+ } else if (!strcasecmp(v->name, "priexclusive")) {
+ confp->chan.priexclusive = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "internationalprefix")) {
+ ast_copy_string(confp->pri.internationalprefix, v->value, sizeof(confp->pri.internationalprefix));
+ } else if (!strcasecmp(v->name, "nationalprefix")) {
+ ast_copy_string(confp->pri.nationalprefix, v->value, sizeof(confp->pri.nationalprefix));
+ } else if (!strcasecmp(v->name, "localprefix")) {
+ ast_copy_string(confp->pri.localprefix, v->value, sizeof(confp->pri.localprefix));
+ } else if (!strcasecmp(v->name, "privateprefix")) {
+ ast_copy_string(confp->pri.privateprefix, v->value, sizeof(confp->pri.privateprefix));
+ } else if (!strcasecmp(v->name, "unknownprefix")) {
+ ast_copy_string(confp->pri.unknownprefix, v->value, sizeof(confp->pri.unknownprefix));
+ } else if (!strcasecmp(v->name, "resetinterval")) {
+ if (!strcasecmp(v->value, "never"))
+ confp->pri.resetinterval = -1;
+ else if (atoi(v->value) >= 60)
+ confp->pri.resetinterval = atoi(v->value);
+ else
+ ast_log(LOG_WARNING, "'%s' is not a valid reset interval, should be >= 60 seconds or 'never' at line %d\n",
+ v->value, v->lineno);
+ } else if (!strcasecmp(v->name, "minunused")) {
+ confp->pri.minunused = atoi(v->value);
+ } else if (!strcasecmp(v->name, "minidle")) {
+ confp->pri.minidle = atoi(v->value);
+ } else if (!strcasecmp(v->name, "idleext")) {
+ ast_copy_string(confp->pri.idleext, v->value, sizeof(confp->pri.idleext));
+ } else if (!strcasecmp(v->name, "idledial")) {
+ ast_copy_string(confp->pri.idledial, v->value, sizeof(confp->pri.idledial));
+ } else if (!strcasecmp(v->name, "overlapdial")) {
+ if (ast_true(v->value)) {
+ confp->pri.overlapdial = ZAP_OVERLAPDIAL_BOTH;
+ } else if (!strcasecmp(v->value, "incoming")) {
+ confp->pri.overlapdial = ZAP_OVERLAPDIAL_INCOMING;
+ } else if (!strcasecmp(v->value, "outgoing")) {
+ confp->pri.overlapdial = ZAP_OVERLAPDIAL_OUTGOING;
+ } else if (!strcasecmp(v->value, "both") || ast_true(v->value)) {
+ confp->pri.overlapdial = ZAP_OVERLAPDIAL_BOTH;
+ } else {
+ confp->pri.overlapdial = ZAP_OVERLAPDIAL_NONE;
+ }
+ } else if (!strcasecmp(v->name, "pritimer")) {
+#ifdef PRI_GETSET_TIMERS
+ char tmp[20], *timerc, *c = tmp;
+ int timer, timeridx;
+ ast_copy_string(tmp, v->value, sizeof(tmp));
+ timerc = strsep(&c, ",");
+ if (timerc) {
+ timer = atoi(c);
+ if (!timer)
+ ast_log(LOG_WARNING, "'%s' is not a valid value for an ISDN timer\n", timerc);
+ else {
+ if ((timeridx = pri_timer2idx(timerc)) >= 0)
+ pritimers[timeridx] = timer;
+ else
+ ast_log(LOG_WARNING, "'%s' is not a valid ISDN timer\n", timerc);
+ }
+ } else
+ ast_log(LOG_WARNING, "'%s' is not a valid ISDN timer configuration string\n", v->value);
+
+ } else if (!strcasecmp(v->name, "facilityenable")) {
+ confp->pri.facilityenable = ast_true(v->value);
+#endif /* PRI_GETSET_TIMERS */
+#endif /* HAVE_PRI */
+#ifdef HAVE_SS7
+ } else if (!strcasecmp(v->name, "ss7type")) {
+ if (!strcasecmp(v->value, "itu")) {
+ cur_ss7type = SS7_ITU;
+ } else if (!strcasecmp(v->value, "ansi")) {
+ cur_ss7type = SS7_ANSI;
+ } else
+ ast_log(LOG_WARNING, "'%s' is an unknown ss7 switch type!\n", v->value);
+ } else if (!strcasecmp(v->name, "linkset")) {
+ cur_linkset = atoi(v->value);
+ } else if (!strcasecmp(v->name, "pointcode")) {
+ cur_pointcode = parse_pointcode(v->value);
+ } else if (!strcasecmp(v->name, "adjpointcode")) {
+ cur_adjpointcode = parse_pointcode(v->value);
+ } else if (!strcasecmp(v->name, "defaultdpc")) {
+ cur_defaultdpc = parse_pointcode(v->value);
+ } else if (!strcasecmp(v->name, "cicbeginswith")) {
+ cur_cicbeginswith = atoi(v->value);
+ } else if (!strcasecmp(v->name, "networkindicator")) {
+ if (!strcasecmp(v->value, "national"))
+ cur_networkindicator = SS7_NI_NAT;
+ else if (!strcasecmp(v->value, "national_spare"))
+ cur_networkindicator = SS7_NI_NAT_SPARE;
+ else if (!strcasecmp(v->value, "international"))
+ cur_networkindicator = SS7_NI_INT;
+ else if (!strcasecmp(v->value, "international_spare"))
+ cur_networkindicator = SS7_NI_INT_SPARE;
+ else
+ cur_networkindicator = -1;
+ } else if (!strcasecmp(v->name, "ss7_internationalprefix")) {
+ ast_copy_string(confp->ss7.internationalprefix, v->value, sizeof(confp->ss7.internationalprefix));
+ } else if (!strcasecmp(v->name, "ss7_nationalprefix")) {
+ ast_copy_string(confp->ss7.nationalprefix, v->value, sizeof(confp->ss7.nationalprefix));
+ } else if (!strcasecmp(v->name, "ss7_subscriberprefix")) {
+ ast_copy_string(confp->ss7.subscriberprefix, v->value, sizeof(confp->ss7.subscriberprefix));
+ } else if (!strcasecmp(v->name, "ss7_unknownprefix")) {
+ ast_copy_string(confp->ss7.unknownprefix, v->value, sizeof(confp->ss7.unknownprefix));
+ } else if (!strcasecmp(v->name, "ss7_called_nai")) {
+ if (!strcasecmp(v->value, "national")) {
+ confp->ss7.called_nai = SS7_NAI_NATIONAL;
+ } else if (!strcasecmp(v->value, "international")) {
+ confp->ss7.called_nai = SS7_NAI_INTERNATIONAL;
+ } else if (!strcasecmp(v->value, "subscriber")) {
+ confp->ss7.called_nai = SS7_NAI_SUBSCRIBER;
+ } else if (!strcasecmp(v->value, "dynamic")) {
+ confp->ss7.called_nai = SS7_NAI_DYNAMIC;
+ } else {
+ ast_log(LOG_WARNING, "Unknown SS7 called_nai '%s' at line %d.\n", v->value, v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "ss7_calling_nai")) {
+ if (!strcasecmp(v->value, "national")) {
+ confp->ss7.calling_nai = SS7_NAI_NATIONAL;
+ } else if (!strcasecmp(v->value, "international")) {
+ confp->ss7.calling_nai = SS7_NAI_INTERNATIONAL;
+ } else if (!strcasecmp(v->value, "subscriber")) {
+ confp->ss7.calling_nai = SS7_NAI_SUBSCRIBER;
+ } else if (!strcasecmp(v->value, "dynamic")) {
+ confp->ss7.calling_nai = SS7_NAI_DYNAMIC;
+ } else {
+ ast_log(LOG_WARNING, "Unknown SS7 calling_nai '%s' at line %d.\n", v->value, v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "sigchan")) {
+ int sigchan, res;
+ sigchan = atoi(v->value);
+ res = linkset_addsigchan(sigchan);
+ if (res < 0)
+ return -1;
+#endif /* HAVE_SS7 */
+ } else if (!strcasecmp(v->name, "cadence")) {
+ /* setup to scan our argument */
+ int element_count, c[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ int i;
+ struct zt_ring_cadence new_cadence;
+ int cid_location = -1;
+ int firstcadencepos = 0;
+ char original_args[80];
+ int cadence_is_ok = 1;
+
+ ast_copy_string(original_args, v->value, sizeof(original_args));
+ /* 16 cadences allowed (8 pairs) */
+ element_count = sscanf(v->value, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", &c[0], &c[1], &c[2], &c[3], &c[4], &c[5], &c[6], &c[7], &c[8], &c[9], &c[10], &c[11], &c[12], &c[13], &c[14], &c[15]);
+
+ /* Cadence must be even (on/off) */
+ if (element_count % 2 == 1) {
+ ast_log(LOG_ERROR, "Must be a silence duration for each ring duration: %s\n",original_args);
+ cadence_is_ok = 0;
+ }
+
+ /* Ring cadences cannot be negative */
+ for (i = 0; i < element_count; i++) {
+ if (c[i] == 0) {
+ ast_log(LOG_ERROR, "Ring or silence duration cannot be zero: %s\n", original_args);
+ cadence_is_ok = 0;
+ break;
+ } else if (c[i] < 0) {
+ if (i % 2 == 1) {
+ /* Silence duration, negative possibly okay */
+ if (cid_location == -1) {
+ cid_location = i;
+ c[i] *= -1;
+ } else {
+ ast_log(LOG_ERROR, "CID location specified twice: %s\n",original_args);
+ cadence_is_ok = 0;
+ break;
+ }
+ } else {
+ if (firstcadencepos == 0) {
+ firstcadencepos = i; /* only recorded to avoid duplicate specification */
+ /* duration will be passed negative to the Zaptel driver */
+ } else {
+ ast_log(LOG_ERROR, "First cadence position specified twice: %s\n",original_args);
+ cadence_is_ok = 0;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Substitute our scanned cadence */
+ for (i = 0; i < 16; i++) {
+ new_cadence.ringcadence[i] = c[i];
+ }
+
+ if (cadence_is_ok) {
+ /* ---we scanned it without getting annoyed; now some sanity checks--- */
+ if (element_count < 2) {
+ ast_log(LOG_ERROR, "Minimum cadence is ring,pause: %s\n", original_args);
+ } else {
+ if (cid_location == -1) {
+ /* user didn't say; default to first pause */
+ cid_location = 1;
+ } else {
+ /* convert element_index to cidrings value */
+ cid_location = (cid_location + 1) / 2;
+ }
+ /* ---we like their cadence; try to install it--- */
+ if (!user_has_defined_cadences++)
+ /* this is the first user-defined cadence; clear the default user cadences */
+ num_cadence = 0;
+ if ((num_cadence+1) >= NUM_CADENCE_MAX)
+ ast_log(LOG_ERROR, "Already %d cadences; can't add another: %s\n", NUM_CADENCE_MAX, original_args);
+ else {
+ cadences[num_cadence] = new_cadence;
+ cidrings[num_cadence++] = cid_location;
+ ast_verb(3, "cadence 'r%d' added: %s\n",num_cadence,original_args);
+ }
+ }
+ }
+ } else if (!strcasecmp(v->name, "ringtimeout")) {
+ ringt_base = (atoi(v->value) * 8) / READ_SIZE;
+ } else if (!strcasecmp(v->name, "prewink")) {
+ confp->timing.prewinktime = atoi(v->value);
+ } else if (!strcasecmp(v->name, "preflash")) {
+ confp->timing.preflashtime = atoi(v->value);
+ } else if (!strcasecmp(v->name, "wink")) {
+ confp->timing.winktime = atoi(v->value);
+ } else if (!strcasecmp(v->name, "flash")) {
+ confp->timing.flashtime = atoi(v->value);
+ } else if (!strcasecmp(v->name, "start")) {
+ confp->timing.starttime = atoi(v->value);
+ } else if (!strcasecmp(v->name, "rxwink")) {
+ confp->timing.rxwinktime = atoi(v->value);
+ } else if (!strcasecmp(v->name, "rxflash")) {
+ confp->timing.rxflashtime = atoi(v->value);
+ } else if (!strcasecmp(v->name, "debounce")) {
+ confp->timing.debouncetime = atoi(v->value);
+ } else if (!strcasecmp(v->name, "toneduration")) {
+ int toneduration;
+ int ctlfd;
+ int res;
+ struct zt_dialparams dps;
+
+ ctlfd = open("/dev/zap/ctl", O_RDWR);
+ if (ctlfd == -1) {
+ ast_log(LOG_ERROR, "Unable to open /dev/zap/ctl to set toneduration\n");
+ return -1;
+ }
+
+ toneduration = atoi(v->value);
+ if (toneduration > -1) {
+ dps.dtmf_tonelen = dps.mfv1_tonelen = toneduration;
+ res = ioctl(ctlfd, ZT_SET_DIALPARAMS, &dps);
+ if (res < 0) {
+ ast_log(LOG_ERROR, "Invalid tone duration: %d ms\n", toneduration);
+ return -1;
+ }
+ }
+ close(ctlfd);
+ } else if (!strcasecmp(v->name, "defaultcic")) {
+ ast_copy_string(defaultcic, v->value, sizeof(defaultcic));
+ } else if (!strcasecmp(v->name, "defaultozz")) {
+ ast_copy_string(defaultozz, v->value, sizeof(defaultozz));
+ } else if (!strcasecmp(v->name, "mwilevel")) {
+ mwilevel = atoi(v->value);
+ }
+ } else if (!skipchannels)
+ ast_log(LOG_WARNING, "Ignoring %s\n", v->name);
+ }
+ if (zapchan[0]) {
+ /* The user has set 'zapchan' */
+ /*< \todo pass proper line number instead of 0 */
+ if (build_channels(*confp, 0, zapchan, reload, 0, &found_pseudo)) {
+ return -1;
+ }
+ }
+ /*< \todo why check for the pseudo in the per-channel section.
+ * Any actual use for manual setup of the pseudo channel? */
+ if (!found_pseudo && reload == 0) {
+ /* Make sure pseudo isn't a member of any groups if
+ we're automatically making it. */
+
+ confp->chan.group = 0;
+ confp->chan.callgroup = 0;
+ confp->chan.pickupgroup = 0;
+
+ tmp = mkintf(CHAN_PSEUDO, *confp, NULL, reload);
+
+ if (tmp) {
+ ast_verb(3, "Automatically generated pseudo channel\n");
+ } else {
+ ast_log(LOG_WARNING, "Unable to register pseudo channel!\n");
+ }
+ }
+ return 0;
+}
+
+static int setup_zap(int reload)
+{
+ struct ast_config *cfg, *ucfg;
+ struct ast_variable *v;
+ struct zt_chan_conf base_conf = zt_chan_conf_default();
+ struct zt_chan_conf conf;
+ struct ast_flags config_flags = { reload == 1 ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+ int res;
+
+#ifdef HAVE_PRI
+ char *c;
+ int spanno;
+ int i;
+ int logicalspan;
+ int trunkgroup;
+ int dchannels[NUM_DCHANS];
+#endif
+
+ cfg = ast_config_load(config, config_flags);
+
+ /* Error if we have no config file */
+ if (!cfg) {
+ ast_log(LOG_ERROR, "Unable to load config %s\n", config);
+ return 0;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
+ ucfg = ast_config_load("users.conf", config_flags);
+ if (ucfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+ ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
+ cfg = ast_config_load(config, config_flags);
+ } else {
+ ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
+ ucfg = ast_config_load("users.conf", config_flags);
+ }
+
+ /* It's a little silly to lock it, but we mind as well just to be sure */
+ ast_mutex_lock(&iflock);
+#ifdef HAVE_PRI
+ if (!reload) {
+ /* Process trunkgroups first */
+ v = ast_variable_browse(cfg, "trunkgroups");
+ while (v) {
+ if (!strcasecmp(v->name, "trunkgroup")) {
+ trunkgroup = atoi(v->value);
+ if (trunkgroup > 0) {
+ if ((c = strchr(v->value, ','))) {
+ i = 0;
+ memset(dchannels, 0, sizeof(dchannels));
+ while (c && (i < NUM_DCHANS)) {
+ dchannels[i] = atoi(c + 1);
+ if (dchannels[i] < 0) {
+ ast_log(LOG_WARNING, "D-channel for trunk group %d must be a postiive number at line %d of zapata.conf\n", trunkgroup, v->lineno);
+ } else
+ i++;
+ c = strchr(c + 1, ',');
+ }
+ if (i) {
+ if (pri_create_trunkgroup(trunkgroup, dchannels)) {
+ ast_log(LOG_WARNING, "Unable to create trunk group %d with Primary D-channel %d at line %d of zapata.conf\n", trunkgroup, dchannels[0], v->lineno);
+ } else
+ ast_verb(2, "Created trunk group %d with Primary D-channel %d and %d backup%s\n", trunkgroup, dchannels[0], i - 1, (i == 1) ? "" : "s");
+ } else
+ ast_log(LOG_WARNING, "Trunk group %d lacks any valid D-channels at line %d of zapata.conf\n", trunkgroup, v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Trunk group %d lacks a primary D-channel at line %d of zapata.conf\n", trunkgroup, v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Trunk group identifier must be a positive integer at line %d of zapata.conf\n", v->lineno);
+ } else if (!strcasecmp(v->name, "spanmap")) {
+ spanno = atoi(v->value);
+ if (spanno > 0) {
+ if ((c = strchr(v->value, ','))) {
+ trunkgroup = atoi(c + 1);
+ if (trunkgroup > 0) {
+ if ((c = strchr(c + 1, ',')))
+ logicalspan = atoi(c + 1);
+ else
+ logicalspan = 0;
+ if (logicalspan >= 0) {
+ if (pri_create_spanmap(spanno - 1, trunkgroup, logicalspan)) {
+ ast_log(LOG_WARNING, "Failed to map span %d to trunk group %d (logical span %d)\n", spanno, trunkgroup, logicalspan);
+ } else
+ ast_verb(2, "Mapped span %d to trunk group %d (logical span %d)\n", spanno, trunkgroup, logicalspan);
+ } else
+ ast_log(LOG_WARNING, "Logical span must be a postive number, or '0' (for unspecified) at line %d of zapata.conf\n", v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Trunk group must be a postive number at line %d of zapata.conf\n", v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Missing trunk group for span map at line %d of zapata.conf\n", v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Span number must be a postive integer at line %d of zapata.conf\n", v->lineno);
+ } else {
+ ast_log(LOG_NOTICE, "Ignoring unknown keyword '%s' in trunkgroups\n", v->name);
+ }
+ v = v->next;
+ }
+ }
+#endif
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ mwimonitornotify[0] = '\0';
+
+ v = ast_variable_browse(cfg, "channels");
+ res = process_zap(&base_conf, v, reload, 0);
+ ast_mutex_unlock(&iflock);
+ ast_config_destroy(cfg);
+ if (res)
+ return res;
+ if (ucfg) {
+ char *cat;
+ const char *chans;
+ process_zap(&base_conf, ast_variable_browse(ucfg, "general"), 1, 1);
+ for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
+ if (!strcasecmp(cat, "general"))
+ continue;
+ chans = ast_variable_retrieve(ucfg, cat, "zapchan");
+ if (!ast_strlen_zero(chans)) {
+ if (memcpy(&conf, &base_conf, sizeof(conf)) == NULL) {
+ ast_log(LOG_ERROR, "Not enough memory for conf copy\n");
+ ast_config_destroy(ucfg);
+ return -1;
+ }
+ process_zap(&conf, ast_variable_browse(ucfg, cat), reload, 0);
+ }
+ }
+ ast_config_destroy(ucfg);
+ }
+#ifdef HAVE_PRI
+ if (!reload) {
+ int x;
+ for (x = 0; x < NUM_SPANS; x++) {
+ if (pris[x].pvts[0]) {
+ if (start_pri(pris + x)) {
+ ast_log(LOG_ERROR, "Unable to start D-channel on span %d\n", x + 1);
+ return -1;
+ } else
+ ast_verb(2, "Starting D-Channel on span %d\n", x + 1);
+ }
+ }
+ }
+#endif
+#ifdef HAVE_SS7
+ if (!reload) {
+ int x;
+ for (x = 0; x < NUM_SPANS; x++) {
+ if (linksets[x].ss7) {
+ if (ast_pthread_create(&linksets[x].master, NULL, ss7_linkset, &linksets[x])) {
+ ast_log(LOG_ERROR, "Unable to start SS7 linkset on span %d\n", x + 1);
+ return -1;
+ } else
+ ast_verb(2, "Starting SS7 linkset on span %d\n", x + 1);
+ }
+ }
+ }
+#endif
+ /* And start the monitor for the first time */
+ restart_monitor();
+ return 0;
+}
+
+static int load_module(void)
+{
+ int res;
+#if defined(HAVE_PRI) || defined(HAVE_SS7)
+ int y, i;
+#endif
+
+#ifdef HAVE_PRI
+ memset(pris, 0, sizeof(pris));
+ for (y = 0; y < NUM_SPANS; y++) {
+ ast_mutex_init(&pris[y].lock);
+ pris[y].offset = -1;
+ pris[y].master = AST_PTHREADT_NULL;
+ for (i = 0; i < NUM_DCHANS; i++)
+ pris[y].fds[i] = -1;
+ }
+ pri_set_error(zt_pri_error);
+ pri_set_message(zt_pri_message);
+ ast_register_application(zap_send_keypad_facility_app, zap_send_keypad_facility_exec,
+ zap_send_keypad_facility_synopsis, zap_send_keypad_facility_descrip);
+#endif
+#ifdef HAVE_SS7
+ memset(linksets, 0, sizeof(linksets));
+ for (y = 0; y < NUM_SPANS; y++) {
+ ast_mutex_init(&linksets[y].lock);
+ linksets[y].master = AST_PTHREADT_NULL;
+ for (i = 0; i < NUM_DCHANS; i++)
+ linksets[y].fds[i] = -1;
+ }
+ ss7_set_error(zt_ss7_error);
+ ss7_set_message(zt_ss7_message);
+#endif /* HAVE_SS7 */
+ res = setup_zap(0);
+ /* Make sure we can register our Zap channel type */
+ if (res)
+ return AST_MODULE_LOAD_DECLINE;
+ if (ast_channel_register(&zap_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'Zap'\n");
+ __unload_module();
+ return AST_MODULE_LOAD_FAILURE;
+ }
+#ifdef HAVE_PRI
+ ast_string_field_init(&inuse, 16);
+ ast_string_field_set(&inuse, name, "GR-303InUse");
+ ast_cli_register_multiple(zap_pri_cli, sizeof(zap_pri_cli) / sizeof(struct ast_cli_entry));
+#endif
+#ifdef HAVE_SS7
+ ast_cli_register_multiple(zap_ss7_cli, sizeof(zap_ss7_cli) / sizeof(zap_ss7_cli[0]));
+#endif
+
+ ast_cli_register_multiple(zap_cli, sizeof(zap_cli) / sizeof(struct ast_cli_entry));
+
+ memset(round_robin, 0, sizeof(round_robin));
+ ast_manager_register( "ZapTransfer", 0, action_transfer, "Transfer Zap Channel" );
+ ast_manager_register( "ZapHangup", 0, action_transferhangup, "Hangup Zap Channel" );
+ ast_manager_register( "ZapDialOffhook", 0, action_zapdialoffhook, "Dial over Zap channel while offhook" );
+ ast_manager_register( "ZapDNDon", 0, action_zapdndon, "Toggle Zap channel Do Not Disturb status ON" );
+ ast_manager_register( "ZapDNDoff", 0, action_zapdndoff, "Toggle Zap channel Do Not Disturb status OFF" );
+ ast_manager_register("ZapShowChannels", 0, action_zapshowchannels, "Show status zapata channels");
+ ast_manager_register("ZapRestart", 0, action_zaprestart, "Fully Restart Zaptel channels (terminates calls)");
+
+ return res;
+}
+
+static int zt_sendtext(struct ast_channel *c, const char *text)
+{
+#define END_SILENCE_LEN 400
+#define HEADER_MS 50
+#define TRAILER_MS 5
+#define HEADER_LEN ((HEADER_MS + TRAILER_MS) * 8)
+#define ASCII_BYTES_PER_CHAR 80
+
+ unsigned char *buf,*mybuf;
+ struct zt_pvt *p = c->tech_pvt;
+ struct pollfd fds[1];
+ int size,res,fd,len,x;
+ int bytes=0;
+ /* Initial carrier (imaginary) */
+ float cr = 1.0;
+ float ci = 0.0;
+ float scont = 0.0;
+ int index;
+
+ index = zt_get_index(c, p, 0);
+ if (index < 0) {
+ ast_log(LOG_WARNING, "Huh? I don't exist?\n");
+ return -1;
+ }
+ if (!text[0]) return(0); /* if nothing to send, dont */
+ if ((!p->tdd) && (!p->mate)) return(0); /* if not in TDD mode, just return */
+ if (p->mate)
+ buf = ast_malloc(((strlen(text) + 1) * ASCII_BYTES_PER_CHAR) + END_SILENCE_LEN + HEADER_LEN);
+ else
+ buf = ast_malloc(((strlen(text) + 1) * TDD_BYTES_PER_CHAR) + END_SILENCE_LEN);
+ if (!buf)
+ return -1;
+ mybuf = buf;
+ if (p->mate) {
+ int codec = AST_LAW(p);
+ for (x = 0; x < HEADER_MS; x++) { /* 50 ms of Mark */
+ PUT_CLID_MARKMS;
+ }
+ /* Put actual message */
+ for (x = 0; text[x]; x++) {
+ PUT_CLID(text[x]);
+ }
+ for (x = 0; x < TRAILER_MS; x++) { /* 5 ms of Mark */
+ PUT_CLID_MARKMS;
+ }
+ len = bytes;
+ buf = mybuf;
+ } else {
+ len = tdd_generate(p->tdd, buf, text);
+ if (len < 1) {
+ ast_log(LOG_ERROR, "TDD generate (len %d) failed!!\n", (int)strlen(text));
+ ast_free(mybuf);
+ return -1;
+ }
+ }
+ memset(buf + len, 0x7f, END_SILENCE_LEN);
+ len += END_SILENCE_LEN;
+ fd = p->subs[index].zfd;
+ while (len) {
+ if (ast_check_hangup(c)) {
+ ast_free(mybuf);
+ return -1;
+ }
+ size = len;
+ if (size > READ_SIZE)
+ size = READ_SIZE;
+ fds[0].fd = fd;
+ fds[0].events = POLLOUT | POLLPRI;
+ fds[0].revents = 0;
+ res = poll(fds, 1, -1);
+ if (!res) {
+ ast_debug(1, "poll (for write) ret. 0 on channel %d\n", p->channel);
+ continue;
+ }
+ /* if got exception */
+ if (fds[0].revents & POLLPRI) {
+ ast_free(mybuf);
+ return -1;
+ }
+ if (!(fds[0].revents & POLLOUT)) {
+ ast_debug(1, "write fd not ready on channel %d\n", p->channel);
+ continue;
+ }
+ res = write(fd, buf, size);
+ if (res != size) {
+ if (res == -1) {
+ ast_free(mybuf);
+ return -1;
+ }
+ ast_debug(1, "Write returned %d (%s) on channel %d\n", res, strerror(errno), p->channel);
+ break;
+ }
+ len -= size;
+ buf += size;
+ }
+ ast_free(mybuf);
+ return(0);
+}
+
+
+static int reload(void)
+{
+ int res = 0;
+
+ res = setup_zap(1);
+ if (res) {
+ ast_log(LOG_WARNING, "Reload of chan_zap.so is unsuccessful!\n");
+ return -1;
+ }
+ return 0;
+}
+
+/* This is a workaround so that menuselect displays a proper description
+ * AST_MODULE_INFO(, , "Zapata Telephony"
+ */
+
+#ifdef ZAPATA_PRI
+#define tdesc "Zapata Telephony w/PRI"
+#else
+#define tdesc "Zapata Telephony"
+#endif
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, tdesc,
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
+
+
diff --git a/trunk/channels/console_board.c b/trunk/channels/console_board.c
new file mode 100644
index 000000000..eafccc391
--- /dev/null
+++ b/trunk/channels/console_board.c
@@ -0,0 +1,329 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright 2007-2008, Marta Carbone, Luigi Rizzo
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ *
+ * $Revision$
+ */
+
+/*
+ * Message board implementation.
+ *
+ * A message board is a region of the SDL screen where
+ * messages can be printed, like on a terminal window.
+ *
+ * At the moment we support fix-size font.
+ *
+ * The text is stored in a buffer
+ * of fixed size (rows and cols). A portion of the buffer is
+ * visible on the screen, and the visible window can be moved up and
+ * down by dragging (not yet!)
+ *
+ * TODO: font dynamic allocation
+ *
+ * The region where the text is displayed on the screen is defined
+ * as keypad element, (the name is defined in the `region' variable
+ * so the board geometry can be read from the skin or from the
+ * configuration file).
+ */
+
+#include "asterisk.h" /* ast_strdupa */
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+#include "asterisk/utils.h" /* ast_strdupa */
+#include "console_video.h" /* ast_strdupa */
+
+#ifdef HAVE_SDL /* we only use this code if SDL is available */
+#include <SDL/SDL.h>
+
+/* Fonts characterization. XXX should be read from the file */
+#define FONT_H 20 /* char height, pixels */
+#define FONT_W 9 /* char width, pixels */
+
+struct board {
+ int kb_output; /* identity of the board */
+ /* pointer to the destination surface (on the keypad window) */
+ SDL_Surface *screen; /* the main screen */
+ SDL_Rect *p_rect; /* where to write on the main screen */
+ SDL_Surface *blank; /* original content of the window */
+
+ int v_h; /* virtual text height, in lines */
+ int v_w; /* virtual text width, in lines (probably same as p_w) */
+ int p_h; /* physical (displayed) text height, in lines
+ * XXX p_h * FONT_H = pixel_height */
+ int p_w; /* physical (displayed) text width, in characters
+ * XXX p_w * FONT_W = pixel_width */
+
+ int cur_col; /* print position (free character) on the last line */
+ int cur_line; /* first (or last ?) virtual line displayed,
+ * 0 is the line at the bottom, 1 is the one above,...
+ */
+
+ SDL_Surface *font; /* points to a surface in the gui structure */
+ SDL_Rect *font_rects; /* pointer to the font rects */
+ char *text;
+ /* text buffer, v_h * v_w char.
+ * We make sure the buffer is always full,
+ * print on some position on the last line,
+ * and scroll up when appending new text
+ */
+};
+
+/*! \brief Initialize the board.
+ * return 0 on success, 1 on error
+ * TODO, if this is done at reload time,
+ * free resources before allocate new ones
+ * TODO: resource deallocation in case of error.
+ * TODO: move the font load at gui_initialization
+ * TODO: deallocation of the message history
+ */
+struct board *board_setup(SDL_Surface *screen, SDL_Rect *dest,
+ SDL_Surface *font, SDL_Rect *font_rects);
+struct board *board_setup(SDL_Surface *screen, SDL_Rect *dest,
+ SDL_Surface *font, SDL_Rect *font_rects)
+{
+ struct board *b = ast_calloc(1, sizeof (*b));
+ SDL_Rect br;
+
+ if (b == NULL)
+ return NULL;
+ /* font, points to the gui structure */
+ b->font = font;
+ b->font_rects = font_rects;
+
+ /* Destination rectangle on the screen - reference is the whole screen */
+ b->p_rect = dest;
+ b->screen = screen;
+
+ /* compute physical sizes */
+ b->p_h = b->p_rect->h/FONT_H;
+ b->p_w = b->p_rect->w/FONT_W;
+
+ /* virtual sizes */
+ b->v_h = b->p_h * 10; /* XXX 10 times larger */
+ b->v_w = b->p_w; /* same width */
+
+ /* the rectangle we actually use */
+ br.h = b->p_h * FONT_H; /* pixel sizes of the background */
+ br.w = b->p_w * FONT_W;
+ br.x = br.y = 0;
+
+ /* allocate a buffer for the text */
+ b->text = ast_calloc(b->v_w*b->v_h + 1, 1);
+ if (b->text == NULL) {
+ ast_log(LOG_WARNING, "Unable to allocate board history memory.\n");
+ ast_free(b);
+ return NULL;
+ }
+ memset(b->text, ' ', b->v_w * b->v_h); /* fill with spaces */
+
+ /* make a copy of the original rectangle, for cleaning up */
+ b->blank = SDL_CreateRGBSurface(screen->flags, br.w, br.h,
+ screen->format->BitsPerPixel,
+ screen->format->Rmask, screen->format->Gmask,
+ screen->format->Bmask, screen->format->Amask);
+
+ if (b->blank == NULL) {
+ ast_log(LOG_WARNING, "Unable to allocate board virtual screen: %s\n",
+ SDL_GetError());
+ ast_free(b->text);
+ ast_free(b);
+ return NULL;
+ }
+ SDL_BlitSurface(screen, b->p_rect, b->blank, &br);
+
+ /* Set color key, if not alpha channel present */
+ //colorkey = SDL_MapRGB(b->board_surface->format, 0, 0, 0);
+ //SDL_SetColorKey(b->board_surface, SDL_SRCCOLORKEY, colorkey);
+
+ b->cur_col = 0; /* current print column */
+ b->cur_line = 0; /* last line displayed */
+
+ ast_log(LOG_WARNING, "Message board %dx%d@%d,%d successfully initialized\n",
+ b->p_rect->w, b->p_rect->h,
+ b->p_rect->x, b->p_rect->y);
+ return b;
+}
+
+/* Render the text on the board surface.
+ * The first line to render is the one at v_h - p_h - cur_line,
+ * the size is p_h * p_w.
+ * XXX we assume here that p_w = v_w.
+ */
+static void render_board(struct board *b)
+{
+ int first_row = b->v_h - b->p_h - b->cur_line;
+ int first_char = b->v_w * first_row;
+ int last_char = first_char + b->p_h * b->v_w;
+ int i, col;
+ SDL_Rect dst;
+
+ /* top left char on the physical surface */
+ dst.w = FONT_W;
+ dst.h = FONT_H;
+ dst.x = b->p_rect->x;
+ dst.y = b->p_rect->y;
+
+
+ /* clean the surface board */
+ SDL_BlitSurface(b->blank, NULL, b->screen, b->p_rect);
+
+ /* blit all characters */
+ for (i = first_char, col = 0; i < last_char; i++) {
+ int c = b->text[i] - 32; /* XXX first 32 chars are not printable */
+ if (c < 0) /* buffer terminator or anything else is a blank */
+ c = 0;
+ SDL_BlitSurface(b->font, &b->font_rects[c], b->screen, &dst);
+ /* point dst to next char position */
+ dst.x += dst.w;
+ col++;
+ if (col >= b->v_w) { /* next row */
+ dst.x = b->p_rect->x;
+ dst.y += dst.h;
+ col = 0;
+ }
+ }
+ SDL_UpdateRects(b->screen, 1, b->p_rect); /* Update the screen */
+}
+
+void move_message_board(struct board *b, int dy)
+{
+ int cur = b->cur_line + dy;
+ if (cur < 0)
+ cur = 0;
+ else if (cur >= b->v_h - b->p_h)
+ cur = b->v_h - b->p_h - 1;
+ b->cur_line = cur;
+ render_board(b);
+}
+
+/* return the content of a board */
+const char *read_message(const struct board *b)
+{
+ return b->text;
+}
+
+int reset_board(struct board *b)
+{
+ memset(b->text, ' ', b->v_w * b->v_h); /* fill with spaces */
+ b->cur_col = 0;
+ b->cur_line = 0;
+ render_board(b);
+ return 0;
+}
+/* Store the message on the history board
+ * and blit on screen if required.
+ * XXX now easy. only regular chars
+ */
+int print_message(struct board *b, const char *s)
+{
+ int i, l, row, col;
+ char *dst;
+
+ if (ast_strlen_zero(s))
+ return 0;
+
+ l = strlen(s);
+ row = 0;
+ col = b->cur_col;
+ /* First, only check how much space we need.
+ * Starting from the current print position, we move
+ * it forward and down (if necessary) according to input
+ * characters (including newlines, tabs, backspaces...).
+ * At the end, row tells us how many rows to scroll, and
+ * col (ignored) is the final print position.
+ */
+ for (i = 0; i < l; i++) {
+ switch (s[i]) {
+ case '\r':
+ col = 0;
+ break;
+ case '\n':
+ col = 0;
+ row++;
+ break;
+ case '\b':
+ if (col > 0)
+ col--;
+ break;
+ default:
+ if (s[i] < 32) /* signed, so take up to 127 */
+ break;
+ col++;
+ if (col >= b->v_w) {
+ col -= b->v_w;
+ row++;
+ }
+ break;
+ }
+ }
+ /* scroll the text window */
+ if (row > 0) { /* need to scroll by 'row' rows */
+ memcpy(b->text, b->text + row * b->v_w, b->v_w * (b->v_h - row));
+ /* clean the destination area */
+ dst = b->text + b->v_w * (b->v_h - row - 1) + b->cur_col;
+ memset(dst, ' ', b->v_w - b->cur_col + b->v_w * row);
+ }
+ /* now do the actual printing. The print position is 'row' lines up
+ * from the bottom of the buffer, start at the same 'cur_col' as before.
+ * dst points to the beginning of the current line.
+ */
+ dst = b->text + b->v_w * (b->v_h - row - 1); /* start of current line */
+ col = b->cur_col;
+ for (i = 0; i < l; i++) {
+ switch (s[i]) {
+ case '\r':
+ col = 0;
+ break;
+ case '\n': /* move to beginning of next line */
+ dst[col] = '\0'; /* mark the rest of the line as empty */
+ col = 0;
+ dst += b->v_w;
+ break;
+ case '\b': /* one char back */
+ if (col > 0)
+ col--;
+ dst[col] = ' '; /* delete current char */
+ break;
+ default:
+ if (s[i] < 32) /* signed, so take up to 127 */
+ break; /* non printable */
+ dst[col] = s[i]; /* store character */
+ col++;
+ if (col >= b->v_w) {
+ col -= b->v_w;
+ dst += b->v_w;
+ }
+ break;
+ }
+ }
+ dst[col] = '\0'; /* the current position is empty */
+ b->cur_col = col;
+ /* everything is printed now, must do the rendering */
+ render_board(b);
+ return 1;
+}
+
+#if 0
+/*! \brief refresh the screen, and also grab a bunch of events.
+ */
+static int scroll_message(...)
+{
+if moving up, scroll text up;
+ if (gui->message_board.screen_cur > 0)
+ gui->message_board.screen_cur--;
+otherwise scroll text down.
+ if ((b->screen_cur + b->p_line) < b->board_next) {
+ gui->message_board.screen_cur++;
+#endif /* notyet */
+
+#endif /* HAVE_SDL */
diff --git a/trunk/channels/console_gui.c b/trunk/channels/console_gui.c
new file mode 100644
index 000000000..aa2f2cf95
--- /dev/null
+++ b/trunk/channels/console_gui.c
@@ -0,0 +1,1036 @@
+/*
+ * GUI for console video.
+ * The routines here are in charge of loading the keypad and handling events.
+ * $Revision$
+ */
+
+/*
+ * GUI layout, structure and management
+
+For the GUI we use SDL to create a large surface (gui->screen)
+containing tree sections: remote video on the left, local video
+on the right, and the keypad with all controls and text windows
+in the center.
+The central section is built using an image for the skin, fonts and
+other GUI elements. Comments embedded in the image to indicate to
+what function each area is mapped to.
+
+Mouse and keyboard events are detected on the whole surface, and
+handled differently according to their location:
+
+- drag on the local video window are used to move the captured
+ area (in the case of X11 grabber) or the picture-in-picture
+ location (in case of camera included on the X11 grab).
+- click on the keypad are mapped to the corresponding key;
+- drag on some keypad areas (sliders etc.) are mapped to the
+ corresponding functions;
+- keystrokes are used as keypad functions, or as text input
+ if we are in text-input mode.
+
+Configuration options control the appeareance of the gui:
+
+ keypad = /tmp/phone.jpg ; the skin
+ keypad_font = /tmp/font.ttf ; the font to use for output (XXX deprecated)
+
+ *
+ */
+
+#include "asterisk.h"
+#include "console_video.h"
+#include "asterisk/lock.h"
+#include "asterisk/frame.h"
+#include "asterisk/utils.h" /* ast_calloc and ast_realloc */
+#include <math.h> /* sqrt */
+
+/* We use 3 'windows' in the GUI */
+enum { WIN_LOCAL, WIN_REMOTE, WIN_KEYPAD, WIN_MAX };
+
+#ifndef HAVE_SDL /* stubs if we don't have any sdl */
+static void show_frame(struct video_desc *env, int out) {}
+static void sdl_setup(struct video_desc *env) {}
+static struct gui_info *cleanup_sdl(struct gui_info *gui) { return NULL; }
+static void eventhandler(struct video_desc *env, const char *caption) {}
+static int keypad_cfg_read(struct gui_info *gui, const char *val) { return 0; }
+
+#else /* HAVE_SDL, the real rendering code */
+
+#include <SDL/SDL.h>
+#ifdef HAVE_SDL_IMAGE
+#include <SDL/SDL_image.h> /* for loading images */
+#endif
+
+enum kp_type { KP_NONE, KP_RECT, KP_CIRCLE };
+struct keypad_entry {
+ int c; /* corresponding character */
+ int x0, y0, x1, y1, h; /* arguments */
+ enum kp_type type;
+};
+
+/* our representation of a displayed window. SDL can only do one main
+ * window so we map everything within that one
+ */
+struct display_window {
+ SDL_Overlay *bmp;
+ SDL_Rect rect; /* location of the window */
+};
+
+struct gui_info {
+ enum kb_output kb_output; /* where the keyboard output goes */
+ struct drag_info drag; /* info on the window are we dragging */
+ /* support for display. */
+ SDL_Surface *screen; /* the main window */
+
+ int outfd; /* fd for output */
+ SDL_Surface *keypad; /* the skin for the keypad */
+ SDL_Rect kp_rect; /* portion of the skin to display - default all */
+ SDL_Surface *font; /* font to be used */
+ SDL_Rect font_rects[96]; /* only printable chars */
+
+ /* each board has two rectangles,
+ * [0] is the geometry relative to the keypad,
+ * [1] is the geometry relative to the whole screen
+ */
+ SDL_Rect kp_msg[2]; /* incoming msg, relative to kpad */
+ struct board *bd_msg;
+
+ SDL_Rect kp_edit[2]; /* edit user input */
+ struct board *bd_edit;
+
+ SDL_Rect kp_dialed[2]; /* dialed number */
+ struct board *bd_dialed;
+
+ /* variable-size array mapping keypad regions to functions */
+ int kp_size, kp_used;
+ struct keypad_entry *kp;
+
+ struct display_window win[WIN_MAX];
+};
+
+/*! \brief free the resources in struct gui_info and the descriptor itself.
+ * Return NULL so we can assign the value back to the descriptor in case.
+ */
+static struct gui_info *cleanup_sdl(struct gui_info *gui)
+{
+ int i;
+
+ if (gui == NULL)
+ return NULL;
+
+ /* unload font file */
+ if (gui->font) {
+ SDL_FreeSurface(gui->font);
+ gui->font = NULL;
+ }
+
+ if (gui->outfd > -1)
+ close(gui->outfd);
+ if (gui->keypad)
+ SDL_FreeSurface(gui->keypad);
+ gui->keypad = NULL;
+ if (gui->kp)
+ ast_free(gui->kp);
+
+ /* uninitialize the SDL environment */
+ for (i = 0; i < WIN_MAX; i++) {
+ if (gui->win[i].bmp)
+ SDL_FreeYUVOverlay(gui->win[i].bmp);
+ }
+ bzero(gui, sizeof(gui));
+ ast_free(gui);
+ SDL_Quit();
+ return NULL;
+}
+
+/*
+ * Display video frames (from local or remote stream) using the SDL library.
+ * - Set the video mode to use the resolution specified by the codec context
+ * - Create a YUV Overlay to copy the frame into it;
+ * - After the frame is copied into the overlay, display it
+ *
+ * The size is taken from the configuration.
+ *
+ * 'out' is 0 for remote video, 1 for the local video
+ */
+static void show_frame(struct video_desc *env, int out)
+{
+ AVPicture *p_in, p_out;
+ struct fbuf_t *b_in, *b_out;
+ SDL_Overlay *bmp;
+ struct gui_info *gui = env->gui;
+
+ if (!gui)
+ return;
+
+ if (out == WIN_LOCAL) { /* webcam/x11 to sdl */
+ b_in = &env->enc_in;
+ b_out = &env->loc_dpy;
+ p_in = NULL;
+ } else {
+ /* copy input format from the decoding context */
+ AVCodecContext *c;
+ if (env->in == NULL) /* XXX should not happen - decoder not ready */
+ return;
+ c = env->in->dec_ctx;
+ b_in = &env->in->dec_out;
+ b_in->pix_fmt = c->pix_fmt;
+ b_in->w = c->width;
+ b_in->h = c->height;
+
+ b_out = &env->rem_dpy;
+ p_in = (AVPicture *)env->in->d_frame;
+ }
+ bmp = gui->win[out].bmp;
+ SDL_LockYUVOverlay(bmp);
+ /* output picture info - this is sdl, YUV420P */
+ bzero(&p_out, sizeof(p_out));
+ p_out.data[0] = bmp->pixels[0];
+ p_out.data[1] = bmp->pixels[1];
+ p_out.data[2] = bmp->pixels[2];
+ p_out.linesize[0] = bmp->pitches[0];
+ p_out.linesize[1] = bmp->pitches[1];
+ p_out.linesize[2] = bmp->pitches[2];
+
+ my_scale(b_in, p_in, b_out, &p_out);
+
+ /* lock to protect access to Xlib by different threads. */
+ SDL_DisplayYUVOverlay(bmp, &gui->win[out].rect);
+ SDL_UnlockYUVOverlay(bmp);
+}
+
+/*
+ * Identifiers for regions of the main window.
+ * Values between 0 and 127 correspond to ASCII characters.
+ * The corresponding strings to be used in the skin comment section
+ * are defined in gui_key_map.
+ */
+enum skin_area {
+ /* answer/close functions */
+ KEY_PICK_UP = 128,
+ KEY_HANG_UP = 129,
+
+ KEY_MUTE = 130,
+ KEY_AUTOANSWER = 131,
+ KEY_SENDVIDEO = 132,
+ KEY_LOCALVIDEO = 133,
+ KEY_REMOTEVIDEO = 134,
+ KEY_FLASH = 136,
+
+ /* sensitive areas for the various text windows */
+ KEY_MESSAGEBOARD = 140,
+ KEY_DIALEDBOARD = 141,
+ KEY_EDITBOARD = 142,
+
+ KEY_GUI_CLOSE = 199, /* close gui */
+ /* regions of the skin - displayed area, fonts, etc.
+ * XXX NOTE these are not sensitive areas.
+ */
+ KEY_KEYPAD = 200, /* the keypad - default to the whole image */
+ KEY_FONT = 201, /* the font. Maybe not really useful */
+ KEY_MESSAGE = 202, /* area for incoming messages */
+ KEY_DIALED = 203, /* area for dialed numbers */
+ KEY_EDIT = 204, /* area for editing user input */
+
+ /* areas outside the keypad - simulated */
+ KEY_OUT_OF_KEYPAD = 241,
+ KEY_REM_DPY = 242,
+ KEY_LOC_DPY = 243,
+ KEY_RESET = 253, /* the 'reset' keyword */
+ KEY_NONE = 254, /* invalid area */
+ KEY_DIGIT_BACKGROUND = 255, /* other areas within the keypad */
+};
+
+/*
+ * Handlers for the various keypad functions
+ */
+
+/* accumulate digits, possibly call dial if in connected mode */
+static void keypad_digit(struct video_desc *env, int digit)
+{
+ if (env->owner) { /* we have a call, send the digit */
+ struct ast_frame f = { AST_FRAME_DTMF, 0 };
+
+ f.subclass = digit;
+ ast_queue_frame(env->owner, &f);
+ } else { /* no call, accumulate digits */
+ char buf[2] = { digit, '\0' };
+ if (env->gui->bd_msg) /* XXX not strictly necessary ... */
+ print_message(env->gui->bd_msg, buf);
+ }
+}
+
+/* function used to toggle on/off the status of some variables */
+static char *keypad_toggle(struct video_desc *env, int index)
+{
+ ast_log(LOG_WARNING, "keypad_toggle(%i) called\n", index);
+
+ switch (index) {
+ case KEY_SENDVIDEO:
+ env->out.sendvideo = !env->out.sendvideo;
+ break;
+#ifdef notyet
+ case KEY_MUTE: {
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ o->mute = !o->mute;
+ }
+ break;
+ case KEY_AUTOANSWER: {
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ o->autoanswer = !o->autoanswer;
+ }
+ break;
+#endif
+ }
+ return NULL;
+}
+
+char *console_do_answer(int fd);
+/*
+ * Function called when the pick up button is pressed
+ * perform actions according the channel status:
+ *
+ * - if no one is calling us and no digits was pressed,
+ * the operation have no effects,
+ * - if someone is calling us we answer to the call.
+ * - if we have no call in progress and we pressed some
+ * digit, send the digit to the console.
+ */
+static void keypad_pick_up(struct video_desc *env)
+{
+ struct gui_info *gui = env->gui;
+
+ ast_log(LOG_WARNING, "keypad_pick_up called\n");
+
+ if (env->owner) { /* someone is calling us, just answer */
+ ast_cli_command(gui->outfd, "console answer");
+ } else { /* we have someone to call */
+ char buf[160];
+ const char *who = ast_skip_blanks(read_message(gui->bd_msg));
+ buf[sizeof(buf) - 1] = '\0';
+ snprintf(buf, sizeof(buf) - 1, "console dial %s", who);
+ ast_log(LOG_WARNING, "doing <%s>\n", buf);
+ print_message(gui->bd_dialed, "\n");
+ print_message(gui->bd_dialed, who);
+ reset_board(gui->bd_msg);
+ ast_cli_command(gui->outfd, buf);
+ }
+}
+
+#if 0 /* still unused */
+/*
+ * As an alternative to SDL_TTF, we can simply load the font from
+ * an image and blit characters on the background of the GUI.
+ *
+ * To generate a font we can use the 'fly' command with the
+ * following script (3 lines with 32 chars each)
+
+size 320,64
+name font.png
+transparent 0,0,0
+string 255,255,255, 0, 0,giant, !"#$%&'()*+,-./0123456789:;<=>?
+string 255,255,255, 0,20,giant,@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
+string 255,255,255, 0,40,giant,`abcdefghijklmnopqrstuvwxyz{|}~
+end
+
+ */
+
+/* Print given text on the gui */
+static int gui_output(struct video_desc *env, const char *text)
+{
+ return 1; /* error, not supported */
+}
+#endif
+
+static int video_geom(struct fbuf_t *b, const char *s);
+static void sdl_setup(struct video_desc *env);
+static int kp_match_area(const struct keypad_entry *e, int x, int y);
+
+static void set_drag(struct drag_info *drag, int x, int y, enum drag_window win)
+{
+ drag->x_start = x;
+ drag->y_start = y;
+ drag->drag_window = win;
+}
+
+/*
+ * Handle SDL_MOUSEBUTTONDOWN type, finding the palette
+ * index value and calling the right callback.
+ *
+ * x, y are referred to the upper left corner of the main SDL window.
+ */
+static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button)
+{
+ uint8_t index = KEY_OUT_OF_KEYPAD; /* the key or region of the display we clicked on */
+ struct gui_info *gui = env->gui;
+
+#if 0
+ ast_log(LOG_WARNING, "event %d %d have %d/%d regions at %p\n",
+ button.x, button.y, gui->kp_used, gui->kp_size, gui->kp);
+#endif
+ /* for each mousedown we end previous drag */
+ gui->drag.drag_window = DRAG_NONE;
+
+ /* define keypad boundary */
+ if (button.x < env->rem_dpy.w)
+ index = KEY_REM_DPY; /* click on remote video */
+ else if (button.x > env->rem_dpy.w + gui->keypad->w)
+ index = KEY_LOC_DPY; /* click on local video */
+ else if (button.y > gui->keypad->h)
+ index = KEY_OUT_OF_KEYPAD; /* click outside the keypad */
+ else if (gui->kp) {
+ int i;
+ for (i = 0; i < gui->kp_used; i++) {
+ if (kp_match_area(&gui->kp[i], button.x - env->rem_dpy.w, button.y)) {
+ index = gui->kp[i].c;
+ break;
+ }
+ }
+ }
+
+ /* exec the function */
+ if (index < 128) { /* surely clicked on the keypad, don't care which key */
+ keypad_digit(env, index);
+ return;
+ }
+ switch (index) {
+ /* answer/close function */
+ case KEY_PICK_UP:
+ keypad_pick_up(env);
+ break;
+ case KEY_HANG_UP:
+ ast_cli_command(gui->outfd, "console hangup");
+ break;
+
+ /* other functions */
+ case KEY_MUTE:
+ case KEY_AUTOANSWER:
+ case KEY_SENDVIDEO:
+ keypad_toggle(env, index);
+ break;
+
+ case KEY_LOCALVIDEO:
+ break;
+ case KEY_REMOTEVIDEO:
+ break;
+
+ case KEY_MESSAGEBOARD:
+ if (button.button == SDL_BUTTON_LEFT)
+ set_drag(&gui->drag, button.x, button.y, DRAG_MESSAGE);
+ break;
+
+ /* press outside the keypad. right increases size, center decreases, left drags */
+ case KEY_LOC_DPY:
+ case KEY_REM_DPY:
+ if (button.button == SDL_BUTTON_LEFT) {
+ if (index == KEY_LOC_DPY)
+ set_drag(&gui->drag, button.x, button.y, DRAG_LOCAL);
+ break;
+ } else {
+ char buf[128];
+ struct fbuf_t *fb = index == KEY_LOC_DPY ? &env->loc_dpy : &env->rem_dpy;
+ sprintf(buf, "%c%dx%d", button.button == SDL_BUTTON_RIGHT ? '>' : '<',
+ fb->w, fb->h);
+ video_geom(fb, buf);
+ sdl_setup(env);
+ }
+ break;
+ case KEY_OUT_OF_KEYPAD:
+ break;
+
+ case KEY_DIGIT_BACKGROUND:
+ break;
+ default:
+ ast_log(LOG_WARNING, "function not yet defined %i\n", index);
+ }
+}
+
+/*
+ * Handle SDL_KEYDOWN type event, put the key pressed
+ * in the dial buffer or in the text-message buffer,
+ * depending on the text_mode variable value.
+ *
+ * key is the SDLKey structure corresponding to the key pressed.
+ * Note that SDL returns modifiers (ctrl, shift, alt) as independent
+ * information so the key itself is not enough and we need to
+ * use a translation table, below - one line per entry,
+ * plain, shift, ctrl, ... using the first char as key.
+ */
+static const char *us_kbd_map[] = {
+ "`~", "1!", "2@", "3#", "4$", "5%", "6^",
+ "7&", "8*", "9(", "0)", "-_", "=+", "[{",
+ "]}", "\\|", ";:", "'\"", ",<", ".>", "/?",
+ "jJ\n",
+ NULL
+};
+
+static char map_key(SDL_keysym *ks)
+{
+ const char *s, **p = us_kbd_map;
+ int c = ks->sym;
+
+ if (c == '\r') /* map cr into lf */
+ c = '\n';
+ if (c >= SDLK_NUMLOCK && c <= SDLK_COMPOSE)
+ return 0; /* only a modifier */
+ if (ks->mod == 0)
+ return c;
+ while ((s = *p) && s[0] != c)
+ p++;
+ if (s) { /* see if we have a modifier and a chance to use it */
+ int l = strlen(s), mod = 0;
+ if (l > 1)
+ mod |= (ks->mod & KMOD_SHIFT) ? 1 : 0;
+ if (l > 2 + mod)
+ mod |= (ks->mod & KMOD_CTRL) ? 2 : 0;
+ if (l > 4 + mod)
+ mod |= (ks->mod & KMOD_ALT) ? 4 : 0;
+ c = s[mod];
+ }
+ if (ks->mod & (KMOD_CAPS|KMOD_SHIFT) && c >= 'a' && c <='z')
+ c += 'A' - 'a';
+ return c;
+}
+
+static void handle_keyboard_input(struct video_desc *env, SDL_keysym *ks)
+{
+ char buf[2] = { map_key(ks), '\0' };
+ struct gui_info *gui = env->gui;
+ if (buf[0] == 0) /* modifier ? */
+ return;
+ switch (gui->kb_output) {
+ default:
+ break;
+ case KO_INPUT: /* to be completed */
+ break;
+ case KO_MESSAGE:
+ if (gui->bd_msg) {
+ print_message(gui->bd_msg, buf);
+ if (buf[0] == '\r' || buf[0] == '\n') {
+ keypad_pick_up(env);
+ }
+ }
+ break;
+
+ case KO_DIALED: /* to be completed */
+ break;
+ }
+
+ return;
+}
+
+static void grabber_move(struct video_out_desc *, int dx, int dy);
+
+int compute_drag(int *start, int end, int magnifier);
+int compute_drag(int *start, int end, int magnifier)
+{
+ int delta = end - *start;
+#define POLARITY -1
+ /* add a small quadratic term */
+ delta += delta * delta * (delta > 0 ? 1 : -1 )/100;
+ delta *= POLARITY * magnifier;
+#undef POLARITY
+ *start = end;
+ return delta;
+}
+
+/*
+ * I am seeing some kind of deadlock or stall around
+ * SDL_PumpEvents() while moving the window on a remote X server
+ * (both xfree-4.4.0 and xorg 7.2)
+ * and windowmaker. It is unclear what causes it.
+ */
+
+/*! \brief refresh the screen, and also grab a bunch of events.
+ */
+static void eventhandler(struct video_desc *env, const char *caption)
+{
+ struct gui_info *gui = env->gui;
+ struct drag_info *drag;
+#define N_EVENTS 32
+ int i, n;
+ SDL_Event ev[N_EVENTS];
+
+ if (!gui)
+ return;
+ drag = &gui->drag;
+ if (caption)
+ SDL_WM_SetCaption(caption, NULL);
+
+#define MY_EV (SDL_MOUSEBUTTONDOWN|SDL_KEYDOWN)
+ while ( (n = SDL_PeepEvents(ev, N_EVENTS, SDL_GETEVENT, SDL_ALLEVENTS)) > 0) {
+ for (i = 0; i < n; i++) {
+#if 0
+ ast_log(LOG_WARNING, "------ event %d at %d %d\n",
+ ev[i].type, ev[i].button.x, ev[i].button.y);
+#endif
+ switch (ev[i].type) {
+ case SDL_KEYDOWN:
+ handle_keyboard_input(env, &ev[i].key.keysym);
+ break;
+ case SDL_MOUSEMOTION:
+ case SDL_MOUSEBUTTONUP:
+ if (drag->drag_window == DRAG_LOCAL) {
+ /* move the capture source */
+ int dx = compute_drag(&drag->x_start, ev[i].motion.x, 3);
+ int dy = compute_drag(&drag->y_start, ev[i].motion.y, 3);
+ grabber_move(&env->out, dx, dy);
+ } else if (drag->drag_window == DRAG_MESSAGE) {
+ /* scroll up/down the window */
+ int dy = compute_drag(&drag->y_start, ev[i].motion.y, 1);
+ move_message_board(gui->bd_msg, dy);
+ }
+ if (ev[i].type == SDL_MOUSEBUTTONUP)
+ drag->drag_window = DRAG_NONE;
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ handle_mousedown(env, ev[i].button);
+ break;
+ }
+ }
+ }
+ if (1) {
+ struct timeval b, a = ast_tvnow();
+ int i;
+ //SDL_Lock_EventThread();
+ SDL_PumpEvents();
+ b = ast_tvnow();
+ i = ast_tvdiff_ms(b, a);
+ if (i > 3)
+ fprintf(stderr, "-------- SDL_PumpEvents took %dms\n", i);
+ //SDL_Unlock_EventThread();
+ }
+}
+
+static SDL_Surface *load_image(const char *file)
+{
+ SDL_Surface *temp;
+
+#ifdef HAVE_SDL_IMAGE
+ temp = IMG_Load(file);
+#else
+ temp = SDL_LoadBMP(file);
+#endif
+ if (temp == NULL)
+ fprintf(stderr, "Unable to load image %s: %s\n",
+ file, SDL_GetError());
+ return temp;
+}
+
+static void keypad_setup(struct gui_info *gui, const char *kp_file);
+
+/* TODO: consistency checks, check for bpp, widht and height */
+/* Init the mask image used to grab the action. */
+static struct gui_info *gui_init(const char *keypad_file, const char *font)
+{
+ struct gui_info *gui = ast_calloc(1, sizeof(*gui));
+
+ if (gui == NULL)
+ return NULL;
+ /* initialize keypad status */
+ gui->kb_output = KO_MESSAGE; /* XXX temp */
+ gui->drag.drag_window = DRAG_NONE;
+ gui->outfd = -1;
+
+ keypad_setup(gui, keypad_file);
+ if (gui->keypad == NULL) /* no keypad, we are done */
+ return gui;
+ /* XXX load image */
+ if (!ast_strlen_zero(font)) {
+ int i;
+ SDL_Rect *r;
+
+ gui->font = load_image(font);
+ if (!gui->font) {
+ ast_log(LOG_WARNING, "Unable to load font %s, no output available\n", font);
+ goto error;
+ }
+ ast_log(LOG_WARNING, "Loaded font %s\n", font);
+ /* XXX hardwired constants - 3 rows of 32 chars */
+ r = gui->font_rects;
+#define FONT_H 20
+#define FONT_W 9
+ for (i = 0; i < 96; r++, i++) {
+ r->x = (i % 32 ) * FONT_W;
+ r->y = (i / 32 ) * FONT_H;
+ r->w = FONT_W;
+ r->h = FONT_H;
+ }
+ }
+
+ gui->outfd = open ("/dev/null", O_WRONLY); /* discard output, temporary */
+ if (gui->outfd < 0) {
+ ast_log(LOG_WARNING, "Unable output fd\n");
+ goto error;
+ }
+ return gui;
+
+error:
+ ast_free(gui);
+ return NULL;
+}
+
+/* setup an sdl overlay and associated info, return 0 on success, != 0 on error */
+static int set_win(SDL_Surface *screen, struct display_window *win, int fmt,
+ int w, int h, int x, int y)
+{
+ win->bmp = SDL_CreateYUVOverlay(w, h, fmt, screen);
+ if (win->bmp == NULL)
+ return -1; /* error */
+ win->rect.x = x;
+ win->rect.y = y;
+ win->rect.w = w;
+ win->rect.h = h;
+ return 0;
+}
+
+static int keypad_cfg_read(struct gui_info *gui, const char *val);
+
+static void keypad_setup(struct gui_info *gui, const char *kp_file)
+{
+ FILE *fd;
+ char buf[1024];
+ const char region[] = "region";
+ int reg_len = strlen(region);
+ int in_comment = 0;
+
+ if (gui->keypad)
+ return;
+ gui->keypad = load_image(kp_file);
+ if (!gui->keypad)
+ return;
+ /* now try to read the keymap from the file. */
+ fd = fopen(kp_file, "r");
+ if (fd == NULL) {
+ ast_log(LOG_WARNING, "fail to open %s\n", kp_file);
+ return;
+ }
+ /*
+ * If the keypad image has a comment field, try to read
+ * the button location from there. The block must start with
+ * a comment (or empty) line, and continue with entries like:
+ * region = token shape x0 y0 x1 y1 h
+ * ...
+ * (basically, lines have the same format as config file entries).
+ * You can add it to a jpeg file using wrjpgcom
+ */
+ while (fgets(buf, sizeof(buf), fd)) {
+ char *s;
+
+ if (!strstr(buf, region)) { /* no keyword yet */
+ if (!in_comment) /* still waiting for initial comment block */
+ continue;
+ else
+ break;
+ }
+ if (!in_comment) { /* first keyword, reset previous entries */
+ keypad_cfg_read(gui, "reset");
+ in_comment = 1;
+ }
+ s = ast_skip_blanks(buf);
+ ast_trim_blanks(s);
+ if (memcmp(s, region, reg_len))
+ break; /* keyword not found */
+ s = ast_skip_blanks(s + reg_len); /* space between token and '=' */
+ if (*s++ != '=') /* missing separator */
+ break;
+ if (*s == '>') /* skip '>' if present */
+ s++;
+ keypad_cfg_read(gui, ast_skip_blanks(s));
+ }
+ fclose(fd);
+}
+
+struct board *board_setup(SDL_Surface *screen, SDL_Rect *dest,
+ SDL_Surface *font, SDL_Rect *font_rects);
+
+/*! \brief initialize the boards we have in the keypad */
+static void init_board(struct gui_info *gui, struct board **dst, SDL_Rect *r, int dx, int dy)
+{
+ if (r[0].w == 0 || r[0].h == 0)
+ return; /* not available */
+ r[1] = r[0]; /* copy geometry */
+ r[1].x += dx; /* add offset of main window */
+ r[1].y += dy;
+ if (*dst == NULL) { /* initial call */
+ *dst = board_setup(gui->screen, &r[1], gui->font, gui->font_rects);
+ } else {
+ /* call a refresh */
+ }
+}
+
+/*! \brief [re]set the main sdl window, useful in case of resize.
+ * We can tell the first from subsequent calls from the value of
+ * env->gui, which is NULL the first time.
+ */
+static void sdl_setup(struct video_desc *env)
+{
+ int dpy_fmt = SDL_IYUV_OVERLAY; /* YV12 causes flicker in SDL */
+ int depth, maxw, maxh;
+ const SDL_VideoInfo *info;
+ int kp_w = 0, kp_h = 0; /* keypad width and height */
+ struct gui_info *gui = env->gui;
+
+ /*
+ * initialize the SDL environment. We have one large window
+ * with local and remote video, and a keypad.
+ * At the moment we arrange them statically, as follows:
+ * - on the left, the remote video;
+ * - on the center, the keypad
+ * - on the right, the local video
+ * We need to read in the skin for the keypad before creating the main
+ * SDL window, because the size is only known here.
+ */
+
+ if (gui == NULL && SDL_Init(SDL_INIT_VIDEO)) {
+ ast_log(LOG_WARNING, "Could not initialize SDL - %s\n",
+ SDL_GetError());
+ /* again not fatal, just we won't display anything */
+ return;
+ }
+ info = SDL_GetVideoInfo();
+ /* We want at least 16bpp to support YUV overlays.
+ * E.g with SDL_VIDEODRIVER = aalib the default is 8
+ */
+ depth = info->vfmt->BitsPerPixel;
+ if (depth < 16)
+ depth = 16;
+ if (!gui)
+ env->gui = gui = gui_init(env->keypad_file, env->keypad_font);
+ if (!gui)
+ goto no_sdl;
+
+ if (gui->keypad) {
+ if (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) {
+ kp_w = gui->kp_rect.w;
+ kp_h = gui->kp_rect.h;
+ } else {
+ kp_w = gui->keypad->w;
+ kp_h = gui->keypad->h;
+ }
+ }
+ /* XXX same for other boards */
+#define BORDER 5 /* border around our windows */
+ maxw = env->rem_dpy.w + env->loc_dpy.w + kp_w;
+ maxh = MAX( MAX(env->rem_dpy.h, env->loc_dpy.h), kp_h);
+ maxw += 4 * BORDER;
+ maxh += 2 * BORDER;
+ gui->screen = SDL_SetVideoMode(maxw, maxh, depth, 0);
+ if (!gui->screen) {
+ ast_log(LOG_ERROR, "SDL: could not set video mode - exiting\n");
+ goto no_sdl;
+ }
+
+ SDL_WM_SetCaption("Asterisk console Video Output", NULL);
+ if (set_win(gui->screen, &gui->win[WIN_REMOTE], dpy_fmt,
+ env->rem_dpy.w, env->rem_dpy.h, BORDER, BORDER))
+ goto no_sdl;
+ if (set_win(gui->screen, &gui->win[WIN_LOCAL], dpy_fmt,
+ env->loc_dpy.w, env->loc_dpy.h,
+ 3*BORDER+env->rem_dpy.w + kp_w, BORDER))
+ goto no_sdl;
+
+ /* display the skin, but do not free it as we need it later to
+ * restore text areas and maybe sliders too.
+ */
+ if (gui->keypad) {
+ struct SDL_Rect *dest = &gui->win[WIN_KEYPAD].rect;
+ struct SDL_Rect *src = (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) ? & gui->kp_rect : NULL;
+ /* set the coordinates of the keypad relative to the main screen */
+ dest->x = 2*BORDER + env->rem_dpy.w;
+ dest->y = BORDER;
+ dest->w = kp_w;
+ dest->h = kp_h;
+ SDL_BlitSurface(gui->keypad, src, gui->screen, dest);
+ init_board(gui, &gui->bd_msg, gui->kp_msg, dest->x, dest->y);
+ init_board(gui, &gui->bd_dialed, gui->kp_dialed, dest->x, dest->y);
+ SDL_UpdateRects(gui->screen, 1, dest);
+ }
+ return;
+
+no_sdl:
+ /* free resources in case of errors */
+ env->gui = cleanup_sdl(gui);
+}
+
+/*
+ * Functions to determine if a point is within a region. Return 1 if success.
+ * First rotate the point, with
+ * x' = (x - x0) * cos A + (y - y0) * sin A
+ * y' = -(x - x0) * sin A + (y - y0) * cos A
+ * where cos A = (x1-x0)/l, sin A = (y1 - y0)/l, and
+ * l = sqrt( (x1-x0)^2 + (y1-y0)^2
+ * Then determine inclusion by simple comparisons i.e.:
+ * rectangle: x >= 0 && x < l && y >= 0 && y < h
+ * ellipse: (x-xc)^2/l^2 + (y-yc)^2/h2 < 1
+ */
+static int kp_match_area(const struct keypad_entry *e, int x, int y)
+{
+ double xp, dx = (e->x1 - e->x0);
+ double yp, dy = (e->y1 - e->y0);
+ double l = sqrt(dx*dx + dy*dy);
+ int ret = 0;
+
+ if (l > 1) { /* large enough */
+ xp = ((x - e->x0)*dx + (y - e->y0)*dy)/l;
+ yp = (-(x - e->x0)*dy + (y - e->y0)*dx)/l;
+ if (e->type == KP_RECT) {
+ ret = (xp >= 0 && xp < l && yp >=0 && yp < l);
+ } else if (e->type == KP_CIRCLE) {
+ dx = xp*xp/(l*l) + yp*yp/(e->h*e->h);
+ ret = (dx < 1);
+ }
+ }
+#if 0
+ ast_log(LOG_WARNING, "result %d [%d] for match %d,%d in type %d p0 %d,%d p1 %d,%d h %d\n",
+ ret, e->c, x, y, e->type, e->x0, e->y0, e->x1, e->y1, e->h);
+#endif
+ return ret;
+}
+
+struct _s_k { const char *s; int k; };
+static struct _s_k gui_key_map[] = {
+ {"PICK_UP", KEY_PICK_UP },
+ {"PICKUP", KEY_PICK_UP },
+ {"HANG_UP", KEY_HANG_UP },
+ {"HANGUP", KEY_HANG_UP },
+ {"MUTE", KEY_MUTE },
+ {"FLASH", KEY_FLASH },
+ {"AUTOANSWER", KEY_AUTOANSWER },
+ {"SENDVIDEO", KEY_SENDVIDEO },
+ {"LOCALVIDEO", KEY_LOCALVIDEO },
+ {"REMOTEVIDEO", KEY_REMOTEVIDEO },
+ {"GUI_CLOSE", KEY_GUI_CLOSE },
+ {"MESSAGEBOARD", KEY_MESSAGEBOARD },
+ {"DIALEDBOARD", KEY_DIALEDBOARD },
+ {"EDITBOARD", KEY_EDITBOARD },
+ {"KEYPAD", KEY_KEYPAD }, /* x0 y0 w h - active area of the keypad */
+ {"MESSAGE", KEY_MESSAGE }, /* x0 y0 w h - incoming messages */
+ {"DIALED", KEY_DIALED }, /* x0 y0 w h - dialed number */
+ {"EDIT", KEY_EDIT }, /* x0 y0 w h - edit user input */
+ {"FONT", KEY_FONT }, /* x0 yo w h rows cols - location and format of the font */
+ {NULL, 0 } };
+
+static int gui_map_token(const char *s)
+{
+ /* map the string into token to be returned */
+ int i = atoi(s);
+ struct _s_k *p;
+ if (i > 0 || s[1] == '\0') /* numbers or single characters */
+ return (i > 9) ? i : s[0];
+ for (p = gui_key_map; p->s; p++) {
+ if (!strcasecmp(p->s, s))
+ return p->k;
+ }
+ return KEY_NONE; /* not found */
+}
+
+/*! \brief read a keypad entry line in the format
+ * reset
+ * token circle xc yc diameter
+ * token circle xc yc x1 y1 h # ellipse, main diameter and height
+ * token rect x0 y0 x1 y1 h # rectangle with main side and eight
+ * token is the token to be returned, either a character or a symbol
+ * as KEY_* above
+ * Return 1 on success, 0 on error.
+ */
+static int keypad_cfg_read(struct gui_info *gui, const char *val)
+{
+ struct keypad_entry e;
+ SDL_Rect *r = NULL;
+ char s1[16], s2[16];
+ int i, ret = 0; /* default, error */
+
+ if (gui == NULL || val == NULL)
+ return 0;
+
+ s1[0] = s2[0] = '\0';
+ bzero(&e, sizeof(e));
+ i = sscanf(val, "%14s %14s %d %d %d %d %d",
+ s1, s2, &e.x0, &e.y0, &e.x1, &e.y1, &e.h);
+
+ e.c = gui_map_token(s1);
+ if (e.c == KEY_NONE)
+ return 0; /* nothing found */
+ switch (i) {
+ default:
+ break;
+ case 1: /* only "reset" is allowed */
+ if (e.c != KEY_RESET)
+ break;
+ if (gui->kp)
+ gui->kp_used = 0;
+ break;
+ case 5:
+ if (e.c == KEY_KEYPAD) /* active keypad area */
+ r = &gui->kp_rect;
+ else if (e.c == KEY_MESSAGE)
+ r = gui->kp_msg;
+ else if (e.c == KEY_DIALED)
+ r = gui->kp_dialed;
+ else if (e.c == KEY_EDIT)
+ r = gui->kp_edit;
+ if (r) {
+ r->x = atoi(s2);
+ r->y = e.x0;
+ r->w = e.y0;
+ r->h = e.x1;
+ break;
+ }
+ if (strcasecmp(s2, "circle")) /* invalid */
+ break;
+ /* token circle xc yc diameter */
+ e.h = e.x1;
+ e.y1 = e.y0; /* map radius in x1 y1 */
+ e.x1 = e.x0 + e.h; /* map radius in x1 y1 */
+ e.x0 = e.x0 - e.h; /* map radius in x1 y1 */
+ /* fallthrough */
+
+ case 7:
+ if (e.c == KEY_FONT) { /* font - x0 y0 w h rows cols */
+ ast_log(LOG_WARNING, "font not supported yet\n");
+ break;
+ }
+ /* token circle|rect x0 y0 x1 y1 h */
+ if (e.x1 < e.x0 || e.h <= 0) {
+ ast_log(LOG_WARNING, "error in coordinates\n");
+ e.type = 0;
+ break;
+ }
+ if (!strcasecmp(s2, "circle")) {
+ /* for a circle we specify the diameter but store center and radii */
+ e.type = KP_CIRCLE;
+ e.x0 = (e.x1 + e.x0) / 2;
+ e.y0 = (e.y1 + e.y0) / 2;
+ e.h = e.h / 2;
+ } else if (!strcasecmp(s2, "rect")) {
+ e.type = KP_RECT;
+ } else
+ break;
+ ret = 1;
+ }
+ // ast_log(LOG_WARNING, "reading [%s] returns %d %d\n", val, i, ret);
+ if (ret == 0)
+ return 0;
+ if (gui->kp_size == 0) {
+ gui->kp = ast_calloc(10, sizeof(e));
+ if (gui->kp == NULL) {
+ ast_log(LOG_WARNING, "cannot allocate kp");
+ return 0;
+ }
+ gui->kp_size = 10;
+ }
+ if (gui->kp_size == gui->kp_used) { /* must allocate */
+ struct keypad_entry *a = ast_realloc(gui->kp, sizeof(e)*(gui->kp_size+10));
+ if (a == NULL) {
+ ast_log(LOG_WARNING, "cannot reallocate kp");
+ return 0;
+ }
+ gui->kp = a;
+ gui->kp_size += 10;
+ }
+ if (gui->kp_size == gui->kp_used)
+ return 0;
+ gui->kp[gui->kp_used++] = e;
+ // ast_log(LOG_WARNING, "now %d regions\n", gui->kp_used);
+ return 1;
+}
+#endif /* HAVE_SDL */
diff --git a/trunk/channels/console_video.c b/trunk/channels/console_video.c
new file mode 100644
index 000000000..554d03c39
--- /dev/null
+++ b/trunk/channels/console_video.c
@@ -0,0 +1,1035 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright 2007-2008, Marta Carbone, Sergio Fadda, Luigi Rizzo
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*
+ * Experimental support for video sessions. We use SDL for rendering, ffmpeg
+ * as the codec library for encoding and decoding, and Video4Linux and X11
+ * to generate the local video stream.
+ *
+ * If one of these pieces is not available, either at compile time or at
+ * runtime, we do our best to run without it. Of course, no codec library
+ * means we can only deal with raw data, no SDL means we cannot do rendering,
+ * no V4L or X11 means we cannot generate data (but in principle we could
+ * stream from or record to a file).
+ *
+ * We need a recent (2007.07.12 or newer) version of ffmpeg to avoid warnings.
+ * Older versions might give 'deprecated' messages during compilation,
+ * thus not compiling in AST_DEVMODE, or don't have swscale, in which case
+ * you can try to compile #defining OLD_FFMPEG here.
+ *
+ * $Revision$
+ */
+
+//#define DROP_PACKETS 5 /* if set, drop this % of video packets */
+//#define OLD_FFMPEG 1 /* set for old ffmpeg with no swscale */
+
+#include "asterisk.h"
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+#include <sys/ioctl.h>
+#include "asterisk/cli.h"
+#include "asterisk/file.h"
+#include "asterisk/channel.h"
+
+#include "console_video.h"
+
+/*
+The code is structured as follows.
+
+When a new console channel is created, we call console_video_start()
+to initialize SDL, the source, and the encoder/ decoder for the
+formats in use (XXX the latter two should be done later, once the
+codec negotiation is complete). Also, a thread is created to handle
+the video source and generate frames.
+
+While communication is on, the local source is generated by the
+video thread, which wakes up periodically, generates frames and
+enqueues them in chan->readq. Incoming rtp frames are passed to
+console_write_video(), decoded and passed to SDL for display.
+
+For as unfortunate and confusing as it can be, we need to deal with a
+number of different video representations (size, codec/pixel format,
+codec parameters), as follows:
+
+ loc_src is the data coming from the camera/X11/etc.
+ The format is typically constrained by the video source.
+
+ enc_in is the input required by the encoder.
+ Typically constrained in size by the encoder type.
+
+ enc_out is the bitstream transmitted over RTP.
+ Typically negotiated while the call is established.
+
+ loc_dpy is the format used to display the local video source.
+ Depending on user preferences this can have the same size as
+ loc_src_fmt, or enc_in_fmt, or thumbnail size (e.g. PiP output)
+
+ dec_in is the incoming RTP bitstream. Negotiated
+ during call establishment, it is not necessarily the same as
+ enc_in_fmt
+
+ dec_out the output of the decoder.
+ The format is whatever the other side sends, and the
+ buffer is allocated by avcodec_decode_... so we only
+ copy the data here.
+
+ rem_dpy the format used to display the remote stream
+
+We store the format info together with the buffer storing the data.
+As a future optimization, a format/buffer may reference another one
+if the formats are equivalent. This will save some unnecessary format
+conversion.
+
+
+In order to handle video you need to add to sip.conf (and presumably
+iax.conf too) the following:
+
+ [general](+)
+ videosupport=yes
+ allow=h263 ; this or other video formats
+ allow=h263p ; this or other video formats
+
+ */
+
+/*
+ * Codecs are absolutely necessary or we cannot do anything.
+ * SDL is optional (used for rendering only), so that we can still
+ * stream video withouth displaying it.
+ */
+#if !defined(HAVE_VIDEO_CONSOLE) || !defined(HAVE_FFMPEG)
+/* stubs if required pieces are missing */
+int console_write_video(struct ast_channel *chan, struct ast_frame *f)
+{
+ return 0; /* writing video not supported */
+}
+
+int console_video_cli(struct video_desc *env, const char *var, int fd)
+{
+ return 1; /* nothing matched */
+}
+
+int console_video_config(struct video_desc **penv, const char *var, const char *val)
+{
+ return 1; /* no configuration */
+}
+
+void console_video_start(struct video_desc *env, struct ast_channel *owner)
+{
+ ast_log(LOG_NOTICE, "voice only, console video support not present\n");
+}
+
+void console_video_uninit(struct video_desc *env)
+{
+}
+
+int console_video_formats = 0;
+
+#else /* defined(HAVE_FFMPEG) && defined(HAVE_SDL) */
+
+/*! The list of video formats we support. */
+int console_video_formats =
+ AST_FORMAT_H263_PLUS | AST_FORMAT_H263 |
+ AST_FORMAT_MP4_VIDEO | AST_FORMAT_H264 | AST_FORMAT_H261 ;
+
+
+
+static void my_scale(struct fbuf_t *in, AVPicture *p_in,
+ struct fbuf_t *out, AVPicture *p_out);
+
+struct video_codec_desc; /* forward declaration */
+/*
+ * Descriptor of the local source, made of the following pieces:
+ * + configuration info (geometry, device name, fps...). These are read
+ * from the config file and copied here before calling video_out_init();
+ * + the frame buffer (buf) and source pixel format, allocated at init time;
+ * + the encoding and RTP info, including timestamps to generate
+ * frames at the correct rate;
+ * + source-specific info, i.e. fd for /dev/video, dpy-image for x11, etc,
+ * filled in by grabber_open
+ * NOTE: loc_src.data == NULL means the rest of the struct is invalid, and
+ * the video source is not available.
+ */
+struct video_out_desc {
+ /* video device support.
+ * videodevice and geometry are read from the config file.
+ * At the right time we try to open it and allocate a buffer.
+ * If we are successful, webcam_bufsize > 0 and we can read.
+ */
+ /* all the following is config file info copied from the parent */
+ char videodevice[64];
+ int fps;
+ int bitrate;
+ int qmin;
+
+ int sendvideo;
+
+ struct fbuf_t loc_src_geometry; /* local source geometry only (from config file) */
+ struct fbuf_t enc_out; /* encoder output buffer, allocated in video_out_init() */
+
+ struct video_codec_desc *enc; /* encoder */
+ void *enc_ctx; /* encoding context */
+ AVCodec *codec;
+ AVFrame *enc_in_frame; /* enc_in mapped into avcodec format. */
+ /* The initial part of AVFrame is an AVPicture */
+ int mtu;
+ struct timeval last_frame; /* when we read the last frame ? */
+
+ struct grab_desc *grabber;
+ void *grabber_data;
+};
+
+/*
+ * The overall descriptor, with room for config info, video source and
+ * received data descriptors, SDL info, etc.
+ * This should be globally visible to all modules (grabber, vcodecs, gui)
+ * and contain all configurtion info.
+ */
+struct video_desc {
+ char codec_name[64]; /* the codec we use */
+
+ int stayopen; /* set if gui starts manually */
+ pthread_t vthread; /* video thread */
+ ast_mutex_t dec_lock; /* sync decoder and video thread */
+ int shutdown; /* set to shutdown vthread */
+ struct ast_channel *owner; /* owner channel */
+
+
+ struct fbuf_t enc_in; /* encoder input buffer, allocated in video_out_init() */
+
+ char keypad_file[256]; /* image for the keypad */
+ char keypad_font[256]; /* font for the keypad */
+
+ char sdl_videodriver[256];
+
+ struct fbuf_t rem_dpy; /* display remote video, no buffer (it is in win[WIN_REMOTE].bmp) */
+ struct fbuf_t loc_dpy; /* display local source, no buffer (managed by SDL in bmp[1]) */
+
+
+ /* local information for grabbers, codecs, gui */
+ struct gui_info *gui;
+ struct video_dec_desc *in; /* remote video descriptor */
+ struct video_out_desc out; /* local video descriptor */
+};
+
+static AVPicture *fill_pict(struct fbuf_t *b, AVPicture *p);
+
+void fbuf_free(struct fbuf_t *b)
+{
+ struct fbuf_t x = *b;
+
+ if (b->data && b->size)
+ ast_free(b->data);
+ bzero(b, sizeof(*b));
+ /* restore some fields */
+ b->w = x.w;
+ b->h = x.h;
+ b->pix_fmt = x.pix_fmt;
+}
+
+#include "vcodecs.c"
+#include "console_gui.c"
+
+/*! \brief Try to open a video source, return 0 on success, 1 on error */
+static int grabber_open(struct video_out_desc *v)
+{
+ struct grab_desc *g;
+ void *g_data;
+ int i;
+
+ for (i = 0; (g = console_grabbers[i]); i++) {
+ g_data = g->open(v->videodevice, &v->loc_src_geometry, v->fps);
+ if (g_data) {
+ v->grabber = g;
+ v->grabber_data = g_data;
+ return 0;
+ }
+ }
+ return 1; /* no source found */
+}
+
+/*! \brief complete a buffer from the local video source.
+ * Called by get_video_frames(), in turn called by the video thread.
+ */
+static struct fbuf_t *grabber_read(struct video_out_desc *v)
+{
+ struct timeval now = ast_tvnow();
+
+ if (v->grabber == NULL) /* not initialized */
+ return 0;
+
+ /* check if it is time to read */
+ if (ast_tvzero(v->last_frame))
+ v->last_frame = now;
+ if (ast_tvdiff_ms(now, v->last_frame) < 1000/v->fps)
+ return 0; /* too early */
+ v->last_frame = now; /* XXX actually, should correct for drift */
+ return v->grabber->read(v->grabber_data);
+}
+
+/*! \brief handler run when dragging with the left button on
+ * the local source window - the effect is to move the offset
+ * of the captured area.
+ */
+static void grabber_move(struct video_out_desc *v, int dx, int dy)
+{
+ if (v->grabber && v->grabber->move)
+ v->grabber->move(v->grabber_data, dx, dy);
+}
+
+/*
+ * Map the codec name to the library. If not recognised, use a default.
+ * This is useful in the output path where we decide by name, presumably.
+ */
+static struct video_codec_desc *map_config_video_format(char *name)
+{
+ int i;
+
+ for (i = 0; supported_codecs[i]; i++)
+ if (!strcasecmp(name, supported_codecs[i]->name))
+ break;
+ if (supported_codecs[i] == NULL) {
+ ast_log(LOG_WARNING, "Cannot find codec for '%s'\n", name);
+ i = 0;
+ strcpy(name, supported_codecs[i]->name);
+ }
+ ast_log(LOG_WARNING, "Using codec '%s'\n", name);
+ return supported_codecs[i];
+}
+
+
+/*! \brief uninitialize the descriptor for local video stream */
+static int video_out_uninit(struct video_desc *env)
+{
+ struct video_out_desc *v = &env->out;
+
+ /* XXX this should be a codec callback */
+ if (v->enc_ctx) {
+ AVCodecContext *enc_ctx = (AVCodecContext *)v->enc_ctx;
+ avcodec_close(enc_ctx);
+ av_free(enc_ctx);
+ v->enc_ctx = NULL;
+ }
+ if (v->enc_in_frame) {
+ av_free(v->enc_in_frame);
+ v->enc_in_frame = NULL;
+ }
+ v->codec = NULL; /* nothing to free, this is only a reference */
+ /* release the buffers */
+ fbuf_free(&env->enc_in);
+ fbuf_free(&v->enc_out);
+ /* close the grabber */
+ if (v->grabber) {
+ v->grabber_data = v->grabber->close(v->grabber_data);
+ v->grabber = NULL;
+ }
+ return -1;
+}
+
+/*
+ * Initialize the encoder for the local source:
+ * - enc_ctx, codec, enc_in_frame are used by ffmpeg for encoding;
+ * - enc_out is used to store the encoded frame (to be sent)
+ * - mtu is used to determine the max size of video fragment
+ * NOTE: we enter here with the video source already open.
+ */
+static int video_out_init(struct video_desc *env)
+{
+ int codec;
+ int size;
+ struct fbuf_t *enc_in;
+ struct video_out_desc *v = &env->out;
+
+ v->enc_ctx = NULL;
+ v->codec = NULL;
+ v->enc_in_frame = NULL;
+ v->enc_out.data = NULL;
+
+ codec = map_video_format(v->enc->format, CM_WR);
+ v->codec = avcodec_find_encoder(codec);
+ if (!v->codec) {
+ ast_log(LOG_WARNING, "Cannot find the encoder for format %d\n",
+ codec);
+ return -1; /* error, but nothing to undo yet */
+ }
+
+ v->mtu = 1400; /* set it early so the encoder can use it */
+
+ /* allocate the input buffer for encoding.
+ * ffmpeg only supports PIX_FMT_YUV420P for the encoding.
+ */
+ enc_in = &env->enc_in;
+ enc_in->pix_fmt = PIX_FMT_YUV420P;
+ enc_in->size = (enc_in->w * enc_in->h * 3)/2;
+ enc_in->data = ast_calloc(1, enc_in->size);
+ if (!enc_in->data) {
+ ast_log(LOG_WARNING, "Cannot allocate encoder input buffer\n");
+ return video_out_uninit(env);
+ }
+ /* construct an AVFrame that points into buf_in */
+ v->enc_in_frame = avcodec_alloc_frame();
+ if (!v->enc_in_frame) {
+ ast_log(LOG_WARNING, "Unable to allocate the encoding video frame\n");
+ return video_out_uninit(env);
+ }
+
+ /* parameters for PIX_FMT_YUV420P */
+ size = enc_in->w * enc_in->h;
+ v->enc_in_frame->data[0] = enc_in->data;
+ v->enc_in_frame->data[1] = v->enc_in_frame->data[0] + size;
+ v->enc_in_frame->data[2] = v->enc_in_frame->data[1] + size/4;
+ v->enc_in_frame->linesize[0] = enc_in->w;
+ v->enc_in_frame->linesize[1] = enc_in->w/2;
+ v->enc_in_frame->linesize[2] = enc_in->w/2;
+
+ /* now setup the parameters for the encoder.
+ * XXX should be codec-specific
+ */
+ {
+ AVCodecContext *enc_ctx = avcodec_alloc_context();
+ v->enc_ctx = enc_ctx;
+ enc_ctx->pix_fmt = enc_in->pix_fmt;
+ enc_ctx->width = enc_in->w;
+ enc_ctx->height = enc_in->h;
+ /* XXX rtp_callback ?
+ * rtp_mode so ffmpeg inserts as many start codes as possible.
+ */
+ enc_ctx->rtp_mode = 1;
+ enc_ctx->rtp_payload_size = v->mtu / 2; // mtu/2
+ enc_ctx->bit_rate = v->bitrate;
+ enc_ctx->bit_rate_tolerance = enc_ctx->bit_rate/2;
+ enc_ctx->qmin = v->qmin; /* should be configured */
+ enc_ctx->time_base = (AVRational){1, v->fps};
+ enc_ctx->gop_size = v->fps*5; // emit I frame every 5 seconds
+
+ v->enc->enc_init(v->enc_ctx);
+
+ if (avcodec_open(enc_ctx, v->codec) < 0) {
+ ast_log(LOG_WARNING, "Unable to initialize the encoder %d\n",
+ codec);
+ av_free(enc_ctx);
+ v->enc_ctx = NULL;
+ return video_out_uninit(env);
+ }
+ }
+ /*
+ * Allocate enough for the encoded bitstream. As we are compressing,
+ * we hope that the output is never larger than the input size.
+ */
+ v->enc_out.data = ast_calloc(1, enc_in->size);
+ v->enc_out.size = enc_in->size;
+ v->enc_out.used = 0;
+
+ return 0;
+}
+
+/*! \brief possibly uninitialize the video console.
+ * Called at the end of a call, should reset the 'owner' field,
+ * then possibly terminate the video thread if the gui has
+ * not been started manually.
+ * In practice, signal the thread and give it a bit of time to
+ * complete, giving up if it gets stuck. Because uninit
+ * is called from hangup with the channel locked, and the thread
+ * uses the chan lock, we need to unlock here. This is unsafe,
+ * and we should really use refcounts for the channels.
+ */
+void console_video_uninit(struct video_desc *env)
+{
+ int i, t = 100; /* initial wait is shorter, than make it longer */
+ if (env->stayopen == 0) { /* in a call */
+ env->shutdown = 1;
+ for (i=0; env->shutdown && i < 10; i++) {
+ if (env->owner)
+ ast_channel_unlock(env->owner);
+ usleep(t);
+ t = 1000000;
+ if (env->owner)
+ ast_channel_lock(env->owner);
+ }
+ }
+ env->owner = NULL; /* this is unconditional */
+}
+
+/*! fill an AVPicture from our fbuf info, as it is required by
+ * the image conversion routines in ffmpeg.
+ * XXX This depends on the format.
+ */
+static AVPicture *fill_pict(struct fbuf_t *b, AVPicture *p)
+{
+ /* provide defaults for commonly used formats */
+ int l4 = b->w * b->h/4; /* size of U or V frame */
+ int len = b->w; /* Y linesize, bytes */
+ int luv = b->w/2; /* U/V linesize, bytes */
+
+ bzero(p, sizeof(*p));
+ switch (b->pix_fmt) {
+ case PIX_FMT_RGB555:
+ case PIX_FMT_RGB565:
+ len *= 2;
+ luv = 0;
+ break;
+ case PIX_FMT_RGBA32:
+ len *= 4;
+ luv = 0;
+ break;
+ case PIX_FMT_YUYV422: /* Packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr */
+ len *= 2; /* all data in first plane, probably */
+ luv = 0;
+ break;
+ }
+ p->data[0] = b->data;
+ p->linesize[0] = len;
+ /* these are only valid for component images */
+ p->data[1] = luv ? b->data + 4*l4 : b->data+len;
+ p->data[2] = luv ? b->data + 5*l4 : b->data+len;
+ p->linesize[1] = luv;
+ p->linesize[2] = luv;
+ return p;
+}
+
+/*! convert/scale between an input and an output format.
+ * Old version of ffmpeg only have img_convert, which does not rescale.
+ * New versions use sws_scale which does both.
+ */
+static void my_scale(struct fbuf_t *in, AVPicture *p_in,
+ struct fbuf_t *out, AVPicture *p_out)
+{
+ AVPicture my_p_in, my_p_out;
+
+ if (p_in == NULL)
+ p_in = fill_pict(in, &my_p_in);
+ if (p_out == NULL)
+ p_out = fill_pict(out, &my_p_out);
+
+#ifdef OLD_FFMPEG
+ /* XXX img_convert is deprecated, and does not do rescaling */
+ img_convert(p_out, out->pix_fmt,
+ p_in, in->pix_fmt, in->w, in->h);
+#else /* XXX replacement */
+ {
+ struct SwsContext *convert_ctx;
+
+ convert_ctx = sws_getContext(in->w, in->h, in->pix_fmt,
+ out->w, out->h, out->pix_fmt,
+ SWS_BICUBIC, NULL, NULL, NULL);
+ if (convert_ctx == NULL) {
+ ast_log(LOG_ERROR, "FFMPEG::convert_cmodel : swscale context initialization failed");
+ return;
+ }
+ if (0)
+ ast_log(LOG_WARNING, "in %d %dx%d out %d %dx%d\n",
+ in->pix_fmt, in->w, in->h, out->pix_fmt, out->w, out->h);
+ sws_scale(convert_ctx,
+ p_in->data, p_in->linesize,
+ in->w, in->h, /* src slice */
+ p_out->data, p_out->linesize);
+
+ sws_freeContext(convert_ctx);
+ }
+#endif /* XXX replacement */
+}
+
+struct video_desc *get_video_desc(struct ast_channel *c);
+
+/*
+ * This function is called (by asterisk) for each video packet
+ * coming from the network (the 'in' path) that needs to be processed.
+ * We need to reconstruct the entire video frame before we can decode it.
+ * After a video packet is received we have to:
+ * - extract the bitstream with pre_process_data()
+ * - append the bitstream to a buffer
+ * - if the fragment is the last (RTP Marker) we decode it with decode_video()
+ * - after the decoding is completed we display the decoded frame with show_frame()
+ */
+int console_write_video(struct ast_channel *chan, struct ast_frame *f);
+int console_write_video(struct ast_channel *chan, struct ast_frame *f)
+{
+ struct video_desc *env = get_video_desc(chan);
+ struct video_dec_desc *v = env->in;
+
+ if (!env->gui) /* no gui, no rendering */
+ return 0;
+ if (v == NULL)
+ env->in = v = dec_init(f->subclass & ~1);
+ if (v == NULL) {
+ /* This is not fatal, but we won't have incoming video */
+ ast_log(LOG_WARNING, "Cannot initialize input decoder\n");
+ return 0;
+ }
+
+ if (v->dec_in_cur == NULL) /* no buffer for incoming frames, drop */
+ return 0;
+#if defined(DROP_PACKETS) && DROP_PACKETS > 0
+ /* Simulate lost packets */
+ if ((random() % 10000) <= 100*DROP_PACKETS) {
+ ast_log(LOG_NOTICE, "Packet lost [%d]\n", f->seqno);
+ return 0;
+ }
+#endif
+ if (v->discard) {
+ /*
+ * In discard mode, drop packets until we find one with
+ * the RTP marker set (which is the end of frame).
+ * Note that the RTP marker flag is sent as the LSB of the
+ * subclass, which is a bitmask of formats. The low bit is
+ * normally used for audio so there is no interference.
+ */
+ if (f->subclass & 0x01) {
+ v->dec_in_cur->used = 0;
+ v->dec_in_cur->ebit = 0;
+ v->next_seq = f->seqno + 1; /* wrap at 16 bit */
+ v->discard = 0;
+ ast_log(LOG_WARNING, "out of discard mode, frame %d\n", f->seqno);
+ }
+ return 0;
+ }
+
+ /*
+ * Only in-order fragments will be accepted. Remember seqno
+ * has 16 bit so there is wraparound. Also, ideally we could
+ * accept a bit of reordering, but at the moment we don't.
+ */
+ if (v->next_seq != f->seqno) {
+ ast_log(LOG_WARNING, "discarding frame out of order, %d %d\n",
+ v->next_seq, f->seqno);
+ v->discard = 1;
+ return 0;
+ }
+ v->next_seq++;
+
+ if (f->data == NULL || f->datalen < 2) {
+ ast_log(LOG_WARNING, "empty video frame, discard\n");
+ return 0;
+ }
+ if (v->d_callbacks->dec_decap(v->dec_in_cur, f->data, f->datalen)) {
+ ast_log(LOG_WARNING, "error in dec_decap, enter discard\n");
+ v->discard = 1;
+ }
+ if (f->subclass & 0x01) { // RTP Marker
+ /* prepare to decode: advance the buffer so the video thread knows. */
+ struct fbuf_t *tmp = v->dec_in_cur; /* store current pointer */
+ ast_mutex_lock(&env->dec_lock);
+ if (++v->dec_in_cur == &v->dec_in[N_DEC_IN]) /* advance to next, circular */
+ v->dec_in_cur = &v->dec_in[0];
+ if (v->dec_in_dpy == NULL) { /* were not displaying anything, so set it */
+ v->dec_in_dpy = tmp;
+ } else if (v->dec_in_dpy == v->dec_in_cur) { /* current slot is busy */
+ v->dec_in_cur = NULL;
+ }
+ ast_mutex_unlock(&env->dec_lock);
+ }
+ return 0;
+}
+
+
+/*! \brief read a frame from webcam or X11 through grabber_read(),
+ * display it, then encode and split it.
+ * Return a list of ast_frame representing the video fragments.
+ * The head pointer is returned by the function, the tail pointer
+ * is returned as an argument.
+ */
+static struct ast_frame *get_video_frames(struct video_desc *env, struct ast_frame **tail)
+{
+ struct video_out_desc *v = &env->out;
+ struct ast_frame *dummy;
+ struct fbuf_t *loc_src = grabber_read(v);
+
+ if (!loc_src)
+ return NULL; /* can happen, e.g. we are reading too early */
+
+ if (tail == NULL)
+ tail = &dummy;
+ *tail = NULL;
+ /* Scale the video for the encoder, then use it for local rendering
+ * so we will see the same as the remote party.
+ */
+ my_scale(loc_src, NULL, &env->enc_in, NULL);
+ show_frame(env, WIN_LOCAL);
+ if (!v->sendvideo)
+ return NULL;
+ if (v->enc_out.data == NULL) {
+ static volatile int a = 0;
+ if (a++ < 2)
+ ast_log(LOG_WARNING, "fail, no encoder output buffer\n");
+ return NULL;
+ }
+ v->enc->enc_run(v);
+ return v->enc->enc_encap(&v->enc_out, v->mtu, tail);
+}
+
+/*
+ * Helper thread to periodically poll the video source and enqueue the
+ * generated frames to the channel's queue.
+ * Using a separate thread also helps because the encoding can be
+ * computationally expensive so we don't want to starve the main thread.
+ */
+static void *video_thread(void *arg)
+{
+ struct video_desc *env = arg;
+ int count = 0;
+ char save_display[128] = "";
+
+ /* if sdl_videodriver is set, override the environment. Also,
+ * if it contains 'console' override DISPLAY around the call to SDL_Init
+ * so we use the console as opposed to the x11 version of aalib
+ */
+ if (!ast_strlen_zero(env->sdl_videodriver)) { /* override */
+ const char *s = getenv("DISPLAY");
+ setenv("SDL_VIDEODRIVER", env->sdl_videodriver, 1);
+ if (s && !strcasecmp(env->sdl_videodriver, "aalib-console")) {
+ ast_copy_string(save_display, s, sizeof(save_display));
+ unsetenv("DISPLAY");
+ }
+ }
+ sdl_setup(env);
+ if (!ast_strlen_zero(save_display))
+ setenv("DISPLAY", save_display, 1);
+
+ /* initialize grab coordinates */
+ env->out.loc_src_geometry.x = 0;
+ env->out.loc_src_geometry.y = 0;
+
+ ast_mutex_init(&env->dec_lock); /* used to sync decoder and renderer */
+
+ if (grabber_open(&env->out)) {
+ ast_log(LOG_WARNING, "cannot open local video source\n");
+ } else {
+#if 0
+ /* In principle, try to register the fd.
+ * In practice, many webcam drivers do not support select/poll,
+ * so don't bother and instead read periodically from the
+ * video thread.
+ */
+ if (env->out.fd >= 0)
+ ast_channel_set_fd(env->owner, 1, env->out.fd);
+#endif
+ video_out_init(env);
+ }
+
+ for (;;) {
+ struct timeval t = { 0, 50000 }; /* XXX 20 times/sec */
+ struct ast_frame *p, *f;
+ struct ast_channel *chan;
+ int fd;
+ char *caption = NULL, buf[160];
+
+ /* determine if video format changed */
+ if (count++ % 10 == 0) {
+ if (env->out.sendvideo)
+ sprintf(buf, "%s %s %dx%d @@ %dfps %dkbps",
+ env->out.videodevice, env->codec_name,
+ env->enc_in.w, env->enc_in.h,
+ env->out.fps, env->out.bitrate/1000);
+ else
+ sprintf(buf, "hold");
+ caption = buf;
+ }
+
+ /* manage keypad events */
+ /* XXX here we should always check for events,
+ * otherwise the drag will not work */
+ if (env->gui)
+ eventhandler(env, caption);
+
+ /* sleep for a while */
+ ast_select(0, NULL, NULL, NULL, &t);
+
+ if (env->in) {
+ struct video_dec_desc *v = env->in;
+
+ /*
+ * While there is something to display, call the decoder and free
+ * the buffer, possibly enabling the receiver to store new data.
+ */
+ while (v->dec_in_dpy) {
+ struct fbuf_t *tmp = v->dec_in_dpy; /* store current pointer */
+
+ if (v->d_callbacks->dec_run(v, tmp))
+ show_frame(env, WIN_REMOTE);
+ tmp->used = 0; /* mark buffer as free */
+ tmp->ebit = 0;
+ ast_mutex_lock(&env->dec_lock);
+ if (++v->dec_in_dpy == &v->dec_in[N_DEC_IN]) /* advance to next, circular */
+ v->dec_in_dpy = &v->dec_in[0];
+
+ if (v->dec_in_cur == NULL) /* receiver was idle, enable it... */
+ v->dec_in_cur = tmp; /* using the slot just freed */
+ else if (v->dec_in_dpy == v->dec_in_cur) /* this was the last slot */
+ v->dec_in_dpy = NULL; /* nothing more to display */
+ ast_mutex_unlock(&env->dec_lock);
+ }
+ }
+
+ if (env->shutdown)
+ break;
+ f = get_video_frames(env, &p); /* read and display */
+ if (!f)
+ continue;
+ chan = env->owner;
+ if (chan == NULL)
+ continue;
+ fd = chan->alertpipe[1];
+ ast_channel_lock(chan);
+
+ /* AST_LIST_INSERT_TAIL is only good for one frame, cannot use here */
+ if (chan->readq.first == NULL) {
+ chan->readq.first = f;
+ } else {
+ chan->readq.last->frame_list.next = f;
+ }
+ chan->readq.last = p;
+ /*
+ * more or less same as ast_queue_frame, but extra
+ * write on the alertpipe to signal frames.
+ */
+ if (fd > -1) {
+ int blah = 1, l = sizeof(blah);
+ for (p = f; p; p = AST_LIST_NEXT(p, frame_list)) {
+ if (write(fd, &blah, l) != l)
+ ast_log(LOG_WARNING, "Unable to write to alert pipe on %s, frametype/subclass %d/%d: %s!\n",
+ chan->name, f->frametype, f->subclass, strerror(errno));
+ }
+ }
+ ast_channel_unlock(chan);
+ }
+ /* thread terminating, here could call the uninit */
+ /* uninitialize the local and remote video environments */
+ env->in = dec_uninit(env->in);
+ video_out_uninit(env);
+
+ if (env->gui)
+ env->gui = cleanup_sdl(env->gui);
+ ast_mutex_destroy(&env->dec_lock);
+ env->shutdown = 0;
+ return NULL;
+}
+
+static void copy_geometry(struct fbuf_t *src, struct fbuf_t *dst)
+{
+ if (dst->w == 0)
+ dst->w = src->w;
+ if (dst->h == 0)
+ dst->h = src->h;
+}
+
+/*! initialize the video environment.
+ * Apart from the formats (constant) used by sdl and the codec,
+ * we use enc_in as the basic geometry.
+ */
+static void init_env(struct video_desc *env)
+{
+ struct fbuf_t *c = &(env->out.loc_src_geometry); /* local source */
+ struct fbuf_t *ei = &(env->enc_in); /* encoder input */
+ struct fbuf_t *ld = &(env->loc_dpy); /* local display */
+ struct fbuf_t *rd = &(env->rem_dpy); /* remote display */
+
+ c->pix_fmt = PIX_FMT_YUV420P; /* default - camera format */
+ ei->pix_fmt = PIX_FMT_YUV420P; /* encoder input */
+ if (ei->w == 0 || ei->h == 0) {
+ ei->w = 352;
+ ei->h = 288;
+ }
+ ld->pix_fmt = rd->pix_fmt = PIX_FMT_YUV420P; /* sdl format */
+ /* inherit defaults */
+ copy_geometry(ei, c); /* camera inherits from encoder input */
+ copy_geometry(ei, rd); /* remote display inherits from encoder input */
+ copy_geometry(rd, ld); /* local display inherits from remote display */
+}
+
+/*!
+ * The first call to the video code, called by oss_new() or similar.
+ * Here we initialize the various components we use, namely SDL for display,
+ * ffmpeg for encoding/decoding, and a local video source.
+ * We do our best to progress even if some of the components are not
+ * available.
+ */
+void console_video_start(struct video_desc *env, struct ast_channel *owner)
+{
+ ast_log(LOG_WARNING, "env %p chan %p\n", env, owner);
+ if (env == NULL) /* video not initialized */
+ return;
+ env->owner = owner; /* work even if no owner is specified */
+ if (env->stayopen)
+ return; /* already initialized, nothing to do */
+ init_env(env);
+ env->out.enc = map_config_video_format(env->codec_name);
+
+ ast_log(LOG_WARNING, "start video out %s %dx%d\n",
+ env->codec_name, env->enc_in.w, env->enc_in.h);
+ /*
+ * Register all codecs supported by the ffmpeg library.
+ * We only need to do it once, but probably doesn't
+ * harm to do it multiple times.
+ */
+ avcodec_init();
+ avcodec_register_all();
+ av_log_set_level(AV_LOG_ERROR); /* only report errors */
+
+ if (env->out.fps == 0) {
+ env->out.fps = 15;
+ ast_log(LOG_WARNING, "fps unset, forcing to %d\n", env->out.fps);
+ }
+ if (env->out.bitrate == 0) {
+ env->out.bitrate = 65000;
+ ast_log(LOG_WARNING, "bitrate unset, forcing to %d\n", env->out.bitrate);
+ }
+ ast_pthread_create_background(&env->vthread, NULL, video_thread, env);
+ if (env->owner == NULL)
+ env->stayopen = 1; /* manually opened so don't close on hangup */
+}
+
+/*
+ * Parse a geometry string, accepting also common names for the formats.
+ * Trick: if we have a leading > or < and a numeric geometry,
+ * return the larger or smaller one.
+ * E.g. <352x288 gives the smaller one, 320x240
+ */
+static int video_geom(struct fbuf_t *b, const char *s)
+{
+ int w = 0, h = 0;
+
+ static struct {
+ const char *s; int w; int h;
+ } *fp, formats[] = {
+ {"16cif", 1408, 1152 },
+ {"xga", 1024, 768 },
+ {"4cif", 704, 576 },
+ {"vga", 640, 480 },
+ {"cif", 352, 288 },
+ {"qvga", 320, 240 },
+ {"qcif", 176, 144 },
+ {"sqcif", 128, 96 },
+ {NULL, 0, 0 },
+ };
+ if (*s == '<' || *s == '>')
+ sscanf(s+1,"%dx%d", &w, &h);
+ for (fp = formats; fp->s; fp++) {
+ if (*s == '>') { /* look for a larger one */
+ if (fp->w <= w) {
+ if (fp > formats)
+ fp--; /* back one step if possible */
+ break;
+ }
+ } else if (*s == '<') { /* look for a smaller one */
+ if (fp->w < w)
+ break;
+ } else if (!strcasecmp(s, fp->s)) { /* look for a string */
+ break;
+ }
+ }
+ if (*s == '<' && fp->s == NULL) /* smallest */
+ fp--;
+ if (fp->s) {
+ b->w = fp->w;
+ b->h = fp->h;
+ } else if (sscanf(s, "%dx%d", &b->w, &b->h) != 2) {
+ ast_log(LOG_WARNING, "Invalid video_size %s, using 352x288\n", s);
+ b->w = 352;
+ b->h = 288;
+ }
+ return 0;
+}
+
+/* extend ast_cli with video commands. Called by console_video_config */
+int console_video_cli(struct video_desc *env, const char *var, int fd)
+{
+ if (env == NULL)
+ return 1; /* unrecognised */
+
+ if (!strcasecmp(var, "videodevice")) {
+ ast_cli(fd, "videodevice is [%s]\n", env->out.videodevice);
+ } else if (!strcasecmp(var, "videocodec")) {
+ ast_cli(fd, "videocodec is [%s]\n", env->codec_name);
+ } else if (!strcasecmp(var, "sendvideo")) {
+ ast_cli(fd, "sendvideo is [%s]\n", env->out.sendvideo ? "on" : "off");
+ } else if (!strcasecmp(var, "video_size")) {
+ int in_w = 0, in_h = 0;
+ if (env->in) {
+ in_w = env->in->dec_out.w;
+ in_h = env->in->dec_out.h;
+ }
+ ast_cli(fd, "sizes: video %dx%d camera %dx%d local %dx%d remote %dx%d in %dx%d\n",
+ env->enc_in.w, env->enc_in.h,
+ env->out.loc_src_geometry.w, env->out.loc_src_geometry.h,
+ env->loc_dpy.w, env->loc_dpy.h,
+ env->rem_dpy.w, env->rem_dpy.h,
+ in_w, in_h);
+ } else if (!strcasecmp(var, "bitrate")) {
+ ast_cli(fd, "bitrate is [%d]\n", env->out.bitrate);
+ } else if (!strcasecmp(var, "qmin")) {
+ ast_cli(fd, "qmin is [%d]\n", env->out.qmin);
+ } else if (!strcasecmp(var, "fps")) {
+ ast_cli(fd, "fps is [%d]\n", env->out.fps);
+ } else if (!strcasecmp(var, "startgui")) {
+ console_video_start(env, NULL);
+ } else if (!strcasecmp(var, "stopgui") && env->stayopen != 0) {
+ env->stayopen = 0;
+ if (env->gui && env->owner)
+ ast_cli_command(-1, "console hangup");
+ else /* not in a call */
+ console_video_uninit(env);
+ } else {
+ return 1; /* unrecognised */
+ }
+ return 0; /* recognised */
+}
+
+/*! parse config command for video support. */
+int console_video_config(struct video_desc **penv,
+ const char *var, const char *val)
+{
+ struct video_desc *env;
+
+ if (penv == NULL) {
+ ast_log(LOG_WARNING, "bad argument penv=NULL\n");
+ return 1; /* error */
+ }
+ /* allocate the video descriptor first time we get here */
+ env = *penv;
+ if (env == NULL) {
+ env = *penv = ast_calloc(1, sizeof(struct video_desc));
+ if (env == NULL) {
+ ast_log(LOG_WARNING, "fail to allocate video_desc\n");
+ return 1; /* error */
+
+ }
+ /* set default values */
+ ast_copy_string(env->out.videodevice, "X11", sizeof(env->out.videodevice));
+ env->out.fps = 5;
+ env->out.bitrate = 65000;
+ env->out.sendvideo = 1;
+ env->out.qmin = 3;
+ }
+ CV_START(var, val);
+ CV_STR("videodevice", env->out.videodevice);
+ CV_BOOL("sendvideo", env->out.sendvideo);
+ CV_F("video_size", video_geom(&env->enc_in, val));
+ CV_F("camera_size", video_geom(&env->out.loc_src_geometry, val));
+ CV_F("local_size", video_geom(&env->loc_dpy, val));
+ CV_F("remote_size", video_geom(&env->rem_dpy, val));
+ CV_STR("keypad", env->keypad_file);
+ CV_F("region", keypad_cfg_read(env->gui, val));
+ CV_STR("keypad_font", env->keypad_font);
+ CV_STR("sdl_videodriver", env->sdl_videodriver);
+ CV_UINT("fps", env->out.fps);
+ CV_UINT("bitrate", env->out.bitrate);
+ CV_UINT("qmin", env->out.qmin);
+ CV_STR("videocodec", env->codec_name);
+ return 1; /* nothing found */
+
+ CV_END; /* the 'nothing found' case */
+ return 0; /* found something */
+}
+
+#endif /* video support */
diff --git a/trunk/channels/console_video.h b/trunk/channels/console_video.h
new file mode 100644
index 000000000..f426a5463
--- /dev/null
+++ b/trunk/channels/console_video.h
@@ -0,0 +1,127 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2007 Luigi Rizzo
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*
+ * Common header for console video support
+ *
+ * $Revision$
+ */
+
+#ifndef CONSOLE_VIDEO_H
+#define CONSOLE_VIDEO_H
+
+#if !defined(HAVE_VIDEO_CONSOLE) || !defined(HAVE_FFMPEG)
+#define CONSOLE_VIDEO_CMDS \
+ "console {device}"
+#else
+
+#include <ffmpeg/avcodec.h>
+#ifndef OLD_FFMPEG
+#include <ffmpeg/swscale.h> /* requires a recent ffmpeg */
+#endif
+
+#define CONSOLE_VIDEO_CMDS \
+ "console {videodevice|videocodec" \
+ "|video_size|bitrate|fps|qmin" \
+ "|sendvideo|keypad" \
+ "|sdl_videodriver" \
+ "|device|startgui|stopgui" \
+ "}"
+
+#endif /* HAVE_VIDEO_CONSOLE and others */
+
+/*
+ * In many places we use buffers to store the raw frames (but not only),
+ * so here is a structure to keep all the info. data = NULL means the
+ * structure is not initialized, so the other fields are invalid.
+ * size = 0 means the buffer is not malloc'ed so we don't have to free it.
+ */
+struct fbuf_t { /* frame buffers, dynamically allocated */
+ uint8_t *data; /* memory, malloced if size > 0, just reference
+ * otherwise */
+ int size; /* total size in bytes */
+ int used; /* space used so far */
+ int ebit; /* bits to ignore at the end */
+ int x; /* origin, if necessary */
+ int y;
+ int w; /* size */
+ int h;
+ int pix_fmt;
+};
+
+void fbuf_free(struct fbuf_t *);
+
+/* descriptor for a grabber */
+struct grab_desc {
+ const char *name;
+ void *(*open)(const char *name, struct fbuf_t *geom, int fps);
+ struct fbuf_t *(*read)(void *d);
+ void (*move)(void *d, int dx, int dy);
+ void *(*close)(void *d);
+};
+
+extern struct grab_desc *console_grabbers[];
+
+struct video_desc; /* opaque type for video support */
+struct video_desc *get_video_desc(struct ast_channel *c);
+
+/* linked by console_video.o */
+int console_write_video(struct ast_channel *chan, struct ast_frame *f);
+extern int console_video_formats;
+int console_video_cli(struct video_desc *env, const char *var, int fd);
+int console_video_config(struct video_desc **penv, const char *var, const char *val);
+void console_video_uninit(struct video_desc *env);
+void console_video_start(struct video_desc *env, struct ast_channel *owner);
+
+/* console_board.c */
+
+/* Where do we send the keyboard/keypad output */
+enum kb_output {
+ KO_NONE,
+ KO_INPUT, /* the local input window */
+ KO_DIALED, /* the 'dialed number' window */
+ KO_MESSAGE, /* the 'message' window */
+};
+
+enum drag_window { /* which window are we dragging */
+ DRAG_NONE,
+ DRAG_LOCAL, /* local video */
+ DRAG_REMOTE, /* remote video */
+ DRAG_DIALED, /* dialed number */
+ DRAG_INPUT, /* input window */
+ DRAG_MESSAGE, /* message window */
+};
+
+/*! \brief support for drag actions */
+struct drag_info {
+ int x_start; /* last known mouse position */
+ int y_start;
+ enum drag_window drag_window;
+};
+/*! \brief info related to the gui: button status, mouse coords, etc. */
+struct board;
+/* !\brief print a message on a board */
+void move_message_board(struct board *b, int dy);
+int print_message(struct board *b, const char *s);
+
+/*! \brief return the whole text from a board */
+const char *read_message(const struct board *b);
+
+/*! \brief reset the board to blank */
+int reset_board(struct board *b);
+
+#endif /* CONSOLE_VIDEO_H */
+/* end of file */
diff --git a/trunk/channels/h323/ChangeLog b/trunk/channels/h323/ChangeLog
new file mode 100644
index 000000000..ddbf08193
--- /dev/null
+++ b/trunk/channels/h323/ChangeLog
@@ -0,0 +1,43 @@
+Build
+ -- Hold lock when creating new H.323 channel to sync the audio channels
+ -- Decrement usage counter when appropriate
+ -- Actually unregister everything in unload_module
+ -- Add IP based authentication using 'host'in type=user's
+0.1.0
+ -- Intergration into the mainline Asterisk codebase
+ -- Remove reduandant debug info
+ -- Add Caller*id support
+ -- Inband DTMF
+ -- Retool port usage (to avoid possible seg fault condition)
+0.0.6
+ -- Configurable support for user-input (DTMF)
+ -- Reworked Gatekeeper support
+ -- Native bridging (but is still broken, help!)
+ -- Locally implement a non-broken G.723.1 Capability
+ -- Utilize the cleaner RTP method implemented by Mark
+ -- AllowGkRouted, thanks to Panny from http://hotlinks.co.uk
+ -- Clened up inbound call flow
+ -- Prefix, E.164 and Gateway support
+ -- Multi-homed support
+ -- Killed more seg's
+0.0.5
+ -- Added H.323 Alias support
+ -- Clened up inbound call flow
+ -- Fixed RTP port logic
+ -- Stomped on possible seg fault conditions thanks to Iain Stevenson
+0.0.4
+ -- Fixed one-way audio on inbound calls. Found
+ race condition in monitor thread.
+
+0.0.3
+ -- Changed name to chan_h323
+ -- Also renamed file names to futher avoid confusion
+
+0.0.2
+ -- First public offering
+ -- removed most hardcoded values
+ -- lots of changes to alias/exension operation
+
+0.0.1
+ -- initial build, lots of hardcoded crap
+ -- Proof of concept for External RTP
diff --git a/trunk/channels/h323/INSTALL.openh323 b/trunk/channels/h323/INSTALL.openh323
new file mode 100644
index 000000000..f46c37905
--- /dev/null
+++ b/trunk/channels/h323/INSTALL.openh323
@@ -0,0 +1,18 @@
+To build Open H.323 see:
+
+http://www.openh323.org/build.html#unix
+
+You only need to do 'make opt'. Anything else you will be simply waisting time and HD space.
+Also, you will notice they never tell you to 'make install' so don't do it.
+
+
+On FreeBSD, the Makefiles are configured to
+locate the compiled openh323 port, if it has
+been built. Here is one way to build
+openh323 and ptlib on such that the Makefiles
+find it:
+ # cd /usr/ports/net/openh323
+ # make
+It is not necessary to install the port. The
+asterisk makefiles do not use any files
+installed by the port.
diff --git a/trunk/channels/h323/Makefile.in b/trunk/channels/h323/Makefile.in
new file mode 100644
index 000000000..083250f55
--- /dev/null
+++ b/trunk/channels/h323/Makefile.in
@@ -0,0 +1,48 @@
+#
+# Makefile
+#
+# Make file for OpenH323 support layer
+#
+
+.PHONY: Makefile.ast clean
+
+default:: @OPENH323_BUILD@
+
+# Verify those options with main Makefile
+STDCCFLAGS = -DNDEBUG
+STDCCFLAGS += -I../../include -include ../../include/asterisk/autoconfig.h
+STDCCFLAGS += -fPIC
+#OPTCCFLAGS +=
+CFLAGS = -pipe
+TARGET = libchanh323.a
+TARGET += Makefile.ast
+SOURCES = ast_h323.cxx compat_h323.cxx cisco-h225.cxx caps_h323.cxx
+OBJDIR = .
+OBJS =
+
+ifndef OPENH323DIR
+OPENH323DIR=@OPENH323DIR@
+endif
+
+include $(OPENH323DIR)/openh323u.mak
+
+notrace::
+ $(MAKE) NOTRACE=1 opt
+
+$(SOURCES):: Makefile ../../Makefile
+ touch $@
+
+libchanh323.a: $(OBJS)
+ ar crv $@ $(OBJS)
+
+cisco-h225.cxx:: cisco-h225.asn
+ asnparser -m CISCO_H225 -c $<
+
+Makefile.ast:
+ @echo H323CFLAGS = $(STDCCFLAGS) $(OPTCCFLAGS) $(CFLAGS) >$@.tmp
+ @echo H323LDFLAGS = $(CFLAGS) $(LDFLAGS) >>$@.tmp
+ @echo H323LDLIBS = $(LDLIBS) $(ENDLDLIBS) $(ENDLDFLAGS) >>$@.tmp
+ @if [ -r $@ ] && cmp -s $@ $@.tmp; then rm -f $@.tmp; else mv -f $@.tmp $@; fi
+
+clean::
+ rm -f $(TARGET) $(OBJS) Makefile.ast *.dep
diff --git a/trunk/channels/h323/README b/trunk/channels/h323/README
new file mode 100644
index 000000000..875bf3668
--- /dev/null
+++ b/trunk/channels/h323/README
@@ -0,0 +1,144 @@
+ Open H.323 Channel Driver for Asterisk
+ By Jeremy McNamara
+ For The NuFone Network
+
+ First public release on November 10th, 2002
+
+ Dependancies (based on OpenH323/PWLib ones):
+ openssl-0.9.6b+
+ openssl-devel-0.9.6b+
+ expat-1.95+
+ expat-dev-1.95+
+
+Tested with Open H.323 version v1.18.0, PWLib v1.10.0 and GCC v3.2.2. Usage of any
+other (especially prior OpenH323 v1.17.3 and PWLib v1.9.2) versions is not
+supported.
+
+NOTICE: Whatever you do, DO NOT USE distrubution specific installs
+of Open H.323 and PWLib. In fact, you should check to make sure
+your distro did not install them for you without your knowledge.
+
+
+To compile this code
+--------------------
+Once PWLib and Open H.323 have been compiled per their specific build
+instructions, issue a make in the asterisk/channels/h323 directory with
+argument used to build PWLib and OpenH323 (for example, make opt), then go
+back to the Asterisk source top level directory and issue a make install.
+
+
+The most common compile error
+----------------------------
+If you receive ANYTHING that says 'undefined symbol' you are experiencing
+typical version skew. For example:
+
+libh323_linux_x86_r.so.1: undefined symbol: GetNumberValueAt__C14PAbstractArrayi
+
+You need to search and destroy every version of libh323 and libpt then
+completely recompile everything
+
+Example commands to make sure everything gets cleaned and then
+rebult in proper order:
+
+cd /path/to/pwlib
+./configure
+make clean opt
+cd /path/to/openh323
+./configure
+make clean opt
+cd /path/to/asterisk/channels/h323
+make opt
+cd /path/to/asterisk
+make install
+
+
+Most common run-time error
+-------------------------
+libpt_linux_x86_r.so.1: cannot open shared object file: No such
+file or directory
+
+You have not set the LD_LIBRARY_PATH environment variable.
+
+Example environment for sh/bash:
+
+PWLIBDIR=$HOME/pwlib
+export PWLIBDIR
+OPENH323DIR=$HOME/openh323
+export OPENH323DIR
+LD_LIBRARY_PATH=$PWLIBDIR/lib:$OPENH323DIR/lib
+export LD_LIBRARY_PATH
+
+We recomend puting the above directives into your /etc/profile so
+you do not have to remember to export those values every time you
+want to recompile. Make sure to logout and log back in, so your
+envrionment can pick up the new variables.
+
+
+Upgrading Asterisk
+-----------------
+After you cvs update (or make update) Asterisk you have to go into
+asterisk/channels/h323 and issue a make clean all, before compiling the
+rest of asterisk. Doing this process every time you upgrade Asterisk
+will ensure a sane build.
+
+
+Dialing an H.323 channel
+------------------------
+Without a gatekeeper:
+exten => _1NXXNXXXXXX,1,Dial,H323/${EXTEN}@peer
+or
+exten => _1NXXNXXXXXX,1,Dial,H323/${EXTEN}@ip.or.hostname
+
+'peer' is defined in h323.conf as:
+
+[peer]
+type=peer
+host=1.2.3.4
+disallow=all
+allow=ulaw
+
+Using a gatekeeper:
+exten => _1NXXNXXXXXX,1,Dial,H323/${EXTEN}
+
+When using a gatekeeper you cannot utilize the type=peer features,
+since the H.323 spec states that when a Gatekeeper is part of an H.323 network,
+the Gatekeeper shall be used for all communication.
+
+
+Developer Contact
+----------------
+If you have trouble contact 'JerJer' in #Asterisk on
+irc.freenode.net and/or send reasonable debug information to support@nufone.net.
+
+If are lucky enough to segfault this code please run a
+backtrace and send the gory details. Segmentation faults are not
+tolerated, no matter what Distro you run (even debian)!
+
+a simple bt example:
+
+# /usr/sbin/asterisk -vvvgc
+...
+[chan_h323.so]
+Segmentation Fault (core dumped)
+
+# ls core.*
+core.1976
+
+# gdb /usr/sbin/asterisk core.1976
+...lots of useless garbage here...
+(gdb) bt
+
+Send whatever shows up right after the 'bt'
+
+Also, a full debug screen output is almost needed. Make sure you are
+in the full console mode (-c) and turn on 'h.323 debug' or worst case
+senerio 'h.323 trace 4'. A nice way to capture debug info is with
+script (man script).
+
+If you are motivated to update/fix this code please submit a
+disclaimer along with the patch to the Asterisk bug
+tracker: http://bugs.digium.com/
+
+
+Jeremy McNamara
+The NuFone Network
diff --git a/trunk/channels/h323/TODO b/trunk/channels/h323/TODO
new file mode 100644
index 000000000..1e114ca3b
--- /dev/null
+++ b/trunk/channels/h323/TODO
@@ -0,0 +1,9 @@
+The NuFone Network's Open H.323 Channel Driver for Asterisk
+
+ TODO:
+
+ - H.323 Native Bridging
+
+ - Gatekeeping support (started)
+
+ - Acutally implement the options for broken H.323 stacks
diff --git a/trunk/channels/h323/ast_h323.cxx b/trunk/channels/h323/ast_h323.cxx
new file mode 100644
index 000000000..fd7d35dff
--- /dev/null
+++ b/trunk/channels/h323/ast_h323.cxx
@@ -0,0 +1,2637 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+/*
+ * ast_h323.cpp
+ *
+ * OpenH323 Channel Driver for ASTERISK PBX.
+ * By Jeremy McNamara
+ * For The NuFone Network
+ *
+ * chan_h323 has been derived from code created by
+ * Michael Manousos and Mark Spencer
+ *
+ * This file is part of the chan_h323 driver for Asterisk
+ *
+ * chan_h323 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * chan_h323 is distributed WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Version Info: $Id$
+ */
+#include <arpa/inet.h>
+
+#include <list>
+#include <string>
+#include <algorithm>
+
+#include <ptlib.h>
+#include <h323.h>
+#include <h323pdu.h>
+#include <h323neg.h>
+#include <mediafmt.h>
+#include <lid.h>
+#ifdef H323_H450
+#include "h4501.h"
+#include "h4504.h"
+#include "h45011.h"
+#include "h450pdu.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include "asterisk/compat.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/astobj.h"
+#ifdef __cplusplus
+}
+#endif
+
+#include "chan_h323.h"
+#include "ast_h323.h"
+#include "cisco-h225.h"
+#include "caps_h323.h"
+
+/* PWlib Required Components */
+#define MAJOR_VERSION 1
+#define MINOR_VERSION 0
+#define BUILD_TYPE ReleaseCode
+#define BUILD_NUMBER 0
+
+/** Counter for the number of connections */
+static int channelsOpen;
+
+/**
+ * We assume that only one endPoint should exist.
+ * The application cannot run the h323_end_point_create() more than once
+ * FIXME: Singleton this, for safety
+ */
+static MyH323EndPoint *endPoint = NULL;
+
+/** PWLib entry point */
+static MyProcess *localProcess = NULL;
+
+static int _timerChangePipe[2];
+
+static unsigned traceOptions = PTrace::Timestamp | PTrace::Thread | PTrace::FileAndLine;
+
+class PAsteriskLog : public PObject, public iostream {
+ PCLASSINFO(PAsteriskLog, PObject);
+
+ public:
+ PAsteriskLog() : iostream(cout.rdbuf()) { init(&buffer); }
+ ~PAsteriskLog() { flush(); }
+
+ private:
+ PAsteriskLog(const PAsteriskLog &) : iostream(cout.rdbuf()) { }
+ PAsteriskLog & operator=(const PAsteriskLog &) { return *this; }
+
+ class Buffer : public streambuf {
+ public:
+ virtual int overflow(int=EOF);
+ virtual int underflow();
+ virtual int sync();
+ PString string;
+ } buffer;
+ friend class Buffer;
+};
+
+static PAsteriskLog *logstream = NULL;
+
+int PAsteriskLog::Buffer::overflow(int c)
+{
+ if (pptr() >= epptr()) {
+ int ppos = pptr() - pbase();
+ char *newptr = string.GetPointer(string.GetSize() + 2000);
+ setp(newptr, newptr + string.GetSize() - 1);
+ pbump(ppos);
+ }
+ if (c != EOF) {
+ *pptr() = (char)c;
+ pbump(1);
+ }
+ return 0;
+}
+
+int PAsteriskLog::Buffer::underflow()
+{
+ return EOF;
+}
+
+int PAsteriskLog::Buffer::sync()
+{
+ char *str = strdup(string);
+ char *s, *s1;
+ char c;
+
+ /* Pass each line with different ast_verbose() call */
+ for (s = str; s && *s; s = s1) {
+ s1 = strchr(s, '\n');
+ if (!s1)
+ s1 = s + strlen(s);
+ else
+ s1++;
+ c = *s1;
+ *s1 = '\0';
+ ast_verbose("%s", s);
+ *s1 = c;
+ }
+ free(str);
+
+ string = PString();
+ char *base = string.GetPointer(2000);
+ setp(base, base + string.GetSize() - 1);
+ return 0;
+}
+
+static ostream &my_endl(ostream &os)
+{
+ if (logstream) {
+ PTrace::SetOptions(traceOptions);
+ return PTrace::End(os);
+ }
+ return endl(os);
+}
+
+#define cout \
+ (logstream ? (PTrace::ClearOptions((unsigned)-1), PTrace::Begin(0, __FILE__, __LINE__)) : std::cout)
+#define endl my_endl
+
+/* Special class designed to call cleanup code on module destruction */
+class MyH323_Shutdown {
+ public:
+ MyH323_Shutdown() { };
+ ~MyH323_Shutdown()
+ {
+ h323_end_process();
+ };
+};
+
+MyProcess::MyProcess(): PProcess("The NuFone Networks",
+ "H.323 Channel Driver for Asterisk",
+ MAJOR_VERSION, MINOR_VERSION, BUILD_TYPE, BUILD_NUMBER)
+{
+ /* Call shutdown when module being unload or asterisk has been stopped */
+ static MyH323_Shutdown x;
+
+ /* Fix missed one in PWLib */
+ PX_firstTimeStart = FALSE;
+ Resume();
+}
+
+MyProcess::~MyProcess()
+{
+ _timerChangePipe[0] = timerChangePipe[0];
+ _timerChangePipe[1] = timerChangePipe[1];
+}
+
+void MyProcess::Main()
+{
+ PTrace::Initialise(PTrace::GetLevel(), NULL, traceOptions);
+ PTrace::SetStream(logstream);
+
+ cout << " == Creating H.323 Endpoint" << endl;
+ if (endPoint) {
+ cout << " == ENDPOINT ALREADY CREATED" << endl;
+ return;
+ }
+ endPoint = new MyH323EndPoint();
+ /* Due to a bug in the H.323 recomendation/stack we should request a sane
+ amount of bandwidth from the GK - this function is ignored if not using a GK
+ We are requesting 128 (64k in each direction), which is the worst case codec. */
+ endPoint->SetInitialBandwidth(1280);
+}
+
+void PAssertFunc(const char *msg)
+{
+ ast_log(LOG_ERROR, "%s\n", msg);
+ /* XXX: Probably we need to crash here */
+}
+
+
+/** MyH323EndPoint
+ */
+MyH323EndPoint::MyH323EndPoint()
+ : H323EndPoint()
+{
+ /* Capabilities will be negotiated on per-connection basis */
+ capabilities.RemoveAll();
+
+ /* Reset call setup timeout to some more reasonable value than 1 minute */
+ signallingChannelCallTimeout = PTimeInterval(0, 0, 10); /* 10 minutes */
+}
+
+/** The fullAddress parameter is used directly in the MakeCall method so
+ * the General form for the fullAddress argument is :
+ * [alias@][transport$]host[:port]
+ * default values: alias = the same value as host.
+ * transport = ip.
+ * port = 1720.
+ */
+int MyH323EndPoint::MyMakeCall(const PString & dest, PString & token, void *_callReference, void *_opts)
+{
+ PString fullAddress;
+ MyH323Connection * connection;
+ H323Transport *transport = NULL;
+ unsigned int *callReference = (unsigned int *)_callReference;
+ call_options_t *opts = (call_options_t *)_opts;
+
+ /* Determine whether we are using a gatekeeper or not. */
+ if (GetGatekeeper()) {
+ fullAddress = dest;
+ if (h323debug) {
+ cout << " -- Making call to " << fullAddress << " using gatekeeper." << endl;
+ }
+ } else {
+ fullAddress = dest;
+ if (h323debug) {
+ cout << " -- Making call to " << fullAddress << " without gatekeeper." << endl;
+ }
+ /* Use bindaddr for outgoing calls too if we don't use gatekeeper */
+ if (listeners.GetSize() > 0) {
+ H323TransportAddress taddr = listeners[0].GetTransportAddress();
+ PIPSocket::Address addr;
+ WORD port;
+ if (taddr.GetIpAndPort(addr, port)) {
+ /* Create own transport for specific addresses only */
+ if (addr) {
+ if (h323debug)
+ cout << "Using " << addr << " for outbound call" << endl;
+ transport = new MyH323TransportTCP(*this, addr);
+ if (!transport)
+ cout << "Unable to create transport for outgoing call" << endl;
+ }
+ } else
+ cout << "Unable to get address and port" << endl;
+ }
+ }
+ if (!(connection = (MyH323Connection *)H323EndPoint::MakeCallLocked(fullAddress, token, opts, transport))) {
+ if (h323debug) {
+ cout << "Error making call to \"" << fullAddress << '"' << endl;
+ }
+ return 1;
+ }
+ *callReference = connection->GetCallReference();
+
+ if (h323debug) {
+ cout << "\t-- " << GetLocalUserName() << " is calling host " << fullAddress << endl;
+ cout << "\t-- Call token is " << (const char *)token << endl;
+ cout << "\t-- Call reference is " << *callReference << endl;
+#ifdef PTRACING
+ cout << "\t-- DTMF Payload is " << connection->dtmfCodec << endl;
+#endif
+ }
+ connection->Unlock();
+ return 0;
+}
+
+void MyH323EndPoint::SetEndpointTypeInfo( H225_EndpointType & info ) const
+{
+ H323EndPoint::SetEndpointTypeInfo(info);
+
+ if (terminalType == e_GatewayOnly){
+ info.RemoveOptionalField(H225_EndpointType::e_terminal);
+ info.IncludeOptionalField(H225_EndpointType::e_gateway);
+ }
+
+ info.m_gateway.IncludeOptionalField(H225_GatewayInfo::e_protocol);
+ info.m_gateway.m_protocol.SetSize(1);
+ H225_SupportedProtocols &protocol=info.m_gateway.m_protocol[0];
+ protocol.SetTag(H225_SupportedProtocols::e_voice);
+ PINDEX as=SupportedPrefixes.GetSize();
+ ((H225_VoiceCaps &)protocol).m_supportedPrefixes.SetSize(as);
+ for (PINDEX p=0; p<as; p++) {
+ H323SetAliasAddress(SupportedPrefixes[p], ((H225_VoiceCaps &)protocol).m_supportedPrefixes[p].m_prefix, H225_AliasAddress::e_dialedDigits);
+ }
+}
+
+void MyH323EndPoint::SetGateway(void)
+{
+ terminalType = e_GatewayOnly;
+}
+
+BOOL MyH323EndPoint::ClearCall(const PString & token, H323Connection::CallEndReason reason)
+{
+ if (h323debug) {
+#ifdef PTRACING
+ cout << "\t-- ClearCall: Request to clear call with token " << token << ", cause " << reason << endl;
+#else
+ cout << "\t-- ClearCall: Request to clear call with token " << token << ", cause [" << (int)reason << "]" << endl;
+#endif
+ }
+ return H323EndPoint::ClearCall(token, reason);
+}
+
+BOOL MyH323EndPoint::ClearCall(const PString & token)
+{
+ if (h323debug) {
+ cout << "\t-- ClearCall: Request to clear call with token " << token << endl;
+ }
+ return H323EndPoint::ClearCall(token, H323Connection::EndedByLocalUser);
+}
+
+void MyH323EndPoint::SendUserTone(const PString &token, char tone)
+{
+ H323Connection *connection = NULL;
+
+ connection = FindConnectionWithLock(token);
+ if (connection != NULL) {
+ connection->SendUserInputTone(tone, 500);
+ connection->Unlock();
+ }
+}
+
+void MyH323EndPoint::OnClosedLogicalChannel(H323Connection & connection, const H323Channel & channel)
+{
+ channelsOpen--;
+ if (h323debug) {
+ cout << "\t\tchannelsOpen = " << channelsOpen << endl;
+ }
+ H323EndPoint::OnClosedLogicalChannel(connection, channel);
+}
+
+BOOL MyH323EndPoint::OnConnectionForwarded(H323Connection & connection,
+ const PString & forwardParty,
+ const H323SignalPDU & pdu)
+{
+ if (h323debug) {
+ cout << "\t-- Call Forwarded to " << forwardParty << endl;
+ }
+ return FALSE;
+}
+
+BOOL MyH323EndPoint::ForwardConnection(H323Connection & connection,
+ const PString & forwardParty,
+ const H323SignalPDU & pdu)
+{
+ if (h323debug) {
+ cout << "\t-- Forwarding call to " << forwardParty << endl;
+ }
+ return H323EndPoint::ForwardConnection(connection, forwardParty, pdu);
+}
+
+void MyH323EndPoint::OnConnectionEstablished(H323Connection & connection, const PString & estCallToken)
+{
+ if (h323debug) {
+ cout << "\t=-= In OnConnectionEstablished for call " << connection.GetCallReference() << endl;
+ cout << "\t\t-- Connection Established with \"" << connection.GetRemotePartyName() << "\"" << endl;
+ }
+ on_connection_established(connection.GetCallReference(), (const char *)connection.GetCallToken());
+}
+
+/** OnConnectionCleared callback function is called upon the dropping of an established
+ * H323 connection.
+ */
+void MyH323EndPoint::OnConnectionCleared(H323Connection & connection, const PString & clearedCallToken)
+{
+ PString remoteName = connection.GetRemotePartyName();
+
+ switch (connection.GetCallEndReason()) {
+ case H323Connection::EndedByCallForwarded:
+ if (h323debug) {
+ cout << "-- " << remoteName << " has forwarded the call" << endl;
+ }
+ break;
+ case H323Connection::EndedByRemoteUser:
+ if (h323debug) {
+ cout << "-- " << remoteName << " has cleared the call" << endl;
+ }
+ break;
+ case H323Connection::EndedByCallerAbort:
+ if (h323debug) {
+ cout << "-- " << remoteName << " has stopped calling" << endl;
+ }
+ break;
+ case H323Connection::EndedByRefusal:
+ if (h323debug) {
+ cout << "-- " << remoteName << " did not accept your call" << endl;
+ }
+ break;
+ case H323Connection::EndedByRemoteBusy:
+ if (h323debug) {
+ cout << "-- " << remoteName << " was busy" << endl;
+ }
+ break;
+ case H323Connection::EndedByRemoteCongestion:
+ if (h323debug) {
+ cout << "-- Congested link to " << remoteName << endl;
+ }
+ break;
+ case H323Connection::EndedByNoAnswer:
+ if (h323debug) {
+ cout << "-- " << remoteName << " did not answer your call" << endl;
+ }
+ break;
+ case H323Connection::EndedByTransportFail:
+ if (h323debug) {
+ cout << "-- Call with " << remoteName << " ended abnormally" << endl;
+ }
+ break;
+ case H323Connection::EndedByCapabilityExchange:
+ if (h323debug) {
+ cout << "-- Could not find common codec with " << remoteName << endl;
+ }
+ break;
+ case H323Connection::EndedByNoAccept:
+ if (h323debug) {
+ cout << "-- Did not accept incoming call from " << remoteName << endl;
+ }
+ break;
+ case H323Connection::EndedByAnswerDenied:
+ if (h323debug) {
+ cout << "-- Refused incoming call from " << remoteName << endl;
+ }
+ break;
+ case H323Connection::EndedByNoUser:
+ if (h323debug) {
+ cout << "-- Remote endpoint could not find user: " << remoteName << endl;
+ }
+ break;
+ case H323Connection::EndedByNoBandwidth:
+ if (h323debug) {
+ cout << "-- Call to " << remoteName << " aborted, insufficient bandwidth." << endl;
+ }
+ break;
+ case H323Connection::EndedByUnreachable:
+ if (h323debug) {
+ cout << "-- " << remoteName << " could not be reached." << endl;
+ }
+ break;
+ case H323Connection::EndedByHostOffline:
+ if (h323debug) {
+ cout << "-- " << remoteName << " is not online." << endl;
+ }
+ break;
+ case H323Connection::EndedByNoEndPoint:
+ if (h323debug) {
+ cout << "-- No phone running for " << remoteName << endl;
+ }
+ break;
+ case H323Connection::EndedByConnectFail:
+ if (h323debug) {
+ cout << "-- Transport error calling " << remoteName << endl;
+ }
+ break;
+ default:
+ if (h323debug) {
+#ifdef PTRACING
+ cout << " -- Call with " << remoteName << " completed (" << connection.GetCallEndReason() << ")" << endl;
+#else
+ cout << " -- Call with " << remoteName << " completed ([" << (int)connection.GetCallEndReason() << "])" << endl;
+#endif
+ }
+ }
+
+ if (connection.IsEstablished()) {
+ if (h323debug) {
+ cout << "\t-- Call duration " << setprecision(0) << setw(5) << (PTime() - connection.GetConnectionStartTime()) << endl;
+ }
+ }
+ /* Invoke the PBX application registered callback */
+ on_connection_cleared(connection.GetCallReference(), clearedCallToken);
+ return;
+}
+
+H323Connection * MyH323EndPoint::CreateConnection(unsigned callReference, void *userData, H323Transport *transport, H323SignalPDU *setupPDU)
+{
+ unsigned options = 0;
+ call_options_t *opts = (call_options_t *)userData;
+ MyH323Connection *conn;
+
+ if (opts && opts->fastStart) {
+ options |= H323Connection::FastStartOptionEnable;
+ } else {
+ options |= H323Connection::FastStartOptionDisable;
+ }
+ if (opts && opts->h245Tunneling) {
+ options |= H323Connection::H245TunnelingOptionEnable;
+ } else {
+ options |= H323Connection::H245TunnelingOptionDisable;
+ }
+/* Disable until I can figure out the proper way to deal with this */
+#if 0
+ if (opts->silenceSuppression) {
+ options |= H323Connection::SilenceSuppresionOptionEnable;
+ } else {
+ options |= H323Connection::SilenceSUppressionOptionDisable;
+ }
+#endif
+ conn = new MyH323Connection(*this, callReference, options);
+ if (conn) {
+ if (opts)
+ conn->SetCallOptions(opts, (setupPDU ? TRUE : FALSE));
+ }
+ return conn;
+}
+
+/* MyH323Connection Implementation */
+MyH323Connection::MyH323Connection(MyH323EndPoint & ep, unsigned callReference,
+ unsigned options)
+ : H323Connection(ep, callReference, options)
+{
+#ifdef H323_H450
+ /* Dispatcher will free out all registered handlers */
+ if (h450dispatcher)
+ delete h450dispatcher;
+ h450dispatcher = new H450xDispatcher(*this);
+ h4502handler = new H4502Handler(*this, *h450dispatcher);
+ h4504handler = new MyH4504Handler(*this, *h450dispatcher);
+ h4506handler = new H4506Handler(*this, *h450dispatcher);
+ h45011handler = new H45011Handler(*this, *h450dispatcher);
+#endif
+ cause = -1;
+ sessionId = 0;
+ bridging = FALSE;
+ holdHandling = progressSetup = progressAlert = 0;
+ dtmfMode = 0;
+ dtmfCodec[0] = dtmfCodec[1] = (RTP_DataFrame::PayloadTypes)0;
+ redirect_reason = -1;
+ transfer_capability = -1;
+#ifdef TUNNELLING
+ tunnelOptions = remoteTunnelOptions = 0;
+#endif
+ if (h323debug) {
+ cout << " == New H.323 Connection created." << endl;
+ }
+ return;
+}
+
+MyH323Connection::~MyH323Connection()
+{
+ if (h323debug) {
+ cout << " == H.323 Connection deleted." << endl;
+ }
+ return;
+}
+
+BOOL MyH323Connection::OnReceivedProgress(const H323SignalPDU &pdu)
+{
+ BOOL isInband;
+ unsigned pi;
+
+ if (!H323Connection::OnReceivedProgress(pdu)) {
+ return FALSE;
+ }
+
+ if (!pdu.GetQ931().GetProgressIndicator(pi))
+ pi = 0;
+ if (h323debug) {
+ cout << "\t- Progress Indicator: " << pi << endl;
+ }
+
+ switch(pi) {
+ case Q931::ProgressNotEndToEndISDN:
+ case Q931::ProgressInbandInformationAvailable:
+ isInband = TRUE;
+ break;
+ default:
+ isInband = FALSE;
+ }
+ on_progress(GetCallReference(), (const char *)GetCallToken(), isInband);
+
+ return connectionState != ShuttingDownConnection;
+}
+
+BOOL MyH323Connection::MySendProgress()
+{
+ /* The code taken from H323Connection::AnsweringCall() but ALWAYS send
+ PROGRESS message, including slow start operations */
+ H323SignalPDU progressPDU;
+ H225_Progress_UUIE &prog = progressPDU.BuildProgress(*this);
+
+ if (!mediaWaitForConnect) {
+ if (SendFastStartAcknowledge(prog.m_fastStart))
+ prog.IncludeOptionalField(H225_Progress_UUIE::e_fastStart);
+ else {
+ if (connectionState == ShuttingDownConnection)
+ return FALSE;
+
+ /* Do early H.245 start */
+ earlyStart = TRUE;
+ if (!h245Tunneling) {
+ if (!H323Connection::StartControlChannel())
+ return FALSE;
+ prog.IncludeOptionalField(H225_Progress_UUIE::e_h245Address);
+ controlChannel->SetUpTransportPDU(prog.m_h245Address, TRUE);
+ }
+ }
+ }
+ progressPDU.GetQ931().SetProgressIndicator(Q931::ProgressInbandInformationAvailable);
+
+#ifdef TUNNELLING
+ EmbedTunneledInfo(progressPDU);
+#endif
+ HandleTunnelPDU(&progressPDU);
+ WriteSignalPDU(progressPDU);
+
+ return TRUE;
+}
+
+H323Connection::AnswerCallResponse MyH323Connection::OnAnswerCall(const PString & caller,
+ const H323SignalPDU & setupPDU,
+ H323SignalPDU & connectPDU)
+{
+ unsigned pi;
+
+ if (h323debug) {
+ cout << "\t=-= In OnAnswerCall for call " << GetCallReference() << endl;
+ }
+
+ if (connectionState == ShuttingDownConnection)
+ return H323Connection::AnswerCallDenied;
+
+ if (!setupPDU.GetQ931().GetProgressIndicator(pi)) {
+ pi = 0;
+ }
+ if (h323debug) {
+ cout << "\t\t- Progress Indicator: " << pi << endl;
+ }
+ if (progressAlert) {
+ pi = progressAlert;
+ } else if (pi == Q931::ProgressOriginNotISDN) {
+ pi = Q931::ProgressInbandInformationAvailable;
+ }
+ if (pi && alertingPDU) {
+ alertingPDU->GetQ931().SetProgressIndicator(pi);
+ }
+ if (h323debug) {
+ cout << "\t\t- Inserting PI of " << pi << " into ALERTING message" << endl;
+ }
+
+#ifdef TUNNELLING
+ if (alertingPDU)
+ EmbedTunneledInfo(*alertingPDU);
+ EmbedTunneledInfo(connectPDU);
+#endif
+
+ if (!on_answer_call(GetCallReference(), (const char *)GetCallToken())) {
+ return H323Connection::AnswerCallDenied;
+ }
+ /* The call will be answered later with "AnsweringCall()" function.
+ */
+ return ((pi || (fastStartState != FastStartDisabled)) ? AnswerCallDeferredWithMedia : AnswerCallDeferred);
+}
+
+BOOL MyH323Connection::OnAlerting(const H323SignalPDU & alertingPDU, const PString & username)
+{
+ if (h323debug) {
+ cout << "\t=-= In OnAlerting for call " << GetCallReference()
+ << ": sessionId=" << sessionId << endl;
+ cout << "\t-- Ringing phone for \"" << username << "\"" << endl;
+ }
+
+ if (on_progress) {
+ BOOL isInband;
+ unsigned alertingPI;
+
+ if (!alertingPDU.GetQ931().GetProgressIndicator(alertingPI)) {
+ alertingPI = 0;
+ }
+ if (h323debug) {
+ cout << "\t\t- Progress Indicator: " << alertingPI << endl;
+ }
+
+ switch(alertingPI) {
+ case Q931::ProgressNotEndToEndISDN:
+ case Q931::ProgressInbandInformationAvailable:
+ isInband = TRUE;
+ break;
+ default:
+ isInband = FALSE;
+ }
+ on_progress(GetCallReference(), (const char *)GetCallToken(), isInband);
+ }
+ on_chan_ringing(GetCallReference(), (const char *)GetCallToken() );
+ return connectionState != ShuttingDownConnection;
+}
+
+void MyH323Connection::SetCallOptions(void *o, BOOL isIncoming)
+{
+ call_options_t *opts = (call_options_t *)o;
+
+ progressSetup = opts->progress_setup;
+ progressAlert = opts->progress_alert;
+ holdHandling = opts->holdHandling;
+ dtmfCodec[0] = (RTP_DataFrame::PayloadTypes)opts->dtmfcodec[0];
+ dtmfCodec[1] = (RTP_DataFrame::PayloadTypes)opts->dtmfcodec[1];
+ dtmfMode = opts->dtmfmode;
+
+ if (isIncoming) {
+ fastStartState = (opts->fastStart ? FastStartInitiate : FastStartDisabled);
+ h245Tunneling = (opts->h245Tunneling ? TRUE : FALSE);
+ } else {
+ SetLocalPartyName(PString(opts->cid_num));
+ SetDisplayName(PString(opts->cid_name));
+ if (opts->redirect_reason >= 0) {
+ rdnis = PString(opts->cid_rdnis);
+ redirect_reason = opts->redirect_reason;
+ }
+ cid_presentation = opts->presentation;
+ cid_ton = opts->type_of_number;
+ if (opts->transfer_capability >= 0) {
+ transfer_capability = opts->transfer_capability;
+ }
+ }
+ tunnelOptions = opts->tunnelOptions;
+}
+
+void MyH323Connection::SetCallDetails(void *callDetails, const H323SignalPDU &setupPDU, BOOL isIncoming)
+{
+ PString sourceE164;
+ PString destE164;
+ PString sourceAliases;
+ PString destAliases;
+ char *s, *s1;
+ call_details_t *cd = (call_details_t *)callDetails;
+
+ memset(cd, 0, sizeof(*cd));
+ cd->call_reference = GetCallReference();
+ cd->call_token = strdup((const char *)GetCallToken());
+
+ sourceE164 = "";
+ setupPDU.GetSourceE164(sourceE164);
+ cd->call_source_e164 = strdup((const char *)sourceE164);
+
+ destE164 = "";
+ setupPDU.GetDestinationE164(destE164);
+ cd->call_dest_e164 = strdup((const char *)destE164);
+
+ /* XXX Is it possible to have this information for outgoing calls too? XXX */
+ if (isIncoming) {
+ PString sourceName;
+ PIPSocket::Address Ip;
+ WORD sourcePort;
+ PString redirect_number;
+ unsigned redirect_reason;
+ unsigned plan, type, screening, presentation;
+ Q931::InformationTransferCapability capability;
+ unsigned transferRate, codingStandard, userInfoLayer1;
+
+ /* Fetch presentation and type information about calling party's number */
+ if (setupPDU.GetQ931().GetCallingPartyNumber(sourceName, &plan, &type, &presentation, &screening, 0, 0)) {
+ /* Construct fields back */
+ cd->type_of_number = (type << 4) | plan;
+ cd->presentation = (presentation << 5) | screening;
+ } else if (cd->call_source_e164[0]) {
+ cd->type_of_number = 0; /* UNKNOWN */
+ cd->presentation = 0x03; /* ALLOWED NETWORK NUMBER - Default */
+ if (setupPDU.GetQ931().HasIE(Q931::UserUserIE)) {
+ const H225_Setup_UUIE &setup_uuie = setupPDU.m_h323_uu_pdu.m_h323_message_body;
+ if (setup_uuie.HasOptionalField(H225_Setup_UUIE::e_presentationIndicator))
+ cd->presentation = (cd->presentation & 0x9f) | (((unsigned int)setup_uuie.m_presentationIndicator.GetTag()) << 5);
+ if (setup_uuie.HasOptionalField(H225_Setup_UUIE::e_screeningIndicator))
+ cd->presentation = (cd->presentation & 0xe0) | (((unsigned int)setup_uuie.m_screeningIndicator.GetValue()) & 0x1f);
+ }
+ } else {
+ cd->type_of_number = 0; /* UNKNOWN */
+ cd->presentation = 0x43; /* NUMBER NOT AVAILABLE */
+ }
+
+ sourceName = setupPDU.GetQ931().GetDisplayName();
+ cd->call_source_name = strdup((const char *)sourceName);
+
+ GetSignallingChannel()->GetRemoteAddress().GetIpAndPort(Ip, sourcePort);
+ cd->sourceIp = strdup((const char *)Ip.AsString());
+
+ if (setupPDU.GetQ931().GetRedirectingNumber(redirect_number, NULL, NULL, NULL, NULL, &redirect_reason, 0, 0, 0)) {
+ cd->redirect_number = strdup((const char *)redirect_number);
+ cd->redirect_reason = redirect_reason;
+ }
+ else
+ cd->redirect_reason = -1;
+
+ /* Fetch Q.931's transfer capability */
+ if (((Q931 &)setupPDU.GetQ931()).GetBearerCapabilities(capability, transferRate, &codingStandard, &userInfoLayer1))
+ cd->transfer_capability = ((unsigned int)capability & 0x1f) | (codingStandard << 5);
+ else
+ cd->transfer_capability = 0x00; /* ITU coding of Speech */
+
+ /* Don't show local username as called party name */
+ SetDisplayName(cd->call_dest_e164);
+ }
+
+ /* Convert complex strings */
+ // FIXME: deal more than one source alias
+ sourceAliases = setupPDU.GetSourceAliases();
+ s1 = strdup((const char *)sourceAliases);
+ if ((s = strchr(s1, ' ')) != NULL)
+ *s = '\0';
+ if ((s = strchr(s1, '\t')) != NULL)
+ *s = '\0';
+ cd->call_source_aliases = s1;
+
+ destAliases = setupPDU.GetDestinationAlias();
+ s1 = strdup((const char *)destAliases);
+ if ((s = strchr(s1, ' ')) != NULL)
+ *s = '\0';
+ if ((s = strchr(s1, '\t')) != NULL)
+ *s = '\0';
+ cd->call_dest_alias = s1;
+}
+
+#ifdef TUNNELLING
+static BOOL FetchInformationElements(Q931 &q931, const PBYTEArray &data)
+{
+ PINDEX offset = 0;
+
+ while (offset < data.GetSize()) {
+ // Get field discriminator
+ int discriminator = data[offset++];
+
+#if 0
+ /* Do not overwrite existing IEs */
+ if (q931.HasIE((Q931::InformationElementCodes)discriminator)) {
+ if ((discriminatir & 0x80) == 0)
+ offset += data[offset++];
+ if (offset > data.GetSize())
+ return FALSE;
+ continue;
+ }
+#endif
+
+ PBYTEArray * item = new PBYTEArray;
+
+ // For discriminator with high bit set there is no data
+ if ((discriminator & 0x80) == 0) {
+ int len = data[offset++];
+
+#if 0 // That is not H.225 but regular Q.931 (ISDN) IEs
+ if (discriminator == UserUserIE) {
+ // Special case of User-user field. See 7.2.2.31/H.225.0v4.
+ len <<= 8;
+ len |= data[offset++];
+
+ // we also have a protocol discriminator, which we ignore
+ offset++;
+
+ // before decrementing the length, make sure it is not zero
+ if (len == 0)
+ return FALSE;
+
+ // adjust for protocol discriminator
+ len--;
+ }
+#endif
+
+ if (offset + len > data.GetSize()) {
+ delete item;
+ return FALSE;
+ }
+
+ memcpy(item->GetPointer(len), (const BYTE *)data+offset, len);
+ offset += len;
+ }
+
+ q931.SetIE((Q931::InformationElementCodes)discriminator, *item);
+ delete item;
+ }
+ return TRUE;
+}
+
+static BOOL FetchCiscoTunneledInfo(Q931 &q931, const H323SignalPDU &pdu)
+{
+ BOOL res = FALSE;
+ const H225_H323_UU_PDU &uuPDU = pdu.m_h323_uu_pdu;
+
+ if(uuPDU.HasOptionalField(H225_H323_UU_PDU::e_nonStandardControl)) {
+ for(int i = 0; i < uuPDU.m_nonStandardControl.GetSize(); ++i) {
+ const H225_NonStandardParameter &np = uuPDU.m_nonStandardControl[i];
+ const H225_NonStandardIdentifier &id = np.m_nonStandardIdentifier;
+ if (id.GetTag() == H225_NonStandardIdentifier::e_h221NonStandard) {
+ const H225_H221NonStandard &ni = id;
+ /* Check for Cisco */
+ if ((ni.m_t35CountryCode == 181) && (ni.m_t35Extension == 0) && (ni.m_manufacturerCode == 18)) {
+ const PBYTEArray &data = np.m_data;
+ if (h323debug)
+ cout << setprecision(0) << "Received non-standard Cisco extension data " << np.m_data << endl;
+ CISCO_H225_H323_UU_NonStdInfo c;
+ PPER_Stream strm(data);
+ if (c.Decode(strm)) {
+ BOOL haveIEs = FALSE;
+ if (h323debug)
+ cout << setprecision(0) << "H323_UU_NonStdInfo = " << c << endl;
+ if (c.HasOptionalField(CISCO_H225_H323_UU_NonStdInfo::e_protoParam)) {
+ FetchInformationElements(q931, c.m_protoParam.m_qsigNonStdInfo.m_rawMesg);
+ haveIEs = TRUE;
+ }
+ if (c.HasOptionalField(CISCO_H225_H323_UU_NonStdInfo::e_commonParam)) {
+ FetchInformationElements(q931, c.m_commonParam.m_redirectIEinfo.m_redirectIE);
+ haveIEs = TRUE;
+ }
+ if (haveIEs && h323debug)
+ cout << setprecision(0) << "Information elements collected:" << q931 << endl;
+ res = TRUE;
+ } else {
+ cout << "ERROR while decoding non-standard Cisco extension" << endl;
+ return FALSE;
+ }
+ }
+ }
+ }
+ }
+ return res;
+}
+
+static BOOL EmbedCiscoTunneledInfo(H323SignalPDU &pdu)
+{
+ const static struct {
+ Q931::InformationElementCodes ie;
+ BOOL dontDelete;
+ } codes[] = {
+ { Q931::RedirectingNumberIE, },
+ { Q931::FacilityIE, },
+// { Q931::CallingPartyNumberIE, TRUE },
+ };
+
+ BOOL res = FALSE;
+ BOOL notRedirOnly = FALSE;
+ Q931 tmpQ931;
+ Q931 &q931 = pdu.GetQ931();
+
+ for(unsigned i = 0; i < (sizeof(codes) / sizeof(codes[0])); ++i) {
+ if (q931.HasIE(codes[i].ie)) {
+ tmpQ931.SetIE(codes[i].ie, q931.GetIE(codes[i].ie));
+ if (!codes[i].dontDelete)
+ q931.RemoveIE(codes[i].ie);
+ if (codes[i].ie != Q931::RedirectingNumberIE)
+ notRedirOnly = TRUE;
+ res = TRUE;
+ }
+ }
+ /* Have something to embed */
+ if (res) {
+ PBYTEArray msg;
+ if (!tmpQ931.Encode(msg))
+ return FALSE;
+ PBYTEArray ies(msg.GetPointer() + 5, msg.GetSize() - 5);
+
+ H225_H323_UU_PDU &uuPDU = pdu.m_h323_uu_pdu;
+ if(!uuPDU.HasOptionalField(H225_H323_UU_PDU::e_nonStandardControl)) {
+ uuPDU.IncludeOptionalField(H225_H323_UU_PDU::e_nonStandardControl);
+ uuPDU.m_nonStandardControl.SetSize(0);
+ }
+ H225_NonStandardParameter *np = new H225_NonStandardParameter;
+ uuPDU.m_nonStandardControl.Append(np);
+ H225_NonStandardIdentifier &nsi = (*np).m_nonStandardIdentifier;
+ nsi.SetTag(H225_NonStandardIdentifier::e_h221NonStandard);
+ H225_H221NonStandard &ns = nsi;
+ ns.m_t35CountryCode = 181;
+ ns.m_t35Extension = 0;
+ ns.m_manufacturerCode = 18;
+
+ CISCO_H225_H323_UU_NonStdInfo c;
+ c.IncludeOptionalField(CISCO_H225_H323_UU_NonStdInfo::e_version);
+ c.m_version = 0;
+
+ if (notRedirOnly) {
+ c.IncludeOptionalField(CISCO_H225_H323_UU_NonStdInfo::e_protoParam);
+ CISCO_H225_QsigNonStdInfo &qsigInfo = c.m_protoParam.m_qsigNonStdInfo;
+ qsigInfo.m_iei = ies[0];
+ qsigInfo.m_rawMesg = ies;
+ } else {
+ c.IncludeOptionalField(CISCO_H225_H323_UU_NonStdInfo::e_commonParam);
+ c.m_commonParam.m_redirectIEinfo.m_redirectIE = ies;
+ }
+ PPER_Stream stream;
+ c.Encode(stream);
+ stream.CompleteEncoding();
+ (*np).m_data = stream;
+ }
+ return res;
+}
+
+static const char OID_QSIG[] = "1.3.12.9";
+
+static BOOL FetchQSIGTunneledInfo(Q931 &q931, const H323SignalPDU &pdu)
+{
+ BOOL res = FALSE;
+ const H225_H323_UU_PDU &uuPDU = pdu.m_h323_uu_pdu;
+ if (uuPDU.HasOptionalField(H225_H323_UU_PDU::e_tunnelledSignallingMessage)) {
+ const H225_H323_UU_PDU_tunnelledSignallingMessage &sig = uuPDU.m_tunnelledSignallingMessage;
+ const H225_TunnelledProtocol_id &proto = sig.m_tunnelledProtocolID.m_id;
+ if ((proto.GetTag() == H225_TunnelledProtocol_id::e_tunnelledProtocolObjectID) &&
+ (((const PASN_ObjectId &)proto).AsString() == OID_QSIG)) {
+ const H225_ArrayOf_PASN_OctetString &sigs = sig.m_messageContent;
+ for(int i = 0; i < sigs.GetSize(); ++i) {
+ const PASN_OctetString &msg = sigs[i];
+ if (h323debug)
+ cout << setprecision(0) << "Q.931 message data is " << msg << endl;
+ if(!q931.Decode((const PBYTEArray &)msg)) {
+ cout << "Error while decoding Q.931 message" << endl;
+ return FALSE;
+ }
+ res = TRUE;
+ if (h323debug)
+ cout << setprecision(0) << "Received QSIG message " << q931 << endl;
+ }
+ }
+ }
+ return res;
+}
+
+static H225_EndpointType *GetEndpointType(H323SignalPDU &pdu)
+{
+ if (!pdu.GetQ931().HasIE(Q931::UserUserIE))
+ return NULL;
+
+ H225_H323_UU_PDU_h323_message_body &body = pdu.m_h323_uu_pdu.m_h323_message_body;
+ switch (body.GetTag()) {
+ case H225_H323_UU_PDU_h323_message_body::e_setup:
+ return &((H225_Setup_UUIE &)body).m_sourceInfo;
+ case H225_H323_UU_PDU_h323_message_body::e_callProceeding:
+ return &((H225_CallProceeding_UUIE &)body).m_destinationInfo;
+ case H225_H323_UU_PDU_h323_message_body::e_connect:
+ return &((H225_Connect_UUIE &)body).m_destinationInfo;
+ case H225_H323_UU_PDU_h323_message_body::e_alerting:
+ return &((H225_Alerting_UUIE &)body).m_destinationInfo;
+ case H225_H323_UU_PDU_h323_message_body::e_facility:
+ return &((H225_Facility_UUIE &)body).m_destinationInfo;
+ case H225_H323_UU_PDU_h323_message_body::e_progress:
+ return &((H225_Progress_UUIE &)body).m_destinationInfo;
+ }
+ return NULL;
+}
+
+static BOOL QSIGTunnelRequested(H323SignalPDU &pdu)
+{
+ H225_EndpointType *epType = GetEndpointType(pdu);
+ if (epType) {
+ if (!(*epType).HasOptionalField(H225_EndpointType::e_supportedTunnelledProtocols)) {
+ return FALSE;
+ }
+ H225_ArrayOf_TunnelledProtocol &protos = (*epType).m_supportedTunnelledProtocols;
+ for (int i = 0; i < protos.GetSize(); ++i)
+ {
+ if ((protos[i].GetTag() == H225_TunnelledProtocol_id::e_tunnelledProtocolObjectID) &&
+ (((const PASN_ObjectId &)protos[i]).AsString() == OID_QSIG)) {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+static BOOL EmbedQSIGTunneledInfo(H323SignalPDU &pdu)
+{
+ const static Q931::InformationElementCodes codes[] =
+ { Q931::RedirectingNumberIE, Q931::FacilityIE };
+
+ Q931 &q931 = pdu.GetQ931();
+ PBYTEArray message;
+
+ q931.Encode(message);
+
+ /* Remove non-standard IEs */
+ for(unsigned i = 0; i < (sizeof(codes) / sizeof(codes[0])); ++i) {
+ if (q931.HasIE(codes[i])) {
+ q931.RemoveIE(codes[i]);
+ }
+ }
+
+ H225_H323_UU_PDU &uuPDU = pdu.m_h323_uu_pdu;
+ H225_EndpointType *epType = GetEndpointType(pdu);
+ if (epType) {
+ if (!(*epType).HasOptionalField(H225_EndpointType::e_supportedTunnelledProtocols)) {
+ (*epType).IncludeOptionalField(H225_EndpointType::e_supportedTunnelledProtocols);
+ (*epType).m_supportedTunnelledProtocols.SetSize(0);
+ }
+ H225_ArrayOf_TunnelledProtocol &protos = (*epType).m_supportedTunnelledProtocols;
+ BOOL addQSIG = TRUE;
+ for (int i = 0; i < protos.GetSize(); ++i)
+ {
+ if ((protos[i].GetTag() == H225_TunnelledProtocol_id::e_tunnelledProtocolObjectID) &&
+ (((PASN_ObjectId &)protos[i]).AsString() == OID_QSIG)) {
+ addQSIG = FALSE;
+ break;
+ }
+ }
+ if (addQSIG) {
+ H225_TunnelledProtocol *proto = new H225_TunnelledProtocol;
+ (*proto).m_id.SetTag(H225_TunnelledProtocol_id::e_tunnelledProtocolObjectID);
+ (PASN_ObjectId &)(proto->m_id) = OID_QSIG;
+ protos.Append(proto);
+ }
+ }
+ if (!uuPDU.HasOptionalField(H225_H323_UU_PDU::e_tunnelledSignallingMessage))
+ uuPDU.IncludeOptionalField(H225_H323_UU_PDU::e_tunnelledSignallingMessage);
+ H225_H323_UU_PDU_tunnelledSignallingMessage &sig = uuPDU.m_tunnelledSignallingMessage;
+ H225_TunnelledProtocol_id &proto = sig.m_tunnelledProtocolID.m_id;
+ if ((proto.GetTag() != H225_TunnelledProtocol_id::e_tunnelledProtocolObjectID) ||
+ (((const PASN_ObjectId &)proto).AsString() != OID_QSIG)) {
+ proto.SetTag(H225_TunnelledProtocol_id::e_tunnelledProtocolObjectID);
+ (PASN_ObjectId &)proto = OID_QSIG;
+ sig.m_messageContent.SetSize(0);
+ }
+ PASN_OctetString *msg = new PASN_OctetString;
+ sig.m_messageContent.Append(msg);
+ *msg = message;
+ return TRUE;
+}
+
+BOOL MyH323Connection::EmbedTunneledInfo(H323SignalPDU &pdu)
+{
+ if ((tunnelOptions & H323_TUNNEL_QSIG) || (remoteTunnelOptions & H323_TUNNEL_QSIG))
+ EmbedQSIGTunneledInfo(pdu);
+ if ((tunnelOptions & H323_TUNNEL_CISCO) || (remoteTunnelOptions & H323_TUNNEL_CISCO))
+ EmbedCiscoTunneledInfo(pdu);
+
+ return TRUE;
+}
+
+/* Handle tunneled messages */
+BOOL MyH323Connection::HandleSignalPDU(H323SignalPDU &pdu)
+{
+ if (pdu.GetQ931().HasIE(Q931::UserUserIE)) {
+ Q931 tunneledInfo;
+ const Q931 *q931Info;
+
+ q931Info = NULL;
+ if (FetchCiscoTunneledInfo(tunneledInfo, pdu)) {
+ q931Info = &tunneledInfo;
+ remoteTunnelOptions |= H323_TUNNEL_CISCO;
+ }
+ if (FetchQSIGTunneledInfo(tunneledInfo, pdu)) {
+ q931Info = &tunneledInfo;
+ remoteTunnelOptions |= H323_TUNNEL_QSIG;
+ }
+ if (!(remoteTunnelOptions & H323_TUNNEL_QSIG) && QSIGTunnelRequested(pdu)) {
+ remoteTunnelOptions |= H323_TUNNEL_QSIG;
+ }
+ if (q931Info) {
+ if (q931Info->HasIE(Q931::RedirectingNumberIE)) {
+ pdu.GetQ931().SetIE(Q931::RedirectingNumberIE, q931Info->GetIE(Q931::RedirectingNumberIE));
+ if (h323debug) {
+ PString number;
+ unsigned reason;
+ if(q931Info->GetRedirectingNumber(number, NULL, NULL, NULL, NULL, &reason, 0, 0, 0))
+ cout << "Got redirection from " << number << ", reason " << reason << endl;
+ }
+ }
+ }
+ }
+
+ return H323Connection::HandleSignalPDU(pdu);
+}
+#endif
+
+BOOL MyH323Connection::OnReceivedSignalSetup(const H323SignalPDU & setupPDU)
+{
+ call_details_t cd;
+
+ if (h323debug) {
+ cout << "\t--Received SETUP message" << endl;
+ }
+
+ if (connectionState == ShuttingDownConnection)
+ return FALSE;
+
+ SetCallDetails(&cd, setupPDU, TRUE);
+
+ /* Notify Asterisk of the request */
+ call_options_t *res = on_incoming_call(&cd);
+
+ if (!res) {
+ if (h323debug) {
+ cout << "\t-- Call Failed" << endl;
+ }
+ return FALSE;
+ }
+
+ SetCallOptions(res, TRUE);
+
+ /* Disable fastStart if requested by remote side */
+ if (h245Tunneling && !setupPDU.m_h323_uu_pdu.m_h245Tunneling) {
+ masterSlaveDeterminationProcedure->Stop();
+ capabilityExchangeProcedure->Stop();
+ PTRACE(3, "H225\tFast Start DISABLED!");
+ h245Tunneling = FALSE;
+ }
+
+ return H323Connection::OnReceivedSignalSetup(setupPDU);
+}
+
+BOOL MyH323Connection::OnSendSignalSetup(H323SignalPDU & setupPDU)
+{
+ call_details_t cd;
+
+ if (h323debug) {
+ cout << "\t-- Sending SETUP message" << endl;
+ }
+
+ if (connectionState == ShuttingDownConnection)
+ return FALSE;
+
+ if (progressSetup)
+ setupPDU.GetQ931().SetProgressIndicator(progressSetup);
+
+ if (redirect_reason >= 0) {
+ setupPDU.GetQ931().SetRedirectingNumber(rdnis, 0, 0, 0, 0, redirect_reason);
+ /* OpenH323 incorrectly fills number IE when redirecting reason is specified - fix it */
+ PBYTEArray IE(setupPDU.GetQ931().GetIE(Q931::RedirectingNumberIE));
+ IE[0] = IE[0] & 0x7f;
+ IE[1] = IE[1] & 0x7f;
+ setupPDU.GetQ931().SetIE(Q931::RedirectingNumberIE, IE);
+ }
+
+ if (transfer_capability)
+ setupPDU.GetQ931().SetBearerCapabilities((Q931::InformationTransferCapability)(transfer_capability & 0x1f), 1, ((transfer_capability >> 5) & 3));
+
+ SetCallDetails(&cd, setupPDU, FALSE);
+
+ int res = on_outgoing_call(&cd);
+ if (!res) {
+ if (h323debug) {
+ cout << "\t-- Call Failed" << endl;
+ }
+ return FALSE;
+ }
+
+ /* OpenH323 will build calling party information with default
+ type and presentation information, so build it to be recorded
+ by embedding routines */
+ setupPDU.GetQ931().SetCallingPartyNumber(GetLocalPartyName(), (cid_ton >> 4) & 0x07,
+ cid_ton & 0x0f, (cid_presentation >> 5) & 0x03, cid_presentation & 0x1f);
+ setupPDU.GetQ931().SetDisplayName(GetDisplayName());
+
+#ifdef TUNNELLING
+ EmbedTunneledInfo(setupPDU);
+#endif
+
+ return H323Connection::OnSendSignalSetup(setupPDU);
+}
+
+static BOOL BuildFastStartList(const H323Channel & channel,
+ H225_ArrayOf_PASN_OctetString & array,
+ H323Channel::Directions reverseDirection)
+{
+ H245_OpenLogicalChannel open;
+ const H323Capability & capability = channel.GetCapability();
+
+ if (channel.GetDirection() != reverseDirection) {
+ if (!capability.OnSendingPDU(open.m_forwardLogicalChannelParameters.m_dataType))
+ return FALSE;
+ }
+ else {
+ if (!capability.OnSendingPDU(open.m_reverseLogicalChannelParameters.m_dataType))
+ return FALSE;
+
+ open.m_forwardLogicalChannelParameters.m_multiplexParameters.SetTag(
+ H245_OpenLogicalChannel_forwardLogicalChannelParameters_multiplexParameters::e_none);
+ open.m_forwardLogicalChannelParameters.m_dataType.SetTag(H245_DataType::e_nullData);
+ open.IncludeOptionalField(H245_OpenLogicalChannel::e_reverseLogicalChannelParameters);
+ }
+
+ if (!channel.OnSendingPDU(open))
+ return FALSE;
+
+ PTRACE(4, "H225\tBuild fastStart:\n " << setprecision(2) << open);
+ PINDEX last = array.GetSize();
+ array.SetSize(last+1);
+ array[last].EncodeSubType(open);
+
+ PTRACE(3, "H225\tBuilt fastStart for " << capability);
+ return TRUE;
+}
+
+H323Connection::CallEndReason MyH323Connection::SendSignalSetup(const PString & alias,
+ const H323TransportAddress & address)
+{
+ // Start the call, first state is asking gatekeeper
+ connectionState = AwaitingGatekeeperAdmission;
+
+ // Indicate the direction of call.
+ if (alias.IsEmpty())
+ remotePartyName = remotePartyAddress = address;
+ else {
+ remotePartyName = alias;
+ remotePartyAddress = alias + '@' + address;
+ }
+
+ // Start building the setup PDU to get various ID's
+ H323SignalPDU setupPDU;
+ H225_Setup_UUIE & setup = setupPDU.BuildSetup(*this, address);
+
+#ifdef H323_H450
+ h450dispatcher->AttachToSetup(setupPDU);
+#endif
+
+ // Save the identifiers generated by BuildSetup
+ setupPDU.GetQ931().GetCalledPartyNumber(remotePartyNumber);
+
+ H323TransportAddress gatekeeperRoute = address;
+
+ // Check for gatekeeper and do admission check if have one
+ H323Gatekeeper * gatekeeper = endpoint.GetGatekeeper();
+ H225_ArrayOf_AliasAddress newAliasAddresses;
+ if (gatekeeper != NULL) {
+ H323Gatekeeper::AdmissionResponse response;
+ response.transportAddress = &gatekeeperRoute;
+ response.aliasAddresses = &newAliasAddresses;
+ if (!gkAccessTokenOID)
+ response.accessTokenData = &gkAccessTokenData;
+ while (!gatekeeper->AdmissionRequest(*this, response, alias.IsEmpty())) {
+ PTRACE(1, "H225\tGatekeeper refused admission: "
+ << (response.rejectReason == UINT_MAX
+ ? PString("Transport error")
+ : H225_AdmissionRejectReason(response.rejectReason).GetTagName()));
+#ifdef H323_H450
+ h4502handler->onReceivedAdmissionReject(H4501_GeneralErrorList::e_notAvailable);
+#endif
+
+ switch (response.rejectReason) {
+ case H225_AdmissionRejectReason::e_calledPartyNotRegistered:
+ return EndedByNoUser;
+ case H225_AdmissionRejectReason::e_requestDenied:
+ return EndedByNoBandwidth;
+ case H225_AdmissionRejectReason::e_invalidPermission:
+ case H225_AdmissionRejectReason::e_securityDenial:
+ return EndedBySecurityDenial;
+ case H225_AdmissionRejectReason::e_resourceUnavailable:
+ return EndedByRemoteBusy;
+ case H225_AdmissionRejectReason::e_incompleteAddress:
+ if (OnInsufficientDigits())
+ break;
+ // Then default case
+ default:
+ return EndedByGatekeeper;
+ }
+
+ PString lastRemotePartyName = remotePartyName;
+ while (lastRemotePartyName == remotePartyName) {
+ Unlock(); // Release the mutex as can deadlock trying to clear call during connect.
+ digitsWaitFlag.Wait();
+ if (!Lock()) // Lock while checking for shutting down.
+ return EndedByCallerAbort;
+ }
+ }
+ mustSendDRQ = TRUE;
+ if (response.gatekeeperRouted) {
+ setup.IncludeOptionalField(H225_Setup_UUIE::e_endpointIdentifier);
+ setup.m_endpointIdentifier = gatekeeper->GetEndpointIdentifier();
+ gatekeeperRouted = TRUE;
+ }
+ }
+
+#ifdef H323_TRANSNEXUS_OSP
+ // check for OSP server (if not using GK)
+ if (gatekeeper == NULL) {
+ OpalOSP::Provider * ospProvider = endpoint.GetOSPProvider();
+ if (ospProvider != NULL) {
+ OpalOSP::Transaction * transaction = new OpalOSP::Transaction();
+ if (transaction->Open(*ospProvider) != 0) {
+ PTRACE(1, "H225\tCannot create OSP transaction");
+ return EndedByOSPRefusal;
+ }
+
+ OpalOSP::Transaction::DestinationInfo destInfo;
+ if (!AuthoriseOSPTransaction(*transaction, destInfo)) {
+ delete transaction;
+ return EndedByOSPRefusal;
+ }
+
+ // save the transaction for use by the call
+ ospTransaction = transaction;
+
+ // retreive the call information
+ gatekeeperRoute = destInfo.destinationAddress;
+ newAliasAddresses.Append(new H225_AliasAddress(destInfo.calledNumber));
+
+ // insert the token
+ setup.IncludeOptionalField(H225_Setup_UUIE::e_tokens);
+ destInfo.InsertToken(setup.m_tokens);
+ }
+ }
+#endif
+
+ // Update the field e_destinationAddress in the SETUP PDU to reflect the new
+ // alias received in the ACF (m_destinationInfo).
+ if (newAliasAddresses.GetSize() > 0) {
+ setup.IncludeOptionalField(H225_Setup_UUIE::e_destinationAddress);
+ setup.m_destinationAddress = newAliasAddresses;
+
+ // Update the Q.931 Information Element (if is an E.164 address)
+ PString e164 = H323GetAliasAddressE164(newAliasAddresses);
+ if (!e164)
+ remotePartyNumber = e164;
+ }
+
+ if (addAccessTokenToSetup && !gkAccessTokenOID && !gkAccessTokenData.IsEmpty()) {
+ PString oid1, oid2;
+ PINDEX comma = gkAccessTokenOID.Find(',');
+ if (comma == P_MAX_INDEX)
+ oid1 = oid2 = gkAccessTokenOID;
+ else {
+ oid1 = gkAccessTokenOID.Left(comma);
+ oid2 = gkAccessTokenOID.Mid(comma+1);
+ }
+ setup.IncludeOptionalField(H225_Setup_UUIE::e_tokens);
+ PINDEX last = setup.m_tokens.GetSize();
+ setup.m_tokens.SetSize(last+1);
+ setup.m_tokens[last].m_tokenOID = oid1;
+ setup.m_tokens[last].IncludeOptionalField(H235_ClearToken::e_nonStandard);
+ setup.m_tokens[last].m_nonStandard.m_nonStandardIdentifier = oid2;
+ setup.m_tokens[last].m_nonStandard.m_data = gkAccessTokenData;
+ }
+
+ if (!signallingChannel->SetRemoteAddress(gatekeeperRoute)) {
+ PTRACE(1, "H225\tInvalid "
+ << (gatekeeperRoute != address ? "gatekeeper" : "user")
+ << " supplied address: \"" << gatekeeperRoute << '"');
+ connectionState = AwaitingTransportConnect;
+ return EndedByConnectFail;
+ }
+
+ // Do the transport connect
+ connectionState = AwaitingTransportConnect;
+
+ // Release the mutex as can deadlock trying to clear call during connect.
+ Unlock();
+
+ signallingChannel->SetWriteTimeout(100);
+
+ BOOL connectFailed = !signallingChannel->Connect();
+
+ // Lock while checking for shutting down.
+ if (!Lock())
+ return EndedByCallerAbort;
+
+ // See if transport connect failed, abort if so.
+ if (connectFailed) {
+ connectionState = NoConnectionActive;
+ switch (signallingChannel->GetErrorNumber()) {
+ case ENETUNREACH :
+ return EndedByUnreachable;
+ case ECONNREFUSED :
+ return EndedByNoEndPoint;
+ case ETIMEDOUT :
+ return EndedByHostOffline;
+ }
+ return EndedByConnectFail;
+ }
+
+ PTRACE(3, "H225\tSending Setup PDU");
+ connectionState = AwaitingSignalConnect;
+
+ // Put in all the signalling addresses for link
+ setup.IncludeOptionalField(H225_Setup_UUIE::e_sourceCallSignalAddress);
+ signallingChannel->SetUpTransportPDU(setup.m_sourceCallSignalAddress, TRUE);
+ if (!setup.HasOptionalField(H225_Setup_UUIE::e_destCallSignalAddress)) {
+ setup.IncludeOptionalField(H225_Setup_UUIE::e_destCallSignalAddress);
+ signallingChannel->SetUpTransportPDU(setup.m_destCallSignalAddress, FALSE);
+ }
+
+ // If a standard call do Fast Start (if required)
+ if (setup.m_conferenceGoal.GetTag() == H225_Setup_UUIE_conferenceGoal::e_create) {
+
+ // Get the local capabilities before fast start is handled
+ OnSetLocalCapabilities();
+
+ // Ask the application what channels to open
+ PTRACE(3, "H225\tCheck for Fast start by local endpoint");
+ fastStartChannels.RemoveAll();
+ OnSelectLogicalChannels();
+
+ // If application called OpenLogicalChannel, put in the fastStart field
+ if (!fastStartChannels.IsEmpty()) {
+ PTRACE(3, "H225\tFast start begun by local endpoint");
+ for (PINDEX i = 0; i < fastStartChannels.GetSize(); i++)
+ BuildFastStartList(fastStartChannels[i], setup.m_fastStart, H323Channel::IsReceiver);
+ if (setup.m_fastStart.GetSize() > 0)
+ setup.IncludeOptionalField(H225_Setup_UUIE::e_fastStart);
+ }
+
+ // Search the capability set and see if we have video capability
+ for (PINDEX i = 0; i < localCapabilities.GetSize(); i++) {
+ switch (localCapabilities[i].GetMainType()) {
+ case H323Capability::e_Audio:
+ case H323Capability::e_UserInput:
+ break;
+
+ default: // Is video or other data (eg T.120)
+ setupPDU.GetQ931().SetBearerCapabilities(Q931::TransferUnrestrictedDigital, 6);
+ i = localCapabilities.GetSize(); // Break out of the for loop
+ break;
+ }
+ }
+ }
+
+ if (!OnSendSignalSetup(setupPDU))
+ return EndedByNoAccept;
+
+ // Do this again (was done when PDU was constructed) in case
+ // OnSendSignalSetup() changed something.
+// setupPDU.SetQ931Fields(*this, TRUE);
+ setupPDU.GetQ931().GetCalledPartyNumber(remotePartyNumber);
+
+ fastStartState = FastStartDisabled;
+ BOOL set_lastPDUWasH245inSETUP = FALSE;
+
+ if (h245Tunneling && doH245inSETUP) {
+ h245TunnelTxPDU = &setupPDU;
+
+ // Try and start the master/slave and capability exchange through the tunnel
+ // Note: this used to be disallowed but is now allowed as of H323v4
+ BOOL ok = StartControlNegotiations();
+
+ h245TunnelTxPDU = NULL;
+
+ if (!ok)
+ return EndedByTransportFail;
+
+ if (setup.m_fastStart.GetSize() > 0) {
+ // Now if fast start as well need to put this in setup specific field
+ // and not the generic H.245 tunneling field
+ setup.IncludeOptionalField(H225_Setup_UUIE::e_parallelH245Control);
+ setup.m_parallelH245Control = setupPDU.m_h323_uu_pdu.m_h245Control;
+ setupPDU.m_h323_uu_pdu.RemoveOptionalField(H225_H323_UU_PDU::e_h245Control);
+ set_lastPDUWasH245inSETUP = TRUE;
+ }
+ }
+
+ // Send the initial PDU
+ setupTime = PTime();
+ if (!WriteSignalPDU(setupPDU))
+ return EndedByTransportFail;
+
+ // WriteSignalPDU always resets lastPDUWasH245inSETUP.
+ // So set it here if required
+ if (set_lastPDUWasH245inSETUP)
+ lastPDUWasH245inSETUP = TRUE;
+
+ // Set timeout for remote party to answer the call
+ signallingChannel->SetReadTimeout(endpoint.GetSignallingChannelCallTimeout());
+
+ return NumCallEndReasons;
+}
+
+
+BOOL MyH323Connection::OnSendReleaseComplete(H323SignalPDU & releaseCompletePDU)
+{
+ if (h323debug) {
+ cout << "\t-- Sending RELEASE COMPLETE" << endl;
+ }
+ if (cause > 0)
+ releaseCompletePDU.GetQ931().SetCause((Q931::CauseValues)cause);
+
+#ifdef TUNNELLING
+ EmbedTunneledInfo(releaseCompletePDU);
+#endif
+
+ return H323Connection::OnSendReleaseComplete(releaseCompletePDU);
+}
+
+BOOL MyH323Connection::OnReceivedFacility(const H323SignalPDU & pdu)
+{
+ if (h323debug) {
+ cout << "\t-- Received Facility message... " << endl;
+ }
+ return H323Connection::OnReceivedFacility(pdu);
+}
+
+void MyH323Connection::OnReceivedReleaseComplete(const H323SignalPDU & pdu)
+{
+ if (h323debug) {
+ cout << "\t-- Received RELEASE COMPLETE message..." << endl;
+ }
+ if (on_hangup)
+ on_hangup(GetCallReference(), (const char *)GetCallToken(), pdu.GetQ931().GetCause());
+ return H323Connection::OnReceivedReleaseComplete(pdu);
+}
+
+BOOL MyH323Connection::OnClosingLogicalChannel(H323Channel & channel)
+{
+ if (h323debug) {
+ cout << "\t-- Closing logical channel..." << endl;
+ }
+ return H323Connection::OnClosingLogicalChannel(channel);
+}
+
+void MyH323Connection::SendUserInputTone(char tone, unsigned duration, unsigned logicalChannel, unsigned rtpTimestamp)
+{
+ SendUserInputModes mode = GetRealSendUserInputMode();
+// That is recursive call... Why?
+// on_receive_digit(GetCallReference(), tone, (const char *)GetCallToken());
+ if ((tone != ' ') || (mode == SendUserInputAsTone) || (mode == SendUserInputAsInlineRFC2833)) {
+ if (h323debug) {
+ cout << "\t-- Sending user input tone (" << tone << ") to remote" << endl;
+ }
+ H323Connection::SendUserInputTone(tone, duration);
+ }
+}
+
+void MyH323Connection::OnUserInputTone(char tone, unsigned duration, unsigned logicalChannel, unsigned rtpTimestamp)
+{
+ /* Why we should check this? */
+ if ((dtmfMode & (H323_DTMF_CISCO | H323_DTMF_RFC2833 | H323_DTMF_SIGNAL)) != 0) {
+ if (h323debug) {
+ cout << "\t-- Received user input tone (" << tone << ") from remote" << endl;
+ }
+ on_receive_digit(GetCallReference(), tone, (const char *)GetCallToken(), duration);
+ }
+}
+
+void MyH323Connection::OnUserInputString(const PString &value)
+{
+ if (h323debug) {
+ cout << "\t-- Received user input string (" << value << ") from remote." << endl;
+ }
+ on_receive_digit(GetCallReference(), value[0], (const char *)GetCallToken(), 0);
+}
+
+void MyH323Connection::OnSendCapabilitySet(H245_TerminalCapabilitySet & pdu)
+{
+ PINDEX i;
+
+ H323Connection::OnSendCapabilitySet(pdu);
+
+ H245_ArrayOf_CapabilityTableEntry & tables = pdu.m_capabilityTable;
+ for(i = 0; i < tables.GetSize(); i++)
+ {
+ H245_CapabilityTableEntry & entry = tables[i];
+ if (entry.HasOptionalField(H245_CapabilityTableEntry::e_capability)) {
+ H245_Capability & cap = entry.m_capability;
+ if (cap.GetTag() == H245_Capability::e_receiveRTPAudioTelephonyEventCapability) {
+ H245_AudioTelephonyEventCapability & atec = cap;
+ atec.m_dynamicRTPPayloadType = dtmfCodec[0];
+// on_set_rfc2833_payload(GetCallReference(), (const char *)GetCallToken(), (int)dtmfCodec[0]);
+#ifdef PTRACING
+ if (h323debug) {
+ cout << "\t-- Receiving RFC2833 on payload " <<
+ atec.m_dynamicRTPPayloadType << endl;
+ }
+#endif
+ }
+ }
+ }
+}
+
+void MyH323Connection::OnSetLocalCapabilities()
+{
+ if (on_setcapabilities)
+ on_setcapabilities(GetCallReference(), (const char *)callToken);
+}
+
+BOOL MyH323Connection::OnReceivedCapabilitySet(const H323Capabilities & remoteCaps,
+ const H245_MultiplexCapability * muxCap,
+ H245_TerminalCapabilitySetReject & reject)
+{
+ struct __codec__ {
+ unsigned int asterisk_codec;
+ unsigned int h245_cap;
+ const char *oid;
+ const char *formatName;
+ };
+ static const struct __codec__ codecs[] = {
+ { AST_FORMAT_G723_1, H245_AudioCapability::e_g7231 },
+ { AST_FORMAT_GSM, H245_AudioCapability::e_gsmFullRate },
+ { AST_FORMAT_ULAW, H245_AudioCapability::e_g711Ulaw64k },
+ { AST_FORMAT_ALAW, H245_AudioCapability::e_g711Alaw64k },
+ { AST_FORMAT_G729A, H245_AudioCapability::e_g729AnnexA },
+ { AST_FORMAT_G729A, H245_AudioCapability::e_g729 },
+ { AST_FORMAT_G726_AAL2, H245_AudioCapability::e_nonStandard, NULL, CISCO_G726r32 },
+#ifdef AST_FORMAT_MODEM
+ { AST_FORMAT_MODEM, H245_DataApplicationCapability_application::e_t38fax },
+#endif
+ { 0 }
+ };
+
+#if 0
+ static const struct __codec__ vcodecs[] = {
+#ifdef HAVE_H261
+ { AST_FORMAT_H261, H245_VideoCapability::e_h261VideoCapability },
+#endif
+#ifdef HAVE_H263
+ { AST_FORMAT_H263, H245_VideoCapability::e_h263VideoCapability },
+#endif
+#ifdef HAVE_H264
+ { AST_FORMAT_H264, H245_VideoCapability::e_genericVideoCapability, "0.0.8.241.0.0.1" },
+#endif
+ { 0 }
+ };
+#endif
+ struct ast_codec_pref prefs;
+ RTP_DataFrame::PayloadTypes pt;
+
+ if (!H323Connection::OnReceivedCapabilitySet(remoteCaps, muxCap, reject)) {
+ return FALSE;
+ }
+
+ memset(&prefs, 0, sizeof(prefs));
+ int peer_capabilities = 0;
+ for (int i = 0; i < remoteCapabilities.GetSize(); ++i) {
+ unsigned int subType = remoteCapabilities[i].GetSubType();
+ if (h323debug) {
+ cout << "Peer capability is " << remoteCapabilities[i] << endl;
+ }
+ switch(remoteCapabilities[i].GetMainType()) {
+ case H323Capability::e_Audio:
+ for (int x = 0; codecs[x].asterisk_codec > 0; ++x) {
+ if ((subType == codecs[x].h245_cap) && (!codecs[x].formatName || (!strcmp(codecs[x].formatName, (const char *)remoteCapabilities[i].GetFormatName())))) {
+ int ast_codec = codecs[x].asterisk_codec;
+ int ms = 0;
+ if (!(peer_capabilities & ast_codec)) {
+ struct ast_format_list format;
+ ast_codec_pref_append(&prefs, ast_codec);
+ format = ast_codec_pref_getsize(&prefs, ast_codec);
+ if ((ast_codec == AST_FORMAT_ALAW) || (ast_codec == AST_FORMAT_ULAW)) {
+ ms = remoteCapabilities[i].GetTxFramesInPacket();
+ if (ms > 60)
+ ms = format.cur_ms;
+ } else
+ ms = remoteCapabilities[i].GetTxFramesInPacket() * format.inc_ms;
+ ast_codec_pref_setsize(&prefs, ast_codec, ms);
+ }
+ if (h323debug) {
+ cout << "Found peer capability " << remoteCapabilities[i] << ", Asterisk code is " << ast_codec << ", frame size (in ms) is " << ms << endl;
+ }
+ peer_capabilities |= ast_codec;
+ }
+ }
+ break;
+ case H323Capability::e_Data:
+ if (!strcmp((const char *)remoteCapabilities[i].GetFormatName(), CISCO_DTMF_RELAY)) {
+ pt = remoteCapabilities[i].GetPayloadType();
+ if ((dtmfMode & H323_DTMF_CISCO) != 0) {
+ on_set_rfc2833_payload(GetCallReference(), (const char *)GetCallToken(), (int)pt, 1);
+// if (sendUserInputMode == SendUserInputAsTone)
+// sendUserInputMode = SendUserInputAsInlineRFC2833;
+ }
+#ifdef PTRACING
+ if (h323debug) {
+ cout << "\t-- Outbound Cisco RTP DTMF on payload " << pt << endl;
+ }
+#endif
+ }
+ break;
+ case H323Capability::e_UserInput:
+ if (!strcmp((const char *)remoteCapabilities[i].GetFormatName(), H323_UserInputCapability::SubTypeNames[H323_UserInputCapability::SignalToneRFC2833])) {
+ pt = remoteCapabilities[i].GetPayloadType();
+ if ((dtmfMode & H323_DTMF_RFC2833) != 0) {
+ on_set_rfc2833_payload(GetCallReference(), (const char *)GetCallToken(), (int)pt, 0);
+// if (sendUserInputMode == SendUserInputAsTone)
+// sendUserInputMode = SendUserInputAsInlineRFC2833;
+ }
+#ifdef PTRACING
+ if (h323debug) {
+ cout << "\t-- Outbound RFC2833 on payload " << pt << endl;
+ }
+#endif
+ }
+ break;
+#if 0
+ case H323Capability::e_Video:
+ for (int x = 0; vcodecs[x].asterisk_codec > 0; ++x) {
+ if (subType == vcodecs[x].h245_cap) {
+ H245_CapabilityIdentifier *cap = NULL;
+ H245_GenericCapability y;
+ if (vcodecs[x].oid) {
+ cap = new H245_CapabilityIdentifier(H245_CapabilityIdentifier::e_standard);
+ PASN_ObjectId &object_id = *cap;
+ object_id = vcodecs[x].oid;
+ y.m_capabilityIdentifier = *cap;
+ }
+ if ((subType != H245_VideoCapability::e_genericVideoCapability) ||
+ (vcodecs[x].oid && ((const H323GenericVideoCapability &)remoteCapabilities[i]).IsGenericMatch((const H245_GenericCapability)y))) {
+ if (h323debug) {
+ cout << "Found peer video capability " << remoteCapabilities[i] << ", Asterisk code is " << vcodecs[x].asterisk_codec << endl;
+ }
+ peer_capabilities |= vcodecs[x].asterisk_codec;
+ }
+ if (cap)
+ delete(cap);
+ }
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ if (h323debug) {
+ char caps_str[1024], caps2_str[1024];
+ ast_codec_pref_string(&prefs, caps2_str, sizeof(caps2_str));
+ cout << "Peer capabilities = " << ast_getformatname_multiple(caps_str, sizeof(caps_str), peer_capabilities)
+ << ", ordered list is " << caps2_str << endl;
+ }
+#if 0
+ redir_capabilities &= peer_capabilities;
+#endif
+ if (on_setpeercapabilities)
+ on_setpeercapabilities(GetCallReference(), (const char *)callToken, peer_capabilities, &prefs);
+
+ return TRUE;
+}
+
+H323Channel * MyH323Connection::CreateRealTimeLogicalChannel(const H323Capability & capability,
+ H323Channel::Directions dir,
+ unsigned sessionID,
+ const H245_H2250LogicalChannelParameters * /*param*/,
+ RTP_QOS * /*param*/ )
+{
+ /* Do not open tx channel when transmitter has been paused by empty TCS */
+ if ((dir == H323Channel::IsTransmitter) && transmitterSidePaused)
+ return NULL;
+
+ return new MyH323_ExternalRTPChannel(*this, capability, dir, sessionID);
+}
+
+/** This callback function is invoked once upon creation of each
+ * channel for an H323 session
+ */
+BOOL MyH323Connection::OnStartLogicalChannel(H323Channel & channel)
+{
+ /* Increase the count of channels we have open */
+ channelsOpen++;
+
+ if (h323debug) {
+ cout << "\t-- Started logical channel: "
+ << ((channel.GetDirection() == H323Channel::IsTransmitter) ? "sending " : ((channel.GetDirection() == H323Channel::IsReceiver) ? "receiving " : " "))
+ << (const char *)(channel.GetCapability()).GetFormatName() << endl;
+ cout << "\t\t-- channelsOpen = " << channelsOpen << endl;
+ }
+ return connectionState != ShuttingDownConnection;
+}
+
+void MyH323Connection::SetCapabilities(int caps, int dtmf_mode, void *_prefs, int pref_codec)
+{
+ PINDEX lastcap = -1; /* last common capability index */
+ int alreadysent = 0;
+ int codec;
+ int x, y;
+ char caps_str[1024];
+ struct ast_codec_pref *prefs = (struct ast_codec_pref *)_prefs;
+ struct ast_format_list format;
+ int frames_per_packet;
+ int max_frames_per_packet;
+ H323Capability *cap;
+
+ localCapabilities.RemoveAll();
+
+ if (h323debug) {
+ cout << "Setting capabilities to " << ast_getformatname_multiple(caps_str, sizeof(caps_str), caps) << endl;
+ ast_codec_pref_string(prefs, caps_str, sizeof(caps_str));
+ cout << "Capabilities in preference order is " << caps_str << endl;
+ }
+ /* Add audio codecs in preference order first, then
+ audio codecs without preference as allowed by mask */
+ for (y = 0, x = -1; x < 32 + 32; ++x) {
+ if (x < 0)
+ codec = pref_codec;
+ else if (y || (!(codec = ast_codec_pref_index(prefs, x)))) {
+ if (!y)
+ y = 1;
+ else
+ y <<= 1;
+ codec = y;
+ }
+ if (!(caps & codec) || (alreadysent & codec) || !(codec & AST_FORMAT_AUDIO_MASK))
+ continue;
+ alreadysent |= codec;
+ format = ast_codec_pref_getsize(prefs, codec);
+ frames_per_packet = (format.inc_ms ? format.cur_ms / format.inc_ms : format.cur_ms);
+ max_frames_per_packet = (format.inc_ms ? format.max_ms / format.inc_ms : 0);
+ switch(codec) {
+#if 0
+ case AST_FORMAT_SPEEX:
+ /* Not real sure if Asterisk acutally supports all
+ of the various different bit rates so add them
+ all and figure it out later*/
+
+ lastcap = localCapabilities.SetCapability(0, 0, new SpeexNarrow2AudioCapability());
+ lastcap = localCapabilities.SetCapability(0, 0, new SpeexNarrow3AudioCapability());
+ lastcap = localCapabilities.SetCapability(0, 0, new SpeexNarrow4AudioCapability());
+ lastcap = localCapabilities.SetCapability(0, 0, new SpeexNarrow5AudioCapability());
+ lastcap = localCapabilities.SetCapability(0, 0, new SpeexNarrow6AudioCapability());
+ break;
+#endif
+ case AST_FORMAT_G729A:
+ AST_G729ACapability *g729aCap;
+ AST_G729Capability *g729Cap;
+ lastcap = localCapabilities.SetCapability(0, 0, g729aCap = new AST_G729ACapability(frames_per_packet));
+ lastcap = localCapabilities.SetCapability(0, 0, g729Cap = new AST_G729Capability(frames_per_packet));
+ if (max_frames_per_packet) {
+ g729aCap->SetTxFramesInPacket(max_frames_per_packet);
+ g729Cap->SetTxFramesInPacket(max_frames_per_packet);
+ }
+ break;
+ case AST_FORMAT_G723_1:
+ AST_G7231Capability *g7231Cap;
+ lastcap = localCapabilities.SetCapability(0, 0, g7231Cap = new AST_G7231Capability(frames_per_packet, TRUE));
+ if (max_frames_per_packet)
+ g7231Cap->SetTxFramesInPacket(max_frames_per_packet);
+ lastcap = localCapabilities.SetCapability(0, 0, g7231Cap = new AST_G7231Capability(frames_per_packet, FALSE));
+ if (max_frames_per_packet)
+ g7231Cap->SetTxFramesInPacket(max_frames_per_packet);
+ break;
+ case AST_FORMAT_GSM:
+ AST_GSM0610Capability *gsmCap;
+ lastcap = localCapabilities.SetCapability(0, 0, gsmCap = new AST_GSM0610Capability(frames_per_packet));
+ if (max_frames_per_packet)
+ gsmCap->SetTxFramesInPacket(max_frames_per_packet);
+ break;
+ case AST_FORMAT_ULAW:
+ AST_G711Capability *g711uCap;
+ lastcap = localCapabilities.SetCapability(0, 0, g711uCap = new AST_G711Capability(format.cur_ms, H323_G711Capability::muLaw));
+ if (format.max_ms)
+ g711uCap->SetTxFramesInPacket(format.max_ms);
+ break;
+ case AST_FORMAT_ALAW:
+ AST_G711Capability *g711aCap;
+ lastcap = localCapabilities.SetCapability(0, 0, g711aCap = new AST_G711Capability(format.cur_ms, H323_G711Capability::ALaw));
+ if (format.max_ms)
+ g711aCap->SetTxFramesInPacket(format.max_ms);
+ break;
+ case AST_FORMAT_G726_AAL2:
+ AST_CiscoG726Capability *g726Cap;
+ lastcap = localCapabilities.SetCapability(0, 0, g726Cap = new AST_CiscoG726Capability(frames_per_packet));
+ if (max_frames_per_packet)
+ g726Cap->SetTxFramesInPacket(max_frames_per_packet);
+ break;
+ default:
+ alreadysent &= ~codec;
+ break;
+ }
+ }
+
+ cap = new H323_UserInputCapability(H323_UserInputCapability::HookFlashH245);
+ if (cap && cap->IsUsable(*this)) {
+ lastcap++;
+ lastcap = localCapabilities.SetCapability(0, lastcap, cap);
+ } else if (cap)
+ delete cap; /* Capability is not usable */
+
+ dtmfMode = dtmf_mode;
+ if (h323debug) {
+ cout << "DTMF mode is " << (int)dtmfMode << endl;
+ }
+ if (dtmfMode) {
+ lastcap++;
+ if (dtmfMode == H323_DTMF_INBAND) {
+ cap = new H323_UserInputCapability(H323_UserInputCapability::BasicString);
+ if (cap && cap->IsUsable(*this)) {
+ lastcap = localCapabilities.SetCapability(0, lastcap, cap);
+ } else if (cap)
+ delete cap; /* Capability is not usable */
+ sendUserInputMode = SendUserInputAsString;
+ } else {
+ if ((dtmfMode & H323_DTMF_RFC2833) != 0) {
+ cap = new H323_UserInputCapability(H323_UserInputCapability::SignalToneRFC2833);
+ if (cap && cap->IsUsable(*this))
+ lastcap = localCapabilities.SetCapability(0, lastcap, cap);
+ else {
+ dtmfMode |= H323_DTMF_SIGNAL;
+ if (cap)
+ delete cap; /* Capability is not usable */
+ }
+ }
+ if ((dtmfMode & H323_DTMF_CISCO) != 0) {
+ /* Try Cisco's RTP DTMF relay too, but prefer RFC2833 or h245-signal */
+ cap = new AST_CiscoDtmfCapability();
+ if (cap && cap->IsUsable(*this)) {
+ lastcap = localCapabilities.SetCapability(0, lastcap, cap);
+ /* We cannot send Cisco RTP DTMFs, use h245-signal instead */
+ dtmfMode |= H323_DTMF_SIGNAL;
+ } else {
+ dtmfMode |= H323_DTMF_SIGNAL;
+ if (cap)
+ delete cap; /* Capability is not usable */
+ }
+ }
+ if ((dtmfMode & H323_DTMF_SIGNAL) != 0) {
+ /* Cisco usually sends DTMF correctly only through h245-alphanumeric or h245-signal */
+ cap = new H323_UserInputCapability(H323_UserInputCapability::SignalToneH245);
+ if (cap && cap->IsUsable(*this))
+ lastcap = localCapabilities.SetCapability(0, lastcap, cap);
+ else if (cap)
+ delete cap; /* Capability is not usable */
+ }
+ sendUserInputMode = SendUserInputAsTone; /* RFC2833 transmission handled at Asterisk level */
+ }
+ }
+
+ if (h323debug) {
+ cout << "Allowed Codecs for " << GetCallToken() << " (" << GetSignallingChannel()->GetLocalAddress() << "):\n\t" << setprecision(2) << localCapabilities << endl;
+ }
+}
+
+BOOL MyH323Connection::StartControlChannel(const H225_TransportAddress & h245Address)
+{
+ // Check that it is an IP address, all we support at the moment
+ if (h245Address.GetTag() != H225_TransportAddress::e_ipAddress
+#if P_HAS_IPV6
+ && h245Address.GetTag() != H225_TransportAddress::e_ip6Address
+#endif
+ ) {
+ PTRACE(1, "H225\tConnect of H245 failed: Unsupported transport");
+ return FALSE;
+ }
+
+ // Already have the H245 channel up.
+ if (controlChannel != NULL)
+ return TRUE;
+
+ PIPSocket::Address addr;
+ WORD port;
+ GetSignallingChannel()->GetLocalAddress().GetIpAndPort(addr, port);
+ if (addr) {
+ if (h323debug)
+ cout << "Using " << addr << " for outbound H.245 transport" << endl;
+ controlChannel = new MyH323TransportTCP(endpoint, addr);
+ } else
+ controlChannel = new H323TransportTCP(endpoint);
+ if (!controlChannel->SetRemoteAddress(h245Address)) {
+ PTRACE(1, "H225\tCould not extract H245 address");
+ delete controlChannel;
+ controlChannel = NULL;
+ return FALSE;
+ }
+ if (!controlChannel->Connect()) {
+ PTRACE(1, "H225\tConnect of H245 failed: " << controlChannel->GetErrorText());
+ delete controlChannel;
+ controlChannel = NULL;
+ return FALSE;
+ }
+
+ controlChannel->StartControlChannel(*this);
+ return TRUE;
+}
+
+#ifdef H323_H450
+void MyH323Connection::OnReceivedLocalCallHold(int linkedId)
+{
+ if (on_hold)
+ on_hold(GetCallReference(), (const char *)GetCallToken(), 1);
+}
+
+void MyH323Connection::OnReceivedLocalCallRetrieve(int linkedId)
+{
+ if (on_hold)
+ on_hold(GetCallReference(), (const char *)GetCallToken(), 0);
+}
+#endif
+
+void MyH323Connection::MyHoldCall(BOOL isHold)
+{
+ if (((holdHandling & H323_HOLD_NOTIFY) != 0) || ((holdHandling & H323_HOLD_Q931ONLY) != 0)) {
+ PBYTEArray x ((const BYTE *)(isHold ? "\xF9" : "\xFA"), 1);
+ H323SignalPDU signal;
+ signal.BuildNotify(*this);
+ signal.GetQ931().SetIE((Q931::InformationElementCodes)39 /* Q931::NotifyIE */, x);
+ if (h323debug)
+ cout << "Sending " << (isHold ? "HOLD" : "RETRIEVE") << " notification: " << signal << endl;
+ if ((holdHandling & H323_HOLD_Q931ONLY) != 0) {
+ PBYTEArray rawData;
+ signal.GetQ931().RemoveIE(Q931::UserUserIE);
+ signal.GetQ931().Encode(rawData);
+ signallingChannel->WritePDU(rawData);
+ } else
+ WriteSignalPDU(signal);
+ }
+#ifdef H323_H450
+ if ((holdHandling & H323_HOLD_H450) != 0) {
+ if (isHold)
+ h4504handler->HoldCall(TRUE);
+ else if (IsLocalHold())
+ h4504handler->RetrieveCall();
+ }
+#endif
+}
+
+
+/* MyH323_ExternalRTPChannel */
+MyH323_ExternalRTPChannel::MyH323_ExternalRTPChannel(MyH323Connection & connection,
+ const H323Capability & capability,
+ Directions direction,
+ unsigned id)
+ : H323_ExternalRTPChannel::H323_ExternalRTPChannel(connection, capability, direction, id)
+{
+ struct rtp_info *info;
+
+ /* Determine the Local (A side) IP Address and port */
+ info = on_external_rtp_create(connection.GetCallReference(), (const char *)connection.GetCallToken());
+ if (!info) {
+ cout << "\tERROR: on_external_rtp_create failure" << endl;
+ return;
+ } else {
+ localIpAddr = info->addr;
+ localPort = info->port;
+ /* tell the H.323 stack */
+ SetExternalAddress(H323TransportAddress(localIpAddr, localPort), H323TransportAddress(localIpAddr, localPort + 1));
+ /* clean up allocated memory */
+ free(info);
+ }
+
+ /* Get the payload code */
+ OpalMediaFormat format(capability.GetFormatName(), FALSE);
+ payloadCode = format.GetPayloadType();
+}
+
+MyH323_ExternalRTPChannel::~MyH323_ExternalRTPChannel()
+{
+ if (h323debug) {
+ cout << "\tExternalRTPChannel Destroyed" << endl;
+ }
+}
+
+BOOL MyH323_ExternalRTPChannel::Start(void)
+{
+ /* Call ancestor first */
+ if (!H323_ExternalRTPChannel::Start()) {
+ return FALSE;
+ }
+
+ if (h323debug) {
+ cout << "\t\tExternal RTP Session Starting" << endl;
+ cout << "\t\tRTP channel id " << sessionID << " parameters:" << endl;
+ }
+
+ /* Collect the remote information */
+ H323_ExternalRTPChannel::GetRemoteAddress(remoteIpAddr, remotePort);
+
+ if (h323debug) {
+ cout << "\t\t-- remoteIpAddress: " << remoteIpAddr << endl;
+ cout << "\t\t-- remotePort: " << remotePort << endl;
+ cout << "\t\t-- ExternalIpAddress: " << localIpAddr << endl;
+ cout << "\t\t-- ExternalPort: " << localPort << endl;
+ }
+ /* Notify Asterisk of remote RTP information */
+ on_start_rtp_channel(connection.GetCallReference(), (const char *)remoteIpAddr.AsString(), remotePort,
+ (const char *)connection.GetCallToken(), (int)payloadCode);
+ return TRUE;
+}
+
+BOOL MyH323_ExternalRTPChannel::OnReceivedAckPDU(const H245_H2250LogicalChannelAckParameters & param)
+{
+ if (h323debug) {
+ cout << " MyH323_ExternalRTPChannel::OnReceivedAckPDU" << endl;
+ }
+
+ if (H323_ExternalRTPChannel::OnReceivedAckPDU(param)) {
+ GetRemoteAddress(remoteIpAddr, remotePort);
+ if (h323debug) {
+ cout << " -- remoteIpAddress: " << remoteIpAddr << endl;
+ cout << " -- remotePort: " << remotePort << endl;
+ }
+ on_start_rtp_channel(connection.GetCallReference(), (const char *)remoteIpAddr.AsString(),
+ remotePort, (const char *)connection.GetCallToken(), (int)payloadCode);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+#ifdef H323_H450
+MyH4504Handler::MyH4504Handler(MyH323Connection &_conn, H450xDispatcher &_disp)
+ :H4504Handler(_conn, _disp)
+{
+ conn = &_conn;
+}
+
+void MyH4504Handler::OnReceivedLocalCallHold(int linkedId)
+{
+ if (conn) {
+ conn->Lock();
+ conn->OnReceivedLocalCallHold(linkedId);
+ conn->Unlock();
+ }
+}
+
+void MyH4504Handler::OnReceivedLocalCallRetrieve(int linkedId)
+{
+ if (conn) {
+ conn->Lock();
+ conn->OnReceivedLocalCallRetrieve(linkedId);
+ conn->Unlock();
+ }
+}
+#endif
+
+
+/** IMPLEMENTATION OF C FUNCTIONS */
+
+/**
+ * The extern "C" directive takes care for
+ * the ANSI-C representation of linkable symbols
+ */
+
+extern "C" {
+
+int h323_end_point_exist(void)
+{
+ if (!endPoint) {
+ return 0;
+ }
+ return 1;
+}
+
+void h323_end_point_create(void)
+{
+ channelsOpen = 0;
+ logstream = new PAsteriskLog();
+ localProcess = new MyProcess();
+ localProcess->Main();
+}
+
+void h323_gk_urq(void)
+{
+ if (!h323_end_point_exist()) {
+ cout << " ERROR: [h323_gk_urq] No Endpoint, this is bad" << endl;
+ return;
+ }
+ endPoint->RemoveGatekeeper();
+}
+
+void h323_debug(int flag, unsigned level)
+{
+ if (flag) {
+ PTrace:: SetLevel(level);
+ } else {
+ PTrace:: SetLevel(0);
+ }
+}
+
+/** Installs the callback functions on behalf of the PBX application */
+void h323_callback_register(setup_incoming_cb ifunc,
+ setup_outbound_cb sfunc,
+ on_rtp_cb rtpfunc,
+ start_rtp_cb lfunc,
+ clear_con_cb clfunc,
+ chan_ringing_cb rfunc,
+ con_established_cb efunc,
+ receive_digit_cb dfunc,
+ answer_call_cb acfunc,
+ progress_cb pgfunc,
+ rfc2833_cb dtmffunc,
+ hangup_cb hangupfunc,
+ setcapabilities_cb capabilityfunc,
+ setpeercapabilities_cb peercapabilityfunc,
+ onhold_cb holdfunc)
+{
+ on_incoming_call = ifunc;
+ on_outgoing_call = sfunc;
+ on_external_rtp_create = rtpfunc;
+ on_start_rtp_channel = lfunc;
+ on_connection_cleared = clfunc;
+ on_chan_ringing = rfunc;
+ on_connection_established = efunc;
+ on_receive_digit = dfunc;
+ on_answer_call = acfunc;
+ on_progress = pgfunc;
+ on_set_rfc2833_payload = dtmffunc;
+ on_hangup = hangupfunc;
+ on_setcapabilities = capabilityfunc;
+ on_setpeercapabilities = peercapabilityfunc;
+ on_hold = holdfunc;
+}
+
+/**
+ * Add capability to the capability table of the end point.
+ */
+int h323_set_capabilities(const char *token, int cap, int dtmf_mode, struct ast_codec_pref *prefs, int pref_codec)
+{
+ MyH323Connection *conn;
+
+ if (!h323_end_point_exist()) {
+ cout << " ERROR: [h323_set_capablities] No Endpoint, this is bad" << endl;
+ return 1;
+ }
+ if (!token || !*token) {
+ cout << " ERROR: [h323_set_capabilities] Invalid call token specified." << endl;
+ return 1;
+ }
+
+ PString myToken(token);
+ conn = (MyH323Connection *)endPoint->FindConnectionWithLock(myToken);
+ if (!conn) {
+ cout << " ERROR: [h323_set_capabilities] Unable to find connection " << token << endl;
+ return 1;
+ }
+ conn->SetCapabilities((/*conn->bridging ? conn->redir_capabilities :*/ cap), dtmf_mode, prefs, pref_codec);
+ conn->Unlock();
+
+ return 0;
+}
+
+/** Start the H.323 listener */
+int h323_start_listener(int listenPort, struct sockaddr_in bindaddr)
+{
+
+ if (!h323_end_point_exist()) {
+ cout << "ERROR: [h323_start_listener] No Endpoint, this is bad!" << endl;
+ return 1;
+ }
+
+ PIPSocket::Address interfaceAddress(bindaddr.sin_addr);
+ if (!listenPort) {
+ listenPort = 1720;
+ }
+ /** H.323 listener */
+ H323ListenerTCP *tcpListener;
+ tcpListener = new H323ListenerTCP(*endPoint, interfaceAddress, (WORD)listenPort);
+ if (!endPoint->StartListener(tcpListener)) {
+ cout << "ERROR: Could not open H.323 listener port on " << ((H323ListenerTCP *) tcpListener)->GetListenerPort() << endl;
+ delete tcpListener;
+ return 1;
+ }
+ cout << " == H.323 listener started" << endl;
+ return 0;
+};
+
+int h323_set_alias(struct oh323_alias *alias)
+{
+ char *p;
+ char *num;
+ PString h323id(alias->name);
+ PString e164(alias->e164);
+ char *prefix;
+
+ if (!h323_end_point_exist()) {
+ cout << "ERROR: [h323_set_alias] No Endpoint, this is bad!" << endl;
+ return 1;
+ }
+
+ cout << "== Adding alias \"" << h323id << "\" to endpoint" << endl;
+ endPoint->AddAliasName(h323id);
+ endPoint->RemoveAliasName(localProcess->GetUserName());
+
+ if (!e164.IsEmpty()) {
+ cout << "== Adding E.164 \"" << e164 << "\" to endpoint" << endl;
+ endPoint->AddAliasName(e164);
+ }
+ if (strlen(alias->prefix)) {
+ p = prefix = strdup(alias->prefix);
+ while((num = strsep(&p, ",")) != (char *)NULL) {
+ cout << "== Adding Prefix \"" << num << "\" to endpoint" << endl;
+ endPoint->SupportedPrefixes += PString(num);
+ endPoint->SetGateway();
+ }
+ if (prefix)
+ free(prefix);
+ }
+ return 0;
+}
+
+void h323_set_id(char *id)
+{
+ PString h323id(id);
+
+ if (h323debug) {
+ cout << " == Using '" << h323id << "' as our H.323ID for this call" << endl;
+ }
+ /* EVIL HACK */
+ endPoint->SetLocalUserName(h323id);
+}
+
+void h323_show_tokens(void)
+{
+ cout << "Current call tokens: " << setprecision(2) << endPoint->GetAllConnections() << endl;
+}
+
+/** Establish Gatekeeper communiations, if so configured,
+ * register aliases for the H.323 endpoint to respond to.
+ */
+int h323_set_gk(int gatekeeper_discover, char *gatekeeper, char *secret)
+{
+ PString gkName = PString(gatekeeper);
+ PString pass = PString(secret);
+ H323TransportUDP *rasChannel;
+
+ if (!h323_end_point_exist()) {
+ cout << "ERROR: [h323_set_gk] No Endpoint, this is bad!" << endl;
+ return 1;
+ }
+
+ if (!gatekeeper) {
+ cout << "Error: Gatekeeper cannot be NULL" << endl;
+ return 1;
+ }
+ if (strlen(secret)) {
+ endPoint->SetGatekeeperPassword(pass);
+ }
+ if (gatekeeper_discover) {
+ /* discover the gk using multicast */
+ if (endPoint->DiscoverGatekeeper(new MyH323TransportUDP(*endPoint))) {
+ cout << "== Using " << (endPoint->GetGatekeeper())->GetName() << " as our Gatekeeper." << endl;
+ } else {
+ cout << "Warning: Could not find a gatekeeper." << endl;
+ return 1;
+ }
+ } else {
+ rasChannel = new MyH323TransportUDP(*endPoint);
+
+ if (!rasChannel) {
+ cout << "Error: No RAS Channel, this is bad" << endl;
+ return 1;
+ }
+ if (endPoint->SetGatekeeper(gkName, rasChannel)) {
+ cout << "== Using " << (endPoint->GetGatekeeper())->GetName() << " as our Gatekeeper." << endl;
+ } else {
+ cout << "Error registering with gatekeeper \"" << gkName << "\". " << endl;
+ /* XXX Maybe we should fire a new thread to attempt to re-register later and not kill asterisk here? */
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/** Send a DTMF tone over the H323Connection with the
+ * specified token.
+ */
+void h323_send_tone(const char *call_token, char tone)
+{
+ if (!h323_end_point_exist()) {
+ cout << "ERROR: [h323_send_tone] No Endpoint, this is bad!" << endl;
+ return;
+ }
+ PString token = PString(call_token);
+ endPoint->SendUserTone(token, tone);
+}
+
+/** Make a call to the remote endpoint.
+ */
+int h323_make_call(char *dest, call_details_t *cd, call_options_t *call_options)
+{
+ int res;
+ PString token;
+ PString host(dest);
+
+ if (!h323_end_point_exist()) {
+ return 1;
+ }
+
+ res = endPoint->MyMakeCall(host, token, &cd->call_reference, call_options);
+ memcpy((char *)(cd->call_token), (const unsigned char *)token, token.GetLength());
+ return res;
+};
+
+int h323_clear_call(const char *call_token, int cause)
+{
+ H225_ReleaseCompleteReason dummy;
+ H323Connection::CallEndReason r = H323Connection::EndedByLocalUser;
+ MyH323Connection *connection;
+ const PString currentToken(call_token);
+
+ if (!h323_end_point_exist()) {
+ return 1;
+ }
+
+ if (cause) {
+ r = H323TranslateToCallEndReason((Q931::CauseValues)(cause), dummy);
+ }
+
+ connection = (MyH323Connection *)endPoint->FindConnectionWithLock(currentToken);
+ if (connection) {
+ connection->SetCause(cause);
+ connection->SetCallEndReason(r);
+ connection->Unlock();
+ }
+ endPoint->ClearCall(currentToken, r);
+ return 0;
+};
+
+/* Send Alerting PDU to H.323 caller */
+int h323_send_alerting(const char *token)
+{
+ const PString currentToken(token);
+ H323Connection * connection;
+
+ if (h323debug) {
+ cout << "\tSending alerting" << endl;
+ }
+ connection = endPoint->FindConnectionWithLock(currentToken);
+ if (!connection) {
+ cout << "No connection found for " << token << endl;
+ return -1;
+ }
+ connection->AnsweringCall(H323Connection::AnswerCallPending);
+ connection->Unlock();
+ return 0;
+}
+
+/* Send Progress PDU to H.323 caller */
+int h323_send_progress(const char *token)
+{
+ const PString currentToken(token);
+ H323Connection * connection;
+
+ connection = endPoint->FindConnectionWithLock(currentToken);
+ if (!connection) {
+ cout << "No connection found for " << token << endl;
+ return -1;
+ }
+#if 1
+ ((MyH323Connection *)connection)->MySendProgress();
+#else
+ connection->AnsweringCall(H323Connection::AnswerCallDeferredWithMedia);
+#endif
+ connection->Unlock();
+ return 0;
+}
+
+/** This function tells the h.323 stack to either
+ answer or deny an incoming call */
+int h323_answering_call(const char *token, int busy)
+{
+ const PString currentToken(token);
+ H323Connection * connection;
+
+ connection = endPoint->FindConnectionWithLock(currentToken);
+
+ if (!connection) {
+ cout << "No connection found for " << token << endl;
+ return -1;
+ }
+ if (!busy) {
+ if (h323debug) {
+ cout << "\tAnswering call " << token << endl;
+ }
+ connection->AnsweringCall(H323Connection::AnswerCallNow);
+ } else {
+ if (h323debug) {
+ cout << "\tdenying call " << token << endl;
+ }
+ connection->AnsweringCall(H323Connection::AnswerCallDenied);
+ }
+ connection->Unlock();
+ return 0;
+}
+
+int h323_soft_hangup(const char *data)
+{
+ PString token(data);
+ BOOL result;
+ cout << "Soft hangup" << endl;
+ result = endPoint->ClearCall(token);
+ return result;
+}
+
+/* alas, this doesn't work :( */
+void h323_native_bridge(const char *token, const char *them, char *capability)
+{
+ H323Channel *channel;
+ MyH323Connection *connection = (MyH323Connection *)endPoint->FindConnectionWithLock(token);
+
+ if (!connection) {
+ cout << "ERROR: No connection found, this is bad" << endl;
+ return;
+ }
+
+ cout << "Native Bridge: them [" << them << "]" << endl;
+
+ channel = connection->FindChannel(connection->sessionId, TRUE);
+ connection->bridging = TRUE;
+ connection->CloseLogicalChannelNumber(channel->GetNumber());
+
+ connection->Unlock();
+ return;
+
+}
+
+int h323_hold_call(const char *token, int is_hold)
+{
+ MyH323Connection *conn = (MyH323Connection *)endPoint->FindConnectionWithLock(token);
+ if (!conn) {
+ cout << "ERROR: No connection found, this is bad" << endl;
+ return -1;
+ }
+ conn->MyHoldCall((BOOL)is_hold);
+ conn->Unlock();
+ return 0;
+}
+
+#undef cout
+#undef endl
+void h323_end_process(void)
+{
+ if (endPoint) {
+ endPoint->ClearAllCalls();
+ endPoint->RemoveListener(NULL);
+ delete endPoint;
+ endPoint = NULL;
+ }
+ if (localProcess) {
+ delete localProcess;
+ localProcess = NULL;
+ close(_timerChangePipe[0]);
+ close(_timerChangePipe[1]);
+ }
+ if (logstream) {
+ PTrace::SetLevel(0);
+ PTrace::SetStream(&cout);
+ delete logstream;
+ logstream = NULL;
+ }
+}
+
+} /* extern "C" */
+
diff --git a/trunk/channels/h323/ast_h323.h b/trunk/channels/h323/ast_h323.h
new file mode 100644
index 000000000..39af427ae
--- /dev/null
+++ b/trunk/channels/h323/ast_h323.h
@@ -0,0 +1,189 @@
+/*
+ * ast_h323.h
+ *
+ * OpenH323 Channel Driver for ASTERISK PBX.
+ * By Jeremy McNamara
+ * For The NuFone Network
+ *
+ * This code has been derived from code created by
+ * Michael Manousos and Mark Spencer
+ *
+ * This file is part of the chan_h323 driver for Asterisk
+ *
+ * chan_h323 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * chan_h323 is distributed WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Version Info: $Id$
+ */
+
+#ifndef AST_H323_H
+#define AST_H323_H
+
+#define VERSION(a,b,c) ((a)*10000+(b)*100+(c))
+
+class MyH323EndPoint : public H323EndPoint
+{
+ PCLASSINFO(MyH323EndPoint, H323EndPoint);
+
+public:
+ MyH323EndPoint();
+ int MyMakeCall(const PString &, PString &, void *_callReference, void *_opts);
+ BOOL ClearCall(const PString &, H323Connection::CallEndReason reason);
+ BOOL ClearCall(const PString &);
+
+ void OnClosedLogicalChannel(H323Connection &, const H323Channel &);
+ void OnConnectionEstablished(H323Connection &, const PString &);
+ void OnConnectionCleared(H323Connection &, const PString &);
+ virtual H323Connection * CreateConnection(unsigned, void *, H323Transport *, H323SignalPDU *);
+ void SendUserTone(const PString &, char);
+ BOOL OnConnectionForwarded(H323Connection &, const PString &, const H323SignalPDU &);
+ BOOL ForwardConnection(H323Connection &, const PString &, const H323SignalPDU &);
+ void SetEndpointTypeInfo( H225_EndpointType & info ) const;
+ void SetGateway(void);
+ PStringArray SupportedPrefixes;
+};
+
+class MyH323Connection : public H323Connection
+{
+ PCLASSINFO(MyH323Connection, H323Connection);
+
+public:
+ MyH323Connection(MyH323EndPoint &, unsigned, unsigned);
+ ~MyH323Connection();
+ H323Channel * CreateRealTimeLogicalChannel(const H323Capability &,
+ H323Channel::Directions,
+ unsigned,
+ const H245_H2250LogicalChannelParameters *,
+ RTP_QOS *);
+ H323Connection::AnswerCallResponse OnAnswerCall(const PString &,
+ const H323SignalPDU &,
+ H323SignalPDU &);
+ void OnReceivedReleaseComplete(const H323SignalPDU &);
+ BOOL OnAlerting(const H323SignalPDU &, const PString &);
+ BOOL OnSendReleaseComplete(H323SignalPDU &);
+ BOOL OnReceivedSignalSetup(const H323SignalPDU &);
+ BOOL OnReceivedFacility(const H323SignalPDU &);
+ BOOL OnSendSignalSetup(H323SignalPDU &);
+ BOOL OnStartLogicalChannel(H323Channel &);
+ BOOL OnClosingLogicalChannel(H323Channel &);
+ virtual void SendUserInputTone(char tone, unsigned duration = 0, unsigned logicalChannel = 0, unsigned rtpTimestamp = 0);
+ virtual void OnUserInputTone(char, unsigned, unsigned, unsigned);
+ virtual void OnUserInputString(const PString &value);
+ BOOL OnReceivedProgress(const H323SignalPDU &);
+ BOOL MySendProgress();
+ void OnSendCapabilitySet(H245_TerminalCapabilitySet &);
+ void OnSetLocalCapabilities();
+ void SetCapabilities(int, int, void *, int);
+ BOOL OnReceivedCapabilitySet(const H323Capabilities &, const H245_MultiplexCapability *,
+ H245_TerminalCapabilitySetReject &);
+ void SetCause(int _cause) { cause = _cause; };
+ virtual BOOL StartControlChannel(const H225_TransportAddress & h245Address);
+ void SetCallOptions(void *opts, BOOL isIncoming);
+ void SetCallDetails(void *callDetails, const H323SignalPDU &setupPDU, BOOL isIncoming);
+ virtual H323Connection::CallEndReason SendSignalSetup(const PString&, const H323TransportAddress&);
+#ifdef TUNNELLING
+ virtual BOOL HandleSignalPDU(H323SignalPDU &pdu);
+ BOOL EmbedTunneledInfo(H323SignalPDU &pdu);
+#endif
+#ifdef H323_H450
+ virtual void OnReceivedLocalCallHold(int linkedId);
+ virtual void OnReceivedLocalCallRetrieve(int linkedId);
+#endif
+ void MyHoldCall(BOOL localHold);
+
+ PString sourceAliases;
+ PString destAliases;
+ PString sourceE164;
+ PString destE164;
+ int cid_presentation;
+ int cid_ton;
+ PString rdnis;
+ int redirect_reason;
+ int transfer_capability;
+
+ WORD sessionId;
+ BOOL bridging;
+#ifdef TUNNELLING
+ int remoteTunnelOptions;
+ int tunnelOptions;
+#endif
+
+ unsigned holdHandling;
+ unsigned progressSetup;
+ unsigned progressAlert;
+ int cause;
+
+ RTP_DataFrame::PayloadTypes dtmfCodec[2];
+ int dtmfMode;
+};
+
+class MyH323_ExternalRTPChannel : public H323_ExternalRTPChannel
+{
+ PCLASSINFO(MyH323_ExternalRTPChannel, H323_ExternalRTPChannel);
+
+public:
+ MyH323_ExternalRTPChannel(
+ MyH323Connection & connection,
+ const H323Capability & capability,
+ Directions direction,
+ unsigned sessionID);
+
+ ~MyH323_ExternalRTPChannel();
+
+ /* Overrides */
+ BOOL Start(void);
+ BOOL OnReceivedAckPDU(const H245_H2250LogicalChannelAckParameters & param);
+
+protected:
+ BYTE payloadCode;
+
+ PIPSocket::Address localIpAddr;
+ PIPSocket::Address remoteIpAddr;
+ WORD localPort;
+ WORD remotePort;
+};
+
+/**
+ * The MyProcess is a necessary descendant PProcess class so that the H323EndPoint
+ * objected to be created from within that class. (Solves the who owns main() problem).
+ */
+class MyProcess : public PProcess
+{
+ PCLASSINFO(MyProcess, PProcess);
+
+public:
+ MyProcess();
+ ~MyProcess();
+ void Main();
+};
+
+#ifdef H323_H450
+#include <h450pdu.h>
+
+class MyH4504Handler : public H4504Handler
+{
+ PCLASSINFO(MyH4504Handler, H4504Handler);
+
+public:
+ MyH4504Handler(MyH323Connection &_conn, H450xDispatcher &_disp);
+ virtual void OnReceivedLocalCallHold(int linkedId);
+ virtual void OnReceivedLocalCallRetrieve(int linkedId);
+
+private:
+ MyH323Connection *conn;
+};
+#endif
+
+#include "compat_h323.h"
+
+#endif /* !defined AST_H323_H */
diff --git a/trunk/channels/h323/caps_h323.cxx b/trunk/channels/h323/caps_h323.cxx
new file mode 100644
index 000000000..ebb90f3f2
--- /dev/null
+++ b/trunk/channels/h323/caps_h323.cxx
@@ -0,0 +1,383 @@
+#include <ptlib.h>
+#include <h323.h>
+#include <h245.h>
+#include "ast_h323.h"
+#include "caps_h323.h"
+
+#define DEFINE_G711_CAPABILITY(cls, code, capName) \
+class cls : public AST_G711Capability { \
+public: \
+ cls() : AST_G711Capability(240, code) { } \
+}; \
+H323_REGISTER_CAPABILITY(cls, capName) \
+
+DEFINE_G711_CAPABILITY(AST_G711ALaw64Capability, H323_G711Capability::ALaw, OPAL_G711_ALAW_64K);
+DEFINE_G711_CAPABILITY(AST_G711uLaw64Capability, H323_G711Capability::muLaw, OPAL_G711_ULAW_64K);
+H323_REGISTER_CAPABILITY(AST_G7231Capability, OPAL_G7231);
+H323_REGISTER_CAPABILITY(AST_G729Capability, OPAL_G729);
+H323_REGISTER_CAPABILITY(AST_G729ACapability, OPAL_G729A);
+H323_REGISTER_CAPABILITY(AST_GSM0610Capability, OPAL_GSM0610);
+H323_REGISTER_CAPABILITY(AST_CiscoG726Capability, CISCO_G726r32);
+H323_REGISTER_CAPABILITY(AST_CiscoDtmfCapability, CISCO_DTMF_RELAY);
+
+OPAL_MEDIA_FORMAT_DECLARE(OpalG711ALaw64kFormat,
+ OPAL_G711_ALAW_64K,
+ OpalMediaFormat::DefaultAudioSessionID,
+ RTP_DataFrame::PCMA,
+ TRUE, // Needs jitter
+ 64000, // bits/sec
+ 8, // bytes/frame
+ 8, // 1 millisecond/frame
+ OpalMediaFormat::AudioTimeUnits,
+ 0);
+OPAL_MEDIA_FORMAT_DECLARE(OpalG711uLaw64kFormat,
+ OPAL_G711_ULAW_64K,
+ OpalMediaFormat::DefaultAudioSessionID,
+ RTP_DataFrame::PCMU,
+ TRUE, // Needs jitter
+ 64000, // bits/sec
+ 8, // bytes/frame
+ 8, // 1 millisecond/frame
+ OpalMediaFormat::AudioTimeUnits,
+ 0);
+OPAL_MEDIA_FORMAT_DECLARE(OpalG729Format,
+ OPAL_G729,
+ OpalMediaFormat::DefaultAudioSessionID,
+ RTP_DataFrame::G729,
+ TRUE, // Needs jitter
+ 8000, // bits/sec
+ 10, // bytes
+ 80, // 10 milliseconds
+ OpalMediaFormat::AudioTimeUnits,
+ 0);
+OPAL_MEDIA_FORMAT_DECLARE(OpalG729AFormat,
+ OPAL_G729 "A",
+ OpalMediaFormat::DefaultAudioSessionID,
+ RTP_DataFrame::G729,
+ TRUE, // Needs jitter
+ 8000, // bits/sec
+ 10, // bytes
+ 80, // 10 milliseconds
+ OpalMediaFormat::AudioTimeUnits,
+ 0);
+OPAL_MEDIA_FORMAT_DECLARE(OpalG7231_6k3Format,
+ OPAL_G7231_6k3,
+ OpalMediaFormat::DefaultAudioSessionID,
+ RTP_DataFrame::G7231,
+ TRUE, // Needs jitter
+ 6400, // bits/sec
+ 24, // bytes
+ 240, // 30 milliseconds
+ OpalMediaFormat::AudioTimeUnits,
+ 0);
+OPAL_MEDIA_FORMAT_DECLARE(OpalG7231A_6k3Format,
+ OPAL_G7231A_6k3,
+ OpalMediaFormat::DefaultAudioSessionID,
+ RTP_DataFrame::G7231,
+ TRUE, // Needs jitter
+ 6400, // bits/sec
+ 24, // bytes
+ 240, // 30 milliseconds
+ OpalMediaFormat::AudioTimeUnits,
+ 0);
+OPAL_MEDIA_FORMAT_DECLARE(OpalGSM0610Format,
+ OPAL_GSM0610,
+ OpalMediaFormat::DefaultAudioSessionID,
+ RTP_DataFrame::GSM,
+ TRUE, // Needs jitter
+ 13200, // bits/sec
+ 33, // bytes
+ 160, // 20 milliseconds
+ OpalMediaFormat::AudioTimeUnits,
+ 0);
+OPAL_MEDIA_FORMAT_DECLARE(OpalCiscoG726Format,
+ CISCO_G726r32,
+ OpalMediaFormat::DefaultAudioSessionID,
+ RTP_DataFrame::G726,
+ TRUE, // Needs jitter
+ 32000, // bits/sec
+ 4, // bytes
+ 8, // 1 millisecond
+ OpalMediaFormat::AudioTimeUnits,
+ 0);
+#if 0
+OPAL_MEDIA_FORMAT_DECLARE(OpalCiscoDTMFRelayFormat,
+ CISCO_DTMF_RELAY,
+ OpalMediaFormat::DefaultAudioSessionID,
+ (RTP_DataFrame::PayloadTypes)121, // Choose this for Cisco IOS compatibility
+ TRUE, // Needs jitter
+ 100, // bits/sec
+ 4, // bytes/frame
+ 8*150, // 150 millisecond
+ OpalMediaFormat::AudioTimeUnits,
+ 0);
+#endif
+
+/*
+ * Capability: G.711
+ */
+AST_G711Capability::AST_G711Capability(int rx_frames, H323_G711Capability::Mode m, H323_G711Capability::Speed s)
+ : H323AudioCapability(rx_frames, 30) // 240ms max, 30ms desired
+{
+ mode = m;
+ speed = s;
+}
+
+
+PObject * AST_G711Capability::Clone() const
+{
+ return new AST_G711Capability(*this);
+}
+
+
+unsigned AST_G711Capability::GetSubType() const
+{
+ static const unsigned G711SubType[2][2] = {
+ { H245_AudioCapability::e_g711Alaw64k, H245_AudioCapability::e_g711Alaw56k },
+ { H245_AudioCapability::e_g711Ulaw64k, H245_AudioCapability::e_g711Ulaw56k }
+ };
+ return G711SubType[mode][speed];
+}
+
+
+PString AST_G711Capability::GetFormatName() const
+{
+ static const char * const G711Name[2][2] = {
+ { OPAL_G711_ALAW_64K, OPAL_G711_ALAW_56K },
+ { OPAL_G711_ULAW_64K, OPAL_G711_ULAW_56K },
+ };
+ return G711Name[mode][speed];
+}
+
+
+H323Codec * AST_G711Capability::CreateCodec(H323Codec::Direction direction) const
+{
+ return NULL;
+}
+
+
+/*
+ * Capability: G.723.1
+ */
+AST_G7231Capability::AST_G7231Capability(int rx_frames, BOOL annexA_)
+ : H323AudioCapability(rx_frames, 4)
+{
+ annexA = annexA_;
+}
+
+PObject::Comparison AST_G7231Capability::Compare(const PObject & obj) const
+{
+ Comparison result = H323AudioCapability::Compare(obj);
+ if (result != EqualTo) {
+ return result;
+ }
+ PINDEX otherAnnexA = ((const AST_G7231Capability &)obj).annexA;
+ if (annexA < otherAnnexA) {
+ return LessThan;
+ }
+ if (annexA > otherAnnexA) {
+ return GreaterThan;
+ }
+ return EqualTo;
+}
+
+PObject * AST_G7231Capability::Clone() const
+{
+ return new AST_G7231Capability(*this);
+}
+
+PString AST_G7231Capability::GetFormatName() const
+{
+ return (annexA ? OPAL_G7231 "A" : OPAL_G7231);
+}
+
+unsigned AST_G7231Capability::GetSubType() const
+{
+ return H245_AudioCapability::e_g7231;
+}
+
+BOOL AST_G7231Capability::OnSendingPDU(H245_AudioCapability & cap,
+ unsigned packetSize) const
+{
+ cap.SetTag(H245_AudioCapability::e_g7231);
+ H245_AudioCapability_g7231 & g7231 = cap;
+ g7231.m_maxAl_sduAudioFrames = packetSize;
+ g7231.m_silenceSuppression = annexA;
+ return TRUE;
+}
+
+BOOL AST_G7231Capability::OnReceivedPDU(const H245_AudioCapability & cap,
+ unsigned & packetSize)
+{
+ if (cap.GetTag() != H245_AudioCapability::e_g7231) {
+ return FALSE;
+ }
+ const H245_AudioCapability_g7231 & g7231 = cap;
+ packetSize = g7231.m_maxAl_sduAudioFrames;
+ annexA = g7231.m_silenceSuppression;
+ return TRUE;
+}
+
+H323Codec * AST_G7231Capability::CreateCodec(H323Codec::Direction direction) const
+{
+ return NULL;
+}
+
+/*
+ * Capability: G.729
+ */
+AST_G729Capability::AST_G729Capability(int rx_frames)
+ : H323AudioCapability(rx_frames, 2)
+{
+}
+
+PObject * AST_G729Capability::Clone() const
+{
+ return new AST_G729Capability(*this);
+}
+
+unsigned AST_G729Capability::GetSubType() const
+{
+ return H245_AudioCapability::e_g729;
+}
+
+PString AST_G729Capability::GetFormatName() const
+{
+ return OPAL_G729;
+}
+
+H323Codec * AST_G729Capability::CreateCodec(H323Codec::Direction direction) const
+{
+ return NULL;
+}
+
+/*
+ * Capability: G.729A
+ */
+AST_G729ACapability::AST_G729ACapability(int rx_frames)
+ : H323AudioCapability(rx_frames, 6)
+{
+}
+
+PObject * AST_G729ACapability::Clone() const
+{
+ return new AST_G729ACapability(*this);
+}
+
+unsigned AST_G729ACapability::GetSubType() const
+{
+ return H245_AudioCapability::e_g729AnnexA;
+}
+
+PString AST_G729ACapability::GetFormatName() const
+{
+ return OPAL_G729A;
+}
+
+H323Codec * AST_G729ACapability::CreateCodec(H323Codec::Direction direction) const
+{
+ return NULL;
+}
+
+/*
+ * Capability: GSM full rate
+ */
+AST_GSM0610Capability::AST_GSM0610Capability(int rx_frames, int comfortNoise_, int scrambled_)
+ : H323AudioCapability(rx_frames, 2)
+{
+ comfortNoise = comfortNoise_;
+ scrambled = scrambled_;
+}
+
+PObject * AST_GSM0610Capability::Clone() const
+{
+ return new AST_GSM0610Capability(*this);
+}
+
+unsigned AST_GSM0610Capability::GetSubType() const
+{
+ return H245_AudioCapability::e_gsmFullRate;
+}
+
+BOOL AST_GSM0610Capability::OnSendingPDU(H245_AudioCapability & cap,
+ unsigned packetSize) const
+{
+ cap.SetTag(H245_AudioCapability::e_gsmFullRate);
+ H245_GSMAudioCapability & gsm = cap;
+ gsm.m_audioUnitSize = packetSize * 33;
+ gsm.m_comfortNoise = comfortNoise;
+ gsm.m_scrambled = scrambled;
+ return TRUE;
+}
+
+BOOL AST_GSM0610Capability::OnReceivedPDU(const H245_AudioCapability & cap,
+ unsigned & packetSize)
+{
+ if (cap.GetTag() != H245_AudioCapability::e_gsmFullRate)
+ return FALSE;
+ const H245_GSMAudioCapability & gsm = cap;
+ packetSize = (gsm.m_audioUnitSize + 32) / 33;
+ comfortNoise = gsm.m_comfortNoise;
+ scrambled = gsm.m_scrambled;
+
+ return TRUE;
+}
+
+PString AST_GSM0610Capability::GetFormatName() const
+{
+ return OPAL_GSM0610;
+}
+
+H323Codec * AST_GSM0610Capability::CreateCodec(H323Codec::Direction direction) const
+{
+ return NULL;
+}
+
+/*
+ * Capability: G.726 32 Kbps
+ */
+AST_CiscoG726Capability::AST_CiscoG726Capability(int rx_frames)
+ : H323NonStandardAudioCapability(rx_frames, 240,
+ 181, 0, 18,
+ (const BYTE *)"G726r32", 0)
+{
+}
+
+PObject *AST_CiscoG726Capability::Clone() const
+{
+ return new AST_CiscoG726Capability(*this);
+}
+
+H323Codec *AST_CiscoG726Capability::CreateCodec(H323Codec::Direction direction) const
+{
+ return NULL;
+}
+
+PString AST_CiscoG726Capability::GetFormatName() const
+{
+ return PString(CISCO_G726r32);
+}
+
+/*
+ * Capability: Cisco RTP DTMF Relay
+ */
+AST_CiscoDtmfCapability::AST_CiscoDtmfCapability()
+ : H323NonStandardDataCapability(0, 181, 0, 18, (const BYTE *)"RtpDtmfRelay", 0)
+{
+ rtpPayloadType = (RTP_DataFrame::PayloadTypes)121;
+}
+
+PObject *AST_CiscoDtmfCapability::Clone() const
+{
+ return new AST_CiscoDtmfCapability(*this);
+}
+
+H323Codec *AST_CiscoDtmfCapability::CreateCodec(H323Codec::Direction direction) const
+{
+ return NULL;
+}
+
+PString AST_CiscoDtmfCapability::GetFormatName() const
+{
+ return PString(CISCO_DTMF_RELAY);
+}
diff --git a/trunk/channels/h323/caps_h323.h b/trunk/channels/h323/caps_h323.h
new file mode 100644
index 000000000..8058054db
--- /dev/null
+++ b/trunk/channels/h323/caps_h323.h
@@ -0,0 +1,172 @@
+#ifndef __AST_H323CAPS_H
+#define __AST_H323CAPS_H
+
+/**This class describes the G.711 codec capability.
+ */
+class AST_G711Capability : public H323AudioCapability
+{
+ PCLASSINFO(AST_G711Capability, H323AudioCapability);
+
+public:
+ AST_G711Capability(int rx_frames = 125, H323_G711Capability::Mode _mode = H323_G711Capability::muLaw, H323_G711Capability::Speed _speed = H323_G711Capability::At64k);
+ virtual PObject *Clone() const;
+ virtual H323Codec * CreateCodec(H323Codec::Direction direction) const;
+ virtual unsigned GetSubType() const;
+ virtual PString GetFormatName() const;
+
+protected:
+ H323_G711Capability::Mode mode;
+ H323_G711Capability::Speed speed;
+};
+
+/**This class describes the G.723.1 codec capability.
+ */
+class AST_G7231Capability : public H323AudioCapability
+{
+ PCLASSINFO(AST_G7231Capability, H323AudioCapability);
+
+public:
+ AST_G7231Capability(int rx_frames = 7, BOOL annexA = TRUE);
+ Comparison Compare(const PObject & obj) const;
+ virtual PObject * Clone() const;
+ virtual H323Codec * CreateCodec(H323Codec::Direction direction) const;
+ virtual unsigned GetSubType() const;
+ virtual PString GetFormatName() const;
+ virtual BOOL OnSendingPDU(H245_AudioCapability & pdu, unsigned packetSize) const;
+ virtual BOOL OnReceivedPDU(const H245_AudioCapability & pdu, unsigned & packetSize);
+
+protected:
+ BOOL annexA;
+};
+
+/**This class describes the (fake) G729 codec capability.
+ */
+class AST_G729Capability : public H323AudioCapability
+{
+ PCLASSINFO(AST_G729Capability, H323AudioCapability);
+
+public:
+ AST_G729Capability(int rx_frames = 24);
+ /* Create a copy of the object. */
+ virtual PObject * Clone() const;
+
+ /* Create the codec instance, allocating resources as required. */
+ virtual H323Codec * CreateCodec(H323Codec::Direction direction) const;
+
+ /* Get the sub-type of the capability. This is a code dependent on the
+ main type of the capability.
+
+ This returns one of the four possible combinations of mode and speed
+ using the enum values of the protocol ASN H245_AudioCapability class. */
+ virtual unsigned GetSubType() const;
+
+ /* Get the name of the media data format this class represents. */
+ virtual PString GetFormatName() const;
+};
+
+/* This class describes the VoiceAge G729A codec capability. */
+class AST_G729ACapability : public H323AudioCapability
+{
+ PCLASSINFO(AST_G729ACapability, H323AudioCapability);
+
+public:
+ /* Create a new G.729A capability. */
+ AST_G729ACapability(int rx_frames = 24);
+
+ /* Create a copy of the object. */
+ virtual PObject * Clone() const;
+ /* Create the codec instance, allocating resources as required. */
+ virtual H323Codec * CreateCodec(H323Codec::Direction direction) const;
+
+ /* Get the sub-type of the capability. This is a code dependent on the
+ main type of the capability.
+
+ This returns one of the four possible combinations of mode and speed
+ using the enum values of the protocol ASN H245_AudioCapability class. */
+ virtual unsigned GetSubType() const;
+
+ /* Get the name of the media data format this class represents. */
+ virtual PString GetFormatName() const;
+};
+
+/* This class describes the GSM-06.10 codec capability. */
+class AST_GSM0610Capability : public H323AudioCapability
+{
+ PCLASSINFO(AST_GSM0610Capability, H323AudioCapability);
+
+public:
+ /* Create a new GSM capability. */
+ AST_GSM0610Capability(int rx_frames = 24, int comfortNoise = 0, int scrambled = 0);
+
+ /* Create a copy of the object. */
+ virtual PObject * Clone() const;
+
+ /* Create the codec instance, allocating resources as required. */
+ virtual H323Codec * CreateCodec(H323Codec::Direction direction) const;
+
+ /* Get the sub-type of the capability. This is a code dependent on the
+ main type of the capability.
+
+ This returns one of the four possible combinations of mode and speed
+ using the enum values of the protocol ASN H245_AudioCapability class. */
+ virtual unsigned GetSubType() const;
+
+ /* Get the name of the media data format this class represents. */
+ virtual PString GetFormatName() const;
+
+ BOOL OnSendingPDU(H245_AudioCapability & pdu, unsigned packetSize) const;
+ BOOL OnReceivedPDU(const H245_AudioCapability & pdu, unsigned & packetSize);
+
+protected:
+ int comfortNoise;
+ int scrambled;
+};
+
+#define CISCO_G726r32 "G726r32"
+
+class AST_CiscoG726Capability : public H323NonStandardAudioCapability {
+ PCLASSINFO(AST_CiscoG726Capability, H323NonStandardAudioCapability);
+
+public:
+ /* Create a new Cisco G.726 capability */
+ AST_CiscoG726Capability(int rx_frames = 80);
+
+ /* Create a copy of the object. */
+ virtual PObject * Clone() const;
+
+ /* Create the codec instance, allocating resources as required. */
+ virtual H323Codec * CreateCodec(H323Codec::Direction direction) const;
+
+ /* Get the name of the media data format this class represents. */
+ virtual PString GetFormatName() const;
+};
+
+#define CISCO_DTMF_RELAY "UserInput/RtpDtmfRelay"
+
+class AST_CiscoDtmfCapability : public H323NonStandardDataCapability
+{
+ PCLASSINFO(AST_CiscoDtmfCapability, H323NonStandardDataCapability);
+
+public:
+ /* Create a new Cisco RTP DTMF Relay capability */
+ AST_CiscoDtmfCapability();
+
+ /* Create a copy of the object. */
+ virtual PObject *Clone() const;
+
+ /* Create the codec instance, allocating resources as required. */
+ virtual H323Codec * CreateCodec(H323Codec::Direction direction) const;
+
+ /* Get the name of the media data format this class represents. */
+ virtual PString GetFormatName() const;
+
+ virtual H323Channel *CreateChannel(H323Connection &,
+ H323Channel::Directions,
+ unsigned,
+ const H245_H2250LogicalChannelParameters *) const
+ {
+ return NULL;
+ }
+};
+
+#endif /* __AST_H323CAPS_H */
diff --git a/trunk/channels/h323/chan_h323.h b/trunk/channels/h323/chan_h323.h
new file mode 100644
index 000000000..62b670fda
--- /dev/null
+++ b/trunk/channels/h323/chan_h323.h
@@ -0,0 +1,269 @@
+/*
+ * chan_h323.h
+ *
+ * OpenH323 Channel Driver for ASTERISK PBX.
+ * By Jeremy McNamara
+ * For The NuFone Network
+ *
+ * This code has been derived from code created by
+ * Michael Manousos and Mark Spencer
+ *
+ * This file is part of the chan_h323 driver for Asterisk
+ *
+ * chan_h323 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * chan_h323 is distributed WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Version Info: $Id$
+ */
+
+#include <arpa/inet.h>
+
+/*
+ * Enable support for sending/reception of tunnelled Q.SIG messages and
+ * some sort of IEs (especially RedirectingNumber) which Cisco CallManager
+ * isn't like to pass in standard Q.931 message.
+ *
+ */
+#define TUNNELLING
+
+#define H323_TUNNEL_CISCO (1 << 0)
+#define H323_TUNNEL_QSIG (1 << 1)
+
+#define H323_HOLD_NOTIFY (1 << 0)
+#define H323_HOLD_Q931ONLY (1 << 1)
+#define H323_HOLD_H450 (1 << 2)
+
+/** call_option struct holds various bits
+ * of information for each call */
+typedef struct call_options {
+ char cid_num[80];
+ char cid_name[80];
+ char cid_rdnis[80];
+ int redirect_reason;
+ int presentation;
+ int type_of_number;
+ int transfer_capability;
+ int fastStart;
+ int h245Tunneling;
+ int silenceSuppression;
+ int progress_setup;
+ int progress_alert;
+ int progress_audio;
+ int dtmfcodec[2];
+ int dtmfmode;
+ int capability;
+ int bridge;
+ int nat;
+ int tunnelOptions;
+ int holdHandling;
+ struct ast_codec_pref prefs;
+} call_options_t;
+
+/* structure to hold the valid asterisk users */
+struct oh323_user {
+ ASTOBJ_COMPONENTS(struct oh323_user);
+// char name[80];
+ char context[80];
+ char secret[80];
+ char accountcode[AST_MAX_ACCOUNT_CODE];
+ int amaflags;
+ int host;
+ struct sockaddr_in addr;
+ struct ast_ha *ha;
+ call_options_t options;
+};
+
+/* structure to hold the valid asterisk peers
+ All peers are registered to a GK if there is one */
+struct oh323_peer {
+ ASTOBJ_COMPONENTS(struct oh323_peer);
+ char mailbox[80];
+ int delme;
+ struct sockaddr_in addr;
+ struct ast_ha *ha;
+ call_options_t options;
+};
+
+/* structure to hold the H.323 aliases which get registered to
+ the H.323 endpoint and gatekeeper */
+struct oh323_alias {
+ ASTOBJ_COMPONENTS(struct oh323_alias);
+ char e164[20]; /* tells a GK to route this E.164 to this alias */
+ char prefix[500]; /* tells a GK this alias supports these prefixes */
+ char secret[20]; /* the H.235 password to send to the GK for authentication */
+ char context[80];
+};
+
+/** call_details struct call detail records
+ to asterisk for processing and used for matching up
+ asterisk channels to acutal h.323 connections */
+typedef struct call_details {
+ unsigned int call_reference;
+ char *call_token;
+ char *call_source_aliases;
+ char *call_dest_alias;
+ char *call_source_name;
+ char *call_source_e164;
+ char *call_dest_e164;
+ char *redirect_number;
+ int redirect_reason;
+ int presentation;
+ int type_of_number;
+ int transfer_capability;
+ char *sourceIp;
+} call_details_t;
+
+typedef struct rtp_info {
+ char addr[32];
+ unsigned int port;
+} rtp_info_t;
+
+/* This is a callback prototype function, called pass
+ DTMF down the RTP. */
+typedef int (*receive_digit_cb)(unsigned, char, const char *, int);
+extern receive_digit_cb on_receive_digit;
+
+/* This is a callback prototype function, called to collect
+ the external RTP port from Asterisk. */
+typedef rtp_info_t *(*on_rtp_cb)(unsigned, const char *);
+extern on_rtp_cb on_external_rtp_create;
+
+/* This is a callback prototype function, called to send
+ the remote IP and RTP port from H.323 to Asterisk */
+typedef void (*start_rtp_cb)(unsigned int, const char *, int, const char *, int);
+extern start_rtp_cb on_start_rtp_channel;
+
+/* This is a callback that happens when call progress is
+ * made, and handles inband progress */
+typedef int (*progress_cb)(unsigned, const char *, int);
+extern progress_cb on_progress;
+
+/* This is a callback prototype function, called upon
+ an incoming call happens. */
+typedef call_options_t *(*setup_incoming_cb)(call_details_t *);
+extern setup_incoming_cb on_incoming_call;
+
+/* This is a callback prototype function, called upon
+ an outbound call. */
+typedef int (*setup_outbound_cb)(call_details_t *);
+extern setup_outbound_cb on_outgoing_call;
+
+/* This is a callback prototype function, called when
+ OnAlerting is invoked */
+typedef void (*chan_ringing_cb)(unsigned, const char *);
+extern chan_ringing_cb on_chan_ringing;
+
+/* This is a callback protoype function, called when
+ OnConnectionEstablished is inovked */
+typedef void (*con_established_cb)(unsigned, const char *);
+extern con_established_cb on_connection_established;
+
+/* This is a callback prototype function, called when
+ OnConnectionCleared callback is invoked */
+typedef void (*clear_con_cb)(unsigned, const char *);
+extern clear_con_cb on_connection_cleared;
+
+/* This is a callback prototype function, called when
+ an H.323 call is answered */
+typedef int (*answer_call_cb)(unsigned, const char *);
+extern answer_call_cb on_answer_call;
+
+/* This is a callback prototype function, called when
+ we know which RTP payload type RFC2833 will be
+ transmitted */
+typedef void (*rfc2833_cb)(unsigned, const char *, int, int);
+extern rfc2833_cb on_set_rfc2833_payload;
+
+typedef void (*hangup_cb)(unsigned, const char *, int);
+extern hangup_cb on_hangup;
+
+typedef void (*setcapabilities_cb)(unsigned, const char *);
+extern setcapabilities_cb on_setcapabilities;
+
+typedef void (*setpeercapabilities_cb)(unsigned, const char *, int, struct ast_codec_pref *);
+extern setpeercapabilities_cb on_setpeercapabilities;
+
+typedef void (*onhold_cb)(unsigned, const char *, int);
+extern onhold_cb on_hold;
+
+/* debug flag */
+extern int h323debug;
+
+#define H323_DTMF_RFC2833 (1 << 0)
+#define H323_DTMF_CISCO (1 << 1)
+#define H323_DTMF_SIGNAL (1 << 2)
+#define H323_DTMF_INBAND (1 << 3)
+
+#define H323_DTMF_RFC2833_PT 101
+#define H323_DTMF_CISCO_PT 121
+
+#ifndef BOOL
+#define BOOL int
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ void h323_gk_urq(void);
+ void h323_end_point_create(void);
+ void h323_end_process(void);
+ int h323_end_point_exist(void);
+
+ void h323_debug(int, unsigned);
+
+ /* callback function handler*/
+ void h323_callback_register(setup_incoming_cb,
+ setup_outbound_cb,
+ on_rtp_cb,
+ start_rtp_cb,
+ clear_con_cb,
+ chan_ringing_cb,
+ con_established_cb,
+ receive_digit_cb,
+ answer_call_cb,
+ progress_cb,
+ rfc2833_cb,
+ hangup_cb,
+ setcapabilities_cb,
+ setpeercapabilities_cb,
+ onhold_cb);
+ int h323_set_capabilities(const char *, int, int, struct ast_codec_pref *, int);
+ int h323_set_alias(struct oh323_alias *);
+ int h323_set_gk(int, char *, char *);
+ void h323_set_id(char *);
+ void h323_show_tokens(void);
+
+ /* H323 listener related funcions */
+ int h323_start_listener(int, struct sockaddr_in);
+
+ void h323_native_bridge(const char *, const char *, char *);
+
+ /* Send a DTMF tone to remote endpoint */
+ void h323_send_tone(const char *call_token, char tone);
+
+ /* H323 create and destroy sessions */
+ int h323_make_call(char *dest, call_details_t *cd, call_options_t *);
+ int h323_clear_call(const char *, int cause);
+
+ /* H.323 alerting and progress */
+ int h323_send_alerting(const char *token);
+ int h323_send_progress(const char *token);
+ int h323_answering_call(const char *token, int);
+ int h323_soft_hangup(const char *data);
+ int h323_show_codec(int fd, int argc, char *argv[]);
+ int h323_hold_call(const char *token, int);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/trunk/channels/h323/cisco-h225.asn b/trunk/channels/h323/cisco-h225.asn
new file mode 100644
index 000000000..1372e67d5
--- /dev/null
+++ b/trunk/channels/h323/cisco-h225.asn
@@ -0,0 +1,74 @@
+CISCO-H225-MESSAGES DEFINITIONS AUTOMATIC TAGS ::=
+BEGIN
+
+H323_UU_NonStdInfo ::= SEQUENCE
+{
+ version INTEGER OPTIONAL,
+ protoParam ProtoParam OPTIONAL,
+ commonParam CommonParam OPTIONAL,
+ ...,
+ dummy1 OCTET STRING OPTIONAL,
+ progIndParam ProgIndParam OPTIONAL,
+ callMgrParam CallMgrParam OPTIONAL,
+ callSignallingParam CallSignallingParam OPTIONAL,
+ dummy2 OCTET STRING OPTIONAL,
+ callPreserveParam CallPreserveParam OPTIONAL
+}
+
+CommonParam ::= SEQUENCE
+{
+ redirectIEinfo RedirectIEinfo,
+ ...
+}
+
+RedirectIEinfo ::= SEQUENCE
+{
+ redirectIE OCTET STRING,
+ ...
+}
+
+ProgIndParam ::= SEQUENCE
+{
+ progIndIEinfo ProgIndIEinfo,
+ ...
+}
+
+ProgIndIEinfo ::= SEQUENCE
+{
+ progIndIE OCTET STRING,
+ ...
+}
+
+ProtoParam ::= SEQUENCE
+{
+ qsigNonStdInfo QsigNonStdInfo,
+ ...
+}
+
+QsigNonStdInfo ::= SEQUENCE
+{
+ iei INTEGER,
+ rawMesg OCTET STRING,
+ ...
+}
+
+CallMgrParam ::= SEQUENCE
+{
+ interclusterVersion INTEGER,
+ enterpriseID OCTET STRING,
+ ...
+}
+
+CallPreserveParam ::= SEQUENCE
+{
+ callPreserveIE BOOLEAN,
+ ...
+}
+
+CallSignallingParam ::= SEQUENCE
+{
+ connectedNumber OCTET STRING (1..127) OPTIONAL,
+ ...
+}
+
+END
diff --git a/trunk/channels/h323/cisco-h225.cxx b/trunk/channels/h323/cisco-h225.cxx
new file mode 100644
index 000000000..37adc4e87
--- /dev/null
+++ b/trunk/channels/h323/cisco-h225.cxx
@@ -0,0 +1,853 @@
+//
+// cisco-h225.cxx
+//
+// Code automatically generated by asnparse.
+//
+
+#ifdef P_USE_PRAGMA
+#pragma implementation "cisco-h225.h"
+#endif
+
+#include <ptlib.h>
+#include "cisco-h225.h"
+
+#define new PNEW
+
+
+#if ! H323_DISABLE_CISCO_H225
+
+//
+// RedirectIEinfo
+//
+
+CISCO_H225_RedirectIEinfo::CISCO_H225_RedirectIEinfo(unsigned tag, PASN_Object::TagClass tagClass)
+ : PASN_Sequence(tag, tagClass, 0, TRUE, 0)
+{
+}
+
+
+#ifndef PASN_NOPRINTON
+void CISCO_H225_RedirectIEinfo::PrintOn(ostream & strm) const
+{
+ int indent = strm.precision() + 2;
+ strm << "{\n";
+ strm << setw(indent+13) << "redirectIE = " << setprecision(indent) << m_redirectIE << '\n';
+ strm << setw(indent-1) << setprecision(indent-2) << "}";
+}
+#endif
+
+
+PObject::Comparison CISCO_H225_RedirectIEinfo::Compare(const PObject & obj) const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(PIsDescendant(&obj, CISCO_H225_RedirectIEinfo), PInvalidCast);
+#endif
+ const CISCO_H225_RedirectIEinfo & other = (const CISCO_H225_RedirectIEinfo &)obj;
+
+ Comparison result;
+
+ if ((result = m_redirectIE.Compare(other.m_redirectIE)) != EqualTo)
+ return result;
+
+ return PASN_Sequence::Compare(other);
+}
+
+
+PINDEX CISCO_H225_RedirectIEinfo::GetDataLength() const
+{
+ PINDEX length = 0;
+ length += m_redirectIE.GetObjectLength();
+ return length;
+}
+
+
+BOOL CISCO_H225_RedirectIEinfo::Decode(PASN_Stream & strm)
+{
+ if (!PreambleDecode(strm))
+ return FALSE;
+
+ if (!m_redirectIE.Decode(strm))
+ return FALSE;
+
+ return UnknownExtensionsDecode(strm);
+}
+
+
+void CISCO_H225_RedirectIEinfo::Encode(PASN_Stream & strm) const
+{
+ PreambleEncode(strm);
+
+ m_redirectIE.Encode(strm);
+
+ UnknownExtensionsEncode(strm);
+}
+
+
+PObject * CISCO_H225_RedirectIEinfo::Clone() const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(IsClass(CISCO_H225_RedirectIEinfo::Class()), PInvalidCast);
+#endif
+ return new CISCO_H225_RedirectIEinfo(*this);
+}
+
+
+//
+// ProgIndIEinfo
+//
+
+CISCO_H225_ProgIndIEinfo::CISCO_H225_ProgIndIEinfo(unsigned tag, PASN_Object::TagClass tagClass)
+ : PASN_Sequence(tag, tagClass, 0, TRUE, 0)
+{
+}
+
+
+#ifndef PASN_NOPRINTON
+void CISCO_H225_ProgIndIEinfo::PrintOn(ostream & strm) const
+{
+ int indent = strm.precision() + 2;
+ strm << "{\n";
+ strm << setw(indent+12) << "progIndIE = " << setprecision(indent) << m_progIndIE << '\n';
+ strm << setw(indent-1) << setprecision(indent-2) << "}";
+}
+#endif
+
+
+PObject::Comparison CISCO_H225_ProgIndIEinfo::Compare(const PObject & obj) const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(PIsDescendant(&obj, CISCO_H225_ProgIndIEinfo), PInvalidCast);
+#endif
+ const CISCO_H225_ProgIndIEinfo & other = (const CISCO_H225_ProgIndIEinfo &)obj;
+
+ Comparison result;
+
+ if ((result = m_progIndIE.Compare(other.m_progIndIE)) != EqualTo)
+ return result;
+
+ return PASN_Sequence::Compare(other);
+}
+
+
+PINDEX CISCO_H225_ProgIndIEinfo::GetDataLength() const
+{
+ PINDEX length = 0;
+ length += m_progIndIE.GetObjectLength();
+ return length;
+}
+
+
+BOOL CISCO_H225_ProgIndIEinfo::Decode(PASN_Stream & strm)
+{
+ if (!PreambleDecode(strm))
+ return FALSE;
+
+ if (!m_progIndIE.Decode(strm))
+ return FALSE;
+
+ return UnknownExtensionsDecode(strm);
+}
+
+
+void CISCO_H225_ProgIndIEinfo::Encode(PASN_Stream & strm) const
+{
+ PreambleEncode(strm);
+
+ m_progIndIE.Encode(strm);
+
+ UnknownExtensionsEncode(strm);
+}
+
+
+PObject * CISCO_H225_ProgIndIEinfo::Clone() const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(IsClass(CISCO_H225_ProgIndIEinfo::Class()), PInvalidCast);
+#endif
+ return new CISCO_H225_ProgIndIEinfo(*this);
+}
+
+
+//
+// QsigNonStdInfo
+//
+
+CISCO_H225_QsigNonStdInfo::CISCO_H225_QsigNonStdInfo(unsigned tag, PASN_Object::TagClass tagClass)
+ : PASN_Sequence(tag, tagClass, 0, TRUE, 0)
+{
+}
+
+
+#ifndef PASN_NOPRINTON
+void CISCO_H225_QsigNonStdInfo::PrintOn(ostream & strm) const
+{
+ int indent = strm.precision() + 2;
+ strm << "{\n";
+ strm << setw(indent+6) << "iei = " << setprecision(indent) << m_iei << '\n';
+ strm << setw(indent+10) << "rawMesg = " << setprecision(indent) << m_rawMesg << '\n';
+ strm << setw(indent-1) << setprecision(indent-2) << "}";
+}
+#endif
+
+
+PObject::Comparison CISCO_H225_QsigNonStdInfo::Compare(const PObject & obj) const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(PIsDescendant(&obj, CISCO_H225_QsigNonStdInfo), PInvalidCast);
+#endif
+ const CISCO_H225_QsigNonStdInfo & other = (const CISCO_H225_QsigNonStdInfo &)obj;
+
+ Comparison result;
+
+ if ((result = m_iei.Compare(other.m_iei)) != EqualTo)
+ return result;
+ if ((result = m_rawMesg.Compare(other.m_rawMesg)) != EqualTo)
+ return result;
+
+ return PASN_Sequence::Compare(other);
+}
+
+
+PINDEX CISCO_H225_QsigNonStdInfo::GetDataLength() const
+{
+ PINDEX length = 0;
+ length += m_iei.GetObjectLength();
+ length += m_rawMesg.GetObjectLength();
+ return length;
+}
+
+
+BOOL CISCO_H225_QsigNonStdInfo::Decode(PASN_Stream & strm)
+{
+ if (!PreambleDecode(strm))
+ return FALSE;
+
+ if (!m_iei.Decode(strm))
+ return FALSE;
+ if (!m_rawMesg.Decode(strm))
+ return FALSE;
+
+ return UnknownExtensionsDecode(strm);
+}
+
+
+void CISCO_H225_QsigNonStdInfo::Encode(PASN_Stream & strm) const
+{
+ PreambleEncode(strm);
+
+ m_iei.Encode(strm);
+ m_rawMesg.Encode(strm);
+
+ UnknownExtensionsEncode(strm);
+}
+
+
+PObject * CISCO_H225_QsigNonStdInfo::Clone() const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(IsClass(CISCO_H225_QsigNonStdInfo::Class()), PInvalidCast);
+#endif
+ return new CISCO_H225_QsigNonStdInfo(*this);
+}
+
+
+//
+// CallMgrParam
+//
+
+CISCO_H225_CallMgrParam::CISCO_H225_CallMgrParam(unsigned tag, PASN_Object::TagClass tagClass)
+ : PASN_Sequence(tag, tagClass, 0, TRUE, 0)
+{
+}
+
+
+#ifndef PASN_NOPRINTON
+void CISCO_H225_CallMgrParam::PrintOn(ostream & strm) const
+{
+ int indent = strm.precision() + 2;
+ strm << "{\n";
+ strm << setw(indent+22) << "interclusterVersion = " << setprecision(indent) << m_interclusterVersion << '\n';
+ strm << setw(indent+15) << "enterpriseID = " << setprecision(indent) << m_enterpriseID << '\n';
+ strm << setw(indent-1) << setprecision(indent-2) << "}";
+}
+#endif
+
+
+PObject::Comparison CISCO_H225_CallMgrParam::Compare(const PObject & obj) const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(PIsDescendant(&obj, CISCO_H225_CallMgrParam), PInvalidCast);
+#endif
+ const CISCO_H225_CallMgrParam & other = (const CISCO_H225_CallMgrParam &)obj;
+
+ Comparison result;
+
+ if ((result = m_interclusterVersion.Compare(other.m_interclusterVersion)) != EqualTo)
+ return result;
+ if ((result = m_enterpriseID.Compare(other.m_enterpriseID)) != EqualTo)
+ return result;
+
+ return PASN_Sequence::Compare(other);
+}
+
+
+PINDEX CISCO_H225_CallMgrParam::GetDataLength() const
+{
+ PINDEX length = 0;
+ length += m_interclusterVersion.GetObjectLength();
+ length += m_enterpriseID.GetObjectLength();
+ return length;
+}
+
+
+BOOL CISCO_H225_CallMgrParam::Decode(PASN_Stream & strm)
+{
+ if (!PreambleDecode(strm))
+ return FALSE;
+
+ if (!m_interclusterVersion.Decode(strm))
+ return FALSE;
+ if (!m_enterpriseID.Decode(strm))
+ return FALSE;
+
+ return UnknownExtensionsDecode(strm);
+}
+
+
+void CISCO_H225_CallMgrParam::Encode(PASN_Stream & strm) const
+{
+ PreambleEncode(strm);
+
+ m_interclusterVersion.Encode(strm);
+ m_enterpriseID.Encode(strm);
+
+ UnknownExtensionsEncode(strm);
+}
+
+
+PObject * CISCO_H225_CallMgrParam::Clone() const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(IsClass(CISCO_H225_CallMgrParam::Class()), PInvalidCast);
+#endif
+ return new CISCO_H225_CallMgrParam(*this);
+}
+
+
+//
+// CallPreserveParam
+//
+
+CISCO_H225_CallPreserveParam::CISCO_H225_CallPreserveParam(unsigned tag, PASN_Object::TagClass tagClass)
+ : PASN_Sequence(tag, tagClass, 0, TRUE, 0)
+{
+}
+
+
+#ifndef PASN_NOPRINTON
+void CISCO_H225_CallPreserveParam::PrintOn(ostream & strm) const
+{
+ int indent = strm.precision() + 2;
+ strm << "{\n";
+ strm << setw(indent+17) << "callPreserveIE = " << setprecision(indent) << m_callPreserveIE << '\n';
+ strm << setw(indent-1) << setprecision(indent-2) << "}";
+}
+#endif
+
+
+PObject::Comparison CISCO_H225_CallPreserveParam::Compare(const PObject & obj) const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(PIsDescendant(&obj, CISCO_H225_CallPreserveParam), PInvalidCast);
+#endif
+ const CISCO_H225_CallPreserveParam & other = (const CISCO_H225_CallPreserveParam &)obj;
+
+ Comparison result;
+
+ if ((result = m_callPreserveIE.Compare(other.m_callPreserveIE)) != EqualTo)
+ return result;
+
+ return PASN_Sequence::Compare(other);
+}
+
+
+PINDEX CISCO_H225_CallPreserveParam::GetDataLength() const
+{
+ PINDEX length = 0;
+ length += m_callPreserveIE.GetObjectLength();
+ return length;
+}
+
+
+BOOL CISCO_H225_CallPreserveParam::Decode(PASN_Stream & strm)
+{
+ if (!PreambleDecode(strm))
+ return FALSE;
+
+ if (!m_callPreserveIE.Decode(strm))
+ return FALSE;
+
+ return UnknownExtensionsDecode(strm);
+}
+
+
+void CISCO_H225_CallPreserveParam::Encode(PASN_Stream & strm) const
+{
+ PreambleEncode(strm);
+
+ m_callPreserveIE.Encode(strm);
+
+ UnknownExtensionsEncode(strm);
+}
+
+
+PObject * CISCO_H225_CallPreserveParam::Clone() const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(IsClass(CISCO_H225_CallPreserveParam::Class()), PInvalidCast);
+#endif
+ return new CISCO_H225_CallPreserveParam(*this);
+}
+
+
+//
+// CallSignallingParam
+//
+
+CISCO_H225_CallSignallingParam::CISCO_H225_CallSignallingParam(unsigned tag, PASN_Object::TagClass tagClass)
+ : PASN_Sequence(tag, tagClass, 1, TRUE, 0)
+{
+ m_connectedNumber.SetConstraints(PASN_Object::FixedConstraint, 1, 127);
+}
+
+
+#ifndef PASN_NOPRINTON
+void CISCO_H225_CallSignallingParam::PrintOn(ostream & strm) const
+{
+ int indent = strm.precision() + 2;
+ strm << "{\n";
+ if (HasOptionalField(e_connectedNumber))
+ strm << setw(indent+18) << "connectedNumber = " << setprecision(indent) << m_connectedNumber << '\n';
+ strm << setw(indent-1) << setprecision(indent-2) << "}";
+}
+#endif
+
+
+PObject::Comparison CISCO_H225_CallSignallingParam::Compare(const PObject & obj) const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(PIsDescendant(&obj, CISCO_H225_CallSignallingParam), PInvalidCast);
+#endif
+ const CISCO_H225_CallSignallingParam & other = (const CISCO_H225_CallSignallingParam &)obj;
+
+ Comparison result;
+
+ if ((result = m_connectedNumber.Compare(other.m_connectedNumber)) != EqualTo)
+ return result;
+
+ return PASN_Sequence::Compare(other);
+}
+
+
+PINDEX CISCO_H225_CallSignallingParam::GetDataLength() const
+{
+ PINDEX length = 0;
+ if (HasOptionalField(e_connectedNumber))
+ length += m_connectedNumber.GetObjectLength();
+ return length;
+}
+
+
+BOOL CISCO_H225_CallSignallingParam::Decode(PASN_Stream & strm)
+{
+ if (!PreambleDecode(strm))
+ return FALSE;
+
+ if (HasOptionalField(e_connectedNumber) && !m_connectedNumber.Decode(strm))
+ return FALSE;
+
+ return UnknownExtensionsDecode(strm);
+}
+
+
+void CISCO_H225_CallSignallingParam::Encode(PASN_Stream & strm) const
+{
+ PreambleEncode(strm);
+
+ if (HasOptionalField(e_connectedNumber))
+ m_connectedNumber.Encode(strm);
+
+ UnknownExtensionsEncode(strm);
+}
+
+
+PObject * CISCO_H225_CallSignallingParam::Clone() const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(IsClass(CISCO_H225_CallSignallingParam::Class()), PInvalidCast);
+#endif
+ return new CISCO_H225_CallSignallingParam(*this);
+}
+
+
+//
+// CommonParam
+//
+
+CISCO_H225_CommonParam::CISCO_H225_CommonParam(unsigned tag, PASN_Object::TagClass tagClass)
+ : PASN_Sequence(tag, tagClass, 0, TRUE, 0)
+{
+}
+
+
+#ifndef PASN_NOPRINTON
+void CISCO_H225_CommonParam::PrintOn(ostream & strm) const
+{
+ int indent = strm.precision() + 2;
+ strm << "{\n";
+ strm << setw(indent+17) << "redirectIEinfo = " << setprecision(indent) << m_redirectIEinfo << '\n';
+ strm << setw(indent-1) << setprecision(indent-2) << "}";
+}
+#endif
+
+
+PObject::Comparison CISCO_H225_CommonParam::Compare(const PObject & obj) const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(PIsDescendant(&obj, CISCO_H225_CommonParam), PInvalidCast);
+#endif
+ const CISCO_H225_CommonParam & other = (const CISCO_H225_CommonParam &)obj;
+
+ Comparison result;
+
+ if ((result = m_redirectIEinfo.Compare(other.m_redirectIEinfo)) != EqualTo)
+ return result;
+
+ return PASN_Sequence::Compare(other);
+}
+
+
+PINDEX CISCO_H225_CommonParam::GetDataLength() const
+{
+ PINDEX length = 0;
+ length += m_redirectIEinfo.GetObjectLength();
+ return length;
+}
+
+
+BOOL CISCO_H225_CommonParam::Decode(PASN_Stream & strm)
+{
+ if (!PreambleDecode(strm))
+ return FALSE;
+
+ if (!m_redirectIEinfo.Decode(strm))
+ return FALSE;
+
+ return UnknownExtensionsDecode(strm);
+}
+
+
+void CISCO_H225_CommonParam::Encode(PASN_Stream & strm) const
+{
+ PreambleEncode(strm);
+
+ m_redirectIEinfo.Encode(strm);
+
+ UnknownExtensionsEncode(strm);
+}
+
+
+PObject * CISCO_H225_CommonParam::Clone() const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(IsClass(CISCO_H225_CommonParam::Class()), PInvalidCast);
+#endif
+ return new CISCO_H225_CommonParam(*this);
+}
+
+
+//
+// ProgIndParam
+//
+
+CISCO_H225_ProgIndParam::CISCO_H225_ProgIndParam(unsigned tag, PASN_Object::TagClass tagClass)
+ : PASN_Sequence(tag, tagClass, 0, TRUE, 0)
+{
+}
+
+
+#ifndef PASN_NOPRINTON
+void CISCO_H225_ProgIndParam::PrintOn(ostream & strm) const
+{
+ int indent = strm.precision() + 2;
+ strm << "{\n";
+ strm << setw(indent+16) << "progIndIEinfo = " << setprecision(indent) << m_progIndIEinfo << '\n';
+ strm << setw(indent-1) << setprecision(indent-2) << "}";
+}
+#endif
+
+
+PObject::Comparison CISCO_H225_ProgIndParam::Compare(const PObject & obj) const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(PIsDescendant(&obj, CISCO_H225_ProgIndParam), PInvalidCast);
+#endif
+ const CISCO_H225_ProgIndParam & other = (const CISCO_H225_ProgIndParam &)obj;
+
+ Comparison result;
+
+ if ((result = m_progIndIEinfo.Compare(other.m_progIndIEinfo)) != EqualTo)
+ return result;
+
+ return PASN_Sequence::Compare(other);
+}
+
+
+PINDEX CISCO_H225_ProgIndParam::GetDataLength() const
+{
+ PINDEX length = 0;
+ length += m_progIndIEinfo.GetObjectLength();
+ return length;
+}
+
+
+BOOL CISCO_H225_ProgIndParam::Decode(PASN_Stream & strm)
+{
+ if (!PreambleDecode(strm))
+ return FALSE;
+
+ if (!m_progIndIEinfo.Decode(strm))
+ return FALSE;
+
+ return UnknownExtensionsDecode(strm);
+}
+
+
+void CISCO_H225_ProgIndParam::Encode(PASN_Stream & strm) const
+{
+ PreambleEncode(strm);
+
+ m_progIndIEinfo.Encode(strm);
+
+ UnknownExtensionsEncode(strm);
+}
+
+
+PObject * CISCO_H225_ProgIndParam::Clone() const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(IsClass(CISCO_H225_ProgIndParam::Class()), PInvalidCast);
+#endif
+ return new CISCO_H225_ProgIndParam(*this);
+}
+
+
+//
+// ProtoParam
+//
+
+CISCO_H225_ProtoParam::CISCO_H225_ProtoParam(unsigned tag, PASN_Object::TagClass tagClass)
+ : PASN_Sequence(tag, tagClass, 0, TRUE, 0)
+{
+}
+
+
+#ifndef PASN_NOPRINTON
+void CISCO_H225_ProtoParam::PrintOn(ostream & strm) const
+{
+ int indent = strm.precision() + 2;
+ strm << "{\n";
+ strm << setw(indent+17) << "qsigNonStdInfo = " << setprecision(indent) << m_qsigNonStdInfo << '\n';
+ strm << setw(indent-1) << setprecision(indent-2) << "}";
+}
+#endif
+
+
+PObject::Comparison CISCO_H225_ProtoParam::Compare(const PObject & obj) const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(PIsDescendant(&obj, CISCO_H225_ProtoParam), PInvalidCast);
+#endif
+ const CISCO_H225_ProtoParam & other = (const CISCO_H225_ProtoParam &)obj;
+
+ Comparison result;
+
+ if ((result = m_qsigNonStdInfo.Compare(other.m_qsigNonStdInfo)) != EqualTo)
+ return result;
+
+ return PASN_Sequence::Compare(other);
+}
+
+
+PINDEX CISCO_H225_ProtoParam::GetDataLength() const
+{
+ PINDEX length = 0;
+ length += m_qsigNonStdInfo.GetObjectLength();
+ return length;
+}
+
+
+BOOL CISCO_H225_ProtoParam::Decode(PASN_Stream & strm)
+{
+ if (!PreambleDecode(strm))
+ return FALSE;
+
+ if (!m_qsigNonStdInfo.Decode(strm))
+ return FALSE;
+
+ return UnknownExtensionsDecode(strm);
+}
+
+
+void CISCO_H225_ProtoParam::Encode(PASN_Stream & strm) const
+{
+ PreambleEncode(strm);
+
+ m_qsigNonStdInfo.Encode(strm);
+
+ UnknownExtensionsEncode(strm);
+}
+
+
+PObject * CISCO_H225_ProtoParam::Clone() const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(IsClass(CISCO_H225_ProtoParam::Class()), PInvalidCast);
+#endif
+ return new CISCO_H225_ProtoParam(*this);
+}
+
+
+//
+// H323_UU_NonStdInfo
+//
+
+CISCO_H225_H323_UU_NonStdInfo::CISCO_H225_H323_UU_NonStdInfo(unsigned tag, PASN_Object::TagClass tagClass)
+ : PASN_Sequence(tag, tagClass, 3, TRUE, 6)
+{
+}
+
+
+#ifndef PASN_NOPRINTON
+void CISCO_H225_H323_UU_NonStdInfo::PrintOn(ostream & strm) const
+{
+ int indent = strm.precision() + 2;
+ strm << "{\n";
+ if (HasOptionalField(e_version))
+ strm << setw(indent+10) << "version = " << setprecision(indent) << m_version << '\n';
+ if (HasOptionalField(e_protoParam))
+ strm << setw(indent+13) << "protoParam = " << setprecision(indent) << m_protoParam << '\n';
+ if (HasOptionalField(e_commonParam))
+ strm << setw(indent+14) << "commonParam = " << setprecision(indent) << m_commonParam << '\n';
+ if (HasOptionalField(e_dummy1))
+ strm << setw(indent+9) << "dummy1 = " << setprecision(indent) << m_dummy1 << '\n';
+ if (HasOptionalField(e_progIndParam))
+ strm << setw(indent+15) << "progIndParam = " << setprecision(indent) << m_progIndParam << '\n';
+ if (HasOptionalField(e_callMgrParam))
+ strm << setw(indent+15) << "callMgrParam = " << setprecision(indent) << m_callMgrParam << '\n';
+ if (HasOptionalField(e_callSignallingParam))
+ strm << setw(indent+22) << "callSignallingParam = " << setprecision(indent) << m_callSignallingParam << '\n';
+ if (HasOptionalField(e_dummy2))
+ strm << setw(indent+9) << "dummy2 = " << setprecision(indent) << m_dummy2 << '\n';
+ if (HasOptionalField(e_callPreserveParam))
+ strm << setw(indent+20) << "callPreserveParam = " << setprecision(indent) << m_callPreserveParam << '\n';
+ strm << setw(indent-1) << setprecision(indent-2) << "}";
+}
+#endif
+
+
+PObject::Comparison CISCO_H225_H323_UU_NonStdInfo::Compare(const PObject & obj) const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(PIsDescendant(&obj, CISCO_H225_H323_UU_NonStdInfo), PInvalidCast);
+#endif
+ const CISCO_H225_H323_UU_NonStdInfo & other = (const CISCO_H225_H323_UU_NonStdInfo &)obj;
+
+ Comparison result;
+
+ if ((result = m_version.Compare(other.m_version)) != EqualTo)
+ return result;
+ if ((result = m_protoParam.Compare(other.m_protoParam)) != EqualTo)
+ return result;
+ if ((result = m_commonParam.Compare(other.m_commonParam)) != EqualTo)
+ return result;
+
+ return PASN_Sequence::Compare(other);
+}
+
+
+PINDEX CISCO_H225_H323_UU_NonStdInfo::GetDataLength() const
+{
+ PINDEX length = 0;
+ if (HasOptionalField(e_version))
+ length += m_version.GetObjectLength();
+ if (HasOptionalField(e_protoParam))
+ length += m_protoParam.GetObjectLength();
+ if (HasOptionalField(e_commonParam))
+ length += m_commonParam.GetObjectLength();
+ return length;
+}
+
+
+BOOL CISCO_H225_H323_UU_NonStdInfo::Decode(PASN_Stream & strm)
+{
+ if (!PreambleDecode(strm))
+ return FALSE;
+
+ if (HasOptionalField(e_version) && !m_version.Decode(strm))
+ return FALSE;
+ if (HasOptionalField(e_protoParam) && !m_protoParam.Decode(strm))
+ return FALSE;
+ if (HasOptionalField(e_commonParam) && !m_commonParam.Decode(strm))
+ return FALSE;
+ if (!KnownExtensionDecode(strm, e_dummy1, m_dummy1))
+ return FALSE;
+ if (!KnownExtensionDecode(strm, e_progIndParam, m_progIndParam))
+ return FALSE;
+ if (!KnownExtensionDecode(strm, e_callMgrParam, m_callMgrParam))
+ return FALSE;
+ if (!KnownExtensionDecode(strm, e_callSignallingParam, m_callSignallingParam))
+ return FALSE;
+ if (!KnownExtensionDecode(strm, e_dummy2, m_dummy2))
+ return FALSE;
+ if (!KnownExtensionDecode(strm, e_callPreserveParam, m_callPreserveParam))
+ return FALSE;
+
+ return UnknownExtensionsDecode(strm);
+}
+
+
+void CISCO_H225_H323_UU_NonStdInfo::Encode(PASN_Stream & strm) const
+{
+ PreambleEncode(strm);
+
+ if (HasOptionalField(e_version))
+ m_version.Encode(strm);
+ if (HasOptionalField(e_protoParam))
+ m_protoParam.Encode(strm);
+ if (HasOptionalField(e_commonParam))
+ m_commonParam.Encode(strm);
+ KnownExtensionEncode(strm, e_dummy1, m_dummy1);
+ KnownExtensionEncode(strm, e_progIndParam, m_progIndParam);
+ KnownExtensionEncode(strm, e_callMgrParam, m_callMgrParam);
+ KnownExtensionEncode(strm, e_callSignallingParam, m_callSignallingParam);
+ KnownExtensionEncode(strm, e_dummy2, m_dummy2);
+ KnownExtensionEncode(strm, e_callPreserveParam, m_callPreserveParam);
+
+ UnknownExtensionsEncode(strm);
+}
+
+
+PObject * CISCO_H225_H323_UU_NonStdInfo::Clone() const
+{
+#ifndef PASN_LEANANDMEAN
+ PAssert(IsClass(CISCO_H225_H323_UU_NonStdInfo::Class()), PInvalidCast);
+#endif
+ return new CISCO_H225_H323_UU_NonStdInfo(*this);
+}
+
+
+#endif // if ! H323_DISABLE_CISCO_H225
+
+
+// End of cisco-h225.cxx
diff --git a/trunk/channels/h323/cisco-h225.h b/trunk/channels/h323/cisco-h225.h
new file mode 100644
index 000000000..7595b4b65
--- /dev/null
+++ b/trunk/channels/h323/cisco-h225.h
@@ -0,0 +1,299 @@
+//
+// cisco-h225.h
+//
+// Code automatically generated by asnparse.
+//
+
+#if ! H323_DISABLE_CISCO_H225
+
+#ifndef __CISCO_H225_H
+#define __CISCO_H225_H
+
+#ifdef P_USE_PRAGMA
+#pragma interface
+#endif
+
+#include <ptclib/asner.h>
+
+//
+// RedirectIEinfo
+//
+
+class CISCO_H225_RedirectIEinfo : public PASN_Sequence
+{
+#ifndef PASN_LEANANDMEAN
+ PCLASSINFO(CISCO_H225_RedirectIEinfo, PASN_Sequence);
+#endif
+ public:
+ CISCO_H225_RedirectIEinfo(unsigned tag = UniversalSequence, TagClass tagClass = UniversalTagClass);
+
+ PASN_OctetString m_redirectIE;
+
+ PINDEX GetDataLength() const;
+ BOOL Decode(PASN_Stream & strm);
+ void Encode(PASN_Stream & strm) const;
+#ifndef PASN_NOPRINTON
+ void PrintOn(ostream & strm) const;
+#endif
+ Comparison Compare(const PObject & obj) const;
+ PObject * Clone() const;
+};
+
+
+//
+// ProgIndIEinfo
+//
+
+class CISCO_H225_ProgIndIEinfo : public PASN_Sequence
+{
+#ifndef PASN_LEANANDMEAN
+ PCLASSINFO(CISCO_H225_ProgIndIEinfo, PASN_Sequence);
+#endif
+ public:
+ CISCO_H225_ProgIndIEinfo(unsigned tag = UniversalSequence, TagClass tagClass = UniversalTagClass);
+
+ PASN_OctetString m_progIndIE;
+
+ PINDEX GetDataLength() const;
+ BOOL Decode(PASN_Stream & strm);
+ void Encode(PASN_Stream & strm) const;
+#ifndef PASN_NOPRINTON
+ void PrintOn(ostream & strm) const;
+#endif
+ Comparison Compare(const PObject & obj) const;
+ PObject * Clone() const;
+};
+
+
+//
+// QsigNonStdInfo
+//
+
+class CISCO_H225_QsigNonStdInfo : public PASN_Sequence
+{
+#ifndef PASN_LEANANDMEAN
+ PCLASSINFO(CISCO_H225_QsigNonStdInfo, PASN_Sequence);
+#endif
+ public:
+ CISCO_H225_QsigNonStdInfo(unsigned tag = UniversalSequence, TagClass tagClass = UniversalTagClass);
+
+ PASN_Integer m_iei;
+ PASN_OctetString m_rawMesg;
+
+ PINDEX GetDataLength() const;
+ BOOL Decode(PASN_Stream & strm);
+ void Encode(PASN_Stream & strm) const;
+#ifndef PASN_NOPRINTON
+ void PrintOn(ostream & strm) const;
+#endif
+ Comparison Compare(const PObject & obj) const;
+ PObject * Clone() const;
+};
+
+
+//
+// CallMgrParam
+//
+
+class CISCO_H225_CallMgrParam : public PASN_Sequence
+{
+#ifndef PASN_LEANANDMEAN
+ PCLASSINFO(CISCO_H225_CallMgrParam, PASN_Sequence);
+#endif
+ public:
+ CISCO_H225_CallMgrParam(unsigned tag = UniversalSequence, TagClass tagClass = UniversalTagClass);
+
+ PASN_Integer m_interclusterVersion;
+ PASN_OctetString m_enterpriseID;
+
+ PINDEX GetDataLength() const;
+ BOOL Decode(PASN_Stream & strm);
+ void Encode(PASN_Stream & strm) const;
+#ifndef PASN_NOPRINTON
+ void PrintOn(ostream & strm) const;
+#endif
+ Comparison Compare(const PObject & obj) const;
+ PObject * Clone() const;
+};
+
+
+//
+// CallPreserveParam
+//
+
+class CISCO_H225_CallPreserveParam : public PASN_Sequence
+{
+#ifndef PASN_LEANANDMEAN
+ PCLASSINFO(CISCO_H225_CallPreserveParam, PASN_Sequence);
+#endif
+ public:
+ CISCO_H225_CallPreserveParam(unsigned tag = UniversalSequence, TagClass tagClass = UniversalTagClass);
+
+ PASN_Boolean m_callPreserveIE;
+
+ PINDEX GetDataLength() const;
+ BOOL Decode(PASN_Stream & strm);
+ void Encode(PASN_Stream & strm) const;
+#ifndef PASN_NOPRINTON
+ void PrintOn(ostream & strm) const;
+#endif
+ Comparison Compare(const PObject & obj) const;
+ PObject * Clone() const;
+};
+
+
+//
+// CallSignallingParam
+//
+
+class CISCO_H225_CallSignallingParam : public PASN_Sequence
+{
+#ifndef PASN_LEANANDMEAN
+ PCLASSINFO(CISCO_H225_CallSignallingParam, PASN_Sequence);
+#endif
+ public:
+ CISCO_H225_CallSignallingParam(unsigned tag = UniversalSequence, TagClass tagClass = UniversalTagClass);
+
+ enum OptionalFields {
+ e_connectedNumber
+ };
+
+ PASN_OctetString m_connectedNumber;
+
+ PINDEX GetDataLength() const;
+ BOOL Decode(PASN_Stream & strm);
+ void Encode(PASN_Stream & strm) const;
+#ifndef PASN_NOPRINTON
+ void PrintOn(ostream & strm) const;
+#endif
+ Comparison Compare(const PObject & obj) const;
+ PObject * Clone() const;
+};
+
+
+//
+// CommonParam
+//
+
+class CISCO_H225_CommonParam : public PASN_Sequence
+{
+#ifndef PASN_LEANANDMEAN
+ PCLASSINFO(CISCO_H225_CommonParam, PASN_Sequence);
+#endif
+ public:
+ CISCO_H225_CommonParam(unsigned tag = UniversalSequence, TagClass tagClass = UniversalTagClass);
+
+ CISCO_H225_RedirectIEinfo m_redirectIEinfo;
+
+ PINDEX GetDataLength() const;
+ BOOL Decode(PASN_Stream & strm);
+ void Encode(PASN_Stream & strm) const;
+#ifndef PASN_NOPRINTON
+ void PrintOn(ostream & strm) const;
+#endif
+ Comparison Compare(const PObject & obj) const;
+ PObject * Clone() const;
+};
+
+
+//
+// ProgIndParam
+//
+
+class CISCO_H225_ProgIndParam : public PASN_Sequence
+{
+#ifndef PASN_LEANANDMEAN
+ PCLASSINFO(CISCO_H225_ProgIndParam, PASN_Sequence);
+#endif
+ public:
+ CISCO_H225_ProgIndParam(unsigned tag = UniversalSequence, TagClass tagClass = UniversalTagClass);
+
+ CISCO_H225_ProgIndIEinfo m_progIndIEinfo;
+
+ PINDEX GetDataLength() const;
+ BOOL Decode(PASN_Stream & strm);
+ void Encode(PASN_Stream & strm) const;
+#ifndef PASN_NOPRINTON
+ void PrintOn(ostream & strm) const;
+#endif
+ Comparison Compare(const PObject & obj) const;
+ PObject * Clone() const;
+};
+
+
+//
+// ProtoParam
+//
+
+class CISCO_H225_ProtoParam : public PASN_Sequence
+{
+#ifndef PASN_LEANANDMEAN
+ PCLASSINFO(CISCO_H225_ProtoParam, PASN_Sequence);
+#endif
+ public:
+ CISCO_H225_ProtoParam(unsigned tag = UniversalSequence, TagClass tagClass = UniversalTagClass);
+
+ CISCO_H225_QsigNonStdInfo m_qsigNonStdInfo;
+
+ PINDEX GetDataLength() const;
+ BOOL Decode(PASN_Stream & strm);
+ void Encode(PASN_Stream & strm) const;
+#ifndef PASN_NOPRINTON
+ void PrintOn(ostream & strm) const;
+#endif
+ Comparison Compare(const PObject & obj) const;
+ PObject * Clone() const;
+};
+
+
+//
+// H323_UU_NonStdInfo
+//
+
+class CISCO_H225_H323_UU_NonStdInfo : public PASN_Sequence
+{
+#ifndef PASN_LEANANDMEAN
+ PCLASSINFO(CISCO_H225_H323_UU_NonStdInfo, PASN_Sequence);
+#endif
+ public:
+ CISCO_H225_H323_UU_NonStdInfo(unsigned tag = UniversalSequence, TagClass tagClass = UniversalTagClass);
+
+ enum OptionalFields {
+ e_version,
+ e_protoParam,
+ e_commonParam,
+ e_dummy1,
+ e_progIndParam,
+ e_callMgrParam,
+ e_callSignallingParam,
+ e_dummy2,
+ e_callPreserveParam
+ };
+
+ PASN_Integer m_version;
+ CISCO_H225_ProtoParam m_protoParam;
+ CISCO_H225_CommonParam m_commonParam;
+ PASN_OctetString m_dummy1;
+ CISCO_H225_ProgIndParam m_progIndParam;
+ CISCO_H225_CallMgrParam m_callMgrParam;
+ CISCO_H225_CallSignallingParam m_callSignallingParam;
+ PASN_OctetString m_dummy2;
+ CISCO_H225_CallPreserveParam m_callPreserveParam;
+
+ PINDEX GetDataLength() const;
+ BOOL Decode(PASN_Stream & strm);
+ void Encode(PASN_Stream & strm) const;
+#ifndef PASN_NOPRINTON
+ void PrintOn(ostream & strm) const;
+#endif
+ Comparison Compare(const PObject & obj) const;
+ PObject * Clone() const;
+};
+
+
+#endif // __CISCO_H225_H
+
+#endif // if ! H323_DISABLE_CISCO_H225
+
+
+// End of cisco-h225.h
diff --git a/trunk/channels/h323/compat_h323.cxx b/trunk/channels/h323/compat_h323.cxx
new file mode 100644
index 000000000..eec7361b2
--- /dev/null
+++ b/trunk/channels/h323/compat_h323.cxx
@@ -0,0 +1,138 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+/*
+ * ast_h323.cpp
+ *
+ * OpenH323 Channel Driver for ASTERISK PBX.
+ * By Jeremy McNamara
+ * For The NuFone Network
+ *
+ * chan_h323 has been derived from code created by
+ * Michael Manousos and Mark Spencer
+ *
+ * This file is part of the chan_h323 driver for Asterisk
+ *
+ * chan_h323 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * chan_h323 is distributed WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Version Info: $Id$
+ */
+#include <ptlib.h>
+#include <h323.h>
+#include <transports.h>
+
+#include "ast_h323.h"
+
+#if VERSION(OPENH323_MAJOR,OPENH323_MINOR,OPENH323_BUILD) < VERSION(1,17,3)
+MyH323TransportTCP::MyH323TransportTCP(
+ H323EndPoint & endpoint,
+ PIPSocket::Address binding,
+ BOOL listen)
+ : H323TransportTCP(endpoint, binding, listen)
+{
+}
+
+BOOL MyH323TransportTCP::Connect()
+{
+ if (IsListening())
+ return TRUE;
+
+ PTCPSocket * socket = new PTCPSocket(remotePort);
+ Open(socket);
+
+ channelPointerMutex.StartRead();
+
+ socket->SetReadTimeout(10000/*endpoint.GetSignallingChannelConnectTimeout()*/);
+
+ localPort = endpoint.GetNextTCPPort();
+ WORD firstPort = localPort;
+ for (;;) {
+ PTRACE(4, "H323TCP\tConnecting to "
+ << remoteAddress << ':' << remotePort
+ << " (local port=" << localPort << ')');
+ if (socket->Connect(localAddress, localPort, remoteAddress))
+ break;
+
+ int errnum = socket->GetErrorNumber();
+ if (localPort == 0 || (errnum != EADDRINUSE && errnum != EADDRNOTAVAIL)) {
+ PTRACE(1, "H323TCP\tCould not connect to "
+ << remoteAddress << ':' << remotePort
+ << " (local port=" << localPort << ") - "
+ << socket->GetErrorText() << '(' << errnum << ')');
+ channelPointerMutex.EndRead();
+ return SetErrorValues(socket->GetErrorCode(), errnum);
+ }
+
+ localPort = endpoint.GetNextTCPPort();
+ if (localPort == firstPort) {
+ PTRACE(1, "H323TCP\tCould not bind to any port in range " <<
+ endpoint.GetTCPPortBase() << " to " << endpoint.GetTCPPortMax());
+ channelPointerMutex.EndRead();
+ return SetErrorValues(socket->GetErrorCode(), errnum);
+ }
+ }
+
+ socket->SetReadTimeout(PMaxTimeInterval);
+
+ channelPointerMutex.EndRead();
+
+ return OnOpen();
+}
+#endif
+
+BOOL MyH323TransportUDP::DiscoverGatekeeper(H323Gatekeeper &gk, H323RasPDU &pdu, const H323TransportAddress &address)
+{
+ PThread *thd = PThread::Current();
+
+ /* If we run in OpenH323's thread use it instead of creating new one */
+ if (thd)
+ return H323TransportUDP::DiscoverGatekeeper(gk, pdu, address);
+
+ /* Make copy of arguments to pass them into thread */
+ discoverGatekeeper = &gk;
+ discoverPDU = &pdu;
+ discoverAddress = &address;
+
+ /* Assume discovery thread isn't finished */
+ discoverReady = FALSE;
+
+ /* Create discovery thread */
+ thd = PThread::Create(PCREATE_NOTIFIER(DiscoverMain), 0,
+ PThread::NoAutoDeleteThread,
+ PThread::NormalPriority,
+ "GkDiscovery:%x");
+
+ /* Wait until discovery thread signal us its finished */
+ for(;;) {
+ discoverMutex.Wait();
+ if (discoverReady) /* Thread has been finished */
+ break;
+ discoverMutex.Signal();
+ }
+ discoverMutex.Signal();
+
+ /* Cleanup/delete thread */
+ thd->WaitForTermination();
+ delete thd;
+
+ return discoverResult;
+}
+
+void MyH323TransportUDP::DiscoverMain(PThread &thread, INT arg)
+{
+ PWaitAndSignal m(discoverMutex);
+
+ discoverResult = H323TransportUDP::DiscoverGatekeeper(*discoverGatekeeper, *discoverPDU, *discoverAddress);
+ discoverReady = TRUE;
+}
diff --git a/trunk/channels/h323/compat_h323.h b/trunk/channels/h323/compat_h323.h
new file mode 100644
index 000000000..5437898f4
--- /dev/null
+++ b/trunk/channels/h323/compat_h323.h
@@ -0,0 +1,94 @@
+#ifndef COMPAT_H323_H
+#define COMPAT_H323_H
+
+#if VERSION(OPENH323_MAJOR,OPENH323_MINOR,OPENH323_BUILD) < VERSION(1,17,3)
+/**
+ * Workaround for broken (less than 1.17.3) OpenH323 stack to be able to
+ * make TCP connections from specific address
+ */
+class MyH323TransportTCP : public H323TransportTCP
+{
+ PCLASSINFO(MyH323TransportTCP, H323TransportTCP);
+
+public:
+ MyH323TransportTCP(
+ H323EndPoint & endpoint, ///< H323 End Point object
+ PIPSocket::Address binding = PIPSocket::GetDefaultIpAny(), ///< Local interface to use
+ BOOL listen = FALSE ///< Flag for need to wait for remote to connect
+ );
+ /**Connect to the remote party.
+ */
+ virtual BOOL Connect();
+};
+#else
+#define MyH323TransportTCP H323TransportTCP
+#endif /* <VERSION(1,17,3) */
+
+class MyH323TransportUDP: public H323TransportUDP
+{
+ PCLASSINFO(MyH323TransportUDP, H323TransportUDP);
+
+public:
+ MyH323TransportUDP(H323EndPoint &endpoint,
+ PIPSocket::Address binding = PIPSocket::GetDefaultIpAny(),
+ WORD localPort = 0,
+ WORD remotePort = 0): H323TransportUDP(endpoint, binding, localPort, remotePort)
+ {
+ }
+ virtual BOOL DiscoverGatekeeper(H323Gatekeeper &,
+ H323RasPDU &,
+ const H323TransportAddress &);
+protected:
+ PDECLARE_NOTIFIER(PThread, MyH323TransportUDP, DiscoverMain);
+ H323Gatekeeper *discoverGatekeeper;
+ H323RasPDU *discoverPDU;
+ const H323TransportAddress *discoverAddress;
+ BOOL discoverResult;
+ BOOL discoverReady;
+ PMutex discoverMutex;
+};
+
+template <class _Abstract_T, typename _Key_T = PString>
+class MyPFactory: public PFactory<_Abstract_T, _Key_T>
+{
+public:
+ template <class _Concrete_T> class Worker: public PFactory<_Abstract_T, _Key_T>::WorkerBase
+ {
+ public:
+ Worker(const _Key_T &_key, bool singleton = false)
+ :PFactory<_Abstract_T, _Key_T>::WorkerBase(singleton), key(_key)
+ {
+ PFactory<_Abstract_T, _Key_T>::Register(key, this);
+ }
+ ~Worker()
+ {
+ PFactory<_Abstract_T, _Key_T>::Unregister(key);
+ }
+ protected:
+ virtual _Abstract_T *Create(const _Key_T &) const { return new _Concrete_T; }
+
+ private:
+ PString key;
+ };
+};
+
+#ifdef H323_REGISTER_CAPABILITY
+#undef H323_REGISTER_CAPABILITY
+#endif
+#define H323_REGISTER_CAPABILITY(cls, capName) static MyPFactory<H323Capability>::Worker<cls> cls##Factory(capName, true)
+
+#ifdef OPAL_MEDIA_FORMAT_DECLARE
+#undef OPAL_MEDIA_FORMAT_DECLARE
+#endif
+
+#define OPAL_MEDIA_FORMAT_DECLARE(classname, _fullName, _defaultSessionID, _rtpPayloadType, _needsJitter,_bandwidth, _frameSize, _frameTime, _timeUnits, _timeStamp) \
+class classname : public OpalMediaFormat \
+{ \
+ public: \
+ classname() \
+ : OpalMediaFormat(_fullName, _defaultSessionID, _rtpPayloadType, _needsJitter, _bandwidth, \
+ _frameSize, _frameTime, _timeUnits, _timeStamp){} \
+}; \
+static MyPFactory<OpalMediaFormat>::Worker<classname> classname##Factory(_fullName, true)
+
+#endif /* !defined AST_H323_H */
diff --git a/trunk/channels/h323/noexport.map b/trunk/channels/h323/noexport.map
new file mode 100644
index 000000000..b51f84263
--- /dev/null
+++ b/trunk/channels/h323/noexport.map
@@ -0,0 +1,5 @@
+{
+ global:
+ _Z11PAssertFuncPKc;
+ local: *;
+}; \ No newline at end of file
diff --git a/trunk/channels/iax2-parser.c b/trunk/channels/iax2-parser.c
new file mode 100644
index 000000000..a86bf7428
--- /dev/null
+++ b/trunk/channels/iax2-parser.c
@@ -0,0 +1,1080 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Implementation of Inter-Asterisk eXchange Protocol, v 2
+ *
+ * \author Mark Spencer <markster@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "asterisk/frame.h"
+#include "asterisk/utils.h"
+#include "asterisk/unaligned.h"
+#include "asterisk/config.h"
+#include "asterisk/lock.h"
+#include "asterisk/threadstorage.h"
+
+#include "iax2.h"
+#include "iax2-parser.h"
+#include "iax2-provision.h"
+
+static int frames = 0;
+static int iframes = 0;
+static int oframes = 0;
+
+#if !defined(LOW_MEMORY)
+static void frame_cache_cleanup(void *data);
+
+/*! \brief A per-thread cache of iax_frame structures */
+AST_THREADSTORAGE_CUSTOM(frame_cache, NULL, frame_cache_cleanup);
+
+/*! \brief This is just so iax_frames, a list head struct for holding a list of
+ * iax_frame structures, is defined. */
+AST_LIST_HEAD_NOLOCK(iax_frames, iax_frame);
+#endif
+
+static void internaloutput(const char *str)
+{
+ fputs(str, stdout);
+}
+
+static void internalerror(const char *str)
+{
+ fprintf(stderr, "WARNING: %s", str);
+}
+
+static void (*outputf)(const char *str) = internaloutput;
+static void (*errorf)(const char *str) = internalerror;
+
+static void dump_addr(char *output, int maxlen, void *value, int len)
+{
+ struct sockaddr_in sin;
+ if (len == (int)sizeof(sin)) {
+ memcpy(&sin, value, len);
+ snprintf(output, maxlen, "IPV4 %s:%d", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+ } else {
+ ast_copy_string(output, "Invalid Address", maxlen);
+ }
+}
+
+static void dump_string(char *output, int maxlen, void *value, int len)
+{
+ maxlen--;
+ if (maxlen > len)
+ maxlen = len;
+ strncpy(output, value, maxlen);
+ output[maxlen] = '\0';
+}
+
+static void dump_prefs(char *output, int maxlen, void *value, int len)
+{
+ struct ast_codec_pref pref;
+ int total_len = 0;
+
+ maxlen--;
+ total_len = maxlen;
+
+ if (maxlen > len)
+ maxlen = len;
+
+ strncpy(output, value, maxlen);
+ output[maxlen] = '\0';
+
+ ast_codec_pref_convert(&pref, output, total_len, 0);
+ memset(output,0,total_len);
+ ast_codec_pref_string(&pref, output, total_len);
+}
+
+static void dump_int(char *output, int maxlen, void *value, int len)
+{
+ if (len == (int)sizeof(unsigned int))
+ snprintf(output, maxlen, "%lu", (unsigned long)ntohl(get_unaligned_uint32(value)));
+ else
+ ast_copy_string(output, "Invalid INT", maxlen);
+}
+
+static void dump_short(char *output, int maxlen, void *value, int len)
+{
+ if (len == (int)sizeof(unsigned short))
+ snprintf(output, maxlen, "%d", ntohs(get_unaligned_uint16(value)));
+ else
+ ast_copy_string(output, "Invalid SHORT", maxlen);
+}
+
+static void dump_byte(char *output, int maxlen, void *value, int len)
+{
+ if (len == (int)sizeof(unsigned char))
+ snprintf(output, maxlen, "%d", *((unsigned char *)value));
+ else
+ ast_copy_string(output, "Invalid BYTE", maxlen);
+}
+
+static void dump_datetime(char *output, int maxlen, void *value, int len)
+{
+ struct ast_tm tm;
+ unsigned long val = (unsigned long) ntohl(get_unaligned_uint32(value));
+ if (len == (int)sizeof(unsigned int)) {
+ tm.tm_sec = (val & 0x1f) << 1;
+ tm.tm_min = (val >> 5) & 0x3f;
+ tm.tm_hour = (val >> 11) & 0x1f;
+ tm.tm_mday = (val >> 16) & 0x1f;
+ tm.tm_mon = ((val >> 21) & 0x0f) - 1;
+ tm.tm_year = ((val >> 25) & 0x7f) + 100;
+ ast_strftime(output, maxlen, "%Y-%m-%d %T", &tm);
+ } else
+ ast_copy_string(output, "Invalid DATETIME format!", maxlen);
+}
+
+static void dump_ipaddr(char *output, int maxlen, void *value, int len)
+{
+ struct sockaddr_in sin;
+ if (len == (int)sizeof(unsigned int)) {
+ memcpy(&sin.sin_addr, value, len);
+ snprintf(output, maxlen, "%s", ast_inet_ntoa(sin.sin_addr));
+ } else
+ ast_copy_string(output, "Invalid IPADDR", maxlen);
+}
+
+
+static void dump_prov_flags(char *output, int maxlen, void *value, int len)
+{
+ char buf[256] = "";
+ if (len == (int)sizeof(unsigned int))
+ snprintf(output, maxlen, "%lu (%s)", (unsigned long)ntohl(get_unaligned_uint32(value)),
+ iax_provflags2str(buf, sizeof(buf), ntohl(get_unaligned_uint32(value))));
+ else
+ ast_copy_string(output, "Invalid INT", maxlen);
+}
+
+static void dump_samprate(char *output, int maxlen, void *value, int len)
+{
+ char tmp[256]="";
+ int sr;
+ if (len == (int)sizeof(unsigned short)) {
+ sr = ntohs(*((unsigned short *)value));
+ if (sr & IAX_RATE_8KHZ)
+ strcat(tmp, ",8khz");
+ if (sr & IAX_RATE_11KHZ)
+ strcat(tmp, ",11.025khz");
+ if (sr & IAX_RATE_16KHZ)
+ strcat(tmp, ",16khz");
+ if (sr & IAX_RATE_22KHZ)
+ strcat(tmp, ",22.05khz");
+ if (sr & IAX_RATE_44KHZ)
+ strcat(tmp, ",44.1khz");
+ if (sr & IAX_RATE_48KHZ)
+ strcat(tmp, ",48khz");
+ if (strlen(tmp))
+ ast_copy_string(output, &tmp[1], maxlen);
+ else
+ ast_copy_string(output, "None Specified!\n", maxlen);
+ } else
+ ast_copy_string(output, "Invalid SHORT", maxlen);
+
+}
+
+static void dump_prov_ies(char *output, int maxlen, unsigned char *iedata, int len);
+static void dump_prov(char *output, int maxlen, void *value, int len)
+{
+ dump_prov_ies(output, maxlen, value, len);
+}
+
+static struct iax2_ie {
+ int ie;
+ char *name;
+ void (*dump)(char *output, int maxlen, void *value, int len);
+} ies[] = {
+ { IAX_IE_CALLED_NUMBER, "CALLED NUMBER", dump_string },
+ { IAX_IE_CALLING_NUMBER, "CALLING NUMBER", dump_string },
+ { IAX_IE_CALLING_ANI, "ANI", dump_string },
+ { IAX_IE_CALLING_NAME, "CALLING NAME", dump_string },
+ { IAX_IE_CALLED_CONTEXT, "CALLED CONTEXT", dump_string },
+ { IAX_IE_USERNAME, "USERNAME", dump_string },
+ { IAX_IE_PASSWORD, "PASSWORD", dump_string },
+ { IAX_IE_CAPABILITY, "CAPABILITY", dump_int },
+ { IAX_IE_FORMAT, "FORMAT", dump_int },
+ { IAX_IE_LANGUAGE, "LANGUAGE", dump_string },
+ { IAX_IE_VERSION, "VERSION", dump_short },
+ { IAX_IE_ADSICPE, "ADSICPE", dump_short },
+ { IAX_IE_DNID, "DNID", dump_string },
+ { IAX_IE_AUTHMETHODS, "AUTHMETHODS", dump_short },
+ { IAX_IE_CHALLENGE, "CHALLENGE", dump_string },
+ { IAX_IE_MD5_RESULT, "MD5 RESULT", dump_string },
+ { IAX_IE_RSA_RESULT, "RSA RESULT", dump_string },
+ { IAX_IE_APPARENT_ADDR, "APPARENT ADDRESS", dump_addr },
+ { IAX_IE_REFRESH, "REFRESH", dump_short },
+ { IAX_IE_DPSTATUS, "DIALPLAN STATUS", dump_short },
+ { IAX_IE_CALLNO, "CALL NUMBER", dump_short },
+ { IAX_IE_CAUSE, "CAUSE", dump_string },
+ { IAX_IE_IAX_UNKNOWN, "UNKNOWN IAX CMD", dump_byte },
+ { IAX_IE_MSGCOUNT, "MESSAGE COUNT", dump_short },
+ { IAX_IE_AUTOANSWER, "AUTO ANSWER REQ" },
+ { IAX_IE_TRANSFERID, "TRANSFER ID", dump_int },
+ { IAX_IE_RDNIS, "REFERRING DNIS", dump_string },
+ { IAX_IE_PROVISIONING, "PROVISIONING", dump_prov },
+ { IAX_IE_AESPROVISIONING, "AES PROVISIONG" },
+ { IAX_IE_DATETIME, "DATE TIME", dump_datetime },
+ { IAX_IE_DEVICETYPE, "DEVICE TYPE", dump_string },
+ { IAX_IE_SERVICEIDENT, "SERVICE IDENT", dump_string },
+ { IAX_IE_FIRMWAREVER, "FIRMWARE VER", dump_short },
+ { IAX_IE_FWBLOCKDESC, "FW BLOCK DESC", dump_int },
+ { IAX_IE_FWBLOCKDATA, "FW BLOCK DATA" },
+ { IAX_IE_PROVVER, "PROVISIONG VER", dump_int },
+ { IAX_IE_CALLINGPRES, "CALLING PRESNTN", dump_byte },
+ { IAX_IE_CALLINGTON, "CALLING TYPEOFNUM", dump_byte },
+ { IAX_IE_CALLINGTNS, "CALLING TRANSITNET", dump_short },
+ { IAX_IE_SAMPLINGRATE, "SAMPLINGRATE", dump_samprate },
+ { IAX_IE_CAUSECODE, "CAUSE CODE", dump_byte },
+ { IAX_IE_ENCRYPTION, "ENCRYPTION", dump_short },
+ { IAX_IE_ENCKEY, "ENCRYPTION KEY" },
+ { IAX_IE_CODEC_PREFS, "CODEC_PREFS", dump_prefs },
+ { IAX_IE_RR_JITTER, "RR_JITTER", dump_int },
+ { IAX_IE_RR_LOSS, "RR_LOSS", dump_int },
+ { IAX_IE_RR_PKTS, "RR_PKTS", dump_int },
+ { IAX_IE_RR_DELAY, "RR_DELAY", dump_short },
+ { IAX_IE_RR_DROPPED, "RR_DROPPED", dump_int },
+ { IAX_IE_RR_OOO, "RR_OUTOFORDER", dump_int },
+ { IAX_IE_VARIABLE, "VARIABLE", dump_string },
+ { IAX_IE_OSPTOKEN, "OSPTOKEN" },
+};
+
+static struct iax2_ie prov_ies[] = {
+ { PROV_IE_USEDHCP, "USEDHCP" },
+ { PROV_IE_IPADDR, "IPADDR", dump_ipaddr },
+ { PROV_IE_SUBNET, "SUBNET", dump_ipaddr },
+ { PROV_IE_GATEWAY, "GATEWAY", dump_ipaddr },
+ { PROV_IE_PORTNO, "BINDPORT", dump_short },
+ { PROV_IE_USER, "USERNAME", dump_string },
+ { PROV_IE_PASS, "PASSWORD", dump_string },
+ { PROV_IE_LANG, "LANGUAGE", dump_string },
+ { PROV_IE_TOS, "TYPEOFSERVICE", dump_byte },
+ { PROV_IE_FLAGS, "FLAGS", dump_prov_flags },
+ { PROV_IE_FORMAT, "FORMAT", dump_int },
+ { PROV_IE_AESKEY, "AESKEY" },
+ { PROV_IE_SERVERIP, "SERVERIP", dump_ipaddr },
+ { PROV_IE_SERVERPORT, "SERVERPORT", dump_short },
+ { PROV_IE_NEWAESKEY, "NEWAESKEY" },
+ { PROV_IE_PROVVER, "PROV VERSION", dump_int },
+ { PROV_IE_ALTSERVER, "ALTSERVERIP", dump_ipaddr },
+};
+
+const char *iax_ie2str(int ie)
+{
+ int x;
+ for (x=0;x<(int)sizeof(ies) / (int)sizeof(ies[0]); x++) {
+ if (ies[x].ie == ie)
+ return ies[x].name;
+ }
+ return "Unknown IE";
+}
+
+
+static void dump_prov_ies(char *output, int maxlen, unsigned char *iedata, int len)
+{
+ int ielen;
+ int ie;
+ int x;
+ int found;
+ char interp[80];
+ char tmp[256];
+ if (len < 2)
+ return;
+ strcpy(output, "\n");
+ maxlen -= strlen(output); output += strlen(output);
+ while(len > 2) {
+ ie = iedata[0];
+ ielen = iedata[1];
+ if (ielen + 2> len) {
+ snprintf(tmp, (int)sizeof(tmp), "Total Prov IE length of %d bytes exceeds remaining prov frame length of %d bytes\n", ielen + 2, len);
+ ast_copy_string(output, tmp, maxlen);
+ maxlen -= strlen(output);
+ output += strlen(output);
+ return;
+ }
+ found = 0;
+ for (x=0;x<(int)sizeof(prov_ies) / (int)sizeof(prov_ies[0]); x++) {
+ if (prov_ies[x].ie == ie) {
+ if (prov_ies[x].dump) {
+ prov_ies[x].dump(interp, (int)sizeof(interp), iedata + 2, ielen);
+ snprintf(tmp, (int)sizeof(tmp), " %-15.15s : %s\n", prov_ies[x].name, interp);
+ ast_copy_string(output, tmp, maxlen);
+ maxlen -= strlen(output); output += strlen(output);
+ } else {
+ if (ielen)
+ snprintf(interp, (int)sizeof(interp), "%d bytes", ielen);
+ else
+ strcpy(interp, "Present");
+ snprintf(tmp, (int)sizeof(tmp), " %-15.15s : %s\n", prov_ies[x].name, interp);
+ ast_copy_string(output, tmp, maxlen);
+ maxlen -= strlen(output); output += strlen(output);
+ }
+ found++;
+ }
+ }
+ if (!found) {
+ snprintf(tmp, (int)sizeof(tmp), " Unknown Prov IE %03d : Present\n", ie);
+ ast_copy_string(output, tmp, maxlen);
+ maxlen -= strlen(output); output += strlen(output);
+ }
+ iedata += (2 + ielen);
+ len -= (2 + ielen);
+ }
+}
+
+static void dump_ies(unsigned char *iedata, int len)
+{
+ int ielen;
+ int ie;
+ int x;
+ int found;
+ char interp[1024];
+ char tmp[1024];
+ if (len < 2)
+ return;
+ while(len > 2) {
+ ie = iedata[0];
+ ielen = iedata[1];
+ if (ielen + 2> len) {
+ snprintf(tmp, (int)sizeof(tmp), "Total IE length of %d bytes exceeds remaining frame length of %d bytes\n", ielen + 2, len);
+ outputf(tmp);
+ return;
+ }
+ found = 0;
+ for (x=0;x<(int)sizeof(ies) / (int)sizeof(ies[0]); x++) {
+ if (ies[x].ie == ie) {
+ if (ies[x].dump) {
+ ies[x].dump(interp, (int)sizeof(interp), iedata + 2, ielen);
+ snprintf(tmp, (int)sizeof(tmp), " %-15.15s : %s\n", ies[x].name, interp);
+ outputf(tmp);
+ } else {
+ if (ielen)
+ snprintf(interp, (int)sizeof(interp), "%d bytes", ielen);
+ else
+ strcpy(interp, "Present");
+ snprintf(tmp, (int)sizeof(tmp), " %-15.15s : %s\n", ies[x].name, interp);
+ outputf(tmp);
+ }
+ found++;
+ }
+ }
+ if (!found) {
+ snprintf(tmp, (int)sizeof(tmp), " Unknown IE %03d : Present\n", ie);
+ outputf(tmp);
+ }
+ iedata += (2 + ielen);
+ len -= (2 + ielen);
+ }
+ outputf("\n");
+}
+
+void iax_showframe(struct iax_frame *f, struct ast_iax2_full_hdr *fhi, int rx, struct sockaddr_in *sin, int datalen)
+{
+ const char *frames[] = {
+ "(0?)",
+ "DTMF_E ",
+ "VOICE ",
+ "VIDEO ",
+ "CONTROL",
+ "NULL ",
+ "IAX ",
+ "TEXT ",
+ "IMAGE ",
+ "HTML ",
+ "CNG ",
+ "MODEM ",
+ "DTMF_B ",
+ };
+ const char *iaxs[] = {
+ "(0?)",
+ "NEW ",
+ "PING ",
+ "PONG ",
+ "ACK ",
+ "HANGUP ",
+ "REJECT ",
+ "ACCEPT ",
+ "AUTHREQ",
+ "AUTHREP",
+ "INVAL ",
+ "LAGRQ ",
+ "LAGRP ",
+ "REGREQ ",
+ "REGAUTH",
+ "REGACK ",
+ "REGREJ ",
+ "REGREL ",
+ "VNAK ",
+ "DPREQ ",
+ "DPREP ",
+ "DIAL ",
+ "TXREQ ",
+ "TXCNT ",
+ "TXACC ",
+ "TXREADY",
+ "TXREL ",
+ "TXREJ ",
+ "QUELCH ",
+ "UNQULCH",
+ "POKE ",
+ "PAGE ",
+ "MWI ",
+ "UNSPRTD",
+ "TRANSFR",
+ "PROVISN",
+ "FWDWNLD",
+ "FWDATA ",
+ "TXMEDIA"
+ };
+ const char *cmds[] = {
+ "(0?)",
+ "HANGUP ",
+ "RING ",
+ "RINGING",
+ "ANSWER ",
+ "BUSY ",
+ "TKOFFHK",
+ "OFFHOOK",
+ "CONGSTN",
+ "FLASH ",
+ "WINK ",
+ "OPTION ",
+ "RDKEY ",
+ "RDUNKEY",
+ "PROGRES",
+ "PROCDNG",
+ "HOLD ",
+ "UNHOLD ",
+ "VIDUPDT", };
+ struct ast_iax2_full_hdr *fh;
+ char retries[20];
+ char class2[20];
+ char subclass2[20];
+ const char *class;
+ const char *subclass;
+ char *dir;
+ char tmp[512];
+
+ switch(rx) {
+ case 0:
+ dir = "Tx";
+ break;
+ case 2:
+ dir = "TE";
+ break;
+ case 3:
+ dir = "RD";
+ break;
+ default:
+ dir = "Rx";
+ break;
+ }
+ if (f) {
+ fh = f->data;
+ snprintf(retries, sizeof(retries), "%03d", f->retries);
+ } else {
+ fh = fhi;
+ if (ntohs(fh->dcallno) & IAX_FLAG_RETRANS)
+ strcpy(retries, "Yes");
+ else
+ strcpy(retries, " No");
+ }
+ if (!(ntohs(fh->scallno) & IAX_FLAG_FULL)) {
+ /* Don't mess with mini-frames */
+ return;
+ }
+ if (fh->type >= (int)sizeof(frames)/(int)sizeof(frames[0])) {
+ snprintf(class2, sizeof(class2), "(%d?)", fh->type);
+ class = class2;
+ } else {
+ class = frames[(int)fh->type];
+ }
+ if (fh->type == AST_FRAME_DTMF_BEGIN || fh->type == AST_FRAME_DTMF_END) {
+ sprintf(subclass2, "%c", fh->csub);
+ subclass = subclass2;
+ } else if (fh->type == AST_FRAME_IAX) {
+ if (fh->csub >= (int)sizeof(iaxs)/(int)sizeof(iaxs[0])) {
+ snprintf(subclass2, sizeof(subclass2), "(%d?)", fh->csub);
+ subclass = subclass2;
+ } else {
+ subclass = iaxs[(int)fh->csub];
+ }
+ } else if (fh->type == AST_FRAME_CONTROL) {
+ if (fh->csub >= (int)sizeof(cmds)/(int)sizeof(cmds[0])) {
+ snprintf(subclass2, sizeof(subclass2), "(%d?)", fh->csub);
+ subclass = subclass2;
+ } else {
+ subclass = cmds[(int)fh->csub];
+ }
+ } else {
+ snprintf(subclass2, sizeof(subclass2), "%d", fh->csub);
+ subclass = subclass2;
+ }
+ snprintf(tmp, sizeof(tmp),
+ "%s-Frame Retry[%s] -- OSeqno: %3.3d ISeqno: %3.3d Type: %s Subclass: %s\n",
+ dir,
+ retries, fh->oseqno, fh->iseqno, class, subclass);
+ outputf(tmp);
+ snprintf(tmp, sizeof(tmp),
+ " Timestamp: %05lums SCall: %5.5d DCall: %5.5d [%s:%d]\n",
+ (unsigned long)ntohl(fh->ts),
+ ntohs(fh->scallno) & ~IAX_FLAG_FULL, ntohs(fh->dcallno) & ~IAX_FLAG_RETRANS,
+ ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ outputf(tmp);
+ if (fh->type == AST_FRAME_IAX)
+ dump_ies(fh->iedata, datalen);
+}
+
+int iax_ie_append_raw(struct iax_ie_data *ied, unsigned char ie, const void *data, int datalen)
+{
+ char tmp[256];
+ if (datalen > ((int)sizeof(ied->buf) - ied->pos)) {
+ snprintf(tmp, (int)sizeof(tmp), "Out of space for ie '%s' (%d), need %d have %d\n", iax_ie2str(ie), ie, datalen, (int)sizeof(ied->buf) - ied->pos);
+ errorf(tmp);
+ return -1;
+ }
+ ied->buf[ied->pos++] = ie;
+ ied->buf[ied->pos++] = datalen;
+ memcpy(ied->buf + ied->pos, data, datalen);
+ ied->pos += datalen;
+ return 0;
+}
+
+int iax_ie_append_addr(struct iax_ie_data *ied, unsigned char ie, const struct sockaddr_in *sin)
+{
+ return iax_ie_append_raw(ied, ie, sin, (int)sizeof(struct sockaddr_in));
+}
+
+int iax_ie_append_int(struct iax_ie_data *ied, unsigned char ie, unsigned int value)
+{
+ unsigned int newval;
+ newval = htonl(value);
+ return iax_ie_append_raw(ied, ie, &newval, (int)sizeof(newval));
+}
+
+int iax_ie_append_short(struct iax_ie_data *ied, unsigned char ie, unsigned short value)
+{
+ unsigned short newval;
+ newval = htons(value);
+ return iax_ie_append_raw(ied, ie, &newval, (int)sizeof(newval));
+}
+
+int iax_ie_append_str(struct iax_ie_data *ied, unsigned char ie, const char *str)
+{
+ return iax_ie_append_raw(ied, ie, str, strlen(str));
+}
+
+int iax_ie_append_byte(struct iax_ie_data *ied, unsigned char ie, unsigned char dat)
+{
+ return iax_ie_append_raw(ied, ie, &dat, 1);
+}
+
+int iax_ie_append(struct iax_ie_data *ied, unsigned char ie)
+{
+ return iax_ie_append_raw(ied, ie, NULL, 0);
+}
+
+void iax_set_output(void (*func)(const char *))
+{
+ outputf = func;
+}
+
+void iax_set_error(void (*func)(const char *))
+{
+ errorf = func;
+}
+
+int iax_parse_ies(struct iax_ies *ies, unsigned char *data, int datalen)
+{
+ /* Parse data into information elements */
+ int len;
+ int ie;
+ char tmp[256], *tmp2;
+ struct ast_variable *var, *var2, *prev;
+ unsigned int count;
+ memset(ies, 0, (int)sizeof(struct iax_ies));
+ ies->msgcount = -1;
+ ies->firmwarever = -1;
+ ies->calling_ton = -1;
+ ies->calling_tns = -1;
+ ies->calling_pres = -1;
+ ies->samprate = IAX_RATE_8KHZ;
+ while(datalen >= 2) {
+ ie = data[0];
+ len = data[1];
+ if (len > datalen - 2) {
+ errorf("Information element length exceeds message size\n");
+ return -1;
+ }
+ switch(ie) {
+ case IAX_IE_CALLED_NUMBER:
+ ies->called_number = (char *)data + 2;
+ break;
+ case IAX_IE_CALLING_NUMBER:
+ ies->calling_number = (char *)data + 2;
+ break;
+ case IAX_IE_CALLING_ANI:
+ ies->calling_ani = (char *)data + 2;
+ break;
+ case IAX_IE_CALLING_NAME:
+ ies->calling_name = (char *)data + 2;
+ break;
+ case IAX_IE_CALLED_CONTEXT:
+ ies->called_context = (char *)data + 2;
+ break;
+ case IAX_IE_USERNAME:
+ ies->username = (char *)data + 2;
+ break;
+ case IAX_IE_PASSWORD:
+ ies->password = (char *)data + 2;
+ break;
+ case IAX_IE_CODEC_PREFS:
+ ies->codec_prefs = (char *)data + 2;
+ break;
+ case IAX_IE_CAPABILITY:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting capability to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else
+ ies->capability = ntohl(get_unaligned_uint32(data + 2));
+ break;
+ case IAX_IE_FORMAT:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting format to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else
+ ies->format = ntohl(get_unaligned_uint32(data + 2));
+ break;
+ case IAX_IE_LANGUAGE:
+ ies->language = (char *)data + 2;
+ break;
+ case IAX_IE_VERSION:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting version to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->version = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_ADSICPE:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting adsicpe to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->adsicpe = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_SAMPLINGRATE:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting samplingrate to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->samprate = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_DNID:
+ ies->dnid = (char *)data + 2;
+ break;
+ case IAX_IE_RDNIS:
+ ies->rdnis = (char *)data + 2;
+ break;
+ case IAX_IE_AUTHMETHODS:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting authmethods to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->authmethods = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_ENCRYPTION:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting encryption to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->encmethods = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_CHALLENGE:
+ ies->challenge = (char *)data + 2;
+ break;
+ case IAX_IE_MD5_RESULT:
+ ies->md5_result = (char *)data + 2;
+ break;
+ case IAX_IE_RSA_RESULT:
+ ies->rsa_result = (char *)data + 2;
+ break;
+ case IAX_IE_APPARENT_ADDR:
+ ies->apparent_addr = ((struct sockaddr_in *)(data + 2));
+ break;
+ case IAX_IE_REFRESH:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting refresh to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->refresh = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_DPSTATUS:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting dpstatus to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->dpstatus = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_CALLNO:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting callno to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->callno = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_CAUSE:
+ ies->cause = (char *)data + 2;
+ break;
+ case IAX_IE_CAUSECODE:
+ if (len != 1) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting causecode to be single byte but was %d\n", len);
+ errorf(tmp);
+ } else {
+ ies->causecode = data[2];
+ }
+ break;
+ case IAX_IE_IAX_UNKNOWN:
+ if (len == 1)
+ ies->iax_unknown = data[2];
+ else {
+ snprintf(tmp, (int)sizeof(tmp), "Expected single byte Unknown command, but was %d long\n", len);
+ errorf(tmp);
+ }
+ break;
+ case IAX_IE_MSGCOUNT:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting msgcount to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->msgcount = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_AUTOANSWER:
+ ies->autoanswer = 1;
+ break;
+ case IAX_IE_MUSICONHOLD:
+ ies->musiconhold = 1;
+ break;
+ case IAX_IE_TRANSFERID:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting transferid to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else
+ ies->transferid = ntohl(get_unaligned_uint32(data + 2));
+ break;
+ case IAX_IE_DATETIME:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting date/time to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else
+ ies->datetime = ntohl(get_unaligned_uint32(data + 2));
+ break;
+ case IAX_IE_FIRMWAREVER:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting firmwarever to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->firmwarever = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_DEVICETYPE:
+ ies->devicetype = (char *)data + 2;
+ break;
+ case IAX_IE_SERVICEIDENT:
+ ies->serviceident = (char *)data + 2;
+ break;
+ case IAX_IE_FWBLOCKDESC:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expected block desc to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else
+ ies->fwdesc = ntohl(get_unaligned_uint32(data + 2));
+ break;
+ case IAX_IE_FWBLOCKDATA:
+ ies->fwdata = data + 2;
+ ies->fwdatalen = len;
+ break;
+ case IAX_IE_ENCKEY:
+ ies->enckey = data + 2;
+ ies->enckeylen = len;
+ break;
+ case IAX_IE_PROVVER:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expected provisioning version to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else {
+ ies->provverpres = 1;
+ ies->provver = ntohl(get_unaligned_uint32(data + 2));
+ }
+ break;
+ case IAX_IE_CALLINGPRES:
+ if (len == 1)
+ ies->calling_pres = data[2];
+ else {
+ snprintf(tmp, (int)sizeof(tmp), "Expected single byte callingpres, but was %d long\n", len);
+ errorf(tmp);
+ }
+ break;
+ case IAX_IE_CALLINGTON:
+ if (len == 1)
+ ies->calling_ton = data[2];
+ else {
+ snprintf(tmp, (int)sizeof(tmp), "Expected single byte callington, but was %d long\n", len);
+ errorf(tmp);
+ }
+ break;
+ case IAX_IE_CALLINGTNS:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting callingtns to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->calling_tns = ntohs(get_unaligned_uint16(data + 2));
+ break;
+ case IAX_IE_RR_JITTER:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expected jitter rr to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else {
+ ies->rr_jitter = ntohl(get_unaligned_uint32(data + 2));
+ }
+ break;
+ case IAX_IE_RR_LOSS:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expected loss rr to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else {
+ ies->rr_loss = ntohl(get_unaligned_uint32(data + 2));
+ }
+ break;
+ case IAX_IE_RR_PKTS:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expected packets rr to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else {
+ ies->rr_pkts = ntohl(get_unaligned_uint32(data + 2));
+ }
+ break;
+ case IAX_IE_RR_DELAY:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expected loss rr to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else {
+ ies->rr_delay = ntohs(get_unaligned_uint16(data + 2));
+ }
+ break;
+ case IAX_IE_RR_DROPPED:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expected packets rr to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else {
+ ies->rr_dropped = ntohl(get_unaligned_uint32(data + 2));
+ }
+ break;
+ case IAX_IE_RR_OOO:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expected packets rr to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else {
+ ies->rr_ooo = ntohl(get_unaligned_uint32(data + 2));
+ }
+ break;
+ case IAX_IE_VARIABLE:
+ ast_copy_string(tmp, (char *)data + 2, len + 1);
+ tmp2 = strchr(tmp, '=');
+ if (tmp2)
+ *tmp2++ = '\0';
+ else
+ tmp2 = "";
+ /* Existing variable or new variable? */
+ for (var2 = ies->vars, prev = NULL; var2; prev = var2, var2 = var2->next) {
+ if (strcmp(tmp, var2->name) == 0) {
+ int len = strlen(var2->value) + strlen(tmp2) + 1;
+ char *tmp3 = alloca(len);
+ snprintf(tmp3, len, "%s%s", var2->value, tmp2);
+ var = ast_variable_new(tmp, tmp3, var2->file);
+ var->next = var2->next;
+ if (prev)
+ prev->next = var;
+ else
+ ies->vars = var;
+ ast_free(var2);
+ break;
+ }
+ }
+ if (!var2) {
+ var = ast_variable_new(tmp, tmp2, "");
+ var->next = ies->vars;
+ ies->vars = var;
+ }
+ break;
+ case IAX_IE_OSPTOKEN:
+ if ((count = data[2]) < IAX_MAX_OSPBLOCK_NUM) {
+ ies->osptokenblock[count] = (char *)data + 2 + 1;
+ ies->ospblocklength[count] = len - 1;
+ } else {
+ snprintf(tmp, (int)sizeof(tmp), "Expected OSP token block index to be 0~%d but was %d\n", IAX_MAX_OSPBLOCK_NUM - 1, count);
+ errorf(tmp);
+ }
+ break;
+ default:
+ snprintf(tmp, (int)sizeof(tmp), "Ignoring unknown information element '%s' (%d) of length %d\n", iax_ie2str(ie), ie, len);
+ outputf(tmp);
+ }
+ /* Overwrite information element with 0, to null terminate previous portion */
+ data[0] = 0;
+ datalen -= (len + 2);
+ data += (len + 2);
+ }
+ /* Null-terminate last field */
+ *data = '\0';
+ if (datalen) {
+ errorf("Invalid information element contents, strange boundary\n");
+ return -1;
+ }
+ return 0;
+}
+
+void iax_frame_wrap(struct iax_frame *fr, struct ast_frame *f)
+{
+ fr->af.frametype = f->frametype;
+ fr->af.subclass = f->subclass;
+ fr->af.mallocd = 0; /* Our frame is static relative to the container */
+ fr->af.datalen = f->datalen;
+ fr->af.samples = f->samples;
+ fr->af.offset = AST_FRIENDLY_OFFSET;
+ fr->af.src = f->src;
+ fr->af.delivery.tv_sec = 0;
+ fr->af.delivery.tv_usec = 0;
+ fr->af.data = fr->afdata;
+ fr->af.len = f->len;
+ if (fr->af.datalen) {
+ size_t copy_len = fr->af.datalen;
+ if (copy_len > fr->afdatalen) {
+ ast_log(LOG_ERROR, "Losing frame data because destination buffer size '%d' bytes not big enough for '%d' bytes in the frame\n",
+ (int) fr->afdatalen, (int) fr->af.datalen);
+ copy_len = fr->afdatalen;
+ }
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ /* We need to byte-swap slinear samples from network byte order */
+ if ((fr->af.frametype == AST_FRAME_VOICE) && (fr->af.subclass == AST_FORMAT_SLINEAR)) {
+ /* 2 bytes / sample for SLINEAR */
+ ast_swapcopy_samples(fr->af.data, f->data, copy_len / 2);
+ } else
+#endif
+ memcpy(fr->af.data, f->data, copy_len);
+ }
+}
+
+struct iax_frame *iax_frame_new(int direction, int datalen, unsigned int cacheable)
+{
+ struct iax_frame *fr = NULL;
+
+#if !defined(LOW_MEMORY)
+ struct iax_frames *iax_frames = NULL;
+
+ /* Attempt to get a frame from this thread's cache */
+ if ((iax_frames = ast_threadstorage_get(&frame_cache, sizeof(*iax_frames)))) {
+ AST_LIST_TRAVERSE_SAFE_BEGIN(iax_frames, fr, list) {
+ if (fr->afdatalen >= datalen) {
+ size_t afdatalen = fr->afdatalen;
+ AST_LIST_REMOVE_CURRENT(list);
+ memset(fr, 0, sizeof(*fr));
+ fr->afdatalen = afdatalen;
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ }
+ if (!fr) {
+ if (!(fr = ast_calloc_cache(1, sizeof(*fr) + datalen)))
+ return NULL;
+ fr->afdatalen = datalen;
+ }
+#else
+ if (!(fr = ast_calloc(1, sizeof(*fr) + datalen)))
+ return NULL;
+ fr->afdatalen = datalen;
+#endif
+
+
+ fr->direction = direction;
+ fr->retrans = -1;
+ fr->cacheable = cacheable;
+
+ if (fr->direction == DIRECTION_INGRESS)
+ ast_atomic_fetchadd_int(&iframes, 1);
+ else
+ ast_atomic_fetchadd_int(&oframes, 1);
+
+ ast_atomic_fetchadd_int(&frames, 1);
+
+ return fr;
+}
+
+void iax_frame_free(struct iax_frame *fr)
+{
+#if !defined(LOW_MEMORY)
+ struct iax_frames *iax_frames = NULL;
+#endif
+
+ /* Note: does not remove from scheduler! */
+ if (fr->direction == DIRECTION_INGRESS)
+ ast_atomic_fetchadd_int(&iframes, -1);
+ else if (fr->direction == DIRECTION_OUTGRESS)
+ ast_atomic_fetchadd_int(&oframes, -1);
+ else {
+ errorf("Attempt to double free frame detected\n");
+ return;
+ }
+ ast_atomic_fetchadd_int(&frames, -1);
+
+#if !defined(LOW_MEMORY)
+ if (!fr->cacheable || !(iax_frames = ast_threadstorage_get(&frame_cache, sizeof(*iax_frames)))) {
+ ast_free(fr);
+ return;
+ }
+
+ fr->direction = 0;
+ AST_LIST_INSERT_HEAD(iax_frames, fr, list);
+#else
+ ast_free(fr);
+#endif
+}
+
+#if !defined(LOW_MEMORY)
+static void frame_cache_cleanup(void *data)
+{
+ struct iax_frames *frames = data;
+ struct iax_frame *cur;
+
+ while ((cur = AST_LIST_REMOVE_HEAD(frames, list)))
+ ast_free(cur);
+
+ ast_free(frames);
+}
+#endif
+
+int iax_get_frames(void) { return frames; }
+int iax_get_iframes(void) { return iframes; }
+int iax_get_oframes(void) { return oframes; }
diff --git a/trunk/channels/iax2-parser.h b/trunk/channels/iax2-parser.h
new file mode 100644
index 000000000..e40669d3d
--- /dev/null
+++ b/trunk/channels/iax2-parser.h
@@ -0,0 +1,163 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Implementation of Inter-Asterisk eXchange
+ *
+ * Copyright (C) 2003, Digium
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+/*!\file
+ * \brief Implementation of the IAX2 protocol
+ */
+
+#ifndef _IAX2_PARSER_H
+#define _IAX2_PARSER_H
+
+#include "asterisk/linkedlists.h"
+
+struct iax_ies {
+ char *called_number;
+ char *calling_number;
+ char *calling_ani;
+ char *calling_name;
+ int calling_ton;
+ int calling_tns;
+ int calling_pres;
+ char *called_context;
+ char *username;
+ char *password;
+ unsigned int capability;
+ unsigned int format;
+ char *codec_prefs;
+ char *language;
+ int version;
+ unsigned short adsicpe;
+ char *dnid;
+ char *rdnis;
+ unsigned int authmethods;
+ unsigned int encmethods;
+ char *challenge;
+ char *md5_result;
+ char *rsa_result;
+ struct sockaddr_in *apparent_addr;
+ unsigned short refresh;
+ unsigned short dpstatus;
+ unsigned short callno;
+ char *cause;
+ unsigned char causecode;
+ unsigned char iax_unknown;
+ int msgcount;
+ int autoanswer;
+ int musiconhold;
+ unsigned int transferid;
+ unsigned int datetime;
+ char *devicetype;
+ char *serviceident;
+ int firmwarever;
+ unsigned int fwdesc;
+ unsigned char *fwdata;
+ unsigned char fwdatalen;
+ unsigned char *enckey;
+ unsigned char enckeylen;
+ unsigned int provver;
+ unsigned short samprate;
+ int provverpres;
+ unsigned int rr_jitter;
+ unsigned int rr_loss;
+ unsigned int rr_pkts;
+ unsigned short rr_delay;
+ unsigned int rr_dropped;
+ unsigned int rr_ooo;
+ struct ast_variable *vars;
+ char *osptokenblock[IAX_MAX_OSPBLOCK_NUM];
+ unsigned int ospblocklength[IAX_MAX_OSPBLOCK_NUM];
+};
+
+#define DIRECTION_INGRESS 1
+#define DIRECTION_OUTGRESS 2
+
+struct iax_frame {
+#ifdef LIBIAX
+ struct iax_session *session;
+ struct iax_event *event;
+#else
+ int sockfd;
+#endif
+
+ /* /Our/ call number */
+ unsigned short callno;
+ /* /Their/ call number */
+ unsigned short dcallno;
+ /* Start of raw frame (outgoing only) */
+ void *data;
+ /* Length of frame (outgoing only) */
+ int datalen;
+ /* How many retries so far? */
+ int retries;
+ /* Outgoing relative timestamp (ms) */
+ unsigned int ts;
+ /* How long to wait before retrying */
+ int retrytime;
+ /* Are we received out of order? */
+ unsigned int outoforder:1;
+ /* Have we been sent at all yet? */
+ unsigned int sentyet:1;
+ /* Non-zero if should be sent to transfer peer */
+ unsigned int transfer:1;
+ /* Non-zero if this is the final message */
+ unsigned int final:1;
+ /* Ingress or outgres */
+ unsigned int direction:2;
+ /* Can this frame be cached? */
+ unsigned int cacheable:1;
+ /* Outgoing Packet sequence number */
+ int oseqno;
+ /* Next expected incoming packet sequence number */
+ int iseqno;
+ /* Retransmission ID */
+ int retrans;
+ /* Easy linking */
+ AST_LIST_ENTRY(iax_frame) list;
+ /* Actual, isolated frame header */
+ struct ast_frame af;
+ /*! Amount of space _allocated_ for data */
+ size_t afdatalen;
+ unsigned char unused[AST_FRIENDLY_OFFSET];
+ unsigned char afdata[0]; /* Data for frame */
+};
+
+struct iax_ie_data {
+ unsigned char buf[1024];
+ int pos;
+};
+
+/* Choose a different function for output */
+void iax_set_output(void (*output)(const char *data));
+/* Choose a different function for errors */
+void iax_set_error(void (*output)(const char *data));
+void iax_showframe(struct iax_frame *f, struct ast_iax2_full_hdr *fhi, int rx, struct sockaddr_in *sin, int datalen);
+
+const char *iax_ie2str(int ie);
+
+int iax_ie_append_raw(struct iax_ie_data *ied, unsigned char ie, const void *data, int datalen);
+int iax_ie_append_addr(struct iax_ie_data *ied, unsigned char ie, const struct sockaddr_in *sin);
+int iax_ie_append_int(struct iax_ie_data *ied, unsigned char ie, unsigned int value);
+int iax_ie_append_short(struct iax_ie_data *ied, unsigned char ie, unsigned short value);
+int iax_ie_append_str(struct iax_ie_data *ied, unsigned char ie, const char *str);
+int iax_ie_append_byte(struct iax_ie_data *ied, unsigned char ie, unsigned char dat);
+int iax_ie_append(struct iax_ie_data *ied, unsigned char ie);
+int iax_parse_ies(struct iax_ies *ies, unsigned char *data, int datalen);
+
+int iax_get_frames(void);
+int iax_get_iframes(void);
+int iax_get_oframes(void);
+
+void iax_frame_wrap(struct iax_frame *fr, struct ast_frame *f);
+struct iax_frame *iax_frame_new(int direction, int datalen, unsigned int cacheable);
+void iax_frame_free(struct iax_frame *fr);
+#endif
diff --git a/trunk/channels/iax2-provision.c b/trunk/channels/iax2-provision.c
new file mode 100644
index 000000000..5b52a0934
--- /dev/null
+++ b/trunk/channels/iax2-provision.c
@@ -0,0 +1,538 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief IAX Provisioning Protocol
+ *
+ * \author Mark Spencer <markster@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/lock.h"
+#include "asterisk/frame.h"
+#include "asterisk/md5.h"
+#include "asterisk/astdb.h"
+#include "asterisk/utils.h"
+#include "asterisk/acl.h"
+#include "iax2.h"
+#include "iax2-provision.h"
+#include "iax2-parser.h"
+
+static int provinit = 0;
+
+struct iax_template {
+ int dead;
+ char name[80];
+ char src[80];
+ struct iax_template *next;
+ char user[20];
+ char pass[20];
+ char lang[10];
+ unsigned short port;
+ unsigned int server;
+ unsigned short serverport;
+ unsigned int altserver;
+ unsigned int flags;
+ unsigned int format;
+ unsigned int tos;
+} *templates;
+
+static struct iax_flag {
+ char *name;
+ int value;
+} iax_flags[] = {
+ { "register", PROV_FLAG_REGISTER },
+ { "secure", PROV_FLAG_SECURE },
+ { "heartbeat", PROV_FLAG_HEARTBEAT },
+ { "debug", PROV_FLAG_DEBUG },
+ { "disablecid", PROV_FLAG_DIS_CALLERID },
+ { "disablecw", PROV_FLAG_DIS_CALLWAIT },
+ { "disablecidcw", PROV_FLAG_DIS_CIDCW },
+ { "disable3way", PROV_FLAG_DIS_THREEWAY },
+};
+
+char *iax_provflags2str(char *buf, int buflen, unsigned int flags)
+{
+ int x;
+
+ if (!buf || buflen < 1)
+ return NULL;
+
+ buf[0] = '\0';
+
+ for (x = 0; x < sizeof(iax_flags) / sizeof(iax_flags[0]); x++) {
+ if (flags & iax_flags[x].value){
+ strncat(buf, iax_flags[x].name, buflen - strlen(buf) - 1);
+ strncat(buf, ",", buflen - strlen(buf) - 1);
+ }
+ }
+
+ if (!ast_strlen_zero(buf))
+ buf[strlen(buf) - 1] = '\0';
+ else
+ strncpy(buf, "none", buflen - 1);
+
+ return buf;
+}
+
+static unsigned int iax_str2flags(const char *buf)
+{
+ int x;
+ int len;
+ int found;
+ unsigned int flags = 0;
+ char *e;
+ while(buf && *buf) {
+ e = strchr(buf, ',');
+ if (e)
+ len = e - buf;
+ else
+ len = 0;
+ found = 0;
+ for (x=0;x<sizeof(iax_flags) / sizeof(iax_flags[0]); x++) {
+ if ((len && !strncasecmp(iax_flags[x].name, buf, len)) ||
+ (!len && !strcasecmp(iax_flags[x].name, buf))) {
+ flags |= iax_flags[x].value;
+ break;
+ }
+ }
+ if (e) {
+ buf = e + 1;
+ while(*buf && (*buf < 33))
+ buf++;
+ } else
+ break;
+ }
+ return flags;
+}
+AST_MUTEX_DEFINE_STATIC(provlock);
+
+static struct iax_template *iax_template_find(const char *s, int allowdead)
+{
+ struct iax_template *cur;
+ cur = templates;
+ while(cur) {
+ if (!strcasecmp(s, cur->name)) {
+ if (!allowdead && cur->dead)
+ cur = NULL;
+ break;
+ }
+ cur = cur->next;
+ }
+ return cur;
+}
+
+char *iax_prov_complete_template(const char *line, const char *word, int pos, int state)
+{
+ struct iax_template *c;
+ int which=0;
+ char *ret = NULL;
+ int wordlen = strlen(word);
+
+ if (pos == 3) {
+ ast_mutex_lock(&provlock);
+ for (c = templates; c; c = c->next) {
+ if (!strncasecmp(word, c->name, wordlen) && ++which > state) {
+ ret = ast_strdup(c->name);
+ break;
+ }
+ }
+ ast_mutex_unlock(&provlock);
+ }
+ return ret;
+}
+
+static unsigned int prov_ver_calc(struct iax_ie_data *provdata)
+{
+ struct MD5Context md5;
+ unsigned int tmp[4];
+ MD5Init(&md5);
+ MD5Update(&md5, provdata->buf, provdata->pos);
+ MD5Final((unsigned char *)tmp, &md5);
+ return tmp[0] ^ tmp[1] ^ tmp[2] ^ tmp[3];
+}
+
+int iax_provision_build(struct iax_ie_data *provdata, unsigned int *signature, const char *template, int force)
+{
+ struct iax_template *cur;
+ unsigned int sig;
+ char tmp[40];
+ memset(provdata, 0, sizeof(*provdata));
+ ast_mutex_lock(&provlock);
+ cur = iax_template_find(template, 1);
+ /* If no match, try searching for '*' */
+ if (!cur)
+ cur = iax_template_find("*", 1);
+ if (cur) {
+ /* found it -- add information elements as appropriate */
+ if (force || strlen(cur->user))
+ iax_ie_append_str(provdata, PROV_IE_USER, cur->user);
+ if (force || strlen(cur->pass))
+ iax_ie_append_str(provdata, PROV_IE_PASS, cur->pass);
+ if (force || strlen(cur->lang))
+ iax_ie_append_str(provdata, PROV_IE_LANG, cur->lang);
+ if (force || cur->port)
+ iax_ie_append_short(provdata, PROV_IE_PORTNO, cur->port);
+ if (force || cur->server)
+ iax_ie_append_int(provdata, PROV_IE_SERVERIP, cur->server);
+ if (force || cur->serverport)
+ iax_ie_append_short(provdata, PROV_IE_SERVERPORT, cur->serverport);
+ if (force || cur->altserver)
+ iax_ie_append_int(provdata, PROV_IE_ALTSERVER, cur->altserver);
+ if (force || cur->flags)
+ iax_ie_append_int(provdata, PROV_IE_FLAGS, cur->flags);
+ if (force || cur->format)
+ iax_ie_append_int(provdata, PROV_IE_FORMAT, cur->format);
+ if (force || cur->tos)
+ iax_ie_append_byte(provdata, PROV_IE_TOS, cur->tos);
+
+ /* Calculate checksum of message so far */
+ sig = prov_ver_calc(provdata);
+ if (signature)
+ *signature = sig;
+ /* Store signature */
+ iax_ie_append_int(provdata, PROV_IE_PROVVER, sig);
+ /* Cache signature for later verification so we need not recalculate all this */
+ snprintf(tmp, sizeof(tmp), "v0x%08x", sig);
+ ast_db_put("iax/provisioning/cache", template, tmp);
+ } else
+ ast_db_put("iax/provisioning/cache", template, "u");
+ ast_mutex_unlock(&provlock);
+ return cur ? 0 : -1;
+}
+
+int iax_provision_version(unsigned int *version, const char *template, int force)
+{
+ char tmp[80] = "";
+ struct iax_ie_data ied;
+ int ret=0;
+ memset(&ied, 0, sizeof(ied));
+
+ ast_mutex_lock(&provlock);
+ ast_db_get("iax/provisioning/cache", template, tmp, sizeof(tmp));
+ if (sscanf(tmp, "v%x", version) != 1) {
+ if (strcmp(tmp, "u")) {
+ ret = iax_provision_build(&ied, version, template, force);
+ if (ret)
+ ast_debug(1, "Unable to create provisioning packet for '%s'\n", template);
+ } else
+ ret = -1;
+ } else
+ ast_debug(1, "Retrieved cached version '%s' = '%08x'\n", tmp, *version);
+ ast_mutex_unlock(&provlock);
+ return ret;
+}
+
+static int iax_template_parse(struct iax_template *cur, struct ast_config *cfg, const char *s, const char *def)
+{
+ struct ast_variable *v;
+ int foundportno = 0;
+ int foundserverportno = 0;
+ int x;
+ struct in_addr ia;
+ struct hostent *hp;
+ struct ast_hostent h;
+ struct iax_template *src, tmp;
+ const char *t;
+ if (def) {
+ t = ast_variable_retrieve(cfg, s ,"template");
+ src = NULL;
+ if (t && strlen(t)) {
+ src = iax_template_find(t, 0);
+ if (!src)
+ ast_log(LOG_WARNING, "Unable to find base template '%s' for creating '%s'. Trying '%s'\n", t, s, def);
+ else
+ def = t;
+ }
+ if (!src) {
+ src = iax_template_find(def, 0);
+ if (!src)
+ ast_log(LOG_WARNING, "Unable to locate default base template '%s' for creating '%s', omitting.\n", def, s);
+ }
+ if (!src)
+ return -1;
+ ast_mutex_lock(&provlock);
+ /* Backup old data */
+ memcpy(&tmp, cur, sizeof(tmp));
+ /* Restore from src */
+ memcpy(cur, src, sizeof(tmp));
+ /* Restore important headers */
+ memcpy(cur->name, tmp.name, sizeof(cur->name));
+ cur->dead = tmp.dead;
+ cur->next = tmp.next;
+ ast_mutex_unlock(&provlock);
+ }
+ if (def)
+ strncpy(cur->src, def, sizeof(cur->src) - 1);
+ else
+ cur->src[0] = '\0';
+ v = ast_variable_browse(cfg, s);
+ while(v) {
+ if (!strcasecmp(v->name, "port") || !strcasecmp(v->name, "serverport")) {
+ if ((sscanf(v->value, "%d", &x) == 1) && (x > 0) && (x < 65535)) {
+ if (!strcasecmp(v->name, "port")) {
+ cur->port = x;
+ foundportno = 1;
+ } else {
+ cur->serverport = x;
+ foundserverportno = 1;
+ }
+ } else
+ ast_log(LOG_WARNING, "Ignoring invalid %s '%s' for '%s' at line %d\n", v->name, v->value, s, v->lineno);
+ } else if (!strcasecmp(v->name, "server") || !strcasecmp(v->name, "altserver")) {
+ hp = ast_gethostbyname(v->value, &h);
+ if (hp) {
+ memcpy(&ia, hp->h_addr, sizeof(ia));
+ if (!strcasecmp(v->name, "server"))
+ cur->server = ntohl(ia.s_addr);
+ else
+ cur->altserver = ntohl(ia.s_addr);
+ } else
+ ast_log(LOG_WARNING, "Ignoring invalid %s '%s' for '%s' at line %d\n", v->name, v->value, s, v->lineno);
+ } else if (!strcasecmp(v->name, "codec")) {
+ if ((x = ast_getformatbyname(v->value)) > 0) {
+ cur->format = x;
+ } else
+ ast_log(LOG_WARNING, "Ignoring invalid codec '%s' for '%s' at line %d\n", v->value, s, v->lineno);
+ } else if (!strcasecmp(v->name, "tos")) {
+ if (ast_str2tos(v->value, &cur->tos))
+ ast_log(LOG_WARNING, "Invalid tos value at line %d, refer to QoS documentation\n", v->lineno);
+ } else if (!strcasecmp(v->name, "user")) {
+ strncpy(cur->user, v->value, sizeof(cur->user) - 1);
+ if (strcmp(cur->user, v->value))
+ ast_log(LOG_WARNING, "Truncating username from '%s' to '%s' for '%s' at line %d\n", v->value, cur->user, s, v->lineno);
+ } else if (!strcasecmp(v->name, "pass")) {
+ strncpy(cur->pass, v->value, sizeof(cur->pass) - 1);
+ if (strcmp(cur->pass, v->value))
+ ast_log(LOG_WARNING, "Truncating password from '%s' to '%s' for '%s' at line %d\n", v->value, cur->pass, s, v->lineno);
+ } else if (!strcasecmp(v->name, "language")) {
+ strncpy(cur->lang, v->value, sizeof(cur->lang) - 1);
+ if (strcmp(cur->lang, v->value))
+ ast_log(LOG_WARNING, "Truncating language from '%s' to '%s' for '%s' at line %d\n", v->value, cur->lang, s, v->lineno);
+ } else if (!strcasecmp(v->name, "flags")) {
+ cur->flags = iax_str2flags(v->value);
+ } else if (!strncasecmp(v->name, "flags", 5) && strchr(v->name, '+')) {
+ cur->flags |= iax_str2flags(v->value);
+ } else if (!strncasecmp(v->name, "flags", 5) && strchr(v->name, '-')) {
+ cur->flags &= ~iax_str2flags(v->value);
+ } else if (strcasecmp(v->name, "template")) {
+ ast_log(LOG_WARNING, "Unknown keyword '%s' in definition of '%s' at line %d\n", v->name, s, v->lineno);
+ }
+ v = v->next;
+ }
+ if (!foundportno)
+ cur->port = IAX_DEFAULT_PORTNO;
+ if (!foundserverportno)
+ cur->serverport = IAX_DEFAULT_PORTNO;
+ return 0;
+}
+
+static int iax_process_template(struct ast_config *cfg, char *s, char *def)
+{
+ /* Find an already existing one if there */
+ struct iax_template *cur;
+ int mallocd = 0;
+ cur = templates;
+ while(cur) {
+ if (!strcasecmp(cur->name, s))
+ break;
+ cur = cur->next;
+ }
+ if (!cur) {
+ mallocd = 1;
+ cur = ast_calloc(1, sizeof(*cur));
+ if (!cur) {
+ ast_log(LOG_WARNING, "Out of memory!\n");
+ return -1;
+ }
+ /* Initialize entry */
+ strncpy(cur->name, s, sizeof(cur->name) - 1);
+ cur->dead = 1;
+ }
+ if (!iax_template_parse(cur, cfg, s, def))
+ cur->dead = 0;
+
+ /* Link if we're mallocd */
+ if (mallocd) {
+ ast_mutex_lock(&provlock);
+ cur->next = templates;
+ templates = cur;
+ ast_mutex_unlock(&provlock);
+ }
+ return 0;
+}
+
+static const char *ifthere(const char *s)
+{
+ if (strlen(s))
+ return s;
+ else
+ return "<unspecified>";
+}
+
+static const char *iax_server(unsigned int addr)
+{
+ struct in_addr ia;
+
+ if (!addr)
+ return "<unspecified>";
+
+ ia.s_addr = htonl(addr);
+
+ return ast_inet_ntoa(ia);
+}
+
+
+static char *iax_show_provisioning(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct iax_template *cur;
+ char server[INET_ADDRSTRLEN];
+ char alternate[INET_ADDRSTRLEN];
+ char flags[80]; /* Has to be big enough for 'flags' too */
+ int found = 0;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "iax2 show provisioning";
+ e->usage =
+ "Usage: iax2 show provisioning [template]\n"
+ " Lists all known IAX provisioning templates or a\n"
+ " specific one if specified.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return iax_prov_complete_template(a->line, a->word, a->pos, a->n);
+ }
+
+ if ((a->argc != 3) && (a->argc != 4))
+ return CLI_SHOWUSAGE;
+ ast_mutex_lock(&provlock);
+ for (cur = templates;cur;cur = cur->next) {
+ if ((a->argc == 3) || (!strcasecmp(a->argv[3], cur->name))) {
+ if (found)
+ ast_cli(a->fd, "\n");
+ ast_copy_string(server, iax_server(cur->server), sizeof(server));
+ ast_copy_string(alternate, iax_server(cur->altserver), sizeof(alternate));
+ ast_cli(a->fd, "== %s ==\n", cur->name);
+ ast_cli(a->fd, "Base Templ: %s\n", strlen(cur->src) ? cur->src : "<none>");
+ ast_cli(a->fd, "Username: %s\n", ifthere(cur->user));
+ ast_cli(a->fd, "Secret: %s\n", ifthere(cur->pass));
+ ast_cli(a->fd, "Language: %s\n", ifthere(cur->lang));
+ ast_cli(a->fd, "Bind Port: %d\n", cur->port);
+ ast_cli(a->fd, "Server: %s\n", server);
+ ast_cli(a->fd, "Server Port: %d\n", cur->serverport);
+ ast_cli(a->fd, "Alternate: %s\n", alternate);
+ ast_cli(a->fd, "Flags: %s\n", iax_provflags2str(flags, sizeof(flags), cur->flags));
+ ast_cli(a->fd, "Format: %s\n", ast_getformatname(cur->format));
+ ast_cli(a->fd, "TOS: 0x%x\n", cur->tos);
+ found++;
+ }
+ }
+ ast_mutex_unlock(&provlock);
+ if (!found) {
+ if (a->argc == 3)
+ ast_cli(a->fd, "No provisioning templates found\n");
+ else
+ ast_cli(a->fd, "No provisioning template matching '%s' found\n", a->argv[3]);
+ }
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_iax2_provision[] = {
+ AST_CLI_DEFINE(iax_show_provisioning, "Display iax provisioning"),
+};
+
+static int iax_provision_init(void)
+{
+ ast_cli_register_multiple(cli_iax2_provision, sizeof(cli_iax2_provision) / sizeof(struct ast_cli_entry));
+ provinit = 1;
+ return 0;
+}
+
+int iax_provision_unload(void)
+{
+ provinit = 0;
+ ast_cli_unregister_multiple(cli_iax2_provision, sizeof(cli_iax2_provision) / sizeof(struct ast_cli_entry));
+ return 0;
+}
+
+int iax_provision_reload(int reload)
+{
+ struct ast_config *cfg;
+ struct iax_template *cur, *prev, *next;
+ char *cat;
+ int found = 0;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+ if (!provinit)
+ iax_provision_init();
+ /* Mark all as dead. No need for locking */
+ cur = templates;
+ while(cur) {
+ cur->dead = 1;
+ cur = cur->next;
+ }
+ cfg = ast_config_load("iaxprov.conf", config_flags);
+ if (cfg != NULL && cfg != CONFIG_STATUS_FILEUNCHANGED) {
+ /* Load as appropriate */
+ cat = ast_category_browse(cfg, NULL);
+ while(cat) {
+ if (strcasecmp(cat, "general")) {
+ iax_process_template(cfg, cat, found ? "default" : NULL);
+ found++;
+ ast_verb(3, "Loaded provisioning template '%s'\n", cat);
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+ ast_config_destroy(cfg);
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+ else
+ ast_log(LOG_NOTICE, "No IAX provisioning configuration found, IAX provisioning disabled.\n");
+ ast_mutex_lock(&provlock);
+ /* Drop dead entries while locked */
+ prev = NULL;
+ cur = templates;
+ while(cur) {
+ next = cur->next;
+ if (cur->dead) {
+ if (prev)
+ prev->next = next;
+ else
+ templates = next;
+ ast_free(cur);
+ } else
+ prev = cur;
+ cur = next;
+ }
+ ast_mutex_unlock(&provlock);
+ /* Purge cached signature DB entries */
+ ast_db_deltree("iax/provisioning/cache", NULL);
+ return 0;
+
+}
diff --git a/trunk/channels/iax2-provision.h b/trunk/channels/iax2-provision.h
new file mode 100644
index 000000000..b1dfd06d0
--- /dev/null
+++ b/trunk/channels/iax2-provision.h
@@ -0,0 +1,53 @@
+/*
+ * IAX Provisioning Protocol
+ *
+ * Sub-information elements
+ *
+ * Copyright (C) 2003, Digium
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ */
+
+/*! \file
+ * \brief IAX2 Provisioning protocol
+ */
+
+#include "iax2-parser.h"
+
+#define PROV_IE_USEDHCP 1 /* Presense only */
+#define PROV_IE_IPADDR 2 /* 32-bit */
+#define PROV_IE_SUBNET 3 /* 32-bit */
+#define PROV_IE_GATEWAY 4 /* 32-bit */
+#define PROV_IE_PORTNO 5 /* 16-bit */
+#define PROV_IE_USER 6 /* < 20 bytes */
+#define PROV_IE_PASS 7 /* < 20 bytes */
+#define PROV_IE_SERVERUSER 8 /* < 20 bytes */
+#define PROV_IE_SERVERPASS 9 /* < 20 bytes */
+#define PROV_IE_LANG 10 /* < 10 bytes */
+#define PROV_IE_TOS 11 /* 8-bits */
+#define PROV_IE_FLAGS 12 /* 32-bits */
+#define PROV_IE_FORMAT 13 /* 32-bits */
+#define PROV_IE_AESKEY 14 /* 128-bits */
+#define PROV_IE_SERVERIP 15 /* 32-bits */
+#define PROV_IE_SERVERPORT 16 /* 16-bits */
+#define PROV_IE_NEWAESKEY 17 /* 128-bits */
+#define PROV_IE_PROVVER 18 /* 32-bits */
+#define PROV_IE_ALTSERVER 19 /* 32-bits */
+
+#define PROV_FLAG_REGISTER (1 << 0)
+#define PROV_FLAG_SECURE (1 << 1)
+#define PROV_FLAG_HEARTBEAT (1 << 2)
+#define PROV_FLAG_DEBUG (1 << 3)
+
+#define PROV_FLAG_DIS_CALLERID (1 << 4) /* Caller-ID Disabled */
+#define PROV_FLAG_DIS_CALLWAIT (1 << 5) /* Caller-ID / Call Waiting Disable */
+#define PROV_FLAG_DIS_CIDCW (1 << 6) /* CID/CW Disabled */
+#define PROV_FLAG_DIS_THREEWAY (1 << 7) /* Three-way calling, transfer disabled */
+
+char *iax_provflags2str(char *buf, int buflen, unsigned int flags);
+int iax_provision_reload(int reload);
+int iax_provision_unload(void);
+int iax_provision_build(struct iax_ie_data *provdata, unsigned int *signature, const char *template, int force);
+int iax_provision_version(unsigned int *signature, const char *template, int force);
+char *iax_prov_complete_template(const char *line, const char *word, int pos, int state);
diff --git a/trunk/channels/iax2.h b/trunk/channels/iax2.h
new file mode 100644
index 000000000..b3947fffa
--- /dev/null
+++ b/trunk/channels/iax2.h
@@ -0,0 +1,277 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Implementation of Inter-Asterisk eXchange
+ *
+ * Copyright (C) 2003, Digium
+ *
+ * Mark Spencer <markster@linux-support.net>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+/*! \file
+ * \brief
+ *
+ * Implementation of Inter-Asterisk eXchange, version 2
+ * \ref iax2-parser.c
+ * \ref iax2-parser.h
+ * \ref chan_iax2.c
+ */
+
+#ifndef _IAX2_H
+#define _IAX2_H
+
+/* Max version of IAX protocol we support */
+#define IAX_PROTO_VERSION 2
+
+#define IAX_MAX_CALLS 32768
+
+#define IAX_FLAG_FULL 0x8000
+
+#define IAX_FLAG_RETRANS 0x8000
+
+#define IAX_FLAG_SC_LOG 0x80
+
+#define IAX_MAX_SHIFT 0x1F
+
+#define IAX_WINDOW 64
+
+/*! Subclass for AST_FRAME_IAX */
+enum {
+ IAX_COMMAND_NEW = 1,
+ IAX_COMMAND_PING = 2,
+ IAX_COMMAND_PONG = 3,
+ IAX_COMMAND_ACK = 4,
+ IAX_COMMAND_HANGUP = 5,
+ IAX_COMMAND_REJECT = 6,
+ IAX_COMMAND_ACCEPT = 7,
+ IAX_COMMAND_AUTHREQ = 8,
+ IAX_COMMAND_AUTHREP = 9,
+ IAX_COMMAND_INVAL = 10,
+ IAX_COMMAND_LAGRQ = 11,
+ IAX_COMMAND_LAGRP = 12,
+ /*! Registration request */
+ IAX_COMMAND_REGREQ = 13,
+ /*! Registration authentication required */
+ IAX_COMMAND_REGAUTH = 14,
+ /*! Registration accepted */
+ IAX_COMMAND_REGACK = 15,
+ /*! Registration rejected */
+ IAX_COMMAND_REGREJ = 16,
+ /*! Force release of registration */
+ IAX_COMMAND_REGREL = 17,
+ /*! If we receive voice before valid first voice frame, send this */
+ IAX_COMMAND_VNAK = 18,
+ /*! Request status of a dialplan entry */
+ IAX_COMMAND_DPREQ = 19,
+ /*! Request status of a dialplan entry */
+ IAX_COMMAND_DPREP = 20,
+ /*! Request a dial on channel brought up TBD */
+ IAX_COMMAND_DIAL = 21,
+ /*! Transfer Request */
+ IAX_COMMAND_TXREQ = 22,
+ /*! Transfer Connect */
+ IAX_COMMAND_TXCNT = 23,
+ /*! Transfer Accepted */
+ IAX_COMMAND_TXACC = 24,
+ /*! Transfer ready */
+ IAX_COMMAND_TXREADY = 25,
+ /*! Transfer release */
+ IAX_COMMAND_TXREL = 26,
+ /*! Transfer reject */
+ IAX_COMMAND_TXREJ = 27,
+ /*! Stop audio/video transmission */
+ IAX_COMMAND_QUELCH = 28,
+ /*! Resume audio/video transmission */
+ IAX_COMMAND_UNQUELCH = 29,
+ /*! Like ping, but does not require an open connection */
+ IAX_COMMAND_POKE = 30,
+ /*! Paging description */
+ IAX_COMMAND_PAGE = 31,
+ /*! Stand-alone message waiting indicator */
+ IAX_COMMAND_MWI = 32,
+ /*! Unsupported message received */
+ IAX_COMMAND_UNSUPPORT = 33,
+ /*! Request remote transfer */
+ IAX_COMMAND_TRANSFER = 34,
+ /*! Provision device */
+ IAX_COMMAND_PROVISION = 35,
+ /*! Download firmware */
+ IAX_COMMAND_FWDOWNL = 36,
+ /*! Firmware Data */
+ IAX_COMMAND_FWDATA = 37,
+ /*! Transfer media only */
+ IAX_COMMAND_TXMEDIA = 38,
+};
+
+/*! By default require re-registration once per minute */
+#define IAX_DEFAULT_REG_EXPIRE 60
+
+/*! How long to wait before closing bridged call */
+#define IAX_LINGER_TIMEOUT 10
+
+#define IAX_DEFAULT_PORTNO 4569
+
+/*! IAX Information elements */
+#define IAX_IE_CALLED_NUMBER 1 /*!< Number/extension being called - string */
+#define IAX_IE_CALLING_NUMBER 2 /*!< Calling number - string */
+#define IAX_IE_CALLING_ANI 3 /*!< Calling number ANI for billing - string */
+#define IAX_IE_CALLING_NAME 4 /*!< Name of caller - string */
+#define IAX_IE_CALLED_CONTEXT 5 /*!< Context for number - string */
+#define IAX_IE_USERNAME 6 /*!< Username (peer or user) for authentication - string */
+#define IAX_IE_PASSWORD 7 /*!< Password for authentication - string */
+#define IAX_IE_CAPABILITY 8 /*!< Actual codec capability - unsigned int */
+#define IAX_IE_FORMAT 9 /*!< Desired codec format - unsigned int */
+#define IAX_IE_LANGUAGE 10 /*!< Desired language - string */
+#define IAX_IE_VERSION 11 /*!< Protocol version - short */
+#define IAX_IE_ADSICPE 12 /*!< CPE ADSI capability - short */
+#define IAX_IE_DNID 13 /*!< Originally dialed DNID - string */
+#define IAX_IE_AUTHMETHODS 14 /*!< Authentication method(s) - short */
+#define IAX_IE_CHALLENGE 15 /*!< Challenge data for MD5/RSA - string */
+#define IAX_IE_MD5_RESULT 16 /*!< MD5 challenge result - string */
+#define IAX_IE_RSA_RESULT 17 /*!< RSA challenge result - string */
+#define IAX_IE_APPARENT_ADDR 18 /*!< Apparent address of peer - struct sockaddr_in */
+#define IAX_IE_REFRESH 19 /*!< When to refresh registration - short */
+#define IAX_IE_DPSTATUS 20 /*!< Dialplan status - short */
+#define IAX_IE_CALLNO 21 /*!< Call number of peer - short */
+#define IAX_IE_CAUSE 22 /*!< Cause - string */
+#define IAX_IE_IAX_UNKNOWN 23 /*!< Unknown IAX command - byte */
+#define IAX_IE_MSGCOUNT 24 /*!< How many messages waiting - short */
+#define IAX_IE_AUTOANSWER 25 /*!< Request auto-answering -- none */
+#define IAX_IE_MUSICONHOLD 26 /*!< Request musiconhold with QUELCH -- none or string */
+#define IAX_IE_TRANSFERID 27 /*!< Transfer Request Identifier -- int */
+#define IAX_IE_RDNIS 28 /*!< Referring DNIS -- string */
+#define IAX_IE_PROVISIONING 29 /*!< Provisioning info */
+#define IAX_IE_AESPROVISIONING 30 /*!< AES Provisioning info */
+#define IAX_IE_DATETIME 31 /*!< Date/Time */
+#define IAX_IE_DEVICETYPE 32 /*!< Device Type -- string */
+#define IAX_IE_SERVICEIDENT 33 /*!< Service Identifier -- string */
+#define IAX_IE_FIRMWAREVER 34 /*!< Firmware revision -- u16 */
+#define IAX_IE_FWBLOCKDESC 35 /*!< Firmware block description -- u32 */
+#define IAX_IE_FWBLOCKDATA 36 /*!< Firmware block of data -- raw */
+#define IAX_IE_PROVVER 37 /*!< Provisioning Version (u32) */
+#define IAX_IE_CALLINGPRES 38 /*!< Calling presentation (u8) */
+#define IAX_IE_CALLINGTON 39 /*!< Calling type of number (u8) */
+#define IAX_IE_CALLINGTNS 40 /*!< Calling transit network select (u16) */
+#define IAX_IE_SAMPLINGRATE 41 /*!< Supported sampling rates (u16) */
+#define IAX_IE_CAUSECODE 42 /*!< Hangup cause (u8) */
+#define IAX_IE_ENCRYPTION 43 /*!< Encryption format (u16) */
+#define IAX_IE_ENCKEY 44 /*!< Encryption key (raw) */
+#define IAX_IE_CODEC_PREFS 45 /*!< Codec Negotiation */
+
+#define IAX_IE_RR_JITTER 46 /*!< Received jitter (as in RFC1889) u32 */
+#define IAX_IE_RR_LOSS 47 /*!< Received loss (high byte loss pct, low 24 bits loss count, as in rfc1889 */
+#define IAX_IE_RR_PKTS 48 /*!< Received frames (total frames received) u32 */
+#define IAX_IE_RR_DELAY 49 /*!< Max playout delay for received frames (in ms) u16 */
+#define IAX_IE_RR_DROPPED 50 /*!< Dropped frames (presumably by jitterbuf) u32 */
+#define IAX_IE_RR_OOO 51 /*!< Frames received Out of Order u32 */
+#define IAX_IE_VARIABLE 52 /*!< Remote variables */
+#define IAX_IE_OSPTOKEN 53 /*!< OSP token */
+
+#define IAX_MAX_OSPBLOCK_SIZE 254 /*!< Max OSP token block size, 255 bytes - 1 byte OSP token block index */
+#define IAX_MAX_OSPBLOCK_NUM 4
+#define IAX_MAX_OSPTOKEN_SIZE (IAX_MAX_OSPBLOCK_SIZE * IAX_MAX_OSPBLOCK_NUM)
+#define IAX_MAX_OSPBUFF_SIZE (IAX_MAX_OSPTOKEN_SIZE + 16)
+
+#define IAX_AUTH_PLAINTEXT (1 << 0)
+#define IAX_AUTH_MD5 (1 << 1)
+#define IAX_AUTH_RSA (1 << 2)
+
+#define IAX_ENCRYPT_AES128 (1 << 0)
+
+#define IAX_META_TRUNK 1 /*!< Trunk meta-message */
+#define IAX_META_VIDEO 2 /*!< Video frame */
+
+#define IAX_META_TRUNK_SUPERMINI 0 /*!< This trunk frame contains classic supermini frames */
+#define IAX_META_TRUNK_MINI 1 /*!< This trunk frame contains trunked mini frames */
+
+#define IAX_RATE_8KHZ (1 << 0) /*!< 8khz sampling (default if absent) */
+#define IAX_RATE_11KHZ (1 << 1) /*!< 11.025khz sampling */
+#define IAX_RATE_16KHZ (1 << 2) /*!< 16khz sampling */
+#define IAX_RATE_22KHZ (1 << 3) /*!< 22.05khz sampling */
+#define IAX_RATE_44KHZ (1 << 4) /*!< 44.1khz sampling */
+#define IAX_RATE_48KHZ (1 << 5) /*!< 48khz sampling */
+
+#define IAX_DPSTATUS_EXISTS (1 << 0)
+#define IAX_DPSTATUS_CANEXIST (1 << 1)
+#define IAX_DPSTATUS_NONEXISTENT (1 << 2)
+#define IAX_DPSTATUS_IGNOREPAT (1 << 14)
+#define IAX_DPSTATUS_MATCHMORE (1 << 15)
+
+/*! Full frames are always delivered reliably */
+struct ast_iax2_full_hdr {
+ unsigned short scallno; /*!< Source call number -- high bit must be 1 */
+ unsigned short dcallno; /*!< Destination call number -- high bit is 1 if retransmission */
+ unsigned int ts; /*!< 32-bit timestamp in milliseconds (from 1st transmission) */
+ unsigned char oseqno; /*!< Packet number (outgoing) */
+ unsigned char iseqno; /*!< Packet number (next incoming expected) */
+ unsigned char type; /*!< Frame type */
+ unsigned char csub; /*!< Compressed subclass */
+ unsigned char iedata[0];
+} __attribute__ ((__packed__));
+
+/*! Full frames are always delivered reliably */
+struct ast_iax2_full_enc_hdr {
+ unsigned short scallno; /*!< Source call number -- high bit must be 1 */
+ unsigned short dcallno; /*!< Destination call number -- high bit is 1 if retransmission */
+ unsigned char encdata[0];
+} __attribute__ ((__packed__));
+
+/*! Mini header is used only for voice frames -- delivered unreliably */
+struct ast_iax2_mini_hdr {
+ unsigned short callno; /*!< Source call number -- high bit must be 0, rest must be non-zero */
+ unsigned short ts; /*!< 16-bit Timestamp (high 16 bits from last ast_iax2_full_hdr) */
+ /* Frametype implicitly VOICE_FRAME */
+ /* subclass implicit from last ast_iax2_full_hdr */
+ unsigned char data[0];
+} __attribute__ ((__packed__));
+
+/*! Mini header is used only for voice frames -- delivered unreliably */
+struct ast_iax2_mini_enc_hdr {
+ unsigned short callno; /*!< Source call number -- high bit must be 0, rest must be non-zero */
+ unsigned char encdata[0];
+} __attribute__ ((__packed__));
+
+struct ast_iax2_meta_hdr {
+ unsigned short zeros; /*!< Zeros field -- must be zero */
+ unsigned char metacmd; /*!< Meta command */
+ unsigned char cmddata; /*!< Command Data */
+ unsigned char data[0];
+} __attribute__ ((__packed__));
+
+struct ast_iax2_video_hdr {
+ unsigned short zeros; /*!< Zeros field -- must be zero */
+ unsigned short callno; /*!< Video call number */
+ unsigned short ts; /*!< Timestamp and mark if present */
+ unsigned char data[0];
+} __attribute__ ((__packed__));
+
+struct ast_iax2_meta_trunk_hdr {
+ unsigned int ts; /*!< 32-bit timestamp for all messages */
+ unsigned char data[0];
+} __attribute__ ((__packed__));
+
+struct ast_iax2_meta_trunk_entry {
+ unsigned short callno; /*!< Call number */
+ unsigned short len; /*!< Length of data for this callno */
+} __attribute__ ((__packed__));
+
+/*! When trunktimestamps are used, we use this format instead */
+struct ast_iax2_meta_trunk_mini {
+ unsigned short len;
+ struct ast_iax2_mini_hdr mini; /*!< this is an actual miniframe */
+} __attribute__ ((__packed__));
+
+#define IAX_FIRMWARE_MAGIC 0x69617879
+
+struct ast_iax2_firmware_header {
+ unsigned int magic; /*!< Magic number */
+ unsigned short version; /*!< Software version */
+ unsigned char devname[16]; /*!< Device */
+ unsigned int datalen; /*!< Data length of file beyond header */
+ unsigned char chksum[16]; /*!< Checksum of all data */
+ unsigned char data[0];
+} __attribute__ ((__packed__));
+#endif
diff --git a/trunk/channels/misdn/Makefile b/trunk/channels/misdn/Makefile
new file mode 100644
index 000000000..85478225b
--- /dev/null
+++ b/trunk/channels/misdn/Makefile
@@ -0,0 +1,17 @@
+#
+# Makefile for chan_misdn support
+#
+ifneq ($(wildcard /usr/include/linux/mISDNdsp.h),)
+CFLAGS+=-DMISDN_1_2
+endif
+
+all:
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+portinfo: portinfo.o
+ $(CC) -o $@ $^ -lisdnnet -lmISDN -lpthread
+
+clean:
+ rm -rf *.a *.o *.so portinfo
diff --git a/trunk/channels/misdn/chan_misdn_config.h b/trunk/channels/misdn/chan_misdn_config.h
new file mode 100644
index 000000000..8c27f4f24
--- /dev/null
+++ b/trunk/channels/misdn/chan_misdn_config.h
@@ -0,0 +1,160 @@
+/*
+ * Chan_Misdn -- Channel Driver for Asterisk
+ *
+ * Interface to mISDN
+ *
+ * Copyright (C) 2004, Christian Richter
+ *
+ * Christian Richter <crich@beronet.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+/*! \file \brief
+ * Interface to mISDN - Config
+ * \author Christian Richter <crich@beronet.com>
+ */
+
+
+
+
+#ifndef CHAN_MISDN_CONFIG_H
+#define CHAN_MISDN_CONFIG_H
+
+#define BUFFERSIZE 512
+
+enum misdn_cfg_elements {
+
+ /* port config items */
+ MISDN_CFG_FIRST = 0,
+ MISDN_CFG_GROUPNAME, /* char[] */
+ MISDN_CFG_ALLOWED_BEARERS, /* char[] */
+ MISDN_CFG_FAR_ALERTING, /* int (bool) */
+ MISDN_CFG_RXGAIN, /* int */
+ MISDN_CFG_TXGAIN, /* int */
+ MISDN_CFG_TE_CHOOSE_CHANNEL, /* int (bool) */
+ MISDN_CFG_PMP_L1_CHECK, /* int (bool) */
+ MISDN_CFG_REJECT_CAUSE, /* int */
+ MISDN_CFG_ALARM_BLOCK, /* int (bool) */
+ MISDN_CFG_HDLC, /* int (bool) */
+ MISDN_CFG_CONTEXT, /* char[] */
+ MISDN_CFG_LANGUAGE, /* char[] */
+ MISDN_CFG_MUSICCLASS, /* char[] */
+ MISDN_CFG_CALLERID, /* char[] */
+ MISDN_CFG_METHOD, /* char[] */
+ MISDN_CFG_DIALPLAN, /* int */
+ MISDN_CFG_LOCALDIALPLAN, /* int */
+ MISDN_CFG_CPNDIALPLAN, /* int */
+ MISDN_CFG_NATPREFIX, /* char[] */
+ MISDN_CFG_INTERNATPREFIX, /* char[] */
+ MISDN_CFG_PRES, /* int */
+ MISDN_CFG_SCREEN, /* int */
+ MISDN_CFG_ALWAYS_IMMEDIATE, /* int (bool) */
+ MISDN_CFG_NODIALTONE, /* int (bool) */
+ MISDN_CFG_IMMEDIATE, /* int (bool) */
+ MISDN_CFG_SENDDTMF, /* int (bool) */
+ MISDN_CFG_ASTDTMF, /* int (bool) */
+ MISDN_CFG_HOLD_ALLOWED, /* int (bool) */
+ MISDN_CFG_EARLY_BCONNECT, /* int (bool) */
+ MISDN_CFG_INCOMING_EARLY_AUDIO, /* int (bool) */
+ MISDN_CFG_ECHOCANCEL, /* int */
+#ifdef MISDN_1_2
+ MISDN_CFG_PIPELINE, /* char[] */
+#endif
+
+#ifdef WITH_BEROEC
+ MISDN_CFG_BNECHOCANCEL,
+ MISDN_CFG_BNEC_ANTIHOWL,
+ MISDN_CFG_BNEC_NLP,
+ MISDN_CFG_BNEC_ZEROCOEFF,
+ MISDN_CFG_BNEC_TD,
+ MISDN_CFG_BNEC_ADAPT,
+#endif
+ MISDN_CFG_NEED_MORE_INFOS, /* bool */
+ MISDN_CFG_NOAUTORESPOND_ON_SETUP, /* bool */
+ MISDN_CFG_NTTIMEOUT, /* bool */
+ MISDN_CFG_BRIDGING, /* bool */
+ MISDN_CFG_JITTERBUFFER, /* int */
+ MISDN_CFG_JITTERBUFFER_UPPER_THRESHOLD, /* int */
+ MISDN_CFG_CALLGROUP, /* ast_group_t */
+ MISDN_CFG_PICKUPGROUP, /* ast_group_t */
+ MISDN_CFG_MAX_IN, /* int */
+ MISDN_CFG_MAX_OUT, /* int */
+ MISDN_CFG_L1_TIMEOUT, /* int */
+ MISDN_CFG_OVERLAP_DIAL, /* int (bool)*/
+ MISDN_CFG_MSNS, /* char[] */
+ MISDN_CFG_FAXDETECT, /* char[] */
+ MISDN_CFG_FAXDETECT_CONTEXT, /* char[] */
+ MISDN_CFG_FAXDETECT_TIMEOUT, /* int */
+ MISDN_CFG_PTP, /* int (bool) */
+ MISDN_CFG_LAST,
+
+ /* general config items */
+ MISDN_GEN_FIRST,
+#ifndef MISDN_1_2
+ MISDN_GEN_MISDN_INIT, /* char[] */
+#endif
+ MISDN_GEN_DEBUG, /* int */
+ MISDN_GEN_TRACEFILE, /* char[] */
+ MISDN_GEN_BRIDGING, /* int (bool) */
+ MISDN_GEN_STOP_TONE, /* int (bool) */
+ MISDN_GEN_APPEND_DIGITS2EXTEN, /* int (bool) */
+ MISDN_GEN_DYNAMIC_CRYPT, /* int (bool) */
+ MISDN_GEN_CRYPT_PREFIX, /* char[] */
+ MISDN_GEN_CRYPT_KEYS, /* char[] */
+ MISDN_GEN_NTKEEPCALLS, /* int (bool) */
+ MISDN_GEN_NTDEBUGFLAGS, /* int */
+ MISDN_GEN_NTDEBUGFILE, /* char[] */
+ MISDN_GEN_LAST
+};
+
+enum misdn_cfg_method {
+ METHOD_STANDARD = 0,
+ METHOD_ROUND_ROBIN,
+ METHOD_STANDARD_DEC
+};
+
+/* you must call misdn_cfg_init before any other function of this header file */
+int misdn_cfg_init(int max_ports, int reload);
+void misdn_cfg_reload(void);
+void misdn_cfg_destroy(void);
+
+void misdn_cfg_update_ptp( void );
+
+/* if you requst a general config element, the port value is ignored. if the requested
+ * value is not available, or the buffer is too small, the buffer will be nulled (in
+ * case of a char* only its first byte will be nulled). */
+void misdn_cfg_get(int port, enum misdn_cfg_elements elem, void* buf, int bufsize);
+
+/* returns the enum element for the given name, returns MISDN_CFG_FIRST if none was found */
+enum misdn_cfg_elements misdn_cfg_get_elem (char *name);
+
+/* fills the buffer with the name of the given config element */
+void misdn_cfg_get_name (enum misdn_cfg_elements elem, void *buf, int bufsize);
+
+/* fills the buffer with the description of the given config element */
+void misdn_cfg_get_desc (enum misdn_cfg_elements elem, void *buf, int bufsize, void *buf_default, int bufsize_default);
+
+/* fills the buffer with a ',' separated list of all active ports */
+void misdn_cfg_get_ports_string(char *ports);
+
+/* fills the buffer with a nice printable string representation of the config element */
+void misdn_cfg_get_config_string(int port, enum misdn_cfg_elements elem, char* buf, int bufsize);
+
+/* returns the next available port number. returns -1 if the last one was reached. */
+int misdn_cfg_get_next_port(int port);
+int misdn_cfg_get_next_port_spin(int port);
+
+int misdn_cfg_is_msn_valid(int port, char* msn);
+int misdn_cfg_is_port_valid(int port);
+int misdn_cfg_is_group_method(char *group, enum misdn_cfg_method meth);
+
+#if 0
+char *misdn_cfg_get_next_group(char *group);
+int misdn_cfg_get_next_port_in_group(int port, char *group);
+#endif
+
+struct ast_jb_conf *misdn_get_global_jbconf(void);
+
+#endif
diff --git a/trunk/channels/misdn/ie.c b/trunk/channels/misdn/ie.c
new file mode 100644
index 000000000..817e3d8cb
--- /dev/null
+++ b/trunk/channels/misdn/ie.c
@@ -0,0 +1,1422 @@
+
+/*
+ * Chan_Misdn -- Channel Driver for Asterisk
+ *
+ * Interface to mISDN
+ *
+ * Copyright (C) 2005, Christian Richter
+ *
+ * Christian Richter <crich@beronet.com>
+ *
+ * heaviliy patched from jollys ie.cpp, jolly gave me ALL
+ * rights for this code, i can even have my own copyright on it.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+/*! \file \brief
+ * Interface to mISDN
+ * \author Christian Richter <crich@beronet.com>
+ */
+
+/*
+ the pointer of enc_ie_* always points to the IE itself
+ if qi is not NULL (TE-mode), offset is set
+*/
+
+
+#include <string.h>
+
+#include <mISDNuser/mISDNlib.h>
+#include <mISDNuser/isdn_net.h>
+#include <mISDNuser/l3dss1.h>
+#include <mISDNuser/net_l3.h>
+#include "asterisk/localtime.h"
+
+
+
+#define MISDN_IE_DEBG 0
+
+/* support stuff */
+static void strnncpy(char *dest, char *src, int len, int dst_len)
+{
+ if (len > dst_len-1)
+ len = dst_len-1;
+ strncpy((char *)dest, (char *)src, len);
+ dest[len] = '\0';
+}
+
+
+/* IE_COMPLETE */
+static void enc_ie_complete(unsigned char **ntmode, msg_t *msg, int complete, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+
+ if (complete<0 || complete>1)
+ {
+ printf("%s: ERROR: complete(%d) is out of range.\n", __FUNCTION__, complete);
+ return;
+ }
+
+ if (complete)
+ if (MISDN_IE_DEBG) printf(" complete=%d\n", complete);
+
+ if (complete)
+ {
+ p = msg_put(msg, 1);
+ if (nt)
+ {
+ *ntmode = p;
+ } else
+ qi->QI_ELEMENT(sending_complete) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+
+ p[0] = IE_COMPLETE;
+ }
+}
+
+static void dec_ie_complete(unsigned char *p, Q931_info_t *qi, int *complete, int nt, struct misdn_bchannel *bc)
+{
+ *complete = 0;
+ if (!nt)
+ {
+ if (qi->QI_ELEMENT(sending_complete))
+ *complete = 1;
+ } else
+ if (p)
+ *complete = 1;
+
+ if (*complete)
+ if (MISDN_IE_DEBG) printf(" complete=%d\n", *complete);
+}
+
+
+/* IE_BEARER */
+static void enc_ie_bearer(unsigned char **ntmode, msg_t *msg, int coding, int capability, int mode, int rate, int multi, int user, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ if (coding<0 || coding>3)
+ {
+ printf("%s: ERROR: coding(%d) is out of range.\n", __FUNCTION__, coding);
+ return;
+ }
+ if (capability<0 || capability>31)
+ {
+ printf("%s: ERROR: capability(%d) is out of range.\n", __FUNCTION__, capability);
+ return;
+ }
+ if (mode<0 || mode>3)
+ {
+ printf("%s: ERROR: mode(%d) is out of range.\n", __FUNCTION__, mode);
+ return;
+ }
+ if (rate<0 || rate>31)
+ {
+ printf("%s: ERROR: rate(%d) is out of range.\n", __FUNCTION__, rate);
+ return;
+ }
+ if (multi>127)
+ {
+ printf("%s: ERROR: multi(%d) is out of range.\n", __FUNCTION__, multi);
+ return;
+ }
+ if (user>31)
+ {
+ printf("%s: ERROR: user L1(%d) is out of range.\n", __FUNCTION__, rate);
+ return;
+ }
+ if (rate!=24 && multi>=0)
+ {
+ printf("%s: WARNING: multi(%d) is only possible if rate(%d) would be 24.\n", __FUNCTION__, multi, rate);
+ multi = -1;
+ }
+
+ if (MISDN_IE_DEBG) printf(" coding=%d capability=%d mode=%d rate=%d multi=%d user=%d\n", coding, capability, mode, rate, multi, user);
+
+ l = 2 + (multi>=0) + (user>=0);
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(bearer_capability) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_BEARER;
+ p[1] = l;
+ p[2] = 0x80 + (coding<<5) + capability;
+ p[3] = 0x80 + (mode<<5) + rate;
+ if (multi >= 0)
+ p[4] = 0x80 + multi;
+ if (user >= 0)
+ p[4+(multi>=0)] = 0xa0 + user;
+}
+
+static void dec_ie_bearer(unsigned char *p, Q931_info_t *qi, int *coding, int *capability, int *mode, int *rate, int *multi, int *user,
+ int *async, int *urate, int *stopbits, int *dbits, int *parity, int nt, struct misdn_bchannel *bc)
+{
+ int octet;
+ *coding = -1;
+ *capability = -1;
+ *mode = -1;
+ *rate = -1;
+ *multi = -1;
+ *user = -1;
+ *async = -1;
+ *urate = -1;
+ *stopbits = -1;
+ *dbits = -1;
+ *parity = -1;
+
+ if (!nt)
+ {
+ p = NULL;
+#ifdef LLC_SUPPORT
+ if (qi->QI_ELEMENT(llc)) {
+
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(llc) + 1;
+ }
+#endif
+ if (qi->QI_ELEMENT(bearer_capability))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(bearer_capability) + 1;
+ }
+ if (!p)
+ return;
+
+ if (p[0] < 2)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ *coding = (p[1]&0x60) >> 5;
+ *capability = p[1] & 0x1f;
+ octet = 2;
+ if (!(p[1] & 0x80))
+ octet++;
+
+ if (p[0] < octet)
+ goto done;
+
+ *mode = (p[octet]&0x60) >> 5;
+ *rate = p[octet] & 0x1f;
+
+ octet++;
+
+ if (p[0] < octet)
+ goto done;
+
+ if (*rate == 0x18) {
+ /* Rate multiplier only present if 64Kb/s base rate */
+ *multi = p[octet++] & 0x7f;
+ }
+
+ if (p[0] < octet)
+ goto done;
+
+ /* Start L1 info */
+ if ((p[octet] & 0x60) == 0x20) {
+ *user = p[octet] & 0x1f;
+
+ if (p[0] <= octet)
+ goto done;
+
+ if (p[octet++] & 0x80)
+ goto l2;
+
+ *async = !!(p[octet] & 0x40);
+ /* 0x20 is inband negotiation */
+ *urate = p[octet] & 0x1f;
+
+ if (p[0] <= octet)
+ goto done;
+
+ if (p[octet++] & 0x80)
+ goto l2;
+
+ /* Ignore next byte for now: Intermediate rate, NIC, flow control */
+
+ if (p[0] <= octet)
+ goto done;
+
+ if (p[octet++] & 0x80)
+ goto l2;
+
+ /* And the next one. Header, multiframe, mode, assignor/ee, negotiation */
+
+ if (p[0] <= octet)
+ goto done;
+
+ if (!p[octet++] & 0x80)
+ goto l2;
+
+ /* Wheee. V.110 speed information */
+
+ *stopbits = (p[octet] & 0x60) >> 5;
+ *dbits = (p[octet] & 0x18) >> 3;
+ *parity = p[octet] & 7;
+
+ octet++;
+ }
+ l2: /* Nobody seems to want the rest so we don't bother (yet) */
+ done:
+ if (MISDN_IE_DEBG) printf(" coding=%d capability=%d mode=%d rate=%d multi=%d user=%d async=%d urate=%d stopbits=%d dbits=%d parity=%d\n", *coding, *capability, *mode, *rate, *multi, *user, *async, *urate, *stopbits, *dbits, *parity);
+}
+
+
+/* IE_CALL_ID */
+#if 0
+static void enc_ie_call_id(unsigned char **ntmode, msg_t *msg, char *callid, int callid_len, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ char debug[25];
+ int i;
+
+ if (!callid || callid_len<=0)
+ {
+ return;
+ }
+ if (callid_len>8)
+ {
+ printf("%s: ERROR: callid_len(%d) is out of range.\n", __FUNCTION__, callid_len);
+ return;
+ }
+
+ i = 0;
+ while(i < callid_len)
+ {
+ if (MISDN_IE_DEBG) printf(debug+(i*3), " %02x", callid[i]);
+ i++;
+ }
+
+ if (MISDN_IE_DEBG) printf(" callid%s\n", debug);
+
+ l = callid_len;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(call_id) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_CALL_ID;
+ p[1] = l;
+ memcpy(p+2, callid, callid_len);
+}
+#endif
+
+#if 0
+static void dec_ie_call_id(unsigned char *p, Q931_info_t *qi, char *callid, int *callid_len, int nt, struct misdn_bchannel *bc)
+{
+ char debug[25];
+ int i;
+
+ *callid_len = -1;
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(call_id))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(call_id) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] > 8)
+ {
+ printf("%s: ERROR: IE too long (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ *callid_len = p[0];
+ memcpy(callid, p+1, *callid_len);
+
+ i = 0;
+ while(i < *callid_len)
+ {
+ if (MISDN_IE_DEBG) printf(debug+(i*3), " %02x", callid[i]);
+ i++;
+ }
+
+ if (MISDN_IE_DEBG) printf(" callid%s\n", debug);
+}
+#endif
+
+/* IE_CALLED_PN */
+static void enc_ie_called_pn(unsigned char **ntmode, msg_t *msg, int type, int plan, char *number, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ if (type<0 || type>7)
+ {
+ printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type);
+ return;
+ }
+ if (plan<0 || plan>15)
+ {
+ printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan);
+ return;
+ }
+ if (!number[0])
+ {
+ printf("%s: ERROR: number is not given.\n", __FUNCTION__);
+ return;
+ }
+
+ if (MISDN_IE_DEBG) printf(" type=%d plan=%d number='%s'\n", type, plan, number);
+
+ l = 1+strlen((char *)number);
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(called_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_CALLED_PN;
+ p[1] = l;
+ p[2] = 0x80 + (type<<4) + plan;
+ strncpy((char *)p+3, (char *)number, strlen((char *)number));
+}
+
+static void dec_ie_called_pn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, char *number, int number_len, int nt, struct misdn_bchannel *bc)
+{
+ *type = -1;
+ *plan = -1;
+ *number = '\0';
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(called_nr))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(called_nr) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] < 2)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ *type = (p[1]&0x70) >> 4;
+ *plan = p[1] & 0xf;
+ strnncpy(number, (char *)p+2, p[0]-1, number_len);
+
+ if (MISDN_IE_DEBG) printf(" type=%d plan=%d number='%s'\n", *type, *plan, number);
+}
+
+
+/* IE_CALLING_PN */
+static void enc_ie_calling_pn(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, int screen, char *number, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ if (type<0 || type>7)
+ {
+ printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type);
+ return;
+ }
+ if (plan<0 || plan>15)
+ {
+ printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan);
+ return;
+ }
+ if (present>3)
+ {
+ printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present);
+ return;
+ }
+ if (present >= 0) if (screen<0 || screen>3)
+ {
+ printf("%s: ERROR: screen(%d) is out of range.\n", __FUNCTION__, screen);
+ return;
+ }
+
+ if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d number='%s'\n", type, plan, present, screen, number);
+
+ l = 1;
+ if (number) if (number[0])
+ l += strlen((char *)number);
+ if (present >= 0)
+ l += 1;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(calling_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_CALLING_PN;
+ p[1] = l;
+ if (present >= 0)
+ {
+ p[2] = 0x00 + (type<<4) + plan;
+ p[3] = 0x80 + (present<<5) + screen;
+ if (number) if (number[0])
+ strncpy((char *)p+4, (char *)number, strlen((char *)number));
+ } else
+ {
+ p[2] = 0x80 + (type<<4) + plan;
+ if (number) if (number[0])
+ strncpy((char *)p+3, (char *)number, strlen((char *)number));
+ }
+}
+
+static void dec_ie_calling_pn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, int *screen, char *number, int number_len, int nt, struct misdn_bchannel *bc)
+{
+ *type = -1;
+ *plan = -1;
+ *present = -1;
+ *screen = -1;
+ *number = '\0';
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(calling_nr))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(calling_nr) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] < 1)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ *type = (p[1]&0x70) >> 4;
+ *plan = p[1] & 0xf;
+ if (!(p[1] & 0x80))
+ {
+ if (p[0] < 2)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+ *present = (p[2]&0x60) >> 5;
+ *screen = p[2] & 0x3;
+ strnncpy(number, (char *)p+3, p[0]-2, number_len);
+ } else
+ {
+ strnncpy(number, (char *)p+2, p[0]-1, number_len);
+ /* SPECIAL workarround for IBT software bug */
+ /* if (number[0]==0x80) */
+ /* strcpy((char *)number, (char *)number+1); */
+ }
+
+ if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d number='%s'\n", *type, *plan, *present, *screen, number);
+}
+
+
+/* IE_CONNECTED_PN */
+static void enc_ie_connected_pn(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, int screen, char *number, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ if (type<0 || type>7)
+ {
+ printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type);
+ return;
+ }
+ if (plan<0 || plan>15)
+ {
+ printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan);
+ return;
+ }
+ if (present>3)
+ {
+ printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present);
+ return;
+ }
+ if (present >= 0) if (screen<0 || screen>3)
+ {
+ printf("%s: ERROR: screen(%d) is out of range.\n", __FUNCTION__, screen);
+ return;
+ }
+
+ if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d number='%s'\n", type, plan, present, screen, number);
+
+ l = 1;
+ if (number) if (number[0])
+ l += strlen((char *)number);
+ if (present >= 0)
+ l += 1;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(connected_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_CONNECT_PN;
+ p[1] = l;
+ if (present >= 0)
+ {
+ p[2] = 0x00 + (type<<4) + plan;
+ p[3] = 0x80 + (present<<5) + screen;
+ if (number) if (number[0])
+ strncpy((char *)p+4, (char *)number, strlen((char *)number));
+ } else
+ {
+ p[2] = 0x80 + (type<<4) + plan;
+ if (number) if (number[0])
+ strncpy((char *)p+3, (char *)number, strlen((char *)number));
+ }
+}
+
+static void dec_ie_connected_pn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, int *screen, char *number, int number_len, int nt, struct misdn_bchannel *bc)
+{
+ *type = -1;
+ *plan = -1;
+ *present = -1;
+ *screen = -1;
+ *number = '\0';
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(connected_nr))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(connected_nr) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] < 1)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ *type = (p[1]&0x70) >> 4;
+ *plan = p[1] & 0xf;
+ if (!(p[1] & 0x80))
+ {
+ if (p[0] < 2)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+ *present = (p[2]&0x60) >> 5;
+ *screen = p[2] & 0x3;
+ strnncpy(number, (char *)p+3, p[0]-2, number_len);
+ } else
+ {
+ strnncpy(number, (char *)p+2, p[0]-1, number_len);
+ }
+
+ if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d number='%s'\n", *type, *plan, *present, *screen, number);
+}
+
+
+/* IE_CAUSE */
+static void enc_ie_cause(unsigned char **ntmode, msg_t *msg, int location, int cause, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ if (location<0 || location>7)
+ {
+ printf("%s: ERROR: location(%d) is out of range.\n", __FUNCTION__, location);
+ return;
+ }
+ if (cause<0 || cause>127)
+ {
+ printf("%s: ERROR: cause(%d) is out of range.\n", __FUNCTION__, cause);
+ return;
+ }
+
+ if (MISDN_IE_DEBG) printf(" location=%d cause=%d\n", location, cause);
+
+ l = 2;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(cause) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_CAUSE;
+ p[1] = l;
+ p[2] = 0x80 + location;
+ p[3] = 0x80 + cause;
+}
+
+#if 0
+static void enc_ie_cause_standalone(unsigned char **ntmode, msg_t *msg, int location, int cause, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p = msg_put(msg, 4);
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ if (ntmode)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(cause) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_CAUSE;
+ p[1] = 2;
+ p[2] = 0x80 + location;
+ p[3] = 0x80 + cause;
+}
+#endif
+
+static void dec_ie_cause(unsigned char *p, Q931_info_t *qi, int *location, int *cause, int nt, struct misdn_bchannel *bc)
+{
+ *location = -1;
+ *cause = -1;
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(cause))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(cause) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] < 2)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ *location = p[1] & 0x0f;
+ *cause = p[2] & 0x7f;
+
+ if (MISDN_IE_DEBG) printf(" location=%d cause=%d\n", *location, *cause);
+}
+
+
+/* IE_CHANNEL_ID */
+static void enc_ie_channel_id(unsigned char **ntmode, msg_t *msg, int exclusive, int channel, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+ int pri = stack->pri;
+
+ if (exclusive<0 || exclusive>1)
+ {
+ printf("%s: ERROR: exclusive(%d) is out of range.\n", __FUNCTION__, exclusive);
+ return;
+ }
+ if ((channel<0 || channel>0xff)
+ || (!pri && (channel>2 && channel<0xff))
+ || (pri && (channel>31 && channel<0xff))
+ || (pri && channel==16))
+ {
+ printf("%s: ERROR: channel(%d) is out of range.\n", __FUNCTION__, channel);
+ return;
+ }
+
+ /* if (MISDN_IE_DEBG) printf(" exclusive=%d channel=%d\n", exclusive, channel); */
+
+
+ if (!pri)
+ {
+ /* BRI */
+ l = 1;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(channel_id) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_CHANNEL_ID;
+ p[1] = l;
+ if (channel == 0xff)
+ channel = 3;
+ p[2] = 0x80 + (exclusive<<3) + channel;
+ /* printf(" exclusive=%d channel=%d\n", exclusive, channel); */
+ } else
+ {
+ /* PRI */
+ if (channel == 0) /* no channel */
+ return; /* IE not present */
+/* if (MISDN_IE_DEBG) printf("channel = %d\n", channel); */
+ if (channel == 0xff) /* any channel */
+ {
+ l = 1;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(channel_id) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_CHANNEL_ID;
+ p[1] = l;
+ p[2] = 0x80 + 0x20 + 0x03;
+/* if (MISDN_IE_DEBG) printf("%02x\n", p[2]); */
+ return; /* end */
+ }
+ l = 3;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(channel_id) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_CHANNEL_ID;
+ p[1] = l;
+ p[2] = 0x80 + 0x20 + (exclusive<<3) + 0x01;
+ p[3] = 0x80 + 3; /* CCITT, Number, B-type */
+ p[4] = 0x80 + channel;
+/* if (MISDN_IE_DEBG) printf("%02x %02x %02x\n", p[2], p[3], p[4]); */
+ }
+}
+
+static void dec_ie_channel_id(unsigned char *p, Q931_info_t *qi, int *exclusive, int *channel, int nt, struct misdn_bchannel *bc)
+{
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+ int pri =stack->pri;
+
+ *exclusive = -1;
+ *channel = -1;
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(channel_id))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(channel_id) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] < 1)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ if (p[1] & 0x40)
+ {
+ printf("%s: ERROR: refering to channels of other interfaces is not supported.\n", __FUNCTION__);
+ return;
+ }
+ if (p[1] & 0x04)
+ {
+ printf("%s: ERROR: using d-channel is not supported.\n", __FUNCTION__);
+ return;
+ }
+
+ *exclusive = (p[1]&0x08) >> 3;
+ if (!pri)
+ {
+ /* BRI */
+ if (p[1] & 0x20)
+ {
+ printf("%s: ERROR: extended channel ID with non PRI interface.\n", __FUNCTION__);
+ return;
+ }
+ *channel = p[1] & 0x03;
+ if (*channel == 3)
+ *channel = 0xff;
+ } else
+ {
+ /* PRI */
+ if (p[0] < 1)
+ {
+ printf("%s: ERROR: IE too short for PRI (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+ if (!(p[1] & 0x20))
+ {
+ printf("%s: ERROR: basic channel ID with PRI interface.\n", __FUNCTION__);
+ return;
+ }
+ if ((p[1]&0x03) == 0x00)
+ {
+ /* no channel */
+ *channel = 0;
+ return;
+ }
+ if ((p[1]&0x03) == 0x03)
+ {
+ /* any channel */
+ *channel = 0xff;
+ return;
+ }
+ if (p[0] < 3)
+ {
+ printf("%s: ERROR: IE too short for PRI with channel(%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+ if (p[2] & 0x10)
+ {
+ printf("%s: ERROR: channel map not supported.\n", __FUNCTION__);
+ return;
+ }
+ *channel = p[3] & 0x7f;
+ if ( (*channel<1) | (*channel==16) | (*channel>31))
+ {
+ printf("%s: ERROR: PRI interface channel out of range (%d).\n", __FUNCTION__, *channel);
+ return;
+ }
+/* if (MISDN_IE_DEBG) printf("%02x %02x %02x\n", p[1], p[2], p[3]); */
+ }
+
+ if (MISDN_IE_DEBG) printf(" exclusive=%d channel=%d\n", *exclusive, *channel);
+}
+
+
+/* IE_DATE */
+static void enc_ie_date(unsigned char **ntmode, msg_t *msg, time_t ti, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+ struct timeval tv = { ti, 0 };
+ struct ast_tm tm;
+
+ ast_localtime(&tv, &tm, NULL);
+ if (MISDN_IE_DEBG) printf(" year=%d month=%d day=%d hour=%d minute=%d\n", tm.tm_year%100, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min);
+
+ l = 5;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(date) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_DATE;
+ p[1] = l;
+ p[2] = tm.tm_year % 100;
+ p[3] = tm.tm_mon + 1;
+ p[4] = tm.tm_mday;
+ p[5] = tm.tm_hour;
+ p[6] = tm.tm_min;
+}
+
+
+/* IE_DISPLAY */
+static void enc_ie_display(unsigned char **ntmode, msg_t *msg, char *display, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ if (!display[0])
+ {
+ printf("%s: ERROR: display text not given.\n", __FUNCTION__);
+ return;
+ }
+
+ if (strlen((char *)display) > 80)
+ {
+ printf("%s: WARNING: display text too long (max 80 chars), cutting.\n", __FUNCTION__);
+ display[80] = '\0';
+ }
+
+ /* if (MISDN_IE_DEBG) printf(" display='%s' (len=%d)\n", display, strlen((char *)display)); */
+
+ l = strlen((char *)display);
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(display) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_DISPLAY;
+ p[1] = l;
+ strncpy((char *)p+2, (char *)display, strlen((char *)display));
+}
+
+#if 0
+static void dec_ie_display(unsigned char *p, Q931_info_t *qi, char *display, int display_len, int nt, struct misdn_bchannel *bc)
+{
+ *display = '\0';
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(display))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(display) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] < 1)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ strnncpy(display, (char *)p+1, p[0], display_len);
+
+ if (MISDN_IE_DEBG) printf(" display='%s'\n", display);
+}
+#endif
+
+/* IE_KEYPAD */
+#if 1
+static void enc_ie_keypad(unsigned char **ntmode, msg_t *msg, char *keypad, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ if (!keypad[0])
+ {
+ printf("%s: ERROR: keypad info not given.\n", __FUNCTION__);
+ return;
+ }
+
+ if (MISDN_IE_DEBG) printf(" keypad='%s'\n", keypad);
+
+ l = strlen(keypad);
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(keypad) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_KEYPAD;
+ p[1] = l;
+ strncpy((char *)p+2, keypad, strlen(keypad));
+}
+#endif
+
+static void dec_ie_keypad(unsigned char *p, Q931_info_t *qi, char *keypad, int keypad_len, int nt, struct misdn_bchannel *bc)
+{
+ *keypad = '\0';
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(keypad))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(keypad) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] < 1)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ strnncpy(keypad, (char *)p+1, p[0], keypad_len);
+
+ if (MISDN_IE_DEBG) printf(" keypad='%s'\n", keypad);
+}
+
+
+/* IE_NOTIFY */
+#if 0
+static void enc_ie_notify(unsigned char **ntmode, msg_t *msg, int notify, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ if (notify<0 || notify>0x7f)
+ {
+ printf("%s: ERROR: notify(%d) is out of range.\n", __FUNCTION__, notify);
+ return;
+ }
+
+ if (MISDN_IE_DEBG) printf(" notify=%d\n", notify);
+
+ l = 1;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(notify) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_NOTIFY;
+ p[1] = l;
+ p[2] = 0x80 + notify;
+}
+#endif
+
+#if 0
+static void dec_ie_notify(unsigned char *p, Q931_info_t *qi, int *notify, int nt, struct misdn_bchannel *bc)
+{
+ *notify = -1;
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(notify))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(notify) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] < 1)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ *notify = p[1] & 0x7f;
+
+ if (MISDN_IE_DEBG) printf(" notify=%d\n", *notify);
+}
+#endif
+
+
+/* IE_PROGRESS */
+static void enc_ie_progress(unsigned char **ntmode, msg_t *msg, int coding, int location, int progress, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ if (coding<0 || coding>0x03)
+ {
+ printf("%s: ERROR: coding(%d) is out of range.\n", __FUNCTION__, coding);
+ return;
+ }
+ if (location<0 || location>0x0f)
+ {
+ printf("%s: ERROR: location(%d) is out of range.\n", __FUNCTION__, location);
+ return;
+ }
+ if (progress<0 || progress>0x7f)
+ {
+ printf("%s: ERROR: progress(%d) is out of range.\n", __FUNCTION__, progress);
+ return;
+ }
+
+ if (MISDN_IE_DEBG) printf(" coding=%d location=%d progress=%d\n", coding, location, progress);
+
+ l = 2;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(progress) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_PROGRESS;
+ p[1] = l;
+ p[2] = 0x80 + (coding<<5) + location;
+ p[3] = 0x80 + progress;
+}
+
+static void dec_ie_progress(unsigned char *p, Q931_info_t *qi, int *coding, int *location, int *progress, int nt, struct misdn_bchannel *bc)
+{
+ *coding = -1;
+ *location = -1;
+ //*progress = -1;
+ *progress = 0;
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(progress))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(progress) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] < 1)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ *coding = (p[1]&0x60) >> 5;
+ *location = p[1] & 0x0f;
+ *progress = p[2] & 0x7f;
+
+ if (MISDN_IE_DEBG) printf(" coding=%d location=%d progress=%d\n", *coding, *location, *progress);
+}
+
+
+/* IE_REDIR_NR (redirecting = during MT_SETUP) */
+static void enc_ie_redir_nr(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, int screen, int reason, char *number, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ if (type<0 || type>7)
+ {
+ printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type);
+ return;
+ }
+ if (plan<0 || plan>15)
+ {
+ printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan);
+ return;
+ }
+ if (present > 3)
+ {
+ printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present);
+ return;
+ }
+ if (present >= 0) if (screen<0 || screen>3)
+ {
+ printf("%s: ERROR: screen(%d) is out of range.\n", __FUNCTION__, screen);
+ return;
+ }
+ if (reason > 0x0f)
+ {
+ printf("%s: ERROR: reason(%d) is out of range.\n", __FUNCTION__, reason);
+ return;
+ }
+
+ if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d readon=%d number='%s'\n", type, plan, present, screen, reason, number);
+
+ l = 1;
+ if (number)
+ l += strlen((char *)number);
+ if (present >= 0)
+ {
+ l += 1;
+ if (reason >= 0)
+ l += 1;
+ }
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(redirect_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_REDIR_NR;
+ p[1] = l;
+ if (present >= 0)
+ {
+ if (reason >= 0)
+ {
+ p[2] = 0x00 + (type<<4) + plan;
+ p[3] = 0x00 + (present<<5) + screen;
+ p[4] = 0x80 + reason;
+ if (number)
+ strncpy((char *)p+5, (char *)number, strlen((char *)number));
+ } else
+ {
+ p[2] = 0x00 + (type<<4) + plan;
+ p[3] = 0x80 + (present<<5) + screen;
+ if (number)
+ strncpy((char *)p+4, (char *)number, strlen((char *)number));
+ }
+ } else
+ {
+ p[2] = 0x80 + (type<<4) + plan;
+ if (number) if (number[0])
+ strncpy((char *)p+3, (char *)number, strlen((char *)number));
+ }
+}
+
+static void dec_ie_redir_nr(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, int *screen, int *reason, char *number, int number_len, int nt, struct misdn_bchannel *bc)
+{
+ *type = -1;
+ *plan = -1;
+ *present = -1;
+ *screen = -1;
+ *reason = -1;
+ *number = '\0';
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(redirect_nr))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(redirect_nr) + 1;
+ }
+ if (!p)
+ return;
+ if (p[0] < 1)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ *type = (p[1]&0x70) >> 4;
+ *plan = p[1] & 0xf;
+ if (!(p[1] & 0x80))
+ {
+ *present = (p[2]&0x60) >> 5;
+ *screen = p[2] & 0x3;
+ if (!(p[2] & 0x80))
+ {
+ *reason = p[3] & 0x0f;
+ strnncpy(number, (char *)p+4, p[0]-3, number_len);
+ } else
+ {
+ strnncpy(number, (char *)p+3, p[0]-2, number_len);
+ }
+ } else
+ {
+ strnncpy(number, (char *)p+2, p[0]-1, number_len);
+ }
+
+ if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d reason=%d number='%s'\n", *type, *plan, *present, *screen, *reason, number);
+}
+
+
+/* IE_REDIR_DN (redirection = during MT_NOTIFY) */
+#if 0
+static void enc_ie_redir_dn(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, char *number, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+/* Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); */
+ int l;
+
+ if (type<0 || type>7)
+ {
+ printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type);
+ return;
+ }
+ if (plan<0 || plan>15)
+ {
+ printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan);
+ return;
+ }
+ if (present > 3)
+ {
+ printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present);
+ return;
+ }
+
+ if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d number='%s'\n", type, plan, present, number);
+
+ l = 1;
+ if (number)
+ l += strlen((char *)number);
+ if (present >= 0)
+ l += 1;
+ p = msg_put(msg, l+2);
+ if (nt)
+ *ntmode = p+1;
+ else
+/* #warning REINSERT redir_dn, when included in te-mode */
+ /*qi->QI_ELEMENT(redir_dn) = p - (unsigned char *)qi - sizeof(Q931_info_t)*/;
+ p[0] = IE_REDIR_DN;
+ p[1] = l;
+ if (present >= 0)
+ {
+ p[2] = 0x00 + (type<<4) + plan;
+ p[3] = 0x80 + (present<<5);
+ if (number)
+ strncpy((char *)p+4, (char *)number, strlen((char *)number));
+ } else
+ {
+ p[2] = 0x80 + (type<<4) + plan;
+ if (number)
+ strncpy((char *)p+3, (char *)number, strlen((char *)number));
+ }
+}
+#endif
+
+#if 0
+static void dec_ie_redir_dn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, char *number, int number_len, int nt, struct misdn_bchannel *bc)
+{
+ *type = -1;
+ *plan = -1;
+ *present = -1;
+ *number = '\0';
+
+ if (!nt)
+ {
+ p = NULL;
+/* #warning REINSERT redir_dn, when included in te-mode */
+/* if (qi->QI_ELEMENT(redir_dn)) */
+/* p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(redir_dn) + 1; */
+ }
+ if (!p)
+ return;
+ if (p[0] < 1)
+ {
+ printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
+ return;
+ }
+
+ *type = (p[1]&0x70) >> 4;
+ *plan = p[1] & 0xf;
+ if (!(p[1] & 0x80))
+ {
+ *present = (p[2]&0x60) >> 5;
+ strnncpy(number, (char *)p+3, p[0]-2, number_len);
+ } else
+ {
+ strnncpy(number, (char *)p+2, p[0]-1, number_len);
+ }
+
+ if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d number='%s'\n", *type, *plan, *present, number);
+}
+#endif
+
+
+/* IE_USERUSER */
+#if 1
+static void enc_ie_useruser(unsigned char **ntmode, msg_t *msg, int protocol, char *user, int user_len, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ int l;
+
+ char debug[768];
+ int i;
+
+ if (protocol<0 || protocol>127)
+ {
+ printf("%s: ERROR: protocol(%d) is out of range.\n", __FUNCTION__, protocol);
+ return;
+ }
+ if (!user || user_len<=0)
+ {
+ return;
+ }
+
+ i = 0;
+ while(i < user_len)
+ {
+ if (MISDN_IE_DEBG) printf(debug+(i*3), " %02x", user[i]);
+ i++;
+ }
+
+ if (MISDN_IE_DEBG) printf(" protocol=%d user-user%s\n", protocol, debug);
+
+ l = user_len+1;
+ p = msg_put(msg, l+3);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(useruser) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_USER_USER;
+ p[1] = l;
+ p[2] = protocol;
+ memcpy(p+3, user, user_len);
+}
+#endif
+
+#if 1
+static void dec_ie_useruser(unsigned char *p, Q931_info_t *qi, int *protocol, char *user, int *user_len, int nt, struct misdn_bchannel *bc)
+{
+ char debug[768];
+ int i;
+
+ *user_len = 0;
+ *protocol = -1;
+
+ if (!nt)
+ {
+ p = NULL;
+ if (qi->QI_ELEMENT(useruser))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(useruser) + 1;
+ }
+ if (!p)
+ return;
+
+ *user_len = p[0]-1;
+ if (p[0] < 1)
+ return;
+ *protocol = p[1];
+ memcpy(user, p+2, (*user_len<=128)?*(user_len):128); /* clip to 128 maximum */
+
+ i = 0;
+ while(i < *user_len)
+ {
+ if (MISDN_IE_DEBG) printf(debug+(i*3), " %02x", user[i]);
+ i++;
+ }
+ debug[i*3] = '\0';
+
+ if (MISDN_IE_DEBG) printf(" protocol=%d user-user%s\n", *protocol, debug);
+}
+#endif
+
+/* IE_DISPLAY */
+static void enc_ie_restart_ind(unsigned char **ntmode, msg_t *msg, unsigned char rind, int nt, struct misdn_bchannel *bc)
+{
+ unsigned char *p;
+ Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ /* if (MISDN_IE_DEBG) printf(" display='%s' (len=%d)\n", display, strlen((char *)display)); */
+
+ p = msg_put(msg, 3);
+ if (nt)
+ *ntmode = p+1;
+ else
+ qi->QI_ELEMENT(restart_ind) = p - (unsigned char *)qi - sizeof(Q931_info_t);
+ p[0] = IE_RESTART_IND;
+ p[1] = 1;
+ p[2] = rind;
+
+}
+
diff --git a/trunk/channels/misdn/isdn_lib.c b/trunk/channels/misdn/isdn_lib.c
new file mode 100644
index 000000000..67ecb47fa
--- /dev/null
+++ b/trunk/channels/misdn/isdn_lib.c
@@ -0,0 +1,4582 @@
+/*
+ * Chan_Misdn -- Channel Driver for Asterisk
+ *
+ * Interface to mISDN
+ *
+ * Copyright (C) 2004, Christian Richter
+ *
+ * Christian Richter <crich@beronet.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+/*! \file \brief
+ * Interface to mISDN
+ * \author Christian Richter <crich@beronet.com>
+ */
+
+
+
+#include <syslog.h>
+#include <mISDNuser/isdn_debug.h>
+
+#include "isdn_lib_intern.h"
+#include "isdn_lib.h"
+
+void misdn_join_conf(struct misdn_bchannel *bc, int conf_id);
+void misdn_split_conf(struct misdn_bchannel *bc, int conf_id);
+
+int queue_cleanup_bc(struct misdn_bchannel *bc) ;
+
+int misdn_lib_get_l2_up(struct misdn_stack *stack);
+
+struct misdn_stack* get_misdn_stack( void );
+
+static int set_chan_in_stack(struct misdn_stack *stack, int channel);
+
+int release_cr(struct misdn_stack *stack, mISDNuser_head_t *hh);
+
+int misdn_lib_port_is_pri(int port)
+{
+ struct misdn_stack *stack=get_misdn_stack();
+ for ( ; stack; stack=stack->next) {
+ if (stack->port == port) {
+ return stack->pri;
+ }
+ }
+
+ return -1;
+}
+
+int misdn_lib_port_is_nt(int port)
+{
+ struct misdn_stack *stack=get_misdn_stack();
+ for ( ; stack; stack=stack->next) {
+ if (stack->port == port) {
+ return stack->nt;
+ }
+ }
+
+ return -1;
+}
+
+void misdn_make_dummy(struct misdn_bchannel *dummybc, int port, int l3id, int nt, int channel)
+{
+ memset (dummybc,0,sizeof(struct misdn_bchannel));
+ dummybc->port=port;
+ if (l3id==0)
+ dummybc->l3_id = MISDN_ID_DUMMY;
+ else
+ dummybc->l3_id=l3id;
+
+ dummybc->nt=nt;
+ dummybc->dummy=1;
+ dummybc->channel=channel;
+}
+
+int misdn_lib_port_block(int port)
+{
+ struct misdn_stack *stack=get_misdn_stack();
+ for ( ; stack; stack=stack->next) {
+ if (stack->port == port) {
+ stack->blocked=1;
+ return 0;
+ }
+ }
+ return -1;
+
+}
+
+int misdn_lib_port_unblock(int port)
+{
+ struct misdn_stack *stack=get_misdn_stack();
+ for ( ; stack; stack=stack->next) {
+ if (stack->port == port) {
+ stack->blocked=0;
+ return 0;
+ }
+ }
+ return -1;
+
+}
+
+int misdn_lib_is_port_blocked(int port)
+{
+ struct misdn_stack *stack=get_misdn_stack();
+ for ( ; stack; stack=stack->next) {
+ if (stack->port == port) {
+ return stack->blocked;
+ }
+ }
+ return -1;
+}
+
+int misdn_lib_is_ptp(int port)
+{
+ struct misdn_stack *stack=get_misdn_stack();
+ for ( ; stack; stack=stack->next) {
+ if (stack->port == port) return stack->ptp;
+ }
+ return -1;
+}
+
+int misdn_lib_get_maxchans(int port)
+{
+ struct misdn_stack *stack=get_misdn_stack();
+ for ( ; stack; stack=stack->next) {
+ if (stack->port == port) {
+ if (stack->pri)
+ return 30;
+ else
+ return 2;
+ }
+ }
+ return -1;
+}
+
+
+struct misdn_stack* get_stack_by_bc(struct misdn_bchannel *bc)
+{
+ struct misdn_stack *stack=get_misdn_stack();
+
+ if (!bc) return NULL;
+
+ for ( ; stack; stack=stack->next) {
+ int i;
+ for (i=0; i <=stack->b_num; i++) {
+ if ( bc->port == stack->port) return stack;
+ }
+ }
+
+ return NULL;
+}
+
+
+void get_show_stack_details(int port, char *buf)
+{
+ struct misdn_stack *stack=get_misdn_stack();
+
+ for ( ; stack; stack=stack->next) {
+ if (stack->port == port) break;
+ }
+
+ if (stack) {
+ sprintf(buf, "* Port %d Type %s Prot. %s L2Link %s L1Link:%s Blocked:%d", stack->port, stack->nt?"NT":"TE", stack->ptp?"PTP":"PMP", stack->l2link?"UP":"DOWN", stack->l1link?"UP":"DOWN",stack->blocked);
+
+ } else {
+ buf[0]=0;
+ }
+
+}
+
+
+static int nt_err_cnt =0 ;
+
+enum global_states {
+ MISDN_INITIALIZING,
+ MISDN_INITIALIZED
+} ;
+
+static enum global_states global_state=MISDN_INITIALIZING;
+
+
+#include <mISDNuser/net_l2.h>
+#include <mISDNuser/tone.h>
+#include <unistd.h>
+#include <semaphore.h>
+#include <pthread.h>
+#include <signal.h>
+
+#include "isdn_lib.h"
+
+
+struct misdn_lib {
+ int midev;
+ int midev_nt;
+
+ pthread_t event_thread;
+ pthread_t event_handler_thread;
+
+ void *user_data;
+
+ msg_queue_t upqueue;
+ msg_queue_t activatequeue;
+
+ sem_t new_msg;
+
+ struct misdn_stack *stack_list;
+} ;
+
+#ifndef ECHOCAN_ON
+#define ECHOCAN_ON 123
+#define ECHOCAN_OFF 124
+#endif
+
+#define MISDN_DEBUG 0
+
+void misdn_tx_jitter(struct misdn_bchannel *bc, int len);
+
+struct misdn_bchannel *find_bc_by_l3id(struct misdn_stack *stack, unsigned long l3id);
+
+struct misdn_bchannel *find_bc_by_confid(unsigned long confid);
+
+struct misdn_bchannel *stack_holder_find_bychan(struct misdn_stack *stack, int chan);
+
+int setup_bc(struct misdn_bchannel *bc);
+
+int manager_isdn_handler(iframe_t *frm ,msg_t *msg);
+
+int misdn_lib_port_restart(int port);
+int misdn_lib_pid_restart(int pid);
+
+extern struct isdn_msg msgs_g[];
+
+#define ISDN_PID_L3_B_USER 0x430000ff
+#define ISDN_PID_L4_B_USER 0x440000ff
+
+/* #define MISDN_IBUF_SIZE 1024 */
+#define MISDN_IBUF_SIZE 512
+
+/* Fine Tuning of Inband Signalling time */
+#define TONE_ALERT_CNT 41 /* 1 Sec */
+#define TONE_ALERT_SILENCE_CNT 200 /* 4 Sec */
+
+#define TONE_BUSY_CNT 20 /* ? */
+#define TONE_BUSY_SILENCE_CNT 48 /* ? */
+
+static int entity;
+
+static struct misdn_lib *glob_mgr;
+
+char tone_425_flip[TONE_425_SIZE];
+char tone_silence_flip[TONE_SILENCE_SIZE];
+
+static void misdn_lib_isdn_event_catcher(void *arg);
+static int handle_event_nt(void *dat, void *arg);
+
+
+void stack_holder_add(struct misdn_stack *stack, struct misdn_bchannel *holder);
+void stack_holder_remove(struct misdn_stack *stack, struct misdn_bchannel *holder);
+struct misdn_bchannel *stack_holder_find(struct misdn_stack *stack, unsigned long l3id);
+
+/* from isdn_lib.h */
+int init_bc(struct misdn_stack * stack, struct misdn_bchannel *bc, int midev, int port, int bidx, char *msn, int firsttime);
+struct misdn_stack* stack_init(int midev, int port, int ptp);
+void stack_destroy(struct misdn_stack* stack);
+ /* user iface */
+int te_lib_init( void ) ; /* returns midev */
+void te_lib_destroy(int midev) ;
+struct misdn_bchannel *manager_find_bc_by_pid(int pid);
+struct misdn_bchannel *manager_find_bc_holded(struct misdn_bchannel* bc);
+void manager_ph_control_block(struct misdn_bchannel *bc, int c1, void *c2, int c2_len);
+void manager_clean_bc(struct misdn_bchannel *bc );
+void manager_bchannel_setup (struct misdn_bchannel *bc);
+void manager_bchannel_cleanup (struct misdn_bchannel *bc);
+
+void ec_chunk( struct misdn_bchannel *bc, unsigned char *rxchunk, unsigned char *txchunk, int chunk_size);
+ /* end */
+int bchdev_echocancel_activate(struct misdn_bchannel* dev);
+void bchdev_echocancel_deactivate(struct misdn_bchannel* dev);
+/* end */
+
+
+static char *bearer2str(int cap) {
+ static char *bearers[]={
+ "Speech",
+ "Audio 3.1k",
+ "Unres Digital",
+ "Res Digital",
+ "Unknown Bearer"
+ };
+
+ switch (cap) {
+ case INFO_CAPABILITY_SPEECH:
+ return bearers[0];
+ break;
+ case INFO_CAPABILITY_AUDIO_3_1K:
+ return bearers[1];
+ break;
+ case INFO_CAPABILITY_DIGITAL_UNRESTRICTED:
+ return bearers[2];
+ break;
+ case INFO_CAPABILITY_DIGITAL_RESTRICTED:
+ return bearers[3];
+ break;
+ default:
+ return bearers[4];
+ break;
+ }
+}
+
+
+static char flip_table[256];
+
+static void init_flip_bits(void)
+{
+ int i,k;
+
+ for (i = 0 ; i < 256 ; i++) {
+ unsigned char sample = 0 ;
+ for (k = 0; k<8; k++) {
+ if ( i & 1 << k ) sample |= 0x80 >> k;
+ }
+ flip_table[i] = sample;
+ }
+}
+
+static char * flip_buf_bits ( char * buf , int len)
+{
+ int i;
+ char * start = buf;
+
+ for (i = 0 ; i < len; i++) {
+ buf[i] = flip_table[(unsigned char)buf[i]];
+ }
+
+ return start;
+}
+
+
+
+
+static msg_t *create_l2msg(int prim, int dinfo, int size) /* NT only */
+{
+ int i = 0;
+ msg_t *dmsg;
+
+ while(i < 10)
+ {
+ dmsg = prep_l3data_msg(prim, dinfo, size, 256, NULL);
+ if (dmsg)
+ return(dmsg);
+
+ if (!i)
+ printf("cannot allocate memory, trying again...\n");
+ i++;
+ usleep(300000);
+ }
+ printf("cannot allocate memory, system overloaded.\n");
+ exit(-1);
+}
+
+
+
+msg_t *create_l3msg(int prim, int mt, int dinfo, int size, int ntmode)
+{
+ int i = 0;
+ msg_t *dmsg;
+ Q931_info_t *qi;
+ iframe_t *frm;
+
+ if (!ntmode)
+ size = sizeof(Q931_info_t)+2;
+
+ while(i < 10) {
+ if (ntmode) {
+ dmsg = prep_l3data_msg(prim, dinfo, size, 256, NULL);
+ if (dmsg) {
+ return(dmsg);
+ }
+ } else {
+ dmsg = alloc_msg(size+256+mISDN_HEADER_LEN+DEFAULT_HEADROOM);
+ if (dmsg)
+ {
+ memset(msg_put(dmsg,size+mISDN_HEADER_LEN), 0, size+mISDN_HEADER_LEN);
+ frm = (iframe_t *)dmsg->data;
+ frm->prim = prim;
+ frm->dinfo = dinfo;
+ qi = (Q931_info_t *)(dmsg->data + mISDN_HEADER_LEN);
+ qi->type = mt;
+ return(dmsg);
+ }
+ }
+
+ if (!i) printf("cannot allocate memory, trying again...\n");
+ i++;
+ usleep(300000);
+ }
+ printf("cannot allocate memory, system overloaded.\n");
+ exit(-1);
+}
+
+
+static int send_msg (int midev, struct misdn_bchannel *bc, msg_t *dmsg)
+{
+ iframe_t *frm = (iframe_t *)dmsg->data;
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+
+ if (!stack) {
+ cb_log(0,bc->port,"send_msg: IEK!! no stack\n ");
+ return -1;
+ }
+
+ frm->addr = (stack->upper_id | FLG_MSG_DOWN);
+ frm->dinfo = bc->l3_id;
+ frm->len = (dmsg->len) - mISDN_HEADER_LEN;
+
+ cb_log(4,stack->port,"Sending msg, prim:%x addr:%x dinfo:%x\n",frm->prim,frm->addr,frm->dinfo);
+
+ mISDN_write(midev, dmsg->data, dmsg->len, TIMEOUT_1SEC);
+ free_msg(dmsg);
+
+ return 0;
+}
+
+
+static int mypid=1;
+
+
+int misdn_cap_is_speech(int cap)
+/** Poor mans version **/
+{
+ if ( (cap != INFO_CAPABILITY_DIGITAL_UNRESTRICTED) &&
+ (cap != INFO_CAPABILITY_DIGITAL_RESTRICTED) ) return 1;
+ return 0;
+}
+
+int misdn_inband_avail(struct misdn_bchannel *bc)
+{
+
+ /*if ! early_bconnect we have never inband available*/
+ if ( ! bc->early_bconnect ) return 0;
+
+ switch (bc->progress_indicator) {
+ case INFO_PI_INBAND_AVAILABLE:
+ case INFO_PI_CALL_NOT_E2E_ISDN:
+ case INFO_PI_CALLED_NOT_ISDN:
+ return 1;
+ default:
+ return 0;
+ }
+ return 0;
+}
+
+
+static void dump_chan_list(struct misdn_stack *stack)
+{
+ int i;
+
+ for (i=0; i <= stack->b_num; i++) {
+ cb_log(6, stack->port, "Idx:%d stack->cchan:%d in_use:%d Chan:%d\n",i,stack->channels[i], stack->bc[i].in_use, i+1);
+ }
+}
+
+
+void misdn_dump_chanlist()
+{
+ struct misdn_stack *stack=get_misdn_stack();
+ for ( ; stack; stack=stack->next) {
+ dump_chan_list(stack);
+ }
+
+}
+
+int set_chan_in_stack(struct misdn_stack *stack, int channel)
+{
+
+ cb_log(4,stack->port,"set_chan_in_stack: %d\n",channel);
+ dump_chan_list(stack);
+ if (channel >=1 && channel <= MAX_BCHANS) {
+ if (!stack->channels[channel-1])
+ stack->channels[channel-1] = 1;
+ else {
+ cb_log(4,stack->port,"channel already in use:%d\n", channel );
+ return -1;
+ }
+ } else {
+ cb_log(0,stack->port,"couldn't set channel %d in\n", channel );
+ return -1;
+ }
+
+ return 0;
+}
+
+
+
+static int find_free_chan_in_stack(struct misdn_stack *stack, struct misdn_bchannel *bc, int channel, int dec)
+{
+ int i;
+ int chan=0;
+ int bnums = stack->pri ? stack->b_num : stack->b_num - 1;
+
+ if (bc->channel_found)
+ return 0;
+
+ bc->channel_found=1;
+
+ cb_log(5,stack->port,"find_free_chan: req_chan:%d\n",channel);
+
+ if (channel < 0 || channel > MAX_BCHANS) {
+ cb_log(0, stack->port, " !! out of bound call to find_free_chan_in_stack! (ch:%d)\n", channel);
+ return 0;
+ }
+
+ channel--;
+
+ if (dec) {
+ for (i = bnums; i >=0; i--) {
+ if (i != 15 && (channel < 0 || i == channel)) { /* skip E1 Dchannel ;) and work with chan preselection */
+ if (!stack->channels[i]) {
+ cb_log (3, stack->port, " --> found chan%s: %d\n", channel>=0?" (preselected)":"", i+1);
+ chan=i+1;
+ break;
+ }
+ }
+ }
+ } else {
+ for (i = 0; i <= bnums; i++) {
+ if (i != 15 && (channel < 0 || i == channel)) { /* skip E1 Dchannel ;) and work with chan preselection */
+ if (!stack->channels[i]) {
+ cb_log (3, stack->port, " --> found chan%s: %d\n", channel>=0?" (preselected)":"", i+1);
+ chan=i+1;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!chan) {
+ cb_log (1, stack->port, " !! NO FREE CHAN IN STACK\n");
+ dump_chan_list(stack);
+ bc->out_cause=34;
+ return -1;
+ }
+
+ if (set_chan_in_stack(stack, chan)<0) {
+ cb_log (0, stack->port, "Channel Already in use:%d\n", chan);
+ bc->out_cause=44;
+ return -1;
+ }
+
+ bc->channel=chan;
+ return 0;
+}
+
+static int empty_chan_in_stack(struct misdn_stack *stack, int channel)
+{
+ if (channel<=0 || channel>MAX_BCHANS) {
+ cb_log(0,stack?stack->port:0, "empty_chan_in_stack: cannot empty channel %d\n",channel);
+ return -1;
+ }
+
+ cb_log (4, stack?stack->port:0, "empty_chan_in_stack: %d\n",channel);
+ stack->channels[channel-1] = 0;
+ dump_chan_list(stack);
+ return 0;
+}
+
+char *bc_state2str(enum bchannel_state state) {
+ int i;
+
+ struct bchan_state_s {
+ char *n;
+ enum bchannel_state s;
+ } states[] = {
+ {"BCHAN_CLEANED", BCHAN_CLEANED },
+ {"BCHAN_EMPTY", BCHAN_EMPTY},
+ {"BCHAN_SETUP", BCHAN_SETUP},
+ {"BCHAN_SETUPED", BCHAN_SETUPED},
+ {"BCHAN_ACTIVE", BCHAN_ACTIVE},
+ {"BCHAN_ACTIVATED", BCHAN_ACTIVATED},
+ {"BCHAN_BRIDGE", BCHAN_BRIDGE},
+ {"BCHAN_BRIDGED", BCHAN_BRIDGED},
+ {"BCHAN_RELEASE", BCHAN_RELEASE},
+ {"BCHAN_RELEASED", BCHAN_RELEASED},
+ {"BCHAN_CLEAN", BCHAN_CLEAN},
+ {"BCHAN_CLEAN_REQUEST", BCHAN_CLEAN_REQUEST},
+ {"BCHAN_ERROR", BCHAN_ERROR}
+ };
+
+ for (i=0; i< sizeof(states)/sizeof(struct bchan_state_s); i++)
+ if ( states[i].s == state)
+ return states[i].n;
+
+ return "UNKNOWN";
+}
+
+void bc_state_change(struct misdn_bchannel *bc, enum bchannel_state state)
+{
+ cb_log(5,bc->port,"BC_STATE_CHANGE: l3id:%x from:%s to:%s\n",
+ bc->l3_id,
+ bc_state2str(bc->bc_state),
+ bc_state2str(state) );
+
+ switch (state) {
+ case BCHAN_ACTIVATED:
+ if (bc->next_bc_state == BCHAN_BRIDGED) {
+ misdn_join_conf(bc, bc->conf_id);
+ bc->next_bc_state = BCHAN_EMPTY;
+ return;
+ }
+ default:
+ bc->bc_state=state;
+ break;
+ }
+}
+
+static void bc_next_state_change(struct misdn_bchannel *bc, enum bchannel_state state)
+{
+ cb_log(5,bc->port,"BC_NEXT_STATE_CHANGE: from:%s to:%s\n",
+ bc_state2str(bc->next_bc_state),
+ bc_state2str(state) );
+
+ bc->next_bc_state=state;
+}
+
+
+static void empty_bc(struct misdn_bchannel *bc)
+{
+ bc->dummy=0;
+
+ bc->bframe_len=0;
+
+ bc->cw= 0;
+
+ bc->dec=0;
+ bc->channel = 0;
+
+ bc->sending_complete = 0;
+
+ bc->restart_channel=0;
+
+ bc->conf_id = 0;
+
+ bc->need_more_infos = 0;
+
+ bc->send_dtmf=0;
+ bc->nodsp=0;
+ bc->nojitter=0;
+
+ bc->time_usec=0;
+
+ bc->rxgain=0;
+ bc->txgain=0;
+
+ bc->crypt=0;
+ bc->curptx=0; bc->curprx=0;
+
+ bc->crypt_key[0] = 0;
+
+ bc->generate_tone=0;
+ bc->tone_cnt=0;
+
+ bc->dnumplan=NUMPLAN_UNKNOWN;
+ bc->onumplan=NUMPLAN_UNKNOWN;
+ bc->rnumplan=NUMPLAN_UNKNOWN;
+ bc->cpnnumplan=NUMPLAN_UNKNOWN;
+
+
+ bc->active = 0;
+
+ bc->early_bconnect = 1;
+
+#ifdef MISDN_1_2
+ *bc->pipeline = 0;
+#else
+ bc->ec_enable = 0;
+ bc->ec_deftaps = 128;
+#endif
+
+ bc->AOCD_need_export = 0;
+
+ bc->orig=0;
+
+ bc->cause=16;
+ bc->out_cause=16;
+ bc->pres=0 ; /* screened */
+
+ bc->evq=EVENT_NOTHING;
+
+ bc->progress_coding=0;
+ bc->progress_location=0;
+ bc->progress_indicator=0;
+
+/** Set Default Bearer Caps **/
+ bc->capability=INFO_CAPABILITY_SPEECH;
+ bc->law=INFO_CODEC_ALAW;
+ bc->mode=0;
+ bc->rate=0x10;
+ bc->user1=0;
+ bc->urate=0;
+
+ bc->hdlc=0;
+
+
+ bc->info_dad[0] = 0;
+ bc->display[0] = 0;
+ bc->infos_pending[0] = 0;
+ bc->cad[0] = 0;
+ bc->oad[0] = 0;
+ bc->dad[0] = 0;
+ bc->rad[0] = 0;
+ bc->orig_dad[0] = 0;
+ bc->uu[0]=0;
+ bc->uulen=0;
+
+ bc->fac_in.Function = Fac_None;
+ bc->fac_out.Function = Fac_None;
+
+ bc->te_choose_channel = 0;
+ bc->channel_found= 0;
+}
+
+
+static int clean_up_bc(struct misdn_bchannel *bc)
+{
+ int ret=0;
+ unsigned char buff[32];
+ struct misdn_stack * stack;
+
+ cb_log(3, bc?bc->port:0, "$$$ CLEANUP CALLED pid:%d\n", bc?bc->pid:-1);
+
+ if (!bc ) return -1;
+ stack=get_stack_by_bc(bc);
+
+ if (!stack) return -1;
+
+ switch (bc->bc_state ) {
+ case BCHAN_CLEANED:
+ cb_log(5, stack->port, "$$$ Already cleaned up bc with stid :%x\n", bc->b_stid);
+ return -1;
+
+ default:
+ break;
+ }
+
+ cb_log(2, stack->port, "$$$ Cleaning up bc with stid :%x pid:%d\n", bc->b_stid, bc->pid);
+
+ manager_ec_disable(bc);
+
+ manager_bchannel_deactivate(bc);
+
+ mISDN_write_frame(stack->midev, buff, bc->layer_id|FLG_MSG_TARGET|FLG_MSG_DOWN, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
+
+ bc->b_stid = 0;
+ bc_state_change(bc, BCHAN_CLEANED);
+
+ return ret;
+}
+
+
+
+static void clear_l3(struct misdn_stack *stack)
+{
+ int i;
+
+ for (i=0; i<=stack->b_num; i++) {
+ if (global_state == MISDN_INITIALIZED) {
+ cb_event(EVENT_CLEANUP, &stack->bc[i], NULL);
+ empty_chan_in_stack(stack,i+1);
+ empty_bc(&stack->bc[i]);
+ clean_up_bc(&stack->bc[i]);
+ stack->bc[i].in_use = 0;
+ }
+
+ }
+}
+
+static int newteid=0;
+
+#define MAXPROCS 0x100
+
+static int misdn_lib_get_l1_down(struct misdn_stack *stack)
+{
+ /* Pull Up L1 */
+ iframe_t act;
+ act.prim = PH_DEACTIVATE | REQUEST;
+ act.addr = stack->lower_id|FLG_MSG_DOWN;
+ act.dinfo = 0;
+ act.len = 0;
+
+ cb_log(1, stack->port, "SENDING PH_DEACTIVATE | REQ\n");
+ return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
+}
+
+
+static int misdn_lib_get_l2_down(struct misdn_stack *stack)
+{
+
+ if (stack->ptp && (stack->nt) ) {
+ msg_t *dmsg;
+ /* L2 */
+ dmsg = create_l2msg(DL_RELEASE| REQUEST, 0, 0);
+
+ if (stack->nst.manager_l3(&stack->nst, dmsg))
+ free_msg(dmsg);
+
+ } else {
+ iframe_t act;
+
+ act.prim = DL_RELEASE| REQUEST;
+ act.addr = (stack->upper_id |FLG_MSG_DOWN) ;
+
+ act.dinfo = 0;
+ act.len = 0;
+ return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
+ }
+
+ return 0;
+}
+
+
+static int misdn_lib_get_l1_up(struct misdn_stack *stack)
+{
+ /* Pull Up L1 */
+ iframe_t act;
+ act.prim = PH_ACTIVATE | REQUEST;
+ act.addr = (stack->upper_id | FLG_MSG_DOWN) ;
+
+
+ act.dinfo = 0;
+ act.len = 0;
+
+ return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
+
+}
+
+int misdn_lib_get_l2_up(struct misdn_stack *stack)
+{
+
+ if (stack->ptp && (stack->nt) ) {
+ msg_t *dmsg;
+ /* L2 */
+ dmsg = create_l2msg(DL_ESTABLISH | REQUEST, 0, 0);
+
+ if (stack->nst.manager_l3(&stack->nst, dmsg))
+ free_msg(dmsg);
+
+ } else {
+ iframe_t act;
+
+ act.prim = DL_ESTABLISH | REQUEST;
+ act.addr = (stack->upper_id |FLG_MSG_DOWN) ;
+
+ act.dinfo = 0;
+ act.len = 0;
+ return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
+ }
+
+ return 0;
+}
+
+#if 0
+static int misdn_lib_get_l2_te_ptp_up(struct misdn_stack *stack)
+{
+ iframe_t act;
+
+ act.prim = DL_ESTABLISH | REQUEST;
+ act.addr = (stack->upper_id & ~LAYER_ID_MASK) | 3 | FLG_MSG_DOWN;
+
+ act.dinfo = 0;
+ act.len = 0;
+ return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
+ return 0;
+}
+#endif
+
+static int misdn_lib_get_short_status(struct misdn_stack *stack)
+{
+ iframe_t act;
+
+
+ act.prim = MGR_SHORTSTATUS | REQUEST;
+
+ act.addr = (stack->upper_id | MSG_BROADCAST) ;
+
+ act.dinfo = SSTATUS_BROADCAST_BIT | SSTATUS_ALL;
+
+ act.len = 0;
+ return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
+}
+
+
+
+static int create_process (int midev, struct misdn_bchannel *bc) {
+ iframe_t ncr;
+ int l3_id;
+ int i;
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+
+ if (stack->nt) {
+ if (find_free_chan_in_stack(stack, bc, bc->channel_preselected?bc->channel:0, 0)<0) return -1;
+ cb_log(4,stack->port, " --> found channel: %d\n",bc->channel);
+
+ for (i=0; i <= MAXPROCS; i++)
+ if (stack->procids[i]==0) break;
+
+ if (i== MAXPROCS) {
+ cb_log(0, stack->port, "Couldnt Create New ProcId.\n");
+ return -1;
+ }
+ stack->procids[i]=1;
+
+ l3_id = 0xff00 | i;
+
+ ncr.prim = CC_NEW_CR | REQUEST;
+
+ ncr.addr = (stack->upper_id | FLG_MSG_DOWN) ;
+
+ ncr.dinfo = l3_id;
+ ncr.len = 0;
+
+ bc->l3_id = l3_id;
+ cb_log(3, stack->port, " --> new_l3id %x\n",l3_id);
+
+ } else {
+ if (stack->ptp || bc->te_choose_channel) {
+ /* we know exactly which channels are in use */
+ if (find_free_chan_in_stack(stack, bc, bc->channel_preselected?bc->channel:0, bc->dec)<0) return -1;
+ cb_log(2,stack->port, " --> found channel: %d\n",bc->channel);
+ } else {
+ /* other phones could have made a call also on this port (ptmp) */
+ bc->channel=0xff;
+ }
+
+
+ /* if we are in te-mode, we need to create a process first */
+ if (newteid++ > 0xffff)
+ newteid = 0x0001;
+
+ l3_id = (entity<<16) | newteid;
+ /* preparing message */
+ ncr.prim = CC_NEW_CR | REQUEST;
+
+ ncr.addr = (stack->upper_id | FLG_MSG_DOWN) ;
+
+ ncr.dinfo =l3_id;
+ ncr.len = 0;
+ /* send message */
+
+ bc->l3_id = l3_id;
+ cb_log(3, stack->port, "--> new_l3id %x\n",l3_id);
+
+ mISDN_write(midev, &ncr, mISDN_HEADER_LEN+ncr.len, TIMEOUT_1SEC);
+ }
+
+ return l3_id;
+}
+
+
+void misdn_lib_setup_bc(struct misdn_bchannel *bc)
+{
+ clean_up_bc(bc);
+ setup_bc(bc);
+}
+
+
+int setup_bc(struct misdn_bchannel *bc)
+{
+ unsigned char buff[1025];
+ int midev, channel, b_stid, i;
+ mISDN_pid_t pid;
+ int ret;
+
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+
+ if (!stack) {
+ cb_log(0, bc->port, "setup_bc: NO STACK FOUND!!\n");
+ return -1;
+ }
+
+ midev = stack->midev;
+ channel = bc->channel - 1 - (bc->channel > 16);
+ b_stid = stack->b_stids[channel >= 0 ? channel : 0];
+
+ switch (bc->bc_state) {
+ case BCHAN_CLEANED:
+ break;
+ default:
+ cb_log(4, stack->port, "$$$ bc already upsetted stid :%x (state:%s)\n", b_stid, bc_state2str(bc->bc_state) );
+ return -1;
+ }
+
+ cb_log(5, stack->port, "$$$ Setting up bc with stid :%x\n", b_stid);
+
+ /*check if the b_stid is alread initialized*/
+ for (i=0; i <= stack->b_num; i++) {
+ if (stack->bc[i].b_stid == b_stid) {
+ cb_log(0, bc->port, "setup_bc: b_stid:%x already in use !!!\n", b_stid);
+ return -1;
+ }
+ }
+
+ if (b_stid <= 0) {
+ cb_log(0, stack->port," -- Stid <=0 at the moment in channel:%d\n",channel);
+
+ bc_state_change(bc,BCHAN_ERROR);
+ return 1;
+ }
+
+ bc->b_stid = b_stid;
+
+ {
+ layer_info_t li;
+ memset(&li, 0, sizeof(li));
+
+ li.object_id = -1;
+ li.extentions = 0;
+
+ li.st = bc->b_stid; /* given idx */
+
+
+#define MISDN_DSP
+#ifndef MISDN_DSP
+ bc->nodsp=1;
+#endif
+ if ( bc->hdlc || bc->nodsp) {
+ cb_log(4, stack->port,"setup_bc: without dsp\n");
+ {
+ int l = sizeof(li.name);
+ strncpy(li.name, "B L3", l);
+ li.name[l-1] = 0;
+ }
+ li.pid.layermask = ISDN_LAYER((3));
+ li.pid.protocol[3] = ISDN_PID_L3_B_USER;
+
+ bc->layer=3;
+ } else {
+ cb_log(4, stack->port,"setup_bc: with dsp\n");
+ {
+ int l = sizeof(li.name);
+ strncpy(li.name, "B L4", l);
+ li.name[l-1] = 0;
+ }
+ li.pid.layermask = ISDN_LAYER((4));
+ li.pid.protocol[4] = ISDN_PID_L4_B_USER
+;
+ bc->layer=4;
+
+ }
+
+ ret = mISDN_new_layer(midev, &li);
+ if (ret ) {
+ cb_log(0, stack->port,"New Layer Err: %d %s\n",ret,strerror(errno));
+
+ bc_state_change(bc,BCHAN_ERROR);
+ return(-EINVAL);
+ }
+
+ bc->layer_id = li.id;
+ }
+
+ memset(&pid, 0, sizeof(pid));
+
+
+
+ cb_log(4, stack->port," --> Channel is %d\n", bc->channel);
+
+ if (bc->nodsp) {
+ cb_log(2, stack->port," --> TRANSPARENT Mode (no DSP, no HDLC)\n");
+ pid.protocol[1] = ISDN_PID_L1_B_64TRANS;
+ pid.protocol[2] = ISDN_PID_L2_B_TRANS;
+ pid.protocol[3] = ISDN_PID_L3_B_USER;
+ pid.layermask = ISDN_LAYER((1)) | ISDN_LAYER((2)) | ISDN_LAYER((3));
+
+ } else if ( bc->hdlc ) {
+ cb_log(2, stack->port," --> HDLC Mode\n");
+ pid.protocol[1] = ISDN_PID_L1_B_64HDLC ;
+ pid.protocol[2] = ISDN_PID_L2_B_TRANS ;
+ pid.protocol[3] = ISDN_PID_L3_B_USER;
+ pid.layermask = ISDN_LAYER((1)) | ISDN_LAYER((2)) | ISDN_LAYER((3)) ;
+ } else {
+ cb_log(2, stack->port," --> TRANSPARENT Mode\n");
+ pid.protocol[1] = ISDN_PID_L1_B_64TRANS;
+ pid.protocol[2] = ISDN_PID_L2_B_TRANS;
+ pid.protocol[3] = ISDN_PID_L3_B_DSP;
+ pid.protocol[4] = ISDN_PID_L4_B_USER;
+ pid.layermask = ISDN_LAYER((1)) | ISDN_LAYER((2)) | ISDN_LAYER((3)) | ISDN_LAYER((4));
+
+ }
+
+ ret = mISDN_set_stack(midev, bc->b_stid, &pid);
+
+ if (ret){
+ cb_log(0, stack->port,"$$$ Set Stack Err: %d %s\n",ret,strerror(errno));
+
+ mISDN_write_frame(midev, buff, bc->layer_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
+
+ bc_state_change(bc,BCHAN_ERROR);
+ cb_event(EVENT_BCHAN_ERROR, bc, glob_mgr->user_data);
+ return(-EINVAL);
+ }
+
+ ret = mISDN_get_setstack_ind(midev, bc->layer_id);
+
+ if (ret) {
+ cb_log(0, stack->port,"$$$ Set StackIND Err: %d %s\n",ret,strerror(errno));
+ mISDN_write_frame(midev, buff, bc->layer_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
+
+ bc_state_change(bc,BCHAN_ERROR);
+ cb_event(EVENT_BCHAN_ERROR, bc, glob_mgr->user_data);
+ return(-EINVAL);
+ }
+
+ ret = mISDN_get_layerid(midev, bc->b_stid, bc->layer) ;
+
+ bc->addr = ret>0? ret : 0;
+
+ if (!bc->addr) {
+ cb_log(0, stack->port,"$$$ Get Layerid Err: %d %s\n",ret,strerror(errno));
+ mISDN_write_frame(midev, buff, bc->layer_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
+
+ bc_state_change(bc,BCHAN_ERROR);
+ cb_event(EVENT_BCHAN_ERROR, bc, glob_mgr->user_data);
+ return (-EINVAL);
+ }
+
+ manager_bchannel_activate(bc);
+
+ bc_state_change(bc,BCHAN_ACTIVATED);
+
+ return 0;
+}
+
+
+
+/** IFACE **/
+int init_bc(struct misdn_stack *stack, struct misdn_bchannel *bc, int midev, int port, int bidx, char *msn, int firsttime)
+{
+ unsigned char buff[1025] = "";
+ iframe_t *frm = (iframe_t *)buff;
+ int ret;
+
+ if (!bc) return -1;
+
+ cb_log(8, port, "Init.BC %d.\n",bidx);
+
+ memset(bc, 0,sizeof(struct misdn_bchannel));
+
+ bc->send_lock=malloc(sizeof(struct send_lock));
+
+ pthread_mutex_init(&bc->send_lock->lock, NULL);
+
+ if (msn) {
+ int l = sizeof(bc->msn);
+ strncpy(bc->msn,msn, l);
+ bc->msn[l-1] = 0;
+ }
+
+
+ empty_bc(bc);
+ bc_state_change(bc, BCHAN_CLEANED);
+
+ bc->port=stack->port;
+ bc->nt=stack->nt?1:0;
+ bc->pri=stack->pri;
+
+ {
+ ibuffer_t* ibuf= init_ibuffer(MISDN_IBUF_SIZE);
+
+ if (!ibuf) return -1;
+
+ clear_ibuffer( ibuf);
+
+ ibuf->rsem=malloc(sizeof(sem_t));
+
+ bc->astbuf=ibuf;
+
+ if (sem_init(ibuf->rsem,1,0)<0)
+ sem_init(ibuf->rsem,0,0);
+
+ }
+
+ {
+ stack_info_t *stinf;
+ ret = mISDN_get_stack_info(midev, stack->port, buff, sizeof(buff));
+ if (ret < 0) {
+ cb_log(0, port, "%s: Cannot get stack info for this port. (ret=%d)\n", __FUNCTION__, ret);
+ return -1;
+ }
+
+ stinf = (stack_info_t *)&frm->data.p;
+
+ cb_log(8, port, " --> Child %x\n",stinf->child[bidx]);
+ }
+
+ return 0;
+}
+
+
+
+struct misdn_stack* stack_init( int midev, int port, int ptp )
+{
+ int ret;
+ unsigned char buff[1025];
+ iframe_t *frm = (iframe_t *)buff;
+ stack_info_t *stinf;
+ int i;
+ layer_info_t li;
+
+ struct misdn_stack *stack = malloc(sizeof(struct misdn_stack));
+ if (!stack ) return NULL;
+
+
+ cb_log(8, port, "Init. Stack.\n");
+
+ memset(stack,0,sizeof(struct misdn_stack));
+
+ for (i=0; i<MAX_BCHANS + 1; i++ ) stack->channels[i]=0;
+
+ stack->port=port;
+ stack->midev=midev;
+ stack->ptp=ptp;
+
+ stack->holding=NULL;
+ stack->pri=0;
+
+ msg_queue_init(&stack->downqueue);
+ msg_queue_init(&stack->upqueue);
+
+ /* query port's requirements */
+ ret = mISDN_get_stack_info(midev, port, buff, sizeof(buff));
+ if (ret < 0) {
+ cb_log(0, port, "%s: Cannot get stack info for this port. (ret=%d)\n", __FUNCTION__, ret);
+ return(NULL);
+ }
+
+ stinf = (stack_info_t *)&frm->data.p;
+
+ stack->d_stid = stinf->id;
+ stack->b_num = stinf->childcnt;
+
+ for (i=0; i<=stinf->childcnt; i++)
+ stack->b_stids[i] = stinf->child[i];
+
+ switch(stinf->pid.protocol[0] & ~ISDN_PID_FEATURE_MASK) {
+ case ISDN_PID_L0_TE_S0:
+ stack->nt=0;
+ break;
+ case ISDN_PID_L0_NT_S0:
+ cb_log(8, port, "NT Stack\n");
+
+ stack->nt=1;
+ break;
+ case ISDN_PID_L0_TE_E1:
+ cb_log(8, port, "TE S2M Stack\n");
+ stack->nt=0;
+ stack->pri=1;
+ break;
+ case ISDN_PID_L0_NT_E1:
+ cb_log(8, port, "TE S2M Stack\n");
+ stack->nt=1;
+ stack->pri=1;
+
+ break;
+ default:
+ cb_log(0, port, "this is a unknown port type 0x%08x\n", stinf->pid.protocol[0]);
+
+ }
+
+ if (!stack->nt) {
+ if (stinf->pid.protocol[2] & ISDN_PID_L2_DF_PTP ) {
+ stack->ptp = 1;
+ } else {
+ stack->ptp = 0;
+ }
+ }
+
+ {
+ int ret;
+ int nt=stack->nt;
+
+ cb_log(8, port, "Init. Stack.\n");
+
+ memset(&li, 0, sizeof(li));
+ {
+ int l = sizeof(li.name);
+ strncpy(li.name,nt?"net l2":"user l4", l);
+ li.name[l-1] = 0;
+ }
+ li.object_id = -1;
+ li.extentions = 0;
+ li.pid.protocol[nt?2:4] = nt?ISDN_PID_L2_LAPD_NET:ISDN_PID_L4_CAPI20;
+ li.pid.layermask = ISDN_LAYER((nt?2:4));
+ li.st = stack->d_stid;
+
+
+ ret = mISDN_new_layer(midev, &li);
+ if (ret) {
+ cb_log(0, port, "%s: Cannot add layer %d to this port.\n", __FUNCTION__, nt?2:4);
+ return(NULL);
+ }
+
+
+ stack->upper_id = li.id;
+ ret = mISDN_register_layer(midev, stack->d_stid, stack->upper_id);
+ if (ret)
+ {
+ cb_log(0,port,"Cannot register layer %d of this port.\n", nt?2:4);
+ return(NULL);
+ }
+
+ stack->lower_id = mISDN_get_layerid(midev, stack->d_stid, nt?1:3);
+ if (stack->lower_id < 0) {
+ cb_log(0, port, "%s: Cannot get layer(%d) id of this port.\n", __FUNCTION__, nt?1:3);
+ return(NULL);
+ }
+
+ stack->upper_id = mISDN_get_layerid(midev, stack->d_stid, nt?2:4);
+ if (stack->upper_id < 0) {
+ cb_log(0, port, "%s: Cannot get layer(%d) id of this port.\n", __FUNCTION__, 2);
+ return(NULL);
+ }
+
+ cb_log(8, port, "NT Stacks upper_id %x\n",stack->upper_id);
+
+
+ /* create nst (nt-mode only) */
+ if (nt) {
+
+ memset(&stack->nst, 0, sizeof(net_stack_t));
+ memset(&stack->mgr, 0, sizeof(manager_t));
+
+ stack->mgr.nst = &stack->nst;
+ stack->nst.manager = &stack->mgr;
+
+ stack->nst.l3_manager = handle_event_nt;
+ stack->nst.device = midev;
+ stack->nst.cardnr = port;
+ stack->nst.d_stid = stack->d_stid;
+
+ stack->nst.feature = FEATURE_NET_HOLD;
+ if (stack->ptp)
+ stack->nst.feature |= FEATURE_NET_PTP;
+ if (stack->pri)
+ stack->nst.feature |= FEATURE_NET_CRLEN2 | FEATURE_NET_EXTCID;
+
+ stack->nst.l1_id = stack->lower_id;
+ stack->nst.l2_id = stack->upper_id;
+
+ msg_queue_init(&stack->nst.down_queue);
+
+ Isdnl2Init(&stack->nst);
+ Isdnl3Init(&stack->nst);
+
+ }
+
+ if (!stack->nt) {
+ /*assume L1 is up, we'll get DEACTIVATES soon, for non
+ * up L1s*/
+ stack->l1link=0;
+ }
+ stack->l1link=0;
+ stack->l2link=0;
+#if 0
+ if (!stack->nt) {
+ misdn_lib_get_short_status(stack);
+ } else {
+ misdn_lib_get_l1_up(stack);
+ if (!stack->ptp) misdn_lib_get_l1_up(stack);
+ misdn_lib_get_l2_up(stack);
+ }
+#endif
+
+ misdn_lib_get_short_status(stack);
+ misdn_lib_get_l1_up(stack);
+ misdn_lib_get_l2_up(stack);
+
+ }
+
+ cb_log(8,0,"stack_init: port:%d lowerId:%x upperId:%x\n",stack->port,stack->lower_id, stack->upper_id);
+
+ return stack;
+}
+
+
+void stack_destroy(struct misdn_stack* stack)
+{
+ char buf[1024];
+ if (!stack) return;
+
+ if (stack->nt) {
+ cleanup_Isdnl2(&stack->nst);
+ cleanup_Isdnl3(&stack->nst);
+ }
+
+ if (stack->lower_id)
+ mISDN_write_frame(stack->midev, buf, stack->lower_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
+
+ if (stack->upper_id)
+ mISDN_write_frame(stack->midev, buf, stack->upper_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
+}
+
+
+static struct misdn_stack * find_stack_by_addr(int addr)
+{
+ struct misdn_stack *stack;
+
+ for (stack=glob_mgr->stack_list;
+ stack;
+ stack=stack->next) {
+ if ( (stack->upper_id&STACK_ID_MASK) == (addr&STACK_ID_MASK)) return stack;
+
+ }
+
+ return NULL;
+}
+
+
+static struct misdn_stack * find_stack_by_port(int port)
+{
+ struct misdn_stack *stack;
+
+ for (stack=glob_mgr->stack_list;
+ stack;
+ stack=stack->next)
+ if (stack->port == port) return stack;
+
+ return NULL;
+}
+
+static struct misdn_stack * find_stack_by_mgr(manager_t* mgr_nt)
+{
+ struct misdn_stack *stack;
+
+ for (stack=glob_mgr->stack_list;
+ stack;
+ stack=stack->next)
+ if ( &stack->mgr == mgr_nt) return stack;
+
+ return NULL;
+}
+
+static struct misdn_bchannel *find_bc_by_masked_l3id(struct misdn_stack *stack, unsigned long l3id, unsigned long mask)
+{
+ int i;
+ for (i=0; i<=stack->b_num; i++) {
+ if ( (stack->bc[i].l3_id & mask) == (l3id & mask)) return &stack->bc[i] ;
+ }
+ return stack_holder_find(stack,l3id);
+}
+
+
+struct misdn_bchannel *find_bc_by_l3id(struct misdn_stack *stack, unsigned long l3id)
+{
+ int i;
+ for (i=0; i<=stack->b_num; i++) {
+ if (stack->bc[i].l3_id == l3id) return &stack->bc[i] ;
+ }
+ return stack_holder_find(stack,l3id);
+}
+
+static struct misdn_bchannel *find_bc_holded(struct misdn_stack *stack)
+{
+ int i;
+ for (i=0; i<=stack->b_num; i++) {
+ if (stack->bc[i].holded ) return &stack->bc[i] ;
+ }
+ return NULL;
+}
+
+
+static struct misdn_bchannel *find_bc_by_addr(unsigned long addr)
+{
+ struct misdn_stack* stack;
+ int i;
+
+ for (stack=glob_mgr->stack_list;
+ stack;
+ stack=stack->next) {
+ for (i=0; i<=stack->b_num; i++) {
+ if ( (stack->bc[i].addr&STACK_ID_MASK)==(addr&STACK_ID_MASK) || stack->bc[i].layer_id== addr ) {
+ return &stack->bc[i];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+struct misdn_bchannel *find_bc_by_confid(unsigned long confid)
+{
+ struct misdn_stack* stack;
+ int i;
+
+ for (stack=glob_mgr->stack_list;
+ stack;
+ stack=stack->next) {
+ for (i=0; i<=stack->b_num; i++) {
+ if ( stack->bc[i].conf_id==confid ) {
+ return &stack->bc[i];
+ }
+ }
+ }
+ return NULL;
+}
+
+
+static struct misdn_bchannel *find_bc_by_channel(int port, int channel)
+{
+ struct misdn_stack* stack=find_stack_by_port(port);
+ int i;
+
+ if (!stack) return NULL;
+
+ for (i=0; i<=stack->b_num; i++) {
+ if ( stack->bc[i].channel== channel ) {
+ return &stack->bc[i];
+ }
+ }
+
+ return NULL;
+}
+
+
+
+
+
+static int handle_event ( struct misdn_bchannel *bc, enum event_e event, iframe_t *frm)
+{
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+
+ if (!stack->nt) {
+
+ switch (event) {
+
+ case EVENT_CONNECT_ACKNOWLEDGE:
+ setup_bc(bc);
+
+ if ( *bc->crypt_key ) {
+ cb_log(4, stack->port, "ENABLING BLOWFISH channel:%d oad%d:%s dad%d:%s\n", bc->channel, bc->onumplan,bc->oad, bc->dnumplan,bc->dad);
+ manager_ph_control_block(bc, BF_ENABLE_KEY, bc->crypt_key, strlen(bc->crypt_key) );
+ }
+
+ if (misdn_cap_is_speech(bc->capability)) {
+ if ( !bc->nodsp) manager_ph_control(bc, DTMF_TONE_START, 0);
+ manager_ec_enable(bc);
+
+ if ( bc->txgain != 0 ) {
+ cb_log(4, stack->port, "--> Changing txgain to %d\n", bc->txgain);
+ manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain);
+ }
+ if ( bc->rxgain != 0 ) {
+ cb_log(4, stack->port, "--> Changing rxgain to %d\n", bc->rxgain);
+ manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain);
+ }
+ }
+
+ break;
+ case EVENT_CONNECT:
+
+ if ( *bc->crypt_key ) {
+ cb_log(4, stack->port, "ENABLING BLOWFISH channel:%d oad%d:%s dad%d:%s\n", bc->channel, bc->onumplan,bc->oad, bc->dnumplan,bc->dad);
+ manager_ph_control_block(bc, BF_ENABLE_KEY, bc->crypt_key, strlen(bc->crypt_key) );
+ }
+ case EVENT_ALERTING:
+ case EVENT_PROGRESS:
+ case EVENT_PROCEEDING:
+ case EVENT_SETUP_ACKNOWLEDGE:
+ case EVENT_SETUP:
+ {
+ if (bc->channel == 0xff || bc->channel<=0)
+ bc->channel=0;
+
+ if (find_free_chan_in_stack(stack, bc, bc->channel, 0)<0){
+ if (!stack->pri && !stack->ptp) {
+ bc->cw=1;
+ break;
+ }
+
+ cb_log(0, stack->port, "Any Channel Requested, but we have no more!!\n");
+ misdn_lib_send_event(bc,EVENT_RELEASE_COMPLETE);
+ return -1;
+ }
+ }
+
+ setup_bc(bc);
+ break;
+
+ case EVENT_RELEASE_COMPLETE:
+ case EVENT_RELEASE:
+ break;
+ default:
+ break;
+ }
+ } else { /** NT MODE **/
+
+ }
+ return 0;
+}
+
+static int handle_cr ( struct misdn_stack *stack, iframe_t *frm)
+{
+ struct misdn_bchannel *bc;
+
+ if (!stack) return -1;
+
+ switch (frm->prim) {
+ case CC_NEW_CR|INDICATION:
+ cb_log(7, stack->port, " --> lib: NEW_CR Ind with l3id:%x on this port.\n",frm->dinfo);
+
+ bc = misdn_lib_get_free_bc(stack->port, 0, 1, 0);
+ if (!bc) {
+ cb_log(0, stack->port, " --> !! lib: No free channel!\n");
+ return -1;
+ }
+
+ cb_log(7, stack->port, " --> new_process: New L3Id: %x\n",frm->dinfo);
+ bc->l3_id=frm->dinfo;
+ return 1;
+ case CC_NEW_CR|CONFIRM:
+ return 1;
+ case CC_NEW_CR|REQUEST:
+ return 1;
+ case CC_RELEASE_CR|REQUEST:
+ return 1;
+ case CC_RELEASE_CR|CONFIRM:
+ break;
+ case CC_RELEASE_CR|INDICATION:
+ cb_log(4, stack->port, " --> lib: RELEASE_CR Ind with l3id:%x\n",frm->dinfo);
+ {
+ struct misdn_bchannel *bc=find_bc_by_l3id(stack, frm->dinfo);
+ struct misdn_bchannel dummybc;
+
+ if (!bc) {
+ cb_log(4, stack->port, " --> Didn't found BC so temporarly creating dummy BC (l3id:%x) on this port.\n", frm->dinfo);
+ misdn_make_dummy(&dummybc, stack->port, frm->dinfo, stack->nt, 0);
+
+ bc=&dummybc;
+ }
+
+ if (bc) {
+ int channel = bc->channel;
+ cb_log(4, stack->port, " --> lib: CLEANING UP l3id: %x\n",frm->dinfo);
+
+ /*bc->pid = 0;*/
+ bc->need_disconnect=0;
+ bc->need_release=0;
+ bc->need_release_complete=0;
+
+ cb_event(EVENT_CLEANUP, bc, glob_mgr->user_data);
+
+ empty_bc(bc);
+ clean_up_bc(bc);
+
+ if (channel>0)
+ empty_chan_in_stack(stack,channel);
+ bc->in_use=0;
+
+ dump_chan_list(stack);
+
+ if (bc->stack_holder) {
+ cb_log(4,stack->port, "REMOVEING Holder\n");
+ stack_holder_remove( stack, bc);
+ free(bc);
+ }
+ }
+ else {
+ if (stack->nt)
+ cb_log(4, stack->port, "BC with dinfo: %x not found.. (prim was %x and addr %x)\n",frm->dinfo, frm->prim, frm->addr);
+ }
+
+ return 1;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+
+/*Emptys bc if it's reserved (no SETUP out yet)*/
+void misdn_lib_release(struct misdn_bchannel *bc)
+{
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+
+ if (!stack) {
+ cb_log(1,0,"misdn_release: No Stack found\n");
+ return;
+ }
+
+ if (bc->channel>0)
+ empty_chan_in_stack(stack,bc->channel);
+
+ empty_bc(bc);
+ clean_up_bc(bc);
+ bc->in_use=0;
+}
+
+
+
+
+int misdn_lib_get_port_up (int port)
+{ /* Pull Up L1 */
+ struct misdn_stack *stack;
+
+ for (stack=glob_mgr->stack_list;
+ stack;
+ stack=stack->next) {
+
+ if (stack->port == port) {
+
+ if (!stack->l1link)
+ misdn_lib_get_l1_up(stack);
+ if (!stack->l2link)
+ misdn_lib_get_l2_up(stack);
+
+ return 0;
+ }
+ }
+ return 0;
+}
+
+
+int misdn_lib_get_port_down (int port)
+{ /* Pull Down L1 */
+ struct misdn_stack *stack;
+ for (stack=glob_mgr->stack_list;
+ stack;
+ stack=stack->next) {
+ if (stack->port == port) {
+ if (stack->l2link)
+ misdn_lib_get_l2_down(stack);
+ misdn_lib_get_l1_down(stack);
+ return 0;
+ }
+ }
+ return 0;
+}
+
+int misdn_lib_port_up(int port, int check)
+{
+ struct misdn_stack *stack;
+
+
+ for (stack=glob_mgr->stack_list;
+ stack;
+ stack=stack->next) {
+
+ if (stack->port == port) {
+
+ if (stack->blocked) {
+ cb_log(0,port, "Port Blocked:%d L2:%d L1:%d\n", stack->blocked, stack->l2link, stack->l1link);
+ return -1;
+ }
+
+ if (stack->ptp ) {
+
+ if (stack->l1link && stack->l2link) {
+ return 1;
+ } else {
+ cb_log(1,port, "Port Down L2:%d L1:%d\n",
+ stack->l2link, stack->l1link);
+ return 0;
+ }
+ } else {
+ if ( !check || stack->l1link )
+ return 1;
+ else {
+ cb_log(1,port, "Port down PMP\n");
+ return 0;
+ }
+ }
+ }
+ }
+
+ return -1;
+}
+
+
+int release_cr(struct misdn_stack *stack, mISDNuser_head_t *hh)
+{
+ struct misdn_bchannel *bc=find_bc_by_l3id(stack, hh->dinfo);
+ struct misdn_bchannel dummybc;
+ iframe_t frm; /* fake te frm to remove callref from global callreflist */
+ frm.dinfo = hh->dinfo;
+
+ frm.addr=stack->upper_id | FLG_MSG_DOWN;
+
+ frm.prim = CC_RELEASE_CR|INDICATION;
+ cb_log(4, stack->port, " --> CC_RELEASE_CR: Faking Realease_cr for %x l3id:%x\n",frm.addr, frm.dinfo);
+ /** removing procid **/
+ if (!bc) {
+ cb_log(4, stack->port, " --> Didn't found BC so temporarly creating dummy BC (l3id:%x) on this port.\n", hh->dinfo);
+ misdn_make_dummy(&dummybc, stack->port, hh->dinfo, stack->nt, 0);
+ bc=&dummybc;
+ }
+
+ if (bc) {
+ if ( (bc->l3_id & 0xff00) == 0xff00) {
+ cb_log(4, stack->port, " --> Removing Process Id:%x on this port.\n", bc->l3_id&0xff);
+ stack->procids[bc->l3_id&0xff] = 0 ;
+ }
+ }
+ else cb_log(0, stack->port, "Couldnt find BC so I couldnt remove the Process!!!! this is a bad port.\n");
+
+ if (handle_cr(stack, &frm)<0) {
+ }
+
+ return 0 ;
+}
+
+int
+handle_event_nt(void *dat, void *arg)
+{
+ manager_t *mgr = (manager_t *)dat;
+ msg_t *msg = (msg_t *)arg;
+ mISDNuser_head_t *hh;
+ int reject=0;
+
+ struct misdn_stack *stack=find_stack_by_mgr(mgr);
+ int port;
+
+ if (!msg || !mgr)
+ return(-EINVAL);
+
+ hh=(mISDNuser_head_t*)msg->data;
+ port=stack->port;
+
+ cb_log(5, stack->port, " --> lib: prim %x dinfo %x\n",hh->prim, hh->dinfo);
+ {
+ switch(hh->prim){
+ case CC_RETRIEVE|INDICATION:
+ {
+ struct misdn_bchannel *bc, *hold_bc;
+
+ iframe_t frm; /* fake te frm to add callref to global callreflist */
+ frm.dinfo = hh->dinfo;
+
+ frm.addr=stack->upper_id | FLG_MSG_DOWN;
+
+ frm.prim = CC_NEW_CR|INDICATION;
+
+ if (handle_cr( stack, &frm)< 0) {
+ msg_t *dmsg;
+ cb_log(4, stack->port, "Patch from MEIDANIS:Sending RELEASE_COMPLETE %x (No free Chan for you..)\n", hh->dinfo);
+ dmsg = create_l3msg(CC_RELEASE_COMPLETE | REQUEST,MT_RELEASE_COMPLETE, hh->dinfo,sizeof(RELEASE_COMPLETE_t), 1);
+ stack->nst.manager_l3(&stack->nst, dmsg);
+ free_msg(msg);
+ return 0;
+ }
+
+ bc = find_bc_by_l3id(stack, hh->dinfo);
+ hold_bc = stack_holder_find(stack, bc->l3_id);
+ cb_log(4, stack->port, "bc_l3id:%x holded_bc_l3id:%x\n",bc->l3_id, hold_bc->l3_id);
+
+ if (hold_bc) {
+ cb_log(4, stack->port, "REMOVEING Holder\n");
+
+ /*swap the backup to our new channel back*/
+ stack_holder_remove(stack, hold_bc);
+ memcpy(bc, hold_bc, sizeof(*bc));
+ free(hold_bc);
+
+ bc->holded=0;
+ bc->b_stid=0;
+ }
+
+ }
+
+ break;
+
+ case CC_SETUP|CONFIRM:
+ {
+ struct misdn_bchannel *bc=find_bc_by_l3id(stack, hh->dinfo);
+ int l3id = *((int *)(((u_char *)msg->data)+ mISDNUSER_HEAD_SIZE));
+ cb_log(4, stack->port, " --> lib: Event_ind:SETUP CONFIRM [NT] : new L3ID is %x\n",l3id );
+
+ if (!bc) { cb_log(4, stack->port, "Bc Not found (after SETUP CONFIRM)\n"); return 0; }
+ cb_log (2,bc->port,"I IND :CC_SETUP|CONFIRM: old l3id:%x new l3id:%x\n", bc->l3_id, l3id);
+ bc->l3_id=l3id;
+ cb_event(EVENT_NEW_L3ID, bc, glob_mgr->user_data);
+ }
+ free_msg(msg);
+ return 0;
+
+ case CC_SETUP|INDICATION:
+ {
+ struct misdn_bchannel* bc=misdn_lib_get_free_bc(stack->port, 0, 1, 0);
+ if (!bc)
+ ERR_NO_CHANNEL:
+ {
+ msg_t *dmsg;
+ cb_log(4, stack->port, "Patch from MEIDANIS:Sending RELEASE_COMPLETE %x (No free Chan for you..)\n", hh->dinfo);
+ dmsg = create_l3msg(CC_RELEASE_COMPLETE | REQUEST,MT_RELEASE_COMPLETE, hh->dinfo,sizeof(RELEASE_COMPLETE_t), 1);
+ stack->nst.manager_l3(&stack->nst, dmsg);
+ free_msg(msg);
+ return 0;
+ }
+
+ cb_log(4, stack->port, " --> new_process: New L3Id: %x\n",hh->dinfo);
+ bc->l3_id=hh->dinfo;
+ }
+ break;
+
+ case CC_CONNECT_ACKNOWLEDGE|INDICATION:
+ break;
+
+ case CC_ALERTING|INDICATION:
+ case CC_PROCEEDING|INDICATION:
+ case CC_SETUP_ACKNOWLEDGE|INDICATION:
+ if(!stack->ptp) break;
+ case CC_CONNECT|INDICATION:
+ break;
+ case CC_DISCONNECT|INDICATION:
+ {
+ struct misdn_bchannel *bc=find_bc_by_l3id(stack, hh->dinfo);
+ if (!bc) {
+ bc=find_bc_by_masked_l3id(stack, hh->dinfo, 0xffff0000);
+ if (bc) {
+ int myprocid=bc->l3_id&0x0000ffff;
+ hh->dinfo=(hh->dinfo&0xffff0000)|myprocid;
+ cb_log(3,stack->port,"Reject dinfo: %x cause:%d\n",hh->dinfo,bc->cause);
+ reject=1;
+ }
+ }
+ }
+ break;
+
+ case CC_FACILITY|INDICATION:
+ {
+ struct misdn_bchannel *bc=find_bc_by_l3id(stack, hh->dinfo);
+ if (!bc) {
+ bc=find_bc_by_masked_l3id(stack, hh->dinfo, 0xffff0000);
+ if (bc) {
+ int myprocid=bc->l3_id&0x0000ffff;
+ hh->dinfo=(hh->dinfo&0xffff0000)|myprocid;
+ cb_log(4,bc->port,"Repaired reject Bug, new dinfo: %x\n",hh->dinfo);
+ }
+ }
+ }
+ break;
+
+ case CC_RELEASE_COMPLETE|INDICATION:
+ break;
+
+ case CC_SUSPEND|INDICATION:
+ {
+ msg_t *dmsg;
+ cb_log(4, stack->port, " --> Got Suspend, sending Reject for now\n");
+ dmsg = create_l3msg(CC_SUSPEND_REJECT | REQUEST,MT_SUSPEND_REJECT, hh->dinfo,sizeof(RELEASE_COMPLETE_t), 1);
+ stack->nst.manager_l3(&stack->nst, dmsg);
+ free_msg(msg);
+ return 0;
+ }
+ break;
+ case CC_RESUME|INDICATION:
+ break;
+
+ case CC_RELEASE|CONFIRM:
+ {
+ struct misdn_bchannel *bc=find_bc_by_l3id(stack, hh->dinfo);
+
+ if (bc) {
+ cb_log(1, stack->port, "CC_RELEASE|CONFIRM (l3id:%x), sending RELEASE_COMPLETE\n", hh->dinfo);
+ misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
+ }
+ }
+ break;
+
+ case CC_RELEASE|INDICATION:
+ break;
+
+ case CC_RELEASE_CR|INDICATION:
+ release_cr(stack, hh);
+ free_msg(msg);
+ return 0 ;
+ break;
+
+ case CC_NEW_CR|INDICATION:
+ /* Got New CR for bchan, for now I handle this one in */
+ /* connect_ack, Need to be changed */
+ {
+ struct misdn_bchannel *bc=find_bc_by_l3id(stack, hh->dinfo);
+ int l3id = *((int *)(((u_char *)msg->data)+ mISDNUSER_HEAD_SIZE));
+ if (!bc) { cb_log(0, stack->port, " --> In NEW_CR: didn't found bc ??\n"); return -1;};
+ if (((l3id&0xff00)!=0xff00) && ((bc->l3_id&0xff00)==0xff00)) {
+ cb_log(4, stack->port, " --> Removing Process Id:%x on this port.\n", 0xff&bc->l3_id);
+ stack->procids[bc->l3_id&0xff] = 0 ;
+ }
+ cb_log(4, stack->port, "lib: Event_ind:CC_NEW_CR : very new L3ID is %x\n",l3id );
+
+ bc->l3_id =l3id;
+ cb_event(EVENT_NEW_L3ID, bc, glob_mgr->user_data);
+
+ free_msg(msg);
+ return 0;
+ }
+
+ case DL_ESTABLISH | INDICATION:
+ case DL_ESTABLISH | CONFIRM:
+ {
+ cb_log(3, stack->port, "%% GOT L2 Activate Info.\n");
+
+ if (stack->ptp && stack->l2link) {
+ cb_log(0, stack->port, "%% GOT L2 Activate Info. but we're activated already.. this l2 is faulty, blocking port\n");
+ cb_event(EVENT_PORT_ALARM, &stack->bc[0], glob_mgr->user_data);
+ }
+
+ if (stack->ptp && !stack->restart_sent) {
+ /* make sure we restart the interface of the
+ * other side */
+ stack->restart_sent=1;
+ misdn_lib_send_restart(stack->port, -1);
+
+ }
+
+ /* when we get the L2 UP, the L1 is UP definitely too*/
+ stack->l2link = 1;
+ stack->l2upcnt=0;
+
+ free_msg(msg);
+ return 0;
+ }
+ break;
+
+
+ case DL_RELEASE | INDICATION:
+ case DL_RELEASE | CONFIRM:
+ {
+ if (stack->ptp) {
+ cb_log(3 , stack->port, "%% GOT L2 DeActivate Info.\n");
+
+ if (stack->l2upcnt>3) {
+ cb_log(0 , stack->port, "!!! Could not Get the L2 up after 3 Attemps!!!\n");
+ } else {
+#if 0
+ if (stack->nt) misdn_lib_reinit_nt_stack(stack->port);
+#endif
+ if (stack->l1link) {
+ misdn_lib_get_l2_up(stack);
+ stack->l2upcnt++;
+ }
+ }
+
+ } else
+ cb_log(3, stack->port, "%% GOT L2 DeActivate Info.\n");
+
+ stack->l2link = 0;
+ free_msg(msg);
+ return 0;
+ }
+ break;
+ }
+ }
+
+ {
+ /* Parse Events and fire_up to App. */
+ struct misdn_bchannel *bc;
+ struct misdn_bchannel dummybc;
+
+ enum event_e event = isdn_msg_get_event(msgs_g, msg, 1);
+
+ bc=find_bc_by_l3id(stack, hh->dinfo);
+
+ if (!bc) {
+ cb_log(4, stack->port, " --> Didn't found BC so temporarly creating dummy BC (l3id:%x).\n", hh->dinfo);
+ misdn_make_dummy(&dummybc, stack->port, hh->dinfo, stack->nt, 0);
+ bc=&dummybc;
+ }
+ if (bc ) {
+ isdn_msg_parse_event(msgs_g,msg,bc, 1);
+
+ switch (event) {
+ case EVENT_SETUP:
+ if (bc->channel<=0 || bc->channel==0xff)
+ bc->channel=0;
+
+ if (find_free_chan_in_stack(stack,bc, bc->channel,0)<0)
+ goto ERR_NO_CHANNEL;
+ break;
+ case EVENT_RELEASE:
+ case EVENT_RELEASE_COMPLETE:
+ {
+ int channel=bc->channel;
+ int tmpcause=bc->cause;
+ empty_bc(bc);
+ bc->cause=tmpcause;
+ clean_up_bc(bc);
+
+ if (channel>0)
+ empty_chan_in_stack(stack,channel);
+ bc->in_use=0;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if(!isdn_get_info(msgs_g,event,1)) {
+ cb_log(4, stack->port, "Unknown Event Ind: prim %x dinfo %x\n",hh->prim, hh->dinfo);
+ } else {
+ if (reject) {
+ switch(bc->cause){
+ case 17:
+ cb_log(1, stack->port, "Siemens Busy reject..\n");
+
+ break;
+ default:
+ break;
+ }
+ }
+ cb_event(event, bc, glob_mgr->user_data);
+ }
+ } else {
+ cb_log(4, stack->port, "No BC found with l3id: prim %x dinfo %x\n",hh->prim, hh->dinfo);
+ }
+
+ free_msg(msg);
+ }
+
+
+ return 0;
+}
+
+
+static int handle_timers(msg_t* msg)
+{
+ iframe_t *frm= (iframe_t*)msg->data;
+ struct misdn_stack *stack;
+
+ /* Timer Stuff */
+ switch (frm->prim) {
+ case MGR_INITTIMER | CONFIRM:
+ case MGR_ADDTIMER | CONFIRM:
+ case MGR_DELTIMER | CONFIRM:
+ case MGR_REMOVETIMER | CONFIRM:
+ free_msg(msg);
+ return(1);
+ }
+
+
+
+ if (frm->prim==(MGR_TIMER | INDICATION) ) {
+ for (stack = glob_mgr->stack_list;
+ stack;
+ stack = stack->next) {
+ itimer_t *it;
+
+ if (!stack->nt) continue;
+
+ it = stack->nst.tlist;
+ /* find timer */
+ for(it=stack->nst.tlist;
+ it;
+ it=it->next) {
+ if (it->id == (int)frm->addr)
+ break;
+ }
+ if (it) {
+ int ret;
+ ret = mISDN_write_frame(stack->midev, msg->data, frm->addr,
+ MGR_TIMER | RESPONSE, 0, 0, NULL, TIMEOUT_1SEC);
+ test_and_clear_bit(FLG_TIMER_RUNING, (long unsigned int *)&it->Flags);
+ ret = it->function(it->data);
+ free_msg(msg);
+ return 1;
+ }
+ }
+
+ cb_log(0, 0, "Timer Msg without Timer ??\n");
+ free_msg(msg);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+
+void misdn_lib_tone_generator_start(struct misdn_bchannel *bc)
+{
+ bc->generate_tone=1;
+}
+
+void misdn_lib_tone_generator_stop(struct misdn_bchannel *bc)
+{
+ bc->generate_tone=0;
+}
+
+
+static int do_tone(struct misdn_bchannel *bc, int len)
+{
+ bc->tone_cnt=len;
+
+ if (bc->generate_tone) {
+ cb_event(EVENT_TONE_GENERATE, bc, glob_mgr->user_data);
+
+ if ( !bc->nojitter ) {
+ misdn_tx_jitter(bc,len);
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+#ifdef MISDN_SAVE_DATA
+static void misdn_save_data(int id, char *p1, int l1, char *p2, int l2)
+{
+ char n1[32],n2[32];
+ FILE *rx, *tx;
+
+ sprintf(n1,"/tmp/misdn-rx-%d.raw",id);
+ sprintf(n2,"/tmp/misdn-tx-%d.raw",id);
+
+ rx = fopen(n1,"a+");
+ tx = fopen(n2,"a+");
+
+ if (!rx || !tx) {
+ cb_log(0,0,"Couldn't open files: %s\n",strerror(errno));
+ return ;
+ }
+
+ fwrite(p1,1,l1,rx);
+ fwrite(p2,1,l2,tx);
+
+ fclose(rx);
+ fclose(tx);
+
+}
+#endif
+
+void misdn_tx_jitter(struct misdn_bchannel *bc, int len)
+{
+ char buf[4096 + mISDN_HEADER_LEN];
+ char *data=&buf[mISDN_HEADER_LEN];
+ iframe_t *txfrm= (iframe_t*)buf;
+ int jlen, r;
+
+ jlen=cb_jb_empty(bc,data,len);
+
+ if (jlen) {
+#ifdef MISDN_SAVE_DATA
+ misdn_save_data((bc->port*100+bc->channel), data, jlen, bc->bframe, bc->bframe_len);
+#endif
+ flip_buf_bits( data, jlen);
+
+ if (jlen < len) {
+ cb_log(7,bc->port,"Jitterbuffer Underrun.\n");
+ }
+
+ txfrm->prim = DL_DATA|REQUEST;
+
+ txfrm->dinfo = 0;
+
+ txfrm->addr = bc->addr|FLG_MSG_DOWN; /* | IF_DOWN; */
+
+ txfrm->len =jlen;
+ cb_log(9, bc->port, "Transmitting %d samples 2 misdn\n", txfrm->len);
+
+ r=mISDN_write( glob_mgr->midev, buf, txfrm->len + mISDN_HEADER_LEN, 8000 );
+ } else {
+#define MISDN_GEN_SILENCE
+#ifdef MISDN_GEN_SILENCE
+ int cnt=len/TONE_SILENCE_SIZE;
+ int rest=len%TONE_SILENCE_SIZE;
+ int i;
+
+ for (i=0; i<cnt; i++) {
+ memcpy(data, tone_silence_flip, TONE_SILENCE_SIZE );
+ data +=TONE_SILENCE_SIZE;
+ }
+
+ if (rest) {
+ memcpy(data, tone_silence_flip, rest);
+ }
+
+ txfrm->prim = DL_DATA|REQUEST;
+
+ txfrm->dinfo = 0;
+
+ txfrm->addr = bc->addr|FLG_MSG_DOWN; /* | IF_DOWN; */
+
+ txfrm->len =len;
+ cb_log(9, bc->port, "Transmitting %d samples 2 misdn\n", txfrm->len);
+
+ r=mISDN_write( glob_mgr->midev, buf, txfrm->len + mISDN_HEADER_LEN, 8000 );
+#endif
+
+ }
+}
+
+static int handle_bchan(msg_t *msg)
+{
+ iframe_t *frm= (iframe_t*)msg->data;
+ struct misdn_bchannel *bc=find_bc_by_addr(frm->addr);
+ struct misdn_stack *stack;
+
+ if (!bc) {
+ cb_log(1,0,"handle_bchan: BC not found for prim:%x with addr:%x dinfo:%x\n", frm->prim, frm->addr, frm->dinfo);
+ return 0 ;
+ }
+
+ stack = get_stack_by_bc(bc);
+
+ if (!stack) {
+ cb_log(0, bc->port,"handle_bchan: STACK not found for prim:%x with addr:%x dinfo:%x\n", frm->prim, frm->addr, frm->dinfo);
+ return 0;
+ }
+
+ switch (frm->prim) {
+
+ case MGR_SETSTACK| CONFIRM:
+ cb_log(3, stack->port, "BCHAN: MGR_SETSTACK|CONFIRM pid:%d\n",bc->pid);
+ break;
+
+ case MGR_SETSTACK| INDICATION:
+ cb_log(3, stack->port, "BCHAN: MGR_SETSTACK|IND pid:%d\n",bc->pid);
+ break;
+#if 0
+ AGAIN:
+ bc->addr = mISDN_get_layerid(stack->midev, bc->b_stid, bc->layer);
+ if (!bc->addr) {
+
+ if (errno == EAGAIN) {
+ usleep(1000);
+ goto AGAIN;
+ }
+
+ cb_log(0,stack->port,"$$$ Get Layer (%d) Id Error: %s\n",bc->layer,strerror(errno));
+
+ /* we kill the channel later, when we received some
+ data. */
+ bc->addr= frm->addr;
+ } else if ( bc->addr < 0) {
+ cb_log(0, stack->port,"$$$ bc->addr <0 Error:%s\n",strerror(errno));
+ bc->addr=0;
+ }
+
+ cb_log(4, stack->port," --> Got Adr %x\n", bc->addr);
+
+ free_msg(msg);
+
+
+ switch(bc->bc_state) {
+ case BCHAN_SETUP:
+ bc_state_change(bc,BCHAN_SETUPED);
+ break;
+
+ case BCHAN_CLEAN_REQUEST:
+ default:
+ cb_log(0, stack->port," --> STATE WASN'T SETUP (but %s) in SETSTACK|IND pid:%d\n",bc_state2str(bc->bc_state), bc->pid);
+ clean_up_bc(bc);
+ }
+ return 1;
+#endif
+
+ case MGR_DELLAYER| INDICATION:
+ cb_log(3, stack->port, "BCHAN: MGR_DELLAYER|IND pid:%d\n",bc->pid);
+ break;
+
+ case MGR_DELLAYER| CONFIRM:
+ cb_log(3, stack->port, "BCHAN: MGR_DELLAYER|CNF pid:%d\n",bc->pid);
+
+ bc->pid=0;
+ bc->addr=0;
+
+ free_msg(msg);
+ return 1;
+
+ case PH_ACTIVATE | INDICATION:
+ case DL_ESTABLISH | INDICATION:
+ cb_log(3, stack->port, "BCHAN: ACT Ind pid:%d\n", bc->pid);
+
+ free_msg(msg);
+ return 1;
+
+ case PH_ACTIVATE | CONFIRM:
+ case DL_ESTABLISH | CONFIRM:
+
+ cb_log(3, stack->port, "BCHAN: bchan ACT Confirm pid:%d\n",bc->pid);
+ free_msg(msg);
+
+ return 1;
+
+ case DL_ESTABLISH | REQUEST:
+ {
+ char buf[128];
+ mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_TARGET | FLG_MSG_DOWN, DL_ESTABLISH | CONFIRM, 0,0, NULL, TIMEOUT_1SEC);
+ }
+ free_msg(msg);
+ return 1;
+
+ case DL_RELEASE|REQUEST:
+ {
+ char buf[128];
+ mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_TARGET | FLG_MSG_DOWN, DL_RELEASE| CONFIRM, 0,0, NULL, TIMEOUT_1SEC);
+ }
+ free_msg(msg);
+ return 1;
+
+ case PH_DEACTIVATE | INDICATION:
+ case DL_RELEASE | INDICATION:
+ cb_log (3, stack->port, "BCHAN: DeACT Ind pid:%d\n",bc->pid);
+
+ free_msg(msg);
+ return 1;
+
+ case PH_DEACTIVATE | CONFIRM:
+ case DL_RELEASE | CONFIRM:
+ cb_log(3, stack->port, "BCHAN: DeACT Conf pid:%d\n",bc->pid);
+
+ free_msg(msg);
+ return 1;
+
+ case PH_CONTROL|INDICATION:
+ {
+ unsigned int cont = *((unsigned int *)&frm->data.p);
+
+ cb_log(4, stack->port, "PH_CONTROL: channel:%d oad%d:%s dad%d:%s \n", bc->channel, bc->onumplan,bc->oad, bc->dnumplan,bc->dad);
+
+ if ((cont&~DTMF_TONE_MASK) == DTMF_TONE_VAL) {
+ int dtmf = cont & DTMF_TONE_MASK;
+ cb_log(4, stack->port, " --> DTMF TONE: %c\n",dtmf);
+ bc->dtmf=dtmf;
+ cb_event(EVENT_DTMF_TONE, bc, glob_mgr->user_data);
+
+ free_msg(msg);
+ return 1;
+ }
+ if (cont == BF_REJECT) {
+ cb_log(4, stack->port, " --> BF REJECT\n");
+ free_msg(msg);
+ return 1;
+ }
+ if (cont == BF_ACCEPT) {
+ cb_log(4, stack->port, " --> BF ACCEPT\n");
+ free_msg(msg);
+ return 1;
+ }
+ }
+ break;
+
+ case PH_DATA|REQUEST:
+ case DL_DATA|REQUEST:
+ cb_log(0, stack->port, "DL_DATA REQUEST \n");
+ do_tone(bc, 64);
+
+ free_msg(msg);
+ return 1;
+
+
+ case PH_DATA|INDICATION:
+ case DL_DATA|INDICATION:
+ {
+ bc->bframe = (void*)&frm->data.i;
+ bc->bframe_len = frm->len;
+
+ /** Anyway flip the bufbits **/
+ if ( misdn_cap_is_speech(bc->capability) )
+ flip_buf_bits(bc->bframe, bc->bframe_len);
+
+
+ if (!bc->bframe_len) {
+ cb_log(2, stack->port, "DL_DATA INDICATION bc->addr:%x frm->addr:%x\n", bc->addr, frm->addr);
+ free_msg(msg);
+ return 1;
+ }
+
+ if ( (bc->addr&STACK_ID_MASK) != (frm->addr&STACK_ID_MASK) ) {
+ cb_log(2, stack->port, "DL_DATA INDICATION bc->addr:%x frm->addr:%x\n", bc->addr, frm->addr);
+ free_msg(msg);
+ return 1;
+ }
+
+#if MISDN_DEBUG
+ cb_log(0, stack->port, "DL_DATA INDICATION Len %d\n", frm->len);
+
+#endif
+
+ if ( (bc->bc_state == BCHAN_ACTIVATED) && frm->len > 0) {
+ int t;
+
+#ifdef MISDN_B_DEBUG
+ cb_log(0,bc->port,"do_tone START\n");
+#endif
+ t=do_tone(bc,frm->len);
+
+#ifdef MISDN_B_DEBUG
+ cb_log(0,bc->port,"do_tone STOP (%d)\n",t);
+#endif
+ if ( !t ) {
+ int i;
+
+ if ( misdn_cap_is_speech(bc->capability)) {
+ if ( !bc->nojitter ) {
+#ifdef MISDN_B_DEBUG
+ cb_log(0,bc->port,"tx_jitter START\n");
+#endif
+ misdn_tx_jitter(bc,frm->len);
+#ifdef MISDN_B_DEBUG
+ cb_log(0,bc->port,"tx_jitter STOP\n");
+#endif
+ }
+ }
+
+#ifdef MISDN_B_DEBUG
+ cb_log(0,bc->port,"EVENT_B_DATA START\n");
+#endif
+
+ i = cb_event(EVENT_BCHAN_DATA, bc, glob_mgr->user_data);
+#ifdef MISDN_B_DEBUG
+ cb_log(0,bc->port,"EVENT_B_DATA STOP\n");
+#endif
+
+ if (i<0) {
+ cb_log(10,stack->port,"cb_event returned <0\n");
+ /*clean_up_bc(bc);*/
+ }
+ }
+ }
+ free_msg(msg);
+ return 1;
+ }
+
+
+ case PH_CONTROL | CONFIRM:
+ cb_log(4, stack->port, "PH_CONTROL|CNF bc->addr:%x\n", frm->addr);
+ free_msg(msg);
+ return 1;
+
+ case PH_DATA | CONFIRM:
+ case DL_DATA|CONFIRM:
+#if MISDN_DEBUG
+
+ cb_log(0, stack->port, "Data confirmed\n");
+
+#endif
+ free_msg(msg);
+ return 1;
+ case DL_DATA|RESPONSE:
+#if MISDN_DEBUG
+ cb_log(0, stack->port, "Data response\n");
+
+#endif
+ break;
+ }
+
+ return 0;
+}
+
+
+
+static int handle_frm_nt(msg_t *msg)
+{
+ iframe_t *frm= (iframe_t*)msg->data;
+ struct misdn_stack *stack;
+ int err=0;
+
+ stack=find_stack_by_addr( frm->addr );
+
+
+
+ if (!stack || !stack->nt) {
+ return 0;
+ }
+
+
+ if ((err=stack->nst.l1_l2(&stack->nst,msg))) {
+
+ if (nt_err_cnt > 0 ) {
+ if (nt_err_cnt < 100) {
+ nt_err_cnt++;
+ cb_log(0, stack->port, "NT Stack sends us error: %d \n", err);
+ } else if (nt_err_cnt < 105){
+ cb_log(0, stack->port, "NT Stack sends us error: %d over 100 times, so I'll stop this message\n", err);
+ nt_err_cnt = - 1;
+ }
+ }
+ free_msg(msg);
+ return 1;
+
+ }
+
+ return 1;
+}
+
+
+static int handle_frm(msg_t *msg)
+{
+ iframe_t *frm = (iframe_t*) msg->data;
+
+ struct misdn_stack *stack=find_stack_by_addr(frm->addr);
+
+ if (!stack || stack->nt) {
+ return 0;
+ }
+
+ cb_log(4,stack?stack->port:0,"handle_frm: frm->addr:%x frm->prim:%x\n",frm->addr,frm->prim);
+
+ {
+ struct misdn_bchannel dummybc;
+ struct misdn_bchannel *bc;
+ int ret=handle_cr(stack, frm);
+
+ if (ret<0) {
+ cb_log(3,stack?stack->port:0,"handle_frm: handle_cr <0 prim:%x addr:%x\n", frm->prim, frm->addr);
+
+
+ }
+
+ if(ret) {
+ free_msg(msg);
+ return 1;
+ }
+
+ bc=find_bc_by_l3id(stack, frm->dinfo);
+
+ if (!bc && (frm->prim==(CC_RESTART|CONFIRM)) ) {
+ misdn_make_dummy(&dummybc, stack->port, MISDN_ID_GLOBAL, stack->nt, 0);
+ bc=&dummybc;
+ }
+
+handle_frm_bc:
+ if (bc ) {
+ enum event_e event = isdn_msg_get_event(msgs_g, msg, 0);
+ enum event_response_e response=RESPONSE_OK;
+ int ret;
+
+ isdn_msg_parse_event(msgs_g,msg,bc, 0);
+
+ /** Preprocess some Events **/
+ ret = handle_event(bc, event, frm);
+ if (ret<0) {
+ cb_log(0,stack->port,"couldn't handle event\n");
+ free_msg(msg);
+ return 1;
+ }
+ /* shoot up event to App: */
+ cb_log(5, stack->port, "lib Got Prim: Addr %x prim %x dinfo %x\n",frm->addr, frm->prim, frm->dinfo);
+
+ if(!isdn_get_info(msgs_g,event,0))
+ cb_log(0, stack->port, "Unknown Event Ind: Addr:%x prim %x dinfo %x\n",frm->addr, frm->prim, frm->dinfo);
+ else
+ response=cb_event(event, bc, glob_mgr->user_data);
+#if 1
+ if (event == EVENT_SETUP) {
+ switch (response) {
+ case RESPONSE_IGNORE_SETUP_WITHOUT_CLOSE:
+
+ cb_log(0, stack->port, "TOTALY IGNORING SETUP \n");
+
+ break;
+ case RESPONSE_IGNORE_SETUP:
+ /* I think we should send CC_RELEASE_CR, but am not sure*/
+ bc->out_cause=16;
+
+ case RESPONSE_RELEASE_SETUP:
+ misdn_lib_send_event(bc,EVENT_RELEASE_COMPLETE);
+ if (bc->channel>0)
+ empty_chan_in_stack(stack, bc->channel);
+ empty_bc(bc);
+ bc_state_change(bc,BCHAN_CLEANED);
+ bc->in_use=0;
+
+ cb_log(0, stack->port, "GOT IGNORE SETUP\n");
+ break;
+ case RESPONSE_OK:
+ cb_log(4, stack->port, "GOT SETUP OK\n");
+
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (event == EVENT_RELEASE_COMPLETE) {
+ /* release bchannel only after we've anounced the RELEASE_COMPLETE */
+ int channel=bc->channel;
+ int tmpcause=bc->cause;
+ int tmp_out_cause=bc->out_cause;
+ empty_bc(bc);
+ bc->cause=tmpcause;
+ bc->out_cause=tmp_out_cause;
+ clean_up_bc(bc);
+
+ if (tmpcause == 44) {
+ cb_log(0,stack->port,"**** Received CAUSE:44, so not cleaning up channel %d\n", channel);
+ cb_log(0,stack->port,"**** This channel is now no longer available,\nplease try to restart it with 'misdn send restart <port> <channel>'\n");
+ set_chan_in_stack(stack, channel);
+ bc->channel=channel;
+ misdn_lib_send_restart(stack->port, channel);
+ } else {
+ if (channel>0)
+ empty_chan_in_stack(stack, channel);
+ }
+ bc->in_use=0;
+ }
+
+ if (event == EVENT_RESTART) {
+ cb_log(0, stack->port, "**** Received RESTART_ACK channel:%d\n", bc->restart_channel);
+ empty_chan_in_stack(stack, bc->restart_channel);
+ }
+
+ cb_log(5, stack->port, "Freeing Msg on prim:%x \n",frm->prim);
+
+
+ free_msg(msg);
+ return 1;
+#endif
+
+ } else {
+ struct misdn_bchannel dummybc;
+ if (frm->prim!=(CC_FACILITY|INDICATION))
+ cb_log(0, stack->port, " --> Didn't find BC so temporarly creating dummy BC (l3id:%x) on this port.\n", frm->dinfo);
+ else
+ cb_log(5, stack->port, " --> Using Dummy BC for FACILITy\n");
+
+ memset (&dummybc,0,sizeof(dummybc));
+ dummybc.port=stack->port;
+ dummybc.l3_id=frm->dinfo;
+ bc=&dummybc;
+ goto handle_frm_bc;
+ }
+ }
+
+ cb_log(4, stack->port, "TE_FRM_HANDLER: Returning 0 on prim:%x \n",frm->prim);
+ return 0;
+}
+
+
+static int handle_l1(msg_t *msg)
+{
+ iframe_t *frm = (iframe_t*) msg->data;
+ struct misdn_stack *stack = find_stack_by_addr(frm->addr);
+ int i ;
+
+ if (!stack) return 0 ;
+
+ switch (frm->prim) {
+ case PH_ACTIVATE | CONFIRM:
+ case PH_ACTIVATE | INDICATION:
+ cb_log (3, stack->port, "L1: PH L1Link Up!\n");
+ stack->l1link=1;
+
+ if (stack->nt) {
+
+ if (stack->nst.l1_l2(&stack->nst, msg))
+ free_msg(msg);
+
+ if (stack->ptp)
+ misdn_lib_get_l2_up(stack);
+ } else {
+ free_msg(msg);
+ }
+
+ for (i=0;i<=stack->b_num; i++) {
+ if (stack->bc[i].evq != EVENT_NOTHING) {
+ cb_log(4, stack->port, "Fireing Queued Event %s because L1 got up\n", isdn_get_info(msgs_g, stack->bc[i].evq, 0));
+ misdn_lib_send_event(&stack->bc[i],stack->bc[i].evq);
+ stack->bc[i].evq=EVENT_NOTHING;
+ }
+
+ }
+ return 1;
+
+ case PH_ACTIVATE | REQUEST:
+ free_msg(msg);
+ cb_log(3,stack->port,"L1: PH_ACTIVATE|REQUEST \n");
+ return 1;
+
+ case PH_DEACTIVATE | REQUEST:
+ free_msg(msg);
+ cb_log(3,stack->port,"L1: PH_DEACTIVATE|REQUEST \n");
+ return 1;
+
+ case PH_DEACTIVATE | CONFIRM:
+ case PH_DEACTIVATE | INDICATION:
+ cb_log (3, stack->port, "L1: PH L1Link Down! \n");
+
+#if 0
+ for (i=0; i<=stack->b_num; i++) {
+ if (global_state == MISDN_INITIALIZED) {
+ cb_event(EVENT_CLEANUP, &stack->bc[i], glob_mgr->user_data);
+ }
+ }
+#endif
+
+ if (stack->nt) {
+ if (stack->nst.l1_l2(&stack->nst, msg))
+ free_msg(msg);
+ } else {
+ free_msg(msg);
+ }
+
+ stack->l1link=0;
+ stack->l2link=0;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int handle_l2(msg_t *msg)
+{
+ iframe_t *frm = (iframe_t*) msg->data;
+
+ struct misdn_stack *stack = find_stack_by_addr(frm->addr);
+
+ if (!stack) {
+ return 0 ;
+ }
+
+ switch(frm->prim) {
+
+ case DL_ESTABLISH | REQUEST:
+ cb_log(1,stack->port,"DL_ESTABLISH|REQUEST \n");
+ return 1;
+ case DL_RELEASE | REQUEST:
+ cb_log(1,stack->port,"DL_RELEASE|REQUEST \n");
+ return 1;
+
+ case DL_ESTABLISH | INDICATION:
+ case DL_ESTABLISH | CONFIRM:
+ {
+ cb_log (3, stack->port, "L2: L2Link Up! \n");
+ if (stack->ptp && stack->l2link) {
+ cb_log (-1, stack->port, "L2: L2Link Up! but it's already UP.. must be faulty, blocking port\n");
+ cb_event(EVENT_PORT_ALARM, &stack->bc[0], glob_mgr->user_data);
+ }
+ stack->l2link=1;
+ free_msg(msg);
+ return 1;
+ }
+ break;
+
+ case DL_RELEASE | INDICATION:
+ case DL_RELEASE | CONFIRM:
+ {
+ cb_log (3, stack->port, "L2: L2Link Down! \n");
+ stack->l2link=0;
+
+ free_msg(msg);
+ return 1;
+ }
+ break;
+ }
+ return 0;
+}
+
+static int handle_mgmt(msg_t *msg)
+{
+ iframe_t *frm = (iframe_t*) msg->data;
+ struct misdn_stack *stack;
+
+ if ( (frm->addr == 0) && (frm->prim == (MGR_DELLAYER|CONFIRM)) ) {
+ cb_log(2, 0, "MGMT: DELLAYER|CONFIRM Addr: 0 !\n") ;
+ free_msg(msg);
+ return 1;
+ }
+
+ stack = find_stack_by_addr(frm->addr);
+
+ if (!stack) {
+ if (frm->prim == (MGR_DELLAYER|CONFIRM)) {
+ cb_log(2, 0, "MGMT: DELLAYER|CONFIRM Addr: %x !\n",
+ frm->addr) ;
+ free_msg(msg);
+ return 1;
+ }
+
+ return 0;
+ }
+
+ switch(frm->prim) {
+ case MGR_SHORTSTATUS | INDICATION:
+ case MGR_SHORTSTATUS | CONFIRM:
+ cb_log(5, 0, "MGMT: Short status dinfo %x\n",frm->dinfo);
+
+ switch (frm->dinfo) {
+ case SSTATUS_L1_ACTIVATED:
+ cb_log(3, 0, "MGMT: SSTATUS: L1_ACTIVATED \n");
+ stack->l1link=1;
+
+ break;
+ case SSTATUS_L1_DEACTIVATED:
+ cb_log(3, 0, "MGMT: SSTATUS: L1_DEACTIVATED \n");
+ stack->l1link=0;
+#if 0
+ clear_l3(stack);
+#endif
+ break;
+
+ case SSTATUS_L2_ESTABLISHED:
+ cb_log(3, stack->port, "MGMT: SSTATUS: L2_ESTABLISH \n");
+ stack->l2link=1;
+ break;
+
+ case SSTATUS_L2_RELEASED:
+ cb_log(3, stack->port, "MGMT: SSTATUS: L2_RELEASED \n");
+ stack->l2link=0;
+ break;
+ }
+
+ free_msg(msg);
+ return 1;
+
+ case MGR_SETSTACK | INDICATION:
+ cb_log(4, stack->port, "MGMT: SETSTACK|IND dinfo %x\n",frm->dinfo);
+ free_msg(msg);
+ return 1;
+ case MGR_DELLAYER | CONFIRM:
+ cb_log(4, stack->port, "MGMT: DELLAYER|CNF dinfo %x\n",frm->dinfo) ;
+ free_msg(msg);
+ return 1;
+
+ }
+
+ /*
+ if ( (frm->prim & 0x0f0000) == 0x0f0000) {
+ cb_log(5, 0, "$$$ MGMT FRAME: prim %x addr %x dinfo %x\n",frm->prim, frm->addr, frm->dinfo) ;
+ free_msg(msg);
+ return 1;
+ } */
+
+ return 0;
+}
+
+
+static msg_t *fetch_msg(int midev)
+{
+ msg_t *msg=alloc_msg(MAX_MSG_SIZE);
+ int r;
+
+ if (!msg) {
+ cb_log(0, 0, "fetch_msg: alloc msg failed !!");
+ return NULL;
+ }
+
+ AGAIN:
+ r=mISDN_read(midev,msg->data,MAX_MSG_SIZE, TIMEOUT_10SEC);
+ msg->len=r;
+
+ if (r==0) {
+ free_msg(msg); /* danger, cauz usualy freeing in main_loop */
+ cb_log(6,0,"Got empty Msg..\n");
+ return NULL;
+ }
+
+ if (r<0) {
+ if (errno == EAGAIN) {
+ /*we wait for mISDN here*/
+ cb_log(4,0,"mISDN_read wants us to wait\n");
+ usleep(5000);
+ goto AGAIN;
+ }
+
+ cb_log(0,0,"mISDN_read returned :%d error:%s (%d)\n",r,strerror(errno),errno);
+ }
+
+#if 0
+ if (!(frm->prim == (DL_DATA|INDICATION) )|| (frm->prim == (PH_DATA|INDICATION)))
+ cb_log(0,0,"prim: %x dinfo:%x addr:%x msglen:%d frm->len:%d\n",frm->prim, frm->dinfo, frm->addr, msg->len,frm->len );
+#endif
+ return msg;
+}
+
+void misdn_lib_isdn_l1watcher(int port)
+{
+ struct misdn_stack *stack;
+
+ for (stack = glob_mgr->stack_list; stack && (stack->port != port); stack = stack->next)
+ ;
+
+ if (stack) {
+ cb_log(4, port, "Checking L1 State\n");
+ if (!stack->l1link) {
+ cb_log(4, port, "L1 State Down, trying to get it up again\n");
+ misdn_lib_get_short_status(stack);
+ misdn_lib_get_l1_up(stack);
+ misdn_lib_get_l2_up(stack);
+ }
+ }
+}
+
+static void misdn_lib_isdn_event_catcher(void *arg)
+{
+ struct misdn_lib *mgr = arg;
+ int zero_frm=0 , fff_frm=0 ;
+ int midev= mgr->midev;
+ int port=0;
+
+ while (1) {
+ msg_t *msg = fetch_msg(midev);
+ iframe_t *frm;
+
+
+ if (!msg) continue;
+
+ frm = (iframe_t*) msg->data;
+
+ /** When we make a call from NT2Ast we get this frames **/
+ if (frm->len == 0 && frm->addr == 0 && frm->dinfo == 0 && frm->prim == 0 ) {
+ zero_frm++;
+ free_msg(msg);
+ continue;
+ } else {
+ if (zero_frm) {
+ cb_log(0, port, "*** Alert: %d zero_frms caught\n", zero_frm);
+ zero_frm = 0 ;
+ }
+ }
+
+ /** I get this sometimes after setup_bc **/
+ if (frm->len == 0 && frm->dinfo == 0 && frm->prim == 0xffffffff ) {
+ fff_frm++;
+ free_msg(msg);
+ continue;
+ } else {
+ if (fff_frm) {
+ cb_log(0, port, "*** Alert: %d fff_frms caught\n", fff_frm);
+ fff_frm = 0 ;
+ }
+ }
+
+ manager_isdn_handler(frm, msg);
+ }
+
+}
+
+
+/** App Interface **/
+
+int te_lib_init() {
+ char buff[1025] = "";
+ iframe_t *frm=(iframe_t*)buff;
+ int midev=mISDN_open();
+ int ret;
+
+ if (midev<=0) return midev;
+
+/* create entity for layer 3 TE-mode */
+ mISDN_write_frame(midev, buff, 0, MGR_NEWENTITY | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
+ ret = mISDN_read_frame(midev, frm, sizeof(iframe_t), 0, MGR_NEWENTITY | CONFIRM, TIMEOUT_1SEC);
+
+ if (ret < mISDN_HEADER_LEN) {
+ noentity:
+ fprintf(stderr, "cannot request MGR_NEWENTITY from mISDN: %s\n",strerror(errno));
+ exit(-1);
+ }
+
+ entity = frm->dinfo & 0xffff ;
+
+ if (!entity)
+ goto noentity;
+
+ return midev;
+
+}
+
+void te_lib_destroy(int midev)
+{
+ char buf[1024];
+ mISDN_write_frame(midev, buf, 0, MGR_DELENTITY | REQUEST, entity, 0, NULL, TIMEOUT_1SEC);
+
+ cb_log(4, 0, "Entetity deleted\n");
+ mISDN_close(midev);
+ cb_log(4, 0, "midev closed\n");
+}
+
+
+
+void misdn_lib_transfer(struct misdn_bchannel* holded_bc)
+{
+ holded_bc->holded=0;
+}
+
+struct misdn_bchannel *manager_find_bc_by_pid(int pid)
+{
+ struct misdn_stack *stack;
+ int i;
+
+ for (stack=glob_mgr->stack_list;
+ stack;
+ stack=stack->next) {
+ for (i=0; i<=stack->b_num; i++)
+ if (stack->bc[i].pid == pid) return &stack->bc[i];
+ }
+
+ return NULL;
+}
+
+struct misdn_bchannel *manager_find_bc_holded(struct misdn_bchannel* bc)
+{
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+ return find_bc_holded(stack);
+}
+
+
+
+static void prepare_bc(struct misdn_bchannel*bc, int channel)
+{
+ bc->channel = channel;
+ bc->channel_preselected = channel?1:0;
+ bc->in_use = 1;
+ bc->need_disconnect=1;
+ bc->need_release=1;
+ bc->need_release_complete=1;
+ bc->cause=16;
+
+ if (++mypid>5000) mypid=1;
+ bc->pid=mypid;
+
+#if 0
+ bc->addr=0;
+ bc->b_stid=0;
+ bc->layer_id=0;
+#endif
+}
+
+struct misdn_bchannel* misdn_lib_get_free_bc(int port, int channel, int inout, int dec)
+{
+ struct misdn_stack *stack;
+ int i;
+
+ if (channel < 0 || channel > MAX_BCHANS) {
+ cb_log(0,port,"Requested channel out of bounds (%d)\n",channel);
+ return NULL;
+ }
+
+ for (stack=glob_mgr->stack_list; stack; stack=stack->next) {
+
+ if (stack->port == port) {
+ int maxnum;
+
+ if (stack->blocked) {
+ cb_log(0,port,"Port is blocked\n");
+ return NULL;
+ }
+
+ if (channel > 0) {
+ if (channel <= stack->b_num) {
+ for (i = 0; i < stack->b_num; i++) {
+ if (stack->bc[i].in_use && stack->bc[i].channel == channel) {
+ cb_log(0,port,"Requested channel:%d on port:%d is already in use\n",channel, port);
+ return NULL;
+ }
+ }
+ } else {
+ cb_log(0,port,"Requested channel:%d is out of bounds on port:%d\n",channel, port);
+ return NULL;
+ }
+ }
+
+ maxnum = inout && !stack->pri && !stack->ptp ? stack->b_num + 1 : stack->b_num;
+
+ if (dec) {
+ for (i = maxnum-1; i>=0; i--) {
+ if (!stack->bc[i].in_use) {
+ /* 3. channel on bri means CW*/
+ if (!stack->pri && i==stack->b_num)
+ stack->bc[i].cw=1;
+
+ prepare_bc(&stack->bc[i], channel);
+ stack->bc[i].dec=1;
+ return &stack->bc[i];
+ }
+ }
+ } else {
+ for (i = 0; i <maxnum; i++) {
+ if (!stack->bc[i].in_use) {
+ /* 3. channel on bri means CW*/
+ if (!stack->pri && i==stack->b_num)
+ stack->bc[i].cw=1;
+
+ prepare_bc(&stack->bc[i], channel);
+ return &stack->bc[i];
+ }
+ }
+ }
+
+ cb_log(1,port,"There is no free channel on port (%d)\n",port);
+ return NULL;
+ }
+ }
+
+ cb_log(0,port,"Port is not configured (%d)\n",port);
+ return NULL;
+}
+
+
+static char *fac2str (enum FacFunction func)
+{
+ struct arr_el {
+ enum FacFunction p;
+ char *s ;
+ } arr[] = {
+ { Fac_None, "Fac_None" },
+ { Fac_CD, "Fac_CD"},
+ };
+
+ int i;
+
+ for (i=0; i < sizeof(arr)/sizeof( struct arr_el) ; i ++)
+ if ( arr[i].p==func) return arr[i].s;
+
+ return "unknown";
+}
+
+void misdn_lib_log_ies(struct misdn_bchannel *bc)
+{
+ struct misdn_stack *stack;
+
+ if (!bc) return;
+
+ stack = get_stack_by_bc(bc);
+
+ if (!stack) return;
+
+ cb_log(2, stack->port, " --> channel:%d mode:%s cause:%d ocause:%d rad:%s cad:%s\n", bc->channel, stack->nt?"NT":"TE", bc->cause, bc->out_cause, bc->rad, bc->cad);
+
+ cb_log(2, stack->port,
+ " --> info_dad:%s onumplan:%c dnumplan:%c rnumplan:%c cpnnumplan:%c\n",
+ bc->info_dad,
+ bc->onumplan>=0?'0'+bc->onumplan:' ',
+ bc->dnumplan>=0?'0'+bc->dnumplan:' ',
+ bc->rnumplan>=0?'0'+bc->rnumplan:' ',
+ bc->cpnnumplan>=0?'0'+bc->cpnnumplan:' '
+ );
+
+ cb_log(3, stack->port, " --> caps:%s pi:%x keypad:%s sending_complete:%d\n", bearer2str(bc->capability),bc->progress_indicator, bc->keypad, bc->sending_complete);
+ cb_log(4, stack->port, " --> screen:%d --> pres:%d\n",
+ bc->screen, bc->pres);
+
+ cb_log(4, stack->port, " --> addr:%x l3id:%x b_stid:%x layer_id:%x\n", bc->addr, bc->l3_id, bc->b_stid, bc->layer_id);
+
+ cb_log(4, stack->port, " --> facility:%s out_facility:%s\n",fac2str(bc->fac_in.Function),fac2str(bc->fac_out.Function));
+
+ cb_log(5, stack->port, " --> urate:%d rate:%d mode:%d user1:%d\n", bc->urate, bc->rate, bc->mode,bc->user1);
+
+ cb_log(5, stack->port, " --> bc:%x h:%d sh:%d\n", bc, bc->holded, bc->stack_holder);
+}
+
+void misdn_send_lock(struct misdn_bchannel *bc);
+void misdn_send_unlock(struct misdn_bchannel *bc);
+
+#define RETURN(a,b) {retval=a; goto b;}
+
+void misdn_send_lock(struct misdn_bchannel *bc)
+{
+ //cb_log(0,bc->port,"Locking bc->pid:%d\n", bc->pid);
+ if (bc->send_lock)
+ pthread_mutex_lock(&bc->send_lock->lock);
+}
+
+void misdn_send_unlock(struct misdn_bchannel *bc)
+{
+ //cb_log(0,bc->port,"UnLocking bc->pid:%d\n", bc->pid);
+ if (bc->send_lock)
+ pthread_mutex_unlock(&bc->send_lock->lock);
+}
+
+int misdn_lib_send_event(struct misdn_bchannel *bc, enum event_e event )
+{
+ msg_t *msg;
+ int retval=0;
+ struct misdn_stack *stack;
+
+ if (!bc) RETURN(-1,OUT_POST_UNLOCK);
+
+ stack = get_stack_by_bc(bc);
+
+ if (!stack) {
+ cb_log(0,bc->port,"SENDEVENT: no Stack for event:%s oad:%s dad:%s \n", isdn_get_info(msgs_g, event, 0), bc->oad, bc->dad);
+ RETURN(-1,OUT);
+ }
+
+ misdn_send_lock(bc);
+
+
+ cb_log(6,stack->port,"SENDEVENT: stack->nt:%d stack->uperid:%x\n",stack->nt, stack->upper_id);
+
+ if ( stack->nt && !stack->l1link) {
+ /** Queue Event **/
+ bc->evq=event;
+ cb_log(1, stack->port, "Queueing Event %s because L1 is down (btw. Activating L1)\n", isdn_get_info(msgs_g, event, 0));
+ misdn_lib_get_l1_up(stack);
+ RETURN(0,OUT);
+ }
+
+ cb_log(1, stack->port, "I SEND:%s oad:%s dad:%s pid:%d\n", isdn_get_info(msgs_g, event, 0), bc->oad, bc->dad, bc->pid);
+ cb_log(4, stack->port, " --> bc_state:%s\n",bc_state2str(bc->bc_state));
+ misdn_lib_log_ies(bc);
+
+ switch (event) {
+ case EVENT_SETUP:
+ if (create_process(glob_mgr->midev, bc)<0) {
+ cb_log(0, stack->port, " No free channel at the moment @ send_event\n");
+
+ RETURN(-ENOCHAN,OUT);
+ }
+ break;
+
+ case EVENT_PROGRESS:
+ case EVENT_ALERTING:
+ case EVENT_PROCEEDING:
+ case EVENT_SETUP_ACKNOWLEDGE:
+ case EVENT_CONNECT:
+ if (!stack->nt) break;
+
+ case EVENT_RETRIEVE_ACKNOWLEDGE:
+
+ if (stack->nt) {
+ if (bc->channel <=0 ) { /* else we have the channel already */
+ if (find_free_chan_in_stack(stack, bc, 0, 0)<0) {
+ cb_log(0, stack->port, " No free channel at the moment\n");
+ /*FIXME: add disconnect*/
+ RETURN(-ENOCHAN,OUT);
+ }
+ }
+ /* Its that i generate channels */
+ }
+
+ retval=setup_bc(bc);
+ if (retval == -EINVAL) {
+ cb_log(0,bc->port,"send_event: setup_bc failed\n");
+ }
+
+ if (misdn_cap_is_speech(bc->capability)) {
+ if ((event==EVENT_CONNECT)||(event==EVENT_RETRIEVE_ACKNOWLEDGE)) {
+ if ( *bc->crypt_key ) {
+ cb_log(4, stack->port, " --> ENABLING BLOWFISH channel:%d oad%d:%s dad%d:%s \n", bc->channel, bc->onumplan,bc->oad, bc->dnumplan,bc->dad);
+
+ manager_ph_control_block(bc, BF_ENABLE_KEY, bc->crypt_key, strlen(bc->crypt_key) );
+ }
+
+ if (!bc->nodsp) manager_ph_control(bc, DTMF_TONE_START, 0);
+ manager_ec_enable(bc);
+
+ if (bc->txgain != 0) {
+ cb_log(4, stack->port, "--> Changing txgain to %d\n", bc->txgain);
+ manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain);
+ }
+
+ if ( bc->rxgain != 0 ) {
+ cb_log(4, stack->port, "--> Changing rxgain to %d\n", bc->rxgain);
+ manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain);
+ }
+ }
+ }
+ break;
+
+ case EVENT_HOLD_ACKNOWLEDGE:
+ {
+ struct misdn_bchannel *holded_bc=malloc(sizeof(struct misdn_bchannel));
+ if (!holded_bc) {
+ cb_log(0,bc->port, "Could not allocate holded_bc!!!\n");
+ RETURN(-1,OUT);
+ }
+
+ /*backup the bc*/
+ memcpy(holded_bc,bc,sizeof(struct misdn_bchannel));
+ holded_bc->holded=1;
+ bc_state_change(holded_bc,BCHAN_CLEANED);
+
+ stack_holder_add(stack,holded_bc);
+
+ /*kill the bridge and clean the bchannel*/
+ if (stack->nt) {
+ int channel;
+ if (bc->bc_state == BCHAN_BRIDGED) {
+ struct misdn_bchannel *bc2;
+
+ misdn_split_conf(bc,bc->conf_id);
+ bc2 = find_bc_by_confid(bc->conf_id);
+ if (!bc2) {
+ cb_log(0,bc->port,"We have no second bc in bridge???\n");
+ } else {
+ misdn_split_conf(bc2,bc->conf_id);
+ }
+ }
+
+ channel = bc->channel;
+
+ empty_bc(bc);
+ clean_up_bc(bc);
+
+ if (channel>0)
+ empty_chan_in_stack(stack,channel);
+
+ bc->in_use=0;
+ }
+
+ }
+ break;
+
+ /* finishing the channel eh ? */
+ case EVENT_DISCONNECT:
+ if (!bc->need_disconnect) {
+ cb_log(0,bc->port," --> we have already send Disconnect\n");
+ RETURN(-1,OUT);
+ }
+
+ bc->need_disconnect=0;
+ break;
+ case EVENT_RELEASE:
+ if (!bc->need_release) {
+ cb_log(0,bc->port," --> we have already send Release\n");
+ RETURN(-1,OUT);
+ }
+ bc->need_disconnect=0;
+ bc->need_release=0;
+ break;
+ case EVENT_RELEASE_COMPLETE:
+ if (!bc->need_release_complete) {
+ cb_log(0,bc->port," --> we have already send Release_complete\n");
+ RETURN(-1,OUT);
+ }
+ bc->need_disconnect=0;
+ bc->need_release=0;
+ bc->need_release_complete=0;
+
+ if (!stack->nt) {
+ /*create clenaup in TE*/
+ int channel=bc->channel;
+
+ int tmpcause=bc->cause;
+ int tmp_out_cause=bc->out_cause;
+ empty_bc(bc);
+ bc->cause=tmpcause;
+ bc->out_cause=tmp_out_cause;
+ clean_up_bc(bc);
+
+ if (channel>0)
+ empty_chan_in_stack(stack,channel);
+
+ bc->in_use=0;
+ }
+ break;
+
+ case EVENT_CONNECT_ACKNOWLEDGE:
+
+ if ( bc->nt || misdn_cap_is_speech(bc->capability)) {
+ int retval=setup_bc(bc);
+ if (retval == -EINVAL){
+ cb_log(0,bc->port,"send_event: setup_bc failed\n");
+
+ }
+ }
+
+ if (misdn_cap_is_speech(bc->capability)) {
+ if ( !bc->nodsp) manager_ph_control(bc, DTMF_TONE_START, 0);
+ manager_ec_enable(bc);
+
+ if ( bc->txgain != 0 ) {
+ cb_log(4, stack->port, "--> Changing txgain to %d\n", bc->txgain);
+ manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain);
+ }
+ if ( bc->rxgain != 0 ) {
+ cb_log(4, stack->port, "--> Changing rxgain to %d\n", bc->rxgain);
+ manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* Later we should think about sending bchannel data directly to misdn. */
+ msg = isdn_msg_build_event(msgs_g, bc, event, stack->nt);
+ msg_queue_tail(&stack->downqueue, msg);
+ sem_post(&glob_mgr->new_msg);
+
+OUT:
+ misdn_send_unlock(bc);
+
+OUT_POST_UNLOCK:
+ return retval;
+}
+
+
+static int handle_err(msg_t *msg)
+{
+ iframe_t *frm = (iframe_t*) msg->data;
+
+
+ if (!frm->addr) {
+ static int cnt=0;
+ if (!cnt)
+ cb_log(0,0,"mISDN Msg without Address pr:%x dinfo:%x\n",frm->prim,frm->dinfo);
+ cnt++;
+ if (cnt>100) {
+ cb_log(0,0,"mISDN Msg without Address pr:%x dinfo:%x (already more than 100 of them)\n",frm->prim,frm->dinfo);
+ cnt=0;
+ }
+
+ free_msg(msg);
+ return 1;
+
+ }
+
+ switch (frm->prim) {
+ case MGR_SETSTACK|INDICATION:
+ return handle_bchan(msg);
+ break;
+
+ case MGR_SETSTACK|CONFIRM:
+ case MGR_CLEARSTACK|CONFIRM:
+ free_msg(msg) ;
+ return 1;
+ break;
+
+ case DL_DATA|CONFIRM:
+ cb_log(4,0,"DL_DATA|CONFIRM\n");
+ free_msg(msg);
+ return 1;
+
+ case PH_CONTROL|CONFIRM:
+ cb_log(4,0,"PH_CONTROL|CONFIRM\n");
+ free_msg(msg);
+ return 1;
+
+ case DL_DATA|INDICATION:
+ {
+ int port=(frm->addr&MASTER_ID_MASK) >> 8;
+ int channel=(frm->addr&CHILD_ID_MASK) >> 16;
+ struct misdn_bchannel *bc;
+
+ /*we flush the read buffer here*/
+
+ cb_log(9,0,"BCHAN DATA without BC: addr:%x port:%d channel:%d\n",frm->addr, port,channel);
+
+ free_msg(msg);
+ return 1;
+
+
+ bc = find_bc_by_channel(port, channel);
+
+ if (!bc) {
+ struct misdn_stack *stack = find_stack_by_port(port);
+
+ if (!stack) {
+ cb_log(0,0," --> stack not found\n");
+ free_msg(msg);
+ return 1;
+ }
+
+ cb_log(0,0," --> bc not found by channel\n");
+ if (stack->l2link)
+ misdn_lib_get_l2_down(stack);
+
+ if (stack->l1link)
+ misdn_lib_get_l1_down(stack);
+
+ free_msg(msg);
+ return 1;
+ }
+
+ cb_log(3,port," --> BC in state:%s\n", bc_state2str(bc->bc_state));
+ }
+ }
+
+ return 0;
+}
+
+#if 0
+static int queue_l2l3(msg_t *msg)
+{
+ iframe_t *frm= (iframe_t*)msg->data;
+ struct misdn_stack *stack;
+ stack=find_stack_by_addr( frm->addr );
+
+
+ if (!stack) {
+ return 0;
+ }
+
+ msg_queue_tail(&stack->upqueue, msg);
+ sem_post(&glob_mgr->new_msg);
+ return 1;
+}
+#endif
+
+int manager_isdn_handler(iframe_t *frm ,msg_t *msg)
+{
+
+ if (frm->dinfo==0xffffffff && frm->prim==(PH_DATA|CONFIRM)) {
+ cb_log(0,0,"SERIOUS BUG, dinfo == 0xffffffff, prim == PH_DATA | CONFIRM !!!!\n");
+ }
+
+ if ( ((frm->addr | ISDN_PID_BCHANNEL_BIT )>> 28 ) == 0x5) {
+ if (handle_bchan(msg)) {
+ return 0 ;
+ }
+ }
+
+#ifdef RECV_FRM_SYSLOG_DEBUG
+ syslog(LOG_NOTICE,"mISDN recv: P(%02d): ADDR:%x PRIM:%x DINFO:%x\n",stack->port, frm->addr, frm->prim, frm->dinfo);
+#endif
+
+ if (handle_timers(msg))
+ return 0 ;
+
+
+ if (handle_mgmt(msg))
+ return 0 ;
+
+ if (handle_l2(msg))
+ return 0 ;
+
+ /* Its important to handle l1 AFTER l2 */
+ if (handle_l1(msg))
+ return 0 ;
+
+ if (handle_frm_nt(msg)) {
+ return 0;
+ }
+
+ if (handle_frm(msg)) {
+ return 0;
+ }
+
+ if (handle_err(msg)) {
+ return 0 ;
+ }
+
+ cb_log(0, 0, "Unhandled Message: prim %x len %d from addr %x, dinfo %x on this port.\n",frm->prim, frm->len, frm->addr, frm->dinfo);
+ free_msg(msg);
+
+
+ return 0;
+}
+
+
+
+
+int misdn_lib_get_port_info(int port)
+{
+ msg_t *msg=alloc_msg(MAX_MSG_SIZE);
+ iframe_t *frm;
+ struct misdn_stack *stack=find_stack_by_port(port);
+ if (!msg) {
+ cb_log(0, port, "misgn_lib_get_port: alloc_msg failed!\n");
+ return -1;
+ }
+ frm=(iframe_t*)msg->data;
+ if (!stack ) {
+ cb_log(0, port, "There is no Stack for this port.\n");
+ return -1;
+ }
+ /* activate bchannel */
+ frm->prim = CC_STATUS_ENQUIRY | REQUEST;
+
+ frm->addr = stack->upper_id| FLG_MSG_DOWN;
+
+ frm->dinfo = 0;
+ frm->len = 0;
+
+ msg_queue_tail(&glob_mgr->activatequeue, msg);
+ sem_post(&glob_mgr->new_msg);
+
+
+ return 0;
+}
+
+
+int queue_cleanup_bc(struct misdn_bchannel *bc)
+{
+ msg_t *msg=alloc_msg(MAX_MSG_SIZE);
+ iframe_t *frm;
+ if (!msg) {
+ cb_log(0, bc->port, "misgn_lib_get_port: alloc_msg failed!\n");
+ return -1;
+ }
+ frm=(iframe_t*)msg->data;
+
+ /* activate bchannel */
+ frm->prim = MGR_CLEARSTACK| REQUEST;
+
+ frm->addr = bc->l3_id;
+
+ frm->dinfo = bc->port;
+ frm->len = 0;
+
+ msg_queue_tail(&glob_mgr->activatequeue, msg);
+ sem_post(&glob_mgr->new_msg);
+
+ return 0;
+
+}
+
+int misdn_lib_pid_restart(int pid)
+{
+ struct misdn_bchannel *bc=manager_find_bc_by_pid(pid);
+
+ if (bc) {
+ manager_clean_bc(bc);
+ }
+ return 0;
+}
+
+/*Sends Restart message for every bchnanel*/
+int misdn_lib_send_restart(int port, int channel)
+{
+ struct misdn_stack *stack=find_stack_by_port(port);
+ struct misdn_bchannel dummybc;
+ /*default is all channels*/
+ cb_log(0, port, "Sending Restarts on this port.\n");
+
+ misdn_make_dummy(&dummybc, stack->port, MISDN_ID_GLOBAL, stack->nt, 0);
+
+ /*default is all channels*/
+ if (channel <0) {
+ dummybc.channel=-1;
+ cb_log(0, port, "Restarting and all Interfaces\n");
+ misdn_lib_send_event(&dummybc, EVENT_RESTART);
+
+ return 0;
+ }
+
+ /*if a channel is specified we restart only this one*/
+ if (channel >0) {
+ int cnt;
+ dummybc.channel=channel;
+ cb_log(0, port, "Restarting and cleaning channel %d\n",channel);
+ misdn_lib_send_event(&dummybc, EVENT_RESTART);
+ /* clean up chan in stack, to be sure we don't think it's
+ * in use anymore */
+ for (cnt=0; cnt<=stack->b_num; cnt++) {
+ if (stack->bc[cnt].channel == channel) {
+ empty_bc(&stack->bc[cnt]);
+ clean_up_bc(&stack->bc[cnt]);
+ stack->bc[cnt].in_use=0;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*reinitializes the L2/L3*/
+int misdn_lib_port_restart(int port)
+{
+ struct misdn_stack *stack=find_stack_by_port(port);
+
+ cb_log(0, port, "Restarting this port.\n");
+ if (stack) {
+ cb_log(0, port, "Stack:%p\n",stack);
+
+ clear_l3(stack);
+ {
+ msg_t *msg=alloc_msg(MAX_MSG_SIZE);
+ iframe_t *frm;
+
+ if (!msg) {
+ cb_log(0, port, "port_restart: alloc_msg failed\n");
+ return -1;
+ }
+
+ frm=(iframe_t*)msg->data;
+ /* we must activate if we are deactivated */
+ /* activate bchannel */
+ frm->prim = DL_RELEASE | REQUEST;
+ frm->addr = stack->upper_id | FLG_MSG_DOWN;
+
+ frm->dinfo = 0;
+ frm->len = 0;
+ msg_queue_tail(&glob_mgr->activatequeue, msg);
+ sem_post(&glob_mgr->new_msg);
+ }
+
+ if (stack->nt)
+ misdn_lib_reinit_nt_stack(stack->port);
+
+ }
+
+ return 0;
+}
+
+
+
+sem_t handler_started;
+
+static void manager_event_handler(void *arg)
+{
+ sem_post(&handler_started);
+ while (1) {
+ struct misdn_stack *stack;
+ msg_t *msg;
+
+ /** wait for events **/
+ sem_wait(&glob_mgr->new_msg);
+
+ for (msg=msg_dequeue(&glob_mgr->activatequeue);
+ msg;
+ msg=msg_dequeue(&glob_mgr->activatequeue)
+ )
+ {
+
+ iframe_t *frm = (iframe_t*) msg->data ;
+
+ switch ( frm->prim) {
+
+ case MGR_CLEARSTACK | REQUEST:
+ /*a queued bchannel cleanup*/
+ {
+ struct misdn_stack *stack=find_stack_by_port(frm->dinfo);
+ struct misdn_bchannel *bc;
+ if (!stack) {
+ cb_log(0,0,"no stack found with port [%d]!! so we cannot cleanup the bc\n",frm->dinfo);
+ free_msg(msg);
+ break;
+ }
+
+ bc = find_bc_by_l3id(stack, frm->addr);
+ if (bc) {
+ cb_log(1,bc->port,"CLEARSTACK queued, cleaning up\n");
+ clean_up_bc(bc);
+ } else {
+ cb_log(0,stack->port,"bc could not be cleaned correctly !! addr [%x]\n",frm->addr);
+ }
+ }
+ free_msg(msg);
+ break;
+ case MGR_SETSTACK | REQUEST :
+ break;
+ default:
+ mISDN_write(glob_mgr->midev, frm, mISDN_HEADER_LEN+frm->len, TIMEOUT_1SEC);
+ free_msg(msg);
+ }
+ }
+
+ for (stack=glob_mgr->stack_list;
+ stack;
+ stack=stack->next ) {
+
+ while ( (msg=msg_dequeue(&stack->upqueue)) ) {
+ /** Handle L2/3 Signalling after bchans **/
+ if (!handle_frm_nt(msg)) {
+ /* Maybe it's TE */
+ if (!handle_frm(msg)) {
+ /* wow none! */
+ cb_log(0,stack->port,"Wow we've got a strange issue while dequeueing a Frame\n");
+ }
+ }
+ }
+
+ /* Here we should check if we really want to
+ send all the messages we've queued, lets
+ assume we've queued a Disconnect, but
+ received it already from the other side!*/
+
+ while ( (msg=msg_dequeue(&stack->downqueue)) ) {
+ if (stack->nt ) {
+ if (stack->nst.manager_l3(&stack->nst, msg))
+ cb_log(0, stack->port, "Error@ Sending Message in NT-Stack.\n");
+
+ } else {
+ iframe_t *frm = (iframe_t *)msg->data;
+ struct misdn_bchannel *bc = find_bc_by_l3id(stack, frm->dinfo);
+ if (bc)
+ send_msg(glob_mgr->midev, bc, msg);
+ else {
+ if (frm->dinfo == MISDN_ID_GLOBAL || frm->dinfo == MISDN_ID_DUMMY ) {
+ struct misdn_bchannel dummybc;
+ cb_log(5,0," --> GLOBAL/DUMMY\n");
+ misdn_make_dummy(&dummybc, stack->port, frm->dinfo, stack->nt, 0);
+ send_msg(glob_mgr->midev, &dummybc, msg);
+ } else {
+ cb_log(0,0,"No bc for Message\n");
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+int misdn_lib_maxports_get() { /** BE AWARE WE HAVE NO CB_LOG HERE! **/
+
+ int i = mISDN_open();
+ int max=0;
+
+ if (i<0)
+ return -1;
+
+ max = mISDN_get_stack_count(i);
+
+ mISDN_close(i);
+
+ return max;
+}
+
+
+void misdn_lib_nt_keepcalls( int kc)
+{
+#ifdef FEATURE_NET_KEEPCALLS
+ if (kc) {
+ struct misdn_stack *stack=get_misdn_stack();
+ for ( ; stack; stack=stack->next) {
+ stack->nst.feature |= FEATURE_NET_KEEPCALLS;
+ }
+ }
+#endif
+}
+
+void misdn_lib_nt_debug_init( int flags, char *file )
+{
+ int static init=0;
+ char *f;
+
+ if (!flags)
+ f=NULL;
+ else
+ f=file;
+
+ if (!init) {
+ debug_init( flags , f, f, f);
+ init=1;
+ } else {
+ debug_close();
+ debug_init( flags , f, f, f);
+ }
+}
+
+
+int misdn_lib_init(char *portlist, struct misdn_lib_iface *iface, void *user_data)
+{
+ struct misdn_lib *mgr=calloc(1, sizeof(struct misdn_lib));
+ char *tok, *tokb;
+ char plist[1024];
+ int midev;
+ int port_count=0;
+
+ cb_log = iface->cb_log;
+ cb_event = iface->cb_event;
+ cb_jb_empty = iface->cb_jb_empty;
+
+ glob_mgr = mgr;
+
+ msg_init();
+
+ misdn_lib_nt_debug_init(0,NULL);
+
+ if (!portlist || (*portlist == 0) ) return 1;
+
+ init_flip_bits();
+
+ {
+ strncpy(plist,portlist, 1024);
+ plist[1023] = 0;
+ }
+
+ memcpy(tone_425_flip,tone_425,TONE_425_SIZE);
+ flip_buf_bits(tone_425_flip,TONE_425_SIZE);
+
+ memcpy(tone_silence_flip,tone_SILENCE,TONE_SILENCE_SIZE);
+ flip_buf_bits(tone_silence_flip,TONE_SILENCE_SIZE);
+
+ midev=te_lib_init();
+ mgr->midev=midev;
+
+ port_count=mISDN_get_stack_count(midev);
+
+ msg_queue_init(&mgr->activatequeue);
+
+ if (sem_init(&mgr->new_msg, 1, 0)<0)
+ sem_init(&mgr->new_msg, 0, 0);
+
+ for (tok=strtok_r(plist," ,",&tokb );
+ tok;
+ tok=strtok_r(NULL," ,",&tokb)) {
+ int port = atoi(tok);
+ struct misdn_stack *stack;
+ static int first=1;
+ int ptp=0;
+
+ if (strstr(tok, "ptp"))
+ ptp=1;
+
+ if (port > port_count) {
+ cb_log(0, port, "Couldn't Initialize this port since we have only %d ports\n", port_count);
+ exit(1);
+ }
+ stack=stack_init(midev, port, ptp);
+
+ if (!stack) {
+ perror("init_stack");
+ exit(1);
+ }
+
+ {
+ int i;
+ for(i=0;i<=stack->b_num; i++) {
+ int r;
+ if ((r=init_bc(stack, &stack->bc[i], stack->midev,port,i, "", 1))<0) {
+ cb_log(0, port, "Got Err @ init_bc :%d\n",r);
+ exit(1);
+ }
+ }
+ }
+
+ if (stack && first) {
+ mgr->stack_list=stack;
+ first=0;
+ continue;
+ }
+
+ if (stack) {
+ struct misdn_stack * help;
+ for ( help=mgr->stack_list; help; help=help->next )
+ if (help->next == NULL) break;
+ help->next=stack;
+ }
+
+ }
+
+ if (sem_init(&handler_started, 1, 0)<0)
+ sem_init(&handler_started, 0, 0);
+
+ cb_log(8, 0, "Starting Event Handler\n");
+ pthread_create( &mgr->event_handler_thread, NULL,(void*)manager_event_handler, mgr);
+
+ sem_wait(&handler_started) ;
+ cb_log(8, 0, "Starting Event Catcher\n");
+ pthread_create( &mgr->event_thread, NULL, (void*)misdn_lib_isdn_event_catcher, mgr);
+
+ cb_log(8, 0, "Event Catcher started\n");
+
+ global_state= MISDN_INITIALIZED;
+
+ return (mgr == NULL);
+}
+
+void misdn_lib_destroy()
+{
+ struct misdn_stack *help;
+ int i;
+
+ for ( help=glob_mgr->stack_list; help; help=help->next ) {
+ for(i=0;i<=help->b_num; i++) {
+ char buf[1024];
+ mISDN_write_frame(help->midev, buf, help->bc[i].addr, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
+ help->bc[i].addr = 0;
+ }
+ cb_log (1, help->port, "Destroying this port.\n");
+ stack_destroy(help);
+ }
+
+ if (global_state == MISDN_INITIALIZED) {
+ cb_log(4, 0, "Killing Handler Thread\n");
+ if ( pthread_cancel(glob_mgr->event_handler_thread) == 0 ) {
+ cb_log(4, 0, "Joining Handler Thread\n");
+ pthread_join(glob_mgr->event_handler_thread, NULL);
+ }
+
+ cb_log(4, 0, "Killing Main Thread\n");
+ if ( pthread_cancel(glob_mgr->event_thread) == 0 ) {
+ cb_log(4, 0, "Joining Main Thread\n");
+ pthread_join(glob_mgr->event_thread, NULL);
+ }
+ }
+
+ cb_log(1, 0, "Closing mISDN device\n");
+ te_lib_destroy(glob_mgr->midev);
+}
+
+char *manager_isdn_get_info(enum event_e event)
+{
+ return isdn_get_info(msgs_g , event, 0);
+}
+
+void manager_bchannel_activate(struct misdn_bchannel *bc)
+{
+ char buf[128];
+
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+
+ if (!stack) {
+ cb_log(0, bc->port, "bchannel_activate: Stack not found !");
+ return ;
+ }
+
+ /* we must activate if we are deactivated */
+ clear_ibuffer(bc->astbuf);
+
+ cb_log(5, stack->port, "$$$ Bchan Activated addr %x\n", bc->addr);
+
+ mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_DOWN, DL_ESTABLISH | REQUEST, 0,0, NULL, TIMEOUT_1SEC);
+
+ return ;
+}
+
+
+void manager_bchannel_deactivate(struct misdn_bchannel * bc)
+{
+
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+ iframe_t dact;
+ char buf[128];
+
+ switch (bc->bc_state) {
+ case BCHAN_ACTIVATED:
+ break;
+ case BCHAN_BRIDGED:
+ misdn_split_conf(bc,bc->conf_id);
+ break;
+ default:
+ cb_log( 4, bc->port,"bchan_deactivate: called but not activated\n");
+ return ;
+
+ }
+
+ cb_log(5, stack->port, "$$$ Bchan deActivated addr %x\n", bc->addr);
+
+ bc->generate_tone=0;
+
+ dact.prim = DL_RELEASE | REQUEST;
+ dact.addr = bc->addr | FLG_MSG_DOWN;
+ dact.dinfo = 0;
+ dact.len = 0;
+ mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_DOWN, DL_RELEASE|REQUEST,0,0,NULL, TIMEOUT_1SEC);
+
+ clear_ibuffer(bc->astbuf);
+
+ bc_state_change(bc,BCHAN_RELEASE);
+
+ return;
+}
+
+
+int misdn_lib_tx2misdn_frm(struct misdn_bchannel *bc, void *data, int len)
+{
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+ char buf[4096 + mISDN_HEADER_LEN];
+ iframe_t *frm = (iframe_t*)buf;
+ int r;
+
+ switch (bc->bc_state) {
+ case BCHAN_ACTIVATED:
+ case BCHAN_BRIDGED:
+ break;
+ default:
+ cb_log(3, bc->port, "BC not yet activated (state:%s)\n",bc_state2str(bc->bc_state));
+ return -1;
+ }
+
+ frm->prim = DL_DATA|REQUEST;
+ frm->dinfo = 0;
+ frm->addr = bc->addr | FLG_MSG_DOWN ;
+
+ frm->len = len;
+ memcpy(&buf[mISDN_HEADER_LEN], data,len);
+
+ if ( misdn_cap_is_speech(bc->capability) )
+ flip_buf_bits( &buf[mISDN_HEADER_LEN], len);
+ else
+ cb_log(6, stack->port, "Writing %d data bytes\n",len);
+
+ cb_log(9, stack->port, "Writing %d bytes 2 mISDN\n",len);
+ r=mISDN_write(stack->midev, buf, frm->len + mISDN_HEADER_LEN, TIMEOUT_INFINIT);
+ return 0;
+}
+
+
+
+/*
+ * send control information to the channel (dsp-module)
+ */
+void manager_ph_control(struct misdn_bchannel *bc, int c1, int c2)
+{
+ unsigned char buffer[mISDN_HEADER_LEN+2*sizeof(int)];
+ iframe_t *ctrl = (iframe_t *)buffer; /* preload data */
+ unsigned int *d = (unsigned int*)&ctrl->data.p;
+ /*struct misdn_stack *stack=get_stack_by_bc(bc);*/
+
+ cb_log(4,bc->port,"ph_control: c1:%x c2:%x\n",c1,c2);
+
+ ctrl->prim = PH_CONTROL | REQUEST;
+ ctrl->addr = bc->addr | FLG_MSG_DOWN;
+ ctrl->dinfo = 0;
+ ctrl->len = sizeof(unsigned int)*2;
+ *d++ = c1;
+ *d++ = c2;
+ mISDN_write(glob_mgr->midev, ctrl, mISDN_HEADER_LEN+ctrl->len, TIMEOUT_1SEC);
+}
+
+/*
+ * allow live control of channel parameters
+ */
+void isdn_lib_update_rxgain (struct misdn_bchannel *bc)
+{
+ manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain);
+}
+
+void isdn_lib_update_txgain (struct misdn_bchannel *bc)
+{
+ manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain);
+}
+
+void isdn_lib_update_ec (struct misdn_bchannel *bc)
+{
+#ifdef MISDN_1_2
+ if (*bc->pipeline)
+#else
+ if (bc->ec_enable)
+#endif
+ manager_ec_enable(bc);
+ else
+ manager_ec_disable(bc);
+}
+
+void isdn_lib_stop_dtmf (struct misdn_bchannel *bc)
+{
+ manager_ph_control(bc, DTMF_TONE_STOP, 0);
+}
+
+/*
+ * send control information to the channel (dsp-module)
+ */
+void manager_ph_control_block(struct misdn_bchannel *bc, int c1, void *c2, int c2_len)
+{
+ unsigned char buffer[mISDN_HEADER_LEN+sizeof(int)+c2_len];
+ iframe_t *ctrl = (iframe_t *)buffer;
+ unsigned int *d = (unsigned int *)&ctrl->data.p;
+ /*struct misdn_stack *stack=get_stack_by_bc(bc);*/
+
+ ctrl->prim = PH_CONTROL | REQUEST;
+ ctrl->addr = bc->addr | FLG_MSG_DOWN;
+ ctrl->dinfo = 0;
+ ctrl->len = sizeof(unsigned int) + c2_len;
+ *d++ = c1;
+ memcpy(d, c2, c2_len);
+ mISDN_write(glob_mgr->midev, ctrl, mISDN_HEADER_LEN+ctrl->len, TIMEOUT_1SEC);
+}
+
+
+
+
+void manager_clean_bc(struct misdn_bchannel *bc )
+{
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+
+ if (bc->channel>0)
+ empty_chan_in_stack(stack, bc->channel);
+ empty_bc(bc);
+ bc->in_use=0;
+
+ cb_event(EVENT_CLEANUP, bc, NULL);
+}
+
+
+void stack_holder_add(struct misdn_stack *stack, struct misdn_bchannel *holder)
+{
+ struct misdn_bchannel *help;
+ cb_log(4,stack->port, "*HOLDER: add %x\n",holder->l3_id);
+
+ holder->stack_holder=1;
+
+ if (!stack ) return ;
+
+ holder->next=NULL;
+
+ if (!stack->holding) {
+ stack->holding = holder;
+ return;
+ }
+
+ for (help=stack->holding;
+ help;
+ help=help->next) {
+ if (!help->next) {
+ help->next=holder;
+ break;
+ }
+ }
+
+}
+
+void stack_holder_remove(struct misdn_stack *stack, struct misdn_bchannel *holder)
+{
+ struct misdn_bchannel *h1;
+
+ if (!holder->stack_holder) return;
+
+ holder->stack_holder=0;
+
+ cb_log(4,stack->port, "*HOLDER: remove %x\n",holder->l3_id);
+ if (!stack || ! stack->holding) return;
+
+ if (holder == stack->holding) {
+ stack->holding = stack->holding->next;
+ return;
+ }
+
+ for (h1=stack->holding;
+ h1;
+ h1=h1->next) {
+ if (h1->next == holder) {
+ h1->next=h1->next->next;
+ return ;
+ }
+ }
+}
+
+struct misdn_bchannel *stack_holder_find_bychan(struct misdn_stack *stack, int chan)
+{
+ struct misdn_bchannel *help;
+
+ cb_log(4,stack?stack->port:0, "*HOLDER: find_bychan %c\n", chan);
+
+ if (!stack) return NULL;
+
+ for (help=stack->holding;
+ help;
+ help=help->next) {
+ if (help->channel == chan) {
+ cb_log(4,stack->port, "*HOLDER: found_bychan bc\n");
+ return help;
+ }
+ }
+
+ cb_log(4,stack->port, "*HOLDER: find_bychan nothing\n");
+ return NULL;
+
+}
+
+struct misdn_bchannel *stack_holder_find(struct misdn_stack *stack, unsigned long l3id)
+{
+ struct misdn_bchannel *help;
+
+ cb_log(4,stack?stack->port:0, "*HOLDER: find %x\n",l3id);
+
+ if (!stack) return NULL;
+
+ for (help=stack->holding;
+ help;
+ help=help->next) {
+ if (help->l3_id == l3id) {
+ cb_log(4,stack->port, "*HOLDER: found bc\n");
+ return help;
+ }
+ }
+
+ cb_log(4,stack->port, "*HOLDER: find nothing\n");
+ return NULL;
+}
+
+
+
+void misdn_lib_send_tone(struct misdn_bchannel *bc, enum tone_e tone)
+{
+ char buf[mISDN_HEADER_LEN + 128] = "";
+ iframe_t *frm = (iframe_t*)buf;
+
+ switch(tone) {
+ case TONE_DIAL:
+ manager_ph_control(bc, TONE_PATT_ON, TONE_GERMAN_DIALTONE);
+ break;
+
+ case TONE_ALERTING:
+ manager_ph_control(bc, TONE_PATT_ON, TONE_GERMAN_RINGING);
+ break;
+
+ case TONE_HANGUP:
+ manager_ph_control(bc, TONE_PATT_ON, TONE_GERMAN_HANGUP);
+ break;
+
+ case TONE_NONE:
+ default:
+ manager_ph_control(bc, TONE_PATT_OFF, TONE_GERMAN_HANGUP);
+ }
+
+ frm->prim=DL_DATA|REQUEST;
+ frm->addr=bc->addr|FLG_MSG_DOWN;
+ frm->dinfo=0;
+ frm->len=128;
+
+ mISDN_write(glob_mgr->midev, frm, mISDN_HEADER_LEN+frm->len, TIMEOUT_1SEC);
+}
+
+
+void manager_ec_enable(struct misdn_bchannel *bc)
+{
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+
+ cb_log(4, stack?stack->port:0,"ec_enable\n");
+
+ if (!misdn_cap_is_speech(bc->capability)) {
+ cb_log(1, stack?stack->port:0, " --> no speech? cannot enable EC\n");
+ } else {
+
+#ifdef MISDN_1_2
+ if (*bc->pipeline) {
+ cb_log(3, stack?stack->port:0,"Sending Control PIPELINE_CFG %s\n",bc->pipeline);
+ manager_ph_control_block(bc, PIPELINE_CFG, bc->pipeline, strlen(bc->pipeline) + 1);
+ }
+#else
+ int ec_arr[2];
+
+ if (bc->ec_enable) {
+ cb_log(3, stack?stack->port:0,"Sending Control ECHOCAN_ON taps:%d\n",bc->ec_deftaps);
+
+ switch (bc->ec_deftaps) {
+ case 4:
+ case 8:
+ case 16:
+ case 32:
+ case 64:
+ case 128:
+ case 256:
+ case 512:
+ case 1024:
+ cb_log(4, stack->port, "Taps is %d\n",bc->ec_deftaps);
+ break;
+ default:
+ cb_log(0, stack->port, "Taps should be power of 2\n");
+ bc->ec_deftaps=128;
+ }
+
+ ec_arr[0]=bc->ec_deftaps;
+ ec_arr[1]=0;
+
+ manager_ph_control_block(bc, ECHOCAN_ON, ec_arr, sizeof(ec_arr));
+ }
+#endif
+ }
+}
+
+
+
+void manager_ec_disable(struct misdn_bchannel *bc)
+{
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+
+ cb_log(4, stack?stack->port:0," --> ec_disable\n");
+
+ if (!misdn_cap_is_speech(bc->capability)) {
+ cb_log(1, stack?stack->port:0, " --> no speech? cannot disable EC\n");
+ return;
+ }
+
+#ifdef MISDN_1_2
+ manager_ph_control_block(bc, PIPELINE_CFG, "", 0);
+#else
+ if ( ! bc->ec_enable) {
+ cb_log(3, stack?stack->port:0, "Sending Control ECHOCAN_OFF\n");
+ manager_ph_control(bc, ECHOCAN_OFF, 0);
+ }
+#endif
+}
+
+struct misdn_stack* get_misdn_stack() {
+ return glob_mgr->stack_list;
+}
+
+
+
+void misdn_join_conf(struct misdn_bchannel *bc, int conf_id)
+{
+ char data[16] = "";
+
+ bc_state_change(bc,BCHAN_BRIDGED);
+ manager_ph_control(bc, CMX_RECEIVE_OFF, 0);
+ manager_ph_control(bc, CMX_CONF_JOIN, conf_id);
+
+ cb_log(3,bc->port, "Joining bc:%x in conf:%d\n",bc->addr,conf_id);
+
+ misdn_lib_tx2misdn_frm(bc, data, sizeof(data) - 1);
+}
+
+
+void misdn_split_conf(struct misdn_bchannel *bc, int conf_id)
+{
+ bc_state_change(bc,BCHAN_ACTIVATED);
+ manager_ph_control(bc, CMX_RECEIVE_ON, 0);
+ manager_ph_control(bc, CMX_CONF_SPLIT, conf_id);
+
+ cb_log(4,bc->port, "Splitting bc:%x in conf:%d\n",bc->addr,conf_id);
+}
+
+void misdn_lib_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2) {
+ int conf_id = bc1->pid + 1;
+ struct misdn_bchannel *bc_list[] = { bc1, bc2, NULL };
+ struct misdn_bchannel **bc;
+
+ cb_log(4, bc1->port, "I Send: BRIDGE from:%d to:%d\n",bc1->port,bc2->port);
+
+ for (bc=bc_list; *bc; bc++) {
+ (*bc)->conf_id=conf_id;
+ cb_log(4, (*bc)->port, " --> bc_addr:%x\n",(*bc)->addr);
+
+ switch((*bc)->bc_state) {
+ case BCHAN_ACTIVATED:
+ misdn_join_conf(*bc,conf_id);
+ break;
+ default:
+ bc_next_state_change(*bc,BCHAN_BRIDGED);
+ break;
+ }
+ }
+}
+
+void misdn_lib_split_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2)
+{
+
+ struct misdn_bchannel *bc_list[]={
+ bc1,bc2,NULL
+ };
+ struct misdn_bchannel **bc;
+
+ for (bc=bc_list; *bc; bc++) {
+ if ( (*bc)->bc_state == BCHAN_BRIDGED){
+ misdn_split_conf( *bc, (*bc)->conf_id);
+ } else {
+ cb_log( 2, (*bc)->port, "BC not bridged (state:%s) so not splitting it\n",bc_state2str((*bc)->bc_state));
+ }
+ }
+
+}
+
+
+
+void misdn_lib_echo(struct misdn_bchannel *bc, int onoff)
+{
+ cb_log(3,bc->port, " --> ECHO %s\n", onoff?"ON":"OFF");
+ manager_ph_control(bc, onoff?CMX_ECHO_ON:CMX_ECHO_OFF, 0);
+}
+
+
+
+void misdn_lib_reinit_nt_stack(int port)
+{
+ struct misdn_stack *stack=find_stack_by_port(port);
+
+ if (stack) {
+ stack->l2link=0;
+ stack->blocked=0;
+
+ cleanup_Isdnl3(&stack->nst);
+ cleanup_Isdnl2(&stack->nst);
+
+
+ memset(&stack->nst, 0, sizeof(net_stack_t));
+ memset(&stack->mgr, 0, sizeof(manager_t));
+
+ stack->mgr.nst = &stack->nst;
+ stack->nst.manager = &stack->mgr;
+
+ stack->nst.l3_manager = handle_event_nt;
+ stack->nst.device = glob_mgr->midev;
+ stack->nst.cardnr = port;
+ stack->nst.d_stid = stack->d_stid;
+
+ stack->nst.feature = FEATURE_NET_HOLD;
+ if (stack->ptp)
+ stack->nst.feature |= FEATURE_NET_PTP;
+ if (stack->pri)
+ stack->nst.feature |= FEATURE_NET_CRLEN2 | FEATURE_NET_EXTCID;
+
+ stack->nst.l1_id = stack->lower_id;
+ stack->nst.l2_id = stack->upper_id;
+
+ msg_queue_init(&stack->nst.down_queue);
+
+ Isdnl2Init(&stack->nst);
+ Isdnl3Init(&stack->nst);
+
+ if (!stack->ptp)
+ misdn_lib_get_l1_up(stack);
+ misdn_lib_get_l2_up(stack);
+ }
+}
+
+
diff --git a/trunk/channels/misdn/isdn_lib.h b/trunk/channels/misdn/isdn_lib.h
new file mode 100644
index 000000000..731d497b3
--- /dev/null
+++ b/trunk/channels/misdn/isdn_lib.h
@@ -0,0 +1,488 @@
+/*
+ * Chan_Misdn -- Channel Driver for Asterisk
+ *
+ * Interface to mISDN
+ *
+ * Copyright (C) 2004, Christian Richter
+ *
+ * Christian Richter <crich@beronet.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+/*! \file \brief
+ *
+ * Interface to mISDN
+ *
+ * \author Christian Richter <crich@beronet.com>
+ */
+
+#ifndef TE_LIB
+#define TE_LIB
+
+#include <mISDNuser/suppserv.h>
+
+/** For initialization usage **/
+/* typedef int ie_nothing_t ;*/
+/** end of init usage **/
+
+
+/*
+ * uncomment the following to make chan_misdn create
+ * record files in /tmp/misdn-{rx|tx}-PortChannel format
+ * */
+
+/*#define MISDN_SAVE_DATA*/
+
+#ifdef WITH_BEROEC
+typedef int beroec_t;
+
+
+enum beroec_type {
+ BEROEC_FULLBAND=0,
+ BEROEC_SUBBAND,
+ BEROEC_FASTSUBBAND
+};
+
+void beroec_init(void);
+void beroec_exit(void);
+beroec_t *beroec_new(int tail, enum beroec_type type, int anti_howl,
+ int tonedisable, int zerocoeff, int adapt, int nlp);
+
+void beroec_destroy(beroec_t *ec);
+int beroec_cancel_alaw_chunk(beroec_t *ec,
+ char *send,
+ char *receive ,
+ int len);
+
+int beroec_version(void);
+#endif
+
+
+
+enum tone_e {
+ TONE_NONE=0,
+ TONE_DIAL,
+ TONE_ALERTING,
+ TONE_FAR_ALERTING,
+ TONE_BUSY,
+ TONE_HANGUP,
+ TONE_CUSTOM,
+ TONE_FILE
+};
+
+
+
+#define MAX_BCHANS 31
+
+enum bchannel_state {
+ BCHAN_CLEANED=0,
+ BCHAN_EMPTY,
+ BCHAN_SETUP,
+ BCHAN_SETUPED,
+ BCHAN_ACTIVE,
+ BCHAN_ACTIVATED,
+ BCHAN_BRIDGE,
+ BCHAN_BRIDGED,
+ BCHAN_RELEASE,
+ BCHAN_RELEASED,
+ BCHAN_CLEAN,
+ BCHAN_CLEAN_REQUEST,
+ BCHAN_ERROR
+};
+
+
+enum misdn_err_e {
+ ENOCHAN=1
+};
+
+
+enum mISDN_NUMBER_PLAN {
+ NUMPLAN_UNINITIALIZED=-1,
+ NUMPLAN_INTERNATIONAL=0x1,
+ NUMPLAN_NATIONAL=0x2,
+ NUMPLAN_SUBSCRIBER=0x4,
+ NUMPLAN_UNKNOWN=0x0
+};
+
+
+enum event_response_e {
+ RESPONSE_IGNORE_SETUP_WITHOUT_CLOSE,
+ RESPONSE_IGNORE_SETUP,
+ RESPONSE_RELEASE_SETUP,
+ RESPONSE_ERR,
+ RESPONSE_OK
+};
+
+
+enum event_e {
+ EVENT_NOTHING,
+ EVENT_TONE_GENERATE,
+ EVENT_BCHAN_DATA,
+ EVENT_BCHAN_ACTIVATED,
+ EVENT_BCHAN_ERROR,
+ EVENT_CLEANUP,
+ EVENT_PROCEEDING,
+ EVENT_PROGRESS,
+ EVENT_SETUP,
+ EVENT_ALERTING,
+ EVENT_CONNECT,
+ EVENT_SETUP_ACKNOWLEDGE,
+ EVENT_CONNECT_ACKNOWLEDGE ,
+ EVENT_USER_INFORMATION,
+ EVENT_SUSPEND_REJECT,
+ EVENT_RESUME_REJECT,
+ EVENT_HOLD,
+ EVENT_SUSPEND,
+ EVENT_RESUME,
+ EVENT_HOLD_ACKNOWLEDGE,
+ EVENT_SUSPEND_ACKNOWLEDGE,
+ EVENT_RESUME_ACKNOWLEDGE,
+ EVENT_HOLD_REJECT,
+ EVENT_RETRIEVE,
+ EVENT_RETRIEVE_ACKNOWLEDGE,
+ EVENT_RETRIEVE_REJECT,
+ EVENT_DISCONNECT,
+ EVENT_RESTART,
+ EVENT_RELEASE,
+ EVENT_RELEASE_COMPLETE,
+ EVENT_FACILITY,
+ EVENT_NOTIFY,
+ EVENT_STATUS_ENQUIRY,
+ EVENT_INFORMATION,
+ EVENT_STATUS,
+ EVENT_TIMEOUT,
+ EVENT_DTMF_TONE,
+ EVENT_NEW_L3ID,
+ EVENT_NEW_BC,
+ EVENT_PORT_ALARM,
+ EVENT_NEW_CHANNEL,
+ EVENT_UNKNOWN
+};
+
+
+enum ie_name_e {
+ IE_DUMMY,
+ IE_LAST
+};
+
+enum { /* bearer capability */
+ INFO_CAPABILITY_SPEECH=0,
+ INFO_CAPABILITY_AUDIO_3_1K=0x10 ,
+ INFO_CAPABILITY_AUDIO_7K=0x11 ,
+ INFO_CAPABILITY_VIDEO =0x18,
+ INFO_CAPABILITY_DIGITAL_UNRESTRICTED =0x8,
+ INFO_CAPABILITY_DIGITAL_RESTRICTED =0x09,
+ INFO_CAPABILITY_DIGITAL_UNRESTRICTED_TONES
+};
+
+enum { /* progress indicators */
+ INFO_PI_CALL_NOT_E2E_ISDN =0x01,
+ INFO_PI_CALLED_NOT_ISDN =0x02,
+ INFO_PI_CALLER_NOT_ISDN =0x03,
+ INFO_PI_CALLER_RETURNED_TO_ISDN =0x04,
+ INFO_PI_INBAND_AVAILABLE =0x08,
+ INFO_PI_DELAY_AT_INTERF =0x0a,
+ INFO_PI_INTERWORKING_WITH_PUBLIC =0x10,
+ INFO_PI_INTERWORKING_NO_RELEASE =0x11,
+ INFO_PI_INTERWORKING_NO_RELEASE_PRE_ANSWER =0x12,
+ INFO_PI_INTERWORKING_NO_RELEASE_POST_ANSWER =0x13
+};
+
+enum { /*CODECS*/
+ INFO_CODEC_ULAW=2,
+ INFO_CODEC_ALAW=3
+};
+
+
+enum layer_e {
+ L3,
+ L2,
+ L1,
+ UNKNOWN
+};
+
+
+
+struct misdn_bchannel {
+ struct send_lock *send_lock;
+
+ int dummy;
+
+ int nt;
+ int pri;
+
+ int port;
+ /** init stuff **/
+ int b_stid;
+ /* int b_addr; */
+ int layer_id;
+
+ int layer;
+
+ /*state stuff*/
+ int need_disconnect;
+ int need_release;
+ int need_release_complete;
+
+ int dec;
+ /** var stuff**/
+ int l3_id;
+ int pid;
+ int ces;
+
+ int restart_channel;
+ int channel;
+ int channel_preselected;
+
+ int in_use;
+ int cw;
+ int addr;
+
+ char * bframe;
+ int bframe_len;
+ int time_usec;
+
+
+ void *astbuf;
+
+ void *misdnbuf;
+
+ int te_choose_channel;
+ int early_bconnect;
+
+ /* dtmf digit */
+ int dtmf;
+ int send_dtmf;
+
+ /* get setup ack */
+ int need_more_infos;
+
+ /* may there be more infos ?*/
+ int sending_complete;
+
+
+ /* wether we should use jollys dsp or not */
+ int nodsp;
+
+ /* wether we should use our jitter buf system or not */
+ int nojitter;
+
+ enum mISDN_NUMBER_PLAN dnumplan;
+ enum mISDN_NUMBER_PLAN rnumplan;
+ enum mISDN_NUMBER_PLAN onumplan;
+ enum mISDN_NUMBER_PLAN cpnnumplan;
+
+ int progress_coding;
+ int progress_location;
+ int progress_indicator;
+
+ struct FacParm fac_in;
+ struct FacParm fac_out;
+
+ /* storing the current AOCD info here */
+ enum FacFunction AOCDtype;
+ union {
+ struct FacAOCDCurrency currency;
+ struct FacAOCDChargingUnit chargingUnit;
+ } AOCD;
+ int AOCD_need_export;
+
+ enum event_e evq;
+
+ /*** CRYPTING STUFF ***/
+
+ int crypt;
+ int curprx;
+ int curptx;
+ char crypt_key[255];
+
+ int crypt_state;
+
+ /*char ast_dtmf_buf[255];
+ char misdn_dtmf_buf[255]; */
+
+ /*** CRYPTING STUFF END***/
+
+ int active;
+ int upset;
+
+ int generate_tone;
+ int tone_cnt;
+
+ enum bchannel_state bc_state;
+ enum bchannel_state next_bc_state;
+
+ int conf_id;
+
+ int holded;
+ int stack_holder;
+
+ int pres;
+ int screen;
+
+ int capability;
+ int law;
+ /** V110 Stuff **/
+ int rate;
+ int mode;
+
+ int user1;
+ int urate;
+ int hdlc;
+ /* V110 */
+
+ char display[84];
+ char msn[32];
+ char oad[32];
+ char rad[32];
+ char dad[32];
+ char cad[32];
+ char orig_dad[32];
+ char keypad[32];
+
+ char info_dad[64];
+ char infos_pending[64];
+
+/* unsigned char info_keypad[32]; */
+/* unsigned char clisub[24]; */
+/* unsigned char cldsub[24]; */
+
+ char uu[256];
+ int uulen;
+
+ int cause;
+ int out_cause;
+
+ /* struct misdn_bchannel hold_bc; */
+
+ /** list stuf **/
+
+#ifdef MISDN_1_2
+ char pipeline[128];
+#else
+ int ec_enable;
+ int ec_deftaps;
+#endif
+
+ int channel_found;
+
+ int orig;
+
+ int txgain;
+ int rxgain;
+
+ struct misdn_bchannel *next;
+};
+
+
+enum event_response_e (*cb_event) (enum event_e event, struct misdn_bchannel *bc, void *user_data);
+void (*cb_log) (int level, int port, char *tmpl, ...);
+int (*cb_jb_empty)(struct misdn_bchannel *bc, char *buffer, int len);
+
+struct misdn_lib_iface {
+ enum event_response_e (*cb_event)(enum event_e event, struct misdn_bchannel *bc, void *user_data);
+ void (*cb_log)(int level, int port, char *tmpl, ...);
+ int (*cb_jb_empty)(struct misdn_bchannel *bc, char *buffer, int len);
+};
+
+/***** USER IFACE **********/
+
+void misdn_lib_nt_keepcalls(int kc);
+
+void misdn_lib_nt_debug_init( int flags, char *file );
+
+int misdn_lib_init(char *portlist, struct misdn_lib_iface* iface, void *user_data);
+int misdn_lib_send_event(struct misdn_bchannel *bc, enum event_e event );
+void misdn_lib_destroy(void);
+
+void misdn_lib_isdn_l1watcher(int port);
+
+void misdn_lib_log_ies(struct misdn_bchannel *bc);
+
+char *manager_isdn_get_info(enum event_e event);
+
+void misdn_lib_transfer(struct misdn_bchannel* holded_bc);
+
+struct misdn_bchannel* misdn_lib_get_free_bc(int port, int channel, int inout, int dec);
+
+void manager_bchannel_activate(struct misdn_bchannel *bc);
+void manager_bchannel_deactivate(struct misdn_bchannel * bc);
+
+int misdn_lib_tx2misdn_frm(struct misdn_bchannel *bc, void *data, int len);
+
+void manager_ph_control(struct misdn_bchannel *bc, int c1, int c2);
+
+void isdn_lib_update_rxgain (struct misdn_bchannel *bc);
+void isdn_lib_update_txgain (struct misdn_bchannel *bc);
+void isdn_lib_update_ec (struct misdn_bchannel *bc);
+void isdn_lib_stop_dtmf (struct misdn_bchannel *bc);
+
+int misdn_lib_port_restart(int port);
+int misdn_lib_pid_restart(int pid);
+int misdn_lib_send_restart(int port, int channel);
+
+int misdn_lib_get_port_info(int port);
+
+int misdn_lib_is_port_blocked(int port);
+int misdn_lib_port_block(int port);
+int misdn_lib_port_unblock(int port);
+
+int misdn_lib_port_is_pri(int port);
+int misdn_lib_port_is_nt(int port);
+
+int misdn_lib_port_up(int port, int notcheck);
+
+int misdn_lib_get_port_down(int port);
+
+int misdn_lib_get_port_up (int port) ;
+
+int misdn_lib_maxports_get(void) ;
+
+void misdn_lib_release(struct misdn_bchannel *bc);
+
+int misdn_cap_is_speech(int cap);
+int misdn_inband_avail(struct misdn_bchannel *bc);
+
+void manager_ec_enable(struct misdn_bchannel *bc);
+void manager_ec_disable(struct misdn_bchannel *bc);
+
+void misdn_lib_send_tone(struct misdn_bchannel *bc, enum tone_e tone);
+
+void get_show_stack_details(int port, char *buf);
+
+
+void misdn_lib_tone_generator_start(struct misdn_bchannel *bc);
+void misdn_lib_tone_generator_stop(struct misdn_bchannel *bc);
+
+
+void misdn_lib_setup_bc(struct misdn_bchannel *bc);
+
+void misdn_lib_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2);
+void misdn_lib_split_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2);
+
+void misdn_lib_echo(struct misdn_bchannel *bc, int onoff);
+
+int misdn_lib_is_ptp(int port);
+int misdn_lib_get_maxchans(int port);
+
+void misdn_lib_reinit_nt_stack(int port);
+
+#define PRI_TRANS_CAP_SPEECH 0x0
+#define PRI_TRANS_CAP_DIGITAL 0x08
+#define PRI_TRANS_CAP_RESTRICTED_DIGITAL 0x09
+#define PRI_TRANS_CAP_3_1K_AUDIO 0x10
+#define PRI_TRANS_CAP_7K_AUDIO 0x11
+
+
+
+char *bc_state2str(enum bchannel_state state);
+void bc_state_change(struct misdn_bchannel *bc, enum bchannel_state state);
+
+void misdn_dump_chanlist(void);
+
+void misdn_make_dummy(struct misdn_bchannel *dummybc, int port, int l3id, int nt, int channel);
+
+
+#endif
diff --git a/trunk/channels/misdn/isdn_lib_intern.h b/trunk/channels/misdn/isdn_lib_intern.h
new file mode 100644
index 000000000..725ef963f
--- /dev/null
+++ b/trunk/channels/misdn/isdn_lib_intern.h
@@ -0,0 +1,124 @@
+#ifndef ISDN_LIB_INTERN
+#define ISDN_LIB_INTERN
+
+
+#include <mISDNuser/mISDNlib.h>
+#include <mISDNuser/isdn_net.h>
+#include <mISDNuser/l3dss1.h>
+#include <mISDNuser/net_l3.h>
+
+#include <pthread.h>
+
+#include "isdn_lib.h"
+
+#ifndef MISDNUSER_VERSION_CODE
+#error "You need a newer version of mISDNuser ..."
+#elif MISDNUSER_VERSION_CODE < MISDNUSER_VERSION(1, 0, 3)
+#error "You need a newer version of mISDNuser ..."
+#endif
+
+
+#define QI_ELEMENT(a) a.off
+
+
+#ifndef mISDNUSER_HEAD_SIZE
+
+#define mISDNUSER_HEAD_SIZE (sizeof(mISDNuser_head_t))
+/*#define mISDNUSER_HEAD_SIZE (sizeof(mISDN_head_t))*/
+#endif
+
+
+ibuffer_t *astbuf;
+ibuffer_t *misdnbuf;
+
+struct send_lock {
+ pthread_mutex_t lock;
+};
+
+
+struct isdn_msg {
+ unsigned long misdn_msg;
+
+ enum layer_e layer;
+ enum event_e event;
+
+ void (*msg_parser)(struct isdn_msg *msgs, msg_t *msg, struct misdn_bchannel *bc, int nt);
+ msg_t *(*msg_builder)(struct isdn_msg *msgs, struct misdn_bchannel *bc, int nt);
+ char *info;
+
+} ;
+
+/* for isdn_msg_parser.c */
+msg_t *create_l3msg(int prim, int mt, int dinfo , int size, int nt);
+
+
+
+struct misdn_stack {
+ /** is first element because &nst equals &mISDNlist **/
+ net_stack_t nst;
+ manager_t mgr;
+
+ int d_stid;
+
+ int b_num;
+
+ int b_stids[MAX_BCHANS + 1];
+
+ int ptp;
+
+ int l2upcnt;
+
+ int l2_id;
+ int lower_id;
+ int upper_id;
+
+
+ int blocked;
+
+ int l2link;
+
+ time_t l2establish;
+
+ int l1link;
+
+ int restart_sent;
+
+ int midev;
+
+ int nt;
+
+ int pri;
+
+
+ int procids[0x100+1];
+
+ msg_queue_t downqueue;
+ msg_queue_t upqueue;
+ int busy;
+
+ int port;
+ struct misdn_bchannel bc[MAX_BCHANS + 1];
+
+ struct misdn_bchannel* bc_list;
+
+ int channels[MAX_BCHANS + 1];
+
+
+ struct misdn_bchannel *holding; /* Queue which holds holded channels :) */
+
+ struct misdn_stack *next;
+};
+
+
+struct misdn_stack* get_stack_by_bc(struct misdn_bchannel *bc);
+
+int isdn_msg_get_index(struct isdn_msg msgs[], msg_t *frm, int nt);
+enum event_e isdn_msg_get_event(struct isdn_msg msgs[], msg_t *frm, int nt);
+int isdn_msg_parse_event(struct isdn_msg msgs[], msg_t *frm, struct misdn_bchannel *bc, int nt);
+char * isdn_get_info(struct isdn_msg msgs[], enum event_e event, int nt);
+msg_t * isdn_msg_build_event(struct isdn_msg msgs[], struct misdn_bchannel *bc, enum event_e event, int nt);
+int isdn_msg_get_index_by_event(struct isdn_msg msgs[], enum event_e event, int nt);
+char * isdn_msg_get_info(struct isdn_msg msgs[], msg_t *msg, int nt);
+
+
+#endif
diff --git a/trunk/channels/misdn/isdn_msg_parser.c b/trunk/channels/misdn/isdn_msg_parser.c
new file mode 100644
index 000000000..ebaf6a92f
--- /dev/null
+++ b/trunk/channels/misdn/isdn_msg_parser.c
@@ -0,0 +1,1353 @@
+/*
+ * Chan_Misdn -- Channel Driver for Asterisk
+ *
+ * Interface to mISDN
+ *
+ * Copyright (C) 2004, Christian Richter
+ *
+ * Christian Richter <crich@beronet.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+/*! \file \brief
+ * Interface to mISDN - message parser
+ * \author Christian Richter <crich@beronet.com>
+ */
+
+
+
+#include "isdn_lib_intern.h"
+
+
+#include "isdn_lib.h"
+
+#include "ie.c"
+
+
+static void set_channel(struct misdn_bchannel *bc, int channel)
+{
+
+ cb_log(3,bc->port,"set_channel: bc->channel:%d channel:%d\n", bc->channel, channel);
+
+
+ if (channel==0xff) {
+ /* any channel */
+ channel=-1;
+ }
+
+ /* ALERT: is that everytime true ? */
+ if (channel > 0 && bc->nt ) {
+
+ if (bc->channel && ( bc->channel != 0xff) ) {
+ cb_log(0,bc->port,"We already have a channel (%d)\n", bc->channel);
+ } else {
+ bc->channel = channel;
+ cb_event(EVENT_NEW_CHANNEL,bc,NULL);
+ }
+ }
+
+ if (channel > 0 && !bc->nt ) {
+ bc->channel = channel;
+ cb_event(EVENT_NEW_CHANNEL,bc,NULL);
+ }
+}
+
+static void parse_proceeding (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ CALL_PROCEEDING_t *proceeding=(CALL_PROCEEDING_t*)((unsigned long)msg->data+ HEADER_LEN);
+ //struct misdn_stack *stack=get_stack_by_bc(bc);
+
+ {
+ int exclusive, channel;
+ dec_ie_channel_id(proceeding->CHANNEL_ID, (Q931_info_t *)proceeding, &exclusive, &channel, nt,bc);
+
+ set_channel(bc,channel);
+
+ }
+
+ dec_ie_progress(proceeding->PROGRESS, (Q931_info_t *)proceeding, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
+
+
+#ifdef DEBUG
+ printf("Parsing PROCEEDING Msg\n");
+#endif
+}
+static msg_t *build_proceeding (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ CALL_PROCEEDING_t *proceeding;
+ msg_t *msg =(msg_t*)create_l3msg(CC_PROCEEDING | REQUEST, MT_CALL_PROCEEDING, bc?bc->l3_id:-1, sizeof(CALL_PROCEEDING_t) ,nt);
+
+ proceeding=(CALL_PROCEEDING_t*)((msg->data+HEADER_LEN));
+
+ enc_ie_channel_id(&proceeding->CHANNEL_ID, msg, 1,bc->channel, nt,bc);
+
+ if (nt)
+ enc_ie_progress(&proceeding->PROGRESS, msg, 0, nt?1:5, 8, nt,bc);
+
+
+#ifdef DEBUG
+ printf("Building PROCEEDING Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_alerting (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ ALERTING_t *alerting=(ALERTING_t*)((unsigned long)(msg->data+HEADER_LEN));
+ //Q931_info_t *qi=(Q931_info_t*)(msg->data+HEADER_LEN);
+
+ dec_ie_progress(alerting->PROGRESS, (Q931_info_t *)alerting, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
+
+#ifdef DEBUG
+ printf("Parsing ALERTING Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_alerting (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ ALERTING_t *alerting;
+ msg_t *msg =(msg_t*)create_l3msg(CC_ALERTING | REQUEST, MT_ALERTING, bc?bc->l3_id:-1, sizeof(ALERTING_t) ,nt);
+
+ alerting=(ALERTING_t*)((msg->data+HEADER_LEN));
+
+ enc_ie_channel_id(&alerting->CHANNEL_ID, msg, 1,bc->channel, nt,bc);
+
+ if (nt)
+ enc_ie_progress(&alerting->PROGRESS, msg, 0, nt?1:5, 8, nt,bc);
+#ifdef DEBUG
+ printf("Building ALERTING Msg\n");
+#endif
+ return msg;
+}
+
+
+static void parse_progress (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ PROGRESS_t *progress=(PROGRESS_t*)((unsigned long)(msg->data+HEADER_LEN));
+ //Q931_info_t *qi=(Q931_info_t*)(msg->data+HEADER_LEN);
+
+ dec_ie_progress(progress->PROGRESS, (Q931_info_t *)progress, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
+
+#ifdef DEBUG
+ printf("Parsing PROGRESS Msg\n");
+#endif
+}
+
+static msg_t *build_progress (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ PROGRESS_t *progress;
+ msg_t *msg =(msg_t*)create_l3msg(CC_PROGRESS | REQUEST, MT_PROGRESS, bc?bc->l3_id:-1, sizeof(PROGRESS_t) ,nt);
+
+ progress=(PROGRESS_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building PROGRESS Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_setup (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ SETUP_t *setup= (SETUP_t*)((unsigned long)msg->data+HEADER_LEN);
+ Q931_info_t *qi=(Q931_info_t*)((unsigned long)msg->data+HEADER_LEN);
+
+#ifdef DEBUG
+ printf("Parsing SETUP Msg\n");
+#endif
+ {
+ int type,plan,present, screen;
+ char id[32];
+ dec_ie_calling_pn(setup->CALLING_PN, qi, &type, &plan, &present, &screen, id, sizeof(id)-1, nt,bc);
+
+ bc->onumplan=type;
+ strcpy(bc->oad, id);
+ switch (present) {
+ case 0:
+ bc->pres=0; /* screened */
+ break;
+ case 1:
+ bc->pres=1; /* not screened */
+ break;
+ default:
+ bc->pres=0;
+ }
+ switch (screen) {
+ case 0:
+ break;
+ default:
+ ;
+ }
+ }
+ {
+ int type, plan;
+ char number[32];
+ dec_ie_called_pn(setup->CALLED_PN, (Q931_info_t *)setup, &type, &plan, number, sizeof(number)-1, nt,bc);
+ strcpy(bc->dad, number);
+ bc->dnumplan=type;
+ }
+ {
+ char keypad[32];
+ dec_ie_keypad(setup->KEYPAD, (Q931_info_t *)setup, keypad, sizeof(keypad)-1, nt,bc);
+ strcpy(bc->keypad, keypad);
+ }
+
+ {
+ dec_ie_complete(setup->COMPLETE, (Q931_info_t *)setup, &bc->sending_complete, nt,bc);
+
+ }
+
+ {
+ int type, plan, present, screen, reason;
+ char id[32];
+ dec_ie_redir_nr(setup->REDIR_NR, (Q931_info_t *)setup, &type, &plan, &present, &screen, &reason, id, sizeof(id)-1, nt,bc);
+
+ strcpy(bc->rad, id);
+ bc->rnumplan=type;
+ }
+ {
+ int coding, capability, mode, rate, multi, user, async, urate, stopbits, dbits, parity;
+ dec_ie_bearer(setup->BEARER, (Q931_info_t *)setup, &coding, &capability, &mode, &rate, &multi, &user, &async, &urate, &stopbits, &dbits, &parity, nt,bc);
+ switch (capability) {
+ case -1: bc->capability=INFO_CAPABILITY_DIGITAL_UNRESTRICTED;
+ break;
+ case 0: bc->capability=INFO_CAPABILITY_SPEECH;
+ break;
+ case 18: bc->capability=INFO_CAPABILITY_VIDEO;
+ break;
+ case 8: bc->capability=INFO_CAPABILITY_DIGITAL_UNRESTRICTED;
+ bc->user1 = user;
+ bc->urate = urate;
+
+ bc->rate = rate;
+ bc->mode = mode;
+ break;
+ case 9: bc->capability=INFO_CAPABILITY_DIGITAL_RESTRICTED;
+ break;
+ default:
+ break;
+ }
+
+ switch(user) {
+ case 2:
+ bc->law=INFO_CODEC_ULAW;
+ break;
+ case 3:
+ bc->law=INFO_CODEC_ALAW;
+ break;
+ default:
+ bc->law=INFO_CODEC_ALAW;
+
+ }
+
+ bc->capability=capability;
+ }
+ {
+ int exclusive, channel;
+ dec_ie_channel_id(setup->CHANNEL_ID, (Q931_info_t *)setup, &exclusive, &channel, nt,bc);
+
+ set_channel(bc,channel);
+ }
+
+ {
+ int protocol ;
+ dec_ie_useruser(setup->USER_USER, (Q931_info_t *)setup, &protocol, bc->uu, &bc->uulen, nt,bc);
+ if (bc->uulen) cb_log(1,bc->port,"USERUESRINFO:%s\n",bc->uu);
+ else
+ cb_log(1,bc->port,"NO USERUESRINFO\n");
+ }
+
+ dec_ie_progress(setup->PROGRESS, (Q931_info_t *)setup, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
+
+}
+
+#define ANY_CHANNEL 0xff /* IE attribut for 'any channel' */
+static msg_t *build_setup (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ SETUP_t *setup;
+ msg_t *msg =(msg_t*)create_l3msg(CC_SETUP | REQUEST, MT_SETUP, bc?bc->l3_id:-1, sizeof(SETUP_t) ,nt);
+
+ setup=(SETUP_t*)((msg->data+HEADER_LEN));
+
+ if (bc->channel == 0 || bc->channel == ANY_CHANNEL || bc->channel==-1)
+ enc_ie_channel_id(&setup->CHANNEL_ID, msg, 0, bc->channel, nt,bc);
+ else
+ enc_ie_channel_id(&setup->CHANNEL_ID, msg, 1, bc->channel, nt,bc);
+
+
+ {
+ int type=bc->onumplan,plan=1,present=bc->pres,screen=bc->screen;
+ enc_ie_calling_pn(&setup->CALLING_PN, msg, type, plan, present,
+ screen, bc->oad, nt, bc);
+ }
+
+ {
+ if (bc->dad[0])
+ enc_ie_called_pn(&setup->CALLED_PN, msg, bc->dnumplan, 1, bc->dad, nt,bc);
+ }
+
+ {
+ if (bc->rad[0])
+ enc_ie_redir_nr(&setup->REDIR_NR, msg, 1, 1, bc->pres, bc->screen, 0, bc->rad, nt,bc);
+ }
+
+ {
+ if (bc->keypad[0])
+ enc_ie_keypad(&setup->KEYPAD, msg, bc->keypad, nt,bc);
+ }
+
+
+ if (*bc->display) {
+ enc_ie_display(&setup->DISPLAY, msg, bc->display, nt,bc);
+ }
+
+ {
+ int coding=0, capability, mode=0 /* 2 for packet ! */
+ ,user, rate=0x10;
+
+ switch (bc->law) {
+ case INFO_CODEC_ULAW: user=2;
+ break;
+ case INFO_CODEC_ALAW: user=3;
+ break;
+ default:
+ user=3;
+ }
+
+ switch (bc->capability) {
+ case INFO_CAPABILITY_SPEECH: capability = 0;
+ break;
+ case INFO_CAPABILITY_DIGITAL_UNRESTRICTED: capability = 8;
+ user=-1;
+ mode=bc->mode;
+ rate=bc->rate;
+ break;
+ case INFO_CAPABILITY_DIGITAL_RESTRICTED: capability = 9;
+ user=-1;
+ break;
+ default:
+ capability=bc->capability;
+ }
+
+
+
+ enc_ie_bearer(&setup->BEARER, msg, coding, capability, mode, rate, -1, user, nt,bc);
+ }
+
+ if (bc->sending_complete) {
+ enc_ie_complete(&setup->COMPLETE,msg, bc->sending_complete, nt, bc);
+ }
+
+ if (bc->uulen) {
+ int protocol=4;
+ enc_ie_useruser(&setup->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc);
+ cb_log(1,bc->port,"ENCODING USERUESRINFO:%s\n",bc->uu);
+ }
+
+#ifdef DEBUG
+ printf("Building SETUP Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_connect (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ CONNECT_t *connect=(CONNECT_t*)((unsigned long)(msg->data+HEADER_LEN));
+
+ int plan,pres,screen;
+
+ bc->ces = connect->ces;
+ bc->ces = connect->ces;
+
+ dec_ie_progress(connect->PROGRESS, (Q931_info_t *)connect, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
+
+ dec_ie_connected_pn(connect->CONNECT_PN,(Q931_info_t *)connect, &bc->cpnnumplan, &plan, &pres, &screen, bc->cad, 31, nt, bc);
+
+ /*
+ cb_log(1,bc->port,"CONNETED PN: %s cpn_dialplan:%d\n", connected_pn, type);
+ */
+
+#ifdef DEBUG
+ printf("Parsing CONNECT Msg\n");
+#endif
+}
+
+static msg_t *build_connect (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ CONNECT_t *connect;
+ msg_t *msg =(msg_t*)create_l3msg(CC_CONNECT | REQUEST, MT_CONNECT, bc?bc->l3_id:-1, sizeof(CONNECT_t) ,nt);
+
+ cb_log(6,bc->port,"BUILD_CONNECT: bc:%p bc->l3id:%d, nt:%d\n",bc,bc->l3_id,nt);
+
+ connect=(CONNECT_t*)((msg->data+HEADER_LEN));
+
+ if (nt) {
+ time_t now;
+ time(&now);
+ enc_ie_date(&connect->DATE, msg, now, nt,bc);
+ }
+
+ {
+ int type=bc->cpnnumplan, plan=1, present=2, screen=0;
+ enc_ie_connected_pn(&connect->CONNECT_PN, msg, type,plan, present, screen, bc->cad, nt , bc);
+ }
+
+#ifdef DEBUG
+ printf("Building CONNECT Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_setup_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ SETUP_ACKNOWLEDGE_t *setup_acknowledge=(SETUP_ACKNOWLEDGE_t*)((unsigned long)(msg->data+HEADER_LEN));
+
+ {
+ int exclusive, channel;
+ dec_ie_channel_id(setup_acknowledge->CHANNEL_ID, (Q931_info_t *)setup_acknowledge, &exclusive, &channel, nt,bc);
+
+
+ set_channel(bc, channel);
+ }
+
+ dec_ie_progress(setup_acknowledge->PROGRESS, (Q931_info_t *)setup_acknowledge, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
+#ifdef DEBUG
+ printf("Parsing SETUP_ACKNOWLEDGE Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_setup_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ SETUP_ACKNOWLEDGE_t *setup_acknowledge;
+ msg_t *msg =(msg_t*)create_l3msg(CC_SETUP_ACKNOWLEDGE | REQUEST, MT_SETUP_ACKNOWLEDGE, bc?bc->l3_id:-1, sizeof(SETUP_ACKNOWLEDGE_t) ,nt);
+
+ setup_acknowledge=(SETUP_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN));
+
+ enc_ie_channel_id(&setup_acknowledge->CHANNEL_ID, msg, 1,bc->channel, nt,bc);
+
+ if (nt)
+ enc_ie_progress(&setup_acknowledge->PROGRESS, msg, 0, nt?1:5, 8, nt,bc);
+
+#ifdef DEBUG
+ printf("Building SETUP_ACKNOWLEDGE Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_connect_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing CONNECT_ACKNOWLEDGE Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_connect_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ CONNECT_ACKNOWLEDGE_t *connect_acknowledge;
+ msg_t *msg =(msg_t*)create_l3msg(CC_CONNECT | RESPONSE, MT_CONNECT, bc?bc->l3_id:-1, sizeof(CONNECT_ACKNOWLEDGE_t) ,nt);
+
+ connect_acknowledge=(CONNECT_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN));
+
+ enc_ie_channel_id(&connect_acknowledge->CHANNEL_ID, msg, 1, bc->channel, nt,bc);
+
+#ifdef DEBUG
+ printf("Building CONNECT_ACKNOWLEDGE Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_user_information (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing USER_INFORMATION Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_user_information (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ USER_INFORMATION_t *user_information;
+ msg_t *msg =(msg_t*)create_l3msg(CC_USER_INFORMATION | REQUEST, MT_USER_INFORMATION, bc?bc->l3_id:-1, sizeof(USER_INFORMATION_t) ,nt);
+
+ user_information=(USER_INFORMATION_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building USER_INFORMATION Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_suspend_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing SUSPEND_REJECT Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_suspend_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ SUSPEND_REJECT_t *suspend_reject;
+ msg_t *msg =(msg_t*)create_l3msg(CC_SUSPEND_REJECT | REQUEST, MT_SUSPEND_REJECT, bc?bc->l3_id:-1, sizeof(SUSPEND_REJECT_t) ,nt);
+
+ suspend_reject=(SUSPEND_REJECT_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building SUSPEND_REJECT Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_resume_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing RESUME_REJECT Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_resume_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RESUME_REJECT_t *resume_reject;
+ msg_t *msg =(msg_t*)create_l3msg(CC_RESUME_REJECT | REQUEST, MT_RESUME_REJECT, bc?bc->l3_id:-1, sizeof(RESUME_REJECT_t) ,nt);
+
+ resume_reject=(RESUME_REJECT_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building RESUME_REJECT Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_hold (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing HOLD Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_hold (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ HOLD_t *hold;
+ msg_t *msg =(msg_t*)create_l3msg(CC_HOLD | REQUEST, MT_HOLD, bc?bc->l3_id:-1, sizeof(HOLD_t) ,nt);
+
+ hold=(HOLD_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building HOLD Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_suspend (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing SUSPEND Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_suspend (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ SUSPEND_t *suspend;
+ msg_t *msg =(msg_t*)create_l3msg(CC_SUSPEND | REQUEST, MT_SUSPEND, bc?bc->l3_id:-1, sizeof(SUSPEND_t) ,nt);
+
+ suspend=(SUSPEND_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building SUSPEND Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_resume (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing RESUME Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_resume (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RESUME_t *resume;
+ msg_t *msg =(msg_t*)create_l3msg(CC_RESUME | REQUEST, MT_RESUME, bc?bc->l3_id:-1, sizeof(RESUME_t) ,nt);
+
+ resume=(RESUME_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building RESUME Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_hold_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing HOLD_ACKNOWLEDGE Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_hold_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ HOLD_ACKNOWLEDGE_t *hold_acknowledge;
+ msg_t *msg =(msg_t*)create_l3msg(CC_HOLD_ACKNOWLEDGE | REQUEST, MT_HOLD_ACKNOWLEDGE, bc?bc->l3_id:-1, sizeof(HOLD_ACKNOWLEDGE_t) ,nt);
+
+ hold_acknowledge=(HOLD_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building HOLD_ACKNOWLEDGE Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_suspend_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing SUSPEND_ACKNOWLEDGE Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_suspend_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ SUSPEND_ACKNOWLEDGE_t *suspend_acknowledge;
+ msg_t *msg =(msg_t*)create_l3msg(CC_SUSPEND_ACKNOWLEDGE | REQUEST, MT_SUSPEND_ACKNOWLEDGE, bc?bc->l3_id:-1, sizeof(SUSPEND_ACKNOWLEDGE_t) ,nt);
+
+ suspend_acknowledge=(SUSPEND_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building SUSPEND_ACKNOWLEDGE Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_resume_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing RESUME_ACKNOWLEDGE Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_resume_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RESUME_ACKNOWLEDGE_t *resume_acknowledge;
+ msg_t *msg =(msg_t*)create_l3msg(CC_RESUME_ACKNOWLEDGE | REQUEST, MT_RESUME_ACKNOWLEDGE, bc?bc->l3_id:-1, sizeof(RESUME_ACKNOWLEDGE_t) ,nt);
+
+ resume_acknowledge=(RESUME_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building RESUME_ACKNOWLEDGE Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_hold_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing HOLD_REJECT Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_hold_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ HOLD_REJECT_t *hold_reject;
+ msg_t *msg =(msg_t*)create_l3msg(CC_HOLD_REJECT | REQUEST, MT_HOLD_REJECT, bc?bc->l3_id:-1, sizeof(HOLD_REJECT_t) ,nt);
+
+ hold_reject=(HOLD_REJECT_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building HOLD_REJECT Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_retrieve (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing RETRIEVE Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_retrieve (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RETRIEVE_t *retrieve;
+ msg_t *msg =(msg_t*)create_l3msg(CC_RETRIEVE | REQUEST, MT_RETRIEVE, bc?bc->l3_id:-1, sizeof(RETRIEVE_t) ,nt);
+
+ retrieve=(RETRIEVE_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building RETRIEVE Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_retrieve_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing RETRIEVE_ACKNOWLEDGE Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_retrieve_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RETRIEVE_ACKNOWLEDGE_t *retrieve_acknowledge;
+ msg_t *msg =(msg_t*)create_l3msg(CC_RETRIEVE_ACKNOWLEDGE | REQUEST, MT_RETRIEVE_ACKNOWLEDGE, bc?bc->l3_id:-1, sizeof(RETRIEVE_ACKNOWLEDGE_t) ,nt);
+
+ retrieve_acknowledge=(RETRIEVE_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN));
+
+ enc_ie_channel_id(&retrieve_acknowledge->CHANNEL_ID, msg, 1, bc->channel, nt,bc);
+#ifdef DEBUG
+ printf("Building RETRIEVE_ACKNOWLEDGE Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_retrieve_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing RETRIEVE_REJECT Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_retrieve_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RETRIEVE_REJECT_t *retrieve_reject;
+ msg_t *msg =(msg_t*)create_l3msg(CC_RETRIEVE_REJECT | REQUEST, MT_RETRIEVE_REJECT, bc?bc->l3_id:-1, sizeof(RETRIEVE_REJECT_t) ,nt);
+
+ retrieve_reject=(RETRIEVE_REJECT_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building RETRIEVE_REJECT Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_disconnect (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ DISCONNECT_t *disconnect=(DISCONNECT_t*)((unsigned long)(msg->data+HEADER_LEN));
+ int location;
+ int cause;
+ dec_ie_cause(disconnect->CAUSE, (Q931_info_t *)(disconnect), &location, &cause, nt,bc);
+ if (cause>0) bc->cause=cause;
+
+ dec_ie_progress(disconnect->PROGRESS, (Q931_info_t *)disconnect, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
+#ifdef DEBUG
+ printf("Parsing DISCONNECT Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_disconnect (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ DISCONNECT_t *disconnect;
+ msg_t *msg =(msg_t*)create_l3msg(CC_DISCONNECT | REQUEST, MT_DISCONNECT, bc?bc->l3_id:-1, sizeof(DISCONNECT_t) ,nt);
+
+ disconnect=(DISCONNECT_t*)((msg->data+HEADER_LEN));
+
+ enc_ie_cause(&disconnect->CAUSE, msg, (nt)?1:0, bc->out_cause,nt,bc);
+ if (nt) enc_ie_progress(&disconnect->PROGRESS, msg, 0, nt?1:5, 8 ,nt,bc);
+
+ if (bc->uulen) {
+ int protocol=4;
+ enc_ie_useruser(&disconnect->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc);
+ cb_log(1,bc->port,"ENCODING USERUESRINFO:%s\n",bc->uu);
+ }
+
+#ifdef DEBUG
+ printf("Building DISCONNECT Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_restart (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RESTART_t *restart=(RESTART_t*)((unsigned long)(msg->data+HEADER_LEN));
+
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+
+#ifdef DEBUG
+ printf("Parsing RESTART Msg\n");
+#endif
+
+ {
+ int exclusive;
+ dec_ie_channel_id(restart->CHANNEL_ID, (Q931_info_t *)restart, &exclusive, &bc->restart_channel, nt,bc);
+ cb_log(3, stack->port, "CC_RESTART Request on channel:%d on this port.\n", bc->restart_channel);
+ }
+
+}
+
+static msg_t *build_restart (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RESTART_t *restart;
+ msg_t *msg =(msg_t*)create_l3msg(CC_RESTART | REQUEST, MT_RESTART, bc?bc->l3_id:-1, sizeof(RESTART_t) ,nt);
+
+ restart=(RESTART_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building RESTART Msg\n");
+#endif
+
+ if (bc->channel > 0) {
+ enc_ie_channel_id(&restart->CHANNEL_ID, msg, 1,bc->channel, nt,bc);
+ enc_ie_restart_ind(&restart->RESTART_IND, msg, 0x80, nt, bc);
+ } else {
+ enc_ie_restart_ind(&restart->RESTART_IND, msg, 0x87, nt, bc);
+ }
+
+ cb_log(0,bc->port, "Restarting channel %d\n", bc->channel);
+ return msg;
+}
+
+static void parse_release (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RELEASE_t *release=(RELEASE_t*)((unsigned long)(msg->data+HEADER_LEN));
+ int location;
+ int cause;
+
+ dec_ie_cause(release->CAUSE, (Q931_info_t *)(release), &location, &cause, nt,bc);
+ if (cause>0) bc->cause=cause;
+#ifdef DEBUG
+ printf("Parsing RELEASE Msg\n");
+#endif
+
+
+}
+
+static msg_t *build_release (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RELEASE_t *release;
+ msg_t *msg =(msg_t*)create_l3msg(CC_RELEASE | REQUEST, MT_RELEASE, bc?bc->l3_id:-1, sizeof(RELEASE_t) ,nt);
+
+ release=(RELEASE_t*)((msg->data+HEADER_LEN));
+
+ if (bc->out_cause>= 0)
+ enc_ie_cause(&release->CAUSE, msg, nt?1:0, bc->out_cause, nt,bc);
+
+ if (bc->uulen) {
+ int protocol=4;
+ enc_ie_useruser(&release->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc);
+ cb_log(1,bc->port,"ENCODING USERUESRINFO:%s\n",bc->uu);
+ }
+
+#ifdef DEBUG
+ printf("Building RELEASE Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_release_complete (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RELEASE_COMPLETE_t *release_complete=(RELEASE_COMPLETE_t*)((unsigned long)(msg->data+HEADER_LEN));
+ int location;
+ int cause;
+ iframe_t *frm = (iframe_t*) msg->data;
+
+ struct misdn_stack *stack=get_stack_by_bc(bc);
+ mISDNuser_head_t *hh;
+ hh=(mISDNuser_head_t*)msg->data;
+
+ /*hh=(mISDN_head_t*)msg->data;
+ mISDN_head_t *hh;*/
+
+ if (nt) {
+ if (hh->prim == (CC_RELEASE_COMPLETE|CONFIRM)) {
+ cb_log(0, stack->port, "CC_RELEASE_COMPLETE|CONFIRM [NT] \n");
+ return;
+ }
+ } else {
+ if (frm->prim == (CC_RELEASE_COMPLETE|CONFIRM)) {
+ cb_log(0, stack->port, "CC_RELEASE_COMPLETE|CONFIRM [TE] \n");
+ return;
+ }
+ }
+ dec_ie_cause(release_complete->CAUSE, (Q931_info_t *)(release_complete), &location, &cause, nt,bc);
+ if (cause>0) bc->cause=cause;
+
+#ifdef DEBUG
+ printf("Parsing RELEASE_COMPLETE Msg\n");
+#endif
+}
+
+static msg_t *build_release_complete (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ RELEASE_COMPLETE_t *release_complete;
+ msg_t *msg =(msg_t*)create_l3msg(CC_RELEASE_COMPLETE | REQUEST, MT_RELEASE_COMPLETE, bc?bc->l3_id:-1, sizeof(RELEASE_COMPLETE_t) ,nt);
+
+ release_complete=(RELEASE_COMPLETE_t*)((msg->data+HEADER_LEN));
+
+ enc_ie_cause(&release_complete->CAUSE, msg, nt?1:0, bc->out_cause, nt,bc);
+
+ if (bc->uulen) {
+ int protocol=4;
+ enc_ie_useruser(&release_complete->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc);
+ cb_log(1,bc->port,"ENCODING USERUESRINFO:%s\n",bc->uu);
+ }
+
+#ifdef DEBUG
+ printf("Building RELEASE_COMPLETE Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_facility (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN;
+ FACILITY_t *facility = (FACILITY_t*)(msg->data+HEADER_LEN);
+ Q931_info_t *qi = (Q931_info_t*)(msg->data+HEADER_LEN);
+ unsigned char *p = NULL;
+ int err;
+
+#ifdef DEBUG
+ printf("Parsing FACILITY Msg\n");
+#endif
+
+ if (!bc->nt) {
+ if (qi->QI_ELEMENT(facility))
+ p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(facility) + 1;
+ } else {
+ p = facility->FACILITY;
+ }
+ if (!p)
+ return;
+
+ err = decodeFac(p, &(bc->fac_in));
+ if (err) {
+ cb_log(5, bc->port, "Decoding FACILITY failed! (%d)\n", err);
+ }
+}
+
+static msg_t *build_facility (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int len,
+ HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN;
+ unsigned char *ie_fac,
+ fac_tmp[256];
+ msg_t *msg =(msg_t*)create_l3msg(CC_FACILITY | REQUEST, MT_FACILITY, bc?bc->l3_id:-1, sizeof(FACILITY_t) ,nt);
+ FACILITY_t *facility = (FACILITY_t*)(msg->data+HEADER_LEN);
+ Q931_info_t *qi;
+
+#ifdef DEBUG
+ printf("Building FACILITY Msg\n");
+#endif
+
+ len = encodeFac(fac_tmp, &(bc->fac_out));
+ if (len <= 0)
+ return NULL;
+
+ ie_fac = msg_put(msg, len);
+ if (bc->nt) {
+ facility->FACILITY = ie_fac + 1;
+ } else {
+ qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
+ qi->QI_ELEMENT(facility) = ie_fac - (unsigned char *)qi - sizeof(Q931_info_t);
+ }
+
+ memcpy(ie_fac, fac_tmp, len);
+
+ if (*bc->display) {
+ printf("Sending %s as Display\n", bc->display);
+ enc_ie_display(&facility->DISPLAY, msg, bc->display, nt,bc);
+ }
+
+ return msg;
+}
+
+static void parse_notify (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing NOTIFY Msg\n");
+#endif
+}
+
+static msg_t *build_notify (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ NOTIFY_t *notify;
+ msg_t *msg =(msg_t*)create_l3msg(CC_NOTIFY | REQUEST, MT_NOTIFY, bc?bc->l3_id:-1, sizeof(NOTIFY_t) ,nt);
+
+ notify=(NOTIFY_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building NOTIFY Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_status_enquiry (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing STATUS_ENQUIRY Msg\n");
+#endif
+}
+
+static msg_t *build_status_enquiry (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ STATUS_ENQUIRY_t *status_enquiry;
+ msg_t *msg =(msg_t*)create_l3msg(CC_STATUS_ENQUIRY | REQUEST, MT_STATUS_ENQUIRY, bc?bc->l3_id:-1, sizeof(STATUS_ENQUIRY_t) ,nt);
+
+ status_enquiry=(STATUS_ENQUIRY_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building STATUS_ENQUIRY Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_information (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ INFORMATION_t *information=(INFORMATION_t*)((unsigned long)(msg->data+HEADER_LEN));
+ {
+ int type, plan;
+ char number[32];
+ char keypad[32];
+ dec_ie_called_pn(information->CALLED_PN, (Q931_info_t *)information, &type, &plan, number, sizeof(number)-1, nt, bc);
+ dec_ie_keypad(information->KEYPAD, (Q931_info_t *)information, keypad, sizeof(keypad)-1, nt, bc);
+ strcpy(bc->info_dad, number);
+ strcpy(bc->keypad,keypad);
+ }
+#ifdef DEBUG
+ printf("Parsing INFORMATION Msg\n");
+#endif
+}
+
+static msg_t *build_information (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ INFORMATION_t *information;
+ msg_t *msg =(msg_t*)create_l3msg(CC_INFORMATION | REQUEST, MT_INFORMATION, bc?bc->l3_id:-1, sizeof(INFORMATION_t) ,nt);
+
+ information=(INFORMATION_t*)((msg->data+HEADER_LEN));
+
+ {
+ enc_ie_called_pn(&information->CALLED_PN, msg, 0, 1, bc->info_dad, nt,bc);
+ }
+
+ {
+ if (*bc->display) {
+ printf("Sending %s as Display\n", bc->display);
+ enc_ie_display(&information->DISPLAY, msg, bc->display, nt,bc);
+ }
+ }
+
+#ifdef DEBUG
+ printf("Building INFORMATION Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_status (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ STATUS_t *status=(STATUS_t*)((unsigned long)(msg->data+HEADER_LEN));
+ int location;
+ int cause;
+
+ dec_ie_cause(status->CAUSE, (Q931_info_t *)(status), &location, &cause, nt,bc);
+ if (cause>0) bc->cause=cause;
+ ;
+
+#ifdef DEBUG
+ printf("Parsing STATUS Msg\n");
+#endif
+}
+
+static msg_t *build_status (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ STATUS_t *status;
+ msg_t *msg =(msg_t*)create_l3msg(CC_STATUS | REQUEST, MT_STATUS, bc?bc->l3_id:-1, sizeof(STATUS_t) ,nt);
+
+ status=(STATUS_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building STATUS Msg\n");
+#endif
+ return msg;
+}
+
+static void parse_timeout (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+#ifdef DEBUG
+ printf("Parsing STATUS Msg\n");
+#endif
+}
+
+static msg_t *build_timeout (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
+{
+ int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
+ STATUS_t *status;
+ msg_t *msg =(msg_t*)create_l3msg(CC_STATUS | REQUEST, MT_STATUS, bc?bc->l3_id:-1, sizeof(STATUS_t) ,nt);
+
+ status=(STATUS_t*)((msg->data+HEADER_LEN));
+
+#ifdef DEBUG
+ printf("Building STATUS Msg\n");
+#endif
+ return msg;
+}
+
+
+/************************************/
+
+
+
+
+/** Msg Array **/
+
+struct isdn_msg msgs_g[] = {
+ {CC_PROCEEDING,L3,EVENT_PROCEEDING,
+ parse_proceeding,build_proceeding,
+ "PROCEEDING"},
+ {CC_ALERTING,L3,EVENT_ALERTING,
+ parse_alerting,build_alerting,
+ "ALERTING"},
+ {CC_PROGRESS,L3,EVENT_PROGRESS,
+ parse_progress,build_progress,
+ "PROGRESS"},
+ {CC_SETUP,L3,EVENT_SETUP,
+ parse_setup,build_setup,
+ "SETUP"},
+ {CC_CONNECT,L3,EVENT_CONNECT,
+ parse_connect,build_connect,
+ "CONNECT"},
+ {CC_SETUP_ACKNOWLEDGE,L3,EVENT_SETUP_ACKNOWLEDGE,
+ parse_setup_acknowledge,build_setup_acknowledge,
+ "SETUP_ACKNOWLEDGE"},
+ {CC_CONNECT_ACKNOWLEDGE ,L3,EVENT_CONNECT_ACKNOWLEDGE ,
+ parse_connect_acknowledge ,build_connect_acknowledge,
+ "CONNECT_ACKNOWLEDGE "},
+ {CC_USER_INFORMATION,L3,EVENT_USER_INFORMATION,
+ parse_user_information,build_user_information,
+ "USER_INFORMATION"},
+ {CC_SUSPEND_REJECT,L3,EVENT_SUSPEND_REJECT,
+ parse_suspend_reject,build_suspend_reject,
+ "SUSPEND_REJECT"},
+ {CC_RESUME_REJECT,L3,EVENT_RESUME_REJECT,
+ parse_resume_reject,build_resume_reject,
+ "RESUME_REJECT"},
+ {CC_HOLD,L3,EVENT_HOLD,
+ parse_hold,build_hold,
+ "HOLD"},
+ {CC_SUSPEND,L3,EVENT_SUSPEND,
+ parse_suspend,build_suspend,
+ "SUSPEND"},
+ {CC_RESUME,L3,EVENT_RESUME,
+ parse_resume,build_resume,
+ "RESUME"},
+ {CC_HOLD_ACKNOWLEDGE,L3,EVENT_HOLD_ACKNOWLEDGE,
+ parse_hold_acknowledge,build_hold_acknowledge,
+ "HOLD_ACKNOWLEDGE"},
+ {CC_SUSPEND_ACKNOWLEDGE,L3,EVENT_SUSPEND_ACKNOWLEDGE,
+ parse_suspend_acknowledge,build_suspend_acknowledge,
+ "SUSPEND_ACKNOWLEDGE"},
+ {CC_RESUME_ACKNOWLEDGE,L3,EVENT_RESUME_ACKNOWLEDGE,
+ parse_resume_acknowledge,build_resume_acknowledge,
+ "RESUME_ACKNOWLEDGE"},
+ {CC_HOLD_REJECT,L3,EVENT_HOLD_REJECT,
+ parse_hold_reject,build_hold_reject,
+ "HOLD_REJECT"},
+ {CC_RETRIEVE,L3,EVENT_RETRIEVE,
+ parse_retrieve,build_retrieve,
+ "RETRIEVE"},
+ {CC_RETRIEVE_ACKNOWLEDGE,L3,EVENT_RETRIEVE_ACKNOWLEDGE,
+ parse_retrieve_acknowledge,build_retrieve_acknowledge,
+ "RETRIEVE_ACKNOWLEDGE"},
+ {CC_RETRIEVE_REJECT,L3,EVENT_RETRIEVE_REJECT,
+ parse_retrieve_reject,build_retrieve_reject,
+ "RETRIEVE_REJECT"},
+ {CC_DISCONNECT,L3,EVENT_DISCONNECT,
+ parse_disconnect,build_disconnect,
+ "DISCONNECT"},
+ {CC_RESTART,L3,EVENT_RESTART,
+ parse_restart,build_restart,
+ "RESTART"},
+ {CC_RELEASE,L3,EVENT_RELEASE,
+ parse_release,build_release,
+ "RELEASE"},
+ {CC_RELEASE_COMPLETE,L3,EVENT_RELEASE_COMPLETE,
+ parse_release_complete,build_release_complete,
+ "RELEASE_COMPLETE"},
+ {CC_FACILITY,L3,EVENT_FACILITY,
+ parse_facility,build_facility,
+ "FACILITY"},
+ {CC_NOTIFY,L3,EVENT_NOTIFY,
+ parse_notify,build_notify,
+ "NOTIFY"},
+ {CC_STATUS_ENQUIRY,L3,EVENT_STATUS_ENQUIRY,
+ parse_status_enquiry,build_status_enquiry,
+ "STATUS_ENQUIRY"},
+ {CC_INFORMATION,L3,EVENT_INFORMATION,
+ parse_information,build_information,
+ "INFORMATION"},
+ {CC_STATUS,L3,EVENT_STATUS,
+ parse_status,build_status,
+ "STATUS"},
+ {CC_TIMEOUT,L3,EVENT_TIMEOUT,
+ parse_timeout,build_timeout,
+ "TIMEOUT"},
+ {0,0,0,NULL,NULL,NULL}
+};
+
+#define msgs_max (sizeof(msgs_g)/sizeof(struct isdn_msg))
+
+/** INTERFACE FCTS ***/
+int isdn_msg_get_index(struct isdn_msg msgs[], msg_t *msg, int nt)
+{
+ int i;
+
+ if (nt){
+ mISDNuser_head_t *hh = (mISDNuser_head_t*)msg->data;
+
+ for (i=0; i< msgs_max -1; i++) {
+ if ( (hh->prim&COMMAND_MASK)==(msgs[i].misdn_msg&COMMAND_MASK)) return i;
+ }
+
+ } else {
+ iframe_t *frm = (iframe_t*)msg->data;
+
+ for (i=0; i< msgs_max -1; i++)
+ if ( (frm->prim&COMMAND_MASK)==(msgs[i].misdn_msg&COMMAND_MASK)) return i;
+ }
+
+ return -1;
+}
+
+int isdn_msg_get_index_by_event(struct isdn_msg msgs[], enum event_e event, int nt)
+{
+ int i;
+ for (i=0; i< msgs_max; i++)
+ if ( event == msgs[i].event) return i;
+
+ cb_log(10,0, "get_index: event not found!\n");
+
+ return -1;
+}
+
+enum event_e isdn_msg_get_event(struct isdn_msg msgs[], msg_t *msg, int nt)
+{
+ int i=isdn_msg_get_index(msgs, msg, nt);
+ if(i>=0) return msgs[i].event;
+ return EVENT_UNKNOWN;
+}
+
+char * isdn_msg_get_info(struct isdn_msg msgs[], msg_t *msg, int nt)
+{
+ int i=isdn_msg_get_index(msgs, msg, nt);
+ if(i>=0) return msgs[i].info;
+ return NULL;
+}
+
+
+char EVENT_CLEAN_INFO[] = "CLEAN_UP";
+char EVENT_DTMF_TONE_INFO[] = "DTMF_TONE";
+char EVENT_NEW_L3ID_INFO[] = "NEW_L3ID";
+char EVENT_NEW_BC_INFO[] = "NEW_BC";
+char EVENT_PORT_ALARM_INFO[] = "ALARM";
+char EVENT_NEW_CHANNEL_INFO[] = "NEW_CHANNEL";
+char EVENT_BCHAN_DATA_INFO[] = "BCHAN_DATA";
+char EVENT_BCHAN_ACTIVATED_INFO[] = "BCHAN_ACTIVATED";
+char EVENT_TONE_GENERATE_INFO[] = "TONE_GENERATE";
+char EVENT_BCHAN_ERROR_INFO[] = "BCHAN_ERROR";
+
+char * isdn_get_info(struct isdn_msg msgs[], enum event_e event, int nt)
+{
+ int i=isdn_msg_get_index_by_event(msgs, event, nt);
+
+ if(i>=0) return msgs[i].info;
+
+ if (event == EVENT_CLEANUP) return EVENT_CLEAN_INFO;
+ if (event == EVENT_DTMF_TONE) return EVENT_DTMF_TONE_INFO;
+ if (event == EVENT_NEW_L3ID) return EVENT_NEW_L3ID_INFO;
+ if (event == EVENT_NEW_BC) return EVENT_NEW_BC_INFO;
+ if (event == EVENT_NEW_CHANNEL) return EVENT_NEW_CHANNEL_INFO;
+ if (event == EVENT_BCHAN_DATA) return EVENT_BCHAN_DATA_INFO;
+ if (event == EVENT_BCHAN_ACTIVATED) return EVENT_BCHAN_ACTIVATED_INFO;
+ if (event == EVENT_TONE_GENERATE) return EVENT_TONE_GENERATE_INFO;
+ if (event == EVENT_PORT_ALARM) return EVENT_PORT_ALARM_INFO;
+ if (event == EVENT_BCHAN_ERROR) return EVENT_BCHAN_ERROR_INFO;
+
+ return NULL;
+}
+
+int isdn_msg_parse_event(struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
+{
+ int i=isdn_msg_get_index(msgs, msg, nt);
+ if(i<0) return -1;
+
+ msgs[i].msg_parser(msgs, msg, bc, nt);
+ return 0;
+}
+
+msg_t * isdn_msg_build_event(struct isdn_msg msgs[], struct misdn_bchannel *bc, enum event_e event, int nt)
+{
+ int i=isdn_msg_get_index_by_event(msgs, event, nt);
+ if(i<0) return NULL;
+
+ return msgs[i].msg_builder(msgs, bc, nt);
+}
diff --git a/trunk/channels/misdn/portinfo.c b/trunk/channels/misdn/portinfo.c
new file mode 100644
index 000000000..8223164e5
--- /dev/null
+++ b/trunk/channels/misdn/portinfo.c
@@ -0,0 +1,202 @@
+/*! \file \brief
+ * Interface to mISDN - ???
+ * \author Christian Richter <crich@beronet.com>
+ */
+
+
+#include "isdn_lib.h"
+#include "isdn_lib_intern.h"
+
+
+/*
+ * global function to show all available isdn ports
+ */
+void isdn_port_info(void)
+{
+ int err;
+ int i, ii, p;
+ int useable, nt, pri;
+ unsigned char buff[1025];
+ iframe_t *frm = (iframe_t *)buff;
+ stack_info_t *stinf;
+ int device;
+
+ /* open mISDN */
+ if ((device = mISDN_open()) < 0)
+ {
+ fprintf(stderr, "mISDN_open() failed: ret=%d errno=%d (%s) Check for mISDN modules and device.\n", device, errno, strerror(errno));
+ exit(-1);
+ }
+
+ /* get number of stacks */
+ i = 1;
+ ii = mISDN_get_stack_count(device);
+ printf("\n");
+ if (ii <= 0)
+ {
+ printf("Found no card. Please be sure to load card drivers.\n");
+ }
+
+ /* loop the number of cards and get their info */
+ while(i <= ii)
+ {
+ err = mISDN_get_stack_info(device, i, buff, sizeof(buff));
+ if (err <= 0)
+ {
+ fprintf(stderr, "mISDN_get_stack_info() failed: port=%d err=%d\n", i, err);
+ break;
+ }
+ stinf = (stack_info_t *)&frm->data.p;
+
+ nt = pri = 0;
+ useable = 1;
+
+ /* output the port info */
+ printf("Port %2d: ", i);
+ switch(stinf->pid.protocol[0] & ~ISDN_PID_FEATURE_MASK)
+ {
+ case ISDN_PID_L0_TE_S0:
+ printf("TE-mode BRI S/T interface line (for phone lines)");
+#if 0
+ if (stinf->pid.protocol[0] & ISDN_PID_L0_TE_S0_HFC & ISDN_PID_FEATURE_MASK)
+ printf(" HFC multiport card");
+#endif
+ break;
+ case ISDN_PID_L0_NT_S0:
+ nt = 1;
+ printf("NT-mode BRI S/T interface port (for phones)");
+#if 0
+ if (stinf->pid.protocol[0] & ISDN_PID_L0_NT_S0_HFC & ISDN_PID_FEATURE_MASK)
+ printf(" HFC multiport card");
+#endif
+ break;
+ case ISDN_PID_L0_TE_U:
+ printf("TE-mode BRI U interface line");
+ break;
+ case ISDN_PID_L0_NT_U:
+ nt = 1;
+ printf("NT-mode BRI U interface port");
+ break;
+ case ISDN_PID_L0_TE_UP2:
+ printf("TE-mode BRI Up2 interface line");
+ break;
+ case ISDN_PID_L0_NT_UP2:
+ nt = 1;
+ printf("NT-mode BRI Up2 interface port");
+ break;
+ case ISDN_PID_L0_TE_E1:
+ pri = 1;
+ printf("TE-mode PRI E1 interface line (for phone lines)");
+#if 0
+ if (stinf->pid.protocol[0] & ISDN_PID_L0_TE_E1_HFC & ISDN_PID_FEATURE_MASK)
+ printf(" HFC-E1 card");
+#endif
+ break;
+ case ISDN_PID_L0_NT_E1:
+ nt = 1;
+ pri = 1;
+ printf("NT-mode PRI E1 interface port (for phones)");
+#if 0
+ if (stinf->pid.protocol[0] & ISDN_PID_L0_NT_E1_HFC & ISDN_PID_FEATURE_MASK)
+ printf(" HFC-E1 card");
+#endif
+ break;
+ default:
+ useable = 0;
+ printf("unknown type 0x%08x",stinf->pid.protocol[0]);
+ }
+ printf("\n");
+
+ if (nt)
+ {
+ if (stinf->pid.protocol[1] == 0)
+ {
+ useable = 0;
+ printf(" -> Missing layer 1 NT-mode protocol.\n");
+ }
+ p = 2;
+ while(p <= MAX_LAYER_NR) {
+ if (stinf->pid.protocol[p])
+ {
+ useable = 0;
+ printf(" -> Layer %d protocol 0x%08x is detected, but not allowed for NT lib.\n", p, stinf->pid.protocol[p]);
+ }
+ p++;
+ }
+ if (useable)
+ {
+ if (pri)
+ printf(" -> Interface is Point-To-Point (PRI).\n");
+ else
+ printf(" -> Interface can be Poin-To-Point/Multipoint.\n");
+ }
+ } else
+ {
+ if (stinf->pid.protocol[1] == 0)
+ {
+ useable = 0;
+ printf(" -> Missing layer 1 protocol.\n");
+ }
+ if (stinf->pid.protocol[2] == 0)
+ {
+ useable = 0;
+ printf(" -> Missing layer 2 protocol.\n");
+ }
+ if (stinf->pid.protocol[2] & ISDN_PID_L2_DF_PTP)
+ {
+ printf(" -> Interface is Poin-To-Point.\n");
+ }
+ if (stinf->pid.protocol[3] == 0)
+ {
+ useable = 0;
+ printf(" -> Missing layer 3 protocol.\n");
+ } else
+ {
+ printf(" -> Protocol: ");
+ switch(stinf->pid.protocol[3] & ~ISDN_PID_FEATURE_MASK)
+ {
+ case ISDN_PID_L3_DSS1USER:
+ printf("DSS1 (Euro ISDN)");
+ break;
+
+ default:
+ useable = 0;
+ printf("unknown protocol 0x%08x",stinf->pid.protocol[3]);
+ }
+ printf("\n");
+ }
+ p = 4;
+ while(p <= MAX_LAYER_NR) {
+ if (stinf->pid.protocol[p])
+ {
+ useable = 0;
+ printf(" -> Layer %d protocol 0x%08x is detected, but not allowed for TE lib.\n", p, stinf->pid.protocol[p]);
+ }
+ p++;
+ }
+ printf(" -> childcnt: %d\n",stinf->childcnt);
+ }
+
+ if (!useable)
+ printf(" * Port NOT useable for PBX\n");
+
+ printf("--------\n");
+
+ i++;
+ }
+ printf("\n");
+
+ /* close mISDN */
+ if ((err = mISDN_close(device)))
+ {
+ fprintf(stderr, "mISDN_close() failed: err=%d '%s'\n", err, strerror(err));
+ exit(-1);
+ }
+}
+
+
+int main()
+{
+ isdn_port_info();
+ return 0;
+}
diff --git a/trunk/channels/misdn_config.c b/trunk/channels/misdn_config.c
new file mode 100644
index 000000000..bbe23fb6c
--- /dev/null
+++ b/trunk/channels/misdn_config.c
@@ -0,0 +1,1158 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2005, Christian Richter
+ *
+ * Christian Richter <crich@beronet.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ *
+ */
+
+/*!
+ * \file
+ *
+ * \brief chan_misdn configuration management
+ * \author Christian Richter <crich@beronet.com>
+ *
+ * \ingroup channel_drivers
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "chan_misdn_config.h"
+
+#include "asterisk/config.h"
+#include "asterisk/channel.h"
+#include "asterisk/lock.h"
+#include "asterisk/pbx.h"
+#include "asterisk/strings.h"
+#include "asterisk/utils.h"
+
+#define AST_LOAD_CFG ast_config_load
+#define AST_DESTROY_CFG ast_config_destroy
+
+#define NO_DEFAULT "<>"
+#define NONE 0
+
+#define GEN_CFG 1
+#define PORT_CFG 2
+#define NUM_GEN_ELEMENTS (sizeof(gen_spec) / sizeof(struct misdn_cfg_spec))
+#define NUM_PORT_ELEMENTS (sizeof(port_spec) / sizeof(struct misdn_cfg_spec))
+
+/*! Global jitterbuffer configuration - by default, jb is disabled */
+static struct ast_jb_conf default_jbconf =
+{
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = "",
+};
+
+static struct ast_jb_conf global_jbconf;
+
+enum misdn_cfg_type {
+ MISDN_CTYPE_STR,
+ MISDN_CTYPE_INT,
+ MISDN_CTYPE_BOOL,
+ MISDN_CTYPE_BOOLINT,
+ MISDN_CTYPE_MSNLIST,
+ MISDN_CTYPE_ASTGROUP
+};
+
+struct msn_list {
+ char *msn;
+ struct msn_list *next;
+};
+
+union misdn_cfg_pt {
+ char *str;
+ int *num;
+ struct msn_list *ml;
+ ast_group_t *grp;
+ void *any;
+};
+
+struct misdn_cfg_spec {
+ char name[BUFFERSIZE];
+ enum misdn_cfg_elements elem;
+ enum misdn_cfg_type type;
+ char def[BUFFERSIZE];
+ int boolint_def;
+ char desc[BUFFERSIZE];
+};
+
+
+static const char ports_description[] =
+ "Define your ports, e.g. 1,2 (depends on mISDN-driver loading order).";
+
+static const struct misdn_cfg_spec port_spec[] = {
+ { "name", MISDN_CFG_GROUPNAME, MISDN_CTYPE_STR, "default", NONE,
+ "Name of the portgroup." },
+ { "allowed_bearers", MISDN_CFG_ALLOWED_BEARERS, MISDN_CTYPE_STR, "all", NONE,
+ "Here you can define which bearers should be allowed." },
+ { "rxgain", MISDN_CFG_RXGAIN, MISDN_CTYPE_INT, "0", NONE,
+ "Set this between -8 and 8 to change the RX Gain." },
+ { "txgain", MISDN_CFG_TXGAIN, MISDN_CTYPE_INT, "0", NONE,
+ "Set this between -8 and 8 to change the TX Gain." },
+ { "te_choose_channel", MISDN_CFG_TE_CHOOSE_CHANNEL, MISDN_CTYPE_BOOL, "no", NONE,
+ "Some telcos espacially in NL seem to need this set to yes,\n"
+ "\talso in switzerland this seems to be important." },
+ { "far_alerting", MISDN_CFG_FAR_ALERTING, MISDN_CTYPE_BOOL, "no", NONE,
+ "If we should generate ringing for chan_sip and others." },
+ { "pmp_l1_check", MISDN_CFG_PMP_L1_CHECK, MISDN_CTYPE_BOOL, "no", NONE,
+ "This option defines, if chan_misdn should check the L1 on a PMP\n"
+ "\tbefore makeing a group call on it. The L1 may go down for PMP Ports\n"
+ "\tso we might need this.\n"
+ "\tBut be aware! a broken or plugged off cable might be used for a group call\n"
+ "\tas well, since chan_misdn has no chance to distinguish if the L1 is down\n"
+ "\tbecause of a lost Link or because the Provider shut it down..." },
+ { "block_on_alarm", MISDN_CFG_ALARM_BLOCK, MISDN_CTYPE_BOOL, "no", NONE ,
+ "Block this port if we have an alarm on it."
+ "default: yes\n" },
+ { "hdlc", MISDN_CFG_HDLC, MISDN_CTYPE_BOOL, "no", NONE,
+ "Set this to yes, if you want to bridge a mISDN data channel to\n"
+ "\tanother channel type or to an application." },
+ { "context", MISDN_CFG_CONTEXT, MISDN_CTYPE_STR, "default", NONE,
+ "Context to use for incoming calls." },
+ { "language", MISDN_CFG_LANGUAGE, MISDN_CTYPE_STR, "en", NONE,
+ "Language." },
+ { "musicclass", MISDN_CFG_MUSICCLASS, MISDN_CTYPE_STR, "default", NONE,
+ "Sets the musiconhold class." },
+ { "callerid", MISDN_CFG_CALLERID, MISDN_CTYPE_STR, "", NONE,
+ "Sets the caller ID." },
+ { "method", MISDN_CFG_METHOD, MISDN_CTYPE_STR, "standard", NONE,
+ "Sets the method to use for channel selection:\n"
+ "\t standard - always choose the first free channel with the lowest number\n"
+ "\t round_robin - use the round robin algorithm to select a channel. use this\n"
+ "\t if you want to balance your load." },
+ { "dialplan", MISDN_CFG_DIALPLAN, MISDN_CTYPE_INT, "0", NONE,
+ "Dialplan means Type Of Number in ISDN Terms (for outgoing calls)\n"
+ "\n"
+ "\tThere are different types of the dialplan:\n"
+ "\n"
+ "\tdialplan -> outgoing Number\n"
+ "\tlocaldialplan -> callerid\n"
+ "\tcpndialplan -> connected party number\n"
+ "\n"
+ "\tdialplan options:\n"
+ "\n"
+ "\t0 - unknown\n"
+ "\t1 - International\n"
+ "\t2 - National\n"
+ "\t4 - Subscriber\n"
+ "\n"
+ "\tThis setting is used for outgoing calls." },
+ { "localdialplan", MISDN_CFG_LOCALDIALPLAN, MISDN_CTYPE_INT, "0", NONE,
+ "Dialplan means Type Of Number in ISDN Terms (for outgoing calls)\n"
+ "\n"
+ "\tThere are different types of the dialplan:\n"
+ "\n"
+ "\tdialplan -> outgoing Number\n"
+ "\tlocaldialplan -> callerid\n"
+ "\tcpndialplan -> connected party number\n"
+ "\n"
+ "\tdialplan options:\n"
+ "\n"
+ "\t0 - unknown\n"
+ "\t1 - International\n"
+ "\t2 - National\n"
+ "\t4 - Subscriber\n"
+ "\n"
+ "\tThis setting is used for outgoing calls" },
+ { "cpndialplan", MISDN_CFG_CPNDIALPLAN, MISDN_CTYPE_INT, "0", NONE,
+ "Dialplan means Type Of Number in ISDN Terms (for outgoing calls)\n"
+ "\n"
+ "\tThere are different types of the dialplan:\n"
+ "\n"
+ "\tdialplan -> outgoing Number\n"
+ "\tlocaldialplan -> callerid\n"
+ "\tcpndialplan -> connected party number\n"
+ "\n"
+ "\tdialplan options:\n"
+ "\n"
+ "\t0 - unknown\n"
+ "\t1 - International\n"
+ "\t2 - National\n"
+ "\t4 - Subscriber\n"
+ "\n"
+ "\tThis setting is used for outgoing calls." },
+ { "nationalprefix", MISDN_CFG_NATPREFIX, MISDN_CTYPE_STR, "0", NONE,
+ "Prefix for national, this is put before the\n"
+ "\toad if an according dialplan is set by the other end." },
+ { "internationalprefix", MISDN_CFG_INTERNATPREFIX, MISDN_CTYPE_STR, "00", NONE,
+ "Prefix for international, this is put before the\n"
+ "\toad if an according dialplan is set by the other end." },
+ { "presentation", MISDN_CFG_PRES, MISDN_CTYPE_INT, "-1", NONE,
+ "These (presentation and screen) are the exact isdn screening and presentation\n"
+ "\tindicators.\n"
+ "\tIf -1 is given for both values, the presentation indicators are used from\n"
+ "\tAsterisks SetCallerPres application.\n"
+ "\n"
+ "\tscreen=0, presentation=0 -> callerid presented not screened\n"
+ "\tscreen=1, presentation=1 -> callerid presented but screened (the remote end doesn't see it!)" },
+ { "screen", MISDN_CFG_SCREEN, MISDN_CTYPE_INT, "-1", NONE,
+ "These (presentation and screen) are the exact isdn screening and presentation\n"
+ "\tindicators.\n"
+ "\tIf -1 is given for both values, the presentation indicators are used from\n"
+ "\tAsterisks SetCallerPres application.\n"
+ "\n"
+ "\tscreen=0, presentation=0 -> callerid presented not screened\n"
+ "\tscreen=1, presentation=1 -> callerid presented but screened (the remote end doesn't see it!)" },
+ { "always_immediate", MISDN_CFG_ALWAYS_IMMEDIATE, MISDN_CTYPE_BOOL, "no", NONE,
+ "Enable this to get into the s dialplan-extension.\n"
+ "\tThere you can use DigitTimeout if you can't or don't want to use\n"
+ "\tisdn overlap dial.\n"
+ "\tNOTE: This will jump into the s extension for every exten!" },
+ { "nodialtone", MISDN_CFG_NODIALTONE, MISDN_CTYPE_BOOL, "no", NONE,
+ "Enable this to prevent chan_misdn to generate the dialtone\n"
+ "\tThis makes only sense together with the always_immediate=yes option\n"
+ "\tto generate your own dialtone with Playtones or so."},
+ { "immediate", MISDN_CFG_IMMEDIATE, MISDN_CTYPE_BOOL, "no", NONE,
+ "Enable this if you want callers which called exactly the base\n"
+ "\tnumber (so no extension is set) to jump into the s extension.\n"
+ "\tIf the user dials something more, it jumps to the correct extension\n"
+ "\tinstead." },
+ { "senddtmf", MISDN_CFG_SENDDTMF, MISDN_CTYPE_BOOL, "no", NONE,
+ "Enable this if we should produce DTMF Tones ourselves." },
+ { "astdtmf", MISDN_CFG_ASTDTMF, MISDN_CTYPE_BOOL, "no", NONE,
+ "Enable this if you want to use the Asterisk dtmf detector\n"
+ "instead of the mISDN_dsp/hfcmulti one."
+ },
+ { "hold_allowed", MISDN_CFG_HOLD_ALLOWED, MISDN_CTYPE_BOOL, "no", NONE,
+ "Enable this to have support for hold and retrieve." },
+ { "early_bconnect", MISDN_CFG_EARLY_BCONNECT, MISDN_CTYPE_BOOL, "yes", NONE,
+ "Disable this if you don't mind correct handling of Progress Indicators." },
+ { "incoming_early_audio", MISDN_CFG_INCOMING_EARLY_AUDIO, MISDN_CTYPE_BOOL, "no", NONE,
+ "Turn this on if you like to send Tone Indications to a Incoming\n"
+ "\tisdn channel on a TE Port. Rarely used, only if the Telco allows\n"
+ "\tyou to send indications by yourself, normally the Telco sends the\n"
+ "\tindications to the remote party." },
+ { "echocancel", MISDN_CFG_ECHOCANCEL, MISDN_CTYPE_BOOLINT, "0", 128,
+ "This enables echocancellation, with the given number of taps.\n"
+ "\tBe aware, move this setting only to outgoing portgroups!\n"
+ "\tA value of zero turns echocancellation off.\n"
+ "\n"
+ "\tPossible values are: 0,32,64,128,256,yes(=128),no(=0)" },
+#ifdef MISDN_1_2
+ { "pipeline", MISDN_CFG_PIPELINE, MISDN_CTYPE_STR, NO_DEFAULT, NONE,
+ "Set the configuration string for the mISDN dsp pipeline.\n"
+ "\n"
+ "\tExample for enabling the mg2 echo cancellation module with deftaps\n"
+ "\tset to 128:\n"
+ "\t\tmg2ec(deftaps=128)" },
+#endif
+#ifdef WITH_BEROEC
+ { "bnechocancel", MISDN_CFG_BNECHOCANCEL, MISDN_CTYPE_BOOLINT, "yes", 64,
+ "echotail in ms (1-200)\n"},
+ { "bnec_antihowl", MISDN_CFG_BNEC_ANTIHOWL, MISDN_CTYPE_INT, "0", NONE,
+ "Use antihowl\n"},
+ { "bnec_nlp", MISDN_CFG_BNEC_NLP, MISDN_CTYPE_BOOL, "yes", NONE,
+ "Nonlinear Processing (much faster adaption)"},
+ { "bnec_zerocoeff", MISDN_CFG_BNEC_ZEROCOEFF, MISDN_CTYPE_BOOL, "no", NONE,
+ "ZeroCoeffeciens\n"},
+ { "bnec_tonedisabler", MISDN_CFG_BNEC_TD, MISDN_CTYPE_BOOL, "no", NONE,
+ "Disable Tone\n"},
+ { "bnec_adaption", MISDN_CFG_BNEC_ADAPT, MISDN_CTYPE_INT, "1", NONE,
+ "Adaption mode (0=no,1=full,2=fast)\n"},
+#endif
+ { "need_more_infos", MISDN_CFG_NEED_MORE_INFOS, MISDN_CTYPE_BOOL, "0", NONE,
+ "Send Setup_Acknowledge on incoming calls anyway (instead of PROCEEDING),\n"
+ "\tthis requests additional Infos, so we can waitfordigits without much\n"
+ "\tissues. This works only for PTP Ports" },
+ { "noautorespond_on_setup", MISDN_CFG_NOAUTORESPOND_ON_SETUP, MISDN_CTYPE_BOOL, "0", NONE,
+ "Do not send SETUP_ACKNOWLEDGE or PROCEEDING automatically to the calling Party.\n"
+ "Instead we directly jump into the dialplan. This might be useful for fast call\n"
+ "rejection, or for some broken switches, that need hangup causes like busy in the.\n"
+ "RELEASE_COMPLETE Message, instead of the DISCONNECT Message.\n"},
+ { "jitterbuffer", MISDN_CFG_JITTERBUFFER, MISDN_CTYPE_INT, "4000", NONE,
+ "The jitterbuffer." },
+ { "jitterbuffer_upper_threshold", MISDN_CFG_JITTERBUFFER_UPPER_THRESHOLD, MISDN_CTYPE_INT, "0", NONE,
+ "Change this threshold to enable dejitter functionality." },
+ { "callgroup", MISDN_CFG_CALLGROUP, MISDN_CTYPE_ASTGROUP, NO_DEFAULT, NONE,
+ "Callgroup." },
+ { "pickupgroup", MISDN_CFG_PICKUPGROUP, MISDN_CTYPE_ASTGROUP, NO_DEFAULT, NONE,
+ "Pickupgroup." },
+ { "max_incoming", MISDN_CFG_MAX_IN, MISDN_CTYPE_INT, "-1", NONE,
+ "Defines the maximum amount of incoming calls per port for this group.\n"
+ "\tCalls which exceed the maximum will be marked with the channel varible\n"
+ "\tMAX_OVERFLOW. It will contain the amount of overflowed calls" },
+ { "max_outgoing", MISDN_CFG_MAX_OUT, MISDN_CTYPE_INT, "-1", NONE,
+ "Defines the maximum amount of outgoing calls per port for this group\n"
+ "\texceeding calls will be rejected" },
+
+ { "reject_cause", MISDN_CFG_REJECT_CAUSE, MISDN_CTYPE_INT, "21", NONE,
+ "Defines the cause with which a 3. call is rejected on PTMP BRI."},
+ { "faxdetect", MISDN_CFG_FAXDETECT, MISDN_CTYPE_STR, "no", NONE,
+ "Setup fax detection:\n"
+ "\t no - no fax detection\n"
+ "\t incoming - fax detection for incoming calls\n"
+ "\t outgoing - fax detection for outgoing calls\n"
+ "\t both - fax detection for incoming and outgoing calls\n"
+ "\tAdd +nojump to your value (i.e. faxdetect=both+nojump) if you don't want to jump into the\n"
+ "\tfax-extension but still want to detect the fax and prepare the channel for fax transfer." },
+ { "faxdetect_timeout", MISDN_CFG_FAXDETECT_TIMEOUT, MISDN_CTYPE_INT, "5", NONE,
+ "Number of seconds the fax detection should do its job. After the given period of time,\n"
+ "\twe assume that it's not a fax call and save some CPU time by turning off fax detection.\n"
+ "\tSet this to 0 if you don't want a timeout (never stop detecting)." },
+ { "faxdetect_context", MISDN_CFG_FAXDETECT_CONTEXT, MISDN_CTYPE_STR, NO_DEFAULT, NONE,
+ "Context to jump into if we detect a fax. Don't set this if you want to stay in the current context." },
+ { "l1watcher_timeout", MISDN_CFG_L1_TIMEOUT, MISDN_CTYPE_BOOLINT, "0", 4,
+ "Watches the layer 1. If the layer 1 is down, it tries to\n"
+ "\tget it up. The timeout is given in seconds. with 0 as value it\n"
+ "\tdoes not watch the l1 at all\n"
+ "\n"
+ "\tThis option is only read at loading time of chan_misdn, which\n"
+ "\tmeans you need to unload and load chan_misdn to change the value,\n"
+ "\tan Asterisk restart should do the trick." },
+ { "overlapdial", MISDN_CFG_OVERLAP_DIAL, MISDN_CTYPE_BOOLINT, "0", 4,
+ "Enables overlap dial for the given amount of seconds.\n"
+ "\tPossible values are positive integers or:\n"
+ "\t yes (= 4 seconds)\n"
+ "\t no (= 0 seconds = disabled)" },
+ { "nttimeout", MISDN_CFG_NTTIMEOUT, MISDN_CTYPE_BOOL, "no", NONE ,
+ "Set this to yes if you want calls disconnected in overlap mode\n"
+ "\twhen a timeout happens." },
+ { "bridging", MISDN_CFG_BRIDGING, MISDN_CTYPE_BOOL, "yes", NONE,
+ "Set this to yes/no, default is yes.\n"
+ "This can be used to have bridging enabled in general and to\n"
+ "disable it for specific ports. It makes sense to disable\n"
+ "bridging on NT Port where you plan to use the HOLD/RETRIEVE\n"
+ "features with ISDN phones.\n"
+ },
+ { "msns", MISDN_CFG_MSNS, MISDN_CTYPE_MSNLIST, "*", NONE,
+ "MSN's for TE ports, listen on those numbers on the above ports, and\n"
+ "\tindicate the incoming calls to Asterisk.\n"
+ "\tHere you can give a comma seperated list, or simply an '*' for any msn." },
+};
+
+static const struct misdn_cfg_spec gen_spec[] = {
+ { "debug", MISDN_GEN_DEBUG, MISDN_CTYPE_INT, "0", NONE,
+ "Sets the debugging flag:\n"
+ "\t0 - No Debug\n"
+ "\t1 - mISDN Messages and * - Messages, and * - State changes\n"
+ "\t2 - Messages + Message specific Informations (e.g. bearer capability)\n"
+ "\t3 - very Verbose, the above + lots of Driver specific infos\n"
+ "\t4 - even more Verbose than 3" },
+#ifndef MISDN_1_2
+ { "misdn_init", MISDN_GEN_MISDN_INIT, MISDN_CTYPE_STR, "/etc/misdn-init.conf", NONE,
+ "Set the path to the misdn-init.conf (for nt_ptp mode checking)." },
+#endif
+ { "tracefile", MISDN_GEN_TRACEFILE, MISDN_CTYPE_STR, "/var/log/asterisk/misdn.log", NONE,
+ "Set the path to the massively growing trace file, if you want that." },
+ { "bridging", MISDN_GEN_BRIDGING, MISDN_CTYPE_BOOL, "yes", NONE,
+ "Set this to yes if you want mISDN_dsp to bridge the calls in HW." },
+ { "stop_tone_after_first_digit", MISDN_GEN_STOP_TONE, MISDN_CTYPE_BOOL, "yes", NONE,
+ "Stops dialtone after getting first digit on NT Port." },
+ { "append_digits2exten", MISDN_GEN_APPEND_DIGITS2EXTEN, MISDN_CTYPE_BOOL, "yes", NONE,
+ "Wether to append overlapdialed Digits to Extension or not." },
+ { "dynamic_crypt", MISDN_GEN_DYNAMIC_CRYPT, MISDN_CTYPE_BOOL, "no", NONE,
+ "Wether to look out for dynamic crypting attempts." },
+ { "crypt_prefix", MISDN_GEN_CRYPT_PREFIX, MISDN_CTYPE_STR, NO_DEFAULT, NONE,
+ "What is used for crypting Protocol." },
+ { "crypt_keys", MISDN_GEN_CRYPT_KEYS, MISDN_CTYPE_STR, NO_DEFAULT, NONE,
+ "Keys for cryption, you reference them in the dialplan\n"
+ "\tLater also in dynamic encr." },
+ { "ntkeepcalls", MISDN_GEN_NTKEEPCALLS, MISDN_CTYPE_BOOL, "no", NONE,
+ "avoid dropping calls if the L2 goes down. some nortel pbx\n"
+ "do put down the L2/L1 for some milliseconds even if there\n"
+ "are running calls. with this option you can avoid dropping them\n" },
+ { "ntdebugflags", MISDN_GEN_NTDEBUGFLAGS, MISDN_CTYPE_INT, "0", NONE,
+ "No description yet."},
+ { "ntdebugfile", MISDN_GEN_NTDEBUGFILE, MISDN_CTYPE_STR, "/var/log/misdn-nt.log", NONE,
+ "No description yet." }
+};
+
+
+/* array of port configs, default is at position 0. */
+static union misdn_cfg_pt **port_cfg;
+/* max number of available ports, is set on init */
+static int max_ports;
+/* general config */
+static union misdn_cfg_pt *general_cfg;
+/* storing the ptp flag separated to save memory */
+static int *ptp;
+/* maps enum config elements to array positions */
+static int *map;
+
+static ast_mutex_t config_mutex;
+
+#define CLI_ERROR(name, value, section) ({ \
+ ast_log(LOG_WARNING, "misdn.conf: \"%s=%s\" (section: %s) invalid or out of range. " \
+ "Please edit your misdn.conf and then do a \"misdn reload\".\n", name, value, section); \
+})
+
+static int _enum_array_map (void)
+{
+ int i, j, ok;
+
+ for (i = MISDN_CFG_FIRST + 1; i < MISDN_CFG_LAST; ++i) {
+ if (i == MISDN_CFG_PTP)
+ continue;
+ ok = 0;
+ for (j = 0; j < NUM_PORT_ELEMENTS; ++j) {
+ if (port_spec[j].elem == i) {
+ map[i] = j;
+ ok = 1;
+ break;
+ }
+ }
+ if (!ok) {
+ ast_log(LOG_WARNING, "Enum element %d in misdn_cfg_elements (port section) has no corresponding element in the config struct!\n", i);
+ return -1;
+ }
+ }
+ for (i = MISDN_GEN_FIRST + 1; i < MISDN_GEN_LAST; ++i) {
+ ok = 0;
+ for (j = 0; j < NUM_GEN_ELEMENTS; ++j) {
+ if (gen_spec[j].elem == i) {
+ map[i] = j;
+ ok = 1;
+ break;
+ }
+ }
+ if (!ok) {
+ ast_log(LOG_WARNING, "Enum element %d in misdn_cfg_elements (general section) has no corresponding element in the config struct!\n", i);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int get_cfg_position (const char *name, int type)
+{
+ int i;
+
+ switch (type) {
+ case PORT_CFG:
+ for (i = 0; i < NUM_PORT_ELEMENTS; ++i) {
+ if (!strcasecmp(name, port_spec[i].name))
+ return i;
+ }
+ break;
+ case GEN_CFG:
+ for (i = 0; i < NUM_GEN_ELEMENTS; ++i) {
+ if (!strcasecmp(name, gen_spec[i].name))
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static inline void misdn_cfg_lock (void)
+{
+ ast_mutex_lock(&config_mutex);
+}
+
+static inline void misdn_cfg_unlock (void)
+{
+ ast_mutex_unlock(&config_mutex);
+}
+
+static void _free_msn_list (struct msn_list* iter)
+{
+ if (iter->next)
+ _free_msn_list(iter->next);
+ if (iter->msn)
+ ast_free(iter->msn);
+ ast_free(iter);
+}
+
+static void _free_port_cfg (void)
+{
+ int i, j;
+ int gn = map[MISDN_CFG_GROUPNAME];
+ union misdn_cfg_pt* free_list[max_ports + 2];
+
+ memset(free_list, 0, sizeof(free_list));
+ free_list[0] = port_cfg[0];
+ for (i = 1; i <= max_ports; ++i) {
+ if (port_cfg[i][gn].str) {
+ /* we always have a groupname in the non-default case, so this is fine */
+ for (j = 1; j <= max_ports; ++j) {
+ if (free_list[j] && free_list[j][gn].str == port_cfg[i][gn].str)
+ break;
+ else if (!free_list[j]) {
+ free_list[j] = port_cfg[i];
+ break;
+ }
+ }
+ }
+ }
+ for (j = 0; free_list[j]; ++j) {
+ for (i = 0; i < NUM_PORT_ELEMENTS; ++i) {
+ if (free_list[j][i].any) {
+ if (port_spec[i].type == MISDN_CTYPE_MSNLIST)
+ _free_msn_list(free_list[j][i].ml);
+ else
+ ast_free(free_list[j][i].any);
+ }
+ }
+ }
+}
+
+static void _free_general_cfg (void)
+{
+ int i;
+
+ for (i = 0; i < NUM_GEN_ELEMENTS; i++)
+ if (general_cfg[i].any)
+ ast_free(general_cfg[i].any);
+}
+
+void misdn_cfg_get(int port, enum misdn_cfg_elements elem, void *buf, int bufsize)
+{
+ int place;
+
+ if ((elem < MISDN_CFG_LAST) && !misdn_cfg_is_port_valid(port)) {
+ memset(buf, 0, bufsize);
+ ast_log(LOG_WARNING, "Invalid call to misdn_cfg_get! Port number %d is not valid.\n", port);
+ return;
+ }
+
+ misdn_cfg_lock();
+ if (elem == MISDN_CFG_PTP) {
+ if (!memcpy(buf, &ptp[port], (bufsize > ptp[port]) ? sizeof(ptp[port]) : bufsize))
+ memset(buf, 0, bufsize);
+ } else {
+ if ((place = map[elem]) < 0) {
+ memset(buf, 0, bufsize);
+ ast_log(LOG_WARNING, "Invalid call to misdn_cfg_get! Invalid element (%d) requested.\n", elem);
+ } else {
+ if (elem < MISDN_CFG_LAST) {
+ switch (port_spec[place].type) {
+ case MISDN_CTYPE_STR:
+ if (port_cfg[port][place].str) {
+ ast_copy_string(buf, port_cfg[port][place].str, bufsize);
+ } else if (port_cfg[0][place].str) {
+ ast_copy_string(buf, port_cfg[0][place].str, bufsize);
+ }
+ break;
+ default:
+ if (port_cfg[port][place].any)
+ memcpy(buf, port_cfg[port][place].any, bufsize);
+ else if (port_cfg[0][place].any)
+ memcpy(buf, port_cfg[0][place].any, bufsize);
+ else
+ memset(buf, 0, bufsize);
+ }
+ } else {
+ switch (gen_spec[place].type) {
+ case MISDN_CTYPE_STR:
+ ast_copy_string(buf, S_OR(general_cfg[place].str, ""), bufsize);
+ break;
+ default:
+ if (general_cfg[place].any)
+ memcpy(buf, general_cfg[place].any, bufsize);
+ else
+ memset(buf, 0, bufsize);
+ }
+ }
+ }
+ }
+ misdn_cfg_unlock();
+}
+
+enum misdn_cfg_elements misdn_cfg_get_elem(char *name)
+{
+ int pos;
+
+ /* here comes a hack to replace the (not existing) "name" elemet with the "ports" element */
+ if (!strcmp(name, "ports"))
+ return MISDN_CFG_GROUPNAME;
+ if (!strcmp(name, "name"))
+ return MISDN_CFG_FIRST;
+
+ pos = get_cfg_position(name, PORT_CFG);
+ if (pos >= 0)
+ return port_spec[pos].elem;
+
+ pos = get_cfg_position(name, GEN_CFG);
+ if (pos >= 0)
+ return gen_spec[pos].elem;
+
+ return MISDN_CFG_FIRST;
+}
+
+void misdn_cfg_get_name(enum misdn_cfg_elements elem, void *buf, int bufsize)
+{
+ struct misdn_cfg_spec *spec = NULL;
+ int place = map[elem];
+
+ /* the ptp hack */
+ if (elem == MISDN_CFG_PTP) {
+ memset(buf, 0, 1);
+ return;
+ }
+
+ /* here comes a hack to replace the (not existing) "name" elemet with the "ports" element */
+ if (elem == MISDN_CFG_GROUPNAME) {
+ if (!snprintf(buf, bufsize, "ports"))
+ memset(buf, 0, 1);
+ return;
+ }
+
+ if ((elem > MISDN_CFG_FIRST) && (elem < MISDN_CFG_LAST))
+ spec = (struct misdn_cfg_spec *)port_spec;
+ else if ((elem > MISDN_GEN_FIRST) && (elem < MISDN_GEN_LAST))
+ spec = (struct misdn_cfg_spec *)gen_spec;
+
+ ast_copy_string(buf, spec ? spec[place].name : "", bufsize);
+}
+
+void misdn_cfg_get_desc (enum misdn_cfg_elements elem, void *buf, int bufsize, void *buf_default, int bufsize_default)
+{
+ int place = map[elem];
+ struct misdn_cfg_spec *spec = NULL;
+
+ /* here comes a hack to replace the (not existing) "name" elemet with the "ports" element */
+ if (elem == MISDN_CFG_GROUPNAME) {
+ ast_copy_string(buf, ports_description, bufsize);
+ if (buf_default && bufsize_default)
+ memset(buf_default, 0, 1);
+ return;
+ }
+
+ if ((elem > MISDN_CFG_FIRST) && (elem < MISDN_CFG_LAST))
+ spec = (struct misdn_cfg_spec *)port_spec;
+ else if ((elem > MISDN_GEN_FIRST) && (elem < MISDN_GEN_LAST))
+ spec = (struct misdn_cfg_spec *)gen_spec;
+
+ if (!spec || !spec[place].desc)
+ memset(buf, 0, 1);
+ else {
+ ast_copy_string(buf, spec[place].desc, bufsize);
+ if (buf_default && bufsize) {
+ if (!strcmp(spec[place].def, NO_DEFAULT))
+ memset(buf_default, 0, 1);
+ else
+ ast_copy_string(buf_default, spec[place].def, bufsize_default);
+ }
+ }
+}
+
+int misdn_cfg_is_msn_valid (int port, char* msn)
+{
+ int re = 0;
+ struct msn_list *iter;
+
+ if (!misdn_cfg_is_port_valid(port)) {
+ ast_log(LOG_WARNING, "Invalid call to misdn_cfg_is_msn_valid! Port number %d is not valid.\n", port);
+ return 0;
+ }
+
+ misdn_cfg_lock();
+ if (port_cfg[port][map[MISDN_CFG_MSNS]].ml)
+ iter = port_cfg[port][map[MISDN_CFG_MSNS]].ml;
+ else
+ iter = port_cfg[0][map[MISDN_CFG_MSNS]].ml;
+ for (; iter; iter = iter->next)
+ if (*(iter->msn) == '*' || ast_extension_match(iter->msn, msn)) {
+ re = 1;
+ break;
+ }
+ misdn_cfg_unlock();
+
+ return re;
+}
+
+int misdn_cfg_is_port_valid (int port)
+{
+ int gn = map[MISDN_CFG_GROUPNAME];
+
+ return (port >= 1 && port <= max_ports && port_cfg[port][gn].str);
+}
+
+int misdn_cfg_is_group_method (char *group, enum misdn_cfg_method meth)
+{
+ int i, re = 0;
+ char *method ;
+
+ misdn_cfg_lock();
+
+ method = port_cfg[0][map[MISDN_CFG_METHOD]].str;
+
+ for (i = 1; i <= max_ports; i++) {
+ if (port_cfg[i] && port_cfg[i][map[MISDN_CFG_GROUPNAME]].str) {
+ if (!strcasecmp(port_cfg[i][map[MISDN_CFG_GROUPNAME]].str, group))
+ method = (port_cfg[i][map[MISDN_CFG_METHOD]].str ?
+ port_cfg[i][map[MISDN_CFG_METHOD]].str : port_cfg[0][map[MISDN_CFG_METHOD]].str);
+ }
+ }
+
+ if (method) {
+ switch (meth) {
+ case METHOD_STANDARD: re = !strcasecmp(method, "standard");
+ break;
+ case METHOD_ROUND_ROBIN: re = !strcasecmp(method, "round_robin");
+ break;
+ case METHOD_STANDARD_DEC: re = !strcasecmp(method, "standard_dec");
+ break;
+ }
+ }
+ misdn_cfg_unlock();
+
+ return re;
+}
+
+void misdn_cfg_get_ports_string (char *ports)
+{
+ char tmp[16];
+ int l, i;
+ int gn = map[MISDN_CFG_GROUPNAME];
+
+ *ports = 0;
+
+ misdn_cfg_lock();
+ for (i = 1; i <= max_ports; i++) {
+ if (port_cfg[i][gn].str) {
+ if (ptp[i])
+ sprintf(tmp, "%dptp,", i);
+ else
+ sprintf(tmp, "%d,", i);
+ strcat(ports, tmp);
+ }
+ }
+ misdn_cfg_unlock();
+
+ if ((l = strlen(ports)))
+ ports[l-1] = 0;
+}
+
+void misdn_cfg_get_config_string (int port, enum misdn_cfg_elements elem, char* buf, int bufsize)
+{
+ int place;
+ char tempbuf[BUFFERSIZE] = "";
+ struct msn_list *iter;
+
+ if ((elem < MISDN_CFG_LAST) && !misdn_cfg_is_port_valid(port)) {
+ *buf = 0;
+ ast_log(LOG_WARNING, "Invalid call to misdn_cfg_get_config_string! Port number %d is not valid.\n", port);
+ return;
+ }
+
+ place = map[elem];
+
+ misdn_cfg_lock();
+ if (elem == MISDN_CFG_PTP) {
+ snprintf(buf, bufsize, " -> ptp: %s", ptp[port] ? "yes" : "no");
+ }
+ else if (elem > MISDN_CFG_FIRST && elem < MISDN_CFG_LAST) {
+ switch (port_spec[place].type) {
+ case MISDN_CTYPE_INT:
+ case MISDN_CTYPE_BOOLINT:
+ if (port_cfg[port][place].num)
+ snprintf(buf, bufsize, " -> %s: %d", port_spec[place].name, *port_cfg[port][place].num);
+ else if (port_cfg[0][place].num)
+ snprintf(buf, bufsize, " -> %s: %d", port_spec[place].name, *port_cfg[0][place].num);
+ else
+ snprintf(buf, bufsize, " -> %s:", port_spec[place].name);
+ break;
+ case MISDN_CTYPE_BOOL:
+ if (port_cfg[port][place].num)
+ snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name, *port_cfg[port][place].num ? "yes" : "no");
+ else if (port_cfg[0][place].num)
+ snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name, *port_cfg[0][place].num ? "yes" : "no");
+ else
+ snprintf(buf, bufsize, " -> %s:", port_spec[place].name);
+ break;
+ case MISDN_CTYPE_ASTGROUP:
+ if (port_cfg[port][place].grp)
+ snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name,
+ ast_print_group(tempbuf, sizeof(tempbuf), *port_cfg[port][place].grp));
+ else if (port_cfg[0][place].grp)
+ snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name,
+ ast_print_group(tempbuf, sizeof(tempbuf), *port_cfg[0][place].grp));
+ else
+ snprintf(buf, bufsize, " -> %s:", port_spec[place].name);
+ break;
+ case MISDN_CTYPE_MSNLIST:
+ if (port_cfg[port][place].ml)
+ iter = port_cfg[port][place].ml;
+ else
+ iter = port_cfg[0][place].ml;
+ if (iter) {
+ for (; iter; iter = iter->next)
+ sprintf(tempbuf, "%s%s, ", tempbuf, iter->msn);
+ tempbuf[strlen(tempbuf)-2] = 0;
+ }
+ snprintf(buf, bufsize, " -> msns: %s", *tempbuf ? tempbuf : "none");
+ break;
+ case MISDN_CTYPE_STR:
+ if ( port_cfg[port][place].str) {
+ snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name, port_cfg[port][place].str);
+ } else if (port_cfg[0][place].str) {
+ snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name, port_cfg[0][place].str);
+ } else {
+ snprintf(buf, bufsize, " -> %s:", port_spec[place].name);
+ }
+ break;
+ }
+ } else if (elem > MISDN_GEN_FIRST && elem < MISDN_GEN_LAST) {
+ switch (gen_spec[place].type) {
+ case MISDN_CTYPE_INT:
+ case MISDN_CTYPE_BOOLINT:
+ if (general_cfg[place].num)
+ snprintf(buf, bufsize, " -> %s: %d", gen_spec[place].name, *general_cfg[place].num);
+ else
+ snprintf(buf, bufsize, " -> %s:", gen_spec[place].name);
+ break;
+ case MISDN_CTYPE_BOOL:
+ if (general_cfg[place].num)
+ snprintf(buf, bufsize, " -> %s: %s", gen_spec[place].name, *general_cfg[place].num ? "yes" : "no");
+ else
+ snprintf(buf, bufsize, " -> %s:", gen_spec[place].name);
+ break;
+ case MISDN_CTYPE_STR:
+ if ( general_cfg[place].str) {
+ snprintf(buf, bufsize, " -> %s: %s", gen_spec[place].name, general_cfg[place].str);
+ } else {
+ snprintf(buf, bufsize, " -> %s:", gen_spec[place].name);
+ }
+ break;
+ default:
+ snprintf(buf, bufsize, " -> type of %s not handled yet", gen_spec[place].name);
+ break;
+ }
+ } else {
+ *buf = 0;
+ ast_log(LOG_WARNING, "Invalid call to misdn_cfg_get_config_string! Invalid config element (%d) requested.\n", elem);
+ }
+ misdn_cfg_unlock();
+}
+
+int misdn_cfg_get_next_port (int port)
+{
+ int p = -1;
+ int gn = map[MISDN_CFG_GROUPNAME];
+
+ misdn_cfg_lock();
+ for (port++; port <= max_ports; port++) {
+ if (port_cfg[port][gn].str) {
+ p = port;
+ break;
+ }
+ }
+ misdn_cfg_unlock();
+
+ return p;
+}
+
+int misdn_cfg_get_next_port_spin (int port)
+{
+ int p = misdn_cfg_get_next_port(port);
+ return (p > 0) ? p : misdn_cfg_get_next_port(0);
+}
+
+static int _parse (union misdn_cfg_pt *dest, const char *value, enum misdn_cfg_type type, int boolint_def)
+{
+ int re = 0;
+ int len, tmp;
+ char *valtmp;
+ char *tmp2 = ast_strdupa(value);
+
+ switch (type) {
+ case MISDN_CTYPE_STR:
+ if ((len = strlen(value))) {
+ dest->str = ast_malloc((len + 1) * sizeof(char));
+ strncpy(dest->str, value, len);
+ dest->str[len] = 0;
+ } else {
+ dest->str = ast_malloc(sizeof(char));
+ dest->str[0] = 0;
+ }
+ break;
+ case MISDN_CTYPE_INT:
+ {
+ char *pat;
+ if (strchr(value,'x'))
+ pat="%x";
+ else
+ pat="%d";
+ if (sscanf(value, pat, &tmp)) {
+ dest->num = ast_malloc(sizeof(int));
+ memcpy(dest->num, &tmp, sizeof(int));
+ } else
+ re = -1;
+ }
+ break;
+ case MISDN_CTYPE_BOOL:
+ dest->num = ast_malloc(sizeof(int));
+ *(dest->num) = (ast_true(value) ? 1 : 0);
+ break;
+ case MISDN_CTYPE_BOOLINT:
+ dest->num = ast_malloc(sizeof(int));
+ if (sscanf(value, "%d", &tmp)) {
+ memcpy(dest->num, &tmp, sizeof(int));
+ } else {
+ *(dest->num) = (ast_true(value) ? boolint_def : 0);
+ }
+ break;
+ case MISDN_CTYPE_MSNLIST:
+ for (valtmp = strsep(&tmp2, ","); valtmp; valtmp = strsep(&tmp2, ",")) {
+ if ((len = strlen(valtmp))) {
+ struct msn_list *ml = ast_malloc(sizeof(*ml));
+ ml->msn = ast_calloc(len+1, sizeof(char));
+ strncpy(ml->msn, valtmp, len);
+ ml->next = dest->ml;
+ dest->ml = ml;
+ }
+ }
+ break;
+ case MISDN_CTYPE_ASTGROUP:
+ dest->grp = ast_malloc(sizeof(ast_group_t));
+ *(dest->grp) = ast_get_group(value);
+ break;
+ }
+
+ return re;
+}
+
+static void _build_general_config (struct ast_variable *v)
+{
+ int pos;
+
+ for (; v; v = v->next) {
+ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
+ continue;
+ if (((pos = get_cfg_position(v->name, GEN_CFG)) < 0) ||
+ (_parse(&general_cfg[pos], v->value, gen_spec[pos].type, gen_spec[pos].boolint_def) < 0))
+ CLI_ERROR(v->name, v->value, "general");
+ }
+}
+
+static void _build_port_config (struct ast_variable *v, char *cat)
+{
+ int pos, i;
+ union misdn_cfg_pt cfg_tmp[NUM_PORT_ELEMENTS];
+ int cfg_for_ports[max_ports + 1];
+
+ if (!v || !cat)
+ return;
+
+ memset(cfg_tmp, 0, sizeof(cfg_tmp));
+ memset(cfg_for_ports, 0, sizeof(cfg_for_ports));
+
+ if (!strcasecmp(cat, "default")) {
+ cfg_for_ports[0] = 1;
+ }
+
+ if (((pos = get_cfg_position("name", PORT_CFG)) < 0) ||
+ (_parse(&cfg_tmp[pos], cat, port_spec[pos].type, port_spec[pos].boolint_def) < 0)) {
+ CLI_ERROR(v->name, v->value, cat);
+ return;
+ }
+
+ for (; v; v = v->next) {
+ if (!strcasecmp(v->name, "ports")) {
+ char *token, *tmp = ast_strdupa(v->value);
+ char ptpbuf[BUFFERSIZE] = "";
+ int start, end;
+ for (token = strsep(&tmp, ","); token; token = strsep(&tmp, ","), *ptpbuf = 0) {
+ if (!*token)
+ continue;
+ if (sscanf(token, "%d-%d%s", &start, &end, ptpbuf) >= 2) {
+ for (; start <= end; start++) {
+ if (start <= max_ports && start > 0) {
+ cfg_for_ports[start] = 1;
+ ptp[start] = (strstr(ptpbuf, "ptp")) ? 1 : 0;
+ } else
+ CLI_ERROR(v->name, v->value, cat);
+ }
+ } else {
+ if (sscanf(token, "%d%s", &start, ptpbuf)) {
+ if (start <= max_ports && start > 0) {
+ cfg_for_ports[start] = 1;
+ ptp[start] = (strstr(ptpbuf, "ptp")) ? 1 : 0;
+ } else
+ CLI_ERROR(v->name, v->value, cat);
+ } else
+ CLI_ERROR(v->name, v->value, cat);
+ }
+ }
+ } else {
+ if (((pos = get_cfg_position(v->name, PORT_CFG)) < 0) ||
+ (_parse(&cfg_tmp[pos], v->value, port_spec[pos].type, port_spec[pos].boolint_def) < 0))
+ CLI_ERROR(v->name, v->value, cat);
+ }
+ }
+
+ for (i = 0; i < (max_ports + 1); ++i) {
+ if (cfg_for_ports[i]) {
+ memcpy(port_cfg[i], cfg_tmp, sizeof(cfg_tmp));
+ }
+ }
+}
+
+void misdn_cfg_update_ptp (void)
+{
+#ifndef MISDN_1_2
+ char misdn_init[BUFFERSIZE];
+ char line[BUFFERSIZE];
+ FILE *fp;
+ char *tok, *p, *end;
+ int port;
+
+ misdn_cfg_get(0, MISDN_GEN_MISDN_INIT, &misdn_init, sizeof(misdn_init));
+
+ if (!ast_strlen_zero(misdn_init)) {
+ fp = fopen(misdn_init, "r");
+ if (fp) {
+ while(fgets(line, sizeof(line), fp)) {
+ if (!strncmp(line, "nt_ptp", 6)) {
+ for (tok = strtok_r(line,",=", &p);
+ tok;
+ tok = strtok_r(NULL,",=", &p)) {
+ port = strtol(tok, &end, 10);
+ if (end != tok && misdn_cfg_is_port_valid(port)) {
+ misdn_cfg_lock();
+ ptp[port] = 1;
+ misdn_cfg_unlock();
+ }
+ }
+ }
+ }
+ fclose(fp);
+ } else {
+ ast_log(LOG_WARNING,"Couldn't open %s: %s\n", misdn_init, strerror(errno));
+ }
+ }
+#else
+ int i;
+ int proto;
+ char filename[128];
+ FILE *fp;
+
+ for (i = 1; i <= max_ports; ++i) {
+ snprintf(filename, sizeof(filename), "/sys/class/mISDN-stacks/st-%08x/protocol", i << 8);
+ fp = fopen(filename, "r");
+ if (!fp) {
+ ast_log(LOG_WARNING, "Could not open %s: %s\n", filename, strerror(errno));
+ continue;
+ }
+ if (fscanf(fp, "0x%08x", &proto) != 1)
+ ast_log(LOG_WARNING, "Could not parse contents of %s!\n", filename);
+ else
+ ptp[i] = proto & 1<<5 ? 1 : 0;
+ fclose(fp);
+ }
+#endif
+}
+
+static void _fill_defaults (void)
+{
+ int i;
+
+ for (i = 0; i < NUM_PORT_ELEMENTS; ++i) {
+ if (!port_cfg[0][i].any && strcasecmp(port_spec[i].def, NO_DEFAULT))
+ _parse(&(port_cfg[0][i]), (char *)port_spec[i].def, port_spec[i].type, port_spec[i].boolint_def);
+ }
+ for (i = 0; i < NUM_GEN_ELEMENTS; ++i) {
+ if (!general_cfg[i].any && strcasecmp(gen_spec[i].def, NO_DEFAULT))
+ _parse(&(general_cfg[i]), (char *)gen_spec[i].def, gen_spec[i].type, gen_spec[i].boolint_def);
+ }
+}
+
+void misdn_cfg_reload (void)
+{
+ misdn_cfg_init(0, 1);
+}
+
+void misdn_cfg_destroy (void)
+{
+ misdn_cfg_lock();
+
+ _free_port_cfg();
+ _free_general_cfg();
+
+ ast_free(port_cfg);
+ ast_free(general_cfg);
+ ast_free(ptp);
+ ast_free(map);
+
+ misdn_cfg_unlock();
+ ast_mutex_destroy(&config_mutex);
+}
+
+int misdn_cfg_init(int this_max_ports, int reload)
+{
+ char config[] = "misdn.conf";
+ char *cat, *p;
+ int i;
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+
+ if (!(cfg = AST_LOAD_CFG(config, config_flags))) {
+ ast_log(LOG_WARNING, "missing file: misdn.conf\n");
+ return -1;
+ } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+ return 0;
+
+ ast_mutex_init(&config_mutex);
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ misdn_cfg_lock();
+
+ if (this_max_ports) {
+ /* this is the first run */
+ max_ports = this_max_ports;
+ map = ast_calloc(MISDN_GEN_LAST + 1, sizeof(int));
+ if (_enum_array_map())
+ return -1;
+ p = ast_calloc(1, (max_ports + 1) * sizeof(union misdn_cfg_pt *)
+ + (max_ports + 1) * NUM_PORT_ELEMENTS * sizeof(union misdn_cfg_pt));
+ port_cfg = (union misdn_cfg_pt **)p;
+ p += (max_ports + 1) * sizeof(union misdn_cfg_pt *);
+ for (i = 0; i <= max_ports; ++i) {
+ port_cfg[i] = (union misdn_cfg_pt *)p;
+ p += NUM_PORT_ELEMENTS * sizeof(union misdn_cfg_pt);
+ }
+ general_cfg = ast_calloc(1, sizeof(union misdn_cfg_pt *) * NUM_GEN_ELEMENTS);
+ ptp = ast_calloc(max_ports + 1, sizeof(int));
+ }
+ else {
+ /* misdn reload */
+ _free_port_cfg();
+ _free_general_cfg();
+ memset(port_cfg[0], 0, NUM_PORT_ELEMENTS * sizeof(union misdn_cfg_pt) * (max_ports + 1));
+ memset(general_cfg, 0, sizeof(union misdn_cfg_pt *) * NUM_GEN_ELEMENTS);
+ memset(ptp, 0, sizeof(int) * (max_ports + 1));
+ }
+
+ cat = ast_category_browse(cfg, NULL);
+
+ while(cat) {
+ v = ast_variable_browse(cfg, cat);
+ if (!strcasecmp(cat, "general")) {
+ _build_general_config(v);
+ } else {
+ _build_port_config(v, cat);
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+
+ _fill_defaults();
+
+ misdn_cfg_unlock();
+ AST_DESTROY_CFG(cfg);
+
+ return 0;
+}
+
+struct ast_jb_conf *misdn_get_global_jbconf() {
+ return &global_jbconf;
+}
diff --git a/trunk/channels/vcodecs.c b/trunk/channels/vcodecs.c
new file mode 100644
index 000000000..ae3770920
--- /dev/null
+++ b/trunk/channels/vcodecs.c
@@ -0,0 +1,1253 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright 2007-2008, Sergio Fadda, Luigi Rizzo
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*
+ * Video codecs support for console_video.c
+ * $Revision$
+ */
+
+#include "asterisk.h"
+#include "console_video.h"
+#include "asterisk/frame.h"
+#include "asterisk/utils.h" /* ast_calloc() */
+
+struct video_out_desc;
+struct video_dec_desc;
+struct fbuf_t;
+
+/*
+ * Each codec is defined by a number of callbacks
+ */
+/*! \brief initialize the encoder */
+typedef int (*encoder_init_f)(AVCodecContext *v);
+
+/*! \brief actually call the encoder */
+typedef int (*encoder_encode_f)(struct video_out_desc *v);
+
+/*! \brief encapsulate the bistream in RTP frames */
+typedef struct ast_frame *(*encoder_encap_f)(struct fbuf_t *, int mtu,
+ struct ast_frame **tail);
+
+/*! \brief inizialize the decoder */
+typedef int (*decoder_init_f)(AVCodecContext *enc_ctx);
+
+/*! \brief extract the bitstream from RTP frames and store in the fbuf.
+ * return 0 if ok, 1 on error
+ */
+typedef int (*decoder_decap_f)(struct fbuf_t *b, uint8_t *data, int len);
+
+/*! \brief actually call the decoder */
+typedef int (*decoder_decode_f)(struct video_dec_desc *v, struct fbuf_t *b);
+
+struct video_codec_desc {
+ const char *name; /* format name */
+ int format; /* AST_FORMAT_* */
+ encoder_init_f enc_init;
+ encoder_encap_f enc_encap;
+ encoder_encode_f enc_run;
+ decoder_init_f dec_init;
+ decoder_decap_f dec_decap;
+ decoder_decode_f dec_run;
+};
+
+/*
+ * Descriptor for the incoming stream, with multiple buffers for the bitstream
+ * extracted from the RTP packets, RTP reassembly info, and a frame buffer
+ * for the decoded frame (buf).
+ * The descriptor is allocated as the first frame comes in.
+ *
+ * Incoming payload is stored in one of the dec_in[] buffers, which are
+ * emptied by the video thread. These buffers are organized in a circular
+ * queue, with dec_in_cur being the buffer in use by the incoming stream,
+ * and dec_in_dpy is the one being displayed. When the pointers need to
+ * be changed, we synchronize the access to them with dec_lock.
+ * When the list is full dec_in_cur = NULL (we cannot store new data),
+ * when the list is empty dec_in_dpy = NULL (we cannot display frames).
+ */
+struct video_dec_desc {
+ struct video_codec_desc *d_callbacks; /* decoder callbacks */
+ AVCodecContext *dec_ctx; /* information about the codec in the stream */
+ AVCodec *codec; /* reference to the codec */
+ AVFrame *d_frame; /* place to store the decoded frame */
+ AVCodecParserContext *parser;
+ uint16_t next_seq; /* must be 16 bit */
+ int discard; /* flag for discard status */
+#define N_DEC_IN 3 /* number of incoming buffers */
+ struct fbuf_t *dec_in_cur; /* buffer being filled in */
+ struct fbuf_t *dec_in_dpy; /* buffer to display */
+ struct fbuf_t dec_in[N_DEC_IN]; /* incoming bitstream, allocated/extended in fbuf_append() */
+ struct fbuf_t dec_out; /* decoded frame, no buffer (data is in AVFrame) */
+};
+
+#ifdef debugging_only
+
+/* some debugging code to check the bitstream:
+ * declare a bit buffer, initialize it, and fetch data from it.
+ */
+struct bitbuf {
+ const uint8_t *base;
+ int bitsize; /* total size in bits */
+ int ofs; /* next bit to read */
+};
+
+static struct bitbuf bitbuf_init(const uint8_t *base, int bitsize, int start_ofs)
+{
+ struct bitbuf a;
+ a.base = base;
+ a.bitsize = bitsize;
+ a.ofs = start_ofs;
+ return a;
+}
+
+static int bitbuf_left(struct bitbuf *b)
+{
+ return b->bitsize - b->ofs;
+}
+
+static uint32_t getbits(struct bitbuf *b, int n)
+{
+ int i, ofs;
+ const uint8_t *d;
+ uint8_t mask;
+ uint32_t retval = 0;
+ if (n> 31) {
+ ast_log(LOG_WARNING, "too many bits %d, max 32\n", n);
+ return 0;
+ }
+ if (n + b->ofs > b->bitsize) {
+ ast_log(LOG_WARNING, "bitbuf overflow %d of %d\n", n + b->ofs, b->bitsize);
+ n = b->bitsize - b->ofs;
+ }
+ ofs = 7 - b->ofs % 8; /* start from msb */
+ mask = 1 << ofs;
+ d = b->base + b->ofs / 8; /* current byte */
+ for (i=0 ; i < n; i++) {
+ retval += retval + (*d & mask ? 1 : 0); /* shift in new byte */
+ b->ofs++;
+ mask >>= 1;
+ if (mask == 0) {
+ d++;
+ mask = 0x80;
+ }
+ }
+ return retval;
+}
+
+static void check_h261(struct fbuf_t *b)
+{
+ struct bitbuf a = bitbuf_init(b->data, b->used * 8, 0);
+ uint32_t x, y;
+
+ x = getbits(&a, 20); /* PSC, 0000 0000 0000 0001 0000 */
+ if (x != 0x10) {
+ ast_log(LOG_WARNING, "bad PSC 0x%x\n", x);
+ return;
+ }
+ x = getbits(&a, 5); /* temporal reference */
+ y = getbits(&a, 6); /* ptype */
+ if (0)
+ ast_log(LOG_WARNING, "size %d TR %d PTY spl %d doc %d freeze %d %sCIF hi %d\n",
+ b->used,
+ x,
+ (y & 0x20) ? 1 : 0,
+ (y & 0x10) ? 1 : 0,
+ (y & 0x8) ? 1 : 0,
+ (y & 0x4) ? "" : "Q",
+ (y & 0x2) ? 1:0);
+ while ( (x = getbits(&a, 1)) == 1)
+ ast_log(LOG_WARNING, "PSPARE 0x%x\n", getbits(&a, 8));
+ // ast_log(LOG_WARNING, "PSPARE 0 - start GOB LAYER\n");
+ while ( (x = bitbuf_left(&a)) > 0) {
+ // ast_log(LOG_WARNING, "GBSC %d bits left\n", x);
+ x = getbits(&a, 16); /* GBSC 0000 0000 0000 0001 */
+ if (x != 0x1) {
+ ast_log(LOG_WARNING, "bad GBSC 0x%x\n", x);
+ break;
+ }
+ x = getbits(&a, 4); /* group number */
+ y = getbits(&a, 5); /* gquant */
+ if (x == 0) {
+ ast_log(LOG_WARNING, " bad GN %d\n", x);
+ break;
+ }
+ while ( (x = getbits(&a, 1)) == 1)
+ ast_log(LOG_WARNING, "GSPARE 0x%x\n", getbits(&a, 8));
+ while ( (x = bitbuf_left(&a)) > 0) { /* MB layer */
+ break;
+ }
+ }
+}
+
+void dump_buf(struct fbuf_t *b);
+void dump_buf(struct fbuf_t *b)
+{
+ int i, x, last2lines;
+ char buf[80];
+
+ last2lines = (b->used - 16) & ~0xf;
+ ast_log(LOG_WARNING, "buf size %d of %d\n", b->used, b->size);
+ for (i = 0; i < b->used; i++) {
+ x = i & 0xf;
+ if ( x == 0) { /* new line */
+ if (i != 0)
+ ast_log(LOG_WARNING, "%s\n", buf);
+ bzero(buf, sizeof(buf));
+ sprintf(buf, "%04x: ", i);
+ }
+ sprintf(buf + 6 + x*3, "%02x ", b->data[i]);
+ if (i > 31 && i < last2lines)
+ i = last2lines - 1;
+ }
+ if (buf[0])
+ ast_log(LOG_WARNING, "%s\n", buf);
+}
+#endif /* debugging_only */
+
+/*!
+ * Build an ast_frame for a given chunk of data, and link it into
+ * the queue, with possibly 'head' bytes at the beginning to
+ * fill in some fields later.
+ */
+static struct ast_frame *create_video_frame(uint8_t *start, uint8_t *end,
+ int format, int head, struct ast_frame *prev)
+{
+ int len = end-start;
+ uint8_t *data;
+ struct ast_frame *f;
+
+ data = ast_calloc(1, len+head);
+ f = ast_calloc(1, sizeof(*f));
+ if (f == NULL || data == NULL) {
+ ast_log(LOG_WARNING, "--- frame error f %p data %p len %d format %d\n",
+ f, data, len, format);
+ if (f)
+ ast_free(f);
+ if (data)
+ ast_free(data);
+ return NULL;
+ }
+ memcpy(data+head, start, len);
+ f->data = data;
+ f->mallocd = AST_MALLOCD_DATA | AST_MALLOCD_HDR;
+ //f->has_timing_info = 1;
+ //f->ts = ast_tvdiff_ms(ast_tvnow(), out->ts);
+ f->datalen = len+head;
+ f->frametype = AST_FRAME_VIDEO;
+ f->subclass = format;
+ f->samples = 0;
+ f->offset = 0;
+ f->src = "Console";
+ f->delivery.tv_sec = 0;
+ f->delivery.tv_usec = 0;
+ f->seqno = 0;
+ AST_LIST_NEXT(f, frame_list) = NULL;
+
+ if (prev)
+ AST_LIST_NEXT(prev, frame_list) = f;
+
+ return f;
+}
+
+
+/*
+ * Append a chunk of data to a buffer taking care of bit alignment
+ * Return 0 on success, != 0 on failure
+ */
+static int fbuf_append(struct fbuf_t *b, uint8_t *src, int len,
+ int sbit, int ebit)
+{
+ /*
+ * Allocate buffer. ffmpeg wants an extra FF_INPUT_BUFFER_PADDING_SIZE,
+ * and also wants 0 as a buffer terminator to prevent trouble.
+ */
+ int need = len + FF_INPUT_BUFFER_PADDING_SIZE;
+ int i;
+ uint8_t *dst, mask;
+
+ if (b->data == NULL) {
+ b->size = need;
+ b->used = 0;
+ b->ebit = 0;
+ b->data = ast_calloc(1, b->size);
+ } else if (b->used + need > b->size) {
+ b->size = b->used + need;
+ b->data = ast_realloc(b->data, b->size);
+ }
+ if (b->data == NULL) {
+ ast_log(LOG_WARNING, "alloc failure for %d, discard\n",
+ b->size);
+ return 1;
+ }
+ if (b->used == 0 && b->ebit != 0) {
+ ast_log(LOG_WARNING, "ebit not reset at start\n");
+ b->ebit = 0;
+ }
+ dst = b->data + b->used;
+ i = b->ebit + sbit; /* bits to ignore around */
+ if (i == 0) { /* easy case, just append */
+ /* do everything in the common block */
+ } else if (i == 8) { /* easy too, just handle the overlap byte */
+ mask = (1 << b->ebit) - 1;
+ /* update the last byte in the buffer */
+ dst[-1] &= ~mask; /* clear bits to ignore */
+ dst[-1] |= (*src & mask); /* append new bits */
+ src += 1; /* skip and prepare for common block */
+ len --;
+ } else { /* must shift the new block, not done yet */
+ ast_log(LOG_WARNING, "must handle shift %d %d at %d\n",
+ b->ebit, sbit, b->used);
+ return 1;
+ }
+ memcpy(dst, src, len);
+ b->used += len;
+ b->ebit = ebit;
+ b->data[b->used] = 0; /* padding */
+ return 0;
+}
+
+/*
+ * Here starts the glue code for the various supported video codecs.
+ * For each of them, we need to provide routines for initialization,
+ * calling the encoder, encapsulating the bitstream in ast_frames,
+ * extracting payload from ast_frames, and calling the decoder.
+ */
+
+/*--- h263+ support --- */
+
+/*! \brief initialization of h263p */
+static int h263p_enc_init(AVCodecContext *enc_ctx)
+{
+ /* modes supported are
+ - Unrestricted Motion Vector (annex D)
+ - Advanced Prediction (annex F)
+ - Advanced Intra Coding (annex I)
+ - Deblocking Filter (annex J)
+ - Slice Structure (annex K)
+ - Alternative Inter VLC (annex S)
+ - Modified Quantization (annex T)
+ */
+ enc_ctx->flags |=CODEC_FLAG_H263P_UMV; /* annex D */
+ enc_ctx->flags |=CODEC_FLAG_AC_PRED; /* annex f ? */
+ enc_ctx->flags |=CODEC_FLAG_H263P_SLICE_STRUCT; /* annex k */
+ enc_ctx->flags |= CODEC_FLAG_H263P_AIC; /* annex I */
+
+ return 0;
+}
+
+
+/*
+ * Create RTP/H.263 fragments to avoid IP fragmentation. We fragment on a
+ * PSC or a GBSC, but if we don't find a suitable place just break somewhere.
+ * Everything is byte-aligned.
+ */
+static struct ast_frame *h263p_encap(struct fbuf_t *b, int mtu,
+ struct ast_frame **tail)
+{
+ struct ast_frame *cur = NULL, *first = NULL;
+ uint8_t *d = b->data;
+ int len = b->used;
+ int l = len; /* size of the current fragment. If 0, must look for a psc */
+
+ for (;len > 0; len -= l, d += l) {
+ uint8_t *data;
+ struct ast_frame *f;
+ int i, h;
+
+ if (len >= 3 && d[0] == 0 && d[1] == 0 && d[2] >= 0x80) {
+ /* we are starting a new block, so look for a PSC. */
+ for (i = 3; i < len - 3; i++) {
+ if (d[i] == 0 && d[i+1] == 0 && d[i+2] >= 0x80) {
+ l = i;
+ break;
+ }
+ }
+ }
+ if (l > mtu || l > len) { /* psc not found, split */
+ l = MIN(len, mtu);
+ }
+ if (l < 1 || l > mtu) {
+ ast_log(LOG_WARNING, "--- frame error l %d\n", l);
+ break;
+ }
+
+ if (d[0] == 0 && d[1] == 0) { /* we start with a psc */
+ h = 0;
+ } else { /* no psc, create a header */
+ h = 2;
+ }
+
+ f = create_video_frame(d, d+l, AST_FORMAT_H263_PLUS, h, cur);
+ if (!f)
+ break;
+
+ data = f->data;
+ if (h == 0) { /* we start with a psc */
+ data[0] |= 0x04; // set P == 1, and we are done
+ } else { /* no psc, create a header */
+ data[0] = data[1] = 0; // P == 0
+ }
+
+ if (!cur)
+ first = f;
+ cur = f;
+ }
+
+ if (cur)
+ cur->subclass |= 1; // RTP Marker
+
+ *tail = cur; /* end of the list */
+ return first;
+}
+
+/*! \brief extract the bitstreem from the RTP payload.
+ * This is format dependent.
+ * For h263+, the format is defined in RFC 2429
+ * and basically has a fixed 2-byte header as follows:
+ * 5 bits RR reserved, shall be 0
+ * 1 bit P indicate a start/end condition,
+ * in which case the payload should be prepended
+ * by two zero-valued bytes.
+ * 1 bit V there is an additional VRC header after this header
+ * 6 bits PLEN length in bytes of extra picture header
+ * 3 bits PEBIT how many bits to be ignored in the last byte
+ *
+ * XXX the code below is not complete.
+ */
+static int h263p_decap(struct fbuf_t *b, uint8_t *data, int len)
+{
+ int PLEN;
+
+ if (len < 2) {
+ ast_log(LOG_WARNING, "invalid framesize %d\n", len);
+ return 1;
+ }
+ PLEN = ( (data[0] & 1) << 5 ) | ( (data[1] & 0xf8) >> 3);
+
+ if (PLEN > 0) {
+ data += PLEN;
+ len -= PLEN;
+ }
+ if (data[0] & 4) /* bit P */
+ data[0] = data[1] = 0;
+ else {
+ data += 2;
+ len -= 2;
+ }
+ return fbuf_append(b, data, len, 0, 0); /* ignore trail bits */
+}
+
+
+/*
+ * generic encoder, used by the various protocols supported here.
+ * We assume that the buffer is empty at the beginning.
+ */
+static int ffmpeg_encode(struct video_out_desc *v)
+{
+ struct fbuf_t *b = &v->enc_out;
+ int i;
+
+ b->used = avcodec_encode_video(v->enc_ctx, b->data, b->size, v->enc_in_frame);
+ i = avcodec_encode_video(v->enc_ctx, b->data + b->used, b->size - b->used, NULL); /* delayed frames ? */
+ if (i > 0) {
+ ast_log(LOG_WARNING, "have %d more bytes\n", i);
+ b->used += i;
+ }
+ return 0;
+}
+
+/*
+ * Generic decoder, which is used by h263p, h263 and h261 as it simply
+ * invokes ffmpeg's decoder.
+ * av_parser_parse should merge a randomly chopped up stream into
+ * proper frames. After that, if we have a valid frame, we decode it
+ * until the entire frame is processed.
+ */
+static int ffmpeg_decode(struct video_dec_desc *v, struct fbuf_t *b)
+{
+ uint8_t *src = b->data;
+ int srclen = b->used;
+ int full_frame = 0;
+
+ if (srclen == 0) /* no data */
+ return 0;
+ while (srclen) {
+ uint8_t *data;
+ int datalen, ret;
+ int len = av_parser_parse(v->parser, v->dec_ctx, &data, &datalen, src, srclen, 0, 0);
+
+ src += len;
+ srclen -= len;
+ /* The parser might return something it cannot decode, so it skips
+ * the block returning no data
+ */
+ if (data == NULL || datalen == 0)
+ continue;
+ ret = avcodec_decode_video(v->dec_ctx, v->d_frame, &full_frame, data, datalen);
+ if (full_frame == 1) /* full frame */
+ break;
+ if (ret < 0) {
+ ast_log(LOG_NOTICE, "Error decoding\n");
+ break;
+ }
+ }
+ if (srclen != 0) /* update b with leftover data */
+ bcopy(src, b->data, srclen);
+ b->used = srclen;
+ b->ebit = 0;
+ return full_frame;
+}
+
+static struct video_codec_desc h263p_codec = {
+ .name = "h263p",
+ .format = AST_FORMAT_H263_PLUS,
+ .enc_init = h263p_enc_init,
+ .enc_encap = h263p_encap,
+ .enc_run = ffmpeg_encode,
+ .dec_init = NULL,
+ .dec_decap = h263p_decap,
+ .dec_run = ffmpeg_decode
+};
+
+/*--- Plain h263 support --------*/
+
+static int h263_enc_init(AVCodecContext *enc_ctx)
+{
+ /* XXX check whether these are supported */
+ enc_ctx->flags |= CODEC_FLAG_H263P_UMV;
+ enc_ctx->flags |= CODEC_FLAG_H263P_AIC;
+ enc_ctx->flags |= CODEC_FLAG_H263P_SLICE_STRUCT;
+ enc_ctx->flags |= CODEC_FLAG_AC_PRED;
+
+ return 0;
+}
+
+/*
+ * h263 encapsulation is specified in RFC2190. There are three modes
+ * defined (A, B, C), with 4, 8 and 12 bytes of header, respectively.
+ * The header is made as follows
+ * 0.....................|.......................|.............|....31
+ * F:1 P:1 SBIT:3 EBIT:3 SRC:3 I:1 U:1 S:1 A:1 R:4 DBQ:2 TRB:3 TR:8
+ * FP = 0- mode A, (only one word of header)
+ * FP = 10 mode B, and also means this is an I or P frame
+ * FP = 11 mode C, and also means this is a PB frame.
+ * SBIT, EBIT nuber of bits to ignore at beginning (msbits) and end (lsbits)
+ * SRC bits 6,7,8 from the h263 PTYPE field
+ * I = 0 intra-coded, 1 = inter-coded (bit 9 from PTYPE)
+ * U = 1 for Unrestricted Motion Vector (bit 10 from PTYPE)
+ * S = 1 for Syntax Based Arith coding (bit 11 from PTYPE)
+ * A = 1 for Advanced Prediction (bit 12 from PTYPE)
+ * R = reserved, must be 0
+ * DBQ = differential quantization, DBQUANT from h263, 0 unless we are using
+ * PB frames
+ * TRB = temporal reference for bframes, also 0 unless this is a PB frame
+ * TR = temporal reference for P frames, also 0 unless PB frame.
+ *
+ * Mode B and mode C description omitted.
+ *
+ * An RTP frame can start with a PSC 0000 0000 0000 0000 1000 0
+ * or with a GBSC, which also has the first 17 bits as a PSC.
+ * Note - PSC are byte-aligned, GOB not necessarily. PSC start with
+ * PSC:22 0000 0000 0000 0000 1000 00 picture start code
+ * TR:8 .... .... temporal reference
+ * PTYPE:13 or more ptype...
+ * If we don't fragment a GOB SBIT and EBIT = 0.
+ * reference, 8 bit)
+ *
+ * The assumption below is that we start with a PSC.
+ */
+static struct ast_frame *h263_encap(struct fbuf_t *b, int mtu,
+ struct ast_frame **tail)
+{
+ uint8_t *d = b->data;
+ int start = 0, i, len = b->used;
+ struct ast_frame *f, *cur = NULL, *first = NULL;
+ const int pheader_len = 4; /* Use RFC-2190 Mode A */
+ uint8_t h263_hdr[12]; /* worst case, room for a type c header */
+ uint8_t *h = h263_hdr; /* shorthand */
+
+#define H263_MIN_LEN 6
+ if (len < H263_MIN_LEN) /* unreasonably small */
+ return NULL;
+
+ bzero(h263_hdr, sizeof(h263_hdr));
+ /* Now set the header bytes. Only type A by now,
+ * and h[0] = h[2] = h[3] = 0 by default.
+ * PTYPE starts 30 bits in the picture, so the first useful
+ * bit for us is bit 36 i.e. within d[4] (0 is the msbit).
+ * SRC = d[4] & 0x1c goes into data[1] & 0xe0
+ * I = d[4] & 0x02 goes into data[1] & 0x10
+ * U = d[4] & 0x01 goes into data[1] & 0x08
+ * S = d[5] & 0x80 goes into data[1] & 0x04
+ * A = d[5] & 0x40 goes into data[1] & 0x02
+ * R = 0 goes into data[1] & 0x01
+ * Optimizing it, we have
+ */
+ h[1] = ( (d[4] & 0x1f) << 3 ) | /* SRC, I, U */
+ ( (d[5] & 0xc0) >> 5 ); /* S, A, R */
+
+ /* now look for the next PSC or GOB header. First try to hit
+ * a '0' byte then look around for the 0000 0000 0000 0000 1 pattern
+ * which is both in the PSC and the GBSC.
+ */
+ for (i = H263_MIN_LEN, start = 0; start < len; start = i, i += 3) {
+ //ast_log(LOG_WARNING, "search at %d of %d/%d\n", i, start, len);
+ for (; i < len ; i++) {
+ uint8_t x, rpos, lpos;
+ int rpos_i; /* index corresponding to rpos */
+ if (d[i] != 0) /* cannot be in a GBSC */
+ continue;
+ if (i > len - 1)
+ break;
+ x = d[i+1];
+ if (x == 0) /* next is equally good */
+ continue;
+ /* see if around us we can make 16 '0' bits for the GBSC.
+ * Look for the first bit set on the right, and then
+ * see if we have enough 0 on the left.
+ * We are guaranteed to end before rpos == 0
+ */
+ for (rpos = 0x80, rpos_i = 8; rpos; rpos >>= 1, rpos_i--)
+ if (x & rpos) /* found the '1' bit in GBSC */
+ break;
+ x = d[i-1]; /* now look behind */
+ for (lpos = rpos; lpos ; lpos >>= 1)
+ if (x & lpos) /* too early, not a GBSC */
+ break;
+ if (lpos) /* as i said... */
+ continue;
+ /* now we have a GBSC starting somewhere in d[i-1],
+ * but it might be not byte-aligned
+ */
+ if (rpos == 0x80) { /* lucky case */
+ i = i - 1;
+ } else { /* XXX to be completed */
+ ast_log(LOG_WARNING, "unaligned GBSC 0x%x %d\n",
+ rpos, rpos_i);
+ }
+ break;
+ }
+ /* This frame is up to offset i (not inclusive).
+ * We do not split it yet even if larger than MTU.
+ */
+ f = create_video_frame(d + start, d+i, AST_FORMAT_H263,
+ pheader_len, cur);
+
+ if (!f)
+ break;
+ bcopy(h, f->data, 4); /* copy the h263 header */
+ /* XXX to do: if not aligned, fix sbit and ebit,
+ * then move i back by 1 for the next frame
+ */
+ if (!cur)
+ first = f;
+ cur = f;
+ }
+
+ if (cur)
+ cur->subclass |= 1; // RTP Marker
+
+ *tail = cur;
+ return first;
+}
+
+/* XXX We only drop the header here, but maybe we need more. */
+static int h263_decap(struct fbuf_t *b, uint8_t *data, int len)
+{
+ if (len < 4) {
+ ast_log(LOG_WARNING, "invalid framesize %d\n", len);
+ return 1; /* error */
+ }
+
+ if ( (data[0] & 0x80) == 0) {
+ len -= 4;
+ data += 4;
+ } else {
+ ast_log(LOG_WARNING, "unsupported mode 0x%x\n",
+ data[0]);
+ return 1;
+ }
+ return fbuf_append(b, data, len, 0, 0); /* XXX no bit alignment support yet */
+}
+
+static struct video_codec_desc h263_codec = {
+ .name = "h263",
+ .format = AST_FORMAT_H263,
+ .enc_init = h263_enc_init,
+ .enc_encap = h263_encap,
+ .enc_run = ffmpeg_encode,
+ .dec_init = NULL,
+ .dec_decap = h263_decap,
+ .dec_run = ffmpeg_decode
+
+};
+
+/*---- h261 support -----*/
+static int h261_enc_init(AVCodecContext *enc_ctx)
+{
+ /* It is important to set rtp_payload_size = 0, otherwise
+ * ffmpeg in h261 mode will produce output that it cannot parse.
+ * Also try to send I frames more frequently than with other codecs.
+ */
+ enc_ctx->rtp_payload_size = 0; /* important - ffmpeg fails otherwise */
+
+ return 0;
+}
+
+/*
+ * The encapsulation of H261 is defined in RFC4587 which obsoletes RFC2032
+ * The bitstream is preceded by a 32-bit header word:
+ * SBIT:3 EBIT:3 I:1 V:1 GOBN:4 MBAP:5 QUANT:5 HMVD:5 VMVD:5
+ * SBIT and EBIT are the bits to be ignored at beginning and end,
+ * I=1 if the stream has only INTRA frames - cannot change during the stream.
+ * V=0 if motion vector is not used. Cannot change.
+ * GOBN is the GOB number in effect at the start of packet, 0 if we
+ * start with a GOB header
+ * QUANT is the quantizer in effect, 0 if we start with GOB header
+ * HMVD reference horizontal motion vector. 10000 is forbidden
+ * VMVD reference vertical motion vector, as above.
+ * Packetization should occur at GOB boundaries, and if not possible
+ * with MacroBlock fragmentation. However it is likely that blocks
+ * are not bit-aligned so we must take care of this.
+ */
+static struct ast_frame *h261_encap(struct fbuf_t *b, int mtu,
+ struct ast_frame **tail)
+{
+ uint8_t *d = b->data;
+ int start = 0, i, len = b->used;
+ struct ast_frame *f, *cur = NULL, *first = NULL;
+ const int pheader_len = 4;
+ uint8_t h261_hdr[4];
+ uint8_t *h = h261_hdr; /* shorthand */
+ int sbit = 0, ebit = 0;
+
+#define H261_MIN_LEN 10
+ if (len < H261_MIN_LEN) /* unreasonably small */
+ return NULL;
+
+ bzero(h261_hdr, sizeof(h261_hdr));
+
+ /* Similar to the code in h263_encap, but the marker there is longer.
+ * Start a few bytes within the bitstream to avoid hitting the marker
+ * twice. Note we might access the buffer at len, but this is ok because
+ * the caller has it oversized.
+ */
+ for (i = H261_MIN_LEN, start = 0; start < len - 1; start = i, i += 4) {
+#if 0 /* test - disable packetization */
+ i = len; /* wrong... */
+#else
+ int found = 0, found_ebit = 0; /* last GBSC position found */
+ for (; i < len ; i++) {
+ uint8_t x, rpos, lpos;
+ if (d[i] != 0) /* cannot be in a GBSC */
+ continue;
+ x = d[i+1];
+ if (x == 0) /* next is equally good */
+ continue;
+ /* See if around us we find 15 '0' bits for the GBSC.
+ * Look for the first bit set on the right, and then
+ * see if we have enough 0 on the left.
+ * We are guaranteed to end before rpos == 0
+ */
+ for (rpos = 0x80, ebit = 7; rpos; ebit--, rpos >>= 1)
+ if (x & rpos) /* found the '1' bit in GBSC */
+ break;
+ x = d[i-1]; /* now look behind */
+ for (lpos = (rpos >> 1); lpos ; lpos >>= 1)
+ if (x & lpos) /* too early, not a GBSC */
+ break;
+ if (lpos) /* as i said... */
+ continue;
+ /* now we have a GBSC starting somewhere in d[i-1],
+ * but it might be not byte-aligned. Just remember it.
+ */
+ if (i - start > mtu) /* too large, stop now */
+ break;
+ found_ebit = ebit;
+ found = i;
+ i += 4; /* continue forward */
+ }
+ if (i >= len) { /* trim if we went too forward */
+ i = len;
+ ebit = 0; /* hopefully... should ask the bitstream ? */
+ }
+ if (i - start > mtu && found) {
+ /* use the previous GBSC, hope is within the mtu */
+ i = found;
+ ebit = found_ebit;
+ }
+#endif /* test */
+ if (i - start < 4) /* XXX too short ? */
+ continue;
+ /* This frame is up to offset i (not inclusive).
+ * We do not split it yet even if larger than MTU.
+ */
+ f = create_video_frame(d + start, d+i, AST_FORMAT_H261,
+ pheader_len, cur);
+
+ if (!f)
+ break;
+ /* recompute header with I=0, V=1 */
+ h[0] = ( (sbit & 7) << 5 ) | ( (ebit & 7) << 2 ) | 1;
+ bcopy(h, f->data, 4); /* copy the h261 header */
+ if (ebit) /* not aligned, restart from previous byte */
+ i--;
+ sbit = (8 - ebit) & 7;
+ ebit = 0;
+ if (!cur)
+ first = f;
+ cur = f;
+ }
+ if (cur)
+ cur->subclass |= 1; // RTP Marker
+
+ *tail = cur;
+ return first;
+}
+
+/*
+ * Pieces might be unaligned so we really need to put them together.
+ */
+static int h261_decap(struct fbuf_t *b, uint8_t *data, int len)
+{
+ int ebit, sbit;
+
+ if (len < 8) {
+ ast_log(LOG_WARNING, "invalid framesize %d\n", len);
+ return 1;
+ }
+ sbit = (data[0] >> 5) & 7;
+ ebit = (data[0] >> 2) & 7;
+ len -= 4;
+ data += 4;
+ return fbuf_append(b, data, len, sbit, ebit);
+}
+
+static struct video_codec_desc h261_codec = {
+ .name = "h261",
+ .format = AST_FORMAT_H261,
+ .enc_init = h261_enc_init,
+ .enc_encap = h261_encap,
+ .enc_run = ffmpeg_encode,
+ .dec_init = NULL,
+ .dec_decap = h261_decap,
+ .dec_run = ffmpeg_decode
+};
+
+/* mpeg4 support */
+static int mpeg4_enc_init(AVCodecContext *enc_ctx)
+{
+#if 0
+ //enc_ctx->flags |= CODEC_FLAG_LOW_DELAY; /*don't use b frames ?*/
+ enc_ctx->flags |= CODEC_FLAG_AC_PRED;
+ enc_ctx->flags |= CODEC_FLAG_H263P_UMV;
+ enc_ctx->flags |= CODEC_FLAG_QPEL;
+ enc_ctx->flags |= CODEC_FLAG_4MV;
+ enc_ctx->flags |= CODEC_FLAG_GMC;
+ enc_ctx->flags |= CODEC_FLAG_LOOP_FILTER;
+ enc_ctx->flags |= CODEC_FLAG_H263P_SLICE_STRUCT;
+#endif
+ enc_ctx->rtp_payload_size = 0; /* important - ffmpeg fails otherwise */
+ return 0;
+}
+
+/* simplistic encapsulation - just split frames in mtu-size units */
+static struct ast_frame *mpeg4_encap(struct fbuf_t *b, int mtu,
+ struct ast_frame **tail)
+{
+ struct ast_frame *f, *cur = NULL, *first = NULL;
+ uint8_t *d = b->data;
+ uint8_t *end = d + b->used;
+ int len;
+
+ for (;d < end; d += len, cur = f) {
+ len = MIN(mtu, end - d);
+ f = create_video_frame(d, d + len, AST_FORMAT_MP4_VIDEO, 0, cur);
+ if (!f)
+ break;
+ if (!first)
+ first = f;
+ }
+ if (cur)
+ cur->subclass |= 1;
+ *tail = cur;
+ return first;
+}
+
+static int mpeg4_decap(struct fbuf_t *b, uint8_t *data, int len)
+{
+ return fbuf_append(b, data, len, 0, 0);
+}
+
+static int mpeg4_decode(struct video_dec_desc *v, struct fbuf_t *b)
+{
+ int full_frame = 0, datalen = b->used;
+ int ret = avcodec_decode_video(v->dec_ctx, v->d_frame, &full_frame,
+ b->data, datalen);
+ if (ret < 0) {
+ ast_log(LOG_NOTICE, "Error decoding\n");
+ ret = datalen; /* assume we used everything. */
+ }
+ datalen -= ret;
+ if (datalen > 0) /* update b with leftover bytes */
+ bcopy(b->data + ret, b->data, datalen);
+ b->used = datalen;
+ b->ebit = 0;
+ return full_frame;
+}
+
+static struct video_codec_desc mpeg4_codec = {
+ .name = "mpeg4",
+ .format = AST_FORMAT_MP4_VIDEO,
+ .enc_init = mpeg4_enc_init,
+ .enc_encap = mpeg4_encap,
+ .enc_run = ffmpeg_encode,
+ .dec_init = NULL,
+ .dec_decap = mpeg4_decap,
+ .dec_run = mpeg4_decode
+};
+
+static int h264_enc_init(AVCodecContext *enc_ctx)
+{
+ enc_ctx->flags |= CODEC_FLAG_TRUNCATED;
+ //enc_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;
+ //enc_ctx->flags2 |= CODEC_FLAG2_FASTPSKIP;
+ /* TODO: Maybe we need to add some other flags */
+ enc_ctx->rtp_mode = 0;
+ enc_ctx->rtp_payload_size = 0;
+ enc_ctx->bit_rate_tolerance = enc_ctx->bit_rate;
+ return 0;
+}
+
+static int h264_dec_init(AVCodecContext *dec_ctx)
+{
+ dec_ctx->flags |= CODEC_FLAG_TRUNCATED;
+
+ return 0;
+}
+
+/*
+ * The structure of a generic H.264 stream is:
+ * - 0..n 0-byte(s), unused, optional. one zero-byte is always present
+ * in the first NAL before the start code prefix.
+ * - start code prefix (3 bytes): 0x000001
+ * (the first bytestream has a
+ * like these 0x00000001!)
+ * - NAL header byte ( F[1] | NRI[2] | Type[5] ) where type != 0
+ * - byte-stream
+ * - 0..n 0-byte(s) (padding, unused).
+ * Segmentation in RTP only needs to be done on start code prefixes.
+ * If fragments are too long... we don't support it yet.
+ * - encapsulate (or fragment) the byte-stream (with NAL header included)
+ */
+static struct ast_frame *h264_encap(struct fbuf_t *b, int mtu,
+ struct ast_frame **tail)
+{
+ struct ast_frame *f = NULL, *cur = NULL, *first = NULL;
+ uint8_t *d, *start = b->data;
+ uint8_t *end = start + b->used;
+
+ /* Search the first start code prefix - ITU-T H.264 sec. B.2,
+ * and move start right after that, on the NAL header byte.
+ */
+#define HAVE_NAL(x) (x[-4] == 0 && x[-3] == 0 && x[-2] == 0 && x[-1] == 1)
+ for (start += 4; start < end; start++) {
+ int ty = start[0] & 0x1f;
+ if (HAVE_NAL(start) && ty != 0 && ty != 31)
+ break;
+ }
+ /* if not found, or too short, we just skip the next loop and are done. */
+
+ /* Here follows the main loop to create frames. Search subsequent start
+ * codes, and then possibly fragment the unit into smaller fragments.
+ */
+ for (;start < end - 4; start = d) {
+ int size; /* size of current block */
+ uint8_t hdr[2]; /* add-on header when fragmenting */
+ int ty = 0;
+
+ /* now search next nal */
+ for (d = start + 4; d < end; d++) {
+ ty = d[0] & 0x1f;
+ if (HAVE_NAL(d))
+ break; /* found NAL */
+ }
+ /* have a block to send. d past the start code unless we overflow */
+ if (d >= end) { /* NAL not found */
+ d = end + 4;
+ } else if (ty == 0 || ty == 31) { /* found but invalid type, skip */
+ ast_log(LOG_WARNING, "skip invalid nal type %d at %d of %d\n",
+ ty, d - (uint8_t *)b->data, b->used);
+ continue;
+ }
+
+ size = d - start - 4; /* don't count the end */
+
+ if (size < mtu) { // test - don't fragment
+ // Single NAL Unit
+ f = create_video_frame(start, d - 4, AST_FORMAT_H264, 0, cur);
+ if (!f)
+ break;
+ if (!first)
+ first = f;
+
+ cur = f;
+ continue;
+ }
+
+ // Fragmented Unit (Mode A: no DON, very weak)
+ hdr[0] = (*start & 0xe0) | 28; /* mark as a fragmentation unit */
+ hdr[1] = (*start++ & 0x1f) | 0x80 ; /* keep type and set START bit */
+ size--; /* skip the NAL header */
+ while (size) {
+ uint8_t *data;
+ int frag_size = MIN(size, mtu);
+
+ f = create_video_frame(start, start+frag_size, AST_FORMAT_H264, 2, cur);
+ if (!f)
+ break;
+ size -= frag_size; /* skip this data block */
+ start += frag_size;
+
+ data = f->data;
+ data[0] = hdr[0];
+ data[1] = hdr[1] | (size == 0 ? 0x40 : 0); /* end bit if we are done */
+ hdr[1] &= ~0x80; /* clear start bit for subsequent frames */
+ if (!first)
+ first = f;
+ cur = f;
+ }
+ }
+
+ if (cur)
+ cur->subclass |= 1; // RTP Marker
+
+ *tail = cur;
+
+ return first;
+}
+
+static int h264_decap(struct fbuf_t *b, uint8_t *data, int len)
+{
+ /* Start Code Prefix (Annex B in specification) */
+ uint8_t scp[] = { 0x00, 0x00, 0x00, 0x01 };
+ int retval = 0;
+ int type, ofs = 0;
+
+ if (len < 2) {
+ ast_log(LOG_WARNING, "--- invalid len %d\n", len);
+ return 1;
+ }
+ /* first of all, check if the packet has F == 0 */
+ if (data[0] & 0x80) {
+ ast_log(LOG_WARNING, "--- forbidden packet; nal: %02x\n",
+ data[0]);
+ return 1;
+ }
+
+ type = data[0] & 0x1f;
+ switch (type) {
+ case 0:
+ case 31:
+ ast_log(LOG_WARNING, "--- invalid type: %d\n", type);
+ return 1;
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ case 29:
+ ast_log(LOG_WARNING, "--- encapsulation not supported : %d\n", type);
+ return 1;
+ case 28: /* FU-A Unit */
+ if (data[1] & 0x80) { // S == 1, import F and NRI from next
+ data[1] &= 0x1f; /* preserve type */
+ data[1] |= (data[0] & 0xe0); /* import F & NRI */
+ retval = fbuf_append(b, scp, sizeof(scp), 0, 0);
+ ofs = 1;
+ } else {
+ ofs = 2;
+ }
+ break;
+ default: /* From 1 to 23 (Single NAL Unit) */
+ retval = fbuf_append(b, scp, sizeof(scp), 0, 0);
+ }
+ if (!retval)
+ retval = fbuf_append(b, data + ofs, len - ofs, 0, 0);
+ if (retval)
+ ast_log(LOG_WARNING, "result %d\n", retval);
+ return retval;
+}
+
+static struct video_codec_desc h264_codec = {
+ .name = "h264",
+ .format = AST_FORMAT_H264,
+ .enc_init = h264_enc_init,
+ .enc_encap = h264_encap,
+ .enc_run = ffmpeg_encode,
+ .dec_init = h264_dec_init,
+ .dec_decap = h264_decap,
+ .dec_run = ffmpeg_decode
+};
+
+/*
+ * Table of translation between asterisk and ffmpeg formats.
+ * We need also a field for read and write (encoding and decoding), because
+ * e.g. H263+ uses different codec IDs in ffmpeg when encoding or decoding.
+ */
+struct _cm { /* map ffmpeg codec types to asterisk formats */
+ uint32_t ast_format; /* 0 is a terminator */
+ enum CodecID codec;
+ enum { CM_RD = 1, CM_WR = 2, CM_RDWR = 3 } rw; /* read or write or both ? */
+ //struct video_codec_desc *codec_desc;
+};
+
+static struct _cm video_formats[] = {
+ { AST_FORMAT_H263_PLUS, CODEC_ID_H263, CM_RD }, /* incoming H263P ? */
+ { AST_FORMAT_H263_PLUS, CODEC_ID_H263P, CM_WR },
+ { AST_FORMAT_H263, CODEC_ID_H263, CM_RD },
+ { AST_FORMAT_H263, CODEC_ID_H263, CM_WR },
+ { AST_FORMAT_H261, CODEC_ID_H261, CM_RDWR },
+ { AST_FORMAT_H264, CODEC_ID_H264, CM_RDWR },
+ { AST_FORMAT_MP4_VIDEO, CODEC_ID_MPEG4, CM_RDWR },
+ { 0, 0, 0 },
+};
+
+
+/*! \brief map an asterisk format into an ffmpeg one */
+static enum CodecID map_video_format(uint32_t ast_format, int rw)
+{
+ struct _cm *i;
+
+ for (i = video_formats; i->ast_format != 0; i++)
+ if (ast_format & i->ast_format && rw & i->rw && rw & i->rw)
+ return i->codec;
+ return CODEC_ID_NONE;
+}
+
+/* pointers to supported codecs. We assume the first one to be non null. */
+static struct video_codec_desc *supported_codecs[] = {
+ &h263p_codec,
+ &h264_codec,
+ &h263_codec,
+ &h261_codec,
+ &mpeg4_codec,
+ NULL
+};
+
+/*
+ * Map the AST_FORMAT to the library. If not recognised, fail.
+ * This is useful in the input path where we get frames.
+ */
+static struct video_codec_desc *map_video_codec(int fmt)
+{
+ int i;
+
+ for (i = 0; supported_codecs[i]; i++)
+ if (fmt == supported_codecs[i]->format) {
+ ast_log(LOG_WARNING, "using %s for format 0x%x\n",
+ supported_codecs[i]->name, fmt);
+ return supported_codecs[i];
+ }
+ return NULL;
+}
+
+/*! \brief uninitialize the descriptor for remote video stream */
+static struct video_dec_desc *dec_uninit(struct video_dec_desc *v)
+{
+ int i;
+
+ if (v == NULL) /* not initialized yet */
+ return NULL;
+ if (v->parser) {
+ av_parser_close(v->parser);
+ v->parser = NULL;
+ }
+ if (v->dec_ctx) {
+ avcodec_close(v->dec_ctx);
+ av_free(v->dec_ctx);
+ v->dec_ctx = NULL;
+ }
+ if (v->d_frame) {
+ av_free(v->d_frame);
+ v->d_frame = NULL;
+ }
+ v->codec = NULL; /* only a reference */
+ v->d_callbacks = NULL; /* forget the decoder */
+ v->discard = 1; /* start in discard mode */
+ for (i = 0; i < N_DEC_IN; i++)
+ fbuf_free(&v->dec_in[i]);
+ fbuf_free(&v->dec_out);
+ ast_free(v);
+ return NULL; /* error, in case someone cares */
+}
+
+/*
+ * initialize ffmpeg resources used for decoding frames from the network.
+ */
+static struct video_dec_desc *dec_init(uint32_t the_ast_format)
+{
+ enum CodecID codec;
+ struct video_dec_desc *v = ast_calloc(1, sizeof(*v));
+ if (v == NULL)
+ return NULL;
+
+ v->discard = 1;
+
+ v->d_callbacks = map_video_codec(the_ast_format);
+ if (v->d_callbacks == NULL) {
+ ast_log(LOG_WARNING, "cannot find video codec, drop input 0x%x\n", the_ast_format);
+ return dec_uninit(v);
+ }
+
+ codec = map_video_format(v->d_callbacks->format, CM_RD);
+
+ v->codec = avcodec_find_decoder(codec);
+ if (!v->codec) {
+ ast_log(LOG_WARNING, "Unable to find the decoder for format %d\n", codec);
+ return dec_uninit(v);
+ }
+ /*
+ * Initialize the codec context.
+ */
+ v->dec_ctx = avcodec_alloc_context();
+ if (!v->dec_ctx) {
+ ast_log(LOG_WARNING, "Cannot allocate the decoder context\n");
+ return dec_uninit(v);
+ }
+ /* XXX call dec_init() ? */
+ if (avcodec_open(v->dec_ctx, v->codec) < 0) {
+ ast_log(LOG_WARNING, "Cannot open the decoder context\n");
+ av_free(v->dec_ctx);
+ v->dec_ctx = NULL;
+ return dec_uninit(v);
+ }
+
+ v->parser = av_parser_init(codec);
+ if (!v->parser) {
+ ast_log(LOG_WARNING, "Cannot initialize the decoder parser\n");
+ return dec_uninit(v);
+ }
+
+ v->d_frame = avcodec_alloc_frame();
+ if (!v->d_frame) {
+ ast_log(LOG_WARNING, "Cannot allocate decoding video frame\n");
+ return dec_uninit(v);
+ }
+ v->dec_in_cur = &v->dec_in[0]; /* buffer for incoming frames */
+ v->dec_in_dpy = NULL; /* nothing to display */
+
+ return v; /* ok */
+}
+/*------ end codec specific code -----*/
diff --git a/trunk/channels/vgrabbers.c b/trunk/channels/vgrabbers.c
new file mode 100644
index 000000000..0e4e62f56
--- /dev/null
+++ b/trunk/channels/vgrabbers.c
@@ -0,0 +1,346 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright 2007, Luigi Rizzo
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*
+ * Video grabbers used in console_video.
+ *
+ * $Revision$
+ *
+ * Each grabber is implemented through open/read/close calls,
+ * plus an additional move() function used e.g. to change origin
+ * for the X grabber (this may be extended in the future to support
+ * more controls e.g. resolution changes etc.).
+ *
+ * open() should try to open and initialize the grabber, returning NULL on error.
+ * On success it allocates a descriptor for its private data (including
+ * a buffer for the video) and returns a pointer to the descriptor.
+ * read() will return NULL on failure, or a pointer to a buffer with data
+ * on success.
+ * close() should release resources.
+ * move() is optional.
+ * For more details look at the X11 grabber below.
+ *
+ * NOTE: at the moment we expect uncompressed video frames in YUV format,
+ * because this is what current sources supply and also is a convenient
+ * format for display. It is conceivable that one might want to support
+ * an already compressed stream, in which case we should redesign the
+ * pipeline used for the local source, which at the moment is
+ *
+ * .->--[loc_dpy]
+ * [src]-->--[enc_in]--+
+ * `->--[enc_out]
+ */
+
+#include "asterisk.h"
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+#include <sys/ioctl.h>
+#include "asterisk/file.h"
+#include "asterisk/utils.h" /* ast_calloc */
+
+#include "console_video.h"
+
+#if defined(HAVE_VIDEO_CONSOLE)
+
+#ifdef HAVE_X11
+
+/* A simple X11 grabber, supporting only truecolor formats */
+
+#include <X11/Xlib.h>
+
+/*! \brief internal info used by the X11 grabber */
+struct grab_x11_desc {
+ Display *dpy;
+ XImage *image;
+ int screen_width; /* width of X screen */
+ int screen_height; /* height of X screen */
+ struct fbuf_t b; /* geometry and pointer into the XImage */
+};
+
+/*! \brief open the grabber.
+ * We use the special name 'X11' to indicate this grabber.
+ */
+static void *grab_x11_open(const char *name, struct fbuf_t *geom, int fps)
+{
+ XImage *im;
+ int screen_num;
+ struct grab_x11_desc *v;
+ struct fbuf_t *b;
+
+ if (strcasecmp(name, "X11"))
+ return NULL; /* not us */
+ v = ast_calloc(1, sizeof(*v));
+ if (v == NULL)
+ return NULL; /* no memory */
+
+ /* init the connection with the X server */
+ v->dpy = XOpenDisplay(NULL);
+ if (v->dpy == NULL) {
+ ast_log(LOG_WARNING, "error opening display\n");
+ goto error;
+ }
+
+ v->b = *geom; /* copy geometry */
+ b = &v->b; /* shorthand */
+ /* find width and height of the screen */
+ screen_num = DefaultScreen(v->dpy);
+ v->screen_width = DisplayWidth(v->dpy, screen_num);
+ v->screen_height = DisplayHeight(v->dpy, screen_num);
+
+ v->image = im = XGetImage(v->dpy,
+ RootWindow(v->dpy, DefaultScreen(v->dpy)),
+ b->x, b->y, b->w, b->h, AllPlanes, ZPixmap);
+ if (v->image == NULL) {
+ ast_log(LOG_WARNING, "error creating Ximage\n");
+ goto error;
+ }
+ switch (im->bits_per_pixel) {
+ case 32:
+ b->pix_fmt = PIX_FMT_RGBA32;
+ break;
+ case 16:
+ b->pix_fmt = (im->green_mask == 0x7e0) ? PIX_FMT_RGB565 : PIX_FMT_RGB555;
+ break;
+ }
+
+ ast_log(LOG_NOTICE, "image: data %p %d bpp fmt %d, mask 0x%lx 0x%lx 0x%lx\n",
+ im->data,
+ im->bits_per_pixel,
+ b->pix_fmt,
+ im->red_mask, im->green_mask, im->blue_mask);
+
+ /* set the pointer but not the size as this is not malloc'ed */
+ b->data = (uint8_t *)im->data;
+ return v;
+
+error:
+ /* XXX maybe XDestroy (v->image) ? */
+ if (v->dpy)
+ XCloseDisplay(v->dpy);
+ v->dpy = NULL;
+ ast_free(v);
+ return NULL;
+}
+
+static struct fbuf_t *grab_x11_read(void *desc)
+{
+ /* read frame from X11 */
+ struct grab_x11_desc *v = desc;
+ struct fbuf_t *b = &v->b;
+
+ XGetSubImage(v->dpy,
+ RootWindow(v->dpy, DefaultScreen(v->dpy)),
+ b->x, b->y, b->w, b->h, AllPlanes, ZPixmap, v->image, 0, 0);
+
+ b->data = (uint8_t *)v->image->data;
+ return b;
+}
+
+static int boundary_checks(int x, int limit)
+{
+ return (x <= 0) ? 0 : (x > limit ? limit : x);
+}
+
+/*! \brief move the origin for the grabbed area, making sure we do not
+ * overflow the screen.
+ */
+static void grab_x11_move(void *desc, int dx, int dy)
+{
+ struct grab_x11_desc *v = desc;
+
+ v->b.x = boundary_checks(v->b.x + dx, v->screen_width - v->b.w);
+ v->b.y = boundary_checks(v->b.y + dy, v->screen_height - v->b.h);
+}
+
+/*! \brief disconnect from the server and release memory */
+static void *grab_x11_close(void *desc)
+{
+ struct grab_x11_desc *v = desc;
+
+ XCloseDisplay(v->dpy);
+ v->dpy = NULL;
+ v->image = NULL;
+ ast_free(v);
+ return NULL;
+}
+
+static struct grab_desc grab_x11_desc = {
+ .name = "X11",
+ .open = grab_x11_open,
+ .read = grab_x11_read,
+ .move = grab_x11_move,
+ .close = grab_x11_close,
+};
+#endif /* HAVE_X11 */
+
+#ifdef HAVE_VIDEODEV_H
+#include <linux/videodev.h> /* Video4Linux stuff is only used in grab_v4l1_open() */
+
+struct grab_v4l1_desc {
+ int fd; /* device handle */
+ struct fbuf_t b; /* buffer (allocated) with grabbed image */
+};
+
+/*! \brief
+ * Open the local video source and allocate a buffer
+ * for storing the image.
+ */
+static void *grab_v4l1_open(const char *dev, struct fbuf_t *geom, int fps)
+{
+ struct video_window vw = { 0 }; /* camera attributes */
+ struct video_picture vp;
+ int fd, i;
+ struct grab_v4l1_desc *v;
+ struct fbuf_t *b;
+
+ fd = open(dev, O_RDONLY | O_NONBLOCK);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "error opening camera %s\n", dev);
+ return NULL;
+ }
+
+ v = ast_calloc(1, sizeof(*v));
+ if (v == NULL) {
+ ast_log(LOG_WARNING, "no memory for camera %s\n", dev);
+ close(fd);
+ return NULL; /* no memory */
+ }
+ v->fd = fd;
+ v->b = *geom;
+ b = &v->b; /* shorthand */
+
+ i = fcntl(fd, F_GETFL);
+ if (-1 == fcntl(fd, F_SETFL, i | O_NONBLOCK)) {
+ /* non fatal, just emit a warning */
+ ast_log(LOG_WARNING, "error F_SETFL for %s [%s]\n",
+ dev, strerror(errno));
+ }
+ /* set format for the camera.
+ * In principle we could retry with a different format if the
+ * one we are asking for is not supported.
+ */
+ vw.width = b->w;
+ vw.height = b->h;
+ vw.flags = fps << 16;
+ if (ioctl(fd, VIDIOCSWIN, &vw) == -1) {
+ ast_log(LOG_WARNING, "error setting format for %s [%s]\n",
+ dev, strerror(errno));
+ goto error;
+ }
+ if (ioctl(fd, VIDIOCGPICT, &vp) == -1) {
+ ast_log(LOG_WARNING, "error reading picture info\n");
+ goto error;
+ }
+ ast_log(LOG_WARNING,
+ "contrast %d bright %d colour %d hue %d white %d palette %d\n",
+ vp.contrast, vp.brightness,
+ vp.colour, vp.hue,
+ vp.whiteness, vp.palette);
+ /* set the video format. Here again, we don't necessary have to
+ * fail if the required format is not supported, but try to use
+ * what the camera gives us.
+ */
+ b->pix_fmt = vp.palette;
+ vp.palette = VIDEO_PALETTE_YUV420P;
+ if (ioctl(v->fd, VIDIOCSPICT, &vp) == -1) {
+ ast_log(LOG_WARNING, "error setting palette, using %d\n",
+ b->pix_fmt);
+ } else
+ b->pix_fmt = vp.palette;
+ /* allocate the source buffer.
+ * XXX, the code here only handles yuv411, for other formats
+ * we need to look at pix_fmt and set size accordingly
+ */
+ b->size = (b->w * b->h * 3)/2; /* yuv411 */
+ ast_log(LOG_WARNING, "videodev %s opened, size %dx%d %d\n",
+ dev, b->w, b->h, b->size);
+ b->data = ast_calloc(1, b->size);
+ if (!b->data) {
+ ast_log(LOG_WARNING, "error allocating buffer %d bytes\n",
+ b->size);
+ goto error;
+ }
+ ast_log(LOG_WARNING, "success opening camera\n");
+ return v;
+
+error:
+ close(v->fd);
+ fbuf_free(b);
+ ast_free(v);
+ return NULL;
+}
+
+/*! \brief read until error, no data or buffer full.
+ * This might be blocking but no big deal since we are in the
+ * display thread.
+ */
+static struct fbuf_t *grab_v4l1_read(void *desc)
+{
+ struct grab_v4l1_desc *v = desc;
+ struct fbuf_t *b = &v->b;
+ for (;;) {
+ int r, l = b->size - b->used;
+ r = read(v->fd, b->data + b->used, l);
+ // ast_log(LOG_WARNING, "read %d of %d bytes from webcam\n", r, l);
+ if (r < 0) /* read error */
+ break;
+ if (r == 0) /* no data */
+ break;
+ b->used += r;
+ if (r == l) {
+ b->used = 0; /* prepare for next frame */
+ return b;
+ }
+ }
+ return NULL;
+}
+
+static void *grab_v4l1_close(void *desc)
+{
+ struct grab_v4l1_desc *v = desc;
+
+ close(v->fd);
+ v->fd = -1;
+ fbuf_free(&v->b);
+ ast_free(v);
+ return NULL;
+}
+
+/*! \brief our descriptor. We don't have .move */
+static struct grab_desc grab_v4l1_desc = {
+ .name = "v4l1",
+ .open = grab_v4l1_open,
+ .read = grab_v4l1_read,
+ .close = grab_v4l1_close,
+};
+#endif /* HAVE_VIDEODEV_H */
+
+/*
+ * Here you can add more grabbers, e.g. V4L2, firewire,
+ * a file, a still image...
+ */
+
+/*! \brief The list of grabbers supported, with a NULL at the end */
+struct grab_desc *console_grabbers[] = {
+#ifdef HAVE_X11
+ &grab_x11_desc,
+#endif
+#ifdef HAVE_VIDEODEV_H
+ &grab_v4l1_desc,
+#endif
+ NULL
+};
+
+#endif /* HAVE_VIDEO_CONSOLE */
diff --git a/trunk/channels/xpmr/sinetabx.h b/trunk/channels/xpmr/sinetabx.h
new file mode 100755
index 000000000..4c4574850
--- /dev/null
+++ b/trunk/channels/xpmr/sinetabx.h
@@ -0,0 +1,290 @@
+/*
+ * sinetabx.h - for Xelatec Private Mobile Radio Processes
+ *
+ * All Rights Reserved. Copyright (C)2007, Xelatec, LLC
+ *
+ * 20070808 1235 Steven Henke, W9SH, sph@xelatec.com
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Private Land Mobile Radio Channel Voice and Signaling Processor
+ *
+ * \author Steven Henke, W9SH <sph@xelatec.com> Xelatec, LLC
+ */
+
+#ifndef XPMR_SINETABX_H
+#define XPMR_SINETABX_H 1
+
+#define SAMPLES_PER_SINE 256
+
+const i16 sinetablex[]={
+0, // 0
+804, // 1
+1608, // 2
+2410, // 3
+3212, // 4
+4011, // 5
+4808, // 6
+5602, // 7
+6393, // 8
+7179, // 9
+7962, // 10
+8739, // 11
+9512, // 12
+10278, // 13
+11039, // 14
+11793, // 15
+12539, // 16
+13279, // 17
+14010, // 18
+14732, // 19
+15446, // 20
+16151, // 21
+16846, // 22
+17530, // 23
+18204, // 24
+18868, // 25
+19519, // 26
+20159, // 27
+20787, // 28
+21403, // 29
+22005, // 30
+22594, // 31
+23170, // 32
+23731, // 33
+24279, // 34
+24811, // 35
+25329, // 36
+25832, // 37
+26319, // 38
+26790, // 39
+27245, // 40
+27683, // 41
+28105, // 42
+28510, // 43
+28898, // 44
+29268, // 45
+29621, // 46
+29956, // 47
+30273, // 48
+30571, // 49
+30852, // 50
+31113, // 51
+31356, // 52
+31580, // 53
+31785, // 54
+31971, // 55
+32137, // 56
+32285, // 57
+32412, // 58
+32521, // 59
+32609, // 60
+32678, // 61
+32728, // 62
+32757, // 63
+32767, // 64
+32757, // 65
+32728, // 66
+32678, // 67
+32609, // 68
+32521, // 69
+32412, // 70
+32285, // 71
+32137, // 72
+31971, // 73
+31785, // 74
+31580, // 75
+31356, // 76
+31113, // 77
+30852, // 78
+30571, // 79
+30273, // 80
+29956, // 81
+29621, // 82
+29268, // 83
+28898, // 84
+28510, // 85
+28105, // 86
+27683, // 87
+27245, // 88
+26790, // 89
+26319, // 90
+25832, // 91
+25329, // 92
+24811, // 93
+24279, // 94
+23731, // 95
+23170, // 96
+22594, // 97
+22005, // 98
+21403, // 99
+20787, // 100
+20159, // 101
+19519, // 102
+18868, // 103
+18204, // 104
+17530, // 105
+16846, // 106
+16151, // 107
+15446, // 108
+14732, // 109
+14010, // 110
+13279, // 111
+12539, // 112
+11793, // 113
+11039, // 114
+10278, // 115
+9512, // 116
+8739, // 117
+7962, // 118
+7179, // 119
+6393, // 120
+5602, // 121
+4808, // 122
+4011, // 123
+3212, // 124
+2410, // 125
+1608, // 126
+804, // 127
+0, // 128
+-804, // 129
+-1608, // 130
+-2410, // 131
+-3212, // 132
+-4011, // 133
+-4808, // 134
+-5602, // 135
+-6393, // 136
+-7179, // 137
+-7962, // 138
+-8739, // 139
+-9512, // 140
+-10278, // 141
+-11039, // 142
+-11793, // 143
+-12539, // 144
+-13279, // 145
+-14010, // 146
+-14732, // 147
+-15446, // 148
+-16151, // 149
+-16846, // 150
+-17530, // 151
+-18204, // 152
+-18868, // 153
+-19519, // 154
+-20159, // 155
+-20787, // 156
+-21403, // 157
+-22005, // 158
+-22594, // 159
+-23170, // 160
+-23731, // 161
+-24279, // 162
+-24811, // 163
+-25329, // 164
+-25832, // 165
+-26319, // 166
+-26790, // 167
+-27245, // 168
+-27683, // 169
+-28105, // 170
+-28510, // 171
+-28898, // 172
+-29268, // 173
+-29621, // 174
+-29956, // 175
+-30273, // 176
+-30571, // 177
+-30852, // 178
+-31113, // 179
+-31356, // 180
+-31580, // 181
+-31785, // 182
+-31971, // 183
+-32137, // 184
+-32285, // 185
+-32412, // 186
+-32521, // 187
+-32609, // 188
+-32678, // 189
+-32728, // 190
+-32757, // 191
+-32767, // 192
+-32757, // 193
+-32728, // 194
+-32678, // 195
+-32609, // 196
+-32521, // 197
+-32412, // 198
+-32285, // 199
+-32137, // 200
+-31971, // 201
+-31785, // 202
+-31580, // 203
+-31356, // 204
+-31113, // 205
+-30852, // 206
+-30571, // 207
+-30273, // 208
+-29956, // 209
+-29621, // 210
+-29268, // 211
+-28898, // 212
+-28510, // 213
+-28105, // 214
+-27683, // 215
+-27245, // 216
+-26790, // 217
+-26319, // 218
+-25832, // 219
+-25329, // 220
+-24811, // 221
+-24279, // 222
+-23731, // 223
+-23170, // 224
+-22594, // 225
+-22005, // 226
+-21403, // 227
+-20787, // 228
+-20159, // 229
+-19519, // 230
+-18868, // 231
+-18204, // 232
+-17530, // 233
+-16846, // 234
+-16151, // 235
+-15446, // 236
+-14732, // 237
+-14010, // 238
+-13279, // 239
+-12539, // 240
+-11793, // 241
+-11039, // 242
+-10278, // 243
+-9512, // 244
+-8739, // 245
+-7962, // 246
+-7179, // 247
+-6393, // 248
+-5602, // 249
+-4808, // 250
+-4011, // 251
+-3212, // 252
+-2410, // 253
+-1608, // 254
+-804, // 255
+};
+
+#endif /* !XPMR_SINETABX_H */
diff --git a/trunk/channels/xpmr/xpmr.c b/trunk/channels/xpmr/xpmr.c
new file mode 100755
index 000000000..a799ca9d3
--- /dev/null
+++ b/trunk/channels/xpmr/xpmr.c
@@ -0,0 +1,2256 @@
+/*
+ * xpmr.c - Xelatec Private Mobile Radio Processes
+ *
+ * All Rights Reserved. Copyright (C)2007, Xelatec, LLC
+ *
+ * 20070808 1235 Steven Henke, W9SH, sph@xelatec.com
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Private Land Mobile Radio Channel Voice and Signaling Processor
+ *
+ * \author Steven Henke, W9SH <sph@xelatec.com> Xelatec, LLC
+ */
+/*
+ FYI = For Your Information
+ PMR = Private Mobile Radio
+ RX = Receive
+ TX = Transmit
+ CTCSS = Continuous Tone Coded Squelch System
+ TONE = Same as above.
+ LSD = Low Speed Data, subaudible signaling. May be tones or codes.
+ VOX = Voice Operated Transmit
+ DSP = Digital Signal Processing
+ LPF = Low Pass Filter
+ FIR = Finite Impulse Response (Filter)
+ IIR = Infinite Impulse Response (Filter)
+*/
+#include <stdio.h>
+#include <ctype.h>
+#include <math.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "xpmr.h"
+#include "xpmr_coef.h"
+#include "sinetabx.h"
+
+static i16 pmrChanIndex=0; // count of created pmr instances
+
+/*
+ Convert a Frequency in Hz to a zero based CTCSS Table index
+*/
+i16 CtcssFreqIndex(float freq)
+{
+ i16 i,hit=-1;
+
+ for(i=0;i<CTCSS_NUM_CODES;i++){
+ if(freq==freq_ctcss[i])hit=i;
+ }
+ return hit;
+}
+/*
+ pmr_rx_frontend
+ Takes a block of data and low pass filters it.
+ Determines the amplitude of high frequency noise for carrier detect.
+ Decimates input data to change the rate.
+*/
+i16 pmr_rx_frontend(t_pmr_sps *mySps)
+{
+ #define DCgainBpfNoise 65536
+
+ i16 samples,iOutput, *input, *output, *noutput;
+ i16 *x, *coef, *coef2;
+ i32 i, naccum, outputGain, calcAdjust;
+ i64 y;
+ i16 nx, hyst, setpt, compOut;
+ i16 amax, amin, apeak, discounteru, discounterl, discfactor;
+ i16 decimator, decimate, doNoise;
+
+ TRACEX(("pmr_rx_frontend()\n"));
+
+ if(!mySps->enabled)return(1);
+
+ decimator = mySps->decimator;
+ decimate = mySps->decimate;
+
+ input = mySps->source;
+ output = mySps->sink;
+ noutput = mySps->parentChan->pRxNoise;
+
+ nx = mySps->nx;
+ coef = mySps->coef;
+ coef2 = mySps->coef2;
+
+ calcAdjust = mySps->calcAdjust;
+ outputGain = mySps->outputGain;
+
+ amax=mySps->amax;
+ amin=mySps->amin;
+ apeak=mySps->apeak;
+ discounteru=mySps->discounteru;
+ discounterl=mySps->discounterl;
+ discfactor=mySps->discfactor;
+ setpt=mySps->setpt;
+ hyst=mySps->hyst;
+ compOut=mySps->compOut;
+
+ samples=mySps->nSamples*decimate;
+ x=mySps->x;
+ iOutput=0;
+
+ if(mySps->parentChan->rxCdType!=CD_XPMR_VOX)doNoise=1;
+ else doNoise=0;
+
+ for(i=0;i<samples;i++)
+ {
+ i16 n;
+
+ //shift the old samples
+ for(n=nx-1; n>0; n--)
+ x[n] = x[n-1];
+
+ x[0] = input[i*2];
+
+ --decimator;
+
+ if(decimator<=0)
+ {
+ decimator=decimate;
+
+ y=0;
+ for(n=0; n<nx; n++)
+ y += coef[n] * x[n];
+
+ y=((y/calcAdjust)*outputGain)/M_Q8;
+
+ if(y>32767)y=32767;
+ else if(y<-32767)y=-32767;
+
+ output[iOutput]=y; // Rx Baseband decimated
+ noutput[iOutput++] = apeak; // Rx Noise
+ }
+
+ if(doNoise)
+ {
+ // calculate noise output
+ naccum=0;
+ for(n=0; n<nx; n++)
+ naccum += coef_fir_bpf_noise_1[n] * x[n];
+
+ naccum /= DCgainBpfNoise;
+
+ if(naccum>amax)
+ {
+ amax=naccum;
+ discounteru=discfactor;
+ }
+ else if(--discounteru<=0)
+ {
+ discounteru=discfactor;
+ amax=(i32)((amax*32700)/32768);
+ }
+
+ if(naccum<amin)
+ {
+ amin=naccum;
+ discounterl=discfactor;
+ }
+ else if(--discounterl<=0)
+ {
+ discounterl=discfactor;
+ amin=(i32)((amin*32700)/32768);
+ }
+
+ apeak=(amax-amin)/2;
+
+ } // if doNoise
+ }
+
+ if(doNoise)
+ {
+ ((t_pmr_chan *)(mySps->parentChan))->rxRssi=apeak;
+
+ if(apeak>setpt || (compOut&&(apeak>(setpt-hyst)))) compOut=1;
+ else compOut=0;
+ mySps->compOut=compOut;
+ mySps->amax=amax;
+ mySps->amin=amin;
+ mySps->apeak=apeak;
+ mySps->discounteru=discounteru;
+ mySps->discounterl=discounterl;
+ }
+
+ return 0;
+}
+/*
+ pmr general purpose fir
+ works on a block of samples
+*/
+i16 pmr_gp_fir(t_pmr_sps *mySps)
+{
+ i32 nsamples,inputGain,outputGain,calcAdjust;
+ i16 *input, *output;
+ i16 *x, *coef;
+ i32 i, ii;
+ i16 nx, hyst, setpt, compOut;
+ i16 amax, amin, apeak=0, discounteru=0, discounterl=0, discfactor;
+ i16 decimator, decimate, interpolate;
+ i16 numChanOut, selChanOut, mixOut, monoOut;
+
+ TRACEX(("pmr_gp_fir() %i\n",mySps->enabled));
+
+ if(!mySps->enabled)return(1);
+
+ inputGain = mySps->inputGain;
+ calcAdjust = mySps->calcAdjust;
+ outputGain = mySps->outputGain;
+
+ input = mySps->source;
+ output = mySps->sink;
+ x = mySps->x;
+ nx = mySps->nx;
+ coef = mySps->coef;
+
+ decimator = mySps->decimator;
+ decimate = mySps->decimate;
+ interpolate = mySps->interpolate;
+
+ setpt = mySps->setpt;
+ compOut = mySps->compOut;
+
+ inputGain = mySps->inputGain;
+ outputGain = mySps->outputGain;
+ numChanOut = mySps->numChanOut;
+ selChanOut = mySps->selChanOut;
+ mixOut = mySps->mixOut;
+ monoOut = mySps->monoOut;
+
+ amax=mySps->amax;
+ amin=mySps->amin;
+
+ discfactor=mySps->discfactor;
+ hyst=mySps->hyst;
+ setpt=mySps->setpt;
+ nsamples=mySps->nSamples;
+
+ if(mySps->option==3)
+ {
+ mySps->option=0;
+ mySps->enabled=0;
+ for(i=0;i<nsamples;i++)
+ {
+ if(monoOut)
+ output[(i*2)]=output[(i*2)+1]=0;
+ else
+ output[(i*numChanOut)+selChanOut]=0;
+ }
+ return 0;
+ }
+
+ ii=0;
+ for(i=0;i<nsamples;i++)
+ {
+ int ix;
+
+ int64_t y=0;
+
+ if(decimate<0)
+ {
+ decimator=decimate;
+ }
+
+ for(ix=0;ix<interpolate;ix++)
+ {
+ i16 n;
+ y=0;
+
+ for(n=nx-1; n>0; n--)
+ x[n] = x[n-1];
+ x[0] = (input[i]*inputGain)/M_Q8;
+
+ #if 0
+ --decimator;
+ if(decimator<=0)
+ {
+ decimator=decimate;
+ for(n=0; n<nx; n++)
+ y += coef[n] * x[n];
+ y /= (outputGain*3);
+ output[ii++]=y;
+ }
+ #else
+ for(n=0; n<nx; n++)
+ y += coef[n] * x[n];
+
+ y=((y/calcAdjust)*outputGain)/M_Q8;
+
+ if(mixOut){
+ if(monoOut){
+ output[(ii*2)]=output[(ii*2)+1]+=y;
+ }
+ else{
+ output[(ii*numChanOut)+selChanOut]+=y;
+ }
+ }
+ else{
+ if(monoOut){
+ output[(ii*2)]=output[(ii*2)+1]=y;
+ }
+ else{
+ output[(ii*numChanOut)+selChanOut]=y;
+ }
+ }
+ ii++;
+ #endif
+ }
+
+ // amplitude detector
+ if(setpt)
+ {
+ i16 accum=y;
+
+ if(accum>amax)
+ {
+ amax=accum;
+ discounteru=discfactor;
+ }
+ else if(--discounteru<=0)
+ {
+ discounteru=discfactor;
+ amax=(i32)((amax*32700)/32768);
+ }
+
+ if(accum<amin)
+ {
+ amin=accum;
+ discounterl=discfactor;
+ }
+ else if(--discounterl<=0)
+ {
+ discounterl=discfactor;
+ amin=(i32)((amin*32700)/32768);
+ }
+
+ apeak = (i32)(amax-amin)/2;
+
+ if(apeak>setpt)compOut=1;
+ else if(compOut&&(apeak<(setpt-hyst)))compOut=0;
+ }
+ }
+
+ mySps->decimator = decimator;
+
+ mySps->amax=amax;
+ mySps->amin=amin;
+ mySps->apeak=apeak;
+ mySps->discounteru=discounteru;
+ mySps->discounterl=discounterl;
+
+ mySps->compOut=compOut;
+
+ return 0;
+}
+/*
+ general purpose integrator lpf
+*/
+i16 gp_inte_00(t_pmr_sps *mySps)
+{
+ i16 npoints;
+ i16 *input, *output;
+
+ i32 inputGain, outputGain,calcAdjust;
+ i32 i;
+ i32 accum;
+
+ i32 state00;
+ i16 coeff00, coeff01;
+
+ TRACEX(("gp_inte_00() %i\n",mySps->enabled));
+ if(!mySps->enabled)return(1);
+
+ input = mySps->source;
+ output = mySps->sink;
+
+ npoints=mySps->nSamples;
+
+ inputGain=mySps->inputGain;
+ outputGain=mySps->outputGain;
+ calcAdjust=mySps->calcAdjust;
+
+ coeff00=((i16*)mySps->coef)[0];
+ coeff01=((i16*)mySps->coef)[1];
+ state00=((i32*)mySps->x)[0];
+
+ // note fixed gain of 2 to compensate for attenuation
+ // in passband
+
+ for(i=0;i<npoints;i++)
+ {
+ accum=input[i];
+ state00 = accum + (state00*coeff01)/M_Q15;
+ accum = (state00*coeff00)/(M_Q15/4);
+ output[i]=(accum*outputGain)/M_Q8;
+ }
+
+ ((i32*)(mySps->x))[0]=state00;
+
+ return 0;
+}
+/*
+ general purpose differentiator hpf
+*/
+i16 gp_diff(t_pmr_sps *mySps)
+{
+ i16 *input, *output;
+ i16 npoints;
+ i32 inputGain, outputGain, calcAdjust;
+ i32 i;
+ i32 temp0,temp1;
+ i16 x0;
+ i32 y0;
+ i16 a0,a1;
+ i16 b0;
+ i16 *coef;
+ i16 *x;
+
+ input = mySps->source;
+ output = mySps->sink;
+
+ npoints=mySps->nSamples;
+
+ inputGain=mySps->inputGain;
+ outputGain=mySps->outputGain;
+ calcAdjust=mySps->calcAdjust;
+
+ coef=(i16*)(mySps->coef);
+ x=(i16*)(mySps->x);
+ a0=coef[0];
+ a1=coef[1];
+ b0=coef[2];
+
+ x0=x[0];
+
+ TRACEX(("gp_diff()\n"));
+
+ for (i=0;i<npoints;i++)
+ {
+ temp0 = x0 * a1;
+ x0 = input[i];
+ temp1 = input[i] * a0;
+ y0 = (temp0 + temp1)/calcAdjust;
+ output[i]=(y0*outputGain)/M_Q8;
+ }
+
+ x[0]=x0;
+
+ return 0;
+}
+/* ----------------------------------------------------------------------
+ CenterSlicer
+*/
+i16 CenterSlicer(t_pmr_sps *mySps)
+{
+ i16 npoints,lhit,uhit;
+ i16 *input, *output, *buff;
+
+ i32 inputGain, outputGain, inputGainB;
+ i32 i;
+ i32 accum;
+
+ i32 amax; // buffer amplitude maximum
+ i32 amin; // buffer amplitude minimum
+ i32 apeak; // buffer amplitude peak
+ i32 center;
+ i32 setpt; // amplitude set point for peak tracking
+
+ i32 discounteru; // amplitude detector integrator discharge counter upper
+ i32 discounterl; // amplitude detector integrator discharge counter lower
+ i32 discfactor; // amplitude detector integrator discharge factor
+
+ TRACEX(("CenterSlicer() %i\n",mySps->enabled));
+
+ input = mySps->source;
+ output = mySps->sink;
+ buff = mySps->buff;
+
+ npoints=mySps->nSamples;
+
+ inputGain=mySps->inputGain;
+ outputGain=mySps->outputGain;
+ inputGainB=mySps->inputGainB;
+
+ amax=mySps->amax;
+ amin=mySps->amin;
+ setpt=mySps->setpt;
+ apeak=mySps->apeak;
+ discounteru=mySps->discounteru;
+ discounterl=mySps->discounterl;
+
+ discfactor=mySps->discfactor;
+ npoints=mySps->nSamples;
+
+ for(i=0;i<npoints;i++)
+ {
+ accum=input[i];
+
+ lhit=uhit=0;
+
+ if(accum>amax)
+ {
+ amax=accum;
+ uhit=1;
+ if(amin<(amax-setpt))
+ {
+ amin=(amax-setpt);
+ lhit=1;
+ }
+ }
+ else if(accum<amin)
+ {
+ amin=accum;
+ lhit=1;
+ if(amax>(amin+setpt))
+ {
+ amax=(amin+setpt);
+ uhit=1;
+ }
+ }
+
+ if(--discounteru<=0 && amax>0)
+ {
+ amax--;
+ uhit=1;
+ }
+
+ if(--discounterl<=0 && amin<0)
+ {
+ amin++;
+ lhit=1;
+ }
+
+ if(uhit)discounteru=discfactor;
+ if(lhit)discounterl=discfactor;
+
+ apeak = (amax-amin)/2;
+ center = (amax+amin)/2;
+ accum = accum - center;
+ output[i]=accum;
+
+ // do limiter function
+ if(accum>inputGainB)accum=inputGainB;
+ else if(accum<-inputGainB)accum=-inputGainB;
+ buff[i]=accum;
+
+ #if XPMR_DEBUG0 == 1
+ #if 0
+ mySps->debugBuff0[i]=center;
+ #endif
+ #if 0
+ if(mySps->parentChan->frameCountRx&0x01) mySps->parentChan->prxDebug1[i]=amax;
+ else mySps->parentChan->prxDebug1[i]=amin;
+ #endif
+ #endif
+ }
+
+ mySps->amax=amax;
+ mySps->amin=amin;
+ mySps->apeak=apeak;
+ mySps->discounteru=discounteru;
+ mySps->discounterl=discounterl;
+
+ return 0;
+}
+/* ----------------------------------------------------------------------
+ MeasureBlock
+ determine peak amplitude
+*/
+i16 MeasureBlock(t_pmr_sps *mySps)
+{
+ i16 npoints;
+ i16 *input, *output;
+
+ i32 inputGain, outputGain;
+ i32 i;
+ i32 accum;
+
+ i16 amax; // buffer amplitude maximum
+ i16 amin; // buffer amplitude minimum
+ i16 apeak=0; // buffer amplitude peak (peak to peak)/2
+ i16 setpt; // amplitude set point for amplitude comparator
+
+ i32 discounteru; // amplitude detector integrator discharge counter upper
+ i32 discounterl; // amplitude detector integrator discharge counter lower
+ i32 discfactor; // amplitude detector integrator discharge factor
+
+ TRACEX(("MeasureBlock() %i\n",mySps->enabled));
+
+ if(!mySps->enabled)return 1;
+
+ if(mySps->option==3)
+ {
+ mySps->amax = mySps->amin = mySps->apeak = \
+ mySps->discounteru = mySps->discounterl = \
+ mySps->enabled = 0;
+ return 1;
+ }
+
+ input = mySps->source;
+ output = mySps->sink;
+
+ npoints=mySps->nSamples;
+
+ inputGain=mySps->inputGain;
+ outputGain=mySps->outputGain;
+
+ amax=mySps->amax;
+ amin=mySps->amin;
+ setpt=mySps->setpt;
+ discounteru=mySps->discounteru;
+ discounterl=mySps->discounterl;
+
+ discfactor=mySps->discfactor;
+ npoints=mySps->nSamples;
+
+ for(i=0;i<npoints;i++)
+ {
+ accum=input[i];
+
+ if(accum>amax)
+ {
+ amax=accum;
+ discounteru=discfactor;
+ }
+ else if(--discounteru<=0)
+ {
+ discounteru=discfactor;
+ amax=(i32)((amax*32700)/32768);
+ }
+
+ if(accum<amin)
+ {
+ amin=accum;
+ discounterl=discfactor;
+ }
+ else if(--discounterl<=0)
+ {
+ discounterl=discfactor;
+ amin=(i32)((amin*32700)/32768);
+ }
+
+ apeak = (i32)(amax-amin)/2;
+ if(output)output[i]=apeak;
+ }
+
+ mySps->amax=amax;
+ mySps->amin=amin;
+ mySps->apeak=apeak;
+ mySps->discounteru=discounteru;
+ mySps->discounterl=discounterl;
+ if(apeak>=setpt) mySps->compOut=1;
+ else mySps->compOut=0;
+
+ //TRACEX((" -MeasureBlock()=%i\n",mySps->apeak));
+ return 0;
+}
+/*
+ SoftLimiter
+*/
+i16 SoftLimiter(t_pmr_sps *mySps)
+{
+ i16 npoints;
+ //i16 samples, lhit,uhit;
+ i16 *input, *output;
+
+ i32 inputGain, outputGain;
+ i32 i;
+ i32 accum;
+ i32 tmp;
+
+ i32 amax; // buffer amplitude maximum
+ i32 amin; // buffer amplitude minimum
+ //i32 apeak; // buffer amplitude peak
+ i32 setpt; // amplitude set point for amplitude comparator
+ i16 compOut; // amplitude comparator output
+
+ input = mySps->source;
+ output = mySps->sink;
+
+ inputGain=mySps->inputGain;
+ outputGain=mySps->outputGain;
+
+ npoints=mySps->nSamples;
+
+ setpt=mySps->setpt;
+ amax=(setpt*124)/128;
+ amin=-amax;
+
+ TRACEX(("SoftLimiter() %i %i %i) \n",amin, amax,setpt));
+
+ for(i=0;i<npoints;i++)
+ {
+ accum=input[i];
+ //accum=input[i]*mySps->inputGain/256;
+
+ if(accum>setpt)
+ {
+ tmp=((accum-setpt)*4)/128;
+ accum=setpt+tmp;
+ if(accum>amax)accum=amax;
+ compOut=1;
+ accum=setpt;
+ }
+ else if(accum<-setpt)
+ {
+ tmp=((accum+setpt)*4)/128;
+ accum=(-setpt)-tmp;
+ if(accum<amin)accum=amin;
+ compOut=1;
+ accum=-setpt;
+ }
+
+ output[i]=(accum*outputGain)/M_Q8;
+ }
+
+ return 0;
+}
+/*
+ SigGen() - sine, square function generator
+ sps overloaded values
+ discfactor = phase factor
+ discfactoru = phase index
+ if source is not NULL then mix it in!
+
+ sign table and output gain are in Q15 format (32767=.999)
+*/
+i16 SigGen(t_pmr_sps *mySps)
+{
+ #define PH_FRACT_FACT 128
+
+ i32 ph;
+ i16 i,outputgain,waveform,numChanOut,selChanOut;
+ i32 accum;
+
+ TRACEX(("SigGen(%i) \n",mySps->option));
+
+ if(!mySps->freq ||!mySps->enabled)return 0;
+
+ outputgain=mySps->outputGain;
+ waveform=0;
+ numChanOut=mySps->numChanOut;
+ selChanOut=mySps->selChanOut;
+
+ if(mySps->option==1)
+ {
+ mySps->option=0;
+ mySps->state=1;
+ mySps->discfactor=
+ (SAMPLES_PER_SINE*mySps->freq*PH_FRACT_FACT)/mySps->sampleRate/10;
+
+ TRACEX((" SigGen() discfactor = %i\n",mySps->discfactor));
+ if(mySps->discounterl)mySps->state=2;
+ }
+ else if(mySps->option==2)
+ {
+ i16 shiftfactor=CTCSS_TURN_OFF_SHIFT;
+ // phase shift request
+ mySps->option=0;
+ mySps->state=2;
+ mySps->discounterl=CTCSS_TURN_OFF_TIME-(2*MS_PER_FRAME); //
+
+ mySps->discounteru = \
+ (mySps->discounteru + (((SAMPLES_PER_SINE*shiftfactor)/360)*PH_FRACT_FACT)) % (SAMPLES_PER_SINE*PH_FRACT_FACT);
+ //printf("shiftfactor = %i\n",shiftfactor);
+ //shiftfactor+=10;
+ }
+ else if(mySps->option==3)
+ {
+ // stop it and clear the output buffer
+ mySps->option=0;
+ mySps->state=0;
+ mySps->enabled=0;
+ for(i=0;i<mySps->nSamples;i++)
+ mySps->sink[(i*numChanOut)+selChanOut]=0;
+ return(0);
+ }
+ else if(mySps->state==2)
+ {
+ // doing turn off
+ mySps->discounterl-=MS_PER_FRAME;
+ if(mySps->discounterl<=0)
+ {
+ mySps->option=3;
+ mySps->state=2;
+ }
+ }
+ else if(mySps->state==0)
+ {
+ return(0);
+ }
+
+ ph=mySps->discounteru;
+
+ for(i=0;i<mySps->nSamples;i++)
+ {
+ if(!waveform)
+ {
+ // sine
+ //tmp=(sinetablex[ph/PH_FRACT_FACT]*amplitude)/M_Q16;
+ accum=sinetablex[ph/PH_FRACT_FACT];
+ accum=(accum*outputgain)/M_Q8;
+ }
+ else
+ {
+ // square
+ if(ph>SAMPLES_PER_SINE/2)
+ accum=outputgain/M_Q8;
+ else
+ accum=-outputgain/M_Q8;
+ }
+
+ if(mySps->source)accum+=mySps->source[i];
+
+ mySps->sink[(i*numChanOut)+selChanOut]=accum;
+
+ ph=(ph+mySps->discfactor)%(SAMPLES_PER_SINE*PH_FRACT_FACT);
+ }
+
+ mySps->discounteru=ph;
+
+ return 0;
+}
+/*
+ adder/mixer
+ takes existing buffer and adds source buffer to destination buffer
+ sink buffer = (sink buffer * gain) + source buffer
+*/
+i16 pmrMixer(t_pmr_sps *mySps)
+{
+ i32 accum;
+ i16 i, *input, *inputB, *output;
+ i16 inputGain, inputGainB; // apply to input data in Q7.8 format
+ i16 outputGain; // apply to output data in Q7.8 format
+ i16 discounteru,discounterl,amax,amin,setpt,discfactor;
+ i16 npoints,uhit,lhit,apeak,measPeak;
+
+ TRACEX(("pmrMixer()\n"));
+
+ input = mySps->source;
+ inputB = mySps->sourceB;
+ output = mySps->sink;
+
+ inputGain=mySps->inputGain;
+ inputGainB=mySps->inputGainB;
+ outputGain=mySps->outputGain;
+
+ amax=mySps->amax;
+ amin=mySps->amin;
+ setpt=mySps->setpt;
+ discounteru=mySps->discounteru;
+ discounterl=mySps->discounteru;
+
+ discfactor=mySps->discfactor;
+ npoints=mySps->nSamples;
+ measPeak=mySps->measPeak;
+
+ for(i=0;i<npoints;i++)
+ {
+ accum = ((input[i]*inputGain)/M_Q8) +
+ ((inputB[i]*inputGainB)/M_Q8);
+
+ accum=(accum*outputGain)/M_Q8;
+ output[i]=accum;
+
+ if(measPeak){
+ lhit=uhit=0;
+
+ if(accum>amax){
+ amax=accum;
+ uhit=1;
+ if(amin<(amax-setpt)){
+ amin=(amax-setpt);
+ lhit=1;
+ }
+ }
+ else if(accum<amin){
+ amin=accum;
+ lhit=1;
+ if(amax>(amin+setpt)){
+ amax=(amin+setpt);
+ uhit=1;
+ }
+ }
+
+ if(--discounteru<=0 && amax>0){
+ amax--;
+ uhit=1;
+ }
+
+ if(--discounterl<=0 && amin<0){
+ amin++;
+ lhit=1;
+ }
+
+ if(uhit)discounteru=discfactor;
+ if(lhit)discounterl=discfactor;
+ }
+ }
+
+ if(measPeak){
+ apeak = (amax-amin)/2;
+ mySps->apeak=apeak;
+ mySps->amax=amax;
+ mySps->amin=amin;
+ mySps->discounteru=discounteru;
+ mySps->discounterl=discounterl;
+ }
+
+ return 0;
+}
+/*
+ DelayLine
+*/
+i16 DelayLine(t_pmr_sps *mySps)
+{
+ i16 *input, *output, *buff;
+ i16 i, npoints,buffsize,inindex,outindex;
+
+ TRACEX((" DelayLine() %i\n",mySps->enabled));
+
+ input = mySps->source;
+ output = mySps->sink;
+ buff = (i16*)(mySps->buff);
+ buffsize = mySps->buffSize;
+ npoints = mySps->nSamples;
+
+ outindex = mySps->buffOutIndex;
+ inindex = outindex + mySps->buffLead;
+
+ for(i=0;i<npoints;i++)
+ {
+ inindex %= buffsize;
+ outindex %= buffsize;
+
+ buff[inindex]=input[i];
+ output[i]=buff[outindex];
+ inindex++;
+ outindex++;
+ }
+ mySps->buffOutIndex=outindex;
+
+ return 0;
+}
+/*
+ Continuous Tone Coded Squelch (CTCSS) Detector
+*/
+i16 ctcss_detect(t_pmr_chan *pmrChan)
+{
+ i16 i,points2do, points=0, *pInput, hit, thit,relax;
+ i16 tnum, tmp, indexWas=0, indexNow, gain, peakwas=0, diffpeak;
+ i16 difftrig;
+ i16 lasttv0=0, lasttv1=0, lasttv2=0, tv0, tv1, tv2, indexDebug;
+
+ TRACEX(("ctcss_detect(%p) %i %i %i %i\n",pmrChan,
+ pmrChan->rxCtcss->enabled,
+ pmrChan->rxCtcssIndex,
+ pmrChan->rxCtcss->testIndex,
+ pmrChan->rxCtcss->decode));
+
+ if(!pmrChan->rxCtcss->enabled)return(1);
+
+ relax = pmrChan->rxCtcss->relax;
+ pInput = pmrChan->rxCtcss->input;
+ gain = pmrChan->rxCtcss->gain;
+
+ if(relax) difftrig=(-0.1*M_Q15);
+ else difftrig=(-0.05*M_Q15);
+
+ thit=hit=-1;
+
+ //TRACEX((" ctcss_detect() %i %i %i %i\n", CTCSS_NUM_CODES,0,0,0));
+
+ for(tnum=0;tnum<CTCSS_NUM_CODES;tnum++)
+ {
+ i32 accum, peak;
+ t_tdet *ptdet;
+ i16 fudgeFactor;
+ i16 binFactor;
+
+ //TRACEX((" ctcss_detect() tnum=%i %i\n",tnum,pmrChan->rxCtcssMap[tnum]));
+
+ if( (pmrChan->rxCtcssMap[tnum] < 0) ||
+ (pmrChan->rxCtcss->decode>=0 && (tnum!= pmrChan->rxCtcss->decode)) ||
+ (!pmrChan->rxCtcss->multiFreq && (tnum!= pmrChan->rxCtcssIndex))
+ )
+ continue;
+
+ //TRACEX((" ctcss_detect() tnum=%i\n",tnum));
+
+ ptdet=&(pmrChan->rxCtcss->tdet[tnum]);
+ indexDebug=0;
+ points=points2do=pmrChan->nSamplesRx;
+ fudgeFactor=ptdet->fudgeFactor;
+ binFactor=ptdet->binFactor;
+
+ while(ptdet->counter < (points2do*CTCSS_SCOUNT_MUL))
+ {
+ //TRACEX((" ctcss_detect() - inner loop\n"));
+ tmp=(ptdet->counter/CTCSS_SCOUNT_MUL)+1;
+ ptdet->counter-=(tmp*CTCSS_SCOUNT_MUL);
+ points2do-=tmp;
+ indexNow=points-points2do;
+
+ ptdet->counter += ptdet->counterFactor;
+
+ accum = pInput[indexNow-1]; // dude's major bug fix!
+
+ peakwas=ptdet->peak;
+
+ ptdet->z[ptdet->zIndex]+=
+ (((accum - ptdet->z[ptdet->zIndex])*binFactor)/M_Q15);
+
+ peak = abs(ptdet->z[0]-ptdet->z[2]) + abs(ptdet->z[1]-ptdet->z[3]);
+
+ if (ptdet->peak < peak)
+ ptdet->peak += ( ((peak-ptdet->peak)*binFactor)/M_Q15);
+ else
+ ptdet->peak=peak;
+
+ {
+ static const i16 a0=13723;
+ static const i16 a1=-13723;
+ i32 temp0,temp1;
+ i16 x0;
+
+ //differentiate
+ x0=ptdet->zd;
+ temp0 = x0 * a1;
+ ptdet->zd = ptdet->peak;
+ temp1 = ptdet->peak * a0;
+ diffpeak = (temp0 + temp1)/1024;
+ }
+
+ if(diffpeak<(-0.03*M_Q15))ptdet->dvd-=4;
+ else if(ptdet->dvd<0)ptdet->dvd++;
+
+ if((ptdet->dvd < -12) && diffpeak > (-0.02*M_Q15))ptdet->dvu+=2;
+ else if(ptdet->dvu)ptdet->dvu--;
+
+ tmp=ptdet->setpt;
+ if(pmrChan->rxCtcss->decode==tnum)
+ {
+ if(relax)tmp=(tmp*55)/100;
+ else tmp=(tmp*80)/100;
+ }
+
+ if(ptdet->peak > tmp)
+ {
+ if(ptdet->decode<(fudgeFactor*32))ptdet->decode++;
+ }
+ else if(pmrChan->rxCtcss->decode==tnum)
+ {
+ if(ptdet->peak > ptdet->hyst)ptdet->decode--;
+ else if(relax) ptdet->decode--;
+ else ptdet->decode-=4;
+ }
+ else
+ {
+ ptdet->decode=0;
+ }
+
+ if((pmrChan->rxCtcss->decode==tnum) && !relax && (ptdet->dvu > (0.00075*M_Q15)))
+ {
+ ptdet->decode=0;
+ ptdet->z[0]=ptdet->z[1]=ptdet->z[2]=ptdet->z[3]=ptdet->dvu=0;
+ //printf("ctcss_detect() turnoff code!\n");
+ }
+
+ if(ptdet->decode<0 || !pmrChan->rxCarrierDetect)ptdet->decode=0;
+
+ if(ptdet->decode>=fudgeFactor)thit=tnum;
+
+ #if XPMR_DEBUG0 == 1
+ //if(thit>=0 && thit==tnum)
+ // printf(" ctcss_detect() %i %i %i %i \n",tnum,ptdet->peak,ptdet->setpt,ptdet->hyst);
+
+ // tv0=accum;
+ tv0=ptdet->peak;
+ tv1=diffpeak;
+ tv2=ptdet->dvu;
+
+ //tv1=ptdet->zi*100;
+ while(indexDebug<indexNow)
+ {
+ if(indexDebug==0)lasttv0=ptdet->pDebug0[points-1];
+ if(ptdet->pDebug0)ptdet->pDebug0[indexDebug]=lasttv0;
+
+ if(indexDebug==0)lasttv1=ptdet->pDebug1[points-1];
+ if(ptdet->pDebug1)ptdet->pDebug1[indexDebug]=lasttv1;
+
+ if(indexDebug==0)lasttv2=ptdet->pDebug2[points-1];
+ if(ptdet->pDebug2)ptdet->pDebug2[indexDebug]=lasttv2;
+
+ indexDebug++;
+ }
+ lasttv0=tv0;
+ lasttv1=tv1;
+ lasttv2=tv2*100;
+ #endif
+ indexWas=indexNow;
+ ptdet->zIndex=(++ptdet->zIndex)%4;
+ }
+ ptdet->counter-=(points2do*CTCSS_SCOUNT_MUL);
+
+ #if XPMR_DEBUG0 == 1
+ for(i=indexWas;i<points;i++)
+ {
+ if(ptdet->pDebug0)ptdet->pDebug0[i]=lasttv0;
+ if(ptdet->pDebug1)ptdet->pDebug1[i]=lasttv1;
+ if(ptdet->pDebug2)ptdet->pDebug2[i]=lasttv2;
+ }
+ #endif
+ }
+
+ //TRACEX((" ctcss_detect() thit %i\n",thit));
+
+ if(pmrChan->rxCtcss->BlankingTimer>0)pmrChan->rxCtcss->BlankingTimer-=points;
+ if(pmrChan->rxCtcss->BlankingTimer<0)pmrChan->rxCtcss->BlankingTimer=0;
+
+ if(thit>=0 && pmrChan->rxCtcss->decode<0 && !pmrChan->rxCtcss->BlankingTimer)
+ {
+ pmrChan->rxCtcss->decode=thit;
+ }
+ else if(thit<0 && pmrChan->rxCtcss->decode>=0)
+ {
+ pmrChan->rxCtcss->BlankingTimer=SAMPLE_RATE_NETWORK/5;
+ pmrChan->rxCtcss->decode=-1;
+
+ for(tnum=0;tnum<CTCSS_NUM_CODES;tnum++)
+ {
+ t_tdet *ptdet=NULL;
+ ptdet=&(pmrChan->rxCtcss->tdet[tnum]);
+ ptdet->decode=0;
+ ptdet->z[0]=ptdet->z[1]=ptdet->z[2]=ptdet->z[3]=0;
+ }
+ }
+ //TRACEX((" ctcss_detect() thit %i %i\n",thit,pmrChan->rxCtcss->decode));
+ return(0);
+}
+/*
+ TxTestTone
+*/
+static i16 TxTestTone(t_pmr_chan *pChan, i16 function)
+{
+ if(function==1)
+ {
+ pChan->spsSigGen1->enabled=1;
+ pChan->spsSigGen1->option=1;
+ pChan->spsTx->source=pChan->spsSigGen1->sink;
+ }
+ else
+ {
+ pChan->spsSigGen1->option=3;
+ }
+ return 0;
+}
+/*
+ assumes:
+ sampling rate is 48KS/s
+ samples are all 16 bits
+ samples are filtered and decimated by 1/6th
+*/
+t_pmr_chan *createPmrChannel(t_pmr_chan *tChan, i16 numSamples)
+{
+ i16 i, *inputTmp;
+
+ t_pmr_chan *pChan;
+ t_pmr_sps *pSps;
+ t_dec_ctcss *pDecCtcss;
+ t_tdet *ptdet;
+
+ TRACEX(("createPmrChannel(%p,%i)\n",tChan,numSamples));
+
+ pChan = (t_pmr_chan *)calloc(sizeof(t_pmr_chan),1);
+
+ if(pChan==NULL)
+ {
+ printf("createPmrChannel() failed\n");
+ return(NULL);
+ }
+
+ pChan->nSamplesRx=numSamples;
+ pChan->nSamplesTx=numSamples;
+
+ pChan->index=pmrChanIndex++;
+
+ for(i=0;i<CTCSS_NUM_CODES;i++)
+ {
+ pChan->rxCtcssMap[i]=-1;
+ }
+
+ pChan->rxCtcssIndex=-1;
+
+ if(tChan==NULL)
+ {
+ pChan->rxNoiseSquelchEnable=0;
+ pChan->rxHpfEnable=0;
+ pChan->rxDeEmpEnable=0;
+ pChan->rxCenterSlicerEnable=0;
+ pChan->rxCtcssDecodeEnable=0;
+ pChan->rxDcsDecodeEnable=0;
+
+ pChan->rxCarrierPoint = 17000;
+ pChan->rxCarrierHyst = 2500;
+
+ pChan->rxCtcssFreq=103.5;
+
+ pChan->txHpfEnable=0;
+ pChan->txLimiterEnable=0;
+ pChan->txPreEmpEnable=0;
+ pChan->txLpfEnable=1;
+ pChan->txCtcssFreq=103.5;
+ pChan->txMixA=TX_OUT_VOICE;
+ pChan->txMixB=TX_OUT_LSD;
+ }
+ else
+ {
+ pChan->rxDemod=tChan->rxDemod;
+ pChan->rxCdType=tChan->rxCdType;
+ pChan->rxSquelchPoint = tChan->rxSquelchPoint;
+ pChan->rxCarrierHyst = 3000;
+ pChan->rxCtcssFreq=tChan->rxCtcssFreq;
+
+ for(i=0;i<CTCSS_NUM_CODES;i++)
+ pChan->rxCtcssMap[i]=tChan->rxCtcssMap[i];
+
+ pChan->txMod=tChan->txMod;
+ pChan->txHpfEnable=1;
+ pChan->txLpfEnable=1;
+ pChan->txCtcssFreq=tChan->txCtcssFreq;
+ pChan->txMixA=tChan->txMixA;
+ pChan->txMixB=tChan->txMixB;
+ pChan->radioDuplex=tChan->radioDuplex;
+ }
+
+ TRACEX(("misc settings \n"));
+
+ if(pChan->rxCdType==CD_XPMR_NOISE){
+ pChan->rxNoiseSquelchEnable=1;
+ }
+
+ if(pChan->rxDemod==RX_AUDIO_FLAT){
+ pChan->rxHpfEnable=1;
+ pChan->rxDeEmpEnable=1;
+ }
+
+ pChan->rxCarrierPoint=(pChan->rxSquelchPoint*32767)/100;
+ pChan->rxCarrierHyst = 3000; //pChan->rxCarrierPoint/15;
+
+ pChan->rxDcsDecodeEnable=0;
+
+ if(pChan->rxCtcssFreq!=0){
+ pChan->rxHpfEnable=1;
+ pChan->rxCenterSlicerEnable=1;
+ pChan->rxCtcssDecodeEnable=1;
+ pChan->rxCtcssIndex=CtcssFreqIndex(pChan->rxCtcssFreq);
+ }
+
+ if(pChan->txMod){
+ pChan->txPreEmpEnable=1;
+ pChan->txLimiterEnable=1;
+ }
+
+ TRACEX(("calloc buffers \n"));
+
+ pChan->pRxDemod = calloc(numSamples,2);
+ pChan->pRxNoise = calloc(numSamples,2);
+ pChan->pRxBase = calloc(numSamples,2);
+ pChan->pRxHpf = calloc(numSamples,2);
+ pChan->pRxLsd = calloc(numSamples,2);
+ pChan->pRxSpeaker = calloc(numSamples,2);
+ pChan->pRxCtcss = calloc(numSamples,2);
+ pChan->pRxDcTrack = calloc(numSamples,2);
+ pChan->pRxLsdLimit = calloc(numSamples,2);
+
+
+ pChan->pTxBase = calloc(numSamples,2);
+ pChan->pTxHpf = calloc(numSamples,2);
+ pChan->pTxPreEmp = calloc(numSamples,2);
+ pChan->pTxLimiter = calloc(numSamples,2);
+ pChan->pTxLsd = calloc(numSamples,2);
+ pChan->pTxLsdLpf = calloc(numSamples,2);
+ pChan->pTxComposite = calloc(numSamples,2);
+ pChan->pSigGen0 = calloc(numSamples,2);
+ pChan->pSigGen1 = calloc(numSamples,2);
+
+ pChan->pTxCode = calloc(numSamples,2);
+ pChan->pTxOut = calloc(numSamples,2*2*6); // output buffer
+
+ #if XPMR_DEBUG0 == 1
+ pChan->pTxPttIn = calloc(numSamples,2);
+ pChan->pTxPttOut = calloc(numSamples,2);
+ pChan->prxDebug0 = calloc(numSamples,2);
+ pChan->prxDebug1 = calloc(numSamples,2);
+ pChan->prxDebug2 = calloc(numSamples,2);
+ pChan->prxDebug3 = calloc(numSamples,2);
+ pChan->ptxDebug0 = calloc(numSamples,2);
+ pChan->ptxDebug1 = calloc(numSamples,2);
+ pChan->ptxDebug2 = calloc(numSamples,2);
+ pChan->ptxDebug3 = calloc(numSamples,2);
+ pChan->pNull = calloc(numSamples,2);
+ for(i=0;i<numSamples;i++)pChan->pNull[i]=((i%(numSamples/2))*8000)-4000;
+ #endif
+
+ TRACEX(("create ctcss\n"));
+
+ pDecCtcss = (t_dec_ctcss *)calloc(sizeof(t_dec_ctcss),1);
+
+ pChan->rxCtcss=pDecCtcss;
+ pDecCtcss->enabled=1;
+ pDecCtcss->gain=1*M_Q8;
+ pDecCtcss->limit=8192;
+ pDecCtcss->input=pChan->pRxLsdLimit;
+ pDecCtcss->testIndex=pChan->rxCtcssIndex;
+ if(!pDecCtcss->testIndex)pDecCtcss->testIndex=1;
+ pChan->rxCtcssMap[pChan->rxCtcssIndex]=pChan->rxCtcssIndex;
+ pDecCtcss->decode=-1;
+
+ for(i=0;i<CTCSS_NUM_CODES;i++)
+ {
+ ptdet=&(pChan->rxCtcss->tdet[i]);
+ ptdet->state=1;
+ ptdet->setpt=(M_Q15*0.067); // 0.069
+ ptdet->hyst =(M_Q15*0.020);
+ ptdet->counterFactor=coef_ctcss_div[i];
+ ptdet->binFactor=(M_Q15*0.135); // was 0.140
+ ptdet->fudgeFactor=8;
+ }
+
+ // General Purpose Function Generator
+ pSps=pChan->spsSigGen1=createPmrSps();
+ pSps->parentChan=pChan;
+ pSps->sink=pChan->pSigGen1;
+ pSps->numChanOut=1;
+ pSps->selChanOut=0;
+ pSps->sigProc=SigGen;
+ pSps->nSamples=pChan->nSamplesTx;
+ pSps->sampleRate=SAMPLE_RATE_NETWORK;
+ pSps->freq=10000; // in increments of 0.1 Hz
+ pSps->outputGain=(.25*M_Q8);
+ pSps->option=0;
+ pSps->interpolate=1;
+ pSps->decimate=1;
+ pSps->enabled=0;
+
+
+ // CTCSS ENCODER
+ pSps = pChan->spsSigGen0 = createPmrSps();
+ pSps->parentChan=pChan;
+ pSps->sink=pChan->pTxLsd;
+ pSps->sigProc=SigGen;
+ pSps->numChanOut=1;
+ pSps->selChanOut=0;
+ pSps->nSamples=pChan->nSamplesTx;
+ pSps->sampleRate=SAMPLE_RATE_NETWORK;
+ pSps->freq=pChan->txCtcssFreq*10; // in increments of 0.1 Hz
+ pSps->outputGain=(0.5*M_Q8);
+ pSps->option=0;
+ pSps->interpolate=1;
+ pSps->decimate=1;
+ pSps->enabled=0;
+
+
+ // Tx LSD Low Pass Filter
+ pSps=pChan->spsTxLsdLpf=pSps->nextSps=createPmrSps();
+ pSps->source=pChan->pTxLsd;
+ pSps->sink=pChan->pTxLsdLpf;
+ pSps->sigProc=pmr_gp_fir;
+ pSps->enabled=0;
+ pSps->numChanOut=1;
+ pSps->selChanOut=0;
+ pSps->nSamples=pChan->nSamplesTx;
+ pSps->decimator=pSps->decimate=1;
+ pSps->interpolate=1;
+ pSps->inputGain=(1*M_Q8);
+ pSps->outputGain=(1*M_Q8);
+
+ if(pChan->txCtcssFreq>203.0)
+ {
+ pSps->ncoef=taps_fir_lpf_250_9_66;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_fir_lpf_250_9_66;
+ pSps->nx=taps_fir_lpf_250_9_66;
+ pSps->size_x=2;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_x));
+ pSps->calcAdjust=gain_fir_lpf_250_9_66;
+ }
+ else
+ {
+ pSps->ncoef=taps_fir_lpf_215_9_88;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_fir_lpf_215_9_88;
+ pSps->nx=taps_fir_lpf_215_9_88;
+ pSps->size_x=2;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_x));
+ pSps->calcAdjust=gain_fir_lpf_215_9_88;
+ }
+
+ pSps->inputGain=(1*M_Q8);
+ pSps->outputGain=(1*M_Q8);
+
+ if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n");
+
+
+ // RX Process
+ TRACEX(("create rx\n"));
+ pSps = NULL;
+
+ // allocate space for first sps and set pointers
+ pSps=pChan->spsRx=createPmrSps();
+ pSps->parentChan=pChan;
+ pSps->source=NULL; //set when called
+ pSps->sink=pChan->pRxBase;
+ pSps->sigProc=pmr_rx_frontend;
+ pSps->enabled=1;
+ pSps->decimator=pSps->decimate=6;
+ pSps->interpolate=pSps->interpolate=1;
+ pSps->nSamples=pChan->nSamplesRx;
+ pSps->ncoef=taps_fir_bpf_noise_1;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_fir_lpf_3K_1;
+ pSps->coef2=(void*)coef_fir_bpf_noise_1;
+ pSps->nx=taps_fir_bpf_noise_1;
+ pSps->size_x=2;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_coef));
+ pSps->calcAdjust=(gain_fir_lpf_3K_1*256)/0x0100;
+ pSps->outputGain=(1.0*M_Q8);
+ pSps->discfactor=2;
+ pSps->hyst=pChan->rxCarrierHyst;
+ pSps->setpt=pChan->rxCarrierPoint;
+ pChan->prxSquelchAdjust=&pSps->setpt;
+ #if XPMR_DEBUG0 == 1
+ pSps->debugBuff0=pChan->pRxDemod;
+ pSps->debugBuff1=pChan->pRxNoise;
+ pSps->debugBuff2=pChan->prxDebug0;
+ #endif
+
+
+ // allocate space for next sps and set pointers
+ // Rx SubAudible Decoder Low Pass Filter
+ pSps=pSps->nextSps=createPmrSps();
+ pSps->parentChan=pChan;
+ pSps->source=pChan->pRxBase;
+ pSps->sink=pChan->pRxLsd;
+ pSps->sigProc=pmr_gp_fir;
+ pSps->enabled=1;
+ pSps->numChanOut=1;
+ pSps->selChanOut=0;
+ pSps->nSamples=pChan->nSamplesRx;
+ pSps->decimator=pSps->decimate=1;
+ pSps->interpolate=1;
+
+ if(pChan->rxCtcssFreq>203.5)
+ {
+ pSps->ncoef=taps_fir_lpf_250_9_66;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_fir_lpf_250_9_66;
+ pSps->nx=taps_fir_lpf_250_9_66;
+ pSps->size_x=2;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_x));
+ pSps->calcAdjust=gain_fir_lpf_250_9_66;
+ }
+ else
+ {
+ pSps->ncoef=taps_fir_lpf_215_9_88;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_fir_lpf_215_9_88;
+ pSps->nx=taps_fir_lpf_215_9_88;
+ pSps->size_x=2;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_x));
+ pSps->calcAdjust=gain_fir_lpf_215_9_88;
+ }
+
+ pSps->inputGain=(1*M_Q8);
+ pSps->outputGain=(1*M_Q8);
+ pChan->prxCtcssMeasure=pSps->sink;
+ pChan->prxCtcssAdjust=&(pSps->outputGain);
+
+
+ // allocate space for next sps and set pointers
+ // CenterSlicer
+ if(pChan->rxCenterSlicerEnable)
+ {
+ pSps=pSps->nextSps=createPmrSps();
+ pSps->parentChan=pChan;
+ pSps->source=pChan->pRxLsd;
+ pSps->sink=pChan->pRxDcTrack;
+ pSps->buff=pChan->pRxLsdLimit;
+ pSps->sigProc=CenterSlicer;
+ pSps->enabled=1;
+ pSps->nSamples=pChan->nSamplesRx;
+ pSps->discfactor=800;
+ pSps->inputGain=(1*M_Q8);
+ pSps->outputGain=(1*M_Q8);
+ pSps->setpt=3000;
+ pSps->inputGainB=1000; // limiter set point
+ }
+
+ // allocate space for next sps and set pointers
+ // Rx HPF
+ pSps=pSps->nextSps=createPmrSps();
+ pSps->parentChan=pChan;
+ pChan->spsRxHpf=pSps;
+ pSps->source=pChan->pRxBase;
+ pSps->sink=pChan->pRxHpf;
+ pSps->sigProc=pmr_gp_fir;
+ pSps->enabled=1;
+ pSps->numChanOut=1;
+ pSps->selChanOut=0;
+ pSps->nSamples=pChan->nSamplesRx;
+ pSps->decimator=pSps->decimate=1;
+ pSps->interpolate=1;
+ pSps->ncoef=taps_fir_hpf_300_9_66;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_fir_hpf_300_9_66;
+ pSps->nx=taps_fir_hpf_300_9_66;
+ pSps->size_x=2;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_x));
+ if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n");
+ pSps->calcAdjust=gain_fir_hpf_300_9_66;
+ pSps->inputGain=(1*M_Q8);
+ pSps->outputGain=(1*M_Q8);
+ pChan->spsRxOut=pSps;
+
+ // allocate space for next sps and set pointers
+ // Rx DeEmp
+ if(pChan->rxDeEmpEnable){
+ pSps=pSps->nextSps=createPmrSps();
+ pSps->parentChan=pChan;
+ pChan->spsRxDeEmp=pSps;
+ pSps->source=pChan->pRxHpf;
+ pSps->sink=pChan->pRxSpeaker;
+ pChan->spsRxOut=pSps; // OUTPUT STRUCTURE! maw
+ pSps->sigProc=gp_inte_00;
+ pSps->enabled=1;
+ pSps->nSamples=pChan->nSamplesRx;
+
+ pSps->ncoef=taps_int_lpf_300_1_2;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_int_lpf_300_1_2;
+
+ pSps->nx=taps_int_lpf_300_1_2;
+ pSps->size_x=4;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_x));
+ if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n");
+ pSps->calcAdjust=gain_int_lpf_300_1_2/2;
+ pSps->inputGain=(1.0*M_Q8);
+ pSps->outputGain=(1.0*M_Q8);
+ pChan->prxVoiceMeasure=pSps->sink;
+ pChan->prxVoiceAdjust=&(pSps->outputGain);
+ }
+
+ if(pChan->rxDelayLineEnable)
+ {
+ TRACEX(("create delayline\n"));
+ pSps=pChan->spsDelayLine=pSps->nextSps=createPmrSps();
+ pSps->sigProc=DelayLine;
+ pSps->source=pChan->pRxSpeaker;
+ pSps->sink=pChan->pRxSpeaker;
+ pSps->enabled=0;
+ pSps->inputGain=1*M_Q8;
+ pSps->outputGain=1*M_Q8;
+ pSps->nSamples=pChan->nSamplesRx;
+ pSps->buffSize=4096;
+ pSps->buff=calloc(4096,2); // one second maximum
+ pSps->buffLead = (SAMPLE_RATE_NETWORK*0.100);
+ pSps->buffOutIndex=0;
+ }
+
+ if(pChan->rxCdType==CD_XPMR_VOX)
+ {
+ TRACEX(("create vox measureblock\n"));
+ pSps=pChan->spsRxVox=pSps->nextSps=createPmrSps();
+ pSps->sigProc=MeasureBlock;
+ pSps->parentChan=pChan;
+ pSps->source=pChan->pRxBase;
+ pSps->sink=pChan->prxDebug1;
+ pSps->inputGain=1*M_Q8;
+ pSps->outputGain=1*M_Q8;
+ pSps->nSamples=pChan->nSamplesRx;
+ pSps->discfactor=3;
+ pSps->setpt=(0.01*M_Q15);
+ pSps->hyst=(pSps->setpt/10);
+ pSps->enabled=1;
+ }
+
+ // tuning measure block
+ pSps=pChan->spsMeasure=pSps->nextSps=createPmrSps();
+ pSps->parentChan=pChan;
+ pSps->source=pChan->spsRx->sink;
+ pSps->sink=pChan->prxDebug2;
+ pSps->sigProc=MeasureBlock;
+ pSps->enabled=0;
+ pSps->nSamples=pChan->nSamplesRx;
+ pSps->discfactor=10;
+
+ pSps->nextSps=NULL; // last sps in chain RX
+
+
+ // CREATE TRANSMIT CHAIN
+ TRACEX((" create tx\n"));
+ inputTmp=NULL;
+ pSps = NULL;
+
+ // allocate space for first sps and set pointers
+
+ // Tx HPF SubAudible
+ if(pChan->txHpfEnable)
+ {
+ pSps=createPmrSps();
+ pChan->spsTx=pSps;
+ pSps->source=pChan->pTxBase;
+ pSps->sink=pChan->pTxHpf;
+ pSps->sigProc=pmr_gp_fir;
+ pSps->enabled=1;
+ pSps->numChanOut=1;
+ pSps->selChanOut=0;
+ pSps->nSamples=pChan->nSamplesTx;
+ pSps->decimator=pSps->decimate=1;
+ pSps->interpolate=1;
+ pSps->ncoef=taps_fir_hpf_300_9_66;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_fir_hpf_300_9_66;
+ pSps->nx=taps_fir_hpf_300_9_66;
+ pSps->size_x=2;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_x));
+ if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n");
+ pSps->calcAdjust=gain_fir_hpf_300_9_66;
+ pSps->inputGain=(1*M_Q8);
+ pSps->outputGain=(1*M_Q8);
+ inputTmp=pChan->pTxHpf;
+ }
+
+ // Tx PreEmphasis
+ if(pChan->txPreEmpEnable)
+ {
+ if(pSps==NULL) pSps=pChan->spsTx=createPmrSps();
+ else pSps=pSps->nextSps=createPmrSps();
+
+ pSps->parentChan=pChan;
+ pSps->source=inputTmp;
+ pSps->sink=pChan->pTxPreEmp;
+
+ pSps->sigProc=gp_diff;
+ pSps->enabled=1;
+ pSps->nSamples=pChan->nSamplesTx;
+
+ pSps->ncoef=taps_int_hpf_4000_1_2;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_int_hpf_4000_1_2;
+
+ pSps->nx=taps_int_hpf_4000_1_2;
+ pSps->size_x=2;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_x));
+ if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n");
+ pSps->outputGain=(1*M_Q8);
+ pSps->calcAdjust=gain_int_hpf_4000_1_2;
+ pSps->inputGain=(1*M_Q8);
+ pSps->outputGain=(1*M_Q8);
+ inputTmp=pSps->sink;
+ }
+
+ // Tx Limiter
+ if(pChan->txLimiterEnable)
+ {
+ if(pSps==NULL) pSps=pChan->spsTx=createPmrSps();
+ else pSps=pSps->nextSps=createPmrSps();
+ pSps->source=inputTmp;
+ pSps->sink=pChan->pTxLimiter;
+ pSps->sigProc=SoftLimiter;
+ pSps->enabled=1;
+ pSps->nSamples=pChan->nSamplesTx;
+ pSps->inputGain=(1*M_Q8);
+ pSps->outputGain=(1*M_Q8);
+ pSps->setpt=12000;
+ inputTmp=pSps->sink;
+ }
+
+ // Composite Mix of Voice and LSD
+ if((pChan->txMixA==TX_OUT_COMPOSITE)||(pChan->txMixB==TX_OUT_COMPOSITE))
+ {
+ if(pSps==NULL)
+ pSps=pChan->spsTx=createPmrSps();
+ else
+ pSps=pSps->nextSps=createPmrSps();
+ pSps->source=inputTmp;
+ pSps->sourceB=pChan->pTxLsdLpf; //asdf ??? !!! maw pTxLsdLpf
+ pSps->sink=pChan->pTxComposite;
+ pSps->sigProc=pmrMixer;
+ pSps->enabled=1;
+ pSps->nSamples=pChan->nSamplesTx;
+ pSps->inputGain=2*M_Q8;
+ pSps->inputGainB=1*M_Q8/8;
+ pSps->outputGain=1*M_Q8;
+ pSps->setpt=0;
+ inputTmp=pSps->sink;
+ pChan->ptxCtcssAdjust=&pSps->inputGainB;
+ }
+
+ // Chan A Upsampler and Filter
+ if(pSps==NULL) pSps=pChan->spsTx=createPmrSps();
+ else pSps=pSps->nextSps=createPmrSps();
+
+ pChan->spsTxOutA=pSps;
+ if(!pChan->spsTx)pChan->spsTx=pSps;
+ pSps->parentChan=pChan;
+
+ if(pChan->txMixA==TX_OUT_COMPOSITE)
+ {
+ pSps->source=pChan->pTxComposite;
+ }
+ else if(pChan->txMixA==TX_OUT_LSD)
+ {
+ pSps->source=pChan->pTxLsdLpf;
+ }
+ else if(pChan->txMixA==TX_OUT_VOICE)
+ {
+ pSps->source=pChan->pTxHpf;
+ }
+ else if (pChan->txMixA==TX_OUT_AUX)
+ {
+ pSps->source=inputTmp;
+ }
+ else
+ {
+ pSps->source=NULL;
+ }
+
+ pSps->sink=pChan->pTxOut;
+ pSps->sigProc=pmr_gp_fir;
+ pSps->enabled=1;
+ pSps->numChanOut=2;
+ pSps->selChanOut=0;
+ pSps->nSamples=pChan->nSamplesTx;
+ pSps->interpolate=6;
+ pSps->ncoef=taps_fir_lpf_3K_1;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_fir_lpf_3K_1;
+ pSps->nx=taps_fir_lpf_3K_1;
+ pSps->size_x=2;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_x));
+ if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n");
+ pSps->calcAdjust=gain_fir_lpf_3K_1;
+ pSps->inputGain=(1*M_Q8);
+ pSps->outputGain=(1*M_Q8);
+ if(pChan->txMixA==pChan->txMixB)pSps->monoOut=1;
+ else pSps->monoOut=0;
+
+
+ // Chan B Upsampler and Filter
+ if((pChan->txMixA!=pChan->txMixB)&&(pChan->txMixB!=TX_OUT_OFF))
+ {
+ if(pSps==NULL) pSps=pChan->spsTx=createPmrSps();
+ else pSps=pSps->nextSps=createPmrSps();
+
+ pChan->spsTxOutB=pSps;
+ pSps->parentChan=pChan;
+ if(pChan->txMixB==TX_OUT_COMPOSITE)
+ {
+ pSps->source=pChan->pTxComposite;
+ }
+ else if(pChan->txMixB==TX_OUT_LSD)
+ {
+ pSps->source=pChan->pTxLsdLpf;
+ // pChan->ptxCtcssAdjust=&pSps->inputGain;
+ }
+ else if(pChan->txMixB==TX_OUT_VOICE)
+ {
+ pSps->source=inputTmp;
+ }
+ else if(pChan->txMixB==TX_OUT_AUX)
+ {
+ pSps->source=pChan->pTxHpf;
+ }
+ else
+ {
+ pSps->source=NULL;
+ }
+
+ pSps->sink=pChan->pTxOut;
+ pSps->sigProc=pmr_gp_fir;
+ pSps->enabled=1;
+ pSps->numChanOut=2;
+ pSps->selChanOut=1;
+ pSps->mixOut=0;
+ pSps->nSamples=pChan->nSamplesTx;
+ pSps->interpolate=6;
+ pSps->ncoef=taps_fir_lpf_3K_1;
+ pSps->size_coef=2;
+ pSps->coef=(void*)coef_fir_lpf_3K_1;
+ pSps->nx=taps_fir_lpf_3K_1;
+ pSps->size_x=2;
+ pSps->x=(void*)(calloc(pSps->nx,pSps->size_x));
+ if(pSps==NULL)printf("Error: calloc(), createPmrChannel()\n");
+ pSps->calcAdjust=(gain_fir_lpf_3K_1);
+ pSps->inputGain=(1*M_Q8);
+ pSps->outputGain=(1*M_Q8);
+
+ }
+
+ pSps->nextSps=NULL;
+
+ #if XPMR_DEBUG0 == 1
+ {
+ t_tdet *ptdet;
+ TRACEX((" configure tracing\n"));
+
+ pChan->rxCtcss->pDebug0=calloc(numSamples,2);
+ pChan->rxCtcss->pDebug1=calloc(numSamples,2);
+ pChan->rxCtcss->pDebug2=calloc(numSamples,2);
+
+ for(i=0;i<CTCSS_NUM_CODES;i++){
+ ptdet=&(pChan->rxCtcss->tdet[i]);
+ ptdet->pDebug0=calloc(numSamples,2);
+ ptdet->pDebug1=calloc(numSamples,2);
+ ptdet->pDebug2=calloc(numSamples,2);
+ }
+
+ // buffer, 2 bytes per sample, and 16 channels
+ pChan->prxDebug=calloc(numSamples*16,2);
+ pChan->ptxDebug=calloc(numSamples*16,2);
+ }
+ #endif
+
+ TRACEX((" createPmrChannel() end\n"));
+
+ return pChan;
+}
+/*
+*/
+i16 destroyPmrChannel(t_pmr_chan *pChan)
+{
+ t_pmr_sps *pmr_sps, *tmp_sps;
+ i16 i;
+
+ TRACEX(("destroyPmrChannel()\n"));
+
+ free(pChan->pRxDemod);
+ free(pChan->pRxNoise);
+ free(pChan->pRxBase);
+ free(pChan->pRxHpf);
+ free(pChan->pRxLsd);
+ free(pChan->pRxSpeaker);
+ free(pChan->pRxDcTrack);
+ if(pChan->pRxLsdLimit)free(pChan->pRxLsdLimit);
+ free(pChan->pTxBase);
+ free(pChan->pTxHpf);
+ free(pChan->pTxPreEmp);
+ free(pChan->pTxLimiter);
+ free(pChan->pTxLsd);
+ free(pChan->pTxLsdLpf);
+ if(pChan->pTxComposite)free(pChan->pTxComposite);
+ free(pChan->pTxCode);
+ free(pChan->pTxOut);
+
+ if(pChan->pSigGen0)free(pChan->pSigGen0);
+ if(pChan->pSigGen1)free(pChan->pSigGen1);
+
+ #if XPMR_DEBUG0 == 1
+ free(pChan->pTxPttIn);
+ free(pChan->pTxPttOut);
+ if(pChan->prxDebug)free(pChan->prxDebug);
+ if(pChan->ptxDebug)free(pChan->ptxDebug);
+ free(pChan->rxCtcss->pDebug0);
+ free(pChan->rxCtcss->pDebug1);
+
+ free(pChan->prxDebug0);
+ free(pChan->prxDebug1);
+ free(pChan->prxDebug2);
+ free(pChan->prxDebug3);
+
+ free(pChan->ptxDebug0);
+ free(pChan->ptxDebug1);
+ free(pChan->ptxDebug2);
+ free(pChan->ptxDebug3);
+
+ for(i=0;i<CTCSS_NUM_CODES;i++)
+ {
+ free(pChan->rxCtcss->tdet[i].pDebug0);
+ free(pChan->rxCtcss->tdet[i].pDebug1);
+ free(pChan->rxCtcss->tdet[i].pDebug2);
+ }
+ #endif
+
+ free(pChan->pRxCtcss);
+
+ pmr_sps=pChan->spsRx;
+
+ while(pmr_sps)
+ {
+ tmp_sps = pmr_sps;
+ pmr_sps = tmp_sps->nextSps;
+ destroyPmrSps(tmp_sps);
+ }
+
+ free(pChan);
+
+ return 0;
+}
+/*
+*/
+t_pmr_sps *createPmrSps(void)
+{
+ t_pmr_sps *pSps;
+
+ TRACEX(("createPmrSps()\n"));
+
+ pSps = (t_pmr_sps *)calloc(sizeof(t_pmr_sps),1);
+
+ if(!pSps)printf("Error: createPmrSps()\n");
+
+ // pSps->x=calloc(pSps->nx,pSps->size_x);
+
+ return pSps;
+}
+/*
+*/
+i16 destroyPmrSps(t_pmr_sps *pSps)
+{
+ TRACEX(("destroyPmrSps(%i)\n",pSps->index));
+
+ if(pSps->x!=NULL)free(pSps->x);
+ free(pSps);
+ return 0;
+}
+/*
+ PmrRx does the whole buffer
+*/
+i16 PmrRx(t_pmr_chan *pChan, i16 *input, i16 *output)
+{
+ int i,ii;
+ t_pmr_sps *pmr_sps;
+
+ TRACEX(("PmrRx() %i\n",pChan->frameCountRx));
+
+ if(pChan==NULL){
+ printf("PmrRx() pChan == NULL\n");
+ return 1;
+ }
+
+ pChan->frameCountRx++;
+
+ pmr_sps=pChan->spsRx; // first sps
+ pmr_sps->source=input;
+
+ if(output!=NULL)pChan->spsRxOut->sink=output; //last sps
+
+ #if 0
+ if(pChan->inputBlanking>0)
+ {
+ pChan->inputBlanking-=pChan->nSamplesRx;
+ if(pChan->inputBlanking<0)pChan->inputBlanking=0;
+ for(i=0;i<pChan->nSamplesRx*6;i++)
+ input[i]=0;
+ }
+ #endif
+
+ // || (pChan->radioDuplex && (pChan->pttIn || pChan->pttOut)))
+ if(pChan->rxCpuSaver && !pChan->rxCarrierDetect)
+ {
+ if(pChan->spsRxHpf)pChan->spsRxHpf->enabled=0;
+ if(pChan->spsRxDeEmp)pChan->spsRxDeEmp->enabled=0;
+ }
+ else
+ {
+ if(pChan->spsRxHpf)pChan->spsRxHpf->enabled=1;
+ if(pChan->spsRxDeEmp)pChan->spsRxDeEmp->enabled=1;
+ }
+
+ i=0;
+ while(pmr_sps!=NULL && pmr_sps!=0)
+ {
+ TRACEX(("PmrRx() sps %i\n",i++));
+ pmr_sps->sigProc(pmr_sps);
+ pmr_sps = (t_pmr_sps *)(pmr_sps->nextSps);
+ //pmr_sps=NULL; // sph maw
+ }
+
+ #define XPMR_VOX_HANGTIME 2000
+
+ if(pChan->rxCdType==CD_XPMR_VOX)
+ {
+ if(pChan->spsRxVox->compOut)
+ {
+ pChan->rxVoxTimer=XPMR_VOX_HANGTIME; //VOX HangTime in ms
+ }
+ if(pChan->rxVoxTimer>0)
+ {
+ pChan->rxVoxTimer-=MS_PER_FRAME;
+ pChan->rxCarrierDetect=1;
+ }
+ else
+ {
+ pChan->rxVoxTimer=0;
+ pChan->rxCarrierDetect=0;
+ }
+ }
+ else
+ {
+ pChan->rxCarrierDetect=!pChan->spsRx->compOut;
+ }
+
+ if( !pChan->rxCpuSaver || pChan->rxCarrierDetect
+ || pChan->rxCtcss->decode!=-1) ctcss_detect(pChan);
+
+ #if XPMR_DEBUG0 == 1
+ // TRACEX(("Write file.\n"));
+ ii=0;
+ if(pChan->b.rxCapture)
+ {
+ for(i=0;i<pChan->nSamplesRx;i++)
+ {
+ pChan->prxDebug[ii++]=input[i*2*6]; // input data
+ pChan->prxDebug[ii++]=output[i]; // output data
+ pChan->prxDebug[ii++]=pChan->rxCarrierDetect*M_Q14; // carrier detect
+ if(pChan->rxCtcss)
+ pChan->prxDebug[ii++]=pChan->rxCtcss->decode*M_Q15/CTCSS_NUM_CODES; // decoded ctcss
+ else
+ pChan->prxDebug[ii++]=0;
+
+ pChan->prxDebug[ii++]=pChan->pRxNoise[i]; // rssi
+ pChan->prxDebug[ii++]=pChan->pRxBase[i]; // decimated, low pass filtered
+ pChan->prxDebug[ii++]=pChan->pRxHpf[i]; // output to network
+ pChan->prxDebug[ii++]=pChan->pRxSpeaker[i];
+
+ pChan->prxDebug[ii++]=pChan->pRxLsd[i]; // CTCSS Filtered
+ pChan->prxDebug[ii++]=pChan->pRxDcTrack[i]; // DC Restoration
+ pChan->prxDebug[ii++]=pChan->pRxLsdLimit[i]; // Amplitude Limited
+
+ //pChan->prxDebug[ii++]=pChan->rxCtcss->tdet[pChan->rxCtcss->testIndex+1].pDebug0[i]; // Upper Adjacent CTCSS Code
+ pChan->prxDebug[ii++]=pChan->rxCtcss->tdet[pChan->rxCtcss->testIndex].pDebug0[i]; // Primary CTCSS Code
+ pChan->prxDebug[ii++]=pChan->rxCtcss->tdet[pChan->rxCtcss->testIndex].pDebug1[i]; // dv/dt of decoder output
+ pChan->prxDebug[ii++]=pChan->rxCtcss->tdet[pChan->rxCtcss->testIndex].pDebug2[i];
+
+ //pChan->prxDebug[ii++]=pChan->rxCtcss->tdet[pChan->rxCtcss->testIndex-1].pDebug0[i]; // Lower Adjacent CTCSS Code
+
+ pChan->prxDebug[ii++]=pChan->prxDebug1[i]; // Measure Output for VOX
+ pChan->prxDebug[ii++]=pChan->prxDebug2[i]; // Measure Output for Tuning
+ }
+ }
+ #endif
+
+ return 0;
+}
+/*
+ PmrTx does the whole buffer
+*/
+i16 PmrTx(t_pmr_chan *pChan, i16 *input, i16 *output)
+{
+ int i, hit=0;
+ t_pmr_sps *pmr_sps;
+
+ pChan->frameCountTx++;
+
+ TRACEX(("PmrTx() %i\n",pChan->frameCountTx));
+
+ if(pChan==NULL){
+ printf("PmrTx() pChan == NULL\n");
+ return 1;
+ }
+
+ if(pChan->b.startSpecialTone)
+ {
+ pChan->b.startSpecialTone=0;
+ pChan->spsSigGen1->option=1;
+ pChan->spsSigGen1->enabled=1;
+ pChan->b.doingSpecialTone=1;
+ } else if(pChan->b.stopSpecialTone)
+ {
+ pChan->b.stopSpecialTone=0;
+ pChan->spsSigGen1->option=0;
+ pChan->b.doingSpecialTone=0;
+ pChan->spsSigGen1->enabled=0;
+ } else if(pChan->b.doingSpecialTone)
+ {
+ pChan->spsSigGen1->sink=output;
+ pChan->spsSigGen1->sigProc(pChan->spsSigGen1);
+ for(i=0;i<(pChan->nSamplesTx*2*6);i+=2)output[i+1]=output[i];
+ return 0;
+ }
+
+ // handle transmitter ptt input
+ hit=0;
+ if( pChan->txPttIn && pChan->txState==0)
+ {
+ pChan->txState = 2;
+ pChan->txPttOut=1;
+ pChan->spsSigGen0->freq=pChan->txCtcssFreq*10;
+ pChan->spsSigGen0->option=1;
+ pChan->spsSigGen0->enabled=1;
+ if(pChan->spsTxOutA)pChan->spsTxOutA->enabled=1;
+ if(pChan->spsTxOutB)pChan->spsTxOutB->enabled=1;
+ if(pChan->spsTxLsdLpf)pChan->spsTxLsdLpf->enabled=1;
+ TRACEX((" TxOn\n"));
+ }
+ else if(!pChan->txPttIn && pChan->txState==2)
+ {
+ if( pChan->txTocType==TOC_NONE || !pChan->txCtcssFreq )
+ {
+ hit=1;
+ TRACEX((" Tx Off Immediate.\n"));
+ }
+ else if(pChan->txCtcssFreq && pChan->txTocType==TOC_NOTONE)
+ {
+ pChan->txState=3;
+ pChan->txHangTime=TOC_NOTONE_TIME/MS_PER_FRAME;
+ pChan->spsSigGen0->option=3;
+ TRACEX((" Tx Turn Off No Tone Start.\n"));
+ }
+ else
+ {
+ pChan->txState=3;
+ pChan->txHangTime=0;
+ pChan->spsSigGen0->option=2;
+ TRACEX((" Tx Turn Off Phase Shift Start.\n"));
+ }
+ }
+ else if(pChan->txState==3)
+ {
+ if(pChan->txHangTime)
+ {
+ if(--pChan->txHangTime==0)hit=1;
+
+ }
+ else if(pChan->txHangTime<=0 && pChan->spsSigGen0->state==0)
+ {
+ hit=1;
+ TRACEX((" Tx Off TOC.\n"));
+ }
+ if(pChan->txPttIn)
+ {
+ TRACEX((" Tx Key During HangTime\n"));
+ if((pChan->txTocType==TOC_PHASE)||(pChan->txTocType==TOC_NONE))
+ {
+ pChan->txState = 2;
+ hit=0;
+ }
+ }
+ }
+
+ if( pChan->txCpuSaver && !hit && !pChan->txPttIn && !pChan->txPttOut && pChan->txState==0 ) return (1);
+
+ if(hit)
+ {
+ pChan->txPttOut=0;
+ pChan->txState=0;
+ if(pChan->spsTxLsdLpf)pChan->spsTxLsdLpf->option=3;
+ if(pChan->spsTxOutA)pChan->spsTxOutA->option=3;
+ if(pChan->spsTxOutB)pChan->spsTxOutB->option=3;
+ TRACEX((" Tx Off hit.\n"));
+ }
+
+ if(pChan->spsSigGen0)
+ {
+ pChan->spsSigGen0->sigProc(pChan->spsSigGen0);
+ pmr_sps=pChan->spsSigGen0->nextSps;
+ i=0;
+ while(pmr_sps!=NULL && pmr_sps!=0)
+ {
+ TRACEX((" PmrTx() subaudible sps %i\n",i++));
+ //printf(" CTCSS ENCODE %i %i\n",pChan->spsSigGen0->freq,pChan->spsSigGen0->outputGain);
+ pmr_sps->sigProc(pmr_sps);
+ pmr_sps = (t_pmr_sps *)(pmr_sps->nextSps);
+ }
+ }
+
+ if(pChan->spsSigGen1 && pChan->spsSigGen1->enabled)
+ {
+ pChan->spsSigGen1->sigProc(pChan->spsSigGen1);
+ }
+
+ // Do Voice
+ pmr_sps=pChan->spsTx;
+ if(!pChan->spsSigGen1->enabled)pmr_sps->source=input;
+ else input=pmr_sps->source;
+
+ if(output!=NULL)
+ {
+ if(pChan->spsTxOutA)pChan->spsTxOutA->sink=output;
+ if(pChan->spsTxOutB)pChan->spsTxOutB->sink=output;
+ }
+
+ i=0;
+ while(pmr_sps!=NULL && pmr_sps!=0)
+ {
+ TRACEX((" PmrTx() sps %i\n",i++));
+ pmr_sps->sigProc(pmr_sps);
+ pmr_sps = (t_pmr_sps *)(pmr_sps->nextSps);
+ }
+
+
+ if(pChan->txMixA==TX_OUT_OFF || !pChan->txPttOut){
+ for(i=0;i<pChan->nSamplesTx*2*6;i+=2)output[i]=0;
+ }
+
+ if(pChan->txMixB==TX_OUT_OFF || !pChan->txPttOut ){
+ for(i=0;i<pChan->nSamplesTx*2*6;i+=2)output[i+1]=0;
+ }
+
+ #if XPMR_DEBUG0 == 1
+ if(pChan->b.txCapture)
+ {
+ i16 ii=0;
+ for(i=0;i<pChan->nSamplesTx;i++)
+ {
+ pChan->ptxDebug[ii++]=input[i];
+ pChan->ptxDebug[ii++]=output[i*2*6];
+ pChan->ptxDebug[ii++]=output[(i*2*6)+1];
+ pChan->ptxDebug[ii++]=pChan->txPttIn*8192;
+
+ pChan->ptxDebug[ii++]=pChan->txPttOut*8192;
+ if(pChan->txHpfEnable)pChan->ptxDebug[ii++]=pChan->pTxHpf[i];
+ else pChan->ptxDebug[ii++]=0;
+ if(pChan->txPreEmpEnable)pChan->ptxDebug[ii++]=pChan->pTxPreEmp[i];
+ else pChan->ptxDebug[ii++]=0;
+ if(pChan->txLimiterEnable)pChan->ptxDebug[ii++]=pChan->pTxLimiter[i];
+ else pChan->ptxDebug[ii++]=0;
+
+ pChan->ptxDebug[ii++]=pChan->pTxLsd[i];
+ pChan->ptxDebug[ii++]=pChan->pTxLsdLpf[i];
+ pChan->ptxDebug[ii++]=pChan->pTxComposite[i];
+ pChan->ptxDebug[ii++]=pChan->pSigGen1[i];
+
+ #if 1
+ pChan->ptxDebug[ii++]=pChan->ptxDebug0[i];
+ pChan->ptxDebug[ii++]=pChan->ptxDebug1[i];
+ pChan->ptxDebug[ii++]=pChan->ptxDebug2[i];
+ pChan->ptxDebug[ii++]=pChan->ptxDebug3[i];
+ #else
+ pChan->ptxDebug[ii++]=0;
+ pChan->ptxDebug[ii++]=0;
+ pChan->ptxDebug[ii++]=0;
+ pChan->ptxDebug[ii++]=0;
+ #endif
+ }
+ }
+ #endif
+
+ return 0;
+}
+/* end of file */
diff --git a/trunk/channels/xpmr/xpmr.h b/trunk/channels/xpmr/xpmr.h
new file mode 100755
index 000000000..b39ce23d6
--- /dev/null
+++ b/trunk/channels/xpmr/xpmr.h
@@ -0,0 +1,543 @@
+/*
+ * xpmr.h - for Xelatec Private Mobile Radio Processes
+ *
+ * All Rights Reserved. Copyright (C)2007, Xelatec, LLC
+ *
+ * 20070808 1235 Steven Henke, W9SH, sph@xelatec.com
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Private Land Mobile Radio Channel Voice and Signaling Processor
+ *
+ * \author Steven Henke, W9SH <sph@xelatec.com> Xelatec, LLC
+ */
+
+#ifndef XPMR_H
+#define XPMR_H 1
+
+#ifdef CHAN_USBRADIO
+#define XPMR_DEBUG0 1
+#define XPMR_TRACE 0
+#else
+#define XPMR_DEBUG0 1
+#define XPMR_TRACE 1
+#endif
+
+#if(XPMR_TRACE == 1)
+#define TRACEX(a) {printf a;}
+#define TRACEXL(a) {printf("%s @ %u : ",__FILE__ ,__LINE__); printf a; }
+#define TRACEXT(a) { struct timeval hack; gettimeofday(&hack,NULL); printf("%ld.",hack.tv_sec%100000); printf("%i : ",(int)hack.tv_usec); printf a; }
+#else
+#define TRACEX(a)
+#define TRACEXL(a)
+#define TRACEXT(a)
+#endif
+
+#define i8 int8_t
+#define u8 u_int8_t
+#define i16 int16_t
+#define u16 u_int16_t
+#define i32 int32_t
+#define u32 u_int32_t
+#define i64 int64_t
+#define u64 u_int64_t
+
+#define M_Q24 0x01000000 //
+#define M_Q23 0x00800000 //
+#define M_Q22 0x00400000 //
+#define M_Q21 0x00200000 //
+#define M_Q20 0x00100000 // 1048576
+#define M_Q19 0x00080000 // 524288
+#define M_Q18 0x00040000 // 262144
+#define M_Q17 0x00020000 // 131072
+#define M_Q16 0x00010000 // 65536
+#define M_Q15 0x00008000 // 32768
+#define M_Q14 0x00004000 // 16384
+#define M_Q13 0x00002000 // 8182
+#define M_Q12 0x00001000 // 4096
+#define M_Q11 0x00000800 // 2048
+#define M_Q10 0x00000400 // 1024
+#define M_Q9 0x00000200 // 512
+#define M_Q8 0x00000100 // 256
+#define M_Q7 0x00000080 // 128
+#define M_Q6 0x00000040 // 64
+#define M_Q5 0x00000020 // 32
+#define M_Q4 0x00000010 // 16
+#define M_Q3 0x00000008 // 16
+#define M_Q2 0x00000004 // 16
+#define M_Q1 0x00000002 // 16
+#define M_Q0 0x00000001 // 16
+
+#define RADIANS_PER_CYCLE (2*M_PI)
+
+#define SAMPLE_RATE_INPUT 48000
+#define SAMPLE_RATE_NETWORK 8000
+
+#define SAMPLES_PER_BLOCK 160
+#define MS_PER_FRAME 20
+
+#define CTCSS_NUM_CODES 38
+#define CTCSS_SCOUNT_MUL 100
+#define CTCSS_INTEGRATE 3932 // 32767*.120 // 120/1000 // 0.120
+#define CTCSS_INPUT_LIMIT 1000
+#define CTCSS_DETECT_POINT 1989
+#define CTCSS_HYSTERSIS 200
+
+#define CTCSS_TURN_OFF_TIME 160 // ms
+#define CTCSS_TURN_OFF_SHIFT 240 // degrees
+#define TOC_NOTONE_TIME 600 // ms
+
+#ifndef CHAN_USBRADIO
+enum {RX_AUDIO_NONE,RX_AUDIO_SPEAKER,RX_AUDIO_FLAT};
+enum {TX_AUDIO_NONE,TX_AUDIO_FLAT,TX_AUDIO_FILTERED,TX_AUDIO_PROC};
+enum {CD_IGNORE,CD_XPMR_NOISE,CD_XPMR_VOX,CD_HID,CD_HID_INVERT};
+enum {SD_IGNORE,SD_HID,SD_HID_INVERT,SD_XPMR}; // no,external,externalinvert,software
+enum {RX_KEY_CARRIER,RX_KEY_CARRIER_CODE};
+enum {TX_OUT_OFF,TX_OUT_VOICE,TX_OUT_LSD,TX_OUT_COMPOSITE,TX_OUT_AUX};
+enum {TOC_NONE,TOC_PHASE,TOC_NOTONE};
+#endif
+
+/*
+ one structure for each ctcss tone to decode
+*/
+typedef struct
+{
+ i16 counter; // counter to next sample
+ i16 counterFactor; // full divisor used to increment counter
+ i16 binFactor;
+ i16 fudgeFactor;
+ i16 peak; // peak amplitude now maw sph now
+ i16 enabled;
+ i16 state; // dead, running, error
+ i16 zIndex; // z bucket index
+ i16 z[4]; // maw sph today
+ i16 zi;
+ i16 dvu;
+ i16 dvd;
+ i16 zd;
+ i16 setpt;
+ i16 hyst;
+ i16 decode;
+ i16 diffpeak;
+ i16 debug; // value held from last pass
+ i16 *pDebug0; // pointer to debug output
+ i16 *pDebug1; // pointer to debug output
+ i16 *pDebug2; // pointer to debug output
+
+} t_tdet;
+
+typedef struct
+{
+ i16 enabled; // if 0 none, 0xFFFF all tones, or single tone
+ i16 *input;
+ i16 clamplitude;
+ i16 center;
+ i16 decode; // current ctcss decode index
+ i32 BlankingTimer;
+ u32 TurnOffTimer;
+ t_tdet tdet[CTCSS_NUM_CODES];
+ i16 gain;
+ i16 limit;
+ i16 *pDebug0;
+ i16 *pDebug1;
+ i16 *pDebug2;
+ i16 testIndex;
+ i16 multiFreq;
+ i8 relax;
+
+} t_dec_ctcss;
+
+typedef struct
+{
+ i16 enabled; // if 0 none, 0xFFFF all tones, or single tone
+ i16 clamplitude;
+ i16 center;
+ i16 decode; // current ctcss decode value
+ i32 BlankingTimer;
+ u32 TurnOffTimer;
+ i16 gain;
+ i16 limit;
+ i16 *pDebug0;
+ i16 *pDebug1;
+ i16 rxPolarity;
+} t_dec_dcs;
+
+/*
+ Low Speed Data decoding both polarities
+*/
+typedef struct
+{
+ i16 counter; // counter to next sample
+ i16 synced;
+ u32 syncCorr[2];
+ u32 data[2];
+ i16 state; // disabled, enabled,
+ i16 decode;
+ i16 debug;
+
+ i16 polarity;
+ u32 frameNum;
+
+ u16 area;
+ u16 chan;
+ u16 home;
+ u16 id;
+ u16 free;
+
+ u16 crc;
+ i16 rssi;
+
+} t_decLsd;
+
+
+/* general purpose pmr signal processing element */
+
+struct t_pmr_chan;
+
+typedef struct t_pmr_sps
+{
+ i16 index; // unique to each instance
+
+ i16 enabled; // enabled/disabled
+
+ struct t_pmr_chan *parentChan;
+
+ i16 *source; // source buffer
+ i16 *sourceB; // source buffer B
+ i16 *sink; // sink buffer
+
+ i16 numChanOut; // allows output direct to interleaved buffer
+ i16 selChanOut;
+
+ u32 ticks;
+
+ void *buff; // this structure's internal buffer
+
+ i16 *debugBuff0; // debug buffer
+ i16 *debugBuff1; // debug buffer
+ i16 *debugBuff2; // debug buffer
+ i16 *debugBuff3; // debug buffer
+
+ i16 nSamples; // number of samples in the buffer
+
+ u32 buffSize; // buffer maximum index
+ u32 buffInIndex; // index to current input point
+ u32 buffOutIndex; // index to current output point
+ u32 buffLead; // lead of input over output through cb
+
+ i16 decimate; // decimation or interpolation factor (could be put in coef's)
+ i16 interpolate;
+ i16 decimator; // like the state this must be saved between calls (could be put in x's)
+
+ u32 sampleRate; // in Hz for elements in this structure
+ u32 freq; // in 0.1 Hz
+
+ i16 measPeak; // do measure Peak
+ i16 amax; // buffer amplitude maximum
+ i16 amin; // buffer amplitude minimum
+ i16 apeak; // buffer amplitude peak value (peak to peak)/2
+ i16 setpt; // amplitude set point for amplitude comparator
+ i16 hyst; // hysterysis for amplitude comparator
+ i16 compOut; // amplitude comparator output
+
+ i32 discounteru; // amplitude detector integrator discharge counter upper
+ i32 discounterl; // amplitude detector integrator discharge counter lower
+ i32 discfactor; // amplitude detector integrator discharge factor
+
+ i16 err; // error condition
+ i16 option; // option / request zero
+ i16 state; // stopped, start, stopped assumes zero'd
+
+ i16 cleared; // output buffer cleared
+
+ i16 delay;
+ i16 decode;
+
+ i32 inputGain; // apply to input data ? in Q7.8 format
+ i32 inputGainB; // apply to input data ? in Q7.8 format
+ i32 outputGain; // apply to output data ? in Q7.8 format
+ i16 mixOut;
+ i16 monoOut;
+
+ i16 filterType; // iir, fir, 1, 2, 3, 4 ...
+
+ i16 (*sigProc)(struct t_pmr_sps *sps); // function to call
+
+ i32 calcAdjust; // final adjustment
+ i16 nx; // number of x history elements
+ i16 ncoef; // number of coefficients
+ i16 size_x; // size of each x history element
+ i16 size_coef; // size of each coefficient
+ void *x; // history registers
+ void *x2; // history registers, 2nd bank
+ void *coef; // coefficients
+ void *coef2; // coefficients 2
+
+ void *nextSps; // next Sps function
+
+} t_pmr_sps;
+
+/*
+ pmr channel
+*/
+typedef struct t_pmr_chan
+{
+ i16 index; // which one
+ i16 enabled; // enabled/disabled
+ i16 status; // ok, error, busy, idle, initializing
+
+ i16 nSamplesRx; // max frame size
+ i16 nSamplesTx;
+
+ i32 inputSampleRate; // in S/s 48000
+ i32 baseSampleRate; // in S/s 8000
+
+ i16 inputGain;
+ i16 inputOffset;
+
+ u32 frameCountRx; // number processed
+ u32 frameCountTx;
+
+ i32 txHangTime;
+ i32 txTurnOff;
+
+ i16 rxDC; // average DC value of input
+ i16 rxSqSet; // carrier squelch threshold
+ i16 rxSqHyst; // carrier squelch hysterysis
+ i16 rxRssi; // current Rssi level
+ i16 rxQuality; // signal quality metric
+ i16 rxCarrierDetect; // carrier detect
+ i16 rxCdType;
+ i16 rxExtCarrierDetect;
+ i32 inputBlanking; // Tx pulse eliminator
+
+ i16 rxDemod; // see enum
+ i16 txMod; //
+
+ i16 rxNoiseSquelchEnable;
+ i16 rxHpfEnable;
+ i16 rxDeEmpEnable;
+ i16 rxCenterSlicerEnable;
+ i16 rxCtcssDecodeEnable;
+ i16 rxDcsDecodeEnable;
+ i16 rxDelayLineEnable;
+
+ i16 txHpfEnable;
+ i16 txLimiterEnable;
+ i16 txPreEmpEnable;
+ i16 txLpfEnable;
+
+ char radioDuplex;
+
+ struct {
+ unsigned pmrNoiseSquelch:1;
+ unsigned rxHpf:1;
+ unsigned txHpf:1;
+ unsigned txLpf:1;
+ unsigned rxDeEmphasis:1;
+ unsigned txPreEmphasis:1;
+ unsigned startSpecialTone:1;
+ unsigned stopSpecialTone:1;
+ unsigned doingSpecialTone:1;
+ unsigned extCarrierDetect:1;
+ unsigned txCapture:1;
+ unsigned rxCapture:1;
+ }b;
+
+ i16 dummy;
+
+ i32 txScramFreq;
+ i32 rxScramFreq;
+
+ i16 gainVoice;
+ i16 gainSubAudible;
+
+ i16 txMixA; // Off, Ctcss, Voice, Composite
+ i16 txMixB; // Off, Ctcss, Voice, Composite
+
+ i16 rxMuting;
+
+ i16 rxCpuSaver;
+ i16 txCpuSaver;
+
+ i8 rxSqMode; // 0 open, 1 carrier, 2 coded
+
+ i8 cdMethod;
+
+ i16 rxSquelchPoint;
+
+ i16 rxCarrierPoint;
+ i16 rxCarrierHyst;
+
+ i16 rxCtcssMap[CTCSS_NUM_CODES];
+
+ i16 txCtcssTocShift;
+ i16 txCtcssTocTime;
+ i8 txTocType;
+
+ float txCtcssFreq;
+ float rxCtcssFreq;
+ float rxInputGain;
+
+ i16 rxCtcssIndex;
+
+ i16 txPttIn; // from external request
+ i16 txPttOut; // to radio hardware
+
+ i16 bandwidth; // wide/narrow
+ i16 txCompand; // type
+ i16 rxCompand; //
+
+ i16 txEqRight; // muted, flat, pre-emp limited filtered
+ i16 txEqLeft;
+
+ i16 txPotRight; //
+ i16 txPotLeft; //
+
+ i16 rxPotRight; //
+ i16 rxPotLeft; //
+
+ i16 function;
+
+ i16 txState; // off,settling,on,hangtime,turnoff
+
+ t_pmr_sps *spsMeasure; // measurement block
+
+ t_pmr_sps *spsRx; // 1st signal processing struct
+ t_pmr_sps *spsRxLsd;
+ t_pmr_sps *spsRxDeEmp;
+ t_pmr_sps *spsRxHpf;
+ t_pmr_sps *spsRxVox;
+ t_pmr_sps *spsDelayLine; // Last signal processing struct
+ t_pmr_sps *spsRxOut; // Last signal processing struct
+
+ t_pmr_sps *spsTx; // 1st signal processing struct
+
+ t_pmr_sps *spsTxLsdLpf;
+ t_pmr_sps *spsTxOutA; // Last signal processing struct
+
+ t_pmr_sps *spsTxOutB; // Last signal processing struct
+
+ t_pmr_sps *spsSigGen0; // ctcss
+ t_pmr_sps *spsSigGen1; // test and other tones
+
+ // tune tweaks
+
+ i32 rxVoxTimer; // Vox Hang Timer
+
+ i16 *prxSquelchAdjust;
+
+ // i16 *prxNoiseMeasure; // for autotune
+ // i32 *prxNoiseAdjust;
+
+ i16 *prxVoiceMeasure;
+ i32 *prxVoiceAdjust;
+
+ i16 *prxCtcssMeasure;
+ i32 *prxCtcssAdjust;
+
+ i16 *ptxVoiceAdjust; // from calling application
+ i32 *ptxCtcssAdjust; // from calling application
+
+ i32 *ptxLimiterAdjust; // from calling application
+
+ i16 *pRxDemod; // buffers
+ i16 *pRxBase; // decimated lpf input
+ i16 *pRxNoise;
+ i16 *pRxLsd; // subaudible only
+ i16 *pRxHpf; // subaudible removed
+ i16 *pRxDeEmp; // EIA Audio
+ i16 *pRxSpeaker; // EIA Audio
+ i16 *pRxDcTrack; // DC Restored LSD
+ i16 *pRxLsdLimit; // LSD Limited
+ i16 *pRxCtcss; //
+ i16 *pRxSquelch;
+
+ i16 *pTxBase; // input data
+ i16 *pTxHpf;
+ i16 *pTxPreEmp;
+ i16 *pTxLimiter;
+ i16 *pTxLsd;
+ i16 *pTxLsdLpf;
+ i16 *pTxComposite;
+ i16 *pTxMod; // upsampled, low pass filtered
+
+ i16 *pTxOut; //
+
+ i16 *pTxPttIn;
+ i16 *pTxPttOut;
+ i16 *pTxHang;
+ i16 *pTxCode;
+
+ i16 *pSigGen0;
+ i16 *pSigGen1;
+
+ i16 *pAlt0;
+ i16 *pAlt1;
+
+ i16 *pNull;
+
+ i16 *prxDebug; // consolidated debug buffer
+ i16 *ptxDebug; // consolidated debug buffer
+
+ i16 *prxDebug0;
+ i16 *prxDebug1;
+ i16 *prxDebug2;
+ i16 *prxDebug3;
+
+ i16 *ptxDebug0;
+ i16 *ptxDebug1;
+ i16 *ptxDebug2;
+ i16 *ptxDebug3;
+
+ t_dec_ctcss *rxCtcss;
+
+ i16 clamplitudeDcs;
+ i16 centerDcs;
+ u32 dcsBlankingTimer;
+ i16 dcsDecode; // current dcs decode value
+
+ i16 clamplitudeLsd;
+ i16 centerLsd;
+ t_decLsd decLsd[2]; // for both polarities
+
+} t_pmr_chan;
+
+static i16 TxTestTone(t_pmr_chan *pChan, i16 function);
+
+t_pmr_chan *createPmrChannel(t_pmr_chan *tChan, i16 numSamples);
+t_pmr_sps *createPmrSps(void);
+i16 destroyPmrChannel(t_pmr_chan *pChan);
+i16 destroyPmrSps(t_pmr_sps *pSps);
+i16 pmr_rx_frontend(t_pmr_sps *mySps);
+i16 pmr_gp_fir(t_pmr_sps *mySps);
+i16 pmr_gp_iir(t_pmr_sps *mySps);
+i16 gp_inte_00(t_pmr_sps *mySps);
+i16 gp_diff(t_pmr_sps *mySps);
+i16 CenterSlicer(t_pmr_sps *mySps);
+i16 ctcss_detect(t_pmr_chan *pmrChan);
+i16 SoftLimiter(t_pmr_sps *mySps);
+i16 SigGen(t_pmr_sps *mySps);
+i16 pmrMixer(t_pmr_sps *mySps);
+i16 DelayLine(t_pmr_sps *mySps);
+i16 PmrRx(t_pmr_chan *PmrChan, i16 *input, i16 *output);
+i16 PmrTx(t_pmr_chan *PmrChan, i16 *input, i16 *output);
+i16 CtcssFreqIndex(float freq);
+i16 MeasureBlock(t_pmr_sps *mySps);
+#endif /* ! XPMR_H */
+
+/* end of file */
+
+
+
diff --git a/trunk/channels/xpmr/xpmr_coef.h b/trunk/channels/xpmr/xpmr_coef.h
new file mode 100755
index 000000000..fda6762da
--- /dev/null
+++ b/trunk/channels/xpmr/xpmr_coef.h
@@ -0,0 +1,951 @@
+/*
+ * xpmr_coef.h - for Xelatec Private Mobile Radio Processes
+ *
+ * All Rights Reserved. Copyright (C)2007, Xelatec, LLC
+ *
+ * 20070808 1235 Steven Henke, W9SH, sph@xelatec.com
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Private Land Mobile Radio Channel Voice and Signaling Processor
+ *
+ * \author Steven Henke, W9SH <sph@xelatec.com> Xelatec, LLC
+ */
+
+#ifndef XPMR_COEF_H
+#define XMPR_COEF_H 1
+
+// frequencies in 0.1 Hz
+const u32 dtmf_row[] =
+{
+ 6970, 7700, 8520, 9410
+};
+const u32 dtmf_col[] =
+{
+ 12090, 13360, 14770, 16330
+};
+
+const i16 coef_dcs_rx = 1488; // dcs rx data divisor for oversampling 8000/134.4
+const i16 coef_dcs_tx = 5952; // dcs tx data divisor
+
+const i16 coef_lsd_div = 672; // low speed data divisor
+const u32 coef_lsd_sync = 0x158; // 000101011000
+const u32 coef_lsd_sync_pattern[] = {0x0000000F, 0x0F0FF000};
+
+#define CTCSS_COEF_INT 120
+#define CTCSS_SAMPLE_RATE 8000
+#define TDIV(x) ((CTCSS_SAMPLE_RATE*1000/x)+5)/10
+
+i32 coef_ctcss[4][5]=
+{
+ // freq, divisor, integrator, filter
+ {770,TDIV(770),CTCSS_COEF_INT,0,0},
+ {1000,TDIV(1000),CTCSS_COEF_INT,0,0},
+ {1035,TDIV(1035),CTCSS_COEF_INT,0,0},
+ {0,0,0,0}
+};
+
+
+i16 coef_ctcss_div[]=
+{
+2985, // 00 067.0
+2782, // 01 071.9
+2688, // 02 074.4
+2597, // 03 077.0
+2509, // 04 079.7
+2424, // 05 082.5
+2342, // 06 085.4
+2260, // 07 088.5
+2186, // 08 091.5
+2110, // 09 094.8
+2053, // 10 097.4
+2000, // 11 100.0
+1932, // 12 103.5
+1866, // 13 107.2
+1803, // 14 110.9
+1742, // 15 114.8
+1684, // 16 118.8
+1626, // 17 123.0
+1571, // 18 127.3
+1517, // 19 131.8
+1465, // 20 136.5
+1415, // 21 141.3
+1368, // 22 146.2
+1321, // 23 151.4
+1276, // 24 156.7
+1233, // 25 162.2
+1191, // 26 167.9
+1151, // 27 173.8
+1112, // 28 179.9
+1074, // 29 186.2
+1037, // 30 192.8
+983, // 31 203.5
+949, // 32 210.7
+917, // 33 218.1
+886, // 34 225.7
+856, // 35 233.6
+827, // 36 241.8
+799 // 37 250.3
+};
+
+float freq_ctcss[]=
+{
+067.0, // 00
+071.9, // 01
+074.4, // 02
+077.0, // 03
+079.7, // 04
+082.5, // 05
+085.4, // 06
+088.5, // 07
+091.5, // 08
+094.8, // 09
+097.4, // 10
+100.0, // 11
+103.5, // 12
+107.2, // 13
+110.9, // 14
+114.8, // 15
+118.8, // 16
+123.0, // 17
+127.3, // 18
+131.8, // 19
+136.5, // 20
+141.3, // 21
+146.2, // 22
+151.4, // 23
+156.7, // 24
+162.2, // 25
+167.9, // 26
+173.8, // 27
+179.9, // 28
+186.2, // 29
+192.8, // 30
+203.5, // 31
+210.7 , // 32
+218.1 , // 33
+225.7 , // 34
+233.6 , // 35
+241.8 , // 36
+250.3 // 37
+};
+
+/*
+ noise squelch carrier detect filter
+*/
+static const int16_t taps_fir_bpf_noise_1 = 66;
+static const int32_t gain_fir_bpf_noise_1 = 65536;
+static const int16_t coef_fir_bpf_noise_1[] = {
+ 139,
+ -182,
+ -269,
+ -66,
+ 56,
+ 59,
+ 250,
+ 395,
+ -80,
+ -775,
+ -557,
+ 437,
+ 779,
+ 210,
+ -17,
+ 123,
+ -692,
+ -1664,
+ -256,
+ 2495,
+ 2237,
+ -1018,
+ -2133,
+ -478,
+ -1134,
+ -2711,
+ 2642,
+ 10453,
+ 4010,
+ -14385,
+ -16488,
+ 6954,
+ 23030,
+ 6954,
+ -16488,
+ -14385,
+ 4010,
+ 10453,
+ 2642,
+ -2711,
+ -1134,
+ -478,
+ -2133,
+ -1018,
+ 2237,
+ 2495,
+ -256,
+ -1664,
+ -692,
+ 123,
+ -17,
+ 210,
+ 779,
+ 437,
+ -557,
+ -775,
+ -80,
+ 395,
+ 250,
+ 59,
+ 56,
+ -66,
+ -269,
+ -182,
+ 139,
+ 257
+};
+/*
+ tbd
+*/
+static const int16_t taps_fir_lpf_3K_1 = 66;
+static const int32_t gain_fir_lpf_3K_1 = 131072;
+static const int16_t coef_fir_lpf_3K_1[] = {
+ 259,
+ 58,
+ -185,
+ -437,
+ -654,
+ -793,
+ -815,
+ -696,
+ -434,
+ -48,
+ 414,
+ 886,
+ 1284,
+ 1523,
+ 1529,
+ 1254,
+ 691,
+ -117,
+ -1078,
+ -2049,
+ -2854,
+ -3303,
+ -3220,
+ -2472,
+ -995,
+ 1187,
+ 3952,
+ 7086,
+ 10300,
+ 13270,
+ 15672,
+ 17236,
+ 17778,
+ 17236,
+ 15672,
+ 13270,
+ 10300,
+ 7086,
+ 3952,
+ 1187,
+ -995,
+ -2472,
+ -3220,
+ -3303,
+ -2854,
+ -2049,
+ -1078,
+ -117,
+ 691,
+ 1254,
+ 1529,
+ 1523,
+ 1284,
+ 886,
+ 414,
+ -48,
+ -434,
+ -696,
+ -815,
+ -793,
+ -654,
+ -437,
+ -185,
+ 58,
+ 259,
+ 393
+};
+
+/**************************************************************
+Filter type: Low Pass
+Filter model: Butterworth
+Filter order: 9
+Sampling Frequency: 8 KHz
+Cut Frequency: 0.250000 KHz
+Coefficents Quantization: 16-bit
+***************************************************************/
+static const int16_t taps_fir_lpf_250_11_64 = 64;
+static const int32_t gain_fir_lpf_250_11_64 = 262144;
+static const int16_t coef_fir_lpf_250_11_64[] =
+{
+ 366,
+ -3,
+ -418,
+ -865,
+ -1328,
+ -1788,
+ -2223,
+ -2609,
+ -2922,
+ -3138,
+ -3232,
+ -3181,
+ -2967,
+ -2573,
+ -1988,
+ -1206,
+ -228,
+ 937,
+ 2277,
+ 3767,
+ 5379,
+ 7077,
+ 8821,
+ 10564,
+ 12259,
+ 13855,
+ 15305,
+ 16563,
+ 17588,
+ 18346,
+ 18812,
+ 18968,
+ 18812,
+ 18346,
+ 17588,
+ 16563,
+ 15305,
+ 13855,
+ 12259,
+ 10564,
+ 8821,
+ 7077,
+ 5379,
+ 3767,
+ 2277,
+ 937,
+ -228,
+ -1206,
+ -1988,
+ -2573,
+ -2967,
+ -3181,
+ -3232,
+ -3138,
+ -2922,
+ -2609,
+ -2223,
+ -1788,
+ -1328,
+ -865,
+ -418,
+ -3,
+ 366,
+ 680
+};
+
+// de-emphasis integrator 300 Hz with 8KS/s
+// a0, b1
+static const int16_t taps_int_lpf_300_1_2 = 2;
+static const int32_t gain_int_lpf_300_1_2 = 8182;
+static const int16_t coef_int_lpf_300_1_2[]={
+6878,
+25889
+};
+
+// pre-emphasis differentiator 4000 Hz with 8KS/s
+// a0,a1,b0,
+static const int16_t taps_int_hpf_4000_1_2 = 2;
+static const int32_t gain_int_hpf_4000_1_2 = 16384;
+static const int16_t coef_int_hpf_4000_1_2[]={
+17610,
+-17610,
+2454
+};
+
+
+/*
+ ltr crc table
+ from http://www.radioreference.com/forums/showthread.php?t=24126
+*/
+
+static const u8 ltr_table[]=
+{
+0x38, // 00 Area 0111000
+0x1c, // 01 Channel 4 0011100
+0x0e, // 02 Channel 3 0001110
+0x46, // 03 Channel 2 1000110
+0x23, // 04 Channel 1 0100011
+0x51, // 05 Channel 0 1010001
+0x68, // 06 Home 4 1101000
+0x75, // 07 Home 3 1110101
+0x7a, // 08 Home 2 1111010
+0x3d, // 09 Home 1 0111101
+0x1f, // 10 Home 0 0011111
+0x4f, // 11 Group 7 1001111
+0x26, // 12 Group 6 0100110
+0x52, // 13 Group 5 1010010
+0x29, // 14 Group 4 0101001
+0x15, // 15 Group 3 0010101
+0x0d, // 16 Group 2 0001101
+0x45, // 17 Group 1 1000101
+0x62, // 18 Group 0 1100010
+0x31, // 19 Free 4 0110001
+0x19, // 20 Free 3 0011001
+0x0d, // 21 Free 2 0001101
+0x07, // 22 Free 1 0000111
+0x43 // 23 Free 0 1000011
+};
+
+static const i16 bitWeight[]=
+{
+0, // 0
+1, // 1
+1, // 2
+2, // 3
+1, // 4
+2, // 5
+2, // 6
+3, // 7
+1, // 8
+2, // 9
+2, // 10
+3, // 11
+2, // 12
+3, // 13
+3, // 14
+4, // 15
+1, // 16
+2, // 17
+2, // 18
+3, // 19
+2, // 20
+3, // 21
+3, // 22
+4, // 23
+2, // 24
+3, // 25
+3, // 26
+4, // 27
+3, // 28
+4, // 29
+4, // 30
+5, // 31
+1, // 32
+2, // 33
+2, // 34
+3, // 35
+2, // 36
+3, // 37
+3, // 38
+4, // 39
+2, // 40
+3, // 41
+3, // 42
+4, // 43
+3, // 44
+4, // 45
+4, // 46
+5, // 47
+2, // 48
+3, // 49
+3, // 50
+4, // 51
+3, // 52
+4, // 53
+4, // 54
+5, // 55
+3, // 56
+4, // 57
+4, // 58
+5, // 59
+4, // 60
+5, // 61
+5, // 62
+6, // 63
+1, // 64
+2, // 65
+2, // 66
+3, // 67
+2, // 68
+3, // 69
+3, // 70
+4, // 71
+2, // 72
+3, // 73
+3, // 74
+4, // 75
+3, // 76
+4, // 77
+4, // 78
+5, // 79
+2, // 80
+3, // 81
+3, // 82
+4, // 83
+3, // 84
+4, // 85
+4, // 86
+5, // 87
+3, // 88
+4, // 89
+4, // 90
+5, // 91
+4, // 92
+5, // 93
+5, // 94
+6, // 95
+2, // 96
+3, // 97
+3, // 98
+4, // 99
+3, // 100
+4, // 101
+4, // 102
+5, // 103
+3, // 104
+4, // 105
+4, // 106
+5, // 107
+4, // 108
+5, // 109
+5, // 110
+6, // 111
+3, // 112
+4, // 113
+4, // 114
+5, // 115
+4, // 116
+5, // 117
+5, // 118
+6, // 119
+4, // 120
+5, // 121
+5, // 122
+6, // 123
+5, // 124
+6, // 125
+6, // 126
+7, // 127
+1, // 128
+2, // 129
+2, // 130
+3, // 131
+2, // 132
+3, // 133
+3, // 134
+4, // 135
+2, // 136
+3, // 137
+3, // 138
+4, // 139
+3, // 140
+4, // 141
+4, // 142
+5, // 143
+2, // 144
+3, // 145
+3, // 146
+4, // 147
+3, // 148
+4, // 149
+4, // 150
+5, // 151
+3, // 152
+4, // 153
+4, // 154
+5, // 155
+4, // 156
+5, // 157
+5, // 158
+6, // 159
+2, // 160
+3, // 161
+3, // 162
+4, // 163
+3, // 164
+4, // 165
+4, // 166
+5, // 167
+3, // 168
+4, // 169
+4, // 170
+5, // 171
+4, // 172
+5, // 173
+5, // 174
+6, // 175
+3, // 176
+4, // 177
+4, // 178
+5, // 179
+4, // 180
+5, // 181
+5, // 182
+6, // 183
+4, // 184
+5, // 185
+5, // 186
+6, // 187
+5, // 188
+6, // 189
+6, // 190
+7, // 191
+2, // 192
+3, // 193
+3, // 194
+4, // 195
+3, // 196
+4, // 197
+4, // 198
+5, // 199
+3, // 200
+4, // 201
+4, // 202
+5, // 203
+4, // 204
+5, // 205
+5, // 206
+6, // 207
+3, // 208
+4, // 209
+4, // 210
+5, // 211
+4, // 212
+5, // 213
+5, // 214
+6, // 215
+4, // 216
+5, // 217
+5, // 218
+6, // 219
+5, // 220
+6, // 221
+6, // 222
+7, // 223
+3, // 224
+4, // 225
+4, // 226
+5, // 227
+4, // 228
+5, // 229
+5, // 230
+6, // 231
+4, // 232
+5, // 233
+5, // 234
+6, // 235
+5, // 236
+6, // 237
+6, // 238
+7, // 239
+4, // 240
+5, // 241
+5, // 242
+6, // 243
+5, // 244
+6, // 245
+6, // 246
+7, // 247
+5, // 248
+6, // 249
+6, // 250
+7, // 251
+6, // 252
+7, // 253
+7, // 254
+8 // 255
+};
+
+
+/*
+ ctcss decode filter
+*/
+/**************************************************************
+Filter type: Low Pass
+Filter model: Butterworth
+Filter order: 9
+Sampling Frequency: 8 KHz
+Cut Frequency: 0.250000 KHz
+Coefficents Quantization: 16-bit
+***************************************************************/
+static const int16_t taps_fir_lpf_250_9_66 = 66;
+static const int32_t gain_fir_lpf_250_9_66 = 262144;
+static const int16_t coef_fir_lpf_250_9_66[] =
+{
+ 676,
+ 364,
+ -3,
+ -415,
+ -860,
+-1320,
+-1777,
+-2209,
+-2593,
+-2904,
+-3119,
+-3212,
+-3162,
+-2949,
+-2557,
+-1975,
+-1198,
+ -226,
+ 932,
+ 2263,
+ 3744,
+ 5346,
+ 7034,
+ 8767,
+10499,
+12184,
+13770,
+15211,
+16462,
+17480,
+18234,
+18696,
+18852,
+18696,
+18234,
+17480,
+16462,
+15211,
+13770,
+12184,
+10499,
+ 8767,
+ 7034,
+ 5346,
+ 3744,
+ 2263,
+ 932,
+ -226,
+-1198,
+-1975,
+-2557,
+-2949,
+-3162,
+-3212,
+-3119,
+-2904,
+-2593,
+-2209,
+-1777,
+-1320,
+ -860,
+ -415,
+ -3,
+ 364,
+ 676,
+ 927
+};
+/* *************************************************************
+Filter type: Low Pass
+Filter model: Butterworth
+Filter order: 9
+Sampling Frequency: 8 KHz
+Cut Frequency: 0.215 KHz
+Coefficents Quantization: 16-bit
+***************************************************************/
+static const int16_t taps_fir_lpf_215_9_88 = 88;
+static const int32_t gain_fir_lpf_215_9_88 = 524288;
+static const int16_t coef_fir_lpf_215_9_88[] = {
+ 2038,
+ 2049,
+ 1991,
+ 1859,
+ 1650,
+ 1363,
+ 999,
+ 562,
+ 58,
+ -502,
+-1106,
+-1739,
+-2382,
+-3014,
+-3612,
+-4153,
+-4610,
+-4959,
+-5172,
+-5226,
+-5098,
+-4769,
+-4222,
+-3444,
+-2430,
+-1176,
+ 310,
+ 2021,
+ 3937,
+ 6035,
+ 8284,
+10648,
+13086,
+15550,
+17993,
+20363,
+22608,
+24677,
+26522,
+28099,
+29369,
+30299,
+30867,
+31058,
+30867,
+30299,
+29369,
+28099,
+26522,
+24677,
+22608,
+20363,
+17993,
+15550,
+13086,
+10648,
+ 8284,
+ 6035,
+ 3937,
+ 2021,
+ 310,
+-1176,
+-2430,
+-3444,
+-4222,
+-4769,
+-5098,
+-5226,
+-5172,
+-4959,
+-4610,
+-4153,
+-3612,
+-3014,
+-2382,
+-1739,
+-1106,
+ -502,
+ 58,
+ 562,
+ 999,
+ 1363,
+ 1650,
+ 1859,
+ 1991,
+ 2049,
+ 2038,
+ 1966
+};
+// end coef fir_lpf_215_9_88
+//
+/**************************************************************
+Filter type: High Pass
+Filter model: Butterworth
+Filter order: 9
+Sampling Frequency: 8 KHz
+Cut Frequency: 0.300000 KHz
+Coefficents Quantization: 16-bit
+***************************************************************/
+static const int16_t taps_fir_hpf_300_9_66 = 66;
+static const int32_t gain_fir_hpf_300_9_66 = 32768;
+static const int16_t coef_fir_hpf_300_9_66[] =
+{
+ -141,
+ -114,
+ -77,
+ -30,
+ 23,
+ 83,
+ 147,
+ 210,
+ 271,
+ 324,
+ 367,
+ 396,
+ 407,
+ 396,
+ 362,
+ 302,
+ 216,
+ 102,
+ -36,
+ -199,
+ -383,
+ -585,
+ -798,
+-1017,
+-1237,
+-1452,
+-1653,
+-1836,
+-1995,
+-2124,
+-2219,
+-2278,
+30463,
+-2278,
+-2219,
+-2124,
+-1995,
+-1836,
+-1653,
+-1452,
+-1237,
+-1017,
+ -798,
+ -585,
+ -383,
+ -199,
+ -36,
+ 102,
+ 216,
+ 302,
+ 362,
+ 396,
+ 407,
+ 396,
+ 367,
+ 324,
+ 271,
+ 210,
+ 147,
+ 83,
+ 23,
+ -30,
+ -77,
+ -114,
+ -141,
+ -158
+ };
+#endif /* !XPMR_COEF_H */
+/* end of file */
+
+
+
+