aboutsummaryrefslogtreecommitdiffstats
path: root/1.4.23-rc4/channels
diff options
context:
space:
mode:
Diffstat (limited to '1.4.23-rc4/channels')
-rw-r--r--1.4.23-rc4/channels/DialTone.h252
-rw-r--r--1.4.23-rc4/channels/Makefile123
-rw-r--r--1.4.23-rc4/channels/answer.h237
-rw-r--r--1.4.23-rc4/channels/busy_tone.h55
-rw-r--r--1.4.23-rc4/channels/chan_agent.c2870
-rw-r--r--1.4.23-rc4/channels/chan_alsa.c1391
-rw-r--r--1.4.23-rc4/channels/chan_dahdi.c11992
-rw-r--r--1.4.23-rc4/channels/chan_features.c580
-rw-r--r--1.4.23-rc4/channels/chan_gtalk.c2067
-rw-r--r--1.4.23-rc4/channels/chan_h323.c3274
-rw-r--r--1.4.23-rc4/channels/chan_iax2.c11336
-rw-r--r--1.4.23-rc4/channels/chan_local.c808
-rw-r--r--1.4.23-rc4/channels/chan_mgcp.c4430
-rw-r--r--1.4.23-rc4/channels/chan_misdn.c5775
-rw-r--r--1.4.23-rc4/channels/chan_nbs.c302
-rw-r--r--1.4.23-rc4/channels/chan_oss.c1899
-rw-r--r--1.4.23-rc4/channels/chan_phone.c1433
-rw-r--r--1.4.23-rc4/channels/chan_sip.c18893
-rw-r--r--1.4.23-rc4/channels/chan_skinny.c5016
-rw-r--r--1.4.23-rc4/channels/chan_vpb.cc2905
-rw-r--r--1.4.23-rc4/channels/gentone-ulaw.c157
-rw-r--r--1.4.23-rc4/channels/gentone.c95
-rw-r--r--1.4.23-rc4/channels/h323/ChangeLog43
-rw-r--r--1.4.23-rc4/channels/h323/INSTALL.openh32318
-rw-r--r--1.4.23-rc4/channels/h323/Makefile.in48
-rw-r--r--1.4.23-rc4/channels/h323/README144
-rw-r--r--1.4.23-rc4/channels/h323/TODO9
-rw-r--r--1.4.23-rc4/channels/h323/ast_h323.cxx2487
-rw-r--r--1.4.23-rc4/channels/h323/ast_h323.h166
-rw-r--r--1.4.23-rc4/channels/h323/caps_h323.cxx239
-rw-r--r--1.4.23-rc4/channels/h323/caps_h323.h124
-rw-r--r--1.4.23-rc4/channels/h323/chan_h323.h254
-rw-r--r--1.4.23-rc4/channels/h323/cisco-h225.asn74
-rw-r--r--1.4.23-rc4/channels/h323/cisco-h225.cxx853
-rw-r--r--1.4.23-rc4/channels/h323/cisco-h225.h299
-rw-r--r--1.4.23-rc4/channels/h323/compat_h323.cxx138
-rw-r--r--1.4.23-rc4/channels/h323/compat_h323.h80
-rw-r--r--1.4.23-rc4/channels/h323/noexport.map5
-rw-r--r--1.4.23-rc4/channels/iax2-parser.c1053
-rw-r--r--1.4.23-rc4/channels/iax2-parser.h160
-rw-r--r--1.4.23-rc4/channels/iax2-provision.c540
-rw-r--r--1.4.23-rc4/channels/iax2-provision.h53
-rw-r--r--1.4.23-rc4/channels/iax2.h237
-rw-r--r--1.4.23-rc4/channels/misdn/Makefile17
-rw-r--r--1.4.23-rc4/channels/misdn/chan_misdn_config.h152
-rw-r--r--1.4.23-rc4/channels/misdn/ie.c1422
-rw-r--r--1.4.23-rc4/channels/misdn/isdn_lib.c4661
-rw-r--r--1.4.23-rc4/channels/misdn/isdn_lib.h674
-rw-r--r--1.4.23-rc4/channels/misdn/isdn_lib_intern.h142
-rw-r--r--1.4.23-rc4/channels/misdn/isdn_msg_parser.c1348
-rw-r--r--1.4.23-rc4/channels/misdn/portinfo.c198
-rw-r--r--1.4.23-rc4/channels/misdn_config.c1160
-rw-r--r--1.4.23-rc4/channels/ring10.h1752
-rw-r--r--1.4.23-rc4/channels/ring_tone.h30
54 files changed, 94470 insertions, 0 deletions
diff --git a/1.4.23-rc4/channels/DialTone.h b/1.4.23-rc4/channels/DialTone.h
new file mode 100644
index 000000000..098ed44d7
--- /dev/null
+++ b/1.4.23-rc4/channels/DialTone.h
@@ -0,0 +1,252 @@
+/*
+ * 8-bit raw data
+ *
+ * Source: DialTone.ulaw
+ *
+ * Copyright (C) 1999, Mark Spencer
+ *
+ * Distributed under the terms of the GNU General Public License
+ *
+ */
+
+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/1.4.23-rc4/channels/Makefile b/1.4.23-rc4/channels/Makefile
new file mode 100644
index 000000000..8f067f6b0
--- /dev/null
+++ b/1.4.23-rc4/channels/Makefile
@@ -0,0 +1,123 @@
+#
+# 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 ../menuselect.makeopts ../menuselect.makedeps
+
+MENUSELECT_CATEGORY=CHANNELS
+MENUSELECT_DESCRIPTION=Channel Drivers
+
+ALL_C_MODS:=$(patsubst %.c,%,$(wildcard chan_*.c))
+ALL_CC_MODS:=$(patsubst %.cc,%,$(wildcard chan_*.cc))
+
+C_MODS:=$(filter-out $(MENUSELECT_CHANNELS),$(ALL_C_MODS))
+CC_MODS:=$(filter-out $(MENUSELECT_CHANNELS),$(ALL_CC_MODS))
+
+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),)
+ CC_MODS:=$(filter-out chan_h323,$(CC_MODS))
+endif
+
+ifndef OPENH323DIR
+ OPENH323DIR=$(HOME)/openh323
+endif
+
+ifndef PWLIBDIR
+ PWLIBDIR=$(HOME)/pwlib
+endif
+
+LOADABLE_MODS:=$(C_MODS) $(CC_MODS)
+
+ifneq ($(findstring channels,$(MENUSELECT_EMBED)),)
+ EMBEDDED_MODS:=$(LOADABLE_MODS)
+ LOADABLE_MODS:=
+endif
+
+all: _all
+
+include $(ASTTOPDIR)/Makefile.moddir_rules
+
+clean::
+ rm -f gentone
+ $(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
+
+gentone: gentone.c
+ $(ECHO_PREFIX) echo " [LD] $^ -> $@"
+ $(CMD_PREFIX) $(HOST_CC) $(STATIC_BUILD) -o $@ $(HOST_CFLAGS) $(HOST_LDFLAGS) $^ $(LIBS)
+gentone: LIBS+=-lm
+
+busy_tone.h:
+ ./gentone busy_tone 480 620
+
+ring_tone.h:
+ ./gentone ring_tone 440 480
+
+$(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
+
+misdn_config.o misdn/isdn_lib.o misdn/isdn_msg_parser.o: ASTCFLAGS+=$(MENUSELECT_OPTS_chan_misdn:%=-D%) $(foreach dep,$(MENUSELECT_DEPENDS_chan_misdn),$(value $(dep)_INCLUDE))
+
+$(if $(filter chan_misdn,$(EMBEDDED_MODS)),modules.link,chan_misdn.so): chan_misdn.o misdn_config.o misdn/isdn_lib.o misdn/isdn_msg_parser.o
diff --git a/1.4.23-rc4/channels/answer.h b/1.4.23-rc4/channels/answer.h
new file mode 100644
index 000000000..4168c5012
--- /dev/null
+++ b/1.4.23-rc4/channels/answer.h
@@ -0,0 +1,237 @@
+/*!\file
+ * \brief Signed 16-bit audio data
+ *
+ * Source: answer.raw
+ *
+ * Copyright (C) 1999-2005, Digium, Inc.
+ *
+ * Distributed under the terms of the GNU General Public License
+ *
+ */
+
+static signed short answer[] = {
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 0x19b7, 0x0245, 0xeee5, 0xb875, 0xd9a4, 0x6018, 0x660a, 0xc3c6,
+0x8741, 0xff55, 0x4c2e, 0x2146, 0xfed2, 0xf079, 0xcbd4, 0xe561, 0x3c41, 0x3166,
+0xd425, 0xdc59, 0x2748, 0x087d, 0xc72b, 0xfe3a, 0x4681, 0x14c6, 0xcf45, 0xdd38,
+0xf8dd, 0x0a39, 0x3a5a, 0x32b9, 0xbfec, 0x957f, 0x15a3, 0x70f4, 0x1d95, 0xbfc4,
+0xd367, 0xfda0, 0x0dc0, 0x29eb, 0x1fc2, 0xd684, 0xcab1, 0x19c7, 0x29ef, 0xe679,
+0xe9d0, 0x2b82, 0x151a, 0xca9f, 0xdb68, 0x1f4a, 0x271c, 0x0e2a, 0xfb32, 0xd1b2,
+0xc8ff, 0x2382, 0x6380, 0x0a52, 0xa118, 0xccbf, 0x2ddc, 0x33fd, 0x0964, 0xf2a4,
+0xdd81, 0xe092, 0x1a00, 0x325c, 0xf5e3, 0xd6a1, 0x0b6c, 0x1c75, 0xe4f8, 0xe07c,
+0x2082, 0x2b3e, 0xf445, 0xdaa9, 0xea13, 0xff3c, 0x245c, 0x35c1, 0xf308, 0xab53,
+0xdf59, 0x4698, 0x3f3b, 0xe7f7, 0xca84, 0xed4d, 0x0c3f, 0x1e94, 0x1c2d, 0xf06f,
+0xd4df, 0xff34, 0x23d8, 0x001e, 0xe3f1, 0x0b15, 0x2113, 0xf3fd, 0xd768, 0xf9a0,
+0x1d31, 0x1c6e, 0x0797, 0xe3a0, 0xce6c, 0xfd7b, 0x422a, 0x2c4c, 0xd364, 0xbf42,
+0x0278, 0x303e, 0x1c51, 0xf737, 0xe25a, 0xe75f, 0x0a8f, 0x22ab, 0x05f4, 0xe3f9,
+0xf8c4, 0x1705, 0x0162, 0xe49f, 0xfb8b, 0x1e2b, 0x13ac, 0xf044, 0xe07b, 0xf01a,
+0x1567, 0x2cbf, 0x0b75, 0xd01b, 0xd206, 0x1563, 0x38d7, 0x0f2e, 0xdb32, 0xdc30,
+0x023b, 0x1e44, 0x16eb, 0xf5f7, 0xe425, 0xfa33, 0x14d5, 0x0968, 0xeff2, 0xf762,
+0x1137, 0x0e59, 0xf13a, 0xe651, 0xff41, 0x1d60, 0x18fd, 0xf1e6, 0xd75f, 0xf097,
+0x20ec, 0x27fa, 0xfba4, 0xd5b8, 0xe68e, 0x1657, 0x2518, 0x04f6, 0xe5a3, 0xe976,
+0x0578, 0x18fa, 0x0a92, 0xec0a, 0xef2a, 0x111f, 0x12f4, 0xeec3, 0xe95e, 0x0d3a,
+0x18fd, 0xff72, 0xeefc, 0xf114, 0xfaaa, 0x14ee, 0x21db, 0xf56e, 0xcb49, 0xf621,
+0x3323, 0x1947, 0xe017, 0xe7e9, 0x0819, 0x0707, 0x084c, 0x0f57, 0xf152, 0xdf92,
+0x104a, 0x28eb, 0xedcc, 0xd4ad, 0x1415, 0x296d, 0xed9a, 0xdf57, 0x0cc2, 0x0d95,
+0xf7b5, 0x0deb, 0x0b34, 0xd713, 0xea08, 0x38d6, 0x216d, 0xc727, 0xdc32, 0x2cd2,
+0x1822, 0xe2d5, 0xfeb3, 0x106c, 0xe6e5, 0xf81e, 0x2fe8, 0x01af, 0xc180, 0x037a,
+0x42d8, 0xf88d, 0xc344, 0x0a4f, 0x2c4e, 0xf19d, 0xebeb, 0x162c, 0xf9e9, 0xde93,
+0x1b56, 0x2c60, 0xd8aa, 0xce3e, 0x2a41, 0x2eeb, 0xdab1, 0xde32, 0x1c32, 0x0aba,
+0xeabe, 0x1008, 0x136d, 0xda2f, 0xec3b, 0x31dd, 0x1130, 0xca79, 0xf5b8, 0x3423,
+0x0274, 0xd27d, 0x035e, 0x1e68, 0xf641, 0xf904, 0x1691, 0xef7d, 0xd57a, 0x1c3b,
+0x3c23, 0xe881, 0xc274, 0x0af5, 0x2962, 0xfa34, 0xf676, 0x0f71, 0xefcc, 0xe01f,
+0x19e7, 0x276f, 0xe694, 0xe134, 0x1c3a, 0x0e8b, 0xd8e7, 0xfa81, 0x2f8b, 0x07c5,
+0xd904, 0xf6fa, 0x0ca5, 0xf9a2, 0x0dc7, 0x2623, 0xec54, 0xbe23, 0x02b6, 0x4296,
+0x10cd, 0xda61, 0xf11c, 0x0103, 0xf41c, 0x10b4, 0x2a03, 0xf63c, 0xce1a, 0xfdbd,
+0x1fb4, 0xfc51, 0xf727, 0x1c8a, 0x04ff, 0xcf41, 0xec05, 0x2913, 0x1ce8, 0xf70c,
+0xf744, 0xede8, 0xdd77, 0x0d99, 0x43f1, 0x119c, 0xc14f, 0xd60e, 0x17cb, 0x1e19,
+0x0d4e, 0x0c95, 0xeed1, 0xcdf4, 0xf7a5, 0x331f, 0x1cd0, 0xeb17, 0xf082, 0xfb19,
+0xe899, 0xfdeb, 0x323c, 0x2036, 0xdad3, 0xd134, 0xfd03, 0x1345, 0x1c10, 0x2239,
+0xf656, 0xbc22, 0xdc3f, 0x3392, 0x3d59, 0xfd77, 0xdb4d, 0xe23f, 0xedbe, 0x0f7e,
+0x35cc, 0x1947, 0xd5dc, 0xd1bf, 0x035d, 0x16fc, 0x1174, 0x1675, 0x0249, 0xd2d4,
+0xd851, 0x184d, 0x32fe, 0x0f91, 0xee14, 0xe1e6, 0xdf9b, 0x016b, 0x3668, 0x2b2b,
+0xe20c, 0xc554, 0xf257, 0x1c05, 0x1fc5, 0x14f0, 0xf891, 0xd41c, 0xdf83, 0x1865,
+0x2de1, 0x0b16, 0xed58, 0xea0c, 0xea79, 0xfbd9, 0x22af, 0x2732, 0xf62f, 0xd389,
+0xe7d9, 0x0b39, 0x1cdc, 0x1de3, 0x038a, 0xd809, 0xd5f7, 0x0b55, 0x305e, 0x1910,
+0xf02e, 0xe089, 0xe7c7, 0x0195, 0x2265, 0x21da, 0xf743, 0xd8f2, 0xe978, 0x09a1,
+0x190a, 0x17c5, 0x045a, 0xe46d, 0xdd06, 0xffb2, 0x2293, 0x1cfe, 0xfd4d, 0xe4f9,
+0xe310, 0xfaf1, 0x1d22, 0x2376, 0x0113, 0xde3a, 0xe21b, 0x0204, 0x1ba1, 0x1bd6,
+0x0333, 0xe563, 0xe104, 0xfd51, 0x1bc1, 0x1ccf, 0x0285, 0xe757, 0xe35e, 0xfaf2,
+0x185d, 0x1d46, 0x06b7, 0xec13, 0xe108, 0xef6e, 0x121d, 0x2a17, 0x16a6, 0xe32c,
+0xc9a9, 0xf070, 0x2f48, 0x3788, 0xfa4e, 0xc32a, 0xd9c2, 0x1fa1, 0x36fe, 0x07fa,
+0xd9e4, 0xe577, 0x0e5e, 0x1755, 0xfb53, 0xed71, 0x0540, 0x19e0, 0x0301, 0xdc97,
+0xe391, 0x1937, 0x367c, 0x0bc9, 0xca4c, 0xc96b, 0x105d, 0x461f, 0x2416, 0xd481,
+0xbc97, 0xf8b7, 0x39af, 0x2ec9, 0xecc6, 0xcb50, 0xeee3, 0x1ffe, 0x1e8e, 0xf700,
+0xe66a, 0xff58, 0x149f, 0x02e5, 0xe792, 0xf2d8, 0x1a4d, 0x225a, 0xf642, 0xce7f,
+0xe6a6, 0x25e2, 0x38f5, 0x01d0, 0xc50f, 0xd243, 0x19bd, 0x3fc6, 0x14f0, 0xd2d7,
+0xcdb6, 0x069a, 0x2ffe, 0x1847, 0xe6f8, 0xdf0a, 0x0337, 0x1a90, 0x067a, 0xeb5b,
+0xf541, 0x143b, 0x14f2, 0xf092, 0xdc02, 0xfb91, 0x28a3, 0x2274, 0xeaa8, 0xc9e7,
+0xef48, 0x2d01, 0x322e, 0xf6d2, 0xc7cb, 0xe13b, 0x1fda, 0x3217, 0x0458, 0xd690,
+0xe2bf, 0x11c4, 0x21d5, 0x0291, 0xe5c8, 0xf3a9, 0x12ba, 0x11aa, 0xf22b, 0xe627,
+0x03ec, 0x219a, 0x1036, 0xe2f2, 0xd93f, 0x059c, 0x2ed6, 0x1b75, 0xe227, 0xce55,
+0xfb19, 0x2de0, 0x2477, 0xed08, 0xd148, 0xf307, 0x21d4, 0x2002, 0xf543, 0xdeac,
+0xf7f9, 0x18a9, 0x11d6, 0xf0ef, 0xe8e4, 0x05ea, 0x1ba5, 0x0727, 0xe448, 0xe748,
+0x100e, 0x265e, 0x07fc, 0xdbae, 0xde78, 0x0efa, 0x2ce0, 0x0f94, 0xddf1, 0xd9ea,
+0x0797, 0x28f6, 0x12eb, 0xe60c, 0xdf46, 0x0469, 0x1fbb, 0x0ced, 0xe9f6, 0xe95f,
+0x09fe, 0x1ab9, 0x02cb, 0xe5a4, 0xef2a, 0x1327, 0x1d7b, 0xfd07, 0xde3d, 0xed9c,
+0x17e5, 0x22e7, 0xfe3a, 0xdb38, 0xe9b9, 0x161a, 0x2416, 0x0175, 0xde3d, 0xe9de,
+0x1294, 0x1fc9, 0x00ea, 0xe2a7, 0xeee2, 0x1298, 0x1a7d, 0xfc1d, 0xe3bb, 0xf47a,
+0x1642, 0x185e, 0xf727, 0xe1af, 0xf709, 0x19c3, 0x18e7, 0xf50d, 0xe010, 0xf75b,
+0x1a9c, 0x18d8, 0xf4c5, 0xe0c9, 0xf865, 0x1a1c, 0x16d5, 0xf3a6, 0xe257, 0xfaf2,
+0x1a44, 0x14d5, 0xf34f, 0xe4b6, 0xfc77, 0x17d5, 0x0ff8, 0xf133, 0xe8b7, 0x0344,
+0x1a37, 0x0ad5, 0xe95e, 0xe61a, 0x08a5, 0x227e, 0x0e33, 0xe4a7, 0xdd70, 0x03b0,
+0x25f4, 0x17b2, 0xec0a, 0xdb4e, 0xf898, 0x1ba3, 0x18f6, 0xf973, 0xe87f, 0xf77a,
+0x0b93, 0x096c, 0xfb0e, 0xfb03, 0x0896, 0x0940, 0xf51d, 0xe904, 0xfdc7, 0x1dda,
+0x1bf9, 0xf29b, 0xd37f, 0xea1b, 0x1f37, 0x3175, 0x07eb, 0xd3f7, 0xd46b, 0x077d,
+0x2eeb, 0x1e67, 0xeeae, 0xd8c7, 0xef85, 0x1119, 0x18d3, 0x088e, 0xf953, 0xf5ad,
+0xf556, 0xf63d, 0x0234, 0x167a, 0x19a1, 0xfbf9, 0xd873, 0xdd4b, 0x0f06, 0x3748,
+0x21e6, 0xe181, 0xc032, 0xe79a, 0x2bec, 0x3e76, 0x0b1b, 0xce41, 0xcb23, 0xff96,
+0x2d79, 0x26d1, 0xfcc7, 0xdf8a, 0xe525, 0xfd83, 0x10f1, 0x16d7, 0x0f50, 0xfaea,
+0xe3f1, 0xe20f, 0x0158, 0x27d9, 0x2866, 0xf96f, 0xcb34, 0xd563, 0x11d6, 0x3d25,
+0x2424, 0xe254, 0xc2c9, 0xe7cd, 0x248d, 0x34f5, 0x0c42, 0xdcd0, 0xd827, 0xfa65,
+0x19eb, 0x1b50, 0x0721, 0xf396, 0xeb9c, 0xefde, 0x0016, 0x1594, 0x1cc1, 0x0658,
+0xe22b, 0xd852, 0xfb3e, 0x2923, 0x2c78, 0xfc87, 0xcdb5, 0xd69c, 0x0e3c, 0x3527,
+0x201f, 0xe993, 0xcf9e, 0xeb21, 0x183f, 0x25ea, 0x0c93, 0xed4d, 0xe5f9, 0xf548,
+0x07fb, 0x117c, 0x0ff2, 0x0398, 0xf08c, 0xe628, 0xf489, 0x143b, 0x2419, 0x0ccf,
+0xe2cc, 0xd5a6, 0xf861, 0x2615, 0x2a1b, 0xfeb4, 0xd543, 0xdc53, 0x09b4, 0x2901,
+0x19ff, 0xf24a, 0xde86, 0xeec4, 0x0b7b, 0x1733, 0x0d0a, 0xfc24, 0xf1bb, 0xf110,
+0xfa03, 0x0a0f, 0x15d4, 0x0e21, 0xf435, 0xe17e, 0xee90, 0x1225, 0x2527, 0x0efa,
+0xe61f, 0xd916, 0xf7b8, 0x1f50, 0x2326, 0x0099, 0xe01e, 0xe473, 0x0491, 0x1b37,
+0x1360, 0xfb17, 0xecd9, 0xf20d, 0x0051, 0x0aec, 0x0d4a, 0x073d, 0xfa5a, 0xeeb8,
+0xf165, 0x0516, 0x17dc, 0x12da, 0xf71b, 0xe213, 0xed85, 0x0eef, 0x20c8, 0x0e09,
+0xebcc, 0xe0d4, 0xf848, 0x1637, 0x19d6, 0x026b, 0xec09, 0xed00, 0xff9b, 0x0e5a,
+0x0d6b, 0x026c, 0xf865, 0xf4da, 0xf888, 0x025a, 0x0cbb, 0x0d53, 0xff96, 0xeefa,
+0xee80, 0x021c, 0x15d6, 0x126a, 0xf9c1, 0xe724, 0xf017, 0x0aa1, 0x18b6, 0x0b4e,
+0xf2d7, 0xea91, 0xf957, 0x0cac, 0x1061, 0x03f4, 0xf6ad, 0xf476, 0xfbdf, 0x0489,
+0x08b1, 0x06df, 0xffcf, 0xf766, 0xf537, 0xfddf, 0x0ad4, 0x0e15, 0x01da, 0xf205,
+0xf0a0, 0x0082, 0x1066, 0x0e41, 0xfc71, 0xef1b, 0xf4ad, 0x05cd, 0x0f32, 0x07ed,
+0xf9c8, 0xf401, 0xfa93, 0x04af, 0x088c, 0x04a7, 0xfe15, 0xf9f1, 0xfa64, 0xff1e,
+0x0539, 0x078c, 0x02af, 0xfa1a, 0xf69d, 0xfd09, 0x075b, 0x0a3d, 0x01f2, 0xf761,
+0xf642, 0xffa7, 0x08f3, 0x0830, 0xff05, 0xf7db, 0xf9bc, 0x0174, 0x068b, 0x04b2,
+0xfeff, 0xfb39, 0xfc1a, 000000, 0x0371, 0x03d7, 0x00fe, 0xfd37, 0xfbe0, 0xfe78,
+0x02af, 0x044a, 0x0180, 0xfd43, 0xfc00, 0xfed1, 0x02aa, 0x0346, 0x00dd, 0xfde0,
+0xfbfe, 0x0114, 0x0987, 0x04bc, 0xf49d, 0xf23a, 0x06ab, 0x162e, 0x0544, 0xe76b,
+0xea25, 0x1015, 0x2474, 0x0431, 0xd7d3, 0xe1ec, 0x1923, 0x2df5, 0x01cd, 0xd386,
+0xe3d9, 0x1b9d, 0x2c62, 0xfeb8, 0xd31a, 0xe6ba, 0x1dbd, 0x2abb, 0xfbab, 0xd2ed,
+0xe9ab, 0x1fa7, 0x28ef, 0xf8b3, 0xd2f5, 0xeca5, 0x2160, 0x26fd, 0xf5d7, 0xd334,
+0xefa1, 0x22e5, 0x24ea, 0xf31b, 0xd3a9, 0xf29f, 0x2435, 0x22b6, 0xf07e, 0xd44e,
+0xf59b, 0x2551, 0x2067, 0xee08, 0xd527, 0xf88e, 0x2639, 0x1e00, 0xebb6, 0xd62d,
+0xfb77, 0x26eb, 0x1b85, 0xe98b, 0xd75f, 0xfe51, 0x276b, 0x18f9, 0xe78e, 0xd8b9,
+0x011a, 0x27b6, 0x1660, 0xe5bb, 0xda3a, 0x03cc, 0x27cf, 0x13bd, 0xe415, 0xdbdf,
+0x066a, 0x27b7, 0x1117, 0xe29e, 0xdda5, 0x08ec, 0x276e, 0x0e6d, 0xe154, 0xdf89,
+0x0b52, 0x26f6, 0x0bc7, 0xe039, 0xe185, 0x0d96, 0x2653, 0x0924, 0xdf4e, 0xe399,
+0x0fb9, 0x2584, 0x068b, 0xde93, 0xe5c0, 0x11b8, 0x248e, 0x03fd, 0xde08, 0xe7f8,
+0x1390, 0x2372, 0x0180, 0xddaa, 0xea3c, 0x1544, 0x2231, 0xff12, 0xdd7a, 0xec89,
+0x16cf, 0x20d0, 0xfcb9, 0xdd77, 0xeedb, 0x1831, 0x1f52, 0xfa77, 0xdd9f, 0xf132,
+0x1969, 0x1db7, 0xf850, 0xddf1, 0xf385, 0x1a75, 0x1c06, 0xf645, 0xde6b, 0xf5d7,
+0x1b5b, 0x1a3f, 0xf457, 0xdf0d, 0xf820, 0x1c13, 0x1867, 0xf288, 0xdfd2, 0xfa5f,
+0x1ca1, 0x167f, 0xf0db, 0xe0ba, 0xfc92, 0x1d06, 0x148b, 0xef50, 0xe1c1, 0xfeb5,
+0x1d43, 0x1290, 0xede9, 0xe2e6, 0x00c6, 0x1d58, 0x108e, 0xeca7, 0xe426, 0x02c4,
+0x1d45, 0x0e8a, 0xeb8a, 0xe57f, 0x04a9, 0x1d0e, 0x0c87, 0xea92, 0xe6ec, 0x0677,
+0x1cb2, 0x0a87, 0xe9be, 0xe86e, 0x082a, 0x1c34, 0x088b, 0xe912, 0xe9fe, 0x09c1,
+0x1b95, 0x069c, 0xe88c, 0xeb9c, 0x0b3a, 0x1ad9, 0x04b6, 0xe82a, 0xed43, 0x0c96,
+0x1a00, 0x02df, 0xe7eb, 0xeef3, 0x0dd0, 0x190d, 0x0116, 0xe7d0, 0xf0a8, 0x0eec,
+0x1804, 0xff61, 0xe7d8, 0xf25d, 0x0fe6, 0x16e3, 0xfdc0, 0xe800, 0xf412, 0x10bf,
+0x15b1, 0xfc36, 0xe848, 0xf5c5, 0x1176, 0x146e, 0xfac2, 0xe8ad, 0xf771, 0x120d,
+0x1320, 0xf969, 0xe92e, 0xf913, 0x1282, 0x11c4, 0xf828, 0xe9cb, 0xfaac, 0x12d8,
+0x1062, 0xf703, 0xea7e, 0xfc38, 0x130e, 0x0efa, 0xf5fb, 0xeb49, 0xfdb5, 0x1325,
+0x0d8e, 0xf50e, 0xec26, 0xff20, 0x131e, 0x0c21, 0xf43f, 0xed15, 0x007a, 0x12fa,
+0x0ab6, 0xf38d, 0xee15, 0x01be, 0x12bd, 0x094f, 0xf2f9, 0xef22, 0x02ef, 0x1265,
+0x07f0, 0xf283, 0xf037, 0x0408, 0x11f6, 0x0699, 0xf226, 0xf156, 0x050a, 0x1170,
+0x054b, 0xf1e8, 0xf27a, 0x05f4, 0x10d8, 0x040c, 0xf1c5, 0xf3a3, 0x06c2, 0x102c,
+0x02da, 0xf1bc, 0xf4cc, 0x0779, 0x0f71, 0x01b7, 0xf1cc, 0xf5f5, 0x0815, 0x0ea7,
+0x00a8, 0xf1f4, 0xf719, 0x0899, 0x0dd2, 0xffab, 0xf233, 0xf839, 0x0902, 0x0cf4,
+0xfec0, 0xf288, 0xf950, 0x0952, 0x0c0e, 0xfdec, 0xf2ee, 0xfa5d, 0x0989, 0x0b23,
+0xfd2d, 0xf368, 0xfb62, 0x09a7, 0x0a35, 0xfc85, 0xf3f1, 0xfc58, 0x09af, 0x0946,
+0xfbf2, 0xf488, 0xfd3f, 0x09a1, 0x0859, 0xfb77, 0xf52c, 0xfe17, 0x097d, 0x076f,
+0xfb14, 0xf5d8, 0xfede, 0x0945, 0x068a, 0xfac6, 0xf68d, 0xff93, 0x08fb, 0x05ad,
+0xfa8e, 0xf747, 0x0034, 0x08a1, 0x04da, 0xfa6f, 0xf805, 0x00c2, 0x0836, 0x0410,
+0xfa63, 0xf8c6, 0x013c, 0x07bf, 0x0354, 0xfa6c, 0xf985, 0x01a1, 0x073b, 0x02a4,
+0xfa8a, 0xfa43, 0x01f1, 0x06af, 0x0204, 0xfab9, 0xfafc, 0x022c, 0x0619, 0x0175,
+0xfafa, 0xfbae, 0x0252, 0x057f, 0x00f6, 0xfb4b, 0xfc5a, 0x0263, 0x04e0, 0x008b,
+0xfbaa, 0xfcfa, 0x0262, 0x0440, 0x0032, 0xfc16, 0xfd90, 0x024b, 0x03a0, 0xffec,
+0xfc8c, 0xfe19, 0x0225, 0x0301, 0xffb9, 0xfd0c, 0xfe93, 0x01ea, 0x0267, 0xff9c,
+0xfd95, 0xfefe, 0x01a0, 0x01d3, 0xff90, 0xfe22, 0xff5a, 0x0147, 0x0145, 0xff99,
+0xfeb3, 0xffa1, 0x00e0, 0x00c3, 0xffb6, 0xff46, 0xffd9, 0x006d, 0x004b, 0xffe5,
+0xffda, 0xfffc, 000000, 0xfffe, 000000, 0xffff, 000000, 0xffff, 0xffff, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000 };
diff --git a/1.4.23-rc4/channels/busy_tone.h b/1.4.23-rc4/channels/busy_tone.h
new file mode 100644
index 000000000..6e5db8e47
--- /dev/null
+++ b/1.4.23-rc4/channels/busy_tone.h
@@ -0,0 +1,55 @@
+/* busy.h: Generated from frequencies 480 and 620
+ by gentone. 400 samples */
+static short busy[400] = {
+ 0, 13697, 24766, 31109, 31585, 26222, 16198, 3569,
+ -9162, -19575, -25812, -26935, -23069, -15322, -5493, 4339,
+ 12277, 16985, 17934, 15440, 10519, 4585, -908, -4827,
+ -6592, -6269, -4489, -2220, -467, 30, -983, -3203,
+ -5839, -7844, -8215, -6301, -2035, 3975, 10543, 16141,
+ 19260, 18787, 14322, 6338, -3845, -14296, -22858, -27611,
+ -27309, -21691, -11585, 1213, 14285, 25068, 31388, 31915,
+ 26457, 16010, 2568, -11282, -22885, -30054, -31509, -27120,
+ -17908, -5805, 6760, 17379, 24147, 26028, 23020, 16094,
+ 6931, -2478, -10279, -15136, -16474, -14538, -10253, -4949,
+ 0, 3515, 5052, 4688, 3045, 1069, -268, -272,
+ 1269, 3996, 7067, 9381, 9889, 7910, 3365, -3123,
+ -10320, -16622, -20424, -20510, -16384, -8448, 2006, 13026,
+ 22383, 28040, 28613, 23696, 13996, 1232, -12193, -23670,
+ -30918, -32459, -27935, -18190, -5103, 8795, 20838, 28764,
+ 31164, 27753, 19395, 7893, -4412, -15136, -22342, -24909,
+ -22717, -16609, -8143, 780, 8361, 13272, 14909, 13455,
+ 9758, 5067, 678, -2387, -3624, -3133, -1538, 224,
+ 1209, 751, -1315, -4580, -8145, -10848, -11585, -9628,
+ -4878, 2038, 9844, 16867, 21403, 22124, 18429, 10638,
+ 0, -11524, -21643, -28211, -29702, -25561, -16364, -3737,
+ 9946, 22044, 30180, 32733, 29182, 20210, 7573, -6269,
+ -18655, -27259, -30558, -28117, -20645, -9807, 2148, 12878,
+ 20426, 23599, 22173, 16865, 9117, 731, -6552, -11426,
+ -13269, -12216, -9050, -4941, -1118, 1460, 2335, 1635,
+ 0, -1635, -2335, -1460, 1118, 4941, 9050, 12216,
+ 13269, 11426, 6552, -731, -9117, -16865, -22173, -23599,
+ -20426, -12878, -2148, 9807, 20645, 28117, 30558, 27259,
+ 18655, 6269, -7573, -20210, -29182, -32733, -30180, -22044,
+ -9946, 3737, 16364, 25561, 29702, 28211, 21643, 11524,
+ 0, -10638, -18429, -22124, -21403, -16867, -9844, -2038,
+ 4878, 9628, 11585, 10848, 8145, 4580, 1315, -751,
+ -1209, -224, 1538, 3133, 3624, 2387, -678, -5067,
+ -9758, -13455, -14909, -13272, -8361, -780, 8143, 16609,
+ 22717, 24909, 22342, 15136, 4412, -7893, -19395, -27753,
+ -31164, -28764, -20838, -8795, 5103, 18190, 27935, 32459,
+ 30918, 23670, 12193, -1232, -13996, -23696, -28613, -28040,
+ -22383, -13026, -2006, 8448, 16384, 20510, 20424, 16622,
+ 10320, 3123, -3365, -7910, -9889, -9381, -7067, -3996,
+ -1269, 272, 268, -1069, -3045, -4688, -5052, -3515,
+ 0, 4949, 10253, 14538, 16474, 15136, 10279, 2478,
+ -6931, -16094, -23020, -26028, -24147, -17379, -6760, 5805,
+ 17908, 27120, 31509, 30054, 22885, 11282, -2568, -16010,
+ -26457, -31915, -31388, -25068, -14285, -1213, 11585, 21691,
+ 27309, 27611, 22858, 14296, 3845, -6338, -14322, -18787,
+ -19260, -16141, -10543, -3975, 2035, 6301, 8215, 7844,
+ 5839, 3203, 983, -30, 467, 2220, 4489, 6269,
+ 6592, 4827, 908, -4585, -10519, -15440, -17934, -16985,
+ -12277, -4339, 5493, 15322, 23069, 26935, 25812, 19575,
+ 9162, -3569, -16198, -26222, -31585, -31109, -24766, -13697,
+
+};
diff --git a/1.4.23-rc4/channels/chan_agent.c b/1.4.23-rc4/channels/chan_agent.c
new file mode 100644
index 000000000..b12983dfc
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_agent.c
@@ -0,0 +1,2870 @@
+/*
+ * 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
+ */
+/*** MODULEINFO
+ <depend>chan_local</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <stdlib.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/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/lock.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 app2[] = "AgentCallbackLogin";
+static const char app3[] = "AgentMonitorOutgoing";
+
+static const char synopsis[] = "Call agent login";
+static const char synopsis2[] = "Call agent callback 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 in/off\n";
+
+static const char descrip2[] =
+" AgentCallbackLogin([AgentNo][|[options][|[exten]@context]]):\n"
+"Asks the agent to login to the system with callback.\n"
+"The agent's callback extension is called (optionally with the specified\n"
+"context).\n"
+"The option string may contain zero or more of the following characters:\n"
+" 's' -- silent login - do not announce the login ok segment agent logged in/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 have to be configured in the agents.conf file.\n"
+"\nReturn value:\n"
+"Normally the app returns 0 unless the options are passed. Also if the callerid or\n"
+"the agentid are not specified it'll look for n+101 priority.\n"
+"\nOptions:\n"
+" 'd' - make the app return -1 if there is an error condition and there is\n"
+" no extension n+101\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 const char mandescr_agent_callback_login[] =
+"Description: Sets an agent as logged in with callback.\n"
+"Variables: (Names marked with * are required)\n"
+" *Agent: Agent ID of the agent to login\n"
+" *Exten: Extension to use for callback\n"
+" Context: Context to use for callback\n"
+" AckCall: Set to 'true' to require an acknowledgement by '#' when agent is called back\n"
+" WrapupTime: the minimum amount of time after disconnecting before the caller can receive a new call\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];
+ int inherited_devicestate; /*!< Does the underlying channel have a devicestate to pass? */
+ ast_mutex_t app_lock; /**< Synchronization between owning applications */
+ int app_lock_flag;
+ ast_cond_t app_complete_cond;
+ 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_log(LOG_DEBUG, "Native formats changing from %d to %d\n", ast->nativeformats, p->chan->nativeformats); \
+ /* Native formats changed, reset things */ \
+ ast->nativeformats = p->chan->nativeformats; \
+ ast_log(LOG_DEBUG, "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->fds[x] = p->chan->fds[x]; \
+ } \
+ ast->fds[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 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,
+};
+
+static int agent_devicestate_cb(const char *dev, int state, void *data)
+{
+ int res, i;
+ struct agent_pvt *p;
+ char basename[AST_CHANNEL_NAME], *tmp;
+
+ /* Skip Agent status */
+ if (!strncasecmp(dev, "Agent/", 6)) {
+ return 0;
+ }
+
+ /* Try to be safe, but don't deadlock */
+ for (i = 0; i < 10; i++) {
+ if ((res = AST_LIST_TRYLOCK(&agents)) == 0) {
+ break;
+ }
+ }
+ if (res) {
+ return -1;
+ }
+
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ ast_mutex_lock(&p->lock);
+ if (p->chan) {
+ ast_copy_string(basename, p->chan->name, sizeof(basename));
+ if ((tmp = strrchr(basename, '-'))) {
+ *tmp = '\0';
+ }
+ if (strcasecmp(p->chan->name, dev) == 0 || strcasecmp(basename, dev) == 0) {
+ p->inherited_devicestate = state;
+ ast_device_state_changed("Agent/%s", p->agent);
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ AST_LIST_UNLOCK(&agents);
+ return 0;
+}
+
+/*!
+ * 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(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_NONSTANDARD_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);
+ ast_cond_init(&p->app_complete_cond, NULL);
+ p->app_lock_flag = 0;
+ p->app_sleep_cond = 1;
+ p->group = group;
+ p->pending = pending;
+ p->inherited_devicestate = -1;
+ 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). */
+ p->app_lock_flag = 0;
+ ast_cond_signal(&p->app_complete_cond);
+ if (chan)
+ ast_channel_free(chan);
+ if (p->dead) {
+ ast_mutex_destroy(&p->lock);
+ ast_mutex_destroy(&p->app_lock);
+ ast_cond_destroy(&p->app_complete_cond);
+ 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);
+ 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_log(LOG_DEBUG, "Bridge on '%s' being cleared (2)\n", p->chan->name);
+ if (p->owner->_state != AST_STATE_UP) {
+ int howlong = time(NULL) - p->start;
+ if (p->autologoff && howlong > p->autologoff) {
+ long logintime = time(NULL) - p->loginstart;
+ p->loginstart = 0;
+ ast_log(LOG_NOTICE, "Agent '%s' didn't answer/confirm within %d seconds (waited %d)\n", p->name, p->autologoff, howlong);
+ agent_logoff_maintenance(p, p->loginchan, logintime, ast->uniqueid, "Autologoff");
+ if (persistent_agents)
+ dump_agents();
+ }
+ }
+ 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->inherited_devicestate = -1;
+ ast_device_state_changed("Agent/%s", p->agent);
+ 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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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 == '#')) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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 && !ast_check_hangup(p->chan)) {
+ while (ast_channel_trylock(p->chan)) {
+ ast_channel_unlock(ast);
+ usleep(1);
+ ast_channel_lock(ast);
+ }
+ res = p->chan->tech->indicate ? p->chan->tech->indicate(p->chan, condition, data, datalen) : -1;
+ ast_channel_unlock(p->chan);
+ } 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);
+ if (p->chan) {
+ 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);
+ if (p->chan) {
+ 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_log(LOG_DEBUG, "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 */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ }
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "agent_call, call to agent '%s' call on '%s'\n", p->agent, p->chan->name);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Playing beep, lang '%s'\n", p->chan->language);
+ res = ast_streamfile(p->chan, beep, p->chan->language);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Played beep, result '%d'\n", res);
+ if (!res) {
+ res = ast_waitstream(p->chan, "");
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Waited for stream, result '%d'\n", res);
+ }
+ if (!res) {
+ res = ast_set_read_format(p->chan, ast_best_codec(p->chan->nativeformats));
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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;
+ p->inherited_devicestate = -1;
+ ast_device_state_changed("Agent/%s", p->agent);
+ }
+
+ if (!res) {
+ res = ast_set_write_format(p->chan, ast_best_codec(p->chan->nativeformats));
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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()
+ */
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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;
+ p->inherited_devicestate = -1;
+ ast_device_state_changed("Agent/%s", p->agent);
+ }
+ ast_log(LOG_DEBUG, "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_cond_destroy(&p->app_complete_cond);
+ 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)) {
+ p->app_lock_flag = 0;
+ ast_cond_signal(&p->app_complete_cond);
+ }
+ }
+ 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(option_debug > 4 && !res )
+ ast_log(LOG_DEBUG, "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;
+ }
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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;
+ int alreadylocked;
+#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, (int) 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;
+ /* XXX: this needs fixing */
+#if 0
+ ast_atomic_fetchadd_int(&__mod_desc->usecnt, +1);
+#endif
+ ast_update_use_count();
+ 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;
+
+ alreadylocked = p->app_lock_flag;
+ p->app_lock_flag = 1;
+
+ if(ast_strlen_zero(p->loginchan) && alreadylocked) {
+ if (p->chan) {
+ ast_queue_frame(p->chan, &ast_null_frame);
+ ast_mutex_unlock(&p->lock); /* For other thread to read the condition. */
+ p->app_lock_flag = 1;
+ 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. */
+ p->app_lock_flag = 0;
+ ast_cond_signal(&p->app_complete_cond);
+ 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);
+ return tmp;
+}
+
+
+/*!
+ * Read configuration data. The file named agents.conf.
+ *
+ * \returns Always 0, or so it seems.
+ */
+static int read_agent_config(void)
+{
+ 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;
+
+ group = 0;
+ autologoff = 0;
+ wrapuptime = 0;
+ ackcall = 0;
+ endcall = 1;
+ cfg = ast_config_load(config);
+ if (!cfg) {
+ ast_log(LOG_NOTICE, "No agent configuration found -- agent support disabled\n");
+ return 0;
+ }
+ 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"))) {
+ 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(&agents, list);
+ /* Destroy if appropriate */
+ if (!p->owner) {
+ if (!p->chan) {
+ ast_mutex_destroy(&p->lock);
+ ast_mutex_destroy(&p->app_lock);
+ ast_cond_destroy(&p->app_complete_cond);
+ 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;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 {
+ if (option_debug > 2)
+ ast_log( LOG_DEBUG, "Playing beep, lang '%s'\n", newlyavailable->chan->language);
+ res = ast_streamfile(newlyavailable->chan, beep, newlyavailable->chan->language);
+ if (option_debug > 2)
+ ast_log( LOG_DEBUG, "Played beep, result '%d'\n", res);
+ if (!res) {
+ res = ast_waitstream(newlyavailable->chan, "");
+ ast_log( LOG_DEBUG, "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_mutex_lock(&parent->lock);
+ ast_set_flag(chan, AST_FLAG_ZOMBIE);
+ ast_channel_masquerade(parent, chan);
+ ast_mutex_unlock(&parent->lock);
+ p->abouttograb = 0;
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Sneaky, parent disappeared in the mean time...\n");
+ agent_cleanup(newlyavailable);
+ }
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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);
+ if (option_debug > 2)
+ ast_log( LOG_DEBUG, "Playing beep, lang '%s'\n", newlyavailable->chan->language);
+ res = ast_streamfile(newlyavailable->chan, beep, newlyavailable->chan->language);
+ if (option_debug > 2)
+ ast_log( LOG_DEBUG, "Played beep, result '%d'\n", res);
+ if (!res) {
+ res = ast_waitstream(newlyavailable->chan, "");
+ if (option_debug)
+ ast_log( LOG_DEBUG, "Waited for stream, result '%d'\n", res);
+ }
+ ast_mutex_lock(&newlyavailable->lock);
+ }
+ return res;
+}
+
+/* return 1 if multiple login is fine, 0 if it is not and we find a match, -1 if multiplelogin is not allowed and we don't find a match. */
+static int allow_multiple_login(char *chan, char *context)
+{
+ struct agent_pvt *p;
+ char loginchan[80];
+
+ if (multiplelogin) {
+ return 1;
+ }
+ if (!chan) {
+ return 0;
+ }
+
+ snprintf(loginchan, sizeof(loginchan), "%s@%s", chan, S_OR(context, "default"));
+
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ if(!strcasecmp(loginchan, p->loginchan))
+ return 0;
+ }
+ return -1;
+}
+
+/*! \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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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(), action_agent_callback_login(), 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 *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;
+ 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) {
+ if (ast_bridged_channel(p->owner)) {
+ talkingtoChan = ast_strdupa(S_OR(ast_bridged_channel(p->owner)->cid.cid_num, ""));
+ } else {
+ talkingtoChan = "n/a";
+ }
+ status = "AGENT_ONCALL";
+ } else {
+ talkingtoChan = "n/a";
+ status = "AGENT_IDLE";
+ }
+ } else {
+ loginChan = "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"
+ "%s"
+ "\r\n",
+ p->agent, username, status, loginChan, (int)p->loginstart, 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';
+ p->inherited_devicestate = -1;
+ 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_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ if (!strcasecmp(p->agent, agent)) {
+ ret = 0;
+ if (p->owner || p->chan) {
+ if (!soft) {
+ ast_mutex_lock(&p->lock);
+
+ while (p->owner && ast_channel_trylock(p->owner)) {
+ DEADLOCK_AVOIDANCE(&p->lock);
+ }
+ if (p->owner) {
+ ast_softhangup(p->owner, AST_SOFTHANGUP_EXPLICIT);
+ ast_channel_unlock(p->owner);
+ }
+
+ while (p->chan && ast_channel_trylock(p->chan)) {
+ DEADLOCK_AVOIDANCE(&p->lock);
+ }
+ if (p->chan) {
+ ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
+ ast_channel_unlock(p->chan);
+ }
+
+ ast_mutex_unlock(&p->lock);
+ } else
+ p->deferlogoff = 1;
+ } else {
+ logintime = time(NULL) - p->loginstart;
+ p->loginstart = 0;
+ agent_logoff_maintenance(p, p->loginchan, logintime, NULL, "CommandLogoff");
+ }
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&agents);
+
+ return ret;
+}
+
+static int agent_logoff_cmd(int fd, int argc, char **argv)
+{
+ int ret;
+ char *agent;
+
+ if (argc < 3 || argc > 4)
+ return RESULT_SHOWUSAGE;
+ if (argc == 4 && strcasecmp(argv[3], "soft"))
+ return RESULT_SHOWUSAGE;
+
+ agent = argv[2] + 6;
+ ret = agent_logoff(agent, argc == 4);
+ if (ret == 0)
+ ast_cli(fd, "Logging out %s\n", agent);
+
+ return RESULT_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(), action_agent_callback_login(), 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)
+{
+ char *ret = NULL;
+
+ if (pos == 2) {
+ struct agent_pvt *p;
+ char name[AST_MAX_AGENT];
+ int which = 0, len = strlen(word);
+
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ snprintf(name, sizeof(name), "Agent/%s", p->agent);
+ if (!strncasecmp(word, name, len) && ++which > state) {
+ ret = ast_strdup(name);
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&agents);
+ } else if (pos == 3 && state == 0)
+ return ast_strdup("soft");
+
+ return ret;
+}
+
+/*!
+ * Show agents in cli.
+ */
+static int agents_show(int fd, int argc, char **argv)
+{
+ 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 */
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ ast_mutex_lock(&p->lock);
+ if (p->pending) {
+ if (p->group)
+ ast_cli(fd, "-- Pending call to group %d\n", powerof(p->group));
+ else
+ ast_cli(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(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(fd, "No Agents are configured in %s\n",config);
+ else
+ ast_cli(fd, "%d agents configured [%d online , %d offline]\n",count_agents, online_agents, offline_agents);
+ ast_cli(fd, "\n");
+
+ return RESULT_SUCCESS;
+}
+
+
+static int agents_show_online(int fd, int argc, char **argv)
+{
+ 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 */
+ if (argc != 3)
+ return RESULT_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(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(fd, "No Agents are configured in %s\n", config);
+ else
+ ast_cli(fd, "%d agents online\n", online_agents);
+ ast_cli(fd, "\n");
+ return RESULT_SUCCESS;
+}
+
+
+
+static char show_agents_usage[] =
+"Usage: agent show\n"
+" Provides summary information on agents.\n";
+
+static char show_agents_online_usage[] =
+"Usage: agent show online\n"
+" Provides a list of all online agents.\n";
+
+static 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_show_agents_deprecated = {
+ { "show", "agents", NULL },
+ agents_show, NULL,
+ NULL, NULL };
+
+static struct ast_cli_entry cli_show_agents_online_deprecated = {
+ { "show", "agents", "online" },
+ agents_show_online, NULL,
+ NULL, NULL };
+
+static struct ast_cli_entry cli_agents[] = {
+ { { "agent", "show", NULL },
+ agents_show, "Show status of agents",
+ show_agents_usage, NULL, &cli_show_agents_deprecated },
+
+ { { "agent", "show", "online" },
+ agents_show_online, "Show all online agents",
+ show_agents_online_usage, NULL, &cli_show_agents_online_deprecated },
+
+ { { "agent", "logoff", NULL },
+ agent_logoff_cmd, "Sets an agent offline",
+ agent_logoff_usage, complete_agent_logoff_cmd },
+};
+
+/*!
+ * \brief Log in agent application.
+ *
+ * \param chan
+ * \param data
+ * \param callbackmode non-zero for AgentCallbackLogin
+ */
+static int __login_exec(struct ast_channel *chan, void *data, int callbackmode)
+{
+ 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;
+ char *context = NULL;
+ int play_announcement = 1;
+ char agent_goodbye[AST_MAX_FILENAME_LEN];
+ int update_cdr = updatecdr;
+ char *filename = "agent-loginok";
+ char tmpchan[AST_MAX_BUF] = "";
+
+ 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));
+
+ ast_channel_lock(chan);
+ /* Set Channel Specific Login Overrides */
+ if (pbx_builtin_getvar_helper(chan, "AGENTLMAXLOGINTRIES") && strlen(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");
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Saw variable AGENTMAXLOGINTRIES=%s, setting max_login_tries to: %d on Channel '%s'.\n",tmpoptions,max_login_tries,chan->name);
+ }
+ if (pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR") && !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");
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Saw variable AGENTUPDATECDR=%s, setting update_cdr to: %d on Channel '%s'.\n",tmpoptions,update_cdr,chan->name);
+ }
+ if (pbx_builtin_getvar_helper(chan, "AGENTGOODBYE") && !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");
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Saw variable AGENTGOODBYE=%s, setting agent_goodbye to: %s on Channel '%s'.\n",tmpoptions,agent_goodbye,chan->name);
+ }
+ ast_channel_unlock(chan);
+ /* End Channel Specific Login Overrides */
+
+ if (callbackmode && args.extension) {
+ parse = args.extension;
+ args.extension = strsep(&parse, "@");
+ context = parse;
+ }
+
+ 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) {
+ int unlock_channel = 1;
+ ast_channel_lock(chan);
+ 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 */
+ gettimeofday(&p->lastdisc, NULL);
+ p->lastdisc.tv_sec++;
+
+ /* Set Channel Specific Agent Overrides */
+ if (pbx_builtin_getvar_helper(chan, "AGENTACKCALL") && strlen(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");
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Saw variable AGENTACKCALL=%s, setting ackcall to: %d for Agent '%s'.\n",tmpoptions,p->ackcall,p->agent);
+ } else {
+ p->ackcall = ackcall;
+ }
+ if (pbx_builtin_getvar_helper(chan, "AGENTAUTOLOGOFF") && strlen(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");
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Saw variable AGENTAUTOLOGOFF=%s, setting autologff to: %d for Agent '%s'.\n",tmpoptions,p->autologoff,p->agent);
+ } else {
+ p->autologoff = autologoff;
+ }
+ if (pbx_builtin_getvar_helper(chan, "AGENTWRAPUPTIME") && strlen(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");
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Saw variable AGENTWRAPUPTIME=%s, setting wrapuptime to: %d for Agent '%s'.\n",tmpoptions,p->wrapuptime,p->agent);
+ } else {
+ p->wrapuptime = wrapuptime;
+ }
+ ast_channel_unlock(chan);
+ unlock_channel = 0;
+ /* End Channel Specific Agent Overrides */
+ if (!p->chan) {
+ char last_loginchan[80] = "";
+ long logintime;
+ snprintf(agent, sizeof(agent), "Agent/%s", p->agent);
+
+ if (callbackmode) {
+ int pos = 0;
+ /* Retrieve login chan */
+ for (;;) {
+ if (!ast_strlen_zero(args.extension)) {
+ ast_copy_string(tmpchan, args.extension, sizeof(tmpchan));
+ res = 0;
+ } else
+ res = ast_app_getdata(chan, "agent-newlocation", tmpchan+pos, sizeof(tmpchan) - 2, 0);
+ if (ast_strlen_zero(tmpchan) )
+ break;
+ if(ast_exists_extension(chan, S_OR(context,"default"), tmpchan,1, NULL) ) {
+ if(!allow_multiple_login(tmpchan,context) ) {
+ args.extension = NULL;
+ pos = 0;
+ } else
+ break;
+ }
+ if (args.extension) {
+ ast_log(LOG_WARNING, "Extension '%s' is not valid for automatic login of agent '%s'\n", args.extension, p->agent);
+ args.extension = NULL;
+ pos = 0;
+ } else {
+ ast_log(LOG_WARNING, "Extension '%s@%s' is not valid for automatic login of agent '%s'\n", tmpchan, S_OR(context, "default"), p->agent);
+ res = ast_streamfile(chan, "invalid", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, AST_DIGIT_ANY);
+ if (res > 0) {
+ tmpchan[0] = res;
+ tmpchan[1] = '\0';
+ pos = 1;
+ } else {
+ tmpchan[0] = '\0';
+ pos = 0;
+ }
+ }
+ }
+ args.extension = tmpchan;
+ if (!res) {
+ set_agentbycallerid(p->logincallerid, NULL);
+ if (!ast_strlen_zero(context) && !ast_strlen_zero(tmpchan))
+ snprintf(p->loginchan, sizeof(p->loginchan), "%s@%s", tmpchan, context);
+ else {
+ ast_copy_string(last_loginchan, p->loginchan, sizeof(last_loginchan));
+ ast_copy_string(p->loginchan, tmpchan, sizeof(p->loginchan));
+ }
+ p->acknowledged = 0;
+ if (ast_strlen_zero(p->loginchan)) {
+ login_state = 2;
+ filename = "agent-loggedoff";
+ } else {
+ if (chan->cid.cid_num) {
+ ast_copy_string(p->logincallerid, chan->cid.cid_num, sizeof(p->logincallerid));
+ set_agentbycallerid(p->logincallerid, p->agent);
+ } else
+ p->logincallerid[0] = '\0';
+ }
+
+ if(update_cdr && chan->cdr)
+ snprintf(chan->cdr->channel, sizeof(chan->cdr->channel), "Agent/%s", p->agent);
+
+ }
+ } else {
+ 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 (callbackmode && !res) {
+ /* Just say goodbye and be done with it */
+ if (!ast_strlen_zero(p->loginchan)) {
+ if (p->loginstart == 0)
+ time(&p->loginstart);
+ manager_event(EVENT_FLAG_AGENT, "Agentcallbacklogin",
+ "Agent: %s\r\n"
+ "Loginchan: %s\r\n"
+ "Uniqueid: %s\r\n",
+ p->agent, p->loginchan, chan->uniqueid);
+ ast_queue_log("NONE", chan->uniqueid, agent, "AGENTCALLBACKLOGIN", "%s", p->loginchan);
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Callback Agent '%s' logged in on %s\n", p->agent, p->loginchan);
+ ast_device_state_changed("Agent/%s", p->agent);
+ if (persistent_agents)
+ dump_agents();
+ } else {
+ logintime = time(NULL) - p->loginstart;
+ p->loginstart = 0;
+
+ agent_logoff_maintenance(p, last_loginchan, logintime, chan->uniqueid, NULL);
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Callback Agent '%s' logged out\n", p->agent);
+ }
+ AST_LIST_UNLOCK(&agents);
+ if (!res)
+ res = ast_safe_sleep(chan, 500);
+ ast_mutex_unlock(&p->lock);
+ } else 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);
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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);
+ if (p->app_lock_flag == 1) {
+ ast_cond_wait(&p->app_complete_cond, &p->app_lock);
+ }
+ ast_mutex_unlock(&p->app_lock);
+ ast_mutex_lock(&p->lock);
+ 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 );
+ 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->inherited_devicestate = -1;
+ }
+ 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);
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_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_cond_destroy(&p->app_complete_cond);
+ 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 (unlock_channel) {
+ ast_channel_unlock(chan);
+ }
+ }
+ 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);
+
+ /* AgentLogin() exit */
+ if (!callbackmode) {
+ ast_module_user_remove(u);
+ return -1;
+ } else { /* AgentCallbackLogin() exit*/
+ /* Set variables */
+ if (login_state > 0) {
+ pbx_builtin_setvar_helper(chan, "AGENTNUMBER", user);
+ if (login_state==1) {
+ pbx_builtin_setvar_helper(chan, "AGENTSTATUS", "on");
+ pbx_builtin_setvar_helper(chan, "AGENTEXTEN", args.extension);
+ } else
+ pbx_builtin_setvar_helper(chan, "AGENTSTATUS", "off");
+ } else {
+ pbx_builtin_setvar_helper(chan, "AGENTSTATUS", "fail");
+ }
+ if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 1, chan->cid.cid_num)) {
+ ast_module_user_remove(u);
+ return 0;
+ }
+ /* Do we need to play agent-goodbye now that we will be hanging up? */
+ if (play_announcement) {
+ if (!res)
+ res = ast_safe_sleep(chan, 1000);
+ res = ast_streamfile(chan, agent_goodbye, chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+ if (!res)
+ res = ast_safe_sleep(chan, 1000);
+ }
+ }
+
+ ast_module_user_remove(u);
+
+ /* We should never get here if next priority exists when in callbackmode */
+ return -1;
+}
+
+/*!
+ * Called by the AgentLogin application (from the dial plan).
+ *
+ * \param chan
+ * \param data
+ * \returns
+ * \sa callback_login_exec(), agentmonitoroutgoing_exec(), load_module().
+ */
+static int login_exec(struct ast_channel *chan, void *data)
+{
+ return __login_exec(chan, data, 0);
+}
+
+static void callback_deprecated(void)
+{
+ static int depwarning = 0;
+
+ if (!depwarning) {
+ depwarning = 1;
+
+ ast_log(LOG_WARNING, "AgentCallbackLogin is deprecated and will be removed in a future release.\n");
+ ast_log(LOG_WARNING, "See doc/queues-with-callback-members.txt for an example of how to achieve\n");
+ ast_log(LOG_WARNING, "the same functionality using only dialplan logic.\n");
+ }
+}
+
+/*!
+ * Called by the AgentCallbackLogin application (from the dial plan).
+ *
+ * \param chan
+ * \param data
+ * \returns
+ * \sa login_exec(), agentmonitoroutgoing_exec(), load_module().
+ */
+static int callback_exec(struct ast_channel *chan, void *data)
+{
+ callback_deprecated();
+
+ return __login_exec(chan, data, 1);
+}
+
+/*!
+ * Sets an agent as logged in by callback 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(), action_agent_logoff(), load_module().
+ */
+static int action_agent_callback_login(struct mansession *s, const struct message *m)
+{
+ const char *agent = astman_get_header(m, "Agent");
+ const char *exten = astman_get_header(m, "Exten");
+ const char *context = astman_get_header(m, "Context");
+ const char *wrapuptime_s = astman_get_header(m, "WrapupTime");
+ const char *ackcall_s = astman_get_header(m, "AckCall");
+ struct agent_pvt *p;
+ int login_state = 0;
+
+ callback_deprecated();
+
+ if (ast_strlen_zero(agent)) {
+ astman_send_error(s, m, "No agent specified");
+ return 0;
+ }
+
+ if (ast_strlen_zero(exten)) {
+ astman_send_error(s, m, "No extension specified");
+ return 0;
+ }
+
+ AST_LIST_LOCK(&agents);
+ AST_LIST_TRAVERSE(&agents, p, list) {
+ if (strcmp(p->agent, agent) || p->pending)
+ continue;
+ if (p->chan) {
+ login_state = 2; /* already logged in (and on the phone)*/
+ break;
+ }
+ ast_mutex_lock(&p->lock);
+ login_state = 1; /* Successful Login */
+
+ if (ast_strlen_zero(context))
+ ast_copy_string(p->loginchan, exten, sizeof(p->loginchan));
+ else
+ snprintf(p->loginchan, sizeof(p->loginchan), "%s@%s", exten, context);
+
+ if (!ast_strlen_zero(wrapuptime_s)) {
+ p->wrapuptime = atoi(wrapuptime_s);
+ if (p->wrapuptime < 0)
+ p->wrapuptime = 0;
+ }
+
+ if (!strcasecmp(ackcall_s, "always"))
+ p->ackcall = 2;
+ else if (ast_true(ackcall_s))
+ p->ackcall = 1;
+ else
+ p->ackcall = 0;
+
+ if (p->loginstart == 0)
+ time(&p->loginstart);
+ manager_event(EVENT_FLAG_AGENT, "Agentcallbacklogin",
+ "Agent: %s\r\n"
+ "Loginchan: %s\r\n",
+ p->agent, p->loginchan);
+ ast_queue_log("NONE", "NONE", agent, "AGENTCALLBACKLOGIN", "%s", p->loginchan);
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Callback Agent '%s' logged in on %s\n", p->agent, p->loginchan);
+ ast_device_state_changed("Agent/%s", p->agent);
+ ast_mutex_unlock(&p->lock);
+ if (persistent_agents)
+ dump_agents();
+ }
+ AST_LIST_UNLOCK(&agents);
+
+ if (login_state == 1)
+ astman_send_ack(s, m, "Agent logged in");
+ else if (login_state == 0)
+ astman_send_error(s, m, "No such agent");
+ else if (login_state == 2)
+ astman_send_error(s, m, "Agent already logged in");
+
+ return 0;
+}
+
+/*!
+ * \brief Called by the AgentMonitorOutgoing application (from the dial plan).
+ *
+ * \param chan
+ * \param data
+ * \returns
+ * \sa login_exec(), callback_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");
+ }
+ /* check if there is n + 101 priority */
+ /*! \todo XXX Needs to check option priorityjump etc etc */
+ if (res) {
+ if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 101, chan->cid.cid_num)) {
+ chan->priority+=100;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Going to %d priority because there is no callerid or the agentid cannot be found.\n",chan->priority);
+ } else 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 if (option_debug)
+ ast_log(LOG_DEBUG, "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)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 waitforagent=0;
+ 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);
+ 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))) {
+ if (p->owner) {
+ if (res != AST_DEVICE_INUSE)
+ res = AST_DEVICE_BUSY;
+ } else if (p->inherited_devicestate > -1) {
+ res = p->inherited_devicestate;
+ } 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;
+}
+
+/*!
+ * \note This function expects the agent list to be locked
+ */
+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, 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";
+
+ AST_LIST_LOCK(&agents);
+
+ if (!(agent = find_agent(args.agentid))) {
+ AST_LIST_UNLOCK(&agents);
+ 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);
+
+ AST_LIST_UNLOCK(&agents);
+
+ 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 -1;
+ }
+ /* Read in the config */
+ if (!read_agent_config())
+ return AST_MODULE_LOAD_DECLINE;
+ if (persistent_agents)
+ reload_agents();
+ /* Dialplan applications */
+ ast_register_application(app, login_exec, synopsis, descrip);
+ ast_register_application(app2, callback_exec, synopsis2, descrip2);
+ 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);
+ ast_manager_register2("AgentCallbackLogin", EVENT_FLAG_AGENT, action_agent_callback_login, "Sets an agent as logged in by callback", mandescr_agent_callback_login);
+
+ /* CLI Commands */
+ ast_cli_register_multiple(cli_agents, sizeof(cli_agents) / sizeof(struct ast_cli_entry));
+
+ /* Dialplan Functions */
+ ast_custom_function_register(&agent_function);
+
+ ast_devstate_add(agent_devicestate_cb, NULL);
+
+ return 0;
+}
+
+static int reload(void)
+{
+ read_agent_config();
+ 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);
+ /* Delete devicestate subscription */
+ ast_devstate_del(agent_devicestate_cb, NULL);
+ /* 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(app2);
+ ast_unregister_application(app3);
+ /* Unregister manager command */
+ ast_manager_unregister("Agents");
+ ast_manager_unregister("AgentLogoff");
+ ast_manager_unregister("AgentCallbackLogin");
+ /* 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);
+ free(p);
+ }
+ AST_LIST_UNLOCK(&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/1.4.23-rc4/channels/chan_alsa.c b/1.4.23-rc4/channels/chan_alsa.c
new file mode 100644
index 000000000..03db26b6a
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_alsa.c
@@ -0,0 +1,1391 @@
+/*
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.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/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/options.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 "busy_tone.h"
+#include "ring_tone.h"
+#include "ring10.h"
+#include "answer.h"
+
+#ifdef ALSA_MONITOR
+#include "alsa-monitor.h"
+#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;
+
+#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 char indevname[50] = ALSA_INDEV;
+static char outdevname[50] = ALSA_OUTDEV;
+
+#if 0
+static struct timeval lasttime;
+#endif
+
+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 short silence[FRAME_SIZE] = { 0, };
+
+struct sound {
+ int ind;
+ short *data;
+ int datalen;
+ int samplen;
+ int silencelen;
+ int repeat;
+};
+
+static struct sound sounds[] = {
+ {AST_CONTROL_RINGING, ringtone, sizeof(ringtone) / 2, 16000, 32000, 1},
+ {AST_CONTROL_BUSY, busy, sizeof(busy) / 2, 4000, 4000, 1},
+ {AST_CONTROL_CONGESTION, busy, sizeof(busy) / 2, 2000, 2000, 1},
+ {AST_CONTROL_RING, ring10, sizeof(ring10) / 2, 16000, 32000, 1},
+ {AST_CONTROL_ANSWER, answer, sizeof(answer) / 2, 2200, 0, 0},
+};
+
+/* Sound command pipe */
+static int sndcmd[2];
+
+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];
+#if 0
+ snd_pcm_t *card;
+#endif
+ 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. */
+
+pthread_t sthread;
+
+#define MAX_BUFFER_SIZE 100
+
+/* File descriptors for sound device */
+static int readdev = -1;
+static int writedev = -1;
+
+static int autoanswer = 1;
+
+static int cursound = -1;
+static int sampsent = 0;
+static int silencelen = 0;
+static int offset = 0;
+static int nosound = 0;
+
+/* ZZ */
+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 int send_sound(void)
+{
+ short myframe[FRAME_SIZE];
+ int total = FRAME_SIZE;
+ short *frame = NULL;
+ int amt = 0, res, myoff;
+ snd_pcm_state_t state;
+
+ if (cursound == -1)
+ return 0;
+
+ res = total;
+ if (sampsent < sounds[cursound].samplen) {
+ myoff = 0;
+ while (total) {
+ amt = total;
+ if (amt > (sounds[cursound].datalen - offset))
+ amt = sounds[cursound].datalen - offset;
+ memcpy(myframe + myoff, sounds[cursound].data + offset, amt * 2);
+ total -= amt;
+ offset += amt;
+ sampsent += amt;
+ myoff += amt;
+ if (offset >= sounds[cursound].datalen)
+ offset = 0;
+ }
+ /* Set it up for silence */
+ if (sampsent >= sounds[cursound].samplen)
+ silencelen = sounds[cursound].silencelen;
+ frame = myframe;
+ } else {
+ if (silencelen > 0) {
+ frame = silence;
+ silencelen -= res;
+ } else {
+ if (sounds[cursound].repeat) {
+ /* Start over */
+ sampsent = 0;
+ offset = 0;
+ } else {
+ cursound = -1;
+ nosound = 0;
+ }
+ return 0;
+ }
+ }
+
+ if (res == 0 || !frame)
+ return 0;
+
+#ifdef ALSA_MONITOR
+ alsa_monitor_write((char *) frame, res * 2);
+#endif
+ state = snd_pcm_state(alsa.ocard);
+ if (state == SND_PCM_STATE_XRUN)
+ snd_pcm_prepare(alsa.ocard);
+ while ((res = snd_pcm_writei(alsa.ocard, frame, res)) == -EAGAIN) {
+ usleep(1);
+ }
+ if (res > 0)
+ return 0;
+ return 0;
+}
+
+static void *sound_thread(void *unused)
+{
+ fd_set rfds;
+ fd_set wfds;
+ int max, res;
+
+ for (;;) {
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ max = sndcmd[0];
+ FD_SET(sndcmd[0], &rfds);
+ if (cursound > -1) {
+ FD_SET(writedev, &wfds);
+ if (writedev > max)
+ max = writedev;
+ }
+#ifdef ALSA_MONITOR
+ if (!alsa.owner) {
+ FD_SET(readdev, &rfds);
+ if (readdev > max)
+ max = readdev;
+ }
+#endif
+ res = ast_select(max + 1, &rfds, &wfds, NULL, NULL);
+ if (res < 1) {
+ ast_log(LOG_WARNING, "select failed: %s\n", strerror(errno));
+ continue;
+ }
+#ifdef ALSA_MONITOR
+ if (FD_ISSET(readdev, &rfds)) {
+ /* Keep the pipe going with read audio */
+ snd_pcm_state_t state;
+ short buf[FRAME_SIZE];
+ int r;
+
+ state = snd_pcm_state(alsa.ocard);
+ if (state == SND_PCM_STATE_XRUN) {
+ snd_pcm_prepare(alsa.ocard);
+ }
+ r = snd_pcm_readi(alsa.icard, buf, FRAME_SIZE);
+ 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
+ alsa_monitor_read((char *) buf, r * 2);
+ }
+#endif
+ if (FD_ISSET(sndcmd[0], &rfds)) {
+ if (read(sndcmd[0], &cursound, sizeof(cursound)) < 0) {
+ ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
+ }
+ silencelen = 0;
+ offset = 0;
+ sampsent = 0;
+ }
+ if (FD_ISSET(writedev, &wfds))
+ if (send_sound())
+ ast_log(LOG_WARNING, "Failed to write sound\n");
+ }
+ /* Never reached */
+ return NULL;
+}
+
+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;
+ /* int period_bytes = 0; */
+ snd_pcm_uframes_t buffer_size = 0;
+
+ unsigned int rate = DESIRED_RATE;
+#if 0
+ unsigned int per_min = 1;
+#endif
+ /* unsigned int per_max = 8; */
+ snd_pcm_uframes_t start_threshold, stop_threshold;
+
+ err = snd_pcm_open(&handle, dev, stream, SND_PCM_NONBLOCK);
+ if (err < 0) {
+ ast_log(LOG_ERROR, "snd_pcm_open failed: %s\n", snd_strerror(err));
+ return NULL;
+ } else
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Buffer size is set to %d frames\n", err);
+
+#if 0
+ direction = 0;
+ err = snd_pcm_hw_params_set_periods_min(handle, hwparams, &per_min, &direction);
+ if (err < 0)
+ ast_log(LOG_ERROR, "periods_min: %s\n", snd_strerror(err));
+
+ err = snd_pcm_hw_params_set_periods_max(handle, hwparams, &per_max, 0);
+ if (err < 0)
+ ast_log(LOG_ERROR, "periods_max: %s\n", snd_strerror(err));
+#endif
+
+ 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 1
+ 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));
+#endif
+
+#if 1
+ 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));
+#endif
+#if 0
+ err = snd_pcm_sw_params_set_xfer_align(handle, swparams, PERIOD_FRAMES);
+ if (err < 0)
+ ast_log(LOG_ERROR, "Unable to set xfer alignment: %s\n", snd_strerror(err));
+#endif
+
+#if 0
+ err = snd_pcm_sw_params_set_silence_threshold(handle, swparams, silencethreshold);
+ if (err < 0)
+ ast_log(LOG_ERROR, "Unable to set silence threshold: %s\n", snd_strerror(err));
+#endif
+ 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_log(LOG_DEBUG, "Can't handle more than one device\n");
+
+ snd_pcm_poll_descriptors(handle, &pfd, err);
+ ast_log(LOG_DEBUG, "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_mutex_trylock(&alsa.owner->lock)) {
+ DEADLOCK_AVOIDANCE(&alsalock);
+ }
+}
+
+static int alsa_call(struct ast_channel *c, char *dest, int timeout)
+{
+ int res = 3;
+ 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_mutex_unlock(&alsa.owner->lock);
+ }
+ } 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_mutex_unlock(&alsa.owner->lock);
+ }
+ if (write(sndcmd[1], &res, sizeof(res)) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ }
+ snd_pcm_prepare(alsa.icard);
+ snd_pcm_start(alsa.icard);
+ ast_mutex_unlock(&alsalock);
+ return 0;
+}
+
+static void answer_sound(void)
+{
+ int res;
+
+ nosound = 1;
+ res = 4;
+ if (write(sndcmd[1], &res, sizeof(res)) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+}
+
+static int alsa_answer(struct ast_channel *c)
+{
+ ast_mutex_lock(&alsalock);
+ ast_verbose(" << Console call has been answered >> \n");
+ answer_sound();
+ ast_setstate(c, AST_STATE_UP);
+ cursound = -1;
+ snd_pcm_prepare(alsa.icard);
+ snd_pcm_start(alsa.icard);
+ ast_mutex_unlock(&alsalock);
+ return 0;
+}
+
+static int alsa_hangup(struct ast_channel *c)
+{
+ int res;
+ ast_mutex_lock(&alsalock);
+ cursound = -1;
+ c->tech_pvt = NULL;
+ alsa.owner = NULL;
+ ast_verbose(" << Hangup on console >> \n");
+ ast_module_unref(ast_module_info->self);
+ if (hookstate) {
+ hookstate = 0;
+ if (!autoanswer) {
+ /* Congestion noise */
+ res = 2;
+ if (write(sndcmd[1], &res, sizeof(res)) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ }
+ }
+ 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;
+
+ /* Immediately return if no sound is enabled */
+ if (nosound)
+ return 0;
+
+ ast_mutex_lock(&alsalock);
+ /* Stop any currently playing sound */
+ if (cursound != -1) {
+ snd_pcm_drop(alsa.ocard);
+ snd_pcm_prepare(alsa.ocard);
+ cursound = -1;
+ }
+
+
+ /* 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;
+#ifdef ALSA_MONITOR
+ alsa_monitor_write(sizbuf, len);
+#endif
+ state = snd_pcm_state(alsa.ocard);
+ if (state == SND_PCM_STATE_XRUN)
+ snd_pcm_prepare(alsa.ocard);
+ while ((res = snd_pcm_writei(alsa.ocard, sizbuf, len / 2)) == -EAGAIN) {
+ usleep(1);
+ }
+ if (res == -EPIPE) {
+#if DEBUG
+ ast_log(LOG_DEBUG, "XRUN write\n");
+#endif
+ snd_pcm_prepare(alsa.ocard);
+ while ((res = snd_pcm_writei(alsa.ocard, sizbuf, len / 2)) == -EAGAIN) {
+ usleep(1);
+ }
+ 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);
+ if (res > 0)
+ res = 0;
+ return 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);
+ /* Acknowledge any pending cmd */
+ 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;
+#ifdef ALSA_MONITOR
+ alsa_monitor_read((char *) buf, FRAME_SIZE * 2);
+#endif
+
+ }
+ 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:
+ res = 1;
+ break;
+ case AST_CONTROL_CONGESTION:
+ res = 2;
+ break;
+ case AST_CONTROL_RINGING:
+ case AST_CONTROL_PROGRESS:
+ break;
+ case -1:
+ res = -1;
+ break;
+ case AST_CONTROL_VIDUPDATE:
+ res = -1;
+ 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;
+ case AST_CONTROL_SRCUPDATE:
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, chan->name);
+ res = -1;
+ }
+
+ if (res > -1) {
+ if (write(sndcmd[1], &res, sizeof(res)) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ }
+
+ 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;
+ tmp->fds[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;
+
+ format &= AST_FORMAT_SLINEAR;
+ if (!format) {
+ 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 int console_autoanswer_deprecated(int fd, int argc, char *argv[])
+{
+ int res = RESULT_SUCCESS;
+
+ if ((argc != 1) && (argc != 2))
+ return RESULT_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (argc == 1) {
+ ast_cli(fd, "Auto answer is %s.\n", autoanswer ? "on" : "off");
+ } else {
+ if (!strcasecmp(argv[1], "on"))
+ autoanswer = -1;
+ else if (!strcasecmp(argv[1], "off"))
+ autoanswer = 0;
+ else
+ res = RESULT_SHOWUSAGE;
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static int console_autoanswer(int fd, int argc, char *argv[])
+{
+ int res = RESULT_SUCCESS;;
+ if ((argc != 2) && (argc != 3))
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&alsalock);
+ if (argc == 2) {
+ ast_cli(fd, "Auto answer is %s.\n", autoanswer ? "on" : "off");
+ } else {
+ if (!strcasecmp(argv[2], "on"))
+ autoanswer = -1;
+ else if (!strcasecmp(argv[2], "off"))
+ autoanswer = 0;
+ else
+ res = RESULT_SHOWUSAGE;
+ }
+ ast_mutex_unlock(&alsalock);
+ return res;
+}
+
+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 const char autoanswer_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";
+
+static int console_answer_deprecated(int fd, int argc, char *argv[])
+{
+ int res = RESULT_SUCCESS;
+
+ if (argc != 1)
+ return RESULT_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (!alsa.owner) {
+ ast_cli(fd, "No one is calling us\n");
+ res = RESULT_FAILURE;
+ } else {
+ hookstate = 1;
+ cursound = -1;
+ grab_owner();
+ if (alsa.owner) {
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
+ ast_queue_frame(alsa.owner, &f);
+ ast_mutex_unlock(&alsa.owner->lock);
+ }
+ answer_sound();
+ }
+
+ snd_pcm_prepare(alsa.icard);
+ snd_pcm_start(alsa.icard);
+
+ ast_mutex_unlock(&alsalock);
+
+ return RESULT_SUCCESS;
+}
+
+static int console_answer(int fd, int argc, char *argv[])
+{
+ int res = RESULT_SUCCESS;
+
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (!alsa.owner) {
+ ast_cli(fd, "No one is calling us\n");
+ res = RESULT_FAILURE;
+ } else {
+ hookstate = 1;
+ cursound = -1;
+ grab_owner();
+ if (alsa.owner) {
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
+ ast_queue_frame(alsa.owner, &f);
+ ast_mutex_unlock(&alsa.owner->lock);
+ }
+ answer_sound();
+ }
+
+ snd_pcm_prepare(alsa.icard);
+ snd_pcm_start(alsa.icard);
+
+ ast_mutex_unlock(&alsalock);
+
+ return RESULT_SUCCESS;
+}
+
+static char sendtext_usage[] =
+ "Usage: console send text <message>\n"
+ " Sends a text message for display on the remote terminal.\n";
+
+static int console_sendtext_deprecated(int fd, int argc, char *argv[])
+{
+ int tmparg = 2;
+ int res = RESULT_SUCCESS;
+
+ if (argc < 2)
+ return RESULT_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (!alsa.owner) {
+ ast_cli(fd, "No one is calling us\n");
+ res = RESULT_FAILURE;
+ } else {
+ struct ast_frame f = { AST_FRAME_TEXT, 0 };
+ char text2send[256] = "";
+ text2send[0] = '\0';
+ while (tmparg < argc) {
+ strncat(text2send, 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_mutex_unlock(&alsa.owner->lock);
+ }
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static int console_sendtext(int fd, int argc, char *argv[])
+{
+ int tmparg = 3;
+ int res = RESULT_SUCCESS;
+
+ if (argc < 3)
+ return RESULT_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (!alsa.owner) {
+ ast_cli(fd, "No one is calling us\n");
+ res = RESULT_FAILURE;
+ } else {
+ struct ast_frame f = { AST_FRAME_TEXT, 0 };
+ char text2send[256] = "";
+ text2send[0] = '\0';
+ while (tmparg < argc) {
+ strncat(text2send, 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_mutex_unlock(&alsa.owner->lock);
+ }
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static char answer_usage[] =
+ "Usage: console answer\n"
+ " Answers an incoming call on the console (ALSA) channel.\n";
+
+static int console_hangup_deprecated(int fd, int argc, char *argv[])
+{
+ int res = RESULT_SUCCESS;
+
+ if (argc != 1)
+ return RESULT_SHOWUSAGE;
+
+ cursound = -1;
+
+ ast_mutex_lock(&alsalock);
+
+ if (!alsa.owner && !hookstate) {
+ ast_cli(fd, "No call to hangup up\n");
+ res = RESULT_FAILURE;
+ } else {
+ hookstate = 0;
+ grab_owner();
+ if (alsa.owner) {
+ ast_queue_hangup(alsa.owner);
+ ast_mutex_unlock(&alsa.owner->lock);
+ }
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static int console_hangup(int fd, int argc, char *argv[])
+{
+ int res = RESULT_SUCCESS;
+
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+
+ cursound = -1;
+
+ ast_mutex_lock(&alsalock);
+
+ if (!alsa.owner && !hookstate) {
+ ast_cli(fd, "No call to hangup up\n");
+ res = RESULT_FAILURE;
+ } else {
+ hookstate = 0;
+ grab_owner();
+ if (alsa.owner) {
+ ast_queue_hangup(alsa.owner);
+ ast_mutex_unlock(&alsa.owner->lock);
+ }
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static char hangup_usage[] =
+ "Usage: console hangup\n"
+ " Hangs up any call currently placed on the console.\n";
+
+static int console_dial_deprecated(int fd, int argc, char *argv[])
+{
+ char tmp[256], *tmp2;
+ char *mye, *myc;
+ char *d;
+ int res = RESULT_SUCCESS;
+
+ if ((argc != 1) && (argc != 2))
+ return RESULT_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (alsa.owner) {
+ if (argc == 2) {
+ d = argv[1];
+ grab_owner();
+ if (alsa.owner) {
+ struct ast_frame f = { AST_FRAME_DTMF };
+ while (*d) {
+ f.subclass = *d;
+ ast_queue_frame(alsa.owner, &f);
+ d++;
+ }
+ ast_mutex_unlock(&alsa.owner->lock);
+ }
+ } else {
+ ast_cli(fd, "You're already in a call. You can use this only to dial digits until you hangup\n");
+ res = RESULT_FAILURE;
+ }
+ } else {
+ mye = exten;
+ myc = context;
+ if (argc == 2) {
+ char *stringp = NULL;
+ ast_copy_string(tmp, argv[1], 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(fd, "No such extension '%s' in context '%s'\n", mye, myc);
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static int console_dial(int fd, int argc, char *argv[])
+{
+ char tmp[256], *tmp2;
+ char *mye, *myc;
+ char *d;
+ int res = RESULT_SUCCESS;
+
+ if ((argc != 2) && (argc != 3))
+ return RESULT_SHOWUSAGE;
+
+ ast_mutex_lock(&alsalock);
+
+ if (alsa.owner) {
+ if (argc == 3) {
+ d = argv[2];
+ grab_owner();
+ if (alsa.owner) {
+ struct ast_frame f = { AST_FRAME_DTMF };
+ while (*d) {
+ f.subclass = *d;
+ ast_queue_frame(alsa.owner, &f);
+ d++;
+ }
+ ast_mutex_unlock(&alsa.owner->lock);
+ }
+ } else {
+ ast_cli(fd, "You're already in a call. You can use this only to dial digits until you hangup\n");
+ res = RESULT_FAILURE;
+ }
+ } else {
+ mye = exten;
+ myc = context;
+ if (argc == 3) {
+ char *stringp = NULL;
+ ast_copy_string(tmp, 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(fd, "No such extension '%s' in context '%s'\n", mye, myc);
+ }
+
+ ast_mutex_unlock(&alsalock);
+
+ return res;
+}
+
+static char dial_usage[] =
+ "Usage: console dial [extension[@context]]\n"
+ " Dials a given extension (and context if specified)\n";
+
+static struct ast_cli_entry cli_alsa_answer_deprecated = {
+ { "answer", NULL },
+ console_answer_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_alsa_hangup_deprecated = {
+ { "hangup", NULL },
+ console_hangup_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_alsa_dial_deprecated = {
+ { "dial", NULL },
+ console_dial_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_alsa_send_text_deprecated = {
+ { "send", "text", NULL },
+ console_sendtext_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_alsa_autoanswer_deprecated = {
+ { "autoanswer", NULL },
+ console_autoanswer_deprecated, NULL,
+ NULL, autoanswer_complete };
+
+static struct ast_cli_entry cli_alsa[] = {
+ { { "console", "answer", NULL },
+ console_answer, "Answer an incoming console call",
+ answer_usage, NULL, &cli_alsa_answer_deprecated },
+
+ { { "console", "hangup", NULL },
+ console_hangup, "Hangup a call on the console",
+ hangup_usage, NULL, &cli_alsa_hangup_deprecated },
+
+ { { "console", "dial", NULL },
+ console_dial, "Dial an extension on the console",
+ dial_usage, NULL, &cli_alsa_dial_deprecated },
+
+ { { "console", "send", "text", NULL },
+ console_sendtext, "Send text to the remote device",
+ sendtext_usage, NULL, &cli_alsa_send_text_deprecated },
+
+ { { "console", "autoanswer", NULL },
+ console_autoanswer, "Sets/displays autoanswer",
+ autoanswer_usage, autoanswer_complete, &cli_alsa_autoanswer_deprecated },
+};
+
+static int load_module(void)
+{
+ int res;
+ struct ast_config *cfg;
+ struct ast_variable *v;
+
+ /* 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))) {
+ 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);
+ }
+ res = pipe(sndcmd);
+ if (res) {
+ ast_log(LOG_ERROR, "Unable to create pipe\n");
+ return -1;
+ }
+ res = soundcard_init();
+ if (res < 0) {
+ if (option_verbose > 1) {
+ ast_verbose(VERBOSE_PREFIX_2 "No sound card detected -- console channel will be unavailable\n");
+ ast_verbose(VERBOSE_PREFIX_2 "Turn off ALSA support by adding 'noload=chan_alsa.so' in /etc/asterisk/modules.conf\n");
+ }
+ return 0;
+ }
+
+ res = ast_channel_register(&alsa_tech);
+ if (res < 0) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'Console'\n");
+ return -1;
+ }
+ ast_cli_register_multiple(cli_alsa, sizeof(cli_alsa) / sizeof(struct ast_cli_entry));
+
+ ast_pthread_create_background(&sthread, NULL, sound_thread, NULL);
+#ifdef ALSA_MONITOR
+ if (alsa_monitor_start())
+ ast_log(LOG_ERROR, "Problem starting Monitoring\n");
+#endif
+ return 0;
+}
+
+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 (sndcmd[0] > 0) {
+ close(sndcmd[0]);
+ close(sndcmd[1]);
+ }
+ 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/1.4.23-rc4/channels/chan_dahdi.c b/1.4.23-rc4/channels/chan_dahdi.c
new file mode 100644
index 000000000..81498b372
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_dahdi.c
@@ -0,0 +1,11992 @@
+/*
+ * 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 DAHDI Pseudo TDM interface
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * Connects to the DAHDI 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 DAHDI channel.
+ *
+ * \par See also
+ * \arg \ref Config_dahdi
+ *
+ * \ingroup channel_drivers
+ *
+ * \todo Deprecate the "musiconhold" configuration option post 1.4
+ */
+
+/*** MODULEINFO
+ <depend>res_smdi</depend>
+ <depend>dahdi</depend>
+ <depend>tonezone</depend>
+ <depend>res_features</depend>
+ <use>pri</use>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <string.h>
+#ifdef __NetBSD__
+#include <pthread.h>
+#include <signal.h>
+#else
+#include <sys/signal.h>
+#endif
+#include <errno.h>
+#include <stdlib.h>
+#if !defined(SOLARIS) && !defined(__FreeBSD__)
+#include <stdint.h>
+#endif
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <math.h>
+#include <ctype.h>
+
+#ifdef HAVE_PRI
+#include <libpri.h>
+#endif
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.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"
+#define SMDI_MD_WAIT_TIMEOUT 1500 /* 1.5 seconds */
+
+#include "asterisk/dahdi_compat.h"
+#include "asterisk/tonezone_compat.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;
+
+#ifndef DAHDI_TONEDETECT
+/* Work around older code with no tone detect */
+#define DAHDI_EVENT_DTMFDOWN 0
+#define DAHDI_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 DAHDI_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 == DAHDI_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[] = "DAHDI Telephony Driver"
+#ifdef HAVE_PRI
+ " w/PRI"
+#endif
+;
+
+#define SIG_EM DAHDI_SIG_EM
+#define SIG_EMWINK (0x0100000 | DAHDI_SIG_EM)
+#define SIG_FEATD (0x0200000 | DAHDI_SIG_EM)
+#define SIG_FEATDMF (0x0400000 | DAHDI_SIG_EM)
+#define SIG_FEATB (0x0800000 | DAHDI_SIG_EM)
+#define SIG_E911 (0x1000000 | DAHDI_SIG_EM)
+#define SIG_FEATDMF_TA (0x2000000 | DAHDI_SIG_EM)
+#define SIG_FGC_CAMA (0x4000000 | DAHDI_SIG_EM)
+#define SIG_FGC_CAMAMF (0x8000000 | DAHDI_SIG_EM)
+#define SIG_FXSLS DAHDI_SIG_FXSLS
+#define SIG_FXSGS DAHDI_SIG_FXSGS
+#define SIG_FXSKS DAHDI_SIG_FXSKS
+#define SIG_FXOLS DAHDI_SIG_FXOLS
+#define SIG_FXOGS DAHDI_SIG_FXOGS
+#define SIG_FXOKS DAHDI_SIG_FXOKS
+#define SIG_PRI DAHDI_SIG_CLEAR
+#define SIG_SF DAHDI_SIG_SF
+#define SIG_SFWINK (0x0100000 | DAHDI_SIG_SF)
+#define SIG_SF_FEATD (0x0200000 | DAHDI_SIG_SF)
+#define SIG_SF_FEATDMF (0x0400000 | DAHDI_SIG_SF)
+#define SIG_SF_FEATB (0x0800000 | DAHDI_SIG_SF)
+#define SIG_EM_E1 DAHDI_SIG_EM_E1
+#define SIG_GR303FXOKS (0x0100000 | DAHDI_SIG_FXOKS)
+#define SIG_GR303FXSKS (0x0100000 | DAHDI_SIG_FXSKS)
+
+#define NUM_SPANS 32
+#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)
+
+static char defaultcic[64] = "";
+static char defaultozz[64] = "";
+
+static char progzone[10] = "";
+
+static int distinctiveringaftercid = 0;
+
+static int numbufs = 4;
+
+#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 dahdi_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 ast_cond_t ss_thread_complete;
+AST_MUTEX_DEFINE_STATIC(ss_thread_lock);
+AST_MUTEX_DEFINE_STATIC(restart_lock);
+static int ss_thread_count = 0;
+static int num_restart_pending = 0;
+
+static int restart_monitor(void);
+
+static enum ast_bridge_result dahdi_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
+
+static int dahdi_sendtext(struct ast_channel *c, const char *text);
+
+/*! \brief Avoid the silly dahdi_getevent which ignores a bunch of events */
+static inline int dahdi_get_event(int fd)
+{
+ int j;
+ if (ioctl(fd, DAHDI_GETEVENT, &j) == -1)
+ return -1;
+ return j;
+}
+
+/*! \brief Avoid the silly dahdi_waitevent which ignores a bunch of events */
+static inline int dahdi_wait_event(int fd)
+{
+ int i, j = 0;
+ i = DAHDI_IOMUX_SIGEVENT;
+ if (ioctl(fd, DAHDI_IOMUX, &i) == -1)
+ return -1;
+ if (ioctl(fd, DAHDI_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) /*!< 10,000 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) /*!< 8,000 ms */
+
+struct dahdi_pvt;
+
+static int ringt_base = DEFAULT_RINGT;
+
+#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 dahdi_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;
+#ifdef HAVE_PRI_INBANDDISCONNECT
+ unsigned int inbanddisconnect:1; /*!< Should we support inband audio after receiving DISCONNECT? */
+#endif
+ time_t lastreset; /*!< time when unused channels were last reset */
+ long resetinterval; /*!< Interval (in seconds) for resetting unused channels */
+ struct dahdi_pvt *pvts[MAX_CHANNELS]; /*!< Member channel pvt structs */
+ struct dahdi_pvt *crvs; /*!< Member CRV structs */
+ struct dahdi_pvt *crvend; /*!< Pointer to end of CRV structs */
+};
+
+
+static struct dahdi_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 dahdi_pri *pri)
+{
+ ast_mutex_unlock(&pri->lock);
+}
+
+#else
+/*! Shut up the compiler */
+struct dahdi_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 dahdi_distRings drings;
+
+struct distRingData {
+ int ring[3];
+};
+struct ringContextData {
+ char contextData[AST_MAX_CONTEXT];
+};
+struct dahdi_distRings {
+ struct distRingData ringnum[3];
+ struct ringContextData ringContext[3];
+};
+
+static char *subnames[] = {
+ "Real",
+ "Callwait",
+ "Threeway"
+};
+
+struct dahdi_subchannel {
+ int dfd;
+ 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;
+ struct dahdi_confinfo curconf;
+};
+
+#define CONF_USER_REAL (1 << 0)
+#define CONF_USER_THIRDCALL (1 << 1)
+
+#define MAX_SLAVES 4
+
+static struct dahdi_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 dahdi_subchannel sub_unused; /*!< Just a safety precaution */
+ struct dahdi_subchannel subs[3]; /*!< Sub-channels */
+ struct dahdi_confinfo saveconf; /*!< Saved conference info */
+
+ struct dahdi_pvt *slaves[MAX_SLAVES]; /*!< Slave to us (follows our conferencing) */
+ struct dahdi_pvt *master; /*!< Master to us (we follow their conferencing) */
+ int inconference; /*!< If our real should be in the conference */
+
+ int buf_no; /*!< Number of buffers */
+ int buf_policy; /*!< Buffer policy */
+ int sig; /*!< Signalling style */
+ int radio; /*!< radio type */
+ int outsigmod; /*!< Outbound Signalling style (modifier) */
+ int oprmode; /*!< "Operator Services" mode */
+ struct dahdi_pvt *oprpeer; /*!< "Operator Services" peer tech_pvt ptr */
+ float rxgain;
+ float txgain;
+ int tonezone; /*!< tone zone for this chan, or -1 for default */
+ struct dahdi_pvt *next; /*!< Next channel in list */
+ struct dahdi_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 unknown_alarm:1;
+ unsigned int mate:1; /*!< flag to say its in MATE mode */
+ unsigned int outgoing:1;
+ unsigned int overlapdial:1;
+ 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 restartpending:1; /*!< flag to ensure counted only once for restart */
+ 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 dahditrcallerid:1; /*!< should we use the callerid from incoming call on dahdi transfer or not */
+ unsigned int transfertobusy:1; /*!< allow flash-transfers to busy channels */
+#if defined(HAVE_PRI)
+ 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 dahdi_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];
+#ifdef PRI_ANI
+ char cid_ani[AST_MAX_EXTENSION];
+#endif
+ 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;
+ 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;
+ int echocancel;
+ 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 */
+ struct dahdi_dialoperation 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];
+ 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 dahdi_pri *pri;
+ struct dahdi_pvt *bearer;
+ struct dahdi_pvt *realcall;
+ q931_call *call;
+ int prioffset;
+ int logicalspan;
+#endif
+ int polarity;
+ int dsp_features;
+ char begindigit;
+} *iflist = NULL, *ifend = NULL;
+
+/*! \brief Channel configuration from chan_dahdi.conf .
+ * This struct is used for parsing the [channels] section of chan_dahdi.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 dahdi_chan_conf is used to configure the
+ * channel (struct dahdi_pvt)
+ *
+ * @seealso dahdi_chan_init for the default values.
+ */
+struct dahdi_chan_conf {
+ struct dahdi_pvt chan;
+#ifdef HAVE_PRI
+ struct dahdi_pri pri;
+#endif
+ struct dahdi_params timing;
+
+ char smdi_port[SMDI_MAX_FILENAME_LEN];
+};
+
+/** returns a new dahdi_chan_conf with default values (by-value) */
+static struct dahdi_chan_conf dahdi_chan_conf_default(void) {
+ /* recall that if a field is not included here it is initialized
+ * to 0 or equivalent
+ */
+ struct dahdi_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 = 3600
+ },
+#endif
+ .chan = {
+ .context = "default",
+ .cid_num = "",
+ .cid_name = "",
+ .mohinterpret = "default",
+ .mohsuggest = "",
+ .transfertobusy = 1,
+
+ .cid_signalling = CID_SIG_BELL,
+ .cid_start = CID_START_RING,
+ .dahditrcallerid = 0,
+ .use_callerid = 1,
+ .sig = -1,
+ .outsigmod = -1,
+
+ .tonezone = -1,
+
+ .echocancel = 1,
+
+ .busycount = 3,
+
+ .accountcode = "",
+
+ .mailbox = "",
+
+
+ .polarityonanswerdelay = 600,
+
+ .sendcalleridafter = DEFAULT_CIDRINGS,
+
+ .buf_policy = DAHDI_POLICY_IMMEDIATE,
+ .buf_no = numbufs
+ },
+ .timing = {
+ .prewinktime = -1,
+ .preflashtime = -1,
+ .winktime = -1,
+ .flashtime = -1,
+ .starttime = -1,
+ .rxwinktime = -1,
+ .rxflashtime = -1,
+ .debouncetime = -1
+ },
+ .smdi_port = "/dev/ttyS0",
+ };
+
+ return conf;
+}
+
+
+static struct ast_channel *dahdi_request(const char *type, int format, void *data, int *cause);
+static int dahdi_digit_begin(struct ast_channel *ast, char digit);
+static int dahdi_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int dahdi_sendtext(struct ast_channel *c, const char *text);
+static int dahdi_call(struct ast_channel *ast, char *rdest, int timeout);
+static int dahdi_hangup(struct ast_channel *ast);
+static int dahdi_answer(struct ast_channel *ast);
+static struct ast_frame *dahdi_read(struct ast_channel *ast);
+static int dahdi_write(struct ast_channel *ast, struct ast_frame *frame);
+static struct ast_frame *dahdi_exception(struct ast_channel *ast);
+static int dahdi_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen);
+static int dahdi_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static int dahdi_setoption(struct ast_channel *chan, int option, void *data, int datalen);
+static int dahdi_func_read(struct ast_channel *chan, char *function, char *data, char *buf, size_t len);
+
+static const struct ast_channel_tech dahdi_tech = {
+ .type = "DAHDI",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_SLINEAR | AST_FORMAT_ULAW | AST_FORMAT_ALAW,
+ .requester = dahdi_request,
+ .send_digit_begin = dahdi_digit_begin,
+ .send_digit_end = dahdi_digit_end,
+ .send_text = dahdi_sendtext,
+ .call = dahdi_call,
+ .hangup = dahdi_hangup,
+ .answer = dahdi_answer,
+ .read = dahdi_read,
+ .write = dahdi_write,
+ .bridge = dahdi_bridge,
+ .exception = dahdi_exception,
+ .indicate = dahdi_indicate,
+ .fixup = dahdi_fixup,
+ .setoption = dahdi_setoption,
+ .func_channel_read = dahdi_func_read,
+};
+
+static const struct ast_channel_tech zap_tech = {
+ .type = "Zap",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_SLINEAR | AST_FORMAT_ULAW | AST_FORMAT_ALAW,
+ .requester = dahdi_request,
+ .send_digit_begin = dahdi_digit_begin,
+ .send_digit_end = dahdi_digit_end,
+ .send_text = dahdi_sendtext,
+ .call = dahdi_call,
+ .hangup = dahdi_hangup,
+ .answer = dahdi_answer,
+ .read = dahdi_read,
+ .write = dahdi_write,
+ .bridge = dahdi_bridge,
+ .exception = dahdi_exception,
+ .indicate = dahdi_indicate,
+ .fixup = dahdi_fixup,
+ .setoption = dahdi_setoption,
+ .func_channel_read = dahdi_func_read,
+};
+
+static const struct ast_channel_tech *chan_tech;
+
+#ifdef HAVE_PRI
+#define GET_CHANNEL(p) ((p)->bearer ? (p)->bearer->channel : p->channel)
+#else
+#define GET_CHANNEL(p) ((p)->channel)
+#endif
+
+struct dahdi_pvt *round_robin[32];
+
+#ifdef HAVE_PRI
+static inline int pri_grab(struct dahdi_pvt *pvt, struct dahdi_pri *pri)
+{
+ int res;
+ /* Grab the lock first */
+ do {
+ res = ast_mutex_trylock(&pri->lock);
+ if (res) {
+ DEADLOCK_AVOIDANCE(&pvt->lock);
+ }
+ } while (res);
+ /* Then break the poll */
+ if (pri->master != AST_PTHREADT_NULL)
+ 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 dahdi_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 & __DAHDI_SIG_FXO) */)
+#define CANPROGRESSDETECT(p) (ISTRUNK(p) || (p->sig & (SIG_EM | SIG_EM_E1 | SIG_SF)) /* || (p->sig & __DAHDI_SIG_FXO) */)
+
+static int dahdi_get_index(struct ast_channel *ast, struct dahdi_pvt *p, int nullok)
+{
+ int res;
+ if (p->subs[SUB_REAL].owner == ast)
+ res = 0;
+ else if (p->subs[SUB_CALLWAIT].owner == ast)
+ res = 1;
+ else if (p->subs[SUB_THREEWAY].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 dahdi_pvt *p, int a, struct dahdi_pri *pri)
+#else
+static void wakeup_sub(struct dahdi_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_mutex_trylock(&p->subs[a].owner->lock)) {
+ DEADLOCK_AVOIDANCE(&p->lock);
+ } else {
+ ast_queue_frame(p->subs[a].owner, &ast_null_frame);
+ ast_mutex_unlock(&p->subs[a].owner->lock);
+ break;
+ }
+ } else
+ break;
+ }
+#ifdef HAVE_PRI
+ if (pri)
+ ast_mutex_lock(&pri->lock);
+#endif
+}
+
+#ifdef HAVE_PRI
+static void dahdi_queue_frame(struct dahdi_pvt *p, struct ast_frame *f, struct dahdi_pri *pri)
+#else
+static void dahdi_queue_frame(struct dahdi_pvt *p, struct ast_frame *f, void *pri)
+#endif
+{
+ /* We must unlock the PRI to avoid the possibility of a deadlock */
+#ifdef HAVE_PRI
+ if (pri)
+ ast_mutex_unlock(&pri->lock);
+#endif
+ for (;;) {
+ if (p->owner) {
+ if (ast_mutex_trylock(&p->owner->lock)) {
+ DEADLOCK_AVOIDANCE(&p->lock);
+ } else {
+ ast_queue_frame(p->owner, f);
+ ast_mutex_unlock(&p->owner->lock);
+ break;
+ }
+ } else
+ break;
+ }
+#ifdef HAVE_PRI
+ if (pri)
+ ast_mutex_lock(&pri->lock);
+#endif
+}
+
+static int restore_gains(struct dahdi_pvt *p);
+
+static void swap_subs(struct dahdi_pvt *p, int a, int b)
+{
+ int tchan;
+ int tinthreeway;
+ struct ast_channel *towner;
+
+ ast_log(LOG_DEBUG, "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)
+ p->subs[a].owner->fds[0] = p->subs[a].dfd;
+ if (p->subs[b].owner)
+ p->subs[b].owner->fds[0] = p->subs[b].dfd;
+ wakeup_sub(p, a, NULL);
+ wakeup_sub(p, b, NULL);
+}
+
+static int dahdi_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 = DAHDI_FILE_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, DAHDI_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, DAHDI_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 dahdi_close(int fd)
+{
+ if (fd > 0)
+ close(fd);
+}
+
+static void dahdi_close_sub(struct dahdi_pvt *chan_pvt, int sub_num)
+{
+ dahdi_close(chan_pvt->subs[sub_num].dfd);
+ chan_pvt->subs[sub_num].dfd = -1;
+}
+
+#ifdef HAVE_PRI
+static void dahdi_close_pri_fd(struct dahdi_pri *pri, int fd_num)
+{
+ dahdi_close(pri->fds[fd_num]);
+ pri->fds[fd_num] = -1;
+}
+#endif
+
+static int dahdi_setlinear(int dfd, int linear)
+{
+ int res;
+ res = ioctl(dfd, DAHDI_SETLINEAR, &linear);
+ if (res)
+ return res;
+ return 0;
+}
+
+
+static int alloc_sub(struct dahdi_pvt *p, int x)
+{
+ struct dahdi_bufferinfo bi;
+ int res;
+ if (p->subs[x].dfd < 0) {
+ p->subs[x].dfd = dahdi_open(DAHDI_FILE_PSEUDO);
+ if (p->subs[x].dfd > -1) {
+ res = ioctl(p->subs[x].dfd, DAHDI_GET_BUFINFO, &bi);
+ if (!res) {
+ bi.txbufpolicy = p->buf_policy;
+ bi.rxbufpolicy = p->buf_policy;
+ bi.numbufs = p->buf_no;
+ res = ioctl(p->subs[x].dfd, DAHDI_SET_BUFINFO, &bi);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set buffer policy on channel %d: %s\n", x, strerror(errno));
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to check buffer policy on channel %d: %s\n", x, strerror(errno));
+ if (ioctl(p->subs[x].dfd, DAHDI_CHANNO, &p->subs[x].chan) == 1) {
+ ast_log(LOG_WARNING, "Unable to get channel number for pseudo channel on FD %d: %s\n", p->subs[x].dfd, strerror(errno));
+ dahdi_close_sub(p, x);
+ return -1;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Allocated %s subchannel on FD %d channel %d\n", subnames[x], p->subs[x].dfd, 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 dahdi_pvt *p, int x)
+{
+ if (!x) {
+ ast_log(LOG_WARNING, "Trying to unalloc the real channel %d?!?\n", p->channel);
+ return -1;
+ }
+ ast_log(LOG_DEBUG, "Released sub %d of channel %d\n", x, p->channel);
+ dahdi_close_sub(p, x);
+ 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 DAHDI_TONE_DTMF_BASE + (digit - '0');
+ else if (digit >= 'A' && digit <= 'D')
+ return DAHDI_TONE_DTMF_A + (digit - 'A');
+ else if (digit >= 'a' && digit <= 'd')
+ return DAHDI_TONE_DTMF_A + (digit - 'a');
+ else if (digit == '*')
+ return DAHDI_TONE_DTMF_s;
+ else if (digit == '#')
+ return DAHDI_TONE_DTMF_p;
+ else
+ return -1;
+}
+
+static int dahdi_digit_begin(struct ast_channel *chan, char digit)
+{
+ struct dahdi_pvt *pvt;
+ int index;
+ int dtmf = -1;
+
+ pvt = chan->tech_pvt;
+
+ ast_mutex_lock(&pvt->lock);
+
+ index = dahdi_get_index(chan, pvt, 0);
+
+ if ((index != SUB_REAL) || !pvt->owner)
+ goto out;
+
+#ifdef HAVE_PRI
+ if ((pvt->sig == SIG_PRI) && (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_log(LOG_DEBUG, "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].dfd, DAHDI_SENDTONE, &dtmf)) {
+ int res;
+ struct dahdi_dialoperation zo = {
+ .op = DAHDI_DIAL_OP_APPEND,
+ .dialstr[0] = 'T',
+ .dialstr[1] = digit,
+ .dialstr[2] = 0,
+ };
+ if ((res = ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_DIAL, &zo)))
+ ast_log(LOG_WARNING, "Couldn't dial digit %c: %s\n", digit, strerror(errno));
+ else
+ pvt->dialing = 1;
+ } else {
+ ast_log(LOG_DEBUG, "Started VLDTMF digit '%c'\n", digit);
+ pvt->dialing = 1;
+ pvt->begindigit = digit;
+ }
+
+out:
+ ast_mutex_unlock(&pvt->lock);
+
+ return 0;
+}
+
+static int dahdi_digit_end(struct ast_channel *chan, char digit, unsigned int duration)
+{
+ struct dahdi_pvt *pvt;
+ int res = 0;
+ int index;
+ int x;
+
+ pvt = chan->tech_pvt;
+
+ ast_mutex_lock(&pvt->lock);
+
+ index = dahdi_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->begindigit)
+ goto out;
+#endif
+
+ if (pvt->begindigit) {
+ x = -1;
+ ast_log(LOG_DEBUG, "Ending VLDTMF digit '%c'\n", digit);
+ res = ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_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[] = {
+ { DAHDI_ALARM_RED, "Red Alarm" },
+ { DAHDI_ALARM_YELLOW, "Yellow Alarm" },
+ { DAHDI_ALARM_BLUE, "Blue Alarm" },
+ { DAHDI_ALARM_RECOVER, "Recovering" },
+ { DAHDI_ALARM_LOOPBACK, "Loopback" },
+ { DAHDI_ALARM_NOTOPEN, "Not Open" },
+ { DAHDI_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) {
+ return("Dynamically set dialplan in ISDN");
+ }
+ return (pri_plan2str(dialplan));
+}
+#endif
+
+static char *dahdi_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_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 dahdi_sig2str
+
+static int conf_add(struct dahdi_pvt *p, struct dahdi_subchannel *c, int index, int slavechannel)
+{
+ /* If the conference already exists, and we're already in it
+ don't bother doing anything */
+ struct dahdi_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 = DAHDI_CONF_DIGITALMON;
+ zi.confno = slavechannel;
+ } else {
+ if (!index) {
+ /* Real-side and pseudo-side both participate in conference */
+ zi.confmode = DAHDI_CONF_REALANDPSEUDO | DAHDI_CONF_TALKER | DAHDI_CONF_LISTENER |
+ DAHDI_CONF_PSEUDO_TALKER | DAHDI_CONF_PSEUDO_LISTENER;
+ } else
+ zi.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER | DAHDI_CONF_LISTENER;
+ zi.confno = p->confno;
+ }
+ if ((zi.confno == c->curconf.confno) && (zi.confmode == c->curconf.confmode))
+ return 0;
+ if (c->dfd < 0)
+ return 0;
+ if (ioctl(c->dfd, DAHDI_SETCONF, &zi)) {
+ ast_log(LOG_WARNING, "Failed to add %d to conference %d/%d: %s\n", c->dfd, zi.confmode, zi.confno, strerror(errno));
+ return -1;
+ }
+ if (slavechannel < 1) {
+ p->confno = zi.confno;
+ }
+ memcpy(&c->curconf, &zi, sizeof(c->curconf));
+ ast_log(LOG_DEBUG, "Added %d to conference %d/%d\n", c->dfd, c->curconf.confmode, c->curconf.confno);
+ return 0;
+}
+
+static int isourconf(struct dahdi_pvt *p, struct dahdi_subchannel *c)
+{
+ /* If they're listening to our channel, they're ours */
+ if ((p->channel == c->curconf.confno) && (c->curconf.confmode == DAHDI_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 & DAHDI_CONF_TALKER))
+ return 1;
+ return 0;
+}
+
+static int conf_del(struct dahdi_pvt *p, struct dahdi_subchannel *c, int index)
+{
+ struct dahdi_confinfo zi;
+ if (/* Can't delete if there's no dfd */
+ (c->dfd < 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->dfd, DAHDI_SETCONF, &zi)) {
+ ast_log(LOG_WARNING, "Failed to drop %d from conference %d/%d: %s\n", c->dfd, c->curconf.confmode, c->curconf.confno, strerror(errno));
+ return -1;
+ }
+ ast_log(LOG_DEBUG, "Removed %d from conference %d/%d\n", c->dfd, c->curconf.confmode, c->curconf.confno);
+ memcpy(&c->curconf, &zi, sizeof(c->curconf));
+ return 0;
+}
+
+static int isslavenative(struct dahdi_pvt *p, struct dahdi_pvt **out)
+{
+ int x;
+ int useslavenative;
+ struct dahdi_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].dfd > -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 dahdi_pvt *p)
+{
+ struct dahdi_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].dfd > -1) {
+ if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETCONF, &zi))
+ ast_log(LOG_WARNING, "Failed to reset conferencing on channel %d: %s\n", p->channel, strerror(errno));
+ }
+ return 0;
+}
+
+static int update_conf(struct dahdi_pvt *p)
+{
+ int needconf = 0;
+ int x;
+ int useslavenative;
+ struct dahdi_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].dfd > -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;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Updated conferencing on %d, with %d conference users\n", p->channel, needconf);
+ return 0;
+}
+
+static void dahdi_enable_ec(struct dahdi_pvt *p)
+{
+ int x;
+ int res;
+ if (!p)
+ return;
+ if (p->echocanon) {
+ ast_log(LOG_DEBUG, "Echo cancellation already on\n");
+ return;
+ }
+ if (p->digital) {
+ ast_log(LOG_DEBUG, "Echo cancellation isn't required on digital connection\n");
+ return;
+ }
+ if (p->echocancel) {
+ if (p->sig == SIG_PRI) {
+ x = 1;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_AUDIOMODE, &x);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to enable audio mode on channel %d (%s)\n", p->channel, strerror(errno));
+ }
+ x = p->echocancel;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_ECHOCANCEL, &x);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to enable echo cancellation on channel %d (%s)\n", p->channel, strerror(errno));
+ else {
+ p->echocanon = 1;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Enabled echo cancellation on channel %d\n", p->channel);
+ }
+ } else if (option_debug)
+ ast_log(LOG_DEBUG, "No echo cancellation requested\n");
+}
+
+static void dahdi_train_ec(struct dahdi_pvt *p)
+{
+ int x;
+ int res;
+ if (p && p->echocancel && p->echotraining) {
+ x = p->echotraining;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_ECHOTRAIN, &x);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to request echo training on channel %d: %s\n", p->channel, strerror(errno));
+ else {
+ ast_log(LOG_DEBUG, "Engaged echo training on channel %d\n", p->channel);
+ }
+ } else
+ ast_log(LOG_DEBUG, "No echo training requested\n");
+}
+
+static void dahdi_disable_ec(struct dahdi_pvt *p)
+{
+ int x;
+ int res;
+ if (p->echocancel) {
+ x = 0;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_ECHOCANCEL, &x);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to disable echo cancellation on channel %d: %s\n", p->channel, strerror(errno));
+ else if (option_debug)
+ ast_log(LOG_DEBUG, "disabled echo cancellation on channel %d\n", p->channel);
+ }
+ p->echocanon = 0;
+}
+
+static void fill_txgain(struct dahdi_gains *g, float gain, int law)
+{
+ int j;
+ int k;
+ float linear_gain = pow(10.0, gain / 20.0);
+
+ switch (law) {
+ case DAHDI_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 DAHDI_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 dahdi_gains *g, float gain, int law)
+{
+ int j;
+ int k;
+ float linear_gain = pow(10.0, gain / 20.0);
+
+ switch (law) {
+ case DAHDI_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 DAHDI_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 dahdi_gains g;
+ int res;
+
+ memset(&g, 0, sizeof(g));
+ g.chan = chan;
+ res = ioctl(fd, DAHDI_GETGAINS, &g);
+ if (res) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Failed to read gains: %s\n", strerror(errno));
+ return res;
+ }
+
+ fill_txgain(&g, gain, law);
+
+ return ioctl(fd, DAHDI_SETGAINS, &g);
+}
+
+static int set_actual_rxgain(int fd, int chan, float gain, int law)
+{
+ struct dahdi_gains g;
+ int res;
+
+ memset(&g, 0, sizeof(g));
+ g.chan = chan;
+ res = ioctl(fd, DAHDI_GETGAINS, &g);
+ if (res) {
+ ast_log(LOG_DEBUG, "Failed to read gains: %s\n", strerror(errno));
+ return res;
+ }
+
+ fill_rxgain(&g, gain, law);
+
+ return ioctl(fd, DAHDI_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 dahdi_pvt *p)
+{
+ int res;
+
+ /* Bump receive gain by 5.0db */
+ res = set_actual_gain(p->subs[SUB_REAL].dfd, 0, p->rxgain + 5.0, 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 dahdi_pvt *p)
+{
+ int res;
+
+ res = set_actual_gain(p->subs[SUB_REAL].dfd, 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 dahdi_set_hook(int fd, int hs)
+{
+ int x, res;
+
+ x = hs;
+ res = ioctl(fd, DAHDI_HOOK, &x);
+
+ if (res < 0) {
+ if (errno == EINPROGRESS)
+ return 0;
+ ast_log(LOG_WARNING, "DAHDI hook failed returned %d (trying %d): %s\n", res, hs, strerror(errno));
+ /* will expectedly fail if phone is off hook during operation, such as during a restart */
+ }
+
+ return res;
+}
+
+static inline int dahdi_confmute(struct dahdi_pvt *p, int muted)
+{
+ int x, y, res;
+ x = muted;
+ if (p->sig == SIG_PRI) {
+ y = 1;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_AUDIOMODE, &y);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set audio mode on %d: %s\n", p->channel, strerror(errno));
+ }
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_CONFMUTE, &x);
+ if (res < 0)
+ ast_log(LOG_WARNING, "dahdi confmute(%d) failed on channel %d: %s\n", muted, p->channel, strerror(errno));
+ return res;
+}
+
+static int save_conference(struct dahdi_pvt *p)
+{
+ struct dahdi_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].dfd, DAHDI_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 = DAHDI_CONF_NORMAL;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETCONF, &c);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to set conference info: %s\n", strerror(errno));
+ return -1;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Disabled conferencing\n");
+ return 0;
+}
+
+static int restore_conference(struct dahdi_pvt *p)
+{
+ int res;
+ if (p->saveconf.confmode) {
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETCONF, &p->saveconf);
+ p->saveconf.confmode = 0;
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to restore conference info: %s\n", strerror(errno));
+ return -1;
+ }
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Restored conferencing\n");
+ return 0;
+}
+
+static int send_callerid(struct dahdi_pvt *p);
+
+static int send_cwcidspill(struct dahdi_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);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "CPE supports Call Waiting Caller*ID. Sending '%s/%s'\n", p->callwait_name, p->callwait_num);
+ return 0;
+}
+
+static int has_voicemail(struct dahdi_pvt *p)
+{
+
+ return ast_app_has_voicemail(p->mailbox, NULL);
+}
+
+static int send_callerid(struct dahdi_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;
+ dahdi_setlinear(p->subs[SUB_REAL].dfd, 0);
+ }
+ while (p->cidpos < p->cidlen) {
+ res = write(p->subs[SUB_REAL].dfd, 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;
+ }
+ 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 dahdi_callwait(struct ast_channel *ast)
+{
+ struct dahdi_pvt *p = ast->tech_pvt;
+ p->callwaitingrepeat = CALLWAITING_REPEAT_SAMPLES;
+ if (p->cidspill) {
+ ast_log(LOG_WARNING, "Spill already exists?!?\n");
+ 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;
+}
+
+static int dahdi_call(struct ast_channel *ast, char *rdest, int timeout)
+{
+ struct dahdi_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, "dahdi_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 = DAHDI_FLUSH_READ | DAHDI_FLUSH_WRITE;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_FLUSH, &x);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to flush input on channel %d: %s\n", p->channel, strerror(errno));
+ p->outgoing = 1;
+
+ set_actual_gain(p->subs[SUB_REAL].dfd, 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");
+ 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].dfd, DAHDI_SETCADENCE, &cadences[p->distinctivering - 1]))
+ ast_log(LOG_WARNING, "Unable to set distinctive ring cadence %d on '%s': %s\n", p->distinctivering, ast->name, strerror(errno));
+ p->cidrings = cidrings[p->distinctivering - 1];
+ } else {
+ if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETCADENCE, NULL))
+ ast_log(LOG_WARNING, "Unable to reset default ring on '%s': %s\n", ast->name, strerror(errno));
+ 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 = DAHDI_DIAL_OP_REPLACE;
+ snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "Tw%s", c);
+ ast_log(LOG_DEBUG, "FXO: setup deferred dialstring: %s\n", c);
+ } else {
+ p->dop.dialstr[0] = '\0';
+ }
+ x = DAHDI_RING;
+ if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_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 (dahdi_callwait(ast)) {
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ /* Make ring-back */
+ if (tone_zone_play_tone(p->subs[SUB_CALLWAIT].dfd, DAHDI_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 = dahdi_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 = DAHDI_START;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_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_log(LOG_DEBUG, "Dialing '%s'\n", c);
+ p->dop.op = DAHDI_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].dfd, DAHDI_DIAL, &p->dop)) {
+ int saveerr = errno;
+
+ x = DAHDI_ONHOOK;
+ ioctl(p->subs[SUB_REAL].dfd, DAHDI_HOOK, &x);
+ ast_log(LOG_WARNING, "Dialing failed on channel %d: %s\n", p->channel, strerror(saveerr));
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ } else
+ ast_log(LOG_DEBUG, "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:
+ /* We'll get it in a moment -- but use dialdest to store pre-setup_ack digits */
+ p->dialdest[0] = '\0';
+ break;
+ default:
+ ast_log(LOG_DEBUG, "not yet implemented\n");
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+#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 = DAHDI_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_destroycall(p->pri->pri, p->call);
+ p->call = NULL;
+ pri_rel(p->pri);
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+ if (p->bearer || (mysig == SIG_FXSKS)) {
+ if (p->bearer) {
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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 == DAHDI_LAW_ALAW) ? PRI_LAYER_1_ALAW : PRI_LAYER_1_ULAW)));
+ if (p->pri->facilityenable)
+ pri_facility_enable(p->pri->pri);
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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) { /* compute dynamically */
+ if (strncmp(c + p->stripmsd, p->pri->internationalprefix, strlen(p->pri->internationalprefix)) == 0) {
+ dp_strip = strlen(p->pri->internationalprefix);
+ pridialplan = PRI_INTERNATIONAL_ISDN;
+ } else if (strncmp(c + p->stripmsd, p->pri->nationalprefix, strlen(p->pri->nationalprefix)) == 0) {
+ dp_strip = strlen(p->pri->nationalprefix);
+ pridialplan = PRI_NATIONAL_ISDN;
+ } else {
+ pridialplan = PRI_LOCAL_ISDN;
+ }
+ }
+ 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)) { /* compute dynamically */
+ if (strncmp(l, p->pri->internationalprefix, strlen(p->pri->internationalprefix)) == 0) {
+ ldp_strip = strlen(p->pri->internationalprefix);
+ prilocaldialplan = PRI_INTERNATIONAL_ISDN;
+ } else if (strncmp(l, p->pri->nationalprefix, strlen(p->pri->nationalprefix)) == 0) {
+ ldp_strip = strlen(p->pri->nationalprefix);
+ prilocaldialplan = PRI_NATIONAL_ISDN;
+ } else {
+ prilocaldialplan = PRI_LOCAL_ISDN;
+ }
+ }
+ 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_dahdi_pvt(struct dahdi_pvt **pvt)
+{
+ struct dahdi_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)
+ ast_smdi_interface_unref(p->smdi_iface);
+ ast_mutex_destroy(&p->lock);
+ dahdi_close_sub(p, SUB_REAL);
+ if (p->owner)
+ p->owner->tech_pvt = NULL;
+ free(p);
+ *pvt = NULL;
+}
+
+static int destroy_channel(struct dahdi_pvt *prev, struct dahdi_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;
+ }
+ destroy_dahdi_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;
+ }
+ destroy_dahdi_pvt(&cur);
+ }
+ return 0;
+}
+
+static void destroy_all_channels(void)
+{
+ int x;
+ struct dahdi_pvt *p, *pl;
+
+ while (num_restart_pending) {
+ usleep(1);
+ }
+
+ 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);
+ pl = p;
+ p = p->next;
+ x = pl->channel;
+ /* Free associated memory */
+ if (pl)
+ destroy_dahdi_pvt(&pl);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_2 "Unregistered channel %d\n", x);
+ }
+ iflist = NULL;
+ ifcount = 0;
+ ast_mutex_unlock(&iflock);
+}
+
+#ifdef HAVE_PRI
+static char *dahdi_send_keypad_facility_app = "DAHDISendKeypadFacility";
+static char *zap_send_keypad_facility_app = "ZapSendKeypadFacility";
+
+static char *dahdi_send_keypad_facility_synopsis = "Send digits out of band over a PRI";
+static char *zap_send_keypad_facility_synopsis = "Send digits out of band over a PRI";
+
+static char *dahdi_send_keypad_facility_descrip =
+" DAHDISendKeypadFacility(): This application will send the given string of digits in a Keypad Facility\n"
+" IE over the current channel.\n";
+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 send_keypad_facility_exec(struct ast_channel *chan, void *data)
+{
+ /* Data will be our digit string */
+ struct dahdi_pvt *p;
+ char *digits = (char *) data;
+
+ if (ast_strlen_zero(digits)) {
+ ast_log(LOG_DEBUG, "No digit string sent to application!\n");
+ return -1;
+ }
+
+ p = (struct dahdi_pvt *)chan->tech_pvt;
+
+ if (!p) {
+ ast_log(LOG_DEBUG, "Unable to find technology private\n");
+ return -1;
+ }
+
+ ast_mutex_lock(&p->lock);
+
+ if (!p->pri || !p->call) {
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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 dahdi_send_keypad_facility_exec(struct ast_channel *chan, void *data)
+{
+ return send_keypad_facility_exec(chan, data);
+}
+
+static int zap_send_keypad_facility_exec(struct ast_channel *chan, void *data)
+{
+ ast_log(LOG_WARNING, "Use of the command %s is deprecated, please use %s instead.\n", zap_send_keypad_facility_app, dahdi_send_keypad_facility_app);
+ return send_keypad_facility_exec(chan, data);
+}
+
+static int pri_is_up(struct dahdi_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 dahdi_pvt *crv, struct dahdi_pri *pri, struct dahdi_pvt *bearer)
+{
+ bearer->owner = &inuse;
+ bearer->realcall = crv;
+ crv->subs[SUB_REAL].dfd = bearer->subs[SUB_REAL].dfd;
+ if (crv->subs[SUB_REAL].owner)
+ crv->subs[SUB_REAL].owner->fds[0] = crv->subs[SUB_REAL].dfd;
+ 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 dahdi_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 dahdi_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 dahdi_hangup(struct ast_channel *ast)
+{
+ int res;
+ int index,x, law;
+ /*static int restore_gains(struct dahdi_pvt *p);*/
+ struct dahdi_pvt *p = ast->tech_pvt;
+ struct dahdi_pvt *tmp = NULL;
+ struct dahdi_pvt *prev = NULL;
+ struct dahdi_params par;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "dahdi_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 = dahdi_get_index(ast, p, 1);
+
+ if (p->sig == SIG_PRI) {
+ x = 1;
+ ast_channel_setoption(ast,AST_OPTION_AUDIO_MODE,&x,sizeof(char),0);
+ }
+
+ x = 0;
+ dahdi_confmute(p, 0);
+ restore_gains(p);
+ if (p->origcid_num) {
+ ast_copy_string(p->cid_num, p->origcid_num, sizeof(p->cid_num));
+ 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));
+ 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';
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Hangup: channel: %d index = %d, normal = %d, callwait = %d, thirdcall = %d\n",
+ p->channel, index, p->subs[SUB_REAL].dfd, p->subs[SUB_CALLWAIT].dfd, p->subs[SUB_THREEWAY].dfd);
+ 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;
+ dahdi_setlinear(p->subs[index].dfd, 0);
+ if (index == SUB_REAL) {
+ if ((p->subs[SUB_CALLWAIT].dfd > -1) && (p->subs[SUB_THREEWAY].dfd > -1)) {
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Call was incomplete, setting owner to NULL\n");
+ p->owner = NULL;
+ }
+ p->subs[SUB_REAL].inthreeway = 0;
+ }
+ } else if (p->subs[SUB_CALLWAIT].dfd > -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].dfd > -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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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);
+#ifdef HAVE_PRI
+ p->proceeding = 0;
+ p->progress = 0;
+ p->alerting = 0;
+ p->setup_ack = 0;
+#endif
+ if (p->dsp) {
+ ast_dsp_free(p->dsp);
+ p->dsp = NULL;
+ }
+
+ law = DAHDI_LAW_DEFAULT;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETLAW, &law);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to set law on channel %d to default: %s\n", p->channel, strerror(errno));
+ /* Perform low level hangup if no owner left */
+#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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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))
+ res = dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_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].dfd, DAHDI_GET_PARAMS, &par);
+ if (!res) {
+#if 0
+ ast_log(LOG_DEBUG, "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].dfd, DAHDI_TONE_CONGESTION);
+ else
+ tone_zone_play_tone(p->subs[SUB_REAL].dfd, -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].dfd, -1);
+ }
+ if (p->cidspill)
+ free(p->cidspill);
+ if (p->sig)
+ dahdi_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) {
+ x = 0;
+ ast_channel_setoption(ast,AST_OPTION_AUDIO_MODE,&x,sizeof(char),0);
+ }
+#ifdef HAVE_PRI
+ if (p->bearer) {
+ ast_log(LOG_DEBUG, "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].dfd = -1;
+ p->pri = NULL;
+ }
+#endif
+ if (num_restart_pending == 0)
+ 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);
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Hungup '%s'\n", ast->name);
+
+ ast_mutex_lock(&iflock);
+
+ if (p->restartpending) {
+ num_restart_pending--;
+ }
+
+ 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 dahdi_answer(struct ast_channel *ast)
+{
+ struct dahdi_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 = dahdi_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_log(LOG_DEBUG, "Took %s off hook\n", ast->name);
+ if (p->hanguponpolarityswitch) {
+ gettimeofday(&p->polaritydelaytv, NULL);
+ }
+ res = dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_OFFHOOK);
+ tone_zone_play_tone(p->subs[index].dfd, -1);
+ p->dialing = 0;
+ if ((index == SUB_REAL) && p->subs[SUB_THREEWAY].inthreeway) {
+ if (oldstate == AST_STATE_RINGING) {
+ ast_log(LOG_DEBUG, "Finally swapping real and threeway\n");
+ tone_zone_play_tone(p->subs[SUB_THREEWAY].dfd, -1);
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ p->owner = p->subs[SUB_REAL].owner;
+ }
+ }
+ if (p->sig & __DAHDI_SIG_FXS) {
+ dahdi_enable_ec(p);
+ dahdi_train_ec(p);
+ }
+ break;
+#ifdef HAVE_PRI
+ 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
+ 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 dahdi_setoption(struct ast_channel *chan, int option, void *data, int datalen)
+{
+ char *cp;
+ signed char *scp;
+ int x;
+ int index;
+ struct dahdi_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 = dahdi_get_index(chan, p, 0);
+ if (index < 0) {
+ ast_log(LOG_WARNING, "No index in TXGAIN?\n");
+ return -1;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Setting actual tx gain on %s to %f\n", chan->name, p->txgain + (float) *scp);
+ return set_actual_txgain(p->subs[index].dfd, 0, p->txgain + (float) *scp, p->law);
+ case AST_OPTION_RXGAIN:
+ scp = (signed char *) data;
+ index = dahdi_get_index(chan, p, 0);
+ if (index < 0) {
+ ast_log(LOG_WARNING, "No index in RXGAIN?\n");
+ return -1;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Setting actual rx gain on %s to %f\n", chan->name, p->rxgain + (float) *scp);
+ return set_actual_rxgain(p->subs[index].dfd, 0, p->rxgain + (float) *scp, p->law);
+ case AST_OPTION_TONE_VERIFY:
+ if (!p->dsp)
+ break;
+ cp = (char *) data;
+ switch (*cp) {
+ case 1:
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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 */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Set option TDD MODE, value: OFF(0) on %s\n",chan->name);
+ if (p->tdd)
+ tdd_free(p->tdd);
+ p->tdd = 0;
+ break;
+ }
+ ast_log(LOG_DEBUG, "Set option TDD MODE, value: %s(%d) on %s\n",
+ (*cp == 2) ? "MATE" : "ON", (int) *cp, chan->name);
+ dahdi_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 = dahdi_get_index(chan, p, 0);
+ if (index < 0) {
+ ast_log(LOG_WARNING, "No index in TDD?\n");
+ return -1;
+ }
+ fd = p->subs[index].dfd;
+ 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Set option RELAX DTMF, value: %s(%d) on %s\n",
+ *cp ? "ON" : "OFF", (int) *cp, chan->name);
+ p->dtmfrelax = 0;
+ if (*cp) p->dtmfrelax = DSP_DIGITMODE_RELAXDTMF;
+ ast_dsp_digitmode(p->dsp, DSP_DIGITMODE_DTMF | p->dtmfrelax);
+ break;
+ case AST_OPTION_AUDIO_MODE: /* Set AUDIO mode (or not) */
+ cp = (char *) data;
+ if (!*cp) {
+ ast_log(LOG_DEBUG, "Set option AUDIO MODE, value: OFF(0) on %s\n", chan->name);
+ x = 0;
+ dahdi_disable_ec(p);
+ } else {
+ ast_log(LOG_DEBUG, "Set option AUDIO MODE, value: ON(1) on %s\n", chan->name);
+ x = 1;
+ }
+ if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_AUDIOMODE, &x) == -1)
+ ast_log(LOG_WARNING, "Unable to set audio mode on channel %d to %d: %s\n", p->channel, x, strerror(errno));
+ 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Enabling echo cancelation on %s\n", chan->name);
+ dahdi_enable_ec(p);
+ } else {
+ ast_log(LOG_DEBUG, "Disabling echo cancelation on %s\n", chan->name);
+ dahdi_disable_ec(p);
+ }
+ break;
+ }
+ errno = 0;
+
+ return 0;
+}
+
+static int dahdi_func_read(struct ast_channel *chan, char *function, char *data, char *buf, size_t len)
+{
+ struct dahdi_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 dahdi_unlink(struct dahdi_pvt *slave, struct dahdi_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)) {
+ DEADLOCK_AVOIDANCE(&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_log(LOG_DEBUG, "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 dahdi_link(struct dahdi_pvt *slave, struct dahdi_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_log(LOG_DEBUG, "Making %d slave to master %d at %d\n", slave->channel, master->channel, x);
+}
+
+static void disable_dtmf_detect(struct dahdi_pvt *p)
+{
+#ifdef DAHDI_TONEDETECT
+ int val;
+#endif
+
+ p->ignoredtmf = 1;
+
+#ifdef DAHDI_TONEDETECT
+ val = 0;
+ ioctl(p->subs[SUB_REAL].dfd, DAHDI_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 dahdi_pvt *p)
+{
+#ifdef DAHDI_TONEDETECT
+ int val;
+#endif
+
+ if (p->channel == CHAN_PSEUDO)
+ return;
+
+ p->ignoredtmf = 0;
+
+#ifdef DAHDI_TONEDETECT
+ val = DAHDI_TONEDETECT_ON | DAHDI_TONEDETECT_MUTE;
+ ioctl(p->subs[SUB_REAL].dfd, DAHDI_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 dahdi_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 dahdi_pvt *p0, *p1, *op0, *op1;
+ struct dahdi_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_mutex_lock(&c0->lock);
+ while (ast_mutex_trylock(&c1->lock)) {
+ DEADLOCK_AVOIDANCE(&c0->lock);
+ }
+
+ p0 = c0->tech_pvt;
+ p1 = c1->tech_pvt;
+ /* cant do pseudo-channels here */
+ if (!p0 || (!p0->sig) || !p1 || (!p1->sig)) {
+ ast_mutex_unlock(&c0->lock);
+ ast_mutex_unlock(&c1->lock);
+ return AST_BRIDGE_FAILED_NOWARN;
+ }
+
+ oi0 = dahdi_get_index(c0, p0, 0);
+ oi1 = dahdi_get_index(c1, p1, 0);
+ if ((oi0 < 0) || (oi1 < 0)) {
+ ast_mutex_unlock(&c0->lock);
+ ast_mutex_unlock(&c1->lock);
+ 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_mutex_unlock(&c0->lock);
+ ast_mutex_unlock(&c1->lock);
+ 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_mutex_unlock(&c0->lock);
+ ast_mutex_unlock(&c1->lock);
+ 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].dfd > -1) ? 1 : 0,
+ p0->subs[SUB_REAL].inthreeway, p0->channel,
+ oi0, (p1->subs[SUB_CALLWAIT].dfd > -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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Playing ringback on %s since %s is in a ringing three-way\n", c0->name, c1->name);
+ tone_zone_play_tone(p0->subs[oi0].dfd, DAHDI_TONE_RINGTONE);
+ os1 = p1->subs[SUB_REAL].owner->_state;
+ } else {
+ ast_log(LOG_DEBUG, "Stopping tones on %d/%d talking to %d/%d\n", p0->channel, oi0, p1->channel, oi1);
+ tone_zone_play_tone(p0->subs[oi0].dfd, -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_log(LOG_DEBUG, "Playing ringback on %s since %s is in a ringing three-way\n", c1->name, c0->name);
+ tone_zone_play_tone(p1->subs[oi1].dfd, DAHDI_TONE_RINGTONE);
+ os0 = p0->subs[SUB_REAL].owner->_state;
+ } else {
+ ast_log(LOG_DEBUG, "Stopping tones on %d/%d talking to %d/%d\n", p1->channel, oi1, p0->channel, oi0);
+ tone_zone_play_tone(p1->subs[oi0].dfd, -1);
+ }
+ if ((oi0 == SUB_REAL) && (oi1 == SUB_REAL)) {
+ if (!p0->echocanbridged || !p1->echocanbridged) {
+ /* Disable echo cancellation if appropriate */
+ dahdi_disable_ec(p0);
+ dahdi_disable_ec(p1);
+ }
+ }
+ dahdi_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_mutex_unlock(&c0->lock);
+ ast_mutex_unlock(&c1->lock);
+
+ /* Native bridge failed */
+ if ((!master || !slave) && !nothingok) {
+ dahdi_enable_ec(p0);
+ dahdi_enable_ec(p1);
+ return AST_BRIDGE_FAILED;
+ }
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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_mutex_lock(&c0->lock);
+ while (ast_mutex_trylock(&c1->lock)) {
+ DEADLOCK_AVOIDANCE(&c0->lock);
+ }
+
+ p0 = c0->tech_pvt;
+ p1 = c1->tech_pvt;
+
+ if (op0 == p0)
+ i0 = dahdi_get_index(c0, p0, 1);
+ if (op1 == p1)
+ i1 = dahdi_get_index(c1, p1, 1);
+ ast_mutex_unlock(&c0->lock);
+ ast_mutex_unlock(&c1->lock);
+
+ 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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)
+ dahdi_enable_ec(p0);
+
+ if (op1 == p1)
+ dahdi_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);
+
+ dahdi_unlink(slave, master, 1);
+
+ return res;
+}
+
+static int dahdi_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct dahdi_pvt *p = newchan->tech_pvt;
+ int x;
+ ast_mutex_lock(&p->lock);
+ ast_log(LOG_DEBUG, "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)
+ dahdi_unlink(NULL, p, 0);
+ p->subs[x].owner = newchan;
+ }
+ if (newchan->_state == AST_STATE_RINGING)
+ dahdi_indicate(newchan, AST_CONTROL_RINGING, NULL, 0);
+ update_conf(p);
+ ast_mutex_unlock(&p->lock);
+ return 0;
+}
+
+static int dahdi_ring_phone(struct dahdi_pvt *p)
+{
+ int x;
+ int res;
+ /* Make sure our transmit state is on hook */
+ x = 0;
+ x = DAHDI_ONHOOK;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_HOOK, &x);
+ do {
+ x = DAHDI_RING;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_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 *dahdi_new(struct dahdi_pvt *, int, int, int, int, int);
+
+static int attempt_transfer(struct dahdi_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].dfd, DAHDI_TONE_RINGTONE);
+ }
+ 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_mutex_unlock(&p->subs[SUB_THREEWAY].owner->lock);
+ 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].dfd, DAHDI_TONE_RINGTONE);
+ }
+ 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_mutex_unlock(&p->subs[SUB_REAL].owner->lock);
+ unalloc_sub(p, SUB_THREEWAY);
+ /* Tell the caller not to hangup */
+ return 1;
+ } else {
+ ast_log(LOG_DEBUG, "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 dahdi_pvt *p)
+{
+ struct dahdi_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].dfd, DAHDI_GETCONF, &ci)) {
+ ast_log(LOG_WARNING, "Failed to get conference info on channel %d: %s\n", p->channel, strerror(errno));
+ 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)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Avoiding 3-way call when in an external conference\n");
+ return 1;
+ }
+ return 0;
+}
+
+static int get_alarms(struct dahdi_pvt *p)
+{
+ int res;
+ struct dahdi_spaninfo zi;
+#if !defined(HAVE_ZAPTEL) || defined(HAVE_ZAPTEL_CHANALARMS)
+ /*
+ * The conditional compilation is needed only in asterisk-1.4 for
+ * backward compatibility with old zaptel drivers that don't have
+ * a DAHDI_PARAMS.chan_alarms field.
+ */
+ struct dahdi_params params;
+#endif
+
+ memset(&zi, 0, sizeof(zi));
+ zi.spanno = p->span;
+
+ /* First check for span alarms */
+ if((res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SPANSTAT, &zi)) < 0) {
+ ast_log(LOG_WARNING, "Unable to determine alarm on channel %d: %s\n", p->channel, strerror(errno));
+ return 0;
+ }
+ if (zi.alarms != DAHDI_ALARM_NONE)
+ return zi.alarms;
+#if !defined(HAVE_ZAPTEL) || defined(HAVE_ZAPTEL_CHANALARMS)
+ /* No alarms on the span. Check for channel alarms. */
+ if ((res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &params)) >= 0)
+ return params.chan_alarms;
+ /* ioctl failed */
+ ast_log(LOG_WARNING, "Unable to determine alarm on channel %d\n", p->channel);
+#endif
+ return DAHDI_ALARM_NONE;
+}
+
+static void dahdi_handle_dtmfup(struct ast_channel *ast, int index, struct ast_frame **dest)
+{
+ struct dahdi_pvt *p = ast->tech_pvt;
+ struct ast_frame *f = *dest;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "DTMF digit: %c on %s\n", f->subclass, ast->name);
+
+ if (p->confirmanswer) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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')) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Got some DTMF, but it's for the CAS\n");
+ if (p->cidspill)
+ 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 & 0x6) && !p->faxhandled) {
+ p->faxhandled++;
+ if (strcmp(ast->exten, "fax")) {
+ const char *target_context = S_OR(ast->macrocontext, ast->context);
+
+ /* We need to unlock 'ast' here because ast_exists_extension has the
+ * potential to start autoservice on the channel. Such action is prone
+ * to deadlock.
+ */
+ ast_mutex_unlock(&p->lock);
+ ast_channel_unlock(ast);
+ if (ast_exists_extension(ast, target_context, "fax", 1, ast->cid.cid_num)) {
+ ast_channel_lock(ast);
+ ast_mutex_lock(&p->lock);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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_channel_lock(ast);
+ ast_mutex_lock(&p->lock);
+ ast_log(LOG_NOTICE, "Fax detected, but no fax extension\n");
+ }
+ } else if (option_debug)
+ ast_log(LOG_DEBUG, "Already in a fax extension, not redirecting\n");
+ } else if (option_debug)
+ ast_log(LOG_DEBUG, "Fax already handled\n");
+ dahdi_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 */
+ dahdi_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 */
+ dahdi_confmute(p, 0);
+ p->subs[index].f.frametype = AST_FRAME_NULL;
+ p->subs[index].f.subclass = 0;
+ *dest = &p->subs[index].f;
+ } else
+ dahdi_confmute(p, 0);
+}
+
+static void handle_alarms(struct dahdi_pvt *p, int alarms)
+{
+ const char *alarm_str = alarm2str(alarms);
+
+ /* hack alert! Zaptel 1.4 and DAHDI expose FXO battery as an alarm, but this code
+ * doesn't know what to do with it. Don't confuse users with log messages. */
+ if (!strcasecmp(alarm_str, "No Alarm") || !strcasecmp(alarm_str, "Unknown Alarm")) {
+ p->unknown_alarm = 1;
+ return;
+ } else {
+ p->unknown_alarm = 0;
+ }
+
+ ast_log(LOG_WARNING, "Detected alarm on channel %d: %s\n", p->channel, alarm_str);
+ manager_event(EVENT_FLAG_SYSTEM, "Alarm",
+ "Alarm: %s\r\n"
+ "Channel: %d\r\n",
+ alarm_str, p->channel);
+}
+
+static struct ast_frame *dahdi_handle_event(struct ast_channel *ast)
+{
+ int res, x;
+ int index, mysig;
+ char *c;
+ struct dahdi_pvt *p = ast->tech_pvt;
+ pthread_t threadid;
+ pthread_attr_t attr;
+ struct ast_channel *chan;
+ struct ast_frame *f;
+
+ index = dahdi_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 = "dahdi_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 = dahdi_get_event(p->subs[index].dfd);
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Got event %s(%d) on channel %d (index %d)\n", event2str(res), res, p->channel, index);
+
+ if (res & (DAHDI_EVENT_PULSEDIGIT | DAHDI_EVENT_DTMFUP)) {
+ p->pulsedial = (res & DAHDI_EVENT_PULSEDIGIT) ? 1 : 0;
+
+ ast_log(LOG_DEBUG, "Detected %sdigit '%c'\n", p->pulsedial ? "pulse ": "", res & 0xff);
+#ifdef HAVE_PRI
+ if (!p->proceeding && p->sig == SIG_PRI && p->pri && p->pri->overlapdial) {
+ /* absorb event */
+ } else {
+#endif
+ p->subs[index].f.frametype = AST_FRAME_DTMF_END;
+ p->subs[index].f.subclass = res & 0xff;
+#ifdef HAVE_PRI
+ }
+#endif
+ dahdi_handle_dtmfup(ast, index, &f);
+ return f;
+ }
+
+ if (res & DAHDI_EVENT_DTMFDOWN) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "DTMF Down '%c'\n", res & 0xff);
+ /* Mute conference */
+ dahdi_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 DAHDI_EVENT_EC_DISABLED
+ case DAHDI_EVENT_EC_DISABLED:
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Channel %d echo canceler disabled due to CED detection\n", p->channel);
+ p->echocanon = 0;
+ break;
+#endif
+ case DAHDI_EVENT_BITSCHANGED:
+ ast_log(LOG_WARNING, "Recieved bits changed on %s signalling?\n", sig2str(p->sig));
+ case DAHDI_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].dfd, -1);
+ break;
+ case DAHDI_EVENT_DIALCOMPLETE:
+ if (p->inalarm) break;
+ if ((p->radio || (p->oprmode < 0))) break;
+ if (ioctl(p->subs[index].dfd,DAHDI_DIALING,&x) == -1) {
+ ast_log(LOG_DEBUG, "DAHDI_DIALING ioctl failed on %s: %s\n",ast->name, strerror(errno));
+ return NULL;
+ }
+ if (!x) { /* if not still dialing in driver */
+ dahdi_enable_ec(p);
+ if (p->echobreak) {
+ dahdi_train_ec(p);
+ ast_copy_string(p->dop.dialstr, p->echorest, sizeof(p->dop.dialstr));
+ p->dop.op = DAHDI_DIAL_OP_REPLACE;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_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 & 1) && CANPROGRESSDETECT(p) && p->dsp && p->outgoing) {
+ ast_log(LOG_DEBUG, "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 DAHDI_EVENT_ALARM:
+#ifdef HAVE_PRI
+ 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);
+ handle_alarms(p, res);
+#ifdef HAVE_LIBPRI
+ if (!p->pri || !p->pri->pri || pri_get_timer(p->pri->pri, PRI_TIMER_T309) < 0) {
+ /* fall through intentionally */
+ } else {
+ break;
+ }
+#endif
+ case DAHDI_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 */
+ dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_RINGOFF);
+ dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_RING);
+ save_conference(p->oprpeer);
+ tone_zone_play_tone(p->oprpeer->subs[SUB_REAL].dfd, DAHDI_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);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ dahdi_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_mutex_trylock(&p->subs[SUB_THREEWAY].owner->lock)) {
+ /* Yuck, didn't get the lock on the 3-way, gotta release everything and re-grab! */
+ ast_mutex_unlock(&p->lock);
+ DEADLOCK_AVOIDANCE(&ast->lock);
+ /* 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_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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Looks like a bounced flash, hanging up both calls on %d\n", p->channel);
+ ast_mutex_unlock(&p->subs[SUB_THREEWAY].owner->lock);
+ } 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_mutex_unlock(&p->subs[SUB_THREEWAY].owner->lock);
+ /* Swap subs and dis-own channel */
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ p->owner = NULL;
+ /* Ring the phone */
+ dahdi_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_mutex_unlock(&p->subs[SUB_THREEWAY].owner->lock);
+ } else if (res) {
+ /* Don't actually hang up at this point */
+ if (p->subs[SUB_THREEWAY].owner)
+ ast_mutex_unlock(&p->subs[SUB_THREEWAY].owner->lock);
+ break;
+ }
+ }
+ } else {
+ p->subs[SUB_THREEWAY].owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ if (p->subs[SUB_THREEWAY].owner)
+ ast_mutex_unlock(&p->subs[SUB_THREEWAY].owner->lock);
+ }
+ } else {
+ ast_mutex_unlock(&p->subs[SUB_THREEWAY].owner->lock);
+ /* Swap subs and dis-own channel */
+ swap_subs(p, SUB_THREEWAY, SUB_REAL);
+ p->owner = NULL;
+ /* Ring the phone */
+ dahdi_ring_phone(p);
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Got a hangup and my index is %d?\n", index);
+ }
+ /* Fall through */
+ default:
+ dahdi_disable_ec(p);
+ return NULL;
+ }
+ break;
+ case DAHDI_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 */
+ dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_RINGOFF);
+ tone_zone_play_tone(p->oprpeer->subs[SUB_REAL].dfd, -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].dfd, DAHDI_DIAL, &p->dop)) {
+ int saveerr = errno;
+
+ x = DAHDI_ONHOOK;
+ ioctl(p->subs[SUB_REAL].dfd, DAHDI_HOOK, &x);
+ ast_log(LOG_WARNING, "Dialing failed on channel %d: %s\n", p->channel, strerror(saveerr));
+ 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:
+ dahdi_enable_ec(p);
+ dahdi_train_ec(p);
+ p->subs[index].f.frametype = AST_FRAME_CONTROL;
+ p->subs[index].f.subclass = AST_CONTROL_ANSWER;
+ /* Make sure it stops ringing */
+ dahdi_set_hook(p->subs[index].dfd, DAHDI_OFFHOOK);
+ ast_log(LOG_DEBUG, "channel %d answered\n", p->channel);
+ if (p->cidspill) {
+ /* Cancel any running CallerID spill */
+ 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].dfd, DAHDI_DIAL, &p->dop);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to initiate dialing on trunk channel %d: %s\n", p->channel, strerror(errno));
+ p->dop.dialstr[0] = '\0';
+ return NULL;
+ } else {
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "channel %d picked up\n", p->channel);
+ return &p->subs[index].f;
+ case AST_STATE_UP:
+ /* Make sure it stops ringing */
+ dahdi_set_hook(p->subs[index].dfd, DAHDI_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].dfd, DAHDI_TONE_STUTTER);
+ else
+ res = tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_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;
+ }
+
+ /* 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)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 DAHDI_EVENT_RINGBEGIN
+ case DAHDI_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 DAHDI_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");
+ 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 DAHDI_EVENT_RINGERON:
+ break;
+ case DAHDI_EVENT_NOALARM:
+ p->inalarm = 0;
+#ifdef HAVE_PRI
+ /* Extremely unlikely but just in case */
+ if (p->bearer)
+ p->bearer->inalarm = 0;
+#endif
+ if (!p->unknown_alarm) {
+ ast_log(LOG_NOTICE, "Alarm cleared on channel %d\n", p->channel);
+ manager_event(EVENT_FLAG_SYSTEM, "AlarmClear",
+ "Channel: %d\r\n", p->channel);
+ } else {
+ p->unknown_alarm = 0;
+ }
+ break;
+ case DAHDI_EVENT_WINKFLASH:
+ if (p->inalarm) break;
+ if (p->radio) break;
+ if (p->oprmode < 0) break;
+ if (p->oprmode > 1)
+ {
+ struct dahdi_params par;
+
+ if (ioctl(p->oprpeer->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &par) != -1)
+ {
+ if (!par.rxisoffhook)
+ {
+ /* Make sure it stops ringing */
+ dahdi_set_hook(p->oprpeer->subs[SUB_REAL].dfd, DAHDI_RINGOFF);
+ dahdi_set_hook(p->oprpeer->subs[SUB_REAL].dfd, DAHDI_RING);
+ save_conference(p);
+ tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_TONE_RINGTONE);
+ }
+ }
+ break;
+ }
+ /* Remember last time we got a flash-hook */
+ gettimeofday(&p->flashtime, NULL);
+ switch (mysig) {
+ case SIG_FXOLS:
+ case SIG_FXOGS:
+ case SIG_FXOKS:
+ ast_log(LOG_DEBUG, "Winkflash, index: %d, normal: %d, callwait: %d, thirdcall: %d\n",
+ index, p->subs[SUB_REAL].dfd, p->subs[SUB_CALLWAIT].dfd, p->subs[SUB_THREEWAY].dfd);
+ 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].dfd, -1);
+ p->owner = p->subs[SUB_REAL].owner;
+ ast_log(LOG_DEBUG, "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->dahditrcallerid && 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_log(LOG_DEBUG, "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 = dahdi_new(p, AST_STATE_RESERVED, 0, SUB_THREEWAY, 0, 0);
+ if (p->dahditrcallerid) {
+ 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 */
+ dahdi_disable_ec(p);
+ res = tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_TONE_DIALRECALL);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to start dial recall tone on channel %d\n", p->channel);
+ p->owner = chan;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ if (!chan) {
+ ast_log(LOG_WARNING, "Cannot allocate new structure on channel %d\n", p->channel);
+ } else if (ast_pthread_create(&threadid, &attr, 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].dfd, DAHDI_TONE_CONGESTION);
+ dahdi_enable_ec(p);
+ ast_hangup(chan);
+ } else {
+ struct ast_channel *other = ast_bridged_channel(p->subs[SUB_THREEWAY].owner);
+ int way3bridge = 0, cdr3way = 0;
+
+ if (!other) {
+ other = ast_bridged_channel(p->subs[SUB_REAL].owner);
+ } else
+ way3bridge = 1;
+
+ if (p->subs[SUB_THREEWAY].owner->cdr)
+ cdr3way = 1;
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ }
+ pthread_attr_destroy(&attr);
+ }
+ } else {
+ /* Already have a 3 way call */
+ if (p->subs[SUB_THREEWAY].inthreeway) {
+ /* Call is already up, drop the last person */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ struct ast_channel *other = ast_bridged_channel(p->subs[SUB_THREEWAY].owner);
+ int way3bridge = 0, cdr3way = 0;
+
+ if (!other) {
+ other = ast_bridged_channel(p->subs[SUB_REAL].owner);
+ } else
+ way3bridge = 1;
+
+ if (p->subs[SUB_THREEWAY].owner->cdr)
+ cdr3way = 1;
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "Enabling ringtone on real and threeway\n");
+ res = tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_TONE_RINGTONE);
+ res = tone_zone_play_tone(p->subs[SUB_THREEWAY].dfd, DAHDI_TONE_RINGTONE);
+ }
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ dahdi_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 (p->dialing)
+ ast_log(LOG_DEBUG, "Ignoring wink on channel %d\n", p->channel);
+ else
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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].dfd, DAHDI_DIAL, &p->dop);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to initiate dialing on trunk channel %d: %s\n", p->channel, strerror(errno));
+ p->dop.dialstr[0] = '\0';
+ return NULL;
+ } else
+ ast_log(LOG_DEBUG, "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 DAHDI_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].dfd, DAHDI_DIAL, &p->dop);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to initiate dialing on trunk channel %d: %s\n", p->channel, strerror(errno));
+ p->dop.dialstr[0] = '\0';
+ return NULL;
+ } else
+ ast_log(LOG_DEBUG, "Sent deferred digit string: %s\n", p->dop.dialstr);
+ }
+ p->dop.dialstr[0] = '\0';
+ p->dop.op = DAHDI_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_log(LOG_DEBUG, "Got hook complete in MF FGD, waiting for wink now on channel %d\n",p->channel);
+ break;
+ default:
+ break;
+ }
+ break;
+ case DAHDI_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_log(LOG_DEBUG, "Answering on polarity switch!\n");
+ ast_setstate(p->owner, AST_STATE_UP);
+ if (p->hanguponpolarityswitch) {
+ gettimeofday(&p->polaritydelaytv, NULL);
+ }
+ } else
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Dunno what to do with event %d on channel %d\n", res, p->channel);
+ }
+ return &p->subs[index].f;
+}
+
+static struct ast_frame *__dahdi_exception(struct ast_channel *ast)
+{
+ struct dahdi_pvt *p = ast->tech_pvt;
+ int res;
+ int usedindex=-1;
+ int index;
+ struct ast_frame *f;
+
+
+ index = dahdi_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 = "dahdi_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 = dahdi_get_event(p->subs[SUB_REAL].dfd);
+ /* Switch to real if there is one and this isn't something really silly... */
+ if ((res != DAHDI_EVENT_RINGEROFF) && (res != DAHDI_EVENT_RINGERON) &&
+ (res != DAHDI_EVENT_HOOKCOMPLETE)) {
+ ast_log(LOG_DEBUG, "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 DAHDI_EVENT_ONHOOK:
+ dahdi_disable_ec(p);
+ if (p->owner) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Channel %s still has call, ringing phone\n", p->owner->name);
+ dahdi_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 DAHDI_EVENT_RINGOFFHOOK:
+ dahdi_enable_ec(p);
+ dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_OFFHOOK);
+ if (p->owner && (p->owner->_state == AST_STATE_RINGING)) {
+ p->subs[SUB_REAL].needanswer = 1;
+ p->dialing = 0;
+ }
+ break;
+ case DAHDI_EVENT_HOOKCOMPLETE:
+ case DAHDI_EVENT_RINGERON:
+ case DAHDI_EVENT_RINGEROFF:
+ /* Do nothing */
+ break;
+ case DAHDI_EVENT_WINKFLASH:
+ gettimeofday(&p->flashtime, NULL);
+ if (p->owner) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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 = dahdi_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)) && option_debug)
+ ast_log(LOG_DEBUG, "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 = dahdi_handle_event(ast);
+ return f;
+}
+
+static struct ast_frame *dahdi_exception(struct ast_channel *ast)
+{
+ struct dahdi_pvt *p = ast->tech_pvt;
+ struct ast_frame *f;
+ ast_mutex_lock(&p->lock);
+ f = __dahdi_exception(ast);
+ ast_mutex_unlock(&p->lock);
+ return f;
+}
+
+static struct ast_frame *dahdi_read(struct ast_channel *ast)
+{
+ struct dahdi_pvt *p = ast->tech_pvt;
+ int res;
+ int index;
+ void *readbuf;
+ struct ast_frame *f;
+
+ while (ast_mutex_trylock(&p->lock)) {
+ DEADLOCK_AVOIDANCE(&ast->lock);
+ }
+
+ index = dahdi_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 = "dahdi_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))
+ {
+ struct dahdi_params ps;
+
+ ps.channo = p->channel;
+ if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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 = dahdi_setlinear(p->subs[index].dfd, 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 = dahdi_setlinear(p->subs[index].dfd, 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].dfd, 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 = __dahdi_exception(ast);
+ } else
+ ast_log(LOG_WARNING, "dahdi_rec: %s\n", strerror(errno));
+ }
+ ast_mutex_unlock(&p->lock);
+ return f;
+ }
+ if (res != (p->subs[index].linear ? READ_SIZE * 2 : READ_SIZE)) {
+ ast_log(LOG_DEBUG, "Short read (%d/%d), must be an event...\n", res, p->subs[index].linear ? READ_SIZE * 2 : READ_SIZE);
+ f = __dahdi_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_log(LOG_DEBUG,"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;
+ }
+ }
+ /* Ensure the CW timer decrements only on a single subchannel */
+ if (p->callwaitingrepeat && dahdi_get_index(ast, p, 1) == SUB_REAL) {
+ p->callwaitingrepeat--;
+ }
+ if (p->cidcwexpire)
+ p->cidcwexpire--;
+ /* Repeat callwaiting */
+ if (p->callwaitingrepeat == 1) {
+ p->callwaitrings++;
+ dahdi_callwait(ast);
+ }
+ /* Expire CID/CW */
+ if (p->cidcwexpire == 1) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "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 dahdi 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->pri && p->pri->overlapdial) {
+ /* 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))
+ dahdi_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_dahdi_write(struct dahdi_pvt *p, unsigned char *buf, int len, int index, int linear)
+{
+ int sent=0;
+ int size;
+ int res;
+ int fd;
+ fd = p->subs[index].dfd;
+ 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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Write returned %d (%s) on channel %d\n", res, strerror(errno), p->channel);
+ return sent;
+ }
+ len -= size;
+ buf += size;
+ }
+ return sent;
+}
+
+static int dahdi_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ struct dahdi_pvt *p = ast->tech_pvt;
+ int res;
+ int index;
+ index = dahdi_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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Dropping frame since I'm still dialing on %s...\n",ast->name);
+ return 0;
+ }
+ if (!p->owner) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Dropping frame since there is no active owner on %s...\n",ast->name);
+ return 0;
+ }
+ if (p->cidspill) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 = dahdi_setlinear(p->subs[index].dfd, p->subs[index].linear);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set linear mode on channel %d\n", p->channel);
+ }
+ res = my_dahdi_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 = dahdi_setlinear(p->subs[index].dfd, p->subs[index].linear);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to set companded mode on channel %d\n", p->channel);
+ }
+ res = my_dahdi_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 dahdi_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen)
+{
+ struct dahdi_pvt *p = chan->tech_pvt;
+ int res=-1;
+ int index;
+ int func = DAHDI_FLASH;
+ ast_mutex_lock(&p->lock);
+ index = dahdi_get_index(chan, p, 0);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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) {
+ chan->hangupcause = AST_CAUSE_USER_BUSY;
+ chan->_softhangup |= AST_SOFTHANGUP_DEV;
+ res = 0;
+ } else if (!p->progress && 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), 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].dfd, DAHDI_TONE_BUSY);
+ } else
+#endif
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_TONE_BUSY);
+ break;
+ case AST_CONTROL_RINGING:
+#ifdef HAVE_PRI
+ if ((!p->alerting) && p->sig==SIG_PRI && 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
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_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_log(LOG_DEBUG,"Received AST_CONTROL_PROCEEDING on %s\n",chan->name);
+#ifdef HAVE_PRI
+ if (!p->proceeding && p->sig==SIG_PRI && 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
+ /* don't continue in ast_indicate */
+ res = 0;
+ break;
+ case AST_CONTROL_PROGRESS:
+ ast_log(LOG_DEBUG,"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->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
+ /* 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) {
+ chan->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
+ chan->_softhangup |= AST_SOFTHANGUP_DEV;
+ res = 0;
+ } else if (!p->progress && p->sig==SIG_PRI && 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].dfd, DAHDI_TONE_CONGESTION);
+ } else
+#endif
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_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 = dahdi_set_hook(p->subs[index].dfd, DAHDI_OFFHOOK);
+ res = 0;
+ break;
+ case AST_CONTROL_RADIO_UNKEY:
+ if (p->radio)
+ res = dahdi_set_hook(p->subs[index].dfd, DAHDI_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].dfd,DAHDI_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 AST_CONTROL_SRCUPDATE:
+ res = 0;
+ break;
+ case -1:
+ res = tone_zone_play_tone(p->subs[index].dfd, -1);
+ break;
+ }
+ } else
+ res = 0;
+ ast_mutex_unlock(&p->lock);
+ return res;
+}
+
+static struct ast_channel *dahdi_new(struct dahdi_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;
+ char *b2 = NULL;
+ struct dahdi_params ps;
+ char chanprefix[*dahdi_chan_name_len + 4];
+
+ 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;
+ do {
+ if (b2)
+ free(b2);
+#ifdef HAVE_PRI
+ if (i->bearer || (i->pri && (i->sig == SIG_FXSKS)))
+ b2 = ast_safe_string_alloc("%d:%d-%d", i->pri->trunkgroup, i->channel, y);
+ else
+#endif
+ if (i->channel == CHAN_PSEUDO)
+ b2 = ast_safe_string_alloc("pseudo-%ld", ast_random());
+ else
+ b2 = ast_safe_string_alloc("%d-%d", i->channel, y);
+ for (x = 0; x < 3; x++) {
+ if ((index != x) && i->subs[x].owner && !strcasecmp(b2, i->subs[x].owner->name + (!strncmp(i->subs[x].owner->name, "Zap", 3) ? 4 : 6)))
+ break;
+ }
+ y++;
+ } while (x < 3);
+ strcpy(chanprefix, dahdi_chan_name);
+ strcat(chanprefix, "/%s");
+ tmp = ast_channel_alloc(0, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, i->amaflags, chanprefix, b2);
+ if (b2) /*!> b2 can be freed now, it's been copied into the channel structure */
+ free(b2);
+ if (!tmp)
+ return NULL;
+ tmp->tech = chan_tech;
+ ps.channo = i->channel;
+ res = ioctl(i->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &ps);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to get parameters, assuming MULAW: %s\n", strerror(errno));
+ ps.curlaw = DAHDI_LAW_MULAW;
+ }
+ if (ps.curlaw == DAHDI_LAW_ALAW)
+ deflaw = AST_FORMAT_ALAW;
+ else
+ deflaw = AST_FORMAT_ULAW;
+ if (law) {
+ if (law == DAHDI_LAW_ALAW)
+ deflaw = AST_FORMAT_ALAW;
+ else
+ deflaw = AST_FORMAT_ULAW;
+ }
+ tmp->fds[0] = i->subs[index].dfd;
+ 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;
+ dahdi_setlinear(i->subs[index].dfd, i->subs[index].linear);
+ features = 0;
+ if (index == SUB_REAL) {
+ if (i->busydetect && CANBUSYDETECT(i))
+ features |= DSP_FEATURE_BUSY_DETECT;
+ if ((i->callprogress & 1) && CANPROGRESSDETECT(i))
+ features |= DSP_FEATURE_CALL_PROGRESS;
+ if ((!i->outgoing && (i->callprogress & 4)) ||
+ (i->outgoing && (i->callprogress & 2))) {
+ features |= DSP_FEATURE_FAX_DETECT;
+ }
+#ifdef DAHDI_TONEDETECT
+ x = DAHDI_TONEDETECT_ON | DAHDI_TONEDETECT_MUTE;
+ if (ioctl(i->subs[index].dfd, DAHDI_TONEDETECT, &x)) {
+#endif
+ i->hardwaredtmf = 0;
+ features |= DSP_FEATURE_DTMF_DETECT;
+#ifdef DAHDI_TONEDETECT
+ } else if (NEED_MFDETECT(i)) {
+ i->hardwaredtmf = 1;
+ features |= DSP_FEATURE_DTMF_DETECT;
+ }
+#endif
+ }
+ if (features) {
+ if (i->dsp) {
+ ast_log(LOG_DEBUG, "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;
+#ifdef HAVE_PRI
+ /* We cannot do progress detection until receives PROGRESS message */
+ if (i->outgoing && (i->sig == SIG_PRI)) {
+ /* Remember requested DSP features, don't treat
+ talking as ANSWER */
+ i->dsp_features = features & ~DSP_PROGRESS_TALK;
+ 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;
+#ifdef HAVE_PRI
+ tmp->transfercapability = transfercapability;
+ pbx_builtin_setvar_helper(tmp, "TRANSFERCAPABILITY", ast_transfercapability2str(transfercapability));
+ if (transfercapability & PRI_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 */
+ dahdi_confmute(i, 0);
+ /* Configure the new channel jb */
+ ast_jb_configure(tmp, &global_jbconf);
+ 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 dahdi_wink(struct dahdi_pvt *p, int index)
+{
+ int j;
+ dahdi_set_hook(p->subs[index].dfd, DAHDI_WINK);
+ for (;;)
+ {
+ /* set bits of interest */
+ j = DAHDI_IOMUX_SIGEVENT;
+ /* wait for some happening */
+ if (ioctl(p->subs[index].dfd,DAHDI_IOMUX,&j) == -1) return(-1);
+ /* exit loop if we have it */
+ if (j & DAHDI_IOMUX_SIGEVENT) break;
+ }
+ /* get the event info */
+ if (ioctl(p->subs[index].dfd,DAHDI_GETEVENT,&j) == -1) return(-1);
+ return 0;
+}
+
+static void *ss_thread(void *data)
+{
+ struct ast_channel *chan = data;
+ struct dahdi_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;
+
+ ast_mutex_lock(&ss_thread_lock);
+ ss_thread_count++;
+ ast_mutex_unlock(&ss_thread_lock);
+ /* 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);
+ goto quit;
+ }
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Starting simple switch on '%s'\n", chan->name);
+ index = dahdi_get_index(chan, p, 1);
+ if (index < 0) {
+ ast_log(LOG_WARNING, "Huh?\n");
+ ast_hangup(chan);
+ goto quit;
+ }
+ if (p->dsp)
+ ast_dsp_digitreset(p->dsp);
+ switch (p->sig) {
+#ifdef HAVE_PRI
+ case SIG_PRI:
+ /* 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].dfd, -1);
+ else
+ tone_zone_play_tone(p->subs[index].dfd, DAHDI_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_log(LOG_DEBUG, "waitfordigit returned < 0...\n");
+ ast_hangup(chan);
+ goto quit;
+ } 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)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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].dfd, -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);
+ dahdi_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_log(LOG_DEBUG, "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;
+ }
+ goto quit;
+ 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 (dahdi_wink(p, index))
+ goto quit;
+ /* Fall through */
+ case SIG_EM:
+ case SIG_EM_E1:
+ case SIG_SF:
+ case SIG_FGC_CAMA:
+ res = tone_zone_play_tone(p->subs[index].dfd, -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 (dahdi_wink(p, index)) goto quit;
+ 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 (dahdi_wink(p, index)) goto quit;
+ 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)
+ dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_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_log(LOG_DEBUG, "waitfordigit returned < 0...\n");
+ ast_hangup(chan);
+ goto quit;
+ } 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);
+ goto quit;
+ } else if (res < 0) {
+ ast_log(LOG_DEBUG, "Got hung up before digits finished\n");
+ ast_hangup(chan);
+ goto quit;
+ }
+
+ if (p->sig == SIG_FGC_CAMA) {
+ char anibuf[100];
+
+ if (ast_safe_sleep(chan,1000) == -1) {
+ ast_hangup(chan);
+ goto quit;
+ }
+ dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_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)) {
+ dahdi_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)) goto quit;
+ }
+ dahdi_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].dfd, DAHDI_TONE_CONGESTION);
+ }
+ goto quit;
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_2 "Unknown extension '%s' in context '%s' requested\n", exten, chan->context);
+ sleep(2);
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_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].dfd, DAHDI_TONE_CONGESTION);
+ ast_hangup(chan);
+ goto quit;
+ }
+ 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_log(LOG_DEBUG, "waitfordigit returned < 0...\n");
+ res = tone_zone_play_tone(p->subs[index].dfd, -1);
+ ast_hangup(chan);
+ goto quit;
+ } else if (res) {
+ exten[len++]=res;
+ exten[len] = '\0';
+ }
+ if (!ast_ignore_pattern(chan->context, exten))
+ tone_zone_play_tone(p->subs[index].dfd, -1);
+ else
+ tone_zone_play_tone(p->subs[index].dfd, DAHDI_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));
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Setting call forward to '%s' on channel %d\n", p->call_forward, p->channel);
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_TONE_DIALRECALL);
+ if (res)
+ break;
+ usleep(500000);
+ res = tone_zone_play_tone(p->subs[index].dfd, -1);
+ sleep(1);
+ memset(exten, 0, sizeof(exten));
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_TONE_DIALTONE);
+ len = 0;
+ getforward = 0;
+ } else {
+ res = tone_zone_play_tone(p->subs[index].dfd, -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);
+ dahdi_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].dfd, DAHDI_TONE_CONGESTION);
+ }
+ goto quit;
+ }
+ } 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_log(LOG_DEBUG, "not enough digits (and no ambiguous match)...\n");
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_TONE_CONGESTION);
+ dahdi_wait_event(p->subs[index].dfd);
+ ast_hangup(chan);
+ goto quit;
+ } else if (p->callwaiting && !strcmp(exten, "*70")) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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].dfd, DAHDI_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].dfd,DAHDI_CONFDIAG,&len);
+ memset(exten, 0, sizeof(exten));
+ timeout = firstdigittimeout;
+
+ } else if (!strcmp(exten,ast_pickup_ext())) {
+ /* Scan all channels and see if there are any
+ * ringing channels 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);
+ }
+ dahdi_enable_ec(p);
+ if (ast_pickup_call(chan)) {
+ ast_log(LOG_DEBUG, "No call pickup possible...\n");
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_TONE_CONGESTION);
+ dahdi_wait_event(p->subs[index].dfd);
+ }
+ ast_hangup(chan);
+ goto quit;
+ } else {
+ ast_log(LOG_WARNING, "Huh? Got *8# on call not on real\n");
+ ast_hangup(chan);
+ goto quit;
+ }
+
+ } else if (!p->hidecallerid && !strcmp(exten, "*67")) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Disabling Caller*ID on %s\n", chan->name);
+ /* Disable Caller*ID if enabled */
+ p->hidecallerid = 1;
+ if (chan->cid.cid_num)
+ free(chan->cid.cid_num);
+ chan->cid.cid_num = NULL;
+ if (chan->cid.cid_name)
+ free(chan->cid.cid_name);
+ chan->cid.cid_name = NULL;
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_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].dfd, DAHDI_TONE_DIALRECALL);
+ break;
+ } else if (!strcmp(exten, "*78")) {
+ /* Do not disturb */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Enabled DND on channel %d\n", p->channel);
+ manager_event(EVENT_FLAG_SYSTEM, "DNDState",
+ "Channel: %s/%d\r\n"
+ "Status: enabled\r\n", dahdi_chan_name, p->channel);
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_TONE_DIALRECALL);
+ p->dnd = 1;
+ getforward = 0;
+ memset(exten, 0, sizeof(exten));
+ len = 0;
+ } else if (!strcmp(exten, "*79")) {
+ /* Do not disturb */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Disabled DND on channel %d\n", p->channel);
+ manager_event(EVENT_FLAG_SYSTEM, "DNDState",
+ "Channel: %s/%d\r\n"
+ "Status: disabled\r\n", dahdi_chan_name, p->channel);
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_TONE_DIALRECALL);
+ p->dnd = 0;
+ getforward = 0;
+ memset(exten, 0, sizeof(exten));
+ len = 0;
+ } else if (p->cancallforward && !strcmp(exten, "*72")) {
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_TONE_DIALRECALL);
+ getforward = 1;
+ memset(exten, 0, sizeof(exten));
+ len = 0;
+ } else if (p->cancallforward && !strcmp(exten, "*73")) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Cancelling call forwarding on channel %d\n", p->channel);
+ res = tone_zone_play_tone(p->subs[index].dfd, DAHDI_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);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Parking call to '%s'\n", chan->name);
+ break;
+ } else if (!ast_strlen_zero(p->lastcid_num) && !strcmp(exten, "*60")) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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].dfd, DAHDI_TONE_DIALRECALL);
+ memset(exten, 0, sizeof(exten));
+ len = 0;
+ }
+ } else if (p->hidecallerid && !strcmp(exten, "*82")) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Enabling Caller*ID on %s\n", chan->name);
+ /* Enable Caller*ID if enabled */
+ p->hidecallerid = 0;
+ if (chan->cid.cid_num)
+ free(chan->cid.cid_num);
+ chan->cid.cid_num = NULL;
+ if (chan->cid.cid_name)
+ 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].dfd, DAHDI_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 dahdi_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 == chan_tech) &&
+ (ast_bridged_channel(nbridge)->tech == chan_tech) &&
+ ISTRUNK(pbridge)) {
+ int func = DAHDI_FLASH;
+ /* Clear out the dial buffer */
+ p->dop.dialstr[0] = '\0';
+ /* flash hookswitch */
+ if ((ioctl(pbridge->subs[SUB_REAL].dfd,DAHDI_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);
+ goto quit;
+ } else {
+ tone_zone_play_tone(p->subs[index].dfd, DAHDI_TONE_CONGESTION);
+ dahdi_wait_event(p->subs[index].dfd);
+ tone_zone_play_tone(p->subs[index].dfd, -1);
+ swap_subs(p, SUB_REAL, SUB_THREEWAY);
+ unalloc_sub(p, SUB_THREEWAY);
+ p->owner = p->subs[SUB_REAL].owner;
+ ast_hangup(chan);
+ goto quit;
+ }
+ } else if (!ast_canmatch_extension(chan, chan->context, exten, 1, chan->cid.cid_num) &&
+ ((exten[0] != '*') || (strlen(exten) > 2))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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].dfd, -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);
+ goto quit;
+ } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_RING)) {
+ res = 1;
+ } else
+ res = 0;
+ ast_frfree(f);
+ if (res) {
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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)) {
+ /* If set to use DTMF CID signalling, listen for DTMF */
+ if (p->cid_signalling == CID_SIG_DTMF) {
+ int i = 0;
+ cs = NULL;
+ ast_log(LOG_DEBUG, "Receiving DTMF cid on "
+ "channel %s\n", chan->name);
+ dahdi_setlinear(p->subs[index].dfd, 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);
+ goto quit;
+ }
+ f = ast_read(chan);
+ if (!f)
+ break;
+ 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 (chan->_state == AST_STATE_RING ||
+ chan->_state == AST_STATE_RINGING)
+ break; /* Got ring */
+ }
+ dtmfbuf[i] = '\0';
+ dahdi_setlinear(p->subs[index].dfd, p->subs[index].linear);
+ /* Got cid and ring. */
+ ast_log(LOG_DEBUG, "CID got string '%s'\n", dtmfbuf);
+ 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 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 */
+ dahdi_setlinear(p->subs[index].dfd, 0);
+
+ /* First we wait and listen for the Caller*ID */
+ for (;;) {
+ i = DAHDI_IOMUX_READ | DAHDI_IOMUX_SIGEVENT;
+ if ((res = ioctl(p->subs[index].dfd, DAHDI_IOMUX, &i))) {
+ ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ goto quit;
+ }
+ if (i & DAHDI_IOMUX_SIGEVENT) {
+ res = dahdi_get_event(p->subs[index].dfd);
+ ast_log(LOG_NOTICE, "Got event %d (%s)...\n", res, event2str(res));
+
+ if (p->cid_signalling == CID_SIG_V23_JP) {
+#ifdef DAHDI_EVENT_RINGBEGIN
+ if (res == DAHDI_EVENT_RINGBEGIN) {
+ res = dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_OFFHOOK);
+ usleep(1);
+ }
+#endif
+ } else {
+ res = 0;
+ break;
+ }
+ } else if (i & DAHDI_IOMUX_READ) {
+ res = read(p->subs[index].dfd, 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);
+ goto quit;
+ }
+ 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 on channel '%s'\n", chan->name);
+ 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 (p->cid_signalling == CID_SIG_V23_JP) {
+ res = dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_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);
+ goto quit;
+ }
+ if (!(f = ast_read(chan))) {
+ ast_log(LOG_WARNING, "Hangup received waiting for ring. Exiting simple switch\n");
+ ast_hangup(chan);
+ goto quit;
+ }
+ 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 = DAHDI_IOMUX_READ | DAHDI_IOMUX_SIGEVENT;
+ if ((res = ioctl(p->subs[index].dfd, DAHDI_IOMUX, &i))) {
+ ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ goto quit;
+ }
+ if (i & DAHDI_IOMUX_SIGEVENT) {
+ res = dahdi_get_event(p->subs[index].dfd);
+ 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 chan_dahdi.conf for distinctive ring */
+ if (++receivedRingT == (sizeof(curRingData) / sizeof(curRingData[0])))
+ break;
+ } else if (i & DAHDI_IOMUX_READ) {
+ res = read(p->subs[index].dfd, 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);
+ goto quit;
+ }
+ break;
+ }
+ if (p->ringt)
+ p->ringt--;
+ if (p->ringt == 1) {
+ res = -1;
+ break;
+ }
+ }
+ }
+ if (option_verbose > 2)
+ /* this only shows up if you have n of the dring patterns filled in */
+ ast_verbose( VERBOSE_PREFIX_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 chan_dahdi.conf for this
+ channel */
+ distMatches = 0;
+ for (counter1 = 0; counter1 < 3; counter1++) {
+ if (curRingData[counter1] <= (p->drings.ringnum[counter].ring[counter1]+10) && curRingData[counter1] >=
+ (p->drings.ringnum[counter].ring[counter1]-10)) {
+ 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));
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Distinctive Ring matched context %s\n",p->context);
+ break;
+ }
+ }
+ }
+ /* Restore linear mode (if appropriate) for Caller*ID processing */
+ dahdi_setlinear(p->subs[index].dfd, 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);
+ goto quit;
+ }
+ } else if (p->use_callerid && p->cid_start == CID_START_RING) {
+ /* 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 */
+ dahdi_setlinear(p->subs[index].dfd, 0);
+ for (;;) {
+ i = DAHDI_IOMUX_READ | DAHDI_IOMUX_SIGEVENT;
+ if ((res = ioctl(p->subs[index].dfd, DAHDI_IOMUX, &i))) {
+ ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ goto quit;
+ }
+ if (i & DAHDI_IOMUX_SIGEVENT) {
+ res = dahdi_get_event(p->subs[index].dfd);
+ 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 == DAHDI_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);
+ goto quit;
+ }
+ 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 chan_dahdi.conf for distinctive ring */
+ if (++receivedRingT == (sizeof(curRingData) / sizeof(curRingData[0])))
+ break;
+ } else if (i & DAHDI_IOMUX_READ) {
+ res = read(p->subs[index].dfd, 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);
+ goto quit;
+ }
+ 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);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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;
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Detecting post-CID distinctive ring\n");
+ for (;;) {
+ i = DAHDI_IOMUX_READ | DAHDI_IOMUX_SIGEVENT;
+ if ((res = ioctl(p->subs[index].dfd, DAHDI_IOMUX, &i))) {
+ ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno));
+ callerid_free(cs);
+ ast_hangup(chan);
+ goto quit;
+ }
+ if (i & DAHDI_IOMUX_SIGEVENT) {
+ res = dahdi_get_event(p->subs[index].dfd);
+ 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 chan_dahdi.conf for distinctive ring */
+ if (++receivedRingT == (sizeof(curRingData) / sizeof(curRingData[0])))
+ break;
+ } else if (i & DAHDI_IOMUX_READ) {
+ res = read(p->subs[index].dfd, 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);
+ goto quit;
+ }
+ break;
+ }
+ if (p->ringt)
+ p->ringt--;
+ if (p->ringt == 1) {
+ res = -1;
+ break;
+ }
+ }
+ }
+ }
+ if (p->usedistinctiveringdetection == 1) {
+ if (option_verbose > 2)
+ /* this only shows up if you have n of the dring patterns filled in */
+ ast_verbose( VERBOSE_PREFIX_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 chan_dahdi.conf for this
+ channel */
+ if (option_verbose > 2)
+ /* this only shows up if you have n of the dring patterns filled in */
+ ast_verbose( VERBOSE_PREFIX_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++) {
+ if (curRingData[counter1] <= (p->drings.ringnum[counter].ring[counter1]+10) && curRingData[counter1] >=
+ (p->drings.ringnum[counter].ring[counter1]-10)) {
+ 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));
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Distinctive Ring matched context %s\n",p->context);
+ break;
+ }
+ }
+ }
+ /* Restore linear mode (if appropriate) for Caller*ID processing */
+ dahdi_setlinear(p->subs[index].dfd, 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");
+ }
+ goto quit;
+ 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].dfd, DAHDI_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].dfd, DAHDI_TONE_CONGESTION);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", p->channel);
+ ast_hangup(chan);
+quit:
+ ast_mutex_lock(&ss_thread_lock);
+ ss_thread_count--;
+ ast_cond_signal(&ss_thread_complete);
+ ast_mutex_unlock(&ss_thread_lock);
+ return NULL;
+}
+
+/* destroy a DAHDI channel, identified by its number */
+static int dahdi_destroy_channel_bynum(int channel)
+{
+ struct dahdi_pvt *tmp = NULL;
+ struct dahdi_pvt *prev = NULL;
+
+ tmp = iflist;
+ while (tmp) {
+ if (tmp->channel == channel) {
+ int x = DAHDI_FLASH;
+ ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_HOOK, &x); /* important to create an event for dahdi_wait_event to register so that all ss_threads terminate */
+ destroy_channel(prev, tmp, 1);
+ ast_module_unref(ast_module_info->self);
+ return RESULT_SUCCESS;
+ }
+ prev = tmp;
+ tmp = tmp->next;
+ }
+ return RESULT_FAILURE;
+}
+
+static int handle_init_event(struct dahdi_pvt *i, int event)
+{
+ int res;
+ pthread_t threadid;
+ pthread_attr_t attr;
+ struct ast_channel *chan;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ /* Handle an event on a given channel for the monitor thread. */
+ switch (event) {
+ case DAHDI_EVENT_NONE:
+ case DAHDI_EVENT_BITSCHANGED:
+ break;
+ case DAHDI_EVENT_WINKFLASH:
+ case DAHDI_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 = dahdi_set_hook(i->subs[SUB_REAL].dfd, DAHDI_OFFHOOK);
+ if (res && (errno == EBUSY))
+ break;
+ if (i->cidspill) {
+ /* Cancel VMWI spill */
+ free(i->cidspill);
+ i->cidspill = NULL;
+ }
+ if (i->immediate) {
+ dahdi_enable_ec(i);
+ /* The channel is immediately up. Start right away */
+ res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, DAHDI_TONE_RINGTONE);
+ chan = dahdi_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].dfd, DAHDI_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 = dahdi_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].dfd, DAHDI_TONE_STUTTER);
+ else
+ res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, DAHDI_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(&threadid, &attr, 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].dfd, DAHDI_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 */
+ chan = dahdi_new(i, AST_STATE_RING, 0, SUB_REAL, 0, 0);
+ if (chan && ast_pthread_create(&threadid, &attr, 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].dfd, DAHDI_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].dfd, DAHDI_TONE_CONGESTION);
+ if (res < 0)
+ ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel);
+ return -1;
+ }
+ break;
+ case DAHDI_EVENT_NOALARM:
+ i->inalarm = 0;
+ if (!i->unknown_alarm) {
+ ast_log(LOG_NOTICE, "Alarm cleared on channel %d\n", i->channel);
+ manager_event(EVENT_FLAG_SYSTEM, "AlarmClear",
+ "Channel: %d\r\n", i->channel);
+ } else {
+ i->unknown_alarm = 0;
+ }
+ break;
+ case DAHDI_EVENT_ALARM:
+ i->inalarm = 1;
+ res = get_alarms(i);
+ handle_alarms(i, res);
+ /* fall thru intentionally */
+ case DAHDI_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:
+ dahdi_disable_ec(i);
+ res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, -1);
+ dahdi_set_hook(i->subs[SUB_REAL].dfd, DAHDI_ONHOOK);
+ break;
+ case SIG_GR303FXOKS:
+ case SIG_FXOKS:
+ dahdi_disable_ec(i);
+ /* Diddle the battery for the zhone */
+#ifdef ZHONE_HACK
+ dahdi_set_hook(i->subs[SUB_REAL].dfd, DAHDI_OFFHOOK);
+ usleep(1);
+#endif
+ res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, -1);
+ dahdi_set_hook(i->subs[SUB_REAL].dfd, DAHDI_ONHOOK);
+ break;
+ case SIG_PRI:
+ dahdi_disable_ec(i);
+ res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, -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].dfd, -1);
+ return -1;
+ }
+ break;
+ case DAHDI_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->polarity = POLARITY_REV;
+ ast_verbose(VERBOSE_PREFIX_2 "Starting post polarity "
+ "CID detection on channel %d\n",
+ i->channel);
+ chan = dahdi_new(i, AST_STATE_PRERING, 0, SUB_REAL, 0, 0);
+ if (chan && ast_pthread_create(&threadid, &attr, 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 DAHDI_EVENT_REMOVED: /* destroy channel */
+ ast_log(LOG_NOTICE,
+ "Got DAHDI_EVENT_REMOVED. Destroying channel %d\n",
+ i->channel);
+ dahdi_destroy_channel_bynum(i->channel);
+ break;
+ }
+ pthread_attr_destroy(&attr);
+ return 0;
+}
+
+static void *do_monitor(void *data)
+{
+ int count, res, res2, spoint, pollres=0;
+ struct dahdi_pvt *i;
+ struct dahdi_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_log(LOG_DEBUG, "Monitor starting...\n");
+#endif
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
+ for (;;) {
+ /* Lock the interface list */
+ ast_mutex_lock(&iflock);
+ if (!pfds || (lastalloc != ifcount)) {
+ if (pfds) {
+ 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
+ dahdi_pvt that does not have an associated owner channel */
+ count = 0;
+ i = iflist;
+ while (i) {
+ if ((i->subs[SUB_REAL].dfd > -1) && i->sig && (!i->radio)) {
+ if (!i->owner && !i->subs[SUB_REAL].owner) {
+ /* This needs to be watched, as it lacks an owner */
+ pfds[count].fd = i->subs[SUB_REAL].dfd;
+ pfds[count].events = POLLPRI;
+ pfds[count].revents = 0;
+ /* Message waiting or r2 channels also get watched for reading */
+ if (i->cidspill)
+ 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_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ pthread_testcancel();
+ /* Wait at least a second for something to happen */
+ res = poll(pfds, count, 1000);
+ pthread_testcancel();
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
+ /* 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 & __DAHDI_SIG_FXO)) {
+ res = ast_app_has_voicemail(last->mailbox, NULL);
+ if (last->msgstate != res) {
+ int x;
+ ast_log(LOG_DEBUG, "Message status for %s changed from %d to %d on %d\n", last->mailbox, last->msgstate, res, last->channel);
+ x = DAHDI_FLUSH_BOTH;
+ res2 = ioctl(last->subs[SUB_REAL].dfd, DAHDI_FLUSH, &x);
+ if (res2)
+ ast_log(LOG_WARNING, "Unable to flush input on channel %d: %s\n", last->channel, strerror(errno));
+ if ((last->cidspill = ast_calloc(1, MAX_CALLERID_SIZE))) {
+ /* Turn on on hook transfer for 4 seconds */
+ x = 4000;
+ ioctl(last->subs[SUB_REAL].dfd, DAHDI_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].dfd > -1) && i->sig) {
+ if (i->radio && !i->owner)
+ {
+ res = dahdi_get_event(i->subs[SUB_REAL].dfd);
+ if (res)
+ {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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].dfd, 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].dfd);
+ i = i->next;
+ continue;
+ }
+ if (!i->cidspill) {
+ ast_log(LOG_WARNING, "Whoa.... I'm reading but have no cidspill (%d)...\n", i->subs[SUB_REAL].dfd);
+ i = i->next;
+ continue;
+ }
+ res = read(i->subs[SUB_REAL].dfd, buf, sizeof(buf));
+ if (res > 0) {
+ /* 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].dfd, 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));
+ }
+ }
+ 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].dfd);
+ i = i->next;
+ continue;
+ }
+ res = dahdi_get_event(i->subs[SUB_REAL].dfd);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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)
+{
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ /* 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, &attr, do_monitor, NULL) < 0) {
+ ast_mutex_unlock(&monlock);
+ ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
+ pthread_attr_destroy(&attr);
+ return -1;
+ }
+ }
+ ast_mutex_unlock(&monlock);
+ pthread_attr_destroy(&attr);
+ return 0;
+}
+
+#ifdef HAVE_PRI
+static int pri_resolve_span(int *span, int channel, int offset, struct dahdi_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) {
+ /* E1 */
+ pris[*span].dchannels[0] = 16 + offset;
+ } else if (si->totalchans == 24) {
+ /* T1 or J1 */
+ pris[*span].dchannels[0] = 24 + offset;
+ } else if (si->totalchans == 3) {
+ /* BRI */
+ pris[*span].dchannels[0] = 3 + offset;
+ } else {
+ ast_log(LOG_WARNING, "Unable to use span %d, since the D-channel cannot be located (unexpected span size of %d channels)\n", *span, si->totalchans);
+ *span = -1;
+ return 0;
+ }
+ 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 dahdi_spaninfo si;
+ struct dahdi_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(DAHDI_FILE_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, DAHDI_SPECIFY, &x)) {
+ ast_log(LOG_WARNING, "Failed to specify channel %d: %s\n", channels[y], strerror(errno));
+ close(fd);
+ return -1;
+ }
+ if (ioctl(fd, DAHDI_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, DAHDI_SPANSTAT, &si)) {
+ ast_log(LOG_WARNING, "Failed go get span information on channel %d (span %d): %s\n", channels[y], p.spanno, strerror(errno));
+ 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);
+ 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);
+ 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;
+ 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
+
+static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf, struct dahdi_pri *pri, int reloading)
+{
+ /* Make a dahdi_pvt structure for this interface (or CRV if "pri" is specified) */
+ struct dahdi_pvt *tmp = NULL, *tmp2, *prev = NULL;
+ char fn[80];
+#if 1
+ struct dahdi_bufferinfo bi;
+#endif
+ int res;
+ int span=0;
+ int here = 0;
+ int x;
+ struct dahdi_pvt **wlist;
+ struct dahdi_pvt **wend;
+ struct dahdi_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 != 1) {
+ if (!(tmp = ast_calloc(1, sizeof(*tmp)))) {
+ if (tmp)
+ free(tmp);
+ return NULL;
+ }
+ ast_mutex_init(&tmp->lock);
+ ifcount++;
+ for (x = 0; x < 3; x++)
+ tmp->subs[x].dfd = -1;
+ tmp->channel = channel;
+ }
+
+ if (tmp) {
+ int chan_sig = conf->chan.sig;
+ if (!here) {
+ if ((channel != CHAN_PSEUDO) && !pri) {
+ int count = 0;
+ snprintf(fn, sizeof(fn), "%d", channel);
+ /* Open non-blocking */
+ tmp->subs[SUB_REAL].dfd = dahdi_open(fn);
+ while (tmp->subs[SUB_REAL].dfd < 0 && reloading == 2 && count < 1000) { /* the kernel may not call dahdi_release fast enough for the open flagbit to be cleared in time */
+ usleep(1);
+ tmp->subs[SUB_REAL].dfd = dahdi_open(fn);
+ count++;
+ }
+ /* Allocate a DAHDI structure */
+ if (tmp->subs[SUB_REAL].dfd < 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_dahdi_pvt(&tmp);
+ return NULL;
+ }
+ memset(&p, 0, sizeof(p));
+ res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &p);
+ if (res < 0) {
+ ast_log(LOG_ERROR, "Unable to get parameters: %s\n", strerror(errno));
+ destroy_dahdi_pvt(&tmp);
+ return NULL;
+ }
+ 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_dahdi_pvt(&tmp);
+ return NULL;
+ }
+ tmp->law = p.curlaw;
+ tmp->span = p.spanno;
+ span = p.spanno - 1;
+ } else {
+ if (channel == CHAN_PSEUDO)
+ chan_sig = 0;
+ else if ((chan_sig != SIG_FXOKS) && (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_PRI
+ if ((chan_sig == SIG_PRI) || (chan_sig == SIG_GR303FXOKS) || (chan_sig == SIG_GR303FXSKS)) {
+ int offset;
+ int myswitchtype;
+ int matchesdchan;
+ int x,y;
+ offset = 0;
+ if ((chan_sig == SIG_PRI) && ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_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_dahdi_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_dahdi_pvt(&tmp);
+ return NULL;
+ } else {
+ struct dahdi_spaninfo si;
+ si.spanno = 0;
+ if (ioctl(tmp->subs[SUB_REAL].dfd,DAHDI_SPANSTAT,&si) == -1) {
+ ast_log(LOG_ERROR, "Unable to get span status: %s\n", strerror(errno));
+ destroy_dahdi_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_dahdi_pvt(&tmp);
+ return NULL;
+ }
+ if (chan_sig == SIG_PRI)
+ 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_dahdi_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_dahdi_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_dahdi_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_dahdi_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_dahdi_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_dahdi_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_dahdi_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_dahdi_pvt(&tmp);
+ return NULL;
+ }
+ 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;
+#ifdef HAVE_PRI_INBANDDISCONNECT
+ pris[span].inbanddisconnect = conf->pri.inbanddisconnect;
+#endif
+ 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_dahdi_pvt(&tmp);
+ return NULL;
+ }
+ }
+ } else {
+ tmp->prioffset = 0;
+ }
+#endif
+ } else {
+ chan_sig = tmp->sig;
+ memset(&p, 0, sizeof(p));
+ if (tmp->subs[SUB_REAL].dfd > -1)
+ res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &p);
+ }
+ /* Adjust starttime on loopstart and kewlstart trunks to reasonable values */
+ switch (chan_sig) {
+ case SIG_FXSKS:
+ case SIG_FXSLS:
+ case SIG_EM:
+ case SIG_EM_E1:
+ case SIG_EMWINK:
+ case SIG_FEATD:
+ case SIG_FEATDMF:
+ case SIG_FEATDMF_TA:
+ case SIG_FEATB:
+ case SIG_E911:
+ case SIG_SF:
+ case SIG_SFWINK:
+ case SIG_FGC_CAMA:
+ case SIG_FGC_CAMAMF:
+ case SIG_SF_FEATD:
+ case SIG_SF_FEATDMF:
+ case SIG_SF_FEATB:
+ p.starttime = 250;
+ break;
+ }
+
+ if (tmp->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 (!tmp->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].dfd >= 0)
+ {
+ res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_SET_PARAMS, &p);
+ if (res < 0) {
+ ast_log(LOG_ERROR, "Unable to set parameters: %s\n", strerror(errno));
+ destroy_dahdi_pvt(&tmp);
+ return NULL;
+ }
+ }
+#if 1
+ if (!here && (tmp->subs[SUB_REAL].dfd > -1)) {
+ memset(&bi, 0, sizeof(bi));
+ res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GET_BUFINFO, &bi);
+ if (!res) {
+ bi.txbufpolicy = conf->chan.buf_policy;
+ bi.rxbufpolicy = conf->chan.buf_policy;
+ bi.numbufs = conf->chan.buf_no;
+ res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_SET_BUFINFO, &bi);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set buffer policy on channel %d: %s\n", channel, strerror(errno));
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to check buffer policy on channel %d: %s\n", channel, strerror(errno));
+ }
+#endif
+ tmp->immediate = conf->chan.immediate;
+ tmp->transfertobusy = conf->chan.transfertobusy;
+ tmp->sig = chan_sig;
+ tmp->outsigmod = conf->chan.outsigmod;
+ tmp->ringt_base = ringt_base;
+ tmp->firstradio = 0;
+ if ((chan_sig == SIG_FXOKS) || (chan_sig == SIG_FXOLS) || (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 = conf->chan.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;
+ if (tmp->echocancel)
+ tmp->echocanbridged = conf->chan.echocanbridged;
+ else {
+ if (conf->chan.echocanbridged)
+ ast_log(LOG_NOTICE, "echocancelwhenbridged requires echocancel to be enabled; ignoring\n");
+ tmp->echocanbridged = 0;
+ }
+ 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->dahditrcallerid = conf->chan.dahditrcallerid;
+ 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));
+ tmp->msgstate = -1;
+ tmp->group = conf->chan.group;
+ tmp->callgroup = conf->chan.callgroup;
+ tmp->pickupgroup= conf->chan.pickupgroup;
+ tmp->rxgain = conf->chan.rxgain;
+ tmp->txgain = conf->chan.txgain;
+ tmp->tonezone = conf->chan.tonezone;
+ tmp->onhooktime = time(NULL);
+ if (tmp->subs[SUB_REAL].dfd > -1) {
+ set_actual_gain(tmp->subs[SUB_REAL].dfd, 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 (chan_sig != SIG_PRI)
+ /* Hang it up to be sure it's good */
+ dahdi_set_hook(tmp->subs[SUB_REAL].dfd, DAHDI_ONHOOK);
+ }
+ ioctl(tmp->subs[SUB_REAL].dfd,DAHDI_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;
+ }
+#endif
+ if ((res = get_alarms(tmp)) != DAHDI_ALARM_NONE) {
+ tmp->inalarm = 1;
+ handle_alarms(tmp, res);
+ } else {
+ /* yes, this looks strange... the unknown_alarm flag is only used to
+ control whether an 'alarm cleared' message gets generated when we
+ get an indication that the channel is no longer in alarm status.
+ however, the channel *could* be in an alarm status that we aren't
+ aware of (since get_alarms() only reports span alarms, not channel
+ alarms). setting this flag will cause any potential 'alarm cleared'
+ message to be suppressed, but if a real alarm occurs before that
+ happens, this flag will get cleared by it and the situation will
+ be normal.
+ */
+ tmp->unknown_alarm = 1;
+ }
+ }
+
+ tmp->polarityonanswerdelay = conf->chan.polarityonanswerdelay;
+ tmp->answeronpolarityswitch = conf->chan.answeronpolarityswitch;
+ tmp->hanguponpolarityswitch = conf->chan.hanguponpolarityswitch;
+ tmp->sendcalleridafter = conf->chan.sendcalleridafter;
+
+ }
+ 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 dahdi_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 dahdi_pvt *p, int channelmatch, ast_group_t groupmatch, int *busy, int *channelmatched, int *groupmatched)
+{
+ int res;
+ struct dahdi_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 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
+ if (!(p->radio || (p->oprmode < 0)))
+ {
+ if (!p->sig || (p->sig == SIG_FXSLS))
+ return 1;
+ /* Check hook state */
+ if (p->subs[SUB_REAL].dfd > -1)
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_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: %s\n", p->channel, strerror(errno));
+ } 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 DAHDI_CHECK_HOOKSTATE
+ return 0;
+#else
+ return 1;
+#endif
+ } else if (par.rxisoffhook) {
+ ast_log(LOG_DEBUG, "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].dfd > -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 dahdi_pvt *chandup(struct dahdi_pvt *src)
+{
+ struct dahdi_pvt *p;
+ struct dahdi_bufferinfo bi;
+ int res;
+
+ if ((p = ast_malloc(sizeof(*p)))) {
+ memcpy(p, src, sizeof(struct dahdi_pvt));
+ ast_mutex_init(&p->lock);
+ p->subs[SUB_REAL].dfd = dahdi_open(DAHDI_FILE_PSEUDO);
+ /* Allocate a DAHDI structure */
+ if (p->subs[SUB_REAL].dfd < 0) {
+ ast_log(LOG_ERROR, "Unable to dup channel: %s\n", strerror(errno));
+ destroy_dahdi_pvt(&p);
+ return NULL;
+ }
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_GET_BUFINFO, &bi);
+ if (!res) {
+ bi.txbufpolicy = p->buf_policy;
+ bi.rxbufpolicy = p->buf_policy;
+ bi.numbufs = p->buf_no;
+ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SET_BUFINFO, &bi);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set buffer policy on dup channel: %s\n", strerror(errno));
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to check buffer policy on dup channel: %s\n", strerror(errno));
+ }
+ p->destroy = 1;
+ p->next = iflist;
+ p->prev = NULL;
+ iflist = p;
+ if (iflist->next)
+ iflist->next->prev = p;
+ return p;
+}
+
+
+#ifdef HAVE_PRI
+static int pri_find_empty_chan(struct dahdi_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_log(LOG_DEBUG, "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 *dahdi_request(const char *type, int format, void *data, int *cause)
+{
+ ast_group_t groupmatch = 0;
+ int channelmatch = -1;
+ int roundrobin = 0;
+ int callwait = 0;
+ int busy = 0;
+ struct dahdi_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 dahdi_pri *pri=NULL;
+#endif
+ struct dahdi_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 = ((ast_group_t) 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)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Using channel %d\n", p->channel);
+ if (p->inalarm)
+ goto next;
+
+ callwait = (p->owner != NULL);
+#ifdef HAVE_PRI
+ if (pri && (p->subs[SUB_REAL].dfd < 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_log(LOG_DEBUG, "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 = dahdi_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;
+}
+
+
+#ifdef HAVE_PRI
+static struct dahdi_pvt *pri_find_crv(struct dahdi_pri *pri, int crv)
+{
+ struct dahdi_pvt *p;
+ p = pri->crvs;
+ while (p) {
+ if (p->channel == crv)
+ return p;
+ p = p->next;
+ }
+ return NULL;
+}
+
+
+static int pri_find_principle(struct dahdi_pri *pri, int channel)
+{
+ int x;
+ int span = PRI_SPAN(channel);
+ int spanfd;
+ struct dahdi_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, DAHDI_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 dahdi_pri *pri, int principle, q931_call *c)
+{
+ int x;
+ struct dahdi_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) {
+ struct dahdi_pvt *new = pri->pvts[principle], *old = pri->pvts[x];
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Moving call from channel %d to channel %d\n",
+ old->channel, new->channel);
+ if (new->owner) {
+ ast_log(LOG_WARNING, "Can't fix up channel from %d to %d because %d is already in use\n",
+ old->channel, new->channel, new->channel);
+ return -1;
+ }
+ /* Fix it all up now */
+ new->owner = old->owner;
+ old->owner = NULL;
+ if (new->owner) {
+ ast_string_field_build(new->owner, name, "%s/%d:%d-%d", dahdi_chan_name, pri->trunkgroup, new->channel, 1);
+ new->owner->tech_pvt = new;
+ new->owner->fds[0] = new->subs[SUB_REAL].dfd;
+ new->subs[SUB_REAL].owner = old->subs[SUB_REAL].owner;
+ old->subs[SUB_REAL].owner = NULL;
+ } else
+ ast_log(LOG_WARNING, "Whoa, there's no owner, and we're having to fix up channel %d to channel %d\n", old->channel, new->channel);
+ new->call = old->call;
+ old->call = NULL;
+
+ /* Copy any DSP that may be present */
+ new->dsp = old->dsp;
+ new->dsp_features = old->dsp_features;
+ old->dsp = NULL;
+ old->dsp_features = 0;
+ }
+ 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 */
+ dahdi_close_sub(crv, SUB_REAL);
+ pri->pvts[principle]->call = crv->call;
+ pri_assign_bearer(crv, pri, pri->pvts[principle]);
+ ast_log(LOG_DEBUG, "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 dahdi_pvt *pvt = chan->tech_pvt;
+ struct ast_frame *f;
+ char ex[80];
+ /* Wait up to 30 seconds for an answer */
+ int newms, ms = 30000;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_3 "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:
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_3 "Idle channel '%s' busy, waiting...\n", chan->name);
+ break;
+ case AST_CONTROL_CONGESTION:
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_3 "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 dahdi_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 ((dchan >= 0) && (span >= 0)) {
+ if (dchancount > 1)
+ ast_verbose("[Span %d D-Channel %d]%s", span, dchan, s);
+ else
+ ast_verbose("%s", s);
+ } else
+ ast_log(LOG_ERROR, "PRI debug error: could not find pri associated it with debug message output\n");
+ } else
+ ast_verbose("%s", s);
+
+ ast_mutex_lock(&pridebugfdlock);
+
+ if (pridebugfd >= 0) {
+ if (write(pridebugfd, s, strlen(s)) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ }
+
+ ast_mutex_unlock(&pridebugfdlock);
+}
+
+static void dahdi_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 ((dchan >= 0) && (span >= 0)) {
+ if (dchancount > 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, "PRI debug error: could not find pri associated it with debug message output\n");
+ } else
+ ast_log(LOG_ERROR, "%s", s);
+
+ ast_mutex_lock(&pridebugfdlock);
+
+ if (pridebugfd >= 0) {
+ if (write(pridebugfd, s, strlen(s)) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ }
+
+ ast_mutex_unlock(&pridebugfdlock);
+}
+
+static int pri_check_restart(struct dahdi_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 dahdi_pvt *p, struct dahdi_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_mutex_trylock(&p->subs[x].owner->lock)) {
+ redo++;
+ DEADLOCK_AVOIDANCE(&p->lock);
+ }
+ if (p->subs[x].owner) {
+ ast_queue_hangup(p->subs[x].owner);
+ ast_mutex_unlock(&p->subs[x].owner->lock);
+ }
+ }
+ } 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 dahdi_pri *pri, const char *number, const int plan)
+{
+ 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 int dahdi_setlaw(int dfd, int law)
+{
+ int res;
+ res = ioctl(dfd, DAHDI_SETLAW, &law);
+ if (res)
+ return res;
+ return 0;
+}
+
+static void *pri_dchannel(void *vpri)
+{
+ struct dahdi_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 = { 0, 0 };
+ 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 dahdi_pvt *crv;
+ pthread_t threadid;
+ pthread_attr_t attr;
+ char ani2str[6];
+ char plancallingnum[256];
+ char plancallingani[256];
+ char calledtonstr[10];
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
+ gettimeofday(&lastidle, NULL);
+ 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 = dahdi_request(dahdi_chan_name, 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);
+ dahdi_hangup(idle);
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to request channel 'DAHDI/%s' for idle call\n", idlen);
+ gettimeofday(&lastidle, NULL);
+ }
+ } 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);
+
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ pthread_testcancel();
+ e = NULL;
+ res = poll(fds, numdchans, lowest.tv_sec * 1000 + lowest.tv_usec / 1000);
+ pthread_testcancel();
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
+ 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], DAHDI_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);
+ /* Keep track of alarm state */
+ if (x == DAHDI_EVENT_ALARM) {
+ pri->dchanavail[which] &= ~(DCHAN_NOTINALARM | DCHAN_UP);
+ pri_find_dchan(pri);
+ } else if (x == DAHDI_EVENT_NOALARM) {
+ pri->dchanavail[which] |= DCHAN_NOTINALARM;
+ pri_restart(pri->dchans[which]);
+ }
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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) {
+ if (!(pri->dchanavail[which] & DCHAN_UP)) {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "%s D-Channel on span %d up\n", pri_order(which), pri->span);
+ }
+ pri->dchanavail[which] |= DCHAN_UP;
+ } else {
+ if (pri->dchanavail[which] & DCHAN_UP) {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "%s D-Channel on span %d down\n", pri_order(which), pri->span);
+ }
+ 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:
+ 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:
+ 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 dahdi_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 {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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 {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_2 "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 && 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, };
+ dahdi_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 && 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, };
+ dahdi_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_log(LOG_DEBUG, "Ring requested on channel %d/%d already in use or previously requested on span %d. Attempting to renegotiating 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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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))) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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 && 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].dfd, DAHDI_AUDIOMODE, &law) == -1)
+ ast_log(LOG_WARNING, "Unable to set audio mode on channel %d to %d: %s\n", pri->pvts[chanpos]->channel, law, strerror(errno));
+ }
+ if (e->ring.layer1 == PRI_LAYER_1_ALAW)
+ law = DAHDI_LAW_ALAW;
+ else
+ law = DAHDI_LAW_MULAW;
+ res = dahdi_setlaw(pri->pvts[chanpos]->subs[SUB_REAL].dfd, 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].dfd, 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) {
+ /* 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 (!e->ring.complete && pri->overlapdial && 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 = dahdi_new(crv, AST_STATE_RESERVED, 0, SUB_REAL, law, e->ring.ctype);
+ pri->pvts[chanpos]->owner = &inuse;
+ ast_log(LOG_DEBUG, "Started up crv %d:%d on bearer channel %d\n", pri->trunkgroup, crv->channel, crv->bearer->channel);
+ } else {
+ c = dahdi_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);
+ }
+
+#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);
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ if (c && !ast_pthread_create(&threadid, &attr, ss_thread, c)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ }
+ }
+ pthread_attr_destroy(&attr);
+ } else {
+ ast_mutex_unlock(&pri->lock);
+ /* Release PRI lock while we create the channel */
+ c = dahdi_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);
+ }
+
+#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);
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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);
+ dahdi_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 {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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)) {
+ dahdi_enable_ec(pri->pvts[chanpos]);
+ pri->pvts[chanpos]->subs[SUB_REAL].needringing = 1;
+ pri->pvts[chanpos]->alerting = 1;
+ } else
+ ast_log(LOG_DEBUG, "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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "PROGRESS with 'user busy' received, signaling 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_log(LOG_DEBUG, "Queuing frame from PRI_EVENT_PROGRESS on channel %d/%d span %d\n",
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset,pri->span);
+ dahdi_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_log(LOG_DEBUG, "Queuing frame from PRI_EVENT_PROCEEDING on channel %d/%d span %d\n",
+ pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset,pri->span);
+ dahdi_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;
+ dahdi_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;
+ dahdi_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_log(LOG_DEBUG, "Starting up GR-303 trunk now that we got CONNECT...\n");
+ x = DAHDI_START;
+ res = ioctl(pri->pvts[chanpos]->subs[SUB_REAL].dfd, DAHDI_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].dfd, DAHDI_DIAL, &pri->pvts[chanpos]->dop);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to initiate dialing on trunk channel %d: %s\n", pri->pvts[chanpos]->channel, strerror(errno));
+ pri->pvts[chanpos]->dop.dialstr[0] = '\0';
+ } else
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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 */
+ dahdi_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 dahdi_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;
+ }
+ }
+ }
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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)
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ }
+ }
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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)
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "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;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "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;
+ dahdi_queue_frame(pri->pvts[chanpos], &f, pri);
+ break;
+ case PRI_NOTIFY_REMOTE_RETRIEVAL:
+ f.subclass = AST_CONTROL_UNHOLD;
+ dahdi_queue_frame(pri->pvts[chanpos], &f, pri);
+ break;
+ }
+ ast_mutex_unlock(&pri->pvts[chanpos]->lock);
+ }
+ break;
+ default:
+ ast_log(LOG_DEBUG, "Event: %d\n", e->e);
+ }
+ }
+ ast_mutex_unlock(&pri->lock);
+ }
+ /* Never reached */
+ return NULL;
+}
+
+static int start_pri(struct dahdi_pri *pri)
+{
+ int res, x;
+ struct dahdi_params p;
+ struct dahdi_bufferinfo bi;
+ struct dahdi_spaninfo si;
+ int i;
+
+ for (i = 0; i < NUM_DCHANS; i++) {
+ if (!pri->dchannels[i])
+ break;
+ pri->fds[i] = open(DAHDI_FILE_CHANNEL, O_RDWR, 0600);
+ x = pri->dchannels[i];
+ if ((pri->fds[i] < 0) || (ioctl(pri->fds[i],DAHDI_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], DAHDI_GET_PARAMS, &p);
+ if (res) {
+ dahdi_close_pri_fd(pri, i);
+ ast_log(LOG_ERROR, "Unable to get parameters for D-channel %d (%s)\n", x, strerror(errno));
+ return -1;
+ }
+ if ((p.sigtype != DAHDI_SIG_HDLCFCS) && (p.sigtype != DAHDI_SIG_HARDHDLC)) {
+ dahdi_close_pri_fd(pri, i);
+ ast_log(LOG_ERROR, "D-channel %d is not in HDLC/FCS mode. See /etc/dahdi/system.conf\n", x);
+ return -1;
+ }
+ memset(&si, 0, sizeof(si));
+ res = ioctl(pri->fds[i], DAHDI_SPANSTAT, &si);
+ if (res) {
+ dahdi_close_pri_fd(pri, i);
+ 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 = DAHDI_POLICY_IMMEDIATE;
+ bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE;
+ bi.numbufs = 32;
+ bi.bufsize = 1024;
+ if (ioctl(pri->fds[i], DAHDI_SET_BUFINFO, &bi)) {
+ ast_log(LOG_ERROR, "Unable to set appropriate buffering on channel %d: %s\n", x, strerror(errno));
+ dahdi_close_pri_fd(pri, i);
+ return -1;
+ }
+ 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 = 1;
+ pri_set_overlapdial(pri->dchans[i],pri->overlapdial);
+#ifdef HAVE_PRI_INBANDDISCONNECT
+ pri_set_inbanddisconnect(pri->dchans[i], pri->inbanddisconnect);
+#endif
+ /* Enslave to master if appropriate */
+ if (i)
+ pri_enslave(pri->dchans[0], pri->dchans[i]);
+ if (!pri->dchans[i]) {
+ dahdi_close_pri_fd(pri, i);
+ 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;
+ dahdi_close_pri_fd(pri, i);
+ }
+ 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) {
+ if (asprintf(&ret, "%d", span + 1) < 0) { /* user indexes start from 1 */
+ ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno));
+ }
+ 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 int handle_pri_set_debug_file(int fd, int argc, char **argv)
+{
+ int myfd;
+
+ if (!strncasecmp(argv[1], "set", 3)) {
+ if (argc < 5)
+ return RESULT_SHOWUSAGE;
+
+ if (ast_strlen_zero(argv[4]))
+ return RESULT_SHOWUSAGE;
+
+ myfd = open(argv[4], O_CREAT|O_WRONLY, 0600);
+ if (myfd < 0) {
+ ast_cli(fd, "Unable to open '%s' for writing\n", argv[4]);
+ return RESULT_SUCCESS;
+ }
+
+ ast_mutex_lock(&pridebugfdlock);
+
+ if (pridebugfd >= 0)
+ close(pridebugfd);
+
+ pridebugfd = myfd;
+ ast_copy_string(pridebugfilename,argv[4],sizeof(pridebugfilename));
+
+ ast_mutex_unlock(&pridebugfdlock);
+
+ ast_cli(fd, "PRI debug output will be sent to '%s'\n", argv[4]);
+ } else {
+ /* Assume it is unset */
+ ast_mutex_lock(&pridebugfdlock);
+ close(pridebugfd);
+ pridebugfd = -1;
+ ast_cli(fd, "PRI debug output to file disabled\n");
+ ast_mutex_unlock(&pridebugfdlock);
+ }
+
+ return RESULT_SUCCESS;
+}
+
+#ifdef HAVE_PRI_VERSION
+static int handle_pri_version(int fd, int agc, char *argv[]) {
+ ast_cli(fd, "libpri version: %s\n", pri_get_version());
+ return RESULT_SUCCESS;
+}
+#endif
+
+static int handle_pri_debug(int fd, int argc, char *argv[])
+{
+ int span;
+ int x;
+ if (argc < 4) {
+ return RESULT_SHOWUSAGE;
+ }
+ span = atoi(argv[3]);
+ if ((span < 1) || (span > NUM_SPANS)) {
+ ast_cli(fd, "Invalid span %s. Should be a number %d to %d\n", argv[3], 1, NUM_SPANS);
+ return RESULT_SUCCESS;
+ }
+ if (!pris[span-1].pri) {
+ ast_cli(fd, "No PRI running on span %d\n", span);
+ return RESULT_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(fd, "Enabled debugging on span %d\n", span);
+ return RESULT_SUCCESS;
+}
+
+
+
+static int handle_pri_no_debug(int fd, int argc, char *argv[])
+{
+ int span;
+ int x;
+ if (argc < 5)
+ return RESULT_SHOWUSAGE;
+ span = atoi(argv[4]);
+ if ((span < 1) || (span > NUM_SPANS)) {
+ ast_cli(fd, "Invalid span %s. Should be a number %d to %d\n", argv[4], 1, NUM_SPANS);
+ return RESULT_SUCCESS;
+ }
+ if (!pris[span-1].pri) {
+ ast_cli(fd, "No PRI running on span %d\n", span);
+ return RESULT_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(fd, "Disabled debugging on span %d\n", span);
+ return RESULT_SUCCESS;
+}
+
+static int handle_pri_really_debug(int fd, int argc, char *argv[])
+{
+ int span;
+ int x;
+ if (argc < 5)
+ return RESULT_SHOWUSAGE;
+ span = atoi(argv[4]);
+ if ((span < 1) || (span > NUM_SPANS)) {
+ ast_cli(fd, "Invalid span %s. Should be a number %d to %d\n", argv[4], 1, NUM_SPANS);
+ return RESULT_SUCCESS;
+ }
+ if (!pris[span-1].pri) {
+ ast_cli(fd, "No PRI running on span %d\n", span);
+ return RESULT_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(fd, "Enabled EXTENSIVE debugging on span %d\n", span);
+ return RESULT_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 int handle_pri_show_spans(int fd, int argc, char *argv[])
+{
+ int span;
+ int x;
+ char status[256];
+ if (argc != 3)
+ return RESULT_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(fd, "PRI span %d/%d: %s\n", span + 1, x, status);
+ }
+ }
+ }
+ }
+ return RESULT_SUCCESS;
+}
+
+static int handle_pri_show_span(int fd, int argc, char *argv[])
+{
+ int span;
+ int x;
+ char status[256];
+ if (argc < 4)
+ return RESULT_SHOWUSAGE;
+ span = atoi(argv[3]);
+ if ((span < 1) || (span > NUM_SPANS)) {
+ ast_cli(fd, "Invalid span '%s'. Should be a number from %d to %d\n", argv[3], 1, NUM_SPANS);
+ return RESULT_SUCCESS;
+ }
+ if (!pris[span-1].pri) {
+ ast_cli(fd, "No PRI running on span %d\n", span);
+ return RESULT_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(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(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(fd, "%s", info_str);
+ free(info_str);
+ }
+#else
+ pri_dump_info(pris[span-1].pri);
+#endif
+ ast_cli(fd, "\n");
+ }
+ }
+ return RESULT_SUCCESS;
+}
+
+static int handle_pri_show_debug(int fd, int argc, char *argv[])
+{
+ int x;
+ int span;
+ int count=0;
+ int debug=0;
+
+ 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(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(fd, "Logging PRI debug to file %s\n", pridebugfilename);
+ ast_mutex_unlock(&pridebugfdlock);
+
+ if (!count)
+ ast_cli(fd, "No debug set or no PRI running\n");
+ return RESULT_SUCCESS;
+}
+
+static const char pri_debug_help[] =
+ "Usage: pri debug span <span>\n"
+ " Enables debugging on a given PRI span\n";
+
+static const char pri_no_debug_help[] =
+ "Usage: pri no debug span <span>\n"
+ " Disables debugging on a given PRI span\n";
+
+static const char pri_really_debug_help[] =
+ "Usage: pri intensive debug span <span>\n"
+ " Enables debugging down to the Q.921 level\n";
+
+static const char pri_show_span_help[] =
+ "Usage: pri show span <span>\n"
+ " Displays PRI Information on a given PRI span\n";
+
+static const char pri_show_spans_help[] =
+ "Usage: pri show spans\n"
+ " Displays PRI Information\n";
+
+static struct ast_cli_entry dahdi_pri_cli[] = {
+ { { "pri", "debug", "span", NULL },
+ handle_pri_debug, "Enables PRI debugging on a span",
+ pri_debug_help, complete_span_4 },
+
+ { { "pri", "no", "debug", "span", NULL },
+ handle_pri_no_debug, "Disables PRI debugging on a span",
+ pri_no_debug_help, complete_span_5 },
+
+ { { "pri", "intense", "debug", "span", NULL },
+ handle_pri_really_debug, "Enables REALLY INTENSE PRI debugging",
+ pri_really_debug_help, complete_span_5 },
+
+ { { "pri", "show", "spans", NULL },
+ handle_pri_show_spans, "Displays PRI Information",
+ pri_show_spans_help },
+
+ { { "pri", "show", "span", NULL },
+ handle_pri_show_span, "Displays PRI Information",
+ pri_show_span_help, complete_span_4 },
+
+ { { "pri", "show", "debug", NULL },
+ handle_pri_show_debug, "Displays current PRI debug settings" },
+
+ { { "pri", "set", "debug", "file", NULL },
+ handle_pri_set_debug_file, "Sends PRI debug output to the specified file" },
+
+ { { "pri", "unset", "debug", "file", NULL },
+ handle_pri_set_debug_file, "Ends PRI debug output to file" },
+
+#ifdef HAVE_PRI_VERSION
+ { { "pri", "show", "version", NULL },
+ handle_pri_version, "Displays version of libpri" },
+#endif
+};
+
+#endif /* HAVE_PRI */
+
+static int dahdi_destroy_channel(int fd, int argc, char **argv)
+{
+ int channel;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ channel = atoi(argv[3]);
+
+ return dahdi_destroy_channel_bynum(channel);
+}
+
+static void dahdi_softhangup_all(void)
+{
+ struct dahdi_pvt *p;
+retry:
+ ast_mutex_lock(&iflock);
+ for (p = iflist; p; p = p->next) {
+ ast_mutex_lock(&p->lock);
+ if (p->owner && !p->restartpending) {
+ if (ast_channel_trylock(p->owner)) {
+ if (option_debug > 2)
+ ast_verbose("Avoiding deadlock\n");
+ /* Avoid deadlock since you're not supposed to lock iflock or pvt before a channel */
+ ast_mutex_unlock(&p->lock);
+ ast_mutex_unlock(&iflock);
+ goto retry;
+ }
+ if (option_debug > 2)
+ ast_verbose("Softhanging up on %s\n", p->owner->name);
+ ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_EXPLICIT);
+ p->restartpending = 1;
+ num_restart_pending++;
+ ast_channel_unlock(p->owner);
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ ast_mutex_unlock(&iflock);
+}
+
+static int setup_dahdi(int reload);
+static int dahdi_restart(void)
+{
+#if defined(HAVE_PRI)
+ int i, j;
+#endif
+ int cancel_code;
+ struct dahdi_pvt *p;
+
+ ast_mutex_lock(&restart_lock);
+
+ if (option_verbose)
+ ast_verbose("Destroying channels and reloading DAHDI configuration.\n");
+ dahdi_softhangup_all();
+ if (option_verbose > 3)
+ ast_verbose("Initial softhangup of all DAHDI channels complete.\n");
+
+ #if defined(HAVE_PRI)
+ for (i = 0; i < NUM_SPANS; i++) {
+ if (pris[i].master && (pris[i].master != AST_PTHREADT_NULL)) {
+ cancel_code = pthread_cancel(pris[i].master);
+ pthread_kill(pris[i].master, SIGURG);
+ if (option_debug > 3)
+ ast_verbose("Waiting to join thread of span %d with pid=%p, cancel_code=%d\n", i, (void *) pris[i].master, cancel_code);
+ pthread_join(pris[i].master, NULL);
+ if (option_debug > 3)
+ ast_verbose("Joined thread of span %d\n", i);
+ }
+ }
+ #endif
+
+ ast_mutex_lock(&monlock);
+ if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) {
+ cancel_code = pthread_cancel(monitor_thread);
+ pthread_kill(monitor_thread, SIGURG);
+ if (option_debug > 3)
+ ast_verbose("Waiting to join monitor thread with pid=%p, cancel_code=%d\n", (void *) monitor_thread, cancel_code);
+ pthread_join(monitor_thread, NULL);
+ if (option_debug > 3)
+ ast_verbose("Joined monitor thread\n");
+ }
+ monitor_thread = AST_PTHREADT_NULL; /* prepare to restart thread in setup_dahdi once channels are reconfigured */
+
+ ast_mutex_lock(&ss_thread_lock);
+ while (ss_thread_count > 0) { /* let ss_threads finish and run dahdi_hangup before dahvi_pvts are destroyed */
+ int x = DAHDI_FLASH;
+ if (option_debug > 2)
+ ast_verbose("Waiting on %d ss_thread(s) to finish\n", ss_thread_count);
+
+ for (p = iflist; p; p = p->next) {
+ if (p->owner)
+ ioctl(p->subs[SUB_REAL].dfd, DAHDI_HOOK, &x); /* important to create an event for dahdi_wait_event to register so that all ss_threads terminate */
+ }
+ ast_cond_wait(&ss_thread_complete, &ss_thread_lock);
+ }
+
+ /* ensure any created channels before monitor threads were stopped are hungup */
+ dahdi_softhangup_all();
+ if (option_verbose > 3)
+ ast_verbose("Final softhangup of all DAHDI channels complete.\n");
+ destroy_all_channels();
+ if (option_debug)
+ ast_verbose("Channels destroyed. Now re-reading config. %d active channels remaining.\n", ast_active_channels());
+
+ ast_mutex_unlock(&monlock);
+
+ #ifdef HAVE_PRI
+ for (i = 0; i < NUM_SPANS; i++) {
+ for (j = 0; j < NUM_DCHANS; j++)
+ dahdi_close_pri_fd(&(pris[i]), j);
+ }
+
+ memset(pris, 0, sizeof(pris));
+ for (i = 0; i < NUM_SPANS; i++) {
+ ast_mutex_init(&pris[i].lock);
+ pris[i].offset = -1;
+ pris[i].master = AST_PTHREADT_NULL;
+ for (j = 0; j < NUM_DCHANS; j++)
+ pris[i].fds[j] = -1;
+ }
+ pri_set_error(dahdi_pri_error);
+ pri_set_message(dahdi_pri_message);
+ #endif
+
+ if (setup_dahdi(2) != 0) {
+ ast_log(LOG_WARNING, "Reload channels from dahdi config failed!\n");
+ ast_mutex_unlock(&ss_thread_lock);
+ return 1;
+ }
+ ast_mutex_unlock(&ss_thread_lock);
+ ast_mutex_unlock(&restart_lock);
+ return 0;
+}
+
+static int dahdi_restart_cmd(int fd, int argc, char **argv)
+{
+ if (argc != 2) {
+ return RESULT_SHOWUSAGE;
+ }
+
+ if (dahdi_restart() != 0)
+ return RESULT_FAILURE;
+ return RESULT_SUCCESS;
+}
+
+static int dahdi_show_channels(int fd, int argc, char **argv)
+{
+#define FORMAT "%7s %-10.10s %-15.15s %-10.10s %-20.20s\n"
+#define FORMAT2 "%7s %-10.10s %-15.15s %-10.10s %-20.20s\n"
+ struct dahdi_pvt *tmp = NULL;
+ char tmps[20] = "";
+ ast_mutex_t *lock;
+ struct dahdi_pvt *start;
+#ifdef HAVE_PRI
+ int trunkgroup;
+ struct dahdi_pri *pri = NULL;
+ int x;
+#endif
+
+ lock = &iflock;
+ start = iflist;
+
+#ifdef HAVE_PRI
+ if (argc == 4) {
+ if ((trunkgroup = atoi(argv[3])) < 1)
+ return RESULT_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(fd, "No such trunk group %d\n", trunkgroup);
+ return RESULT_FAILURE;
+ }
+ } else
+#endif
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ ast_mutex_lock(lock);
+#ifdef HAVE_PRI
+ ast_cli(fd, FORMAT2, pri ? "CRV" : "Chan", "Extension", "Context", "Language", "MOH Interpret");
+#else
+ ast_cli(fd, FORMAT2, "Chan", "Extension", "Context", "Language", "MOH Interpret");
+#endif
+
+ tmp = start;
+ while (tmp) {
+ if (tmp->channel > 0) {
+ snprintf(tmps, sizeof(tmps), "%d", tmp->channel);
+ } else
+ ast_copy_string(tmps, "pseudo", sizeof(tmps));
+ ast_cli(fd, FORMAT, tmps, tmp->exten, tmp->context, tmp->language, tmp->mohinterpret);
+ tmp = tmp->next;
+ }
+ ast_mutex_unlock(lock);
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static int dahdi_show_channel(int fd, int argc, char **argv)
+{
+ int channel;
+ struct dahdi_pvt *tmp = NULL;
+ struct dahdi_confinfo ci;
+ struct dahdi_params ps;
+ int x;
+ ast_mutex_t *lock;
+ struct dahdi_pvt *start;
+#ifdef HAVE_PRI
+ char *c;
+ int trunkgroup;
+ struct dahdi_pri *pri=NULL;
+#endif
+
+ lock = &iflock;
+ start = iflist;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+#ifdef HAVE_PRI
+ if ((c = strchr(argv[3], ':'))) {
+ if (sscanf(argv[3], "%d:%d", &trunkgroup, &channel) != 2)
+ return RESULT_SHOWUSAGE;
+ if ((trunkgroup < 1) || (channel < 1))
+ return RESULT_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(fd, "No such trunk group %d\n", trunkgroup);
+ return RESULT_FAILURE;
+ }
+ } else
+#endif
+ channel = atoi(argv[3]);
+
+ ast_mutex_lock(lock);
+ tmp = start;
+ while (tmp) {
+ if (tmp->channel == channel) {
+#ifdef HAVE_PRI
+ if (pri)
+ ast_cli(fd, "Trunk/CRV: %d/%d\n", trunkgroup, tmp->channel);
+ else
+#endif
+ ast_cli(fd, "Channel: %d\n", tmp->channel);
+ ast_cli(fd, "File Descriptor: %d\n", tmp->subs[SUB_REAL].dfd);
+ ast_cli(fd, "Span: %d\n", tmp->span);
+ ast_cli(fd, "Extension: %s\n", tmp->exten);
+ ast_cli(fd, "Dialing: %s\n", tmp->dialing ? "yes" : "no");
+ ast_cli(fd, "Context: %s\n", tmp->context);
+ ast_cli(fd, "Caller ID: %s\n", tmp->cid_num);
+ ast_cli(fd, "Calling TON: %d\n", tmp->cid_ton);
+ ast_cli(fd, "Caller ID name: %s\n", tmp->cid_name);
+ ast_cli(fd, "Destroy: %d\n", tmp->destroy);
+ ast_cli(fd, "InAlarm: %d\n", tmp->inalarm);
+ ast_cli(fd, "Signalling Type: %s\n", sig2str(tmp->sig));
+ ast_cli(fd, "Radio: %d\n", tmp->radio);
+ ast_cli(fd, "Owner: %s\n", tmp->owner ? tmp->owner->name : "<None>");
+ ast_cli(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(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(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(fd, "Confno: %d\n", tmp->confno);
+ ast_cli(fd, "Propagated Conference: %d\n", tmp->propconfno);
+ ast_cli(fd, "Real in conference: %d\n", tmp->inconference);
+ ast_cli(fd, "DSP: %s\n", tmp->dsp ? "yes" : "no");
+ ast_cli(fd, "Relax DTMF: %s\n", tmp->dtmfrelax ? "yes" : "no");
+ ast_cli(fd, "Dialing/CallwaitCAS: %d/%d\n", tmp->dialing, tmp->callwaitcas);
+ ast_cli(fd, "Default law: %s\n", tmp->law == DAHDI_LAW_MULAW ? "ulaw" : tmp->law == DAHDI_LAW_ALAW ? "alaw" : "unknown");
+ ast_cli(fd, "Fax Handled: %s\n", tmp->faxhandled ? "yes" : "no");
+ ast_cli(fd, "Pulse phone: %s\n", tmp->pulsedial ? "yes" : "no");
+ ast_cli(fd, "Echo Cancellation: %d taps%s, currently %s\n", tmp->echocancel, tmp->echocanbridged ? "" : " unless TDM bridged", tmp->echocanon ? "ON" : "OFF");
+ if (tmp->master)
+ ast_cli(fd, "Master Channel: %d\n", tmp->master->channel);
+ for (x = 0; x < MAX_SLAVES; x++) {
+ if (tmp->slaves[x])
+ ast_cli(fd, "Slave Channel: %d\n", tmp->slaves[x]->channel);
+ }
+#ifdef HAVE_PRI
+ if (tmp->pri) {
+ ast_cli(fd, "PRI Flags: ");
+ if (tmp->resetting)
+ ast_cli(fd, "Resetting ");
+ if (tmp->call)
+ ast_cli(fd, "Call ");
+ if (tmp->bearer)
+ ast_cli(fd, "Bearer ");
+ ast_cli(fd, "\n");
+ if (tmp->logicalspan)
+ ast_cli(fd, "PRI Logical Span: %d\n", tmp->logicalspan);
+ else
+ ast_cli(fd, "PRI Logical Span: Implicit\n");
+ }
+
+#endif
+ memset(&ci, 0, sizeof(ci));
+ ps.channo = tmp->channel;
+ if (tmp->subs[SUB_REAL].dfd > -1) {
+ if (!ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GETCONF, &ci)) {
+ ast_cli(fd, "Actual Confinfo: Num/%d, Mode/0x%04x\n", ci.confno, ci.confmode);
+ }
+#ifdef DAHDI_GETCONFMUTE
+ if (!ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GETCONFMUTE, &x)) {
+ ast_cli(fd, "Actual Confmute: %s\n", x ? "Yes" : "No");
+ }
+#endif
+ if (ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &ps) < 0) {
+ ast_log(LOG_WARNING, "Failed to get parameters on channel %d: %s\n", tmp->channel, strerror(errno));
+ } else {
+ ast_cli(fd, "Hookstate (FXS only): %s\n", ps.rxisoffhook ? "Offhook" : "Onhook");
+ }
+ }
+ ast_mutex_unlock(lock);
+ return RESULT_SUCCESS;
+ }
+ tmp = tmp->next;
+ }
+
+ ast_cli(fd, "Unable to find given channel %d\n", channel);
+ ast_mutex_unlock(lock);
+ return RESULT_FAILURE;
+}
+
+static char dahdi_show_cadences_usage[] =
+"Usage: dahdi show cadences\n"
+" Shows all cadences currently defined\n";
+
+static int handle_dahdi_show_cadences(int fd, int argc, char *argv[])
+{
+ int i, j;
+ 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(fd,"%s\n",output);
+ }
+ return 0;
+}
+
+/* Based on irqmiss.c */
+static int dahdi_show_status(int fd, int argc, char *argv[]) {
+ #define FORMAT "%-40.40s %-10.10s %-10d %-10d %-10d\n"
+ #define FORMAT2 "%-40.40s %-10.10s %-10.10s %-10.10s %-10.10s\n"
+
+ int span;
+ int res;
+ char alarms[50];
+
+ int ctl;
+ struct dahdi_spaninfo s;
+
+ if ((ctl = open(DAHDI_FILE_CTL, O_RDWR)) < 0) {
+ ast_log(LOG_WARNING, "Unable to open " DAHDI_FILE_CTL ": %s\n", strerror(errno));
+ ast_cli(fd, "No " DAHDI_NAME " interface found.\n");
+ return RESULT_FAILURE;
+ }
+ ast_cli(fd, FORMAT2, "Description", "Alarms", "IRQ", "bpviol", "CRC4");
+
+ for (span = 1; span < DAHDI_MAX_SPANS; ++span) {
+ s.spanno = span;
+ res = ioctl(ctl, DAHDI_SPANSTAT, &s);
+ if (res) {
+ continue;
+ }
+ alarms[0] = '\0';
+ if (s.alarms > 0) {
+ if (s.alarms & DAHDI_ALARM_BLUE)
+ strcat(alarms, "BLU/");
+ if (s.alarms & DAHDI_ALARM_YELLOW)
+ strcat(alarms, "YEL/");
+ if (s.alarms & DAHDI_ALARM_RED)
+ strcat(alarms, "RED/");
+ if (s.alarms & DAHDI_ALARM_LOOPBACK)
+ strcat(alarms, "LB/");
+ if (s.alarms & DAHDI_ALARM_RECOVER)
+ strcat(alarms, "REC/");
+ if (s.alarms & DAHDI_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(fd, FORMAT, s.desc, alarms, s.irqmisses, s.bpvcount, s.crc4count);
+ }
+ close(ctl);
+
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static char show_channels_usage[] =
+ "Usage: dahdi show channels\n"
+ " Shows a list of available channels\n";
+
+static char show_channel_usage[] =
+ "Usage: dahdi show channel <chan num>\n"
+ " Detailed information about a given channel\n";
+
+static char dahdi_show_status_usage[] =
+ "Usage: dahdi show status\n"
+ " Shows a list of DAHDI cards with status\n";
+
+static char destroy_channel_usage[] =
+ "Usage: dahdi 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";
+
+static char dahdi_restart_usage[] =
+ "Usage: dahdi restart\n"
+ " Restarts the DAHDI channels: destroys them all and then\n"
+ " re-reads them from chan_dahdi.conf.\n"
+ " Note that this will STOP any running CALL on DAHDI channels.\n"
+ "";
+
+static struct ast_cli_entry cli_zap_show_cadences_deprecated = {
+ { "zap", "show", "cadences", NULL },
+ handle_dahdi_show_cadences, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_zap_show_channels_deprecated = {
+ { "zap", "show", "channels", NULL },
+ dahdi_show_channels, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_zap_show_channel_deprecated = {
+ { "zap", "show", "channel", NULL },
+ dahdi_show_channel, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_zap_destroy_channel_deprecated = {
+ { "zap", "destroy", "channel", NULL },
+ dahdi_destroy_channel, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_zap_restart_deprecated = {
+ { "zap", "restart", NULL },
+ dahdi_restart_cmd, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_zap_show_status_deprecated = {
+ { "zap", "show", "status", NULL },
+ dahdi_show_status, NULL,
+ NULL };
+
+static struct ast_cli_entry dahdi_cli[] = {
+ { { "dahdi", "show", "cadences", NULL },
+ handle_dahdi_show_cadences, "List cadences",
+ dahdi_show_cadences_usage, NULL, &cli_zap_show_cadences_deprecated },
+
+ { { "dahdi", "show", "channels", NULL},
+ dahdi_show_channels, "Show active DAHDI channels",
+ show_channels_usage, NULL, &cli_zap_show_channels_deprecated },
+
+ { { "dahdi", "show", "channel", NULL},
+ dahdi_show_channel, "Show information on a channel",
+ show_channel_usage, NULL, &cli_zap_show_channel_deprecated },
+
+ { { "dahdi", "destroy", "channel", NULL},
+ dahdi_destroy_channel, "Destroy a channel",
+ destroy_channel_usage, NULL, &cli_zap_destroy_channel_deprecated },
+
+ { { "dahdi", "restart", NULL},
+ dahdi_restart_cmd, "Fully restart DAHDI channels",
+ dahdi_restart_usage, NULL, &cli_zap_restart_deprecated },
+
+ { { "dahdi", "show", "status", NULL},
+ dahdi_show_status, "Show all DAHDI cards status",
+ dahdi_show_status_usage, NULL, &cli_zap_show_status_deprecated },
+};
+
+#define TRANSFER 0
+#define HANGUP 1
+
+static int dahdi_fake_event(struct dahdi_pvt *p, int mode)
+{
+ if (p) {
+ switch (mode) {
+ case TRANSFER:
+ p->fake_event = DAHDI_EVENT_WINKFLASH;
+ break;
+ case HANGUP:
+ p->fake_event = DAHDI_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 dahdi_pvt *find_channel(int channel)
+{
+ struct dahdi_pvt *p = iflist;
+ while (p) {
+ if (p->channel == channel) {
+ break;
+ }
+ p = p->next;
+ }
+ return p;
+}
+
+#define local_astman_ack(s, m, msg, zap) do { if (!zap) astman_send_ack(s, m, "DAHDI" msg); else astman_send_ack(s, m, "Zap" msg); } while (0)
+#define local_astman_header(m, hdr, zap) astman_get_header(m, (!zap) ? "DAHDI" hdr : "Zap" hdr)
+
+static int __action_dnd(struct mansession *s, const struct message *m, int zap_mode, int dnd)
+{
+ struct dahdi_pvt *p = NULL;
+ const char *channel = local_astman_header(m, "Channel", zap_mode);
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ if (!(p = find_channel(atoi(channel)))) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ p->dnd = dnd;
+ local_astman_ack(s, m, "DND", zap_mode);
+
+ return 0;
+}
+
+static int zap_action_dndon(struct mansession *s, const struct message *m)
+{
+ return __action_dnd(s, m, 1, 1);
+}
+
+static int dahdi_action_dndon(struct mansession *s, const struct message *m)
+{
+ return __action_dnd(s, m, 0, 1);
+}
+
+static int zap_action_dndoff(struct mansession *s, const struct message *m)
+{
+ return __action_dnd(s, m, 1, 0);
+}
+
+static int dahdi_action_dndoff(struct mansession *s, const struct message *m)
+{
+ return __action_dnd(s, m, 0, 0);
+}
+
+static int __action_transfer(struct mansession *s, const struct message *m, int zap_mode)
+{
+ struct dahdi_pvt *p = NULL;
+ const char *channel = local_astman_header(m, "Channel", zap_mode);
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ if (!(p = find_channel(atoi(channel)))) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ dahdi_fake_event(p,TRANSFER);
+ local_astman_ack(s, m, "Transfer", zap_mode);
+
+ return 0;
+}
+
+static int zap_action_transfer(struct mansession *s, const struct message *m)
+{
+ return __action_transfer(s, m, 1);
+}
+
+static int dahdi_action_transfer(struct mansession *s, const struct message *m)
+{
+ return __action_transfer(s, m, 0);
+}
+
+static int __action_transferhangup(struct mansession *s, const struct message *m, int zap_mode)
+{
+ struct dahdi_pvt *p = NULL;
+ const char *channel = local_astman_header(m, "Channel", zap_mode);
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ if (!(p = find_channel(atoi(channel)))) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ dahdi_fake_event(p, HANGUP);
+ local_astman_ack(s, m, "Hangup", zap_mode);
+ return 0;
+}
+
+static int zap_action_transferhangup(struct mansession *s, const struct message *m)
+{
+ return __action_transferhangup(s, m, 1);
+}
+
+static int dahdi_action_transferhangup(struct mansession *s, const struct message *m)
+{
+ return __action_transferhangup(s, m, 0);
+}
+
+static int __action_dialoffhook(struct mansession *s, const struct message *m, int zap_mode)
+{
+ struct dahdi_pvt *p = NULL;
+ const char *channel = local_astman_header(m, "Channel", zap_mode);
+ 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;
+ }
+ if (!(p = find_channel(atoi(channel)))) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ if (!p->owner) {
+ astman_send_error(s, m, "Channel does not have an owner");
+ return 0;
+ }
+ for (i = 0; i < strlen(number); i++) {
+ struct ast_frame f = { AST_FRAME_DTMF, number[i] };
+
+ dahdi_queue_frame(p, &f, NULL);
+ }
+ local_astman_ack(s, m, "DialOffHook", zap_mode);
+
+ return 0;
+}
+
+static int zap_action_dialoffhook(struct mansession *s, const struct message *m)
+{
+ return __action_dialoffhook(s, m, 1);
+}
+
+static int dahdi_action_dialoffhook(struct mansession *s, const struct message *m)
+{
+ return __action_dialoffhook(s, m, 0);
+}
+
+static int __action_showchannels(struct mansession *s, const struct message *m, int zap_mode)
+{
+ struct dahdi_pvt *tmp = NULL;
+ const char *id = astman_get_header(m, "ActionID");
+ char idText[256] = "";
+
+ local_astman_ack(s, m, " channel status will follow", zap_mode);
+ 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);
+ astman_append(s,
+ "Event: %sShowChannels\r\n"
+ "Channel: %d\r\n"
+ "Signalling: %s\r\n"
+ "Context: %s\r\n"
+ "DND: %s\r\n"
+ "Alarm: %s\r\n"
+ "%s"
+ "\r\n",
+ dahdi_chan_name,
+ tmp->channel, sig2str(tmp->sig), tmp->context,
+ tmp->dnd ? "Enabled" : "Disabled",
+ alarm2str(alarm), idText);
+ }
+
+ tmp = tmp->next;
+ }
+
+ ast_mutex_unlock(&iflock);
+
+ astman_append(s,
+ "Event: %sShowChannelsComplete\r\n"
+ "%s"
+ "\r\n",
+ dahdi_chan_name,
+ idText);
+ return 0;
+}
+
+static int zap_action_showchannels(struct mansession *s, const struct message *m)
+{
+ return __action_showchannels(s, m, 1);
+}
+
+static int dahdi_action_showchannels(struct mansession *s, const struct message *m)
+{
+ return __action_showchannels(s, m, 0);
+}
+
+static int __action_restart(struct mansession *s, const struct message *m, int zap_mode)
+{
+ if (dahdi_restart() != 0) {
+ if (zap_mode) {
+ astman_send_error(s, m, "Failed to restart Zap");
+ } else {
+ astman_send_error(s, m, "Failed to restart DAHDI");
+ }
+ return 1;
+ }
+ local_astman_ack(s, m, "Restart: Success", zap_mode);
+ return 0;
+}
+
+static int zap_action_restart(struct mansession *s, const struct message *m)
+{
+ return __action_restart(s, m, 1);
+}
+
+static int dahdi_action_restart(struct mansession *s, const struct message *m)
+{
+ return __action_restart(s, m, 0);
+}
+
+#define local_astman_unregister(a) do { \
+ if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) { \
+ ast_manager_unregister("DAHDI" a); \
+ } \
+ ast_manager_unregister("Zap" a); \
+ } while (0)
+
+static int __unload_module(void)
+{
+ struct dahdi_pvt *p;
+
+#ifdef HAVE_PRI
+ int i, j;
+ for (i = 0; i < NUM_SPANS; i++) {
+ if (pris[i].master != AST_PTHREADT_NULL)
+ pthread_cancel(pris[i].master);
+ }
+ ast_cli_unregister_multiple(dahdi_pri_cli, sizeof(dahdi_pri_cli) / sizeof(struct ast_cli_entry));
+
+ if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) {
+ ast_unregister_application(dahdi_send_keypad_facility_app);
+ }
+ ast_unregister_application(zap_send_keypad_facility_app);
+#endif
+ ast_cli_unregister_multiple(dahdi_cli, sizeof(dahdi_cli) / sizeof(struct ast_cli_entry));
+ local_astman_unregister("DialOffHook");
+ local_astman_unregister("Hangup");
+ local_astman_unregister("Transfer");
+ local_astman_unregister("DNDoff");
+ local_astman_unregister("DNDon");
+ local_astman_unregister("ShowChannels");
+ local_astman_unregister("Restart");
+ ast_channel_unregister(chan_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);
+
+ destroy_all_channels();
+#ifdef 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);
+ for (j = 0; j < NUM_DCHANS; j++) {
+ dahdi_close_pri_fd(&(pris[i]), j);
+ }
+ }
+#endif
+ ast_cond_destroy(&ss_thread_complete);
+ return 0;
+}
+
+static int unload_module(void)
+{
+#ifdef HAVE_PRI
+ int y;
+ for (y = 0; y < NUM_SPANS; y++)
+ ast_mutex_destroy(&pris[y].lock);
+#endif
+ return __unload_module();
+}
+
+static int build_channels(struct dahdi_chan_conf *conf, int iscrv, const char *value, int reload, int lineno, int *found_pseudo)
+{
+ char *c, *chan;
+ int x, start, finish;
+ struct dahdi_pvt *tmp;
+#ifdef HAVE_PRI
+ struct dahdi_pri *pri;
+ int trunkgroup, y;
+#endif
+
+ if ((reload == 0) && (conf->chan.sig < 0)) {
+ 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) {
+ if (option_verbose > 2) {
+#ifdef HAVE_PRI
+ if (pri)
+ ast_verbose(VERBOSE_PREFIX_3 "%s CRV %d:%d, %s signalling\n", reload ? "Reconfigured" : "Registered", trunkgroup, x, sig2str(tmp->sig));
+ else
+#endif
+ ast_verbose(VERBOSE_PREFIX_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 'dahdichan'.
+ * \todo Move definition of MAX_CHANLIST_LEN to a proper place. */
+#define MAX_CHANLIST_LEN 80
+static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct ast_variable *v, int reload, int skipchannels)
+{
+ struct dahdi_pvt *tmp;
+ int y;
+ int found_pseudo = 0;
+ char dahdichan[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, "buffers")) {
+ int res;
+ char policy[21] = "";
+
+ res = sscanf(v->value, "%d,%20s", &confp->chan.buf_no, policy);
+ if (res != 2) {
+ ast_log(LOG_WARNING, "Parsing buffers option data failed, using defaults.\n");
+ confp->chan.buf_no = numbufs;
+ continue;
+ }
+ if (confp->chan.buf_no < 0)
+ confp->chan.buf_no = numbufs;
+ if (!strcasecmp(policy, "full")) {
+ confp->chan.buf_policy = DAHDI_POLICY_WHEN_FULL;
+ } else if (!strcasecmp(policy, "immediate")) {
+ confp->chan.buf_policy = DAHDI_POLICY_IMMEDIATE;
+ } else {
+ ast_log(LOG_WARNING, "Invalid policy name given (%s).\n", policy);
+ }
+ } else if (!strcasecmp(v->name, "zapchan") || !strcasecmp(v->name, "dahdichan")) {
+ ast_copy_string(dahdichan, v->value, sizeof(dahdichan));
+ if (v->name[0] == 'z' || v->name[0] == 'Z') {
+ ast_log(LOG_WARNING, "Option zapchan has been deprecated in favor of dahdichan (found in [%s])\n", cat);
+ }
+ } else if (!strcasecmp(v->name, "usedistinctiveringdetection")) {
+ if (ast_true(v->value))
+ confp->chan.usedistinctiveringdetection = 1;
+ } else if (!strcasecmp(v->name, "distinctiveringaftercid")) {
+ if (ast_true(v->value))
+ distinctiveringaftercid = 1;
+ } else if (!strcasecmp(v->name, "dring1context")) {
+ ast_copy_string(drings.ringContext[0].contextData, v->value, sizeof(drings.ringContext[0].contextData));
+ } else if (!strcasecmp(v->name, "dring2context")) {
+ ast_copy_string(drings.ringContext[1].contextData, v->value, sizeof(drings.ringContext[1].contextData));
+ } else if (!strcasecmp(v->name, "dring3context")) {
+ ast_copy_string(drings.ringContext[2].contextData, v->value, sizeof(drings.ringContext[2].contextData));
+ } else if (!strcasecmp(v->name, "dring1")) {
+ sscanf(v->value, "%d,%d,%d", &drings.ringnum[0].ring[0], &drings.ringnum[0].ring[1], &drings.ringnum[0].ring[2]);
+ } else if (!strcasecmp(v->name, "dring2")) {
+ sscanf(v->value, "%d,%d,%d", &drings.ringnum[1].ring[0], &drings.ringnum[1].ring[1], &drings.ringnum[1].ring[2]);
+ } else if (!strcasecmp(v->name, "dring3")) {
+ sscanf(v->value, "%d,%d,%d", &drings.ringnum[2].ring[0], &drings.ringnum[2].ring[1], &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"))
+ 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, "hasvoicemail")) {
+ if (ast_true(v->value) && ast_strlen_zero(confp->chan.mailbox)) {
+ ast_copy_string(confp->chan.mailbox, cat, 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")) {
+ if (ast_true(v->value))
+ confp->chan.callprogress |= 1;
+ else
+ confp->chan.callprogress &= ~1;
+ } else if (!strcasecmp(v->name, "faxdetect")) {
+ if (!strcasecmp(v->value, "incoming")) {
+ confp->chan.callprogress |= 4;
+ confp->chan.callprogress &= ~2;
+ } else if (!strcasecmp(v->value, "outgoing")) {
+ confp->chan.callprogress &= ~4;
+ confp->chan.callprogress |= 2;
+ } else if (!strcasecmp(v->value, "both") || ast_true(v->value))
+ confp->chan.callprogress |= 6;
+ else
+ confp->chan.callprogress &= ~6;
+ } else if (!strcasecmp(v->name, "echocancel")) {
+ if (!ast_strlen_zero(v->value)) {
+ y = atoi(v->value);
+ } else
+ y = 0;
+ if ((y == 32) || (y == 64) || (y == 128) || (y == 256) || (y == 512) || (y == 1024))
+ confp->chan.echocancel = y;
+ else {
+ confp->chan.echocancel = ast_true(v->value);
+ if (confp->chan.echocancel)
+ confp->chan.echocancel=128;
+ }
+ } 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")) {
+ confp->chan.callgroup = ast_get_group(v->value);
+ } else if (!strcasecmp(v->name, "pickupgroup")) {
+ confp->chan.pickupgroup = ast_get_group(v->value);
+ } 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, "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, "useincomingcalleridondahditransfer") || !strcasecmp(v->name, "useincomingcalleridonzaptransfer")) {
+ confp->chan.dahditrcallerid = ast_true(v->value);
+ if (strstr(v->name, "zap")) {
+ ast_log(LOG_WARNING, "Option useincomingcalleridonzaptransfer has been deprecated in favor of useincomingcalleridondahditransfer (in [%s]).\n", cat);
+ }
+ } 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 (reload != 1){
+ if (!strcasecmp(v->name, "signalling")) {
+ confp->chan.outsigmod = -1;
+ 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;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "fxs_ls")) {
+ confp->chan.sig = SIG_FXSLS;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "fxs_gs")) {
+ confp->chan.sig = SIG_FXSGS;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "fxs_ks")) {
+ confp->chan.sig = SIG_FXSKS;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "fxo_ls")) {
+ confp->chan.sig = SIG_FXOLS;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "fxo_gs")) {
+ confp->chan.sig = SIG_FXOGS;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "fxo_ks")) {
+ confp->chan.sig = SIG_FXOKS;
+ confp->chan.radio = 0;
+ } 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;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "sf_w")) {
+ confp->chan.sig = SIG_SFWINK;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "sf_featd")) {
+ confp->chan.sig = SIG_FEATD;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "sf_featdmf")) {
+ confp->chan.sig = SIG_FEATDMF;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "sf_featb")) {
+ confp->chan.sig = SIG_SF_FEATB;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "sf")) {
+ confp->chan.sig = SIG_SF;
+ confp->chan.radio = 0;
+ } 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;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "featdmf")) {
+ confp->chan.sig = SIG_FEATDMF;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "featdmf_ta")) {
+ confp->chan.sig = SIG_FEATDMF_TA;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "e911")) {
+ confp->chan.sig = SIG_E911;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "fgccama")) {
+ confp->chan.sig = SIG_FGC_CAMA;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "fgccamamf")) {
+ confp->chan.sig = SIG_FGC_CAMAMF;
+ confp->chan.radio = 0;
+ } else if (!strcasecmp(v->value, "featb")) {
+ confp->chan.sig = SIG_FEATB;
+ confp->chan.radio = 0;
+#ifdef HAVE_PRI
+ } else if (!strcasecmp(v->value, "pri_net")) {
+ confp->chan.radio = 0;
+ confp->chan.sig = SIG_PRI;
+ confp->pri.nodetype = PRI_NETWORK;
+ } else if (!strcasecmp(v->value, "pri_cpe")) {
+ confp->chan.sig = SIG_PRI;
+ confp->chan.radio = 0;
+ confp->pri.nodetype = PRI_CPE;
+ } else if (!strcasecmp(v->value, "gr303fxoks_net")) {
+ confp->chan.sig = SIG_GR303FXOKS;
+ confp->chan.radio = 0;
+ confp->pri.nodetype = PRI_NETWORK;
+ } else if (!strcasecmp(v->value, "gr303fxsks_cpe")) {
+ confp->chan.sig = SIG_GR303FXSKS;
+ confp->chan.radio = 0;
+ confp->pri.nodetype = PRI_CPE;
+#endif
+ } else {
+ ast_log(LOG_ERROR, "Unknown signalling method '%s'\n", v->value);
+ }
+ } else if (!strcasecmp(v->name, "outsignalling")) {
+ 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 {
+ 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 {
+ ast_log(LOG_WARNING, "Unknown PRI dialplan '%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")) {
+ confp->pri.overlapdial = ast_true(v->value);
+#ifdef HAVE_PRI_INBANDDISCONNECT
+ } else if (!strcasecmp(v->name, "inbanddisconnect")) {
+ confp->pri.inbanddisconnect = ast_true(v->value);
+#endif
+ } else if (!strcasecmp(v->name, "pritimer")) {
+#ifdef PRI_GETSET_TIMERS
+ char *timerc, *c;
+ int timer, timeridx;
+ c = v->value;
+ 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 */
+ } 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 dahdi_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 DAHDI 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;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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 dahdi_dialparams dps;
+
+ ctlfd = open(DAHDI_FILE_CTL, O_RDWR);
+
+ if (ctlfd == -1) {
+ ast_log(LOG_ERROR, "Unable to open " DAHDI_FILE_CTL " to set toneduration\n");
+ return -1;
+ }
+
+ toneduration = atoi(v->value);
+ if (toneduration > -1) {
+ memset(&dps, 0, sizeof(dps));
+
+ dps.dtmf_tonelen = dps.mfv1_tonelen = toneduration;
+ res = ioctl(ctlfd, DAHDI_SET_DIALPARAMS, &dps);
+ if (res < 0) {
+ ast_log(LOG_ERROR, "Invalid tone duration: %d ms: %s\n", toneduration, strerror(errno));
+ 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 (!skipchannels)
+ ast_log(LOG_WARNING, "Ignoring %s\n", v->name);
+ }
+ if (dahdichan[0]) {
+ /* The user has set 'dahdichan' */
+ /*< \todo pass proper line number instead of 0 */
+ if (build_channels(confp, 0, dahdichan, 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) {
+ /* use the default configuration for a channel, so
+ that any settings from real configured channels
+ don't "leak" into the pseudo channel config
+ */
+ struct dahdi_chan_conf conf = dahdi_chan_conf_default();
+
+ tmp = mkintf(CHAN_PSEUDO, &conf, NULL, reload);
+
+ if (tmp) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Automatically generated pseudo channel\n");
+ } else {
+ ast_log(LOG_WARNING, "Unable to register pseudo channel!\n");
+ }
+ }
+ return 0;
+}
+
+static int setup_dahdi(int reload)
+{
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ struct dahdi_chan_conf conf = dahdi_chan_conf_default();
+ int res;
+
+#ifdef HAVE_PRI
+ char *c;
+ int spanno;
+ int i, x;
+ int logicalspan;
+ int trunkgroup;
+ int dchannels[NUM_DCHANS];
+#endif
+
+#ifdef HAVE_ZAPTEL
+ int load_from_zapata_conf = 1;
+#else
+ int load_from_zapata_conf = (dahdi_chan_mode == CHAN_ZAP_MODE);
+#endif
+
+ if (load_from_zapata_conf) {
+ if (!(cfg = ast_config_load("zapata.conf"))) {
+ ast_log(LOG_ERROR, "Unable to load zapata.conf\n");
+ return 0;
+ }
+ } else {
+ if (!(cfg = ast_config_load("chan_dahdi.conf"))) {
+ ast_log(LOG_ERROR, "Unable to load chan_dahdi.conf\n");
+ return 0;
+ }
+ }
+
+ /* 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 != 1) {
+ /* 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 chan_dahdi.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 chan_dahdi.conf\n", trunkgroup, dchannels[0], v->lineno);
+ } else if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_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 chan_dahdi.conf\n", trunkgroup, v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Trunk group %d lacks a primary D-channel at line %d of chan_dahdi.conf\n", trunkgroup, v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Trunk group identifier must be a positive integer at line %d of chan_dahdi.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 if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_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 chan_dahdi.conf\n", v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Trunk group must be a postive number at line %d of chan_dahdi.conf\n", v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Missing trunk group for span map at line %d of chan_dahdi.conf\n", v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Span number must be a postive integer at line %d of chan_dahdi.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));
+
+ v = ast_variable_browse(cfg, "channels");
+ res = process_dahdi(&conf, "", v, reload, 0);
+ ast_mutex_unlock(&iflock);
+ ast_config_destroy(cfg);
+ if (res)
+ return res;
+ cfg = ast_config_load("users.conf");
+ if (cfg) {
+ char *cat;
+ const char *chans;
+ process_dahdi(&conf, "", ast_variable_browse(cfg, "general"), 1, 1);
+ for (cat = ast_category_browse(cfg, NULL); cat ; cat = ast_category_browse(cfg, cat)) {
+ if (!strcasecmp(cat, "general"))
+ continue;
+ chans = ast_variable_retrieve(cfg, cat, "dahdichan");
+ if (!ast_strlen_zero(chans)) {
+ struct dahdi_chan_conf sect_conf;
+ memcpy(&sect_conf, &conf, sizeof(sect_conf));
+
+ process_dahdi(&sect_conf, cat, ast_variable_browse(cfg, cat), reload, 0);
+ }
+ }
+ ast_config_destroy(cfg);
+ }
+#ifdef HAVE_PRI
+ if (reload != 1) {
+ 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 if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Starting D-Channel on span %d\n", x + 1);
+ }
+ }
+ }
+#endif
+ /* And start the monitor for the first time */
+ restart_monitor();
+ return 0;
+}
+
+#define local_astman_register(a, b, c, d) do { \
+ if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) { \
+ ast_manager_register("DAHDI" a, b, dahdi_ ## c, d); \
+ } \
+ ast_manager_register("Zap" a, b, zap_ ## c, d); \
+ } while (0)
+
+static int load_module(void)
+{
+ int res;
+
+#ifdef HAVE_PRI
+ int y,i;
+ 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(dahdi_pri_error);
+ pri_set_message(dahdi_pri_message);
+ if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) {
+ ast_register_application(dahdi_send_keypad_facility_app, dahdi_send_keypad_facility_exec,
+ dahdi_send_keypad_facility_synopsis, dahdi_send_keypad_facility_descrip);
+ }
+ ast_register_application(zap_send_keypad_facility_app, zap_send_keypad_facility_exec,
+ zap_send_keypad_facility_synopsis, zap_send_keypad_facility_descrip);
+#endif
+ if ((res = setup_dahdi(0))) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) {
+ chan_tech = &dahdi_tech;
+ } else {
+ chan_tech = &zap_tech;
+ }
+ if (ast_channel_register(chan_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class '%s'\n", chan_tech->type);
+ __unload_module();
+ return -1;
+ }
+#ifdef HAVE_PRI
+ ast_string_field_init(&inuse, 16);
+ ast_string_field_set(&inuse, name, "GR-303InUse");
+ ast_cli_register_multiple(dahdi_pri_cli, sizeof(dahdi_pri_cli) / sizeof(struct ast_cli_entry));
+#endif
+ ast_cli_register_multiple(dahdi_cli, sizeof(dahdi_cli) / sizeof(struct ast_cli_entry));
+
+ memset(round_robin, 0, sizeof(round_robin));
+ local_astman_register("Transfer", 0, action_transfer, "Transfer Channel");
+ local_astman_register("Hangup", 0, action_transferhangup, "Hangup Channel");
+ local_astman_register("DialOffHook", 0, action_dialoffhook, "Dial over channel while offhook");
+ local_astman_register("DNDon", 0, action_dndon, "Toggle channel Do Not Disturb status ON");
+ local_astman_register("DNDoff", 0, action_dndoff, "Toggle channel Do Not Disturb status OFF");
+ local_astman_register("ShowChannels", 0, action_showchannels, "Show status channels");
+ local_astman_register("Restart", 0, action_restart, "Fully Restart channels (terminates calls)");
+
+ ast_cond_init(&ss_thread_complete, NULL);
+
+ return res;
+}
+
+static int dahdi_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 dahdi_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 = dahdi_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));
+ free(mybuf);
+ return -1;
+ }
+ }
+ memset(buf + len, 0x7f, END_SILENCE_LEN);
+ len += END_SILENCE_LEN;
+ fd = p->subs[index].dfd;
+ while (len) {
+ if (ast_check_hangup(c)) {
+ 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "write fd not ready on channel %d\n", p->channel);
+ continue;
+ }
+ res = write(fd, buf, size);
+ if (res != size) {
+ if (res == -1) {
+ free(mybuf);
+ return -1;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Write returned %d (%s) on channel %d\n", res, strerror(errno), p->channel);
+ break;
+ }
+ len -= size;
+ buf += size;
+ }
+ free(mybuf);
+ return(0);
+}
+
+
+static int reload(void)
+{
+ int res = 0;
+
+ res = setup_dahdi(1);
+ if (res) {
+ ast_log(LOG_WARNING, "Reload of chan_dahdi.so is unsuccessful!\n");
+ return -1;
+ }
+ return 0;
+}
+
+/* This is a workaround so that menuselect displays a proper description
+ * AST_MODULE_INFO(, , "DAHDI Telephony"
+ */
+
+#ifdef HAVE_PRI
+#define tdesc "DAHDI Telephony w/PRI"
+#else
+#define tdesc "DAHDI Telephony"
+#endif
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, tdesc,
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
+
+
diff --git a/1.4.23-rc4/channels/chan_features.c b/1.4.23-rc4/channels/chan_features.c
new file mode 100644
index 000000000..043576623
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_features.c
@@ -0,0 +1,580 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <stdlib.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/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/lock.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];
+ p->subs[index].owner->fds[AST_ALERT_FD] = p->subs[index].alertpipebackup[0];
+ p->subs[index].owner->fds[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)
+ p->subs[index].owner->fds[x] = -1;
+ else
+ p->subs[index].owner->fds[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_log(LOG_DEBUG, "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);
+ 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 = malloc(sizeof(struct feature_pvt));
+ if (tmp) {
+ memset(tmp, 0, sizeof(struct feature_pvt));
+ 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)
+ free(b2);
+ b2 = ast_safe_string_alloc("%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)
+ 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 int features_show(int fd, int argc, char **argv)
+{
+ struct feature_pvt *p;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ if (AST_LIST_EMPTY(&features)) {
+ ast_cli(fd, "No feature channels in use\n");
+ return RESULT_SUCCESS;
+ }
+
+ AST_LIST_LOCK(&features);
+ AST_LIST_TRAVERSE(&features, p, list) {
+ ast_mutex_lock(&p->lock);
+ ast_cli(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 RESULT_SUCCESS;
+}
+
+static char show_features_usage[] =
+"Usage: feature show channels\n"
+" Provides summary information on feature channels.\n";
+
+static struct ast_cli_entry cli_features[] = {
+ { { "feature", "show", "channels", NULL },
+ features_show, "List status of feature channels",
+ show_features_usage },
+};
+
+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 -1;
+ }
+ ast_cli_register_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry));
+ return 0;
+}
+
+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 */
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&features, p, list) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ AST_LIST_REMOVE_CURRENT(&features, list);
+ free(p);
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ AST_LIST_UNLOCK(&features);
+
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Feature Proxy Channel");
+
diff --git a/1.4.23-rc4/channels/chan_gtalk.c b/1.4.23-rc4/channels/chan_gtalk.c
new file mode 100644
index 000000000..abe0d63b6
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_gtalk.c
@@ -0,0 +1,2067 @@
+/*
+ * 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>gnutls</use>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <stdlib.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>
+
+#ifdef HAVE_GNUTLS
+#include <gcrypt.h>
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+#endif /* HAVE_GNUTLS */
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/lock.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_action(struct gtalk *client, struct gtalk_pvt *p, const char *action);
+static void gtalk_free_pvt(struct gtalk *client, struct gtalk_pvt *p);
+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 int gtalk_do_reload(int fd, int argc, char **argv);
+static int gtalk_show_channels(int fd, int argc, char **argv);
+/*----- RTP interface functions */
+static int gtalk_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp,
+ struct ast_rtp *vrtp, 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_MAX_AUDIO << 1) - 1),
+ .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 char show_channels_usage[] =
+"Usage: gtalk show channels\n"
+" Shows current state of the Gtalk channels.\n";
+
+static char reload_usage[] =
+"Usage: gtalk reload\n"
+" Reload gtalk channel driver.\n";
+
+
+static struct ast_cli_entry gtalk_cli[] = {
+ {{ "gtalk", "reload", NULL}, gtalk_do_reload, "Reload GoogleTalk configuration", reload_usage },
+ {{ "gtalk", "show", "channels", NULL}, gtalk_show_channels, "Show GoogleTalk channels", show_channels_usage },
+ };
+
+
+
+static char externip[16];
+
+static struct gtalk_container gtalk_list;
+
+static void gtalk_member_destroy(struct gtalk *obj)
+{
+ 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")) {
+ 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)
+{
+ int res = 0;
+ 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);
+ res ++;
+ }
+ 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);
+ res ++;
+ }
+ 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);
+ res ++;
+ }
+ 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);
+ res ++;
+ }
+ 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);
+ res++;
+ }
+ if (!strcasecmp("gsm", format)) {
+ iks *payload_gsm = iks_new("payload-type");
+ if(!payload_gsm) {
+ ast_log(LOG_WARNING,"Failed to allocate iks node");
+ return -1;
+ }
+ iks_insert_attrib(payload_gsm, "id", "103");
+ iks_insert_attrib(payload_gsm, "name", "gsm");
+ iks_insert_node(dcodecs, payload_gsm);
+ res++;
+ }
+ ast_rtp_lookup_code(p->rtp, 1, codec);
+ return res;
+}
+
+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;
+ int codecs_num = 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;
+ codecs_num = add_codec_to_answer(p, pref_codec, dcodecs);
+ alreadysent |= pref_codec;
+ }
+
+ if (codecs_num) {
+ /* only propose DTMF within an audio session */
+ 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);
+
+ iks_send(client->connection->p, 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);
+ iks_send(p->parent->connection->p, 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;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Answer!\n");
+ ast_mutex_lock(&p->lock);
+ gtalk_invite(p, p->them, p->us,p->sid, 0);
+ 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, 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);
+ }
+ }
+ iks_send(client->connection->p, 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;
+ iks *codec;
+ char s1[BUFSIZ], s2[BUFSIZ], s3[BUFSIZ];
+ int peernoncodeccapability;
+
+ ast_log(LOG_DEBUG, "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;
+ }
+
+ /* codec points to the first <payload-type/> tag */
+ codec = iks_child(iks_child(iks_child(pak->x)));
+ while (codec) {
+ ast_rtp_set_m_type(tmp->rtp, atoi(iks_find_attrib(codec, "id")));
+ ast_rtp_set_rtpmap_type(tmp->rtp, atoi(iks_find_attrib(codec, "id")), "audio", iks_find_attrib(codec, "name"), 0);
+ codec = iks_next(codec);
+ }
+
+ /* Now gather all of the codecs that we are asked for */
+ ast_rtp_get_current_formats(tmp->rtp, &tmp->peercapability, &peernoncodeccapability);
+
+ /* at this point, we received an awser from the remote Gtalk client,
+ which allows us to compare capabilities */
+ tmp->jointcapability = tmp->capability & tmp->peercapability;
+ if (!tmp->jointcapability) {
+ ast_log(LOG_WARNING, "Capabilities don't match : us - %s, peer - %s, combined - %s \n", ast_getformatname_multiple(s1, BUFSIZ, tmp->capability),
+ ast_getformatname_multiple(s2, BUFSIZ, tmp->peercapability),
+ ast_getformatname_multiple(s3, BUFSIZ, tmp->jointcapability));
+ /* close session if capabilities don't match */
+ ast_queue_hangup(tmp->owner);
+
+ return -1;
+
+ }
+
+ 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_log(LOG_DEBUG, "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");
+ iks_send(c->p, iq);
+ }
+ p->laststun = 0;
+
+safeout:
+ if (ours1)
+ free(ours1);
+ if (ours2)
+ 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;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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;
+ }
+ 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;
+ }
+ /* clear codecs */
+ tmp->rtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, bindaddr.sin_addr);
+ ast_rtp_pt_clear(tmp->rtp);
+
+ /* add user configured codec capabilites */
+ if (client->capability)
+ tmp->capability = client->capability;
+ else if (global_capability)
+ tmp->capability = global_capability;
+
+ tmp->parent = client;
+ if (!tmp->rtp) {
+ ast_log(LOG_WARNING, "Out of RTP sessions?\n");
+ 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. */
+ if (i->jointcapability)
+ what = i->jointcapability;
+ else if (i->capability)
+ what = i->capability;
+ else
+ what = global_capability;
+ 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);
+ tmp->fds[0] = ast_rtp_fd(i->rtp);
+ tmp->fds[1] = ast_rtcp_fd(i->rtp);
+ }
+ if (i->vrtp) {
+ ast_rtp_setstun(i->rtp, 1);
+ tmp->fds[2] = ast_rtp_fd(i->vrtp);
+ tmp->fds[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;
+ }
+
+ 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);
+ iks_send(client->connection->p, 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;
+ 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);
+ 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;
+ char s1[BUFSIZ], s2[BUFSIZ], s3[BUFSIZ];
+ int peernoncodeccapability;
+
+ /* 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;
+ }
+
+ if (!strcasecmp(client->name, "guest")){
+ /* the guest account is not tied to any configured XMPP client,
+ let's set it now */
+ client->connection = ast_aji_get_client(from);
+ if (!client->connection) {
+ ast_log(LOG_ERROR, "No XMPP client to talk to, us (partial JID) : %s\n", from);
+ return -1;
+ }
+ }
+
+ 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) {
+ gtalk_free_pvt(client, p);
+ return -1;
+ }
+
+ 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 points to the first <payload-type/> tag */
+ 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);
+ }
+
+ /* Now gather all of the codecs that we are asked for */
+ ast_rtp_get_current_formats(p->rtp, &p->peercapability, &peernoncodeccapability);
+ p->jointcapability = p->capability & p->peercapability;
+ ast_mutex_unlock(&p->lock);
+
+ ast_setstate(chan, AST_STATE_RING);
+ if (!p->jointcapability) {
+ ast_log(LOG_WARNING, "Capabilities don't match : us - %s, peer - %s, combined - %s \n", ast_getformatname_multiple(s1, BUFSIZ, p->capability),
+ ast_getformatname_multiple(s2, BUFSIZ, p->peercapability),
+ ast_getformatname_multiple(s3, BUFSIZ, p->jointcapability));
+ /* close session if capabilities don't match */
+ gtalk_action(client, p, "reject");
+ p->alreadygone = 1;
+ gtalk_hangup(chan);
+ ast_channel_free(chan);
+ return -1;
+ }
+
+ 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;
+ }
+
+ 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 && option_debug > 3) {
+ ast_log(LOG_DEBUG, "Receiving RTP traffic from IP %s, matches with remote candidate's IP %s\n", ast_inet_ntoa(aux.sin_addr), tmp->ip);
+ ast_log(LOG_DEBUG, "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"));
+ iks_send(c->p, 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)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "* 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");
+ }
+ iks_send(client->connection->p, 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);
+ 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;
+ }
+ if (!strcasecmp(client->name, "guest")){
+ /* the guest account is not tied to any configured XMPP client,
+ let's set it now */
+ client->connection = ast_aji_get_client(sender);
+ if (!client->connection) {
+ ast_log(LOG_ERROR, "No XMPP client to talk to, us (partial JID) : %s\n", sender);
+ ASTOBJ_UNREF(client, gtalk_member_destroy);
+ 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 int gtalk_show_channels(int fd, int argc, char **argv)
+{
+#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;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ ast_mutex_lock(&gtalklock);
+ ast_cli(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(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(fd, "%d active gtalk channel%s\n", numchans, (numchans != 1) ? "s" : "");
+ return RESULT_SUCCESS;
+#undef FORMAT
+}
+
+/*! \brief CLI command "gtalk show channels" */
+static int gtalk_do_reload(int fd, int argc, char **argv)
+{
+ ast_verbose("IT DOES WORK!\n");
+ return RESULT_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")) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "About to add candidate!\n");
+ gtalk_add_candidate(client, pak);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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 = malloc(sizeof(struct gtalk_candidate));
+ memset(res, 0, sizeof(struct gtalk_candidate));
+ 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;
+
+ cfg = ast_config_load(GOOGLE_CONFIG);
+ 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 = (struct gtalk *) malloc(sizeof(struct gtalk));
+ memset(member, 0, sizeof(struct gtalk));
+ 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 = NULL;
+ 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_UNLOCK(iterator);
+ });
+ ASTOBJ_CONTAINER_LINK(&gtalk_list, member);
+ ASTOBJ_UNREF(member, gtalk_member_destroy);
+ } 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)
+{
+ char *jabber_loaded = ast_module_helper("", "res_jabber.so", 0, 0, 0, 0);
+ free(jabber_loaded);
+ if (!jabber_loaded) {
+ /* If embedded, check for a different module name */
+ jabber_loaded = ast_module_helper("", "res_jabber", 0, 0, 0, 0);
+ free(jabber_loaded);
+ if (!jabber_loaded) {
+ ast_log(LOG_ERROR, "chan_gtalk.so depends upon res_jabber.so\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ }
+
+#ifdef HAVE_GNUTLS
+ gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
+#endif /* HAVE_GNUTLS */
+
+ 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/1.4.23-rc4/channels/chan_h323.c b/1.4.23-rc4/channels/chan_h323.c
new file mode 100644
index 000000000..1199ddda9
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_h323.c
@@ -0,0 +1,3274 @@
+/*
+ * 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
+ *
+ * \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>
+#if defined(BSD) || defined(SOLARIS)
+#ifndef IPTOS_MINCOST
+#define IPTOS_MINCOST 0x02
+#endif
+#endif
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "asterisk/lock.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.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;
+
+/* global debug flag */
+int h323debug;
+
+/*! 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_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 int tos = 0;
+static char secret[50];
+static unsigned int unique = 0;
+
+static call_options_t global_options;
+
+/** 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; /* Payload code used for RFC2833 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;
+
+static struct ast_user_list {
+ ASTOBJ_CONTAINER_COMPONENTS(struct oh323_user);
+} userl;
+
+static struct ast_peer_list {
+ ASTOBJ_CONTAINER_COMPONENTS(struct oh323_peer);
+} peerl;
+
+static struct ast_alias_list {
+ ASTOBJ_CONTAINER_COMPONENTS(struct oh323_alias);
+} aliasl;
+
+/** Asterisk RTP stuff */
+static struct sched_context *sched;
+static struct io_context *io;
+
+/** Protect the interface list (oh323_pvt) */
+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);
+
+/* 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);
+
+/* Protect the reload process */
+AST_MUTEX_DEFINE_STATIC(h323_reload_lock);
+static int h323_reloading = 0;
+
+/* 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 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_MAX_AUDIO << 1) - 1),
+ .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_log(LOG_DEBUG, "Destroying alias '%s'\n", alias->name);
+ free(alias);
+}
+
+static void oh323_destroy_user(struct oh323_user *user)
+{
+ if (h323debug)
+ ast_log(LOG_DEBUG, "Destroying user '%s'\n", user->name);
+ ast_free_ha(user->ha);
+ free(user);
+}
+
+static void oh323_destroy_peer(struct oh323_peer *peer)
+{
+ if (h323debug)
+ ast_log(LOG_DEBUG, "Destroying peer '%s'\n", peer->name);
+ ast_free_ha(peer->ha);
+ 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;
+}
+
+/* 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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);
+ }
+ } else { /* Regular input or signal message */
+ if (pvt->newduration) { /* This is a signal, signalUpdate follows */
+ f.frametype = AST_FRAME_DTMF_BEGIN;
+ 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);
+ c->fds[0] = ast_rtp_fd(pvt->rtp);
+ c->fds[1] = ast_rtcp_fd(pvt->rtp);
+ ast_queue_frame(pvt->owner, &ast_null_frame); /* Tell Asterisk to apply changes */
+ }
+ pvt->update_rtp_info = -1;
+ }
+}
+
+/* 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) {
+ free(cd->call_token);
+ cd->call_token = NULL;
+ }
+ if (cd->call_source_aliases) {
+ free(cd->call_source_aliases);
+ cd->call_source_aliases = NULL;
+ }
+ if (cd->call_dest_alias) {
+ free(cd->call_dest_alias);
+ cd->call_dest_alias = NULL;
+ }
+ if (cd->call_source_name) {
+ free(cd->call_source_name);
+ cd->call_source_name = NULL;
+ }
+ if (cd->call_source_e164) {
+ free(cd->call_source_e164);
+ cd->call_source_e164 = NULL;
+ }
+ if (cd->call_dest_e164) {
+ free(cd->call_dest_e164);
+ cd->call_dest_e164 = NULL;
+ }
+ if (cd->sourceIp) {
+ free(cd->sourceIp);
+ cd->sourceIp = NULL;
+ }
+ if (cd->redirect_number) {
+ free(cd->redirect_number);
+ cd->redirect_number = NULL;
+ }
+}
+
+static void __oh323_destroy(struct oh323_pvt *pvt)
+{
+ struct oh323_pvt *cur, *prev = NULL;
+
+ AST_SCHED_DEL(sched, pvt->DTMFsched);
+
+ 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_log(LOG_DEBUG, "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);
+ free(pvt);
+ }
+}
+
+static void oh323_destroy(struct oh323_pvt *pvt)
+{
+ if (h323debug) {
+ ast_log(LOG_DEBUG, "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)) {
+ /* 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 ? strdup(pvt->cd.call_token) : NULL;
+ ast_mutex_unlock(&pvt->lock);
+ h323_send_tone(token, digit);
+ if (token) {
+ free(token);
+ }
+ } else
+ ast_mutex_unlock(&pvt->lock);
+ oh323_update_info(c);
+ return 0;
+}
+
+/**
+ * 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)) {
+ /* 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 ? strdup(pvt->cd.call_token) : NULL;
+ ast_mutex_unlock(&pvt->lock);
+ h323_send_tone(token, ' ');
+ if (token) {
+ free(token);
+ }
+ }
+ oh323_update_info(c);
+ return 0;
+}
+
+/**
+ * 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_log(LOG_DEBUG, "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;
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Requested transfer capability: 0x%.2x - %s\n", c->transfercapability, ast_transfercapability2str(c->transfercapability));
+ if (h323debug)
+ ast_log(LOG_DEBUG, "Placing outgoing call to %s, %d\n", called_addr, pvt->options.dtmfcodec);
+ 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_log(LOG_DEBUG, "Answering on %s\n", c->name);
+
+ ast_mutex_lock(&pvt->lock);
+ token = pvt->cd.call_token ? strdup(pvt->cd.call_token) : NULL;
+ ast_mutex_unlock(&pvt->lock);
+ res = h323_answering_call(token, 0);
+ if (token)
+ 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_log(LOG_DEBUG, "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 ? 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");
+ }
+ 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;
+}
+
+static struct ast_frame *oh323_rtp_read(struct oh323_pvt *pvt)
+{
+ /* Retrieve audio/etc from channel. Assumes pvt->lock is already held. */
+ 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)) {
+ 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_log(LOG_DEBUG, "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 ? 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_log(LOG_DEBUG, "OH323: Indicating %d on %s\n", condition, token);
+
+ 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:
+ ast_moh_start(c, data, NULL);
+ res = 0;
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_moh_stop(c);
+ res = 0;
+ break;
+ case AST_CONTROL_SRCUPDATE:
+ ast_rtp_new_source(pvt->rtp);
+ 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_log(LOG_DEBUG, "OH323: Indicated %d on %s, res=%d\n", condition, token, res);
+ if (token)
+ 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_log(LOG_DEBUG, "Created RTP channel\n");
+
+ ast_rtp_settos(pvt->rtp, tos);
+
+ if (h323debug)
+ ast_log(LOG_DEBUG, "Setting NAT on RTP to %d\n", pvt->options.nat);
+ ast_rtp_setnat(pvt->rtp, pvt->options.nat);
+
+ if (pvt->dtmf_pt > 0)
+ ast_rtp_set_rtpmap_type(pvt->rtp, pvt->dtmf_pt, "audio", "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);
+ pvt->owner->fds[0] = ast_rtp_fd(pvt->rtp);
+ pvt->owner->fds[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;
+}
+
+/* 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
+ ch->fds[0] = ast_rtp_fd(pvt->rtp);
+ ch->fds[1] = ast_rtcp_fd(pvt->rtp);
+#endif
+#ifdef VIDEO_SUPPORT
+ if (pvt->vrtp) {
+ ch->fds[2] = ast_rtp_fd(pvt->vrtp);
+ ch->fds[3] = ast_rtcp_fd(pvt->vrtp);
+ }
+#endif
+#ifdef T38_SUPPORT
+ if (pvt->udptl) {
+ ch->fds[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 = 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 = (struct oh323_pvt *) malloc(sizeof(struct oh323_pvt));
+ if (!pvt) {
+ ast_log(LOG_ERROR, "Couldn't allocate private structure. This is bad\n");
+ return NULL;
+ }
+ memset(pvt, 0, sizeof(struct oh323_pvt));
+ 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 = (char *)malloc(128);
+ }
+ if (!pvt->cd.call_token) {
+ ast_log(LOG_ERROR, "Not enough memory to alocate call token\n");
+ ast_rtp_destroy(pvt->rtp);
+ 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) {
+ 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) && (pvt->cd.call_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 = (struct oh323_alias *)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;
+}
+
+#define DEPRECATED(_v, _new_opt) \
+ ast_log(LOG_WARNING, "Option %s found at line %d has beed deprecated. Use %s instead.\n", (_v)->name, (_v)->lineno, (_new_opt))
+
+static int update_common_options(struct ast_variable *v, struct call_options *options)
+{
+ int tmp;
+
+ 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")) {
+ if (!strcasecmp(v->value, "inband")) {
+ options->dtmfmode = H323_DTMF_INBAND;
+ } else if (!strcasecmp(v->value, "rfc2833")) {
+ options->dtmfmode = H323_DTMF_RFC2833;
+ } else {
+ ast_log(LOG_WARNING, "Unknown dtmf mode '%s', using rfc2833\n", v->value);
+ options->dtmfmode = H323_DTMF_RFC2833;
+ }
+ } else if (!strcasecmp(v->name, "dtmfcodec")) {
+ 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 = 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, "noFastStart")) {
+ DEPRECATED(v, "fastStart");
+ options->fastStart = !ast_true(v->value);
+ } else if (!strcasecmp(v->name, "fastStart")) {
+ options->fastStart = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "noH245Tunneling")) {
+ DEPRECATED(v, "h245Tunneling");
+ options->h245Tunneling = !ast_true(v->value);
+ } else if (!strcasecmp(v->name, "h245Tunneling")) {
+ options->h245Tunneling = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "noSilenceSuppression")) {
+ DEPRECATED(v, "silenceSuppression");
+ options->silenceSuppression = !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
+ return 1;
+
+ return 0;
+}
+#undef DEPRECATED
+
+static struct oh323_user *build_user(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 = (struct oh323_user *)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));
+ /* 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")) {
+ user->ha = ast_append_ha(v->name, v->value, user->ha);
+ }
+ }
+ 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;
+ 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 = (struct oh323_peer*)calloc(1, sizeof(*peer))))
+ return NULL;
+ ASTOBJ_INIT(peer);
+ }
+ oldha = peer->ha;
+ peer->ha = NULL;
+ memcpy(&peer->options, &global_options, sizeof(peer->options));
+ 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")) {
+ peer->ha = ast_append_ha(v->name, v->value, peer->ha);
+ } else if (!strcasecmp(v->name, "mailbox")) {
+ ast_copy_string(peer->mailbox, v->value, sizeof(peer->mailbox));
+ } else if (!strcasecmp(v->name, "hasvoicemail")) {
+ if (ast_true(v->value) && ast_strlen_zero(peer->mailbox)) {
+ ast_copy_string(peer->mailbox, name, sizeof(peer->mailbox));
+ }
+ }
+ }
+ 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_MAX_AUDIO << 1) - 1);
+ 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_log(LOG_DEBUG, "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;
+}
+
+/** 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;
+}
+
+/**
+ * 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;
+ AST_SCHED_DEL(sched, pvt->DTMFsched);
+ } 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);
+ 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;
+}
+
+/**
+ * Callback function used to inform the H.323 stack of the local rtp ip/port details
+ *
+ * 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 = (struct rtp_info *)malloc(sizeof(struct rtp_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) {
+ 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);
+ 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_log(LOG_DEBUG, "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;
+};
+
+/**
+ * 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_log(LOG_DEBUG, "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);
+
+ 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "RTP connection preparation for %s is pending...\n", token);
+ }
+ }
+ ast_mutex_unlock(&pvt->lock);
+
+ if (h323debug)
+ ast_log(LOG_DEBUG, "RTP connection prepared for %s\n", token);
+
+ return;
+}
+
+/**
+ * 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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;
+}
+
+/**
+ * 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_log(LOG_DEBUG, "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_verbose(VERBOSE_PREFIX_3 "Setting up Call\n");
+ ast_verbose(VERBOSE_PREFIX_3 " \tCall token: [%s]\n", pvt->cd.call_token);
+ ast_verbose(VERBOSE_PREFIX_3 " \tCalling party name: [%s]\n", pvt->cd.call_source_name);
+ ast_verbose(VERBOSE_PREFIX_3 " \tCalling party number: [%s]\n", pvt->cd.call_source_e164);
+ ast_verbose(VERBOSE_PREFIX_3 " \tCalled party name: [%s]\n", pvt->cd.call_dest_alias);
+ ast_verbose(VERBOSE_PREFIX_3 " \tCalled party number: [%s]\n", pvt->cd.call_dest_e164);
+ if (pvt->cd.redirect_reason >= 0)
+ ast_verbose(VERBOSE_PREFIX_3 " \tRedirecting party number: [%s] (reason %d)\n", pvt->cd.redirect_number, pvt->cd.redirect_reason);
+ ast_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "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;
+}
+
+/**
+ * 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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_log(LOG_DEBUG, "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;
+}
+
+/**
+ * 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_log(LOG_DEBUG, "Cleaning connection to %s\n", call_token);
+
+ while (1) {
+ pvt = find_call_locked(call_reference, call_token);
+ if (!pvt) {
+ if (h323debug)
+ ast_log(LOG_DEBUG, "No connection for %s\n", call_token);
+ return;
+ }
+ if (!pvt->owner || !ast_channel_trylock(pvt->owner))
+ break;
+#if 1
+#ifdef DEBUG_THREADS
+ ast_log(LOG_NOTICE, "Avoiding H.323 destory deadlock on %s, locked at %ld/%d by %s (%s:%d)\n", call_token, pvt->owner->lock.thread[0], pvt->owner->lock.reentrancy, pvt->owner->lock.func[0], pvt->owner->lock.file[0], pvt->owner->lock.lineno[0]);
+#else
+ ast_log(LOG_NOTICE, "Avoiding H.323 destory deadlock on %s\n", call_token);
+#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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Hanging up connection to %s with cause %d\n", token, cause);
+ }
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ if (h323debug) {
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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)
+{
+ struct oh323_pvt *pvt;
+
+ if (h323debug)
+ ast_log(LOG_DEBUG, "Setting DTMF payload to %d on %s\n", payload, token);
+
+ pvt = find_call_locked(call_reference, token);
+ if (!pvt) {
+ return;
+ }
+ if (pvt->rtp) {
+ ast_rtp_set_rtpmap_type(pvt->rtp, payload, "audio", "telephone-event", 0);
+ }
+ pvt->dtmf_pt = payload;
+ ast_mutex_unlock(&pvt->lock);
+ if (h323debug)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Capabilities for connection %s is set\n", token);
+}
+
+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) {
+ if (option_verbose > 0) {
+ ast_verbose(VERBOSE_PREFIX_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_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 int h323_do_trace(int fd, int argc, char *argv[])
+{
+ if (argc != 4) {
+ return RESULT_SHOWUSAGE;
+ }
+ h323_debug(1, atoi(argv[3]));
+ ast_cli(fd, "H.323 trace set to level %s\n", argv[2]);
+ return RESULT_SUCCESS;
+}
+
+static int h323_no_trace(int fd, int argc, char *argv[])
+{
+ if (argc < 3 || argc > 4) {
+ return RESULT_SHOWUSAGE;
+ }
+ h323_debug(0,0);
+ ast_cli(fd, "H.323 trace disabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int h323_do_debug(int fd, int argc, char *argv[])
+{
+ if (argc < 2 || argc > 3) {
+ return RESULT_SHOWUSAGE;
+ }
+ h323debug = 1;
+ ast_cli(fd, "H.323 debug enabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int h323_no_debug(int fd, int argc, char *argv[])
+{
+ if (argc < 3 || argc > 4) {
+ return RESULT_SHOWUSAGE;
+ }
+ h323debug = 0;
+ ast_cli(fd, "H.323 debug disabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int h323_gk_cycle(int fd, int argc, char *argv[])
+{
+ if (argc != 3) {
+ return RESULT_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 RESULT_SUCCESS;
+}
+
+static int h323_ep_hangup(int fd, int argc, char *argv[])
+{
+ if (argc != 3) {
+ return RESULT_SHOWUSAGE;
+ }
+ if (h323_soft_hangup(argv[2])) {
+ ast_verbose(VERBOSE_PREFIX_3 "Hangup succeeded on %s\n", argv[2]);
+ } else {
+ ast_verbose(VERBOSE_PREFIX_3 "Hangup failed for %s\n", argv[2]);
+ }
+ return RESULT_SUCCESS;
+}
+
+static int h323_tokens_show(int fd, int argc, char *argv[])
+{
+ if (argc != 3) {
+ return RESULT_SHOWUSAGE;
+ }
+ h323_show_tokens();
+ return RESULT_SUCCESS;
+}
+
+static char trace_usage[] =
+"Usage: h.323 trace <level num>\n"
+" Enables H.323 stack tracing for debugging purposes\n";
+
+static char no_trace_usage[] =
+"Usage: h.323 trace off\n"
+" Disables H.323 stack tracing for debugging purposes\n";
+
+static char debug_usage[] =
+"Usage: h.323 debug\n"
+" Enables H.323 debug output\n";
+
+static char no_debug_usage[] =
+"Usage: h.323 debug off\n"
+" Disables H.323 debug output\n";
+
+static char show_cycle_usage[] =
+"Usage: h.323 gk cycle\n"
+" Manually re-register with the Gatekeper (Currently Disabled)\n";
+
+static char show_hangup_usage[] =
+"Usage: h.323 hangup <token>\n"
+" Manually try to hang up call identified by <token>\n";
+
+static char show_tokens_usage[] =
+"Usage: h.323 show tokens\n"
+" Print out all active call tokens\n";
+
+static char h323_reload_usage[] =
+"Usage: h323 reload\n"
+" Reloads H.323 configuration from h323.conf\n";
+
+static struct ast_cli_entry cli_h323_no_trace_deprecated = {
+ { "h.323", "no", "trace", NULL },
+ h323_no_trace, "Disable H.323 Stack Tracing",
+ no_trace_usage };
+
+static struct ast_cli_entry cli_h323_no_debug_deprecated = {
+ { "h.323", "no", "debug", NULL },
+ h323_no_debug, "Disable H.323 debug",
+ no_debug_usage };
+
+static struct ast_cli_entry cli_h323_debug_deprecated = {
+ { "h.323", "debug", NULL },
+ h323_do_debug, "Enable H.323 debug",
+ debug_usage };
+
+static struct ast_cli_entry cli_h323_trace_deprecated = {
+ { "h.323", "trace", NULL },
+ h323_do_trace, "Enable H.323 Stack Tracing",
+ trace_usage };
+
+static struct ast_cli_entry cli_h323_gk_cycle_deprecated = {
+ { "h.323", "gk", "cycle", NULL },
+ h323_gk_cycle, "Manually re-register with the Gatekeper",
+ show_cycle_usage };
+
+static struct ast_cli_entry cli_h323[] = {
+ { { "h323", "set", "trace", NULL },
+ h323_do_trace, "Enable H.323 Stack Tracing",
+ trace_usage, NULL, &cli_h323_trace_deprecated },
+
+ { { "h323", "set", "trace", "off", NULL },
+ h323_no_trace, "Disable H.323 Stack Tracing",
+ no_trace_usage, NULL, &cli_h323_no_trace_deprecated },
+
+ { { "h323", "set", "debug", NULL },
+ h323_do_debug, "Enable H.323 debug",
+ debug_usage, NULL, &cli_h323_debug_deprecated },
+
+ { { "h323", "set", "debug", "off", NULL },
+ h323_no_debug, "Disable H.323 debug",
+ no_debug_usage, NULL, &cli_h323_no_debug_deprecated },
+
+ { { "h323", "cycle", "gk", NULL },
+ h323_gk_cycle, "Manually re-register with the Gatekeper",
+ show_cycle_usage, NULL, &cli_h323_gk_cycle_deprecated },
+
+ { { "h323", "hangup", NULL },
+ h323_ep_hangup, "Manually try to hang up a call",
+ show_hangup_usage },
+
+ { { "h323", "show", "tokens", NULL },
+ h323_tokens_show, "Show all active call tokens",
+ show_tokens_usage },
+};
+
+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)
+{
+ int format;
+ 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;
+
+ cfg = ast_config_load(config);
+
+ /* 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;
+ }
+
+ 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 = 101;
+ global_options.dtmfmode = H323_DTMF_RFC2833;
+ 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;
+
+ /* Copy the default jb config over global_jbconf */
+ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
+
+ /* Load configuration from users.conf */
+ ucfg = ast_config_load("users.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")) {
+ if (sscanf(v->value, "%d", &format)) {
+ tos = format & 0xff;
+ } else if (!strcasecmp(v->value, "lowdelay")) {
+ tos = IPTOS_LOWDELAY;
+ } else if (!strcasecmp(v->value, "throughput")) {
+ tos = IPTOS_THROUGHPUT;
+ } else if (!strcasecmp(v->value, "reliability")) {
+ tos = IPTOS_RELIABILITY;
+ } else if (!strcasecmp(v->value, "mincost")) {
+ tos = IPTOS_MINCOST;
+ } else if (!strcasecmp(v->value, "none")) {
+ tos = 0;
+ } else {
+ ast_log(LOG_WARNING, "Invalid tos value at line %d, should be 'lowdelay', 'throughput', 'reliability', 'mincost', or 'none'\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_verbose(VERBOSE_PREFIX_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 */
+ }
+ }
+
+ 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(int fd, int argc, char *argv[])
+{
+ 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 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(0, 0, NULL);
+}
+
+static struct ast_cli_entry cli_h323_reload =
+ { { "h.323", "reload", NULL },
+ h323_reload, "Reload H.323 configuration",
+ h323_reload_usage
+};
+
+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, 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);
+ /* 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)) {
+ 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);
+ 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/1.4.23-rc4/channels/chan_iax2.c b/1.4.23-rc4/channels/chan_iax2.c
new file mode 100644
index 000000000..d03f47c6c
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_iax2.c
@@ -0,0 +1,11336 @@
+/*
+ * 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>dahdi</use>
+ <depend>res_features</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#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 <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <regex.h>
+
+#if defined(HAVE_ZAPTEL) || defined (HAVE_DAHDI)
+#include <sys/ioctl.h>
+#include "asterisk/dahdi_compat.h"
+#endif
+
+#include "asterisk/lock.h"
+#include "asterisk/frame.h"
+#include "asterisk/channel.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/config.h"
+#include "asterisk/options.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/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
+
+#ifndef IPTOS_MINCOST
+#define IPTOS_MINCOST 0x02
+#endif
+
+#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
+
+#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)";
+
+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 maxjitterbuffer=1000;
+static int resyncthreshold=1000;
+static int maxjitterinterps=10;
+static int trunkfreq = 20;
+static int authdebug = 1;
+static int autokill = 0;
+static int iaxcompat = 0;
+static int last_authmethod = 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 int min_reg_expire;
+static int max_reg_expire;
+
+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 {
+ IAX_STATE_STARTED = (1 << 0),
+ IAX_STATE_AUTHENTICATED = (1 << 1),
+ IAX_STATE_TBD = (1 << 2),
+ IAX_STATE_UNCHANGED = (1 << 3),
+} iax2_state;
+
+struct iax2_context {
+ char context[AST_MAX_CONTEXT];
+ struct iax2_context *next;
+};
+
+enum {
+ 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*/
+ IAX_ALLOWFWDOWNLOAD = (1 << 26), /*!< Allow the FWDOWNL command? */
+} iax2_flags;
+
+static int global_rtautoclear = 120;
+
+static int reload_config(void);
+static int iax2_reload(int fd, int argc, char *argv[]);
+
+
+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_ha *ha;
+};
+
+#define IAX2_TRUNK_PREFACE (sizeof(struct iax_frame) + sizeof(struct ast_iax2_meta_hdr) + sizeof(struct ast_iax2_meta_trunk_hdr))
+
+static 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;
+ struct iax2_trunk_peer *next;
+ int trunkerror;
+ int calls;
+} *tpeers = NULL;
+
+AST_MUTEX_DEFINE_STATIC(tpeerlock);
+
+struct iax_firmware {
+ struct iax_firmware *next;
+ 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 */
+ char random[80];
+ 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_TRUNKDATA 640 * 200 /*!< 40ms, uncompressed linear * 200 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 iaxdynamicthreadnum = 0;
+static int iaxactivethreadcount = 0;
+
+struct iax_rr {
+ int jitter;
+ int losspct;
+ int losscnt;
+ int packets;
+ int delay;
+ int dropped;
+ int ooo;
+};
+
+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;
+ /*! Timestamp of the last video frame sent */
+ unsigned int lastvsent;
+ /*! Next outgoing timestamp if everything is good */
+ unsigned int nextpred;
+ /*! True if the last voice we transmitted was not silence/CNG */
+ int notsilenttx;
+ /*! 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);
+ );
+
+ /*! permitted authentication methods */
+ int authmethods;
+ /*! permitted encryption methods */
+ int encmethods;
+ /*! Encryption AES-128 Key */
+ aes_encrypt_ctx ecx;
+ /*! Decryption AES-128 Key */
+ aes_decrypt_ctx 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 transfering to */
+ struct sockaddr_in transfer;
+ /*! What's the new call number for the transfer */
+ unsigned short transfercallno;
+ /*! Transfer decrypt AES-128 Key */
+ aes_encrypt_ctx 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;
+ struct 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;
+};
+
+static struct ast_iax2_queue {
+ AST_LIST_HEAD(, iax_frame) queue;
+ int count;
+} iaxq;
+
+/*!
+ * 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 1
+/* #define MAX_PEER_BUCKETS 17 */
+#else
+#define MAX_PEER_BUCKETS 1
+/* #define MAX_PEER_BUCKETS 563 */
+#endif
+static struct ao2_container *peers;
+
+#define MAX_USER_BUCKETS MAX_PEER_BUCKETS
+static struct ao2_container *users;
+
+static struct ast_firmware_list {
+ struct iax_firmware *wares;
+ ast_mutex_t lock;
+} waresl;
+
+/*! Extension exists */
+#define CACHE_FLAG_EXISTS (1 << 0)
+/*! Extension is nonexistent */
+#define CACHE_FLAG_NONEXISTENT (1 << 1)
+/*! Extension can exist */
+#define CACHE_FLAG_CANEXIST (1 << 2)
+/*! Waiting to hear back response */
+#define CACHE_FLAG_PENDING (1 << 3)
+/*! Timed out */
+#define CACHE_FLAG_TIMEOUT (1 << 4)
+/*! Request transmitted */
+#define CACHE_FLAG_TRANSMITTED (1 << 5)
+/*! Timeout */
+#define CACHE_FLAG_UNKNOWN (1 << 6)
+/*! Matchmore */
+#define CACHE_FLAG_MATCHMORE (1 << 7)
+
+static 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];
+ struct iax2_dpcache *next;
+ struct iax2_dpcache *peer; /*!< For linking in peers */
+} *dpcache;
+
+AST_MUTEX_DEFINE_STATIC(dpcache_lock);
+
+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);
+
+#define IAX_IOSTATE_IDLE 0
+#define IAX_IOSTATE_READY 1
+#define IAX_IOSTATE_PROCESSING 2
+#define IAX_IOSTATE_SCHEDREADY 3
+
+#define IAX_TYPE_POOL 1
+#define IAX_TYPE_DYNAMIC 2
+
+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;
+ int type;
+ int 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 __attribute__((format(printf, 1, 2))) jb_error_output(const char *fmt, ...)
+{
+ va_list args;
+ char buf[1024];
+
+ va_start(args, fmt);
+ vsnprintf(buf, 1024, fmt, args);
+ va_end(args);
+
+ ast_log(LOG_ERROR, "%s", buf);
+}
+
+static void __attribute__((format(printf, 1, 2))) jb_warning_output(const char *fmt, ...)
+{
+ va_list args;
+ char buf[1024];
+
+ va_start(args, fmt);
+ vsnprintf(buf, 1024, fmt, args);
+ va_end(args);
+
+ ast_log(LOG_WARNING, "%s", buf);
+}
+
+static void __attribute__((format(printf, 1, 2))) jb_debug_output(const char *fmt, ...)
+{
+ va_list args;
+ char buf[1024];
+
+ va_start(args, fmt);
+ vsnprintf(buf, 1024, fmt, args);
+ va_end(args);
+
+ ast_verbose("%s", buf);
+}
+
+/* XXX We probably should use a mutex when working with this XXX */
+static struct chan_iax2_pvt *iaxs[IAX_MAX_CALLS];
+static ast_mutex_t iaxsl[ARRAY_LEN(iaxs)];
+static struct timeval lastused[ARRAY_LEN(iaxs)];
+
+/*!
+ * \brief Another container of iax2_pvt structures
+ *
+ * Active IAX2 pvt structs are also stored in this container, if they are a part
+ * of an active call where we know the remote side's call number. The reason
+ * for this is that incoming media frames do not contain our call number. So,
+ * instead of having to iterate the entire iaxs array, we use this container to
+ * look up calls where the remote side is using a given call number.
+ */
+static struct ao2_container *iax_peercallno_pvts;
+
+/* 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 ARRAY_LEN(iaxs) / 2
+
+static int maxtrunkcall = TRUNK_CALL_START;
+static int maxnontrunkcall = 1;
+
+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_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 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,
+};
+
+/* 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_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)
+{
+ pthread_attr_t attr;
+ struct iax2_thread *thread = NULL;
+
+ /* Pop the head of the list off */
+ AST_LIST_LOCK(&idle_list);
+ thread = AST_LIST_REMOVE_HEAD(&idle_list, list);
+ AST_LIST_UNLOCK(&idle_list);
+
+ /* If no idle thread is available from the regular list, try dynamic */
+ if (thread == NULL) {
+ AST_LIST_LOCK(&dynamic_list);
+ thread = AST_LIST_REMOVE_HEAD(&dynamic_list, list);
+ /* Make sure we absolutely have a thread... if not, try to make one if allowed */
+ if (thread == NULL && iaxmaxthreadcount > iaxdynamicthreadcount) {
+ /* We need to MAKE a thread! */
+ if ((thread = ast_calloc(1, sizeof(*thread)))) {
+ thread->threadnum = iaxdynamicthreadnum++;
+ thread->type = IAX_TYPE_DYNAMIC;
+ ast_mutex_init(&thread->lock);
+ ast_cond_init(&thread->cond, NULL);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ if (ast_pthread_create(&thread->threadid, &attr, iax2_process_thread, thread)) {
+ free(thread);
+ thread = NULL;
+ } else {
+ /* All went well and the thread is up, so increment our count */
+ iaxdynamicthreadcount++;
+
+ /* Wait for the thread to be ready before returning it to the caller */
+ while (!thread->ready_for_signal)
+ usleep(1);
+ }
+ }
+ }
+ AST_LIST_UNLOCK(&dynamic_list);
+ }
+
+ /* 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 */
+ if (thread)
+ memset(&thread->ffinfo, 0, sizeof(thread->ffinfo));
+
+ 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 && option_debug)
+ ast_log(LOG_DEBUG, "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_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]) {
+ if (iaxs[callno]->peercallno) {
+ 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);
+ } else {
+ /* I am the schedule, so I'm allowed to do this */
+ iaxs[callno]->pingid = -1;
+ }
+ } else if (option_debug > 0) {
+ ast_log(LOG_DEBUG, "I was supposed to send a PING with callno %d, but no such call exists (and I cannot remove pingid, either).\n", callno);
+ }
+
+ 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;
+
+ ast_mutex_lock(&iaxsl[callno]);
+
+ if (iaxs[callno]) {
+ if (iaxs[callno]->peercallno) {
+ 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);
+ } else {
+ /* I am the schedule, so I'm allowed to do this */
+ iaxs[callno]->lagid = -1;
+ }
+ } else {
+ ast_log(LOG_WARNING, "I was supposed to send a LAGRQ with callno %d, but no such call exists (and I cannot remove lagid, either).\n", callno);
+ }
+
+ 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 !strcmp(peer->name, peer2->name) ? CMP_MATCH | CMP_STOP : 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 !strcmp(user->name, user2->name) ? CMP_MATCH | CMP_STOP : 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 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 = user_unref(user);
+ }
+
+ ast_clear_flag(pvt, IAX_MAXAUTHREQ);
+ }
+
+ /* No more pings or lagrq's */
+ AST_SCHED_DEL(sched, pvt->pingid);
+ AST_SCHED_DEL(sched, pvt->lagid);
+ AST_SCHED_DEL(sched, pvt->autoid);
+ AST_SCHED_DEL(sched, pvt->authid);
+ AST_SCHED_DEL(sched, pvt->initid);
+ AST_SCHED_DEL(sched, pvt->jbid);
+}
+
+static void store_by_peercallno(struct chan_iax2_pvt *pvt)
+{
+ if (!pvt->peercallno) {
+ ast_log(LOG_ERROR, "This should not be called without a peer call number.\n");
+ return;
+ }
+
+ ao2_link(iax_peercallno_pvts, pvt);
+}
+
+static void remove_by_peercallno(struct chan_iax2_pvt *pvt)
+{
+ if (!pvt->peercallno) {
+ ast_log(LOG_ERROR, "This should not be called without a peer call number.\n");
+ return;
+ }
+
+ ao2_unlink(iax_peercallno_pvts, pvt);
+}
+
+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 < ARRAY_LEN(iaxs) - 1; x++) {
+ if (iaxs[x]) {
+ max = x + 1;
+ }
+ }
+
+ maxtrunkcall = max;
+ if (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "New max trunk callno is %d\n", max);
+}
+
+static void iax2_frame_free(struct iax_frame *fr)
+{
+ AST_SCHED_DEL(sched, fr->retrans);
+ iax_frame_free(fr);
+}
+
+static void iax2_destroy(int callno)
+{
+ struct chan_iax2_pvt *pvt;
+ struct ast_channel *owner;
+
+retry:
+ pvt = iaxs[callno];
+ gettimeofday(&lastused[callno], NULL);
+
+ owner = pvt ? pvt->owner : NULL;
+
+ if (owner) {
+ if (ast_mutex_trylock(&owner->lock)) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Avoiding IAX destroy deadlock\n");
+ DEADLOCK_AVOIDANCE(&iaxsl[callno]);
+ goto retry;
+ }
+ }
+ if (!owner && iaxs[callno]) {
+ AST_SCHED_DEL_SPINLOCK(sched, iaxs[callno]->lagid, &iaxsl[callno]);
+ AST_SCHED_DEL_SPINLOCK(sched, iaxs[callno]->pingid, &iaxsl[callno]);
+ iaxs[callno] = NULL;
+ }
+
+ if (pvt) {
+ if (!owner) {
+ pvt->owner = NULL;
+ } else {
+ /* 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);
+ }
+
+ if (pvt->peercallno) {
+ remove_by_peercallno(pvt);
+ }
+
+ if (!owner) {
+ ao2_ref(pvt, -1);
+ pvt = NULL;
+ }
+ }
+
+ if (owner) {
+ ast_mutex_unlock(&owner->lock);
+ }
+
+ if (callno & 0x4000) {
+ update_max_trunk();
+ }
+}
+
+static int scheduled_destroy(const void *vid)
+{
+ short callno = PTR_TO_CALLNO(vid);
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno]) {
+ if (option_debug) {
+ ast_log(LOG_DEBUG, "Really destroying %d now...\n", callno);
+ }
+ iax2_destroy(callno);
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+ return 0;
+}
+
+static void pvt_destructor(void *obj)
+{
+ struct chan_iax2_pvt *pvt = obj;
+ struct iax_frame *cur = NULL;
+
+ iax2_destroy_helper(pvt);
+
+ /* Already gone */
+ ast_set_flag(pvt, IAX_ALREADYGONE);
+
+ AST_LIST_LOCK(&iaxq.queue);
+ AST_LIST_TRAVERSE(&iaxq.queue, cur, list) {
+ /* Cancel any pending transmissions */
+ if (cur->callno == pvt->callno) {
+ cur->retries = -1;
+ }
+ }
+ AST_LIST_UNLOCK(&iaxq.queue);
+
+ if (pvt->reg) {
+ pvt->reg->callno = 0;
+ }
+
+ if (!pvt->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);
+ ast_string_field_free_memory(pvt);
+ }
+}
+
+static struct chan_iax2_pvt *new_iax(struct sockaddr_in *sin, const char *host)
+{
+ struct chan_iax2_pvt *tmp;
+ jb_conf jbconf;
+
+ if (!(tmp = ao2_alloc(sizeof(*tmp), pvt_destructor))) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(tmp, 32)) {
+ ao2_ref(tmp, -1);
+ tmp = NULL;
+ return NULL;
+ }
+
+ tmp->prefs = prefs;
+ tmp->callno = 0;
+ tmp->peercallno = 0;
+ tmp->transfercallno = 0;
+ tmp->bridgecallno = 0;
+ 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;
+ jb_setconf(tmp->jb,&jbconf);
+
+ 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, struct chan_iax2_pvt *cur, int check_dcallno)
+{
+ 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 == 0 || cur->peercallno == callno) &&
+ (check_dcallno ? dcallno == cur->callno : 1) ) {
+ /* 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_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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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;
+ 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;
+ }
+ gettimeofday(&now, NULL);
+ for (x = TRUNK_CALL_START; x < ARRAY_LEN(iaxs) - 1; x++) {
+ ast_mutex_lock(&iaxsl[x]);
+ if (!iaxs[x] && ((now.tv_sec - lastused[x].tv_sec) > MIN_REUSE_TIME)) {
+ /* Update the two timers that should have been started */
+ /*!
+ * \note We delete these before switching the slot, because if
+ * they fire in the meantime, they will generate a warning.
+ */
+ AST_SCHED_DEL(sched, iaxs[callno]->pingid);
+ AST_SCHED_DEL(sched, iaxs[callno]->lagid);
+ iaxs[x] = iaxs[callno];
+ iaxs[x]->callno = x;
+ iaxs[callno] = NULL;
+ 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);
+ if (locked)
+ ast_mutex_unlock(&iaxsl[callno]);
+ res = x;
+ if (!locked)
+ ast_mutex_unlock(&iaxsl[x]);
+ break;
+ }
+ ast_mutex_unlock(&iaxsl[x]);
+ }
+ if (x >= ARRAY_LEN(iaxs) - 1) {
+ ast_log(LOG_WARNING, "Unable to trunk call: Insufficient space\n");
+ return -1;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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;
+}
+
+/*!
+ * \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 return_locked, int check_dcallno)
+{
+ int res = 0;
+ int x;
+ struct timeval now;
+ char host[80];
+
+ if (new <= NEW_ALLOW) {
+ if (callno) {
+ struct chan_iax2_pvt *pvt;
+ struct chan_iax2_pvt tmp_pvt = {
+ .callno = dcallno,
+ .peercallno = callno,
+ /* hack!! */
+ .frames_received = check_dcallno,
+ };
+
+ memcpy(&tmp_pvt.addr, sin, sizeof(tmp_pvt.addr));
+
+ if ((pvt = ao2_find(iax_peercallno_pvts, &tmp_pvt, OBJ_POINTER))) {
+ if (return_locked) {
+ ast_mutex_lock(&iaxsl[pvt->callno]);
+ }
+ res = pvt->callno;
+ ao2_ref(pvt, -1);
+ pvt = NULL;
+ return res;
+ }
+ }
+
+ /* This will occur on the first response to a message that we initiated,
+ * such as a PING. */
+ if (dcallno) {
+ ast_mutex_lock(&iaxsl[dcallno]);
+ }
+ if (callno && dcallno && iaxs[dcallno] && !iaxs[dcallno]->peercallno && match(sin, callno, dcallno, iaxs[dcallno], check_dcallno)) {
+ iaxs[dcallno]->peercallno = callno;
+ res = dcallno;
+ store_by_peercallno(iaxs[dcallno]);
+ if (!res || !return_locked) {
+ ast_mutex_unlock(&iaxsl[dcallno]);
+ }
+ return res;
+ }
+ if (dcallno) {
+ ast_mutex_unlock(&iaxsl[dcallno]);
+ }
+
+#ifdef IAX_OLD_FIND
+ /* If we get here, we SHOULD NOT find a call structure for this
+ callno; if we do, it means that there is a call structure that
+ has a peer callno but did NOT get entered into the hash table,
+ which is bad.
+
+ If we find a call structure using this old, slow method, output a log
+ message so we'll know about it. After a few months of leaving this in
+ place, if we don't hear about people seeing these messages, we can
+ remove this code for good.
+ */
+
+ for (x = 1; !res && x < maxnontrunkcall; x++) {
+ ast_mutex_lock(&iaxsl[x]);
+ if (iaxs[x]) {
+ /* Look for an exact match */
+ if (match(sin, callno, dcallno, iaxs[x], check_dcallno)) {
+ res = x;
+ }
+ }
+ if (!res || !return_locked)
+ ast_mutex_unlock(&iaxsl[x]);
+ }
+
+ for (x = TRUNK_CALL_START; !res && x < maxtrunkcall; x++) {
+ ast_mutex_lock(&iaxsl[x]);
+ if (iaxs[x]) {
+ /* Look for an exact match */
+ if (match(sin, callno, dcallno, iaxs[x], check_dcallno)) {
+ res = x;
+ }
+ }
+ if (!res || !return_locked)
+ ast_mutex_unlock(&iaxsl[x]);
+ }
+
+ if (res) {
+ ast_log(LOG_WARNING, "Old call search code found call number %d that was not in hash table!\n", res);
+ }
+#endif
+ }
+ if (!res && (new >= NEW_ALLOW)) {
+ int start, found = 0;
+
+ /* 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();
+ start = 2 + (ast_random() % (TRUNK_CALL_START - 1));
+ for (x = start; 1; x++) {
+ if (x == TRUNK_CALL_START) {
+ x = 1;
+ continue;
+ }
+
+ /* 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)) {
+ found = 1;
+ break;
+ }
+ ast_mutex_unlock(&iaxsl[x]);
+
+ if (x == start - 1) {
+ break;
+ }
+ }
+ /* We've still got lock held if we found a spot */
+ if (x == start - 1 && !found) {
+ ast_log(LOG_WARNING, "No more space\n");
+ return 0;
+ }
+ iaxs[x] = new_iax(sin, host);
+ update_max_nontrunk();
+ if (iaxs[x]) {
+ if (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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);
+
+ if (iaxs[x]->peercallno) {
+ store_by_peercallno(iaxs[x]);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Out of resources\n");
+ ast_mutex_unlock(&iaxsl[x]);
+ return 0;
+ }
+ if (!return_locked)
+ ast_mutex_unlock(&iaxsl[x]);
+ res = x;
+ }
+ return res;
+}
+
+static int find_callno(unsigned short callno, unsigned short dcallno, struct sockaddr_in *sin, int new, int sockfd, int full_frame) {
+
+ return __find_callno(callno, dcallno, sin, new, sockfd, 0, full_frame);
+}
+
+static int find_callno_locked(unsigned short callno, unsigned short dcallno, struct sockaddr_in *sin, int new, int sockfd, int full_frame) {
+
+ return __find_callno(callno, dcallno, sin, new, sockfd, 1, full_frame);
+}
+
+/*!
+ * \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_mutex_trylock(&iaxs[callno]->owner->lock)) {
+ /* Avoid deadlock by pausing and trying again */
+ DEADLOCK_AVOIDANCE(&iaxsl[callno]);
+ } else {
+ ast_queue_frame(iaxs[callno]->owner, f);
+ ast_mutex_unlock(&iaxs[callno]->owner->lock);
+ 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_mutex_trylock(&iaxs[callno]->owner->lock)) {
+ /* Avoid deadlock by pausing and trying again */
+ DEADLOCK_AVOIDANCE(&iaxsl[callno]);
+ } else {
+ ast_queue_hangup(iaxs[callno]->owner);
+ ast_mutex_unlock(&iaxs[callno]->owner->lock);
+ 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_mutex_trylock(&iaxs[callno]->owner->lock)) {
+ /* Avoid deadlock by pausing and trying again */
+ DEADLOCK_AVOIDANCE(&iaxsl[callno]);
+ } else {
+ ast_queue_control_data(iaxs[callno]->owner, control, data, datalen);
+ ast_mutex_unlock(&iaxs[callno]->owner->lock);
+ 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);
+ free(cur);
+}
+
+static int try_firmware(char *s)
+{
+ struct stat stbuf;
+ struct iax_firmware *cur;
+ int ifd;
+ int fd;
+ int res;
+
+ struct ast_iax2_firmware_header *fwh, fwh2;
+ struct MD5Context md5;
+ unsigned char sum[16];
+ unsigned char buf[1024];
+ int len, chunk;
+ char *s2;
+ char *last;
+ s2 = alloca(strlen(s) + 100);
+ if (!s2) {
+ 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());
+ res = stat(s, &stbuf);
+ if (res < 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, 0600);
+ 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;
+ }
+ cur = waresl.wares;
+ while(cur) {
+ 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;
+ }
+ cur = cur->next;
+ }
+ if (!cur) {
+ /* Allocate a new one and link it */
+ if ((cur = ast_calloc(1, sizeof(*cur)))) {
+ cur->fd = -1;
+ cur->next = waresl.wares;
+ waresl.wares = cur;
+ }
+ }
+ 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;
+ if (!ast_strlen_zero(dev)) {
+ ast_mutex_lock(&waresl.lock);
+ cur = waresl.wares;
+ while(cur) {
+ if (!strcmp(dev, (char *)cur->fwh->devname)) {
+ res = ntohs(cur->fwh->version);
+ break;
+ }
+ cur = cur->next;
+ }
+ ast_mutex_unlock(&waresl.lock);
+ }
+ 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) {
+ start *= bs;
+ ast_mutex_lock(&waresl.lock);
+ cur = waresl.wares;
+ while(cur) {
+ if (!strcmp((char *)dev, (char *)cur->fwh->devname)) {
+ 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;
+ }
+ cur = cur->next;
+ }
+ ast_mutex_unlock(&waresl.lock);
+ }
+ return res;
+}
+
+
+static void reload_firmware(int unload)
+{
+ struct iax_firmware *cur, *curl, *curp;
+ DIR *fwd;
+ struct dirent *de;
+ char dir[256];
+ char fn[256];
+ /* Mark all as dead */
+ ast_mutex_lock(&waresl.lock);
+ cur = waresl.wares;
+ while(cur) {
+ cur->dead = 1;
+ cur = cur->next;
+ }
+
+ /* Now that we've freed them, load the new ones */
+ if (!unload) {
+ snprintf(dir, sizeof(dir), "%s/firmware/iax", (char *)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)) {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_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 */
+ cur = waresl.wares;
+ curp = NULL;
+ while(cur) {
+ curl = cur;
+ cur = cur->next;
+ if (curl->dead) {
+ if (curp) {
+ curp->next = cur;
+ } else {
+ waresl.wares = cur;
+ }
+ destroy_firmware(curl);
+ } else {
+ curp = cur;
+ }
+ }
+ ast_mutex_unlock(&waresl.lock);
+}
+
+/*!
+ * \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 occured 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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 (option_debug > 2 && iaxdebug)
+ ast_log(LOG_DEBUG, "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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "Received error: %s\n", strerror(errno));
+ handle_error();
+ } else
+ res = 0;
+ return res;
+}
+
+/*!
+ * \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;
+ 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);
+ }
+ c = pvt->owner;
+ if (c) {
+ c->tech_pvt = NULL;
+ iax2_queue_hangup(callno);
+ pvt->owner = NULL;
+ ast_module_unref(ast_module_info->self);
+ }
+ return 0;
+}
+
+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++;
+ } 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++;
+ }
+ 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(&iaxq.queue);
+ AST_LIST_REMOVE(&iaxq.queue, f, list);
+ iaxq.count--;
+ AST_LIST_UNLOCK(&iaxq.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 int iax2_prune_realtime(int fd, int argc, char *argv[])
+{
+ struct iax2_peer *peer;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ if (!strcmp(argv[3],"all")) {
+ reload_config();
+ ast_cli(fd, "OK cache is flushed.\n");
+ } else if ((peer = find_peer(argv[3], 0))) {
+ if(ast_test_flag(peer, IAX_RTCACHEFRIENDS)) {
+ ast_set_flag(peer, IAX_RTAUTOCLEAR);
+ expire_registry(peer_ref(peer));
+ ast_cli(fd, "OK peer %s was removed from the cache.\n", argv[3]);
+ } else {
+ ast_cli(fd, "SORRY peer %s is not eligible for this operation.\n", argv[3]);
+ }
+ peer_unref(peer);
+ } else {
+ ast_cli(fd, "SORRY peer %s was not found in the cache.\n", argv[3]);
+ }
+
+ return RESULT_SUCCESS;
+}
+
+static int iax2_test_losspct(int fd, int argc, char *argv[])
+{
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ test_losspct = atoi(argv[3]);
+
+ return RESULT_SUCCESS;
+}
+
+#ifdef IAXTESTS
+static int iax2_test_late(int fd, int argc, char *argv[])
+{
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ test_late = atoi(argv[3]);
+
+ return RESULT_SUCCESS;
+}
+
+static int iax2_test_resync(int fd, int argc, char *argv[])
+{
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ test_resync = atoi(argv[3]);
+
+ return RESULT_SUCCESS;
+}
+
+static int iax2_test_jitter(int fd, int argc, char *argv[])
+{
+ if (argc < 4 || argc > 5)
+ return RESULT_SHOWUSAGE;
+
+ test_jit = atoi(argv[3]);
+ if (argc == 5)
+ test_jitpct = atoi(argv[4]);
+
+ return RESULT_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 int iax2_show_peer(int fd, int argc, char *argv[])
+{
+ char status[30];
+ char cbuf[256];
+ struct iax2_peer *peer;
+ char codec_buf[512];
+ int x = 0, codec = 0, load_realtime = 0;
+
+ if (argc < 4)
+ return RESULT_SHOWUSAGE;
+
+ load_realtime = (argc == 5 && !strcmp(argv[4], "load")) ? 1 : 0;
+
+ peer = find_peer(argv[3], load_realtime);
+ if (peer) {
+ ast_cli(fd,"\n\n");
+ ast_cli(fd, " * Name : %s\n", peer->name);
+ ast_cli(fd, " Secret : %s\n", ast_strlen_zero(peer->secret)?"<Not set>":"<Set>");
+ ast_cli(fd, " Context : %s\n", peer->context);
+ ast_cli(fd, " Mailbox : %s\n", peer->mailbox);
+ ast_cli(fd, " Dynamic : %s\n", ast_test_flag(peer, IAX_DYNAMIC) ? "Yes":"No");
+ ast_cli(fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, "<unspecified>"));
+ ast_cli(fd, " Expire : %d\n", peer->expire);
+ ast_cli(fd, " ACL : %s\n", (peer->ha?"Yes":"No"));
+ 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, " Username : %s\n", peer->username);
+ 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 : (");
+ for(x = 0; x < 32 ; x++) {
+ codec = ast_codec_pref_index(&peer->prefs,x);
+ if(!codec)
+ break;
+ ast_cli(fd, "%s", ast_getformatname(codec));
+ if(x < 31 && ast_codec_pref_index(&peer->prefs,x+1))
+ ast_cli(fd, "|");
+ }
+
+ if (!x)
+ ast_cli(fd, "none");
+ ast_cli(fd, ")\n");
+
+ ast_cli(fd, " Status : ");
+ peer_status(peer, status, sizeof(status));
+ ast_cli(fd, "%s\n",status);
+ ast_cli(fd, " Qualify : every %dms when OK, every %dms when UNREACHABLE (sample smoothing %s)\n", peer->pokefreqok, peer->pokefreqnotok, peer->smoothing ? "On" : "Off");
+ ast_cli(fd,"\n");
+ peer_unref(peer);
+ } else {
+ ast_cli(fd,"Peer %s not found.\n", argv[3]);
+ ast_cli(fd,"\n");
+ }
+
+ return RESULT_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 int iax2_show_stats(int fd, int argc, char *argv[])
+{
+ struct iax_frame *cur;
+ int cnt = 0, dead=0, final=0;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ AST_LIST_LOCK(&iaxq.queue);
+ AST_LIST_TRAVERSE(&iaxq.queue, cur, list) {
+ if (cur->retries < 0)
+ dead++;
+ if (cur->final)
+ final++;
+ cnt++;
+ }
+ AST_LIST_UNLOCK(&iaxq.queue);
+
+ ast_cli(fd, " IAX Statistics\n");
+ ast_cli(fd, "---------------------\n");
+ ast_cli(fd, "Outstanding frames: %d (%d ingress, %d egress)\n", iax_get_frames(), iax_get_iframes(), iax_get_oframes());
+ ast_cli(fd, "Packets in transmit queue: %d dead, %d final, %d total\n\n", dead, final, cnt);
+
+ return RESULT_SUCCESS;
+}
+
+static int iax2_show_cache(int fd, int argc, char *argv[])
+{
+ struct iax2_dpcache *dp;
+ char tmp[1024], *pc;
+ int s;
+ int x,y;
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ ast_mutex_lock(&dpcache_lock);
+ dp = dpcache;
+ ast_cli(fd, "%-20.20s %-12.12s %-9.9s %-8.8s %s\n", "Peer/Context", "Exten", "Exp.", "Wait.", "Flags");
+ while(dp) {
+ 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(fd, "%-20.20s %-12.12s %-9d %-8d %s\n", pc, dp->exten, s, y, tmp);
+ else
+ ast_cli(fd, "%-20.20s %-12.12s %-9.9s %-8d %s\n", pc, dp->exten, "(expired)", y, tmp);
+ dp = dp->next;
+ }
+ ast_mutex_unlock(&dpcache_lock);
+ return RESULT_SUCCESS;
+}
+
+static unsigned int calc_rxstamp(struct chan_iax2_pvt *p, unsigned int offset);
+
+static void unwrap_timestamp(struct iax_frame *fr)
+{
+ /* Video mini frames only encode the lower 15 bits of the session
+ * timestamp, but other frame types (e.g. audio) encode 16 bits. */
+ const int ts_shift = (fr->af.frametype == AST_FRAME_VIDEO) ? 15 : 16;
+ const int lower_mask = (1 << ts_shift) - 1;
+ const int upper_mask = ~lower_mask;
+ const int last_upper = iaxs[fr->callno]->last & upper_mask;
+
+ if ( (fr->ts & upper_mask) == last_upper ) {
+ const int x = fr->ts - iaxs[fr->callno]->last;
+ const int threshold = (ts_shift == 15) ? 25000 : 50000;
+
+ if (x < -threshold) {
+ /* 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 = (last_upper + (1 << ts_shift)) | (fr->ts & lower_mask);
+ if (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "schedule_delivery: pushed forward timestamp\n");
+ } else if (x > threshold) {
+ /* Sudden apparent big jump forwards in timestamp:
+ What's likely happened is this is an old miniframe belonging to the previous
+ top 15 or 16-bit timestamp that has turned up out of order.
+ Adjust the timestamp appropriately. */
+ fr->ts = (last_upper - (1 << ts_shift)) | (fr->ts & lower_mask);
+ if (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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;
+
+ AST_SCHED_DEL(sched, pvt->jbid);
+
+ if(when <= 0) {
+ /* XXX should really just empty until when > 0.. */
+ when = 1;
+ }
+
+ pvt->jbid = iax2_sched_add(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;
+
+ /* 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;
+
+ gettimeofday(&tv,NULL);
+ /* 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;
+ struct ast_channel *owner = NULL;
+ struct ast_channel *bridge = NULL;
+
+ /* 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
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 ((owner = iaxs[fr->callno]->owner))
+ bridge = ast_bridged_channel(owner);
+
+ /* 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)) && owner && bridge && (bridge->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);
+
+ AST_SCHED_DEL(sched, iaxs[fr->callno]->jbid);
+
+ /* 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(&iaxq.queue);
+ AST_LIST_INSERT_TAIL(&iaxq.queue, fr, list);
+ iaxq.count++;
+ AST_LIST_UNLOCK(&iaxq.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 ast_hostent ahp;
+ struct hostent *hp;
+ if (!(hp = ast_gethostbyname(tmp->value, &ahp)) || (memcmp(&hp->h_addr, &sin->sin_addr, sizeof(hp->h_addr)))) {
+ /* 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);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "realtime_peer: Bah, '%s' is expired (%d/%d/%d)!\n",
+ peername, (int)(nowtime - regseconds), (int)regseconds, (int)nowtime);
+ }
+ else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 ast_hostent ahp;
+ struct hostent *hp;
+ if (!(hp = ast_gethostbyname(tmp->value, &ahp)) || (memcmp(&hp->h_addr, &sin->sin_addr, sizeof(hp->h_addr)))) {
+ /* 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 ast_hostent ahp;
+ struct hostent *hp;
+ 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;
+
+ hp = ast_gethostbyname(peername, &ahp);
+ if (hp) {
+ memcpy(&sin->sin_addr, hp->h_addr, sizeof(sin->sin_addr));
+ 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;
+ } else {
+ ast_log(LOG_WARNING, "No such host: %s\n", peername);
+ return -1;
+ }
+ }
+
+ 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)
+{
+ time_t t;
+ struct tm tm;
+ unsigned int tmp;
+ time(&t);
+ if (!ast_strlen_zero(tz))
+ ast_localtime(&t, &tm, tz);
+ else
+ ast_localtime(&t, &tm, NULL);
+ 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;
+};
+
+static int send_apathetic_reply(unsigned short callno, unsigned short dcallno, struct sockaddr_in *sin, int command, int ts, unsigned char seqno)
+{
+ struct ast_iax2_full_hdr f = { .scallno = htons(0x8000 | callno), .dcallno = htons(dcallno),
+ .ts = htonl(ts), .iseqno = seqno, .oseqno = 0, .type = AST_FRAME_IAX,
+ .csub = compress_subclass(command) };
+
+ return sendto(defaultsockfd, &f, sizeof(f), 0, (struct sockaddr *)sin, sizeof(*sin));
+}
+
+/*!
+ * \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;
+
+ 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 (ast_strlen_zero(pds.peer)) {
+ ast_log(LOG_WARNING, "No peer provided in the IAX2 dial string '%s'\n", dest);
+ return -1;
+ }
+
+ 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));
+ }
+
+ /* send the command using the appropriate socket for this peer */
+ iaxs[callno]->sockfd = cai.sockfd;
+
+ /* 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);
+ struct iax_ie_data ied;
+ int alreadygone;
+ memset(&ied, 0, sizeof(ied));
+ ast_mutex_lock(&iaxsl[callno]);
+ if (callno && iaxs[callno]) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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) {
+ if (send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_HANGUP, 0, ied.buf, ied.pos, -1)) {
+ ast_log(LOG_WARNING, "No final packet could be sent for callno %d\n", callno);
+ }
+ 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 (iaxs[callno] && alreadygone) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Really destroying %s now...\n", c->name);
+ iax2_destroy(callno);
+ } else if (iaxs[callno]) {
+ ast_sched_add(sched, 10000, scheduled_destroy, CALLNO_TO_PTR(callno));
+ }
+ } else if (c->tech_pvt) {
+ /* If this call no longer exists, but the channel still
+ * references it we need to set the channel's tech_pvt to null
+ * to avoid ast_channel_free() trying to free it.
+ */
+ c->tech_pvt = NULL;
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Hungup '%s'\n", c->name);
+ return 0;
+}
+
+/*!
+ * \note expects the pvt to be locked
+ */
+static int wait_for_peercallno(struct chan_iax2_pvt *pvt)
+{
+ unsigned short callno = pvt->callno;
+
+ if (!pvt->peercallno) {
+ /* We don't know the remote side's call number, yet. :( */
+ int count = 10;
+ while (count-- && pvt && !pvt->peercallno) {
+ DEADLOCK_AVOIDANCE(&iaxsl[callno]);
+ pvt = iaxs[callno];
+ }
+ if (!pvt->peercallno) {
+ return -1;
+ }
+ }
+
+ 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:
+ {
+ unsigned short callno = PTR_TO_CALLNO(c->tech_pvt);
+ struct chan_iax2_pvt *pvt;
+
+ ast_mutex_lock(&iaxsl[callno]);
+ pvt = iaxs[callno];
+
+ if (wait_for_peercallno(pvt)) {
+ ast_mutex_unlock(&iaxsl[callno]);
+ return -1;
+ }
+
+ ast_mutex_unlock(&iaxsl[callno]);
+
+ 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);
+ 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])) {
+ DEADLOCK_AVOIDANCE(&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;
+ }
+ /* If the bridge got retried, don't queue up more packets - the transfer request will be retransmitted as necessary */
+ if (iaxs[callno0]->transferring && iaxs[callno1]->transferring) {
+ transferstarted = 1;
+ }
+ 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)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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) {
+ if (option_verbose > 2) {
+ 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_verbose(VERBOSE_PREFIX_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 */
+ gettimeofday(&tv, NULL);
+ 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);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Answering IAX2 call\n");
+ 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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "Indicating condition %d\n", condition);
+
+ ast_mutex_lock(&iaxsl[callno]);
+ pvt = iaxs[callno];
+
+ if (wait_for_peercallno(pvt)) {
+ res = -1;
+ goto done;
+ }
+
+ 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++;
+ }
+ memset(&ied, 0, sizeof(ied));
+ iax_ie_append_str(&ied, IAX_IE_CALLED_NUMBER, tmp);
+ if (context)
+ iax_ie_append_str(&ied, IAX_IE_CALLED_CONTEXT, context);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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]);
+ if (i != iaxs[callno]) {
+ if (tmp) {
+ /* unlock and relock iaxsl[callno] to preserve locking order */
+ ast_mutex_unlock(&iaxsl[callno]);
+ ast_channel_free(tmp);
+ ast_mutex_lock(&iaxsl[callno]);
+ }
+ return NULL;
+ }
+
+ if (!tmp)
+ return NULL;
+ tmp->tech = &iax2_tech;
+ /* We can support any format by default, until we get restricted */
+ tmp->nativeformats = capability;
+ tmp->readformat = tmp->rawreadformat = ast_best_codec(capability);
+ tmp->writeformat = tmp->rawwriteformat = 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;
+
+ 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 */
+ gettimeofday(&iaxs[callno]->rxcore, NULL);
+ /* 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)) {
+ gettimeofday(&p->offset, NULL);
+ /* 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 (option_debug > 2 && iaxdebug)
+ ast_log(LOG_DEBUG, "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 (option_debug && iaxdebug && abs(ms - p->nextpred) > MAX_TIMESTAMP_SKEW )
+ ast_log(LOG_DEBUG, "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 if ( f->frametype == AST_FRAME_VIDEO ) {
+ /*
+ * IAX2 draft 03 says that timestamps MUST be in order.
+ * It does not say anything about several frames having the same timestamp
+ * When transporting video, we can have a frame that spans multiple iax packets
+ * (so called slices), so it would make sense to use the same timestamp for all of
+ * them
+ * We do want to make sure that frames don't go backwards though
+ */
+ if ( (unsigned int)ms < p->lastsent )
+ ms = p->lastsent;
+ } 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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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;
+
+ /* Finds and locks trunk peer */
+ ast_mutex_lock(&tpeerlock);
+ for (tpeer = tpeers; tpeer; tpeer = tpeer->next) {
+ /* We don't lock here because tpeer->addr *never* changes */
+ 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->next = tpeers;
+ tpeer->sockfd = fd;
+ tpeers = tpeer;
+#ifdef SO_NO_CHECK
+ setsockopt(tpeer->sockfd, SOL_SOCKET, SO_NO_CHECK, &nochecksums, sizeof(nochecksums));
+#endif
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Created trunk peer for '%s:%d'\n", ast_inet_ntoa(tpeer->addr.sin_addr), ntohs(tpeer->addr.sin_port));
+ }
+ }
+ ast_mutex_unlock(&tpeerlock);
+ 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 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 < MAX_TRUNKDATA) {
+ 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;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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++;
+ ast_mutex_unlock(&tpeer->lock);
+ }
+ return 0;
+}
+
+static void build_enc_keys(const unsigned char *digest, aes_encrypt_ctx *ecx, aes_decrypt_ctx *dcx)
+{
+ aes_encrypt_key128(digest, ecx);
+ aes_decrypt_key128(digest, dcx);
+}
+
+static void memcpy_decrypt(unsigned char *dst, const unsigned char *src, int len, aes_decrypt_ctx *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) {
+ 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, aes_encrypt_ctx *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];
+ aes_encrypt(curblock, dst, ecx);
+ memcpy(curblock, dst, sizeof(curblock));
+ dst += 16;
+ src += 16;
+ len -= 16;
+ }
+#endif
+}
+
+static int decode_frame(aes_decrypt_ctx *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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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(aes_encrypt_ctx *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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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 predecting */
+ 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 ( f->frametype == AST_FRAME_VIDEO ) {
+ /*
+ * If the lower 15 bits of the timestamp roll over, or if
+ * the video format changed then send a full frame.
+ * Otherwise send a mini video frame
+ */
+ if (((fts & 0xFFFF8000L) == (pvt->lastvsent & 0xFFFF8000L)) &&
+ ((f->subclass & ~0x1) == pvt->svideoformat)
+ ) {
+ now = 1;
+ sendmini = 1;
+ } else {
+ now = 0;
+ sendmini = 0;
+ }
+ pvt->lastvsent = fts;
+ }
+ /* 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 int iax2_show_users(int fd, int argc, char *argv[])
+{
+ 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 (argc) {
+ 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;
+ case 3:
+ break;
+ default:
+ return RESULT_SHOWUSAGE;
+ }
+
+ ast_cli(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(fd, FORMAT2, user->name, auth, user->authmethods,
+ user->contexts ? user->contexts->context : context,
+ user->ha ? "Yes" : "No", pstr);
+ }
+
+ if (havepattern)
+ regfree(&regexbuf);
+
+ return RESULT_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";
+
+ 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)
+ astman_append(s, FORMAT2, "Name/Username", "Host", " ", "Mask", "Port", " ", "Status", term);
+ else
+ 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, 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);
+ 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)
+ astman_append(s,"%d iax2 peers [%d online, %d offline, %d unmonitored]%s", total_peers, online_peers, offline_peers, unmonitored_peers, term);
+ else
+ 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 int iax2_show_threads(int fd, int argc, char *argv[])
+{
+ struct iax2_thread *thread = NULL;
+ time_t t;
+ int threadcount = 0, dynamiccount = 0;
+ char type;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ ast_cli(fd, "IAX2 Thread Information\n");
+ time(&t);
+ ast_cli(fd, "Idle Threads:\n");
+ AST_LIST_LOCK(&idle_list);
+ AST_LIST_TRAVERSE(&idle_list, thread, list) {
+#ifdef DEBUG_SCHED_MULTITHREAD
+ ast_cli(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(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(fd, "Active Threads:\n");
+ AST_LIST_LOCK(&active_list);
+ AST_LIST_TRAVERSE(&active_list, thread, list) {
+ if (thread->type == IAX_TYPE_DYNAMIC)
+ type = 'D';
+ else
+ type = 'P';
+#ifdef DEBUG_SCHED_MULTITHREAD
+ ast_cli(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(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(fd, "Dynamic Threads:\n");
+ AST_LIST_LOCK(&dynamic_list);
+ AST_LIST_TRAVERSE(&dynamic_list, thread, list) {
+#ifdef DEBUG_SCHED_MULTITHREAD
+ ast_cli(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(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(fd, "%d of %d threads accounted for with %d dynamic threads\n", threadcount, iaxthreadcount, dynamiccount);
+ return RESULT_SUCCESS;
+}
+
+static int iax2_show_peers(int fd, int argc, char *argv[])
+{
+ return __iax2_show_peers(0, fd, NULL, argc, argv);
+}
+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 int iax2_show_firmware(int fd, int argc, char *argv[])
+{
+#define FORMAT2 "%-15.15s %-15.15s %-15.15s\n"
+#if !defined(__FreeBSD__)
+#define FORMAT "%-15.15s %-15d %-15d\n"
+#else /* __FreeBSD__ */
+#define FORMAT "%-15.15s %-15d %-15d\n" /* XXX 2.95 ? */
+#endif /* __FreeBSD__ */
+ struct iax_firmware *cur;
+ if ((argc != 3) && (argc != 4))
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&waresl.lock);
+
+ ast_cli(fd, FORMAT2, "Device", "Version", "Size");
+ for (cur = waresl.wares;cur;cur = cur->next) {
+ if ((argc == 3) || (!strcasecmp(argv[3], (char *)cur->fwh->devname)))
+ ast_cli(fd, FORMAT, cur->fwh->devname, ntohs(cur->fwh->version),
+ (int)ntohl(cur->fwh->datalen));
+ }
+ ast_mutex_unlock(&waresl.lock);
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+/* JDG: 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" };
+ int ret;
+ const char *id = astman_get_header(m,"ActionID");
+
+ if (!ast_strlen_zero(id))
+ astman_append(s, "ActionID: %s\r\n",id);
+ ret = __iax2_show_peers(1, -1, s, 3, a );
+ astman_append(s, "\r\n\r\n" );
+ return ret;
+} /* /JDG */
+
+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 int iax2_show_registry(int fd, int argc, char *argv[])
+{
+#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];
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_cli(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(fd, FORMAT, host,
+ (reg->dnsmgr) ? "Y" : "N",
+ reg->username, perceived, reg->refresh, regstate2str(reg->regstate));
+ }
+ AST_LIST_UNLOCK(&registrations);
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static int iax2_show_channels(int fd, int argc, char *argv[])
+{
+#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;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_cli(fd, FORMAT2, "Channel", "Peer", "Username", "ID (Lo/Rem)", "Seq (Tx/Rx)", "Lag", "Jitter", "JitBuf", "Format");
+ for (x = 0; x < ARRAY_LEN(iaxs); 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(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(fd, "%d active IAX channel%s\n", numchans, (numchans != 1) ? "s" : "");
+ return RESULT_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 < ARRAY_LEN(iaxs); 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 int iax2_show_netstats(int fd, int argc, char *argv[])
+{
+ int numchans = 0;
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_cli(fd, " -------- LOCAL --------------------- -------- REMOTE --------------------\n");
+ ast_cli(fd, "Channel RTT Jit Del Lost %% Drop OOO Kpkts Jit Del Lost %% Drop OOO Kpkts\n");
+ numchans = ast_cli_netstats(NULL, fd, 1);
+ ast_cli(fd, "%d active IAX channel%s\n", numchans, (numchans != 1) ? "s" : "");
+ return RESULT_SUCCESS;
+}
+
+static int iax2_do_debug(int fd, int argc, char *argv[])
+{
+ if (argc < 2 || argc > 3)
+ return RESULT_SHOWUSAGE;
+ iaxdebug = 1;
+ ast_cli(fd, "IAX2 Debugging Enabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int iax2_do_trunk_debug(int fd, int argc, char *argv[])
+{
+ if (argc < 3 || argc > 4)
+ return RESULT_SHOWUSAGE;
+ iaxtrunkdebug = 1;
+ ast_cli(fd, "IAX2 Trunk Debug Requested\n");
+ return RESULT_SUCCESS;
+}
+
+static int iax2_do_jb_debug(int fd, int argc, char *argv[])
+{
+ if (argc < 3 || argc > 4)
+ return RESULT_SHOWUSAGE;
+ jb_setoutput(jb_error_output, jb_warning_output, jb_debug_output);
+ ast_cli(fd, "IAX2 Jitterbuffer Debugging Enabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int iax2_no_debug(int fd, int argc, char *argv[])
+{
+ if (argc < 3 || argc > 4)
+ return RESULT_SHOWUSAGE;
+ iaxdebug = 0;
+ ast_cli(fd, "IAX2 Debugging Disabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int iax2_no_trunk_debug(int fd, int argc, char *argv[])
+{
+ if (argc < 4 || argc > 5)
+ return RESULT_SHOWUSAGE;
+ iaxtrunkdebug = 0;
+ ast_cli(fd, "IAX2 Trunk Debugging Disabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int iax2_no_jb_debug(int fd, int argc, char *argv[])
+{
+ if (argc < 4 || argc > 5)
+ return RESULT_SHOWUSAGE;
+ jb_setoutput(jb_error_output, jb_warning_output, NULL);
+ jb_debug_output("\n");
+ ast_cli(fd, "IAX2 Jitterbuffer Debugging Disabled\n");
+ return RESULT_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 {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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->dbsecret) && 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))) {
+ 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 the user has callerid, override the remote caller id. */
+ 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);
+ ast_string_field_set(iaxs[callno], ani, user->cid_num);
+ iaxs[callno]->calling_pres = AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN;
+ } else if (ast_strlen_zero(iaxs[callno]->cid_num) && ast_strlen_zero(iaxs[callno]->cid_name)) {
+ iaxs[callno]->calling_pres = AST_PRES_NUMBER_NOT_AVAILABLE;
+ } /* else user is allowed to set their own CID settings */
+ 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 (option_debug)
+ ast_log(LOG_DEBUG, "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, aes_encrypt_ctx *ecx, aes_decrypt_ctx *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)
+ 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;
+ int expiry = iaxdefaultdpcache;
+ int x;
+ int matchmore = 0;
+ struct iax2_dpcache *dp, *prev;
+
+ 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->dpstatus & IAX_DPSTATUS_IGNOREPAT) {
+ /* Don't really do anything with this */
+ }
+ if (ies->refresh)
+ expiry = ies->refresh;
+ if (ies->dpstatus & IAX_DPSTATUS_MATCHMORE)
+ matchmore = CACHE_FLAG_MATCHMORE;
+ ast_mutex_lock(&dpcache_lock);
+ prev = NULL;
+ dp = pvt->dpentries;
+ while(dp) {
+ if (!strcmp(dp->exten, exten)) {
+ /* Let them go */
+ if (prev)
+ prev->peer = dp->peer;
+ else
+ pvt->dpentries = dp->peer;
+ dp->peer = NULL;
+ 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 < ARRAY_LEN(dp->waiters); x++) {
+ if (dp->waiters[x] > -1) {
+ if (write(dp->waiters[x], "asdf", 4) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ }
+ }
+ }
+ prev = dp;
+ dp = dp->peer;
+ }
+ ast_mutex_unlock(&dpcache_lock);
+ 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;
+
+ if (pvt->peercallno) {
+ remove_by_peercallno(pvt);
+ }
+ pvt->peercallno = peercallno;
+ store_by_peercallno(pvt);
+
+ 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(&iaxq.queue);
+ AST_LIST_TRAVERSE(&iaxq.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(&iaxq.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;
+ AST_SCHED_DEL(sched, reg->expire);
+ reg->expire = iax2_sched_add(sched, (5 * reg->refresh / 6) * 1000, iax2_do_register_s, reg);
+ if (inaddrcmp(&oldus, &reg->us) || (reg->messages != oldmsgs)) {
+ if (option_verbose > 2) {
+ 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)
+ snprintf(msgstatus, sizeof(msgstatus), " with 1 new message waiting\n");
+ else
+ snprintf(msgstatus, sizeof(msgstatus), " with no messages waiting\n");
+ snprintf(ourip, sizeof(ourip), "%s:%d", ast_inet_ntoa(reg->us.sin_addr), ntohs(reg->us.sin_port));
+ ast_verbose(VERBOSE_PREFIX_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", "ChannelDriver: 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_register(char *value, int lineno)
+{
+ struct iax2_registry *reg;
+ 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;
+ }
+ if (!(reg = ast_calloc(1, sizeof(*reg))))
+ return -1;
+ if (ast_dnsmgr_lookup(hostname, &reg->addr.sin_addr, &reg->dnsmgr) < 0) {
+ 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 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, "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;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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", "Peer: 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++;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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", "Peer: 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)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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", "Peer: 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 = -1;
+ 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)) {
+ int new, old;
+ 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;
+ int sentauthmethod;
+
+ peer_name = ast_strdupa(iaxs[callno]->peer);
+
+ /* SLD: third call to find_peer in registration */
+ ast_mutex_unlock(&iaxsl[callno]);
+ if ((p = find_peer(peer_name, 1))) {
+ last_authmethod = p->authmethods;
+ }
+
+ ast_mutex_lock(&iaxsl[callno]);
+ if (!iaxs[callno])
+ goto return_unref;
+ if (!p && !delayreject) {
+ ast_log(LOG_WARNING, "No such peer '%s'\n", peer_name);
+ goto return_unref;
+ }
+
+ memset(&ied, 0, sizeof(ied));
+ /* The selection of which delayed reject is sent may leak information,
+ * if it sets a static response. For example, if a host is known to only
+ * use MD5 authentication, then an RSA response would indicate that the
+ * peer does not exist, and vice-versa.
+ * Therefore, we use whatever the last peer used (which may vary over the
+ * course of a server, which should leak minimal information). */
+ sentauthmethod = p ? p->authmethods : last_authmethod ? last_authmethod : (IAX_AUTH_MD5 | IAX_AUTH_PLAINTEXT);
+ iax_ie_append_short(&ied, IAX_IE_AUTHMETHODS, sentauthmethod);
+ if (sentauthmethod & (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) {
+ AST_SCHED_DEL(sched, iaxs[callno]->authid);
+ iaxs[callno]->authid = iax2_sched_add(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 */
+ AST_SCHED_DEL(sched, iaxs[callno]->autoid);
+ iaxs[callno]->autoid = iax2_sched_add(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(&iaxq.queue);
+ AST_LIST_TRAVERSE(&iaxq.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(&iaxq.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
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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;
+ struct iax2_trunk_peer *tpeer, *prev = NULL, *drop=NULL;
+ int processed = 0;
+ int totalcalls = 0;
+#ifdef DAHDI_TIMERACK
+ int x = 1;
+#endif
+ struct timeval now;
+ if (iaxtrunkdebug)
+ ast_verbose("Beginning trunk processing. Trunk queue ceiling is %d bytes per host\n", MAX_TRUNKDATA);
+ gettimeofday(&now, NULL);
+ if (events & AST_IO_PRI) {
+#ifdef DAHDI_TIMERACK
+ /* Great, this is a timing interface, just call the ioctl */
+ if (ioctl(fd, DAHDI_TIMERACK, &x)) {
+ ast_log(LOG_WARNING, "Unable to acknowledge timer. IAX trunking will fail!\n");
+ usleep(1);
+ return -1;
+ }
+#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_mutex_lock(&tpeerlock);
+ tpeer = tpeers;
+ while(tpeer) {
+ 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 */
+ if (prev)
+ prev->next = tpeer->next;
+ else
+ tpeers = tpeer->next;
+ drop = tpeer;
+ } else {
+ res = send_trunk(tpeer, &now);
+ 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);
+ prev = tpeer;
+ tpeer = tpeer->next;
+ }
+ ast_mutex_unlock(&tpeerlock);
+ 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 */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Dropping unused iax2 trunk peer '%s:%d'\n", ast_inet_ntoa(drop->addr.sin_addr), ntohs(drop->addr.sin_port));
+ if (drop->trunkdata) {
+ free(drop->trunkdata);
+ drop->trunkdata = NULL;
+ }
+ ast_mutex_unlock(&drop->lock);
+ ast_mutex_destroy(&drop->lock);
+ 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)
+ free(dpr->callerid);
+ 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;
+ pthread_attr_t attr;
+
+ if (!(dpr = ast_calloc(1, sizeof(*dpr))))
+ return;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ 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(&newthread, &attr, dp_lookup_thread, dpr)) {
+ ast_log(LOG_WARNING, "Unable to start lookup thread!\n");
+ }
+
+ pthread_attr_destroy(&attr);
+}
+
+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;
+ 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)))) {
+ pthread_attr_t attr;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ d->chan1 = chan1m;
+ d->chan2 = chan2m;
+ if (!ast_pthread_create_background(&th, &attr, iax_park_thread, d)) {
+ pthread_attr_destroy(&attr);
+ return 0;
+ }
+ pthread_attr_destroy(&attr);
+ 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;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 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(&to_here->full_frames, 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 && option_debug)
+ ast_log(LOG_DEBUG, "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(struct iax2_thread *thread)
+{
+ struct sockaddr_in sin;
+ int res;
+ int updatehistory=1;
+ int new = NEW_PREVENT;
+ void *ptr;
+ 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 ast_iax2_meta_trunk_hdr *mth;
+ struct ast_iax2_meta_trunk_entry *mte;
+ struct ast_iax2_meta_trunk_mini *mtm;
+ struct iax_frame *fr;
+ struct iax_frame *cur;
+ struct ast_frame f = { 0, };
+ struct ast_channel *c;
+ struct iax2_dpcache *dp;
+ struct iax2_peer *peer;
+ struct iax2_trunk_peer *tpeer;
+ struct timeval rxtrunktime;
+ struct iax_ies ies;
+ struct iax_ie_data ied0, ied1;
+ int format;
+ int fd;
+ int exists;
+ int minivid = 0;
+ unsigned int ts;
+ 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);
+ memset(fr, 0, sizeof(*fr));
+ 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 %zd min)\n", res, 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, 0);
+ minivid = 1;
+ } else if ((meta->zeros == 0) && !(ntohs(meta->metacmd) & 0x8000)) {
+ unsigned char metatype;
+
+ if (res < 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;
+ }
+
+ /* This is a meta header */
+ switch(meta->metacmd) {
+ case IAX_META_TRUNK:
+ if (res < (sizeof(*meta) + sizeof(*mth))) {
+ ast_log(LOG_WARNING, "midget meta trunk packet received (%d of %zd min)\n", res,
+ sizeof(*meta) + sizeof(*mth));
+ return 1;
+ }
+ mth = (struct ast_iax2_meta_trunk_hdr *)(meta->data);
+ ts = ntohl(mth->ts);
+ metatype = meta->cmddata;
+ res -= (sizeof(*meta) + sizeof(*mth));
+ ptr = mth->data;
+ tpeer = find_tpeer(&sin, fd);
+ 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(res >= 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);
+ res -= 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);
+ res -= 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 > res)
+ break;
+ fr->callno = find_callno_locked(callno & ~IAX_FLAG_FULL, 0, &sin, NEW_PREVENT, fd, 0);
+ if (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]) {
+ if (iaxs[fr->callno]->voiceformat > 0) {
+ f.subclass = iaxs[fr->callno]->voiceformat;
+ f.datalen = len;
+ if (f.datalen >= 0) {
+ if (f.datalen)
+ f.data = ptr;
+ 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)) {
+ /* Common things */
+ f.src = "IAX2";
+ if (f.datalen && (f.frametype == AST_FRAME_VOICE))
+ f.samples = ast_codec_get_samples(&f);
+ iax_frame_wrap(fr, &f);
+ duped_fr = iaxfrdup2(fr);
+ if (duped_fr) {
+ schedule_delivery(duped_fr, updatehistory, 1, &fr->ts);
+ }
+ /* It is possible for the pvt structure to go away after we call schedule_delivery */
+ if (iaxs[fr->callno] && iaxs[fr->callno]->last < fr->ts) {
+ iaxs[fr->callno]->last = fr->ts;
+#if 1
+ if (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "For call=%d, set last=%d\n", fr->callno, fr->ts);
+#endif
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Datalen < 0?\n");
+ }
+ } else {
+ ast_log(LOG_WARNING, "Received trunked frame before first full voice frame\n");
+ iax2_vnak(fr->callno);
+ }
+ }
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ }
+ ptr += len;
+ res -= len;
+ }
+
+ }
+ return 1;
+ }
+
+#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);
+ }
+
+ /* Deal with POKE/PONG without allocating a callno */
+ if (f.frametype == AST_FRAME_IAX && f.subclass == IAX_COMMAND_POKE) {
+ /* Reply back with a PONG, but don't care about the result. */
+ send_apathetic_reply(1, ntohs(fh->scallno), &sin, IAX_COMMAND_PONG, ntohs(fh->ts), fh->iseqno + 1);
+ return 1;
+ } else if (f.frametype == AST_FRAME_IAX && f.subclass == IAX_COMMAND_ACK && dcallno == 1) {
+ /* Ignore */
+ return 1;
+ }
+
+ 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) {
+ int check_dcallno = 0;
+
+ /*
+ * We enforce accurate destination call numbers for all full frames except
+ * LAGRQ and PING commands. This is because older versions of Asterisk
+ * schedule these commands to get sent very quickly, and they will sometimes
+ * be sent before they receive the first frame from the other side. When
+ * that happens, it doesn't contain the destination call number. However,
+ * not checking it for these frames is safe.
+ *
+ * Discussed in the following thread:
+ * http://lists.digium.com/pipermail/asterisk-dev/2008-May/033217.html
+ */
+
+ if (ntohs(mh->callno) & IAX_FLAG_FULL) {
+ check_dcallno = f.frametype == AST_FRAME_IAX ? (f.subclass != IAX_COMMAND_PING && f.subclass != IAX_COMMAND_LAGRQ) : 1;
+ }
+
+ fr->callno = find_callno(ntohs(mh->callno) & ~IAX_FLAG_FULL, dcallno, &sin, new, fd, check_dcallno);
+ }
+
+ 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 */
+ unsigned short new_peercallno;
+
+ new_peercallno = (unsigned short) (ntohs(mh->callno) & ~IAX_FLAG_FULL);
+ if (new_peercallno && new_peercallno != iaxs[fr->callno]->peercallno) {
+ if (iaxs[fr->callno]->peercallno) {
+ remove_by_peercallno(iaxs[fr->callno]);
+ }
+ iaxs[fr->callno]->peercallno = new_peercallno;
+ store_by_peercallno(iaxs[fr->callno]);
+ }
+ }
+ if (ntohs(mh->callno) & IAX_FLAG_FULL) {
+ if (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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. */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 %zd min)\n", res, 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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "Cancelling transmission of packet %d\n", x);
+ call_to_destroy = 0;
+ AST_LIST_LOCK(&iaxq.queue);
+ AST_LIST_TRAVERSE(&iaxq.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(&iaxq.queue);
+ if (call_to_destroy) {
+ if (iaxdebug && option_debug)
+ ast_log(LOG_DEBUG, "Really destroying %d, having been acked on final message\n", call_to_destroy);
+ ast_mutex_lock(&iaxsl[call_to_destroy]);
+ iax2_destroy(call_to_destroy);
+ ast_mutex_unlock(&iaxsl[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 if (option_debug)
+ ast_log(LOG_DEBUG, "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;
+ }
+ }
+ }
+
+ if (f.frametype == AST_FRAME_VOICE) {
+ if (f.subclass != iaxs[fr->callno]->voiceformat) {
+ iaxs[fr->callno]->voiceformat = f.subclass;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Ooh, voice format changed to %d\n", f.subclass);
+ if (iaxs[fr->callno]->owner) {
+ int orignative;
+retryowner:
+ if (ast_mutex_trylock(&iaxs[fr->callno]->owner->lock)) {
+ DEADLOCK_AVOIDANCE(&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_mutex_unlock(&iaxs[fr->callno]->owner->lock);
+ }
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Neat, somebody took away the channel at a magical time but i found it!\n");
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+ }
+ }
+ }
+ if (f.frametype == AST_FRAME_VIDEO) {
+ if (f.subclass != iaxs[fr->callno]->videoformat) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Ooh, video format changed to %d\n", f.subclass & ~0x1);
+ iaxs[fr->callno]->videoformat = f.subclass & ~0x1;
+ }
+ }
+ if (f.frametype == AST_FRAME_CONTROL && iaxs[fr->callno]->owner) {
+ if (f.subclass == AST_CONTROL_BUSY) {
+ iaxs[fr->callno]->owner->hangupcause = AST_CAUSE_BUSY;
+ } else if (f.subclass == AST_CONTROL_CONGESTION) {
+ iaxs[fr->callno]->owner->hangupcause = AST_CAUSE_CONGESTION;
+ }
+ }
+ if (f.frametype == AST_FRAME_IAX) {
+ AST_SCHED_DEL(sched, iaxs[fr->callno]->initid);
+ /* Handle the IAX pseudo frame itself */
+ if (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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",
+ "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, "Unhold",
+ "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(&iaxq.queue);
+ AST_LIST_TRAVERSE(&iaxq.queue, cur, list) {
+ /* Cancel any outstanding txcnt's */
+ if ((fr->callno == cur->callno) && (cur->transfer))
+ cur->retries = -1;
+ }
+ AST_LIST_UNLOCK(&iaxq.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;
+ 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);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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... */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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>");
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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:
+ {
+ struct ast_channel *bridged_chan;
+
+ if (iaxs[fr->callno]->owner && (bridged_chan = ast_bridged_channel(iaxs[fr->callno]->owner)) && ies.called_number) {
+ /* Set BLINDTRANSFER channel variables */
+
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ pbx_builtin_setvar_helper(iaxs[fr->callno]->owner, "BLINDTRANSFER", bridged_chan->name);
+ ast_mutex_lock(&iaxsl[fr->callno]);
+ if (!iaxs[fr->callno]) {
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ return 1;
+ }
+
+ pbx_builtin_setvar_helper(bridged_chan, "BLINDTRANSFER", iaxs[fr->callno]->owner->name);
+ if (!strcmp(ies.called_number, ast_parking_ext())) {
+ struct ast_channel *saved_channel = iaxs[fr->callno]->owner;
+ ast_mutex_unlock(&iaxsl[fr->callno]);
+ if (iax_park(bridged_chan, saved_channel)) {
+ ast_log(LOG_WARNING, "Failed to park call on '%s'\n", bridged_chan->name);
+ } else {
+ ast_log(LOG_DEBUG, "Parked call on '%s'\n", bridged_chan->name);
+ }
+ ast_mutex_lock(&iaxsl[fr->callno]);
+ } else {
+ if (ast_async_goto(bridged_chan, iaxs[fr->callno]->context, ies.called_number, 1))
+ ast_log(LOG_WARNING, "Async goto of '%s' to '%s@%s' failed\n", bridged_chan->name,
+ ies.called_number, iaxs[fr->callno]->context);
+ else
+ ast_log(LOG_DEBUG, "Async goto of '%s' to '%s@%s' started\n", bridged_chan->name,
+ ies.called_number, iaxs[fr->callno]->context);
+ }
+ } else
+ ast_log(LOG_DEBUG, "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;
+ }
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Format for call is %s\n", ast_getformatname(iaxs[fr->callno]->owner->nativeformats));
+retryowner2:
+ if (ast_mutex_trylock(&iaxs[fr->callno]->owner->lock)) {
+ DEADLOCK_AVOIDANCE(&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_mutex_unlock(&iaxs[fr->callno]->owner->lock);
+ }
+ }
+ }
+ if (iaxs[fr->callno]) {
+ ast_mutex_lock(&dpcache_lock);
+ dp = iaxs[fr->callno]->dpentries;
+ while(dp) {
+ if (!(dp->flags & CACHE_FLAG_TRANSMITTED)) {
+ iax2_dprequest(dp, fr->callno);
+ }
+ dp = dp->peer;
+ }
+ ast_mutex_unlock(&dpcache_lock);
+ }
+ 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", "Peer: 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", "Peer: 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;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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)) {
+ struct ast_frame hangup_fr = { .frametype = AST_FRAME_CONTROL,
+ .subclass = AST_CONTROL_HANGUP,
+ };
+ 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));
+ iax2_queue_frame(fr->callno, &hangup_fr);
+ }
+ 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)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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 {
+ ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD);
+ /* If this is a TBD call, we're ready but now what... */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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);
+ }
+ }
+ break;
+ case IAX_COMMAND_INVAL:
+ iaxs[fr->callno]->error = ENOTCONN;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Immediately destroying %d, having received INVAL\n", fr->callno);
+ iax2_destroy(fr->callno);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Destroying call %d\n", fr->callno);
+ break;
+ case IAX_COMMAND_VNAK:
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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", "ChannelDriver: 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;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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 {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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(&iaxq.queue);
+ AST_LIST_TRAVERSE(&iaxq.queue, cur, list) {
+ /* Cancel any outstanding frames and start anew */
+ if ((fr->callno == cur->callno) && (cur->transfer)) {
+ cur->retries = -1;
+ }
+ }
+ AST_LIST_UNLOCK(&iaxq.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 */
+ if (!ast_test_flag(&globalflags, IAX_ALLOWFWDOWNLOAD)) {
+ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_UNSUPPORT, 0, NULL, 0, -1);
+ break;
+ }
+ 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:
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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);
+ }
+ /* 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 {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 (option_debug && iaxdebug && iaxs[fr->callno])
+ ast_log(LOG_DEBUG, "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;
+ }
+ fr->cacheable = ((f.frametype == AST_FRAME_VOICE) || (f.frametype == AST_FRAME_VIDEO));
+ 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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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);
+ 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_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)))
+ iaxdynamicthreadcount--;
+ 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
+ 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 (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "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)) {
+ int callno = reg->callno;
+ ast_mutex_lock(&iaxsl[callno]);
+ iax2_destroy(callno);
+ ast_mutex_unlock(&iaxsl[callno]);
+ reg->callno = 0;
+ }
+ if (!reg->addr.sin_addr.s_addr) {
+ if (option_debug && iaxdebug)
+ ast_log(LOG_DEBUG, "Unable to send registration request for '%s' without IP address\n", reg->username);
+ /* Setup the next registration attempt */
+ AST_SCHED_DEL(sched, reg->expire);
+ reg->expire = iax2_sched_add(sched, (5 * reg->refresh / 6) * 1000, iax2_do_register_s, reg);
+ return -1;
+ }
+
+ if (!reg->callno) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Allocate call number\n");
+ reg->callno = find_callno_locked(0, 0, &reg->addr, NEW_FORCE, defaultsockfd, 0);
+ if (reg->callno < 1) {
+ ast_log(LOG_WARNING, "Unable to create call for registration\n");
+ return -1;
+ } else if (option_debug)
+ ast_log(LOG_DEBUG, "Registration created on call %d\n", reg->callno);
+ iaxs[reg->callno]->reg = reg;
+ ast_mutex_unlock(&iaxsl[reg->callno]);
+ }
+ /* Schedule the next registration attempt */
+ AST_SCHED_DEL(sched, reg->expire);
+ /* Setup the next registration a little early */
+ reg->expire = iax2_sched_add(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 char *iax2_prov_complete_template_3rd(const char *line, const char *word, int pos, int state)
+{
+ if (pos != 3)
+ return NULL;
+ return iax_prov_complete_template(line, word, pos, state);
+}
+
+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));
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Provisioning '%s' from template '%s'\n", dest, template);
+
+ if (iax_provision_build(&provdata, &sig, template, force)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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_locked(0, 0, &sin, NEW_FORCE, cai.sockfd, 0);
+ if (!callno)
+ return -1;
+
+ if (iaxs[callno]) {
+ /* Schedule autodestruct in case they don't ever give us anything back */
+ AST_SCHED_DEL(sched, iaxs[callno]->autoid);
+ iaxs[callno]->autoid = iax2_sched_add(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);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Provisioned IAXY at '%s' with '%s'= %d\n",
+ ast_inet_ntoa(iaxs[callno]->addr.sin_addr),
+ sdata, res);
+ return res;
+}
+
+
+static int iax2_prov_cmd(int fd, int argc, char *argv[])
+{
+ int force = 0;
+ int res;
+ if (argc < 4)
+ return RESULT_SHOWUSAGE;
+ if ((argc > 4)) {
+ if (!strcasecmp(argv[4], "forced"))
+ force = 1;
+ else
+ return RESULT_SHOWUSAGE;
+ }
+ res = iax2_provision(NULL, -1, argv[2], argv[3], force);
+ if (res < 0)
+ ast_cli(fd, "Unable to find peer/address '%s'\n", argv[2]);
+ else if (res < 1)
+ ast_cli(fd, "No template (including wildcard) matching '%s'\n", argv[3]);
+ else
+ ast_cli(fd, "Provisioning '%s' with template '%s'%s\n", argv[2], argv[3], force ? ", forced" : "");
+ return RESULT_SUCCESS;
+}
+
+static void __iax2_poke_noanswer(const void *data)
+{
+ struct iax2_peer *peer = (struct iax2_peer *)data;
+ int callno;
+
+ 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", "Peer: 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 ((callno = peer->callno) > 0) {
+ ast_mutex_lock(&iaxsl[callno]);
+ iax2_destroy(callno);
+ ast_mutex_unlock(&iaxsl[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)
+{
+ int callno;
+ 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;
+ }
+
+ /* The peer could change the callno inside iax2_destroy, since we do deadlock avoidance */
+ if ((callno = peer->callno) > 0) {
+ ast_log(LOG_NOTICE, "Still have a callno...\n");
+ ast_mutex_lock(&iaxsl[callno]);
+ iax2_destroy(callno);
+ ast_mutex_unlock(&iaxsl[callno]);
+ }
+ if (heldcall)
+ ast_mutex_unlock(&iaxsl[heldcall]);
+ callno = peer->callno = find_callno(0, 0, &peer->addr, NEW_FORCE, peer->sockfd, 0);
+ 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;
+
+ /* Remove any pending pokeexpire task */
+ 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 */
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno]) {
+ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_POKE, 0, NULL, 0, -1);
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+
+ return 0;
+}
+
+static void free_context(struct iax2_context *con)
+{
+ struct iax2_context *conl;
+ while(con) {
+ conl = con;
+ con = con->next;
+ 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);
+
+ if (ast_strlen_zero(pds.peer)) {
+ ast_log(LOG_WARNING, "No peer provided in the IAX2 dial string '%s'\n", (char *) data);
+ return NULL;
+ }
+
+ memset(&cai, 0, sizeof(cai));
+ cai.capability = iax2_capability;
+
+ ast_copy_flags(&cai, &globalflags, IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF);
+
+ /* 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_locked(0, 0, &sin, NEW_FORCE, cai.sockfd, 0);
+ if (callno < 1) {
+ ast_log(LOG_WARNING, "Unable to create call\n");
+ *cause = AST_CAUSE_CONGESTION;
+ return NULL;
+ }
+
+ /* 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 (;;) {
+ pthread_testcancel();
+ ast_mutex_lock(&sched_lock);
+ 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;
+ ast_cond_timedwait(&sched_cond, &sched_lock, &ts);
+ ast_mutex_unlock(&sched_lock);
+ pthread_testcancel();
+
+ count = ast_sched_runq(sched);
+ if (option_debug && count >= 20)
+ ast_log(LOG_DEBUG, "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(&iaxq.queue);
+ count = 0;
+ wakeup = -1;
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&iaxq.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++;
+
+ 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(&iaxq.queue, list);
+ iaxq.count--;
+ /* 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(&iaxq.queue);
+
+ pthread_testcancel();
+
+ if (option_debug && count >= 20)
+ ast_log(LOG_DEBUG, "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 (option_debug && res >= 20)
+ ast_log(LOG_DEBUG, "chan_iax2: ast_io_wait ran %d I/Os all at once\n", res);
+ }
+ }
+ return NULL;
+}
+
+static int start_network_thread(void)
+{
+ pthread_attr_t attr;
+ int threadcount = 0;
+ int x;
+ for (x = 0; x < iaxthreadcount; x++) {
+ struct iax2_thread *thread = ast_calloc(1, sizeof(struct iax2_thread));
+ if (thread) {
+ thread->type = IAX_TYPE_POOL;
+ thread->threadnum = ++threadcount;
+ ast_mutex_init(&thread->lock);
+ ast_cond_init(&thread->cond, NULL);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ if (ast_pthread_create(&thread->threadid, &attr, iax2_process_thread, thread)) {
+ ast_log(LOG_WARNING, "Failed to create new thread!\n");
+ 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);
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "%d helper threads started\n", threadcount);
+ return 0;
+}
+
+static struct iax2_context *build_context(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(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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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, 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 {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Using sourceaddress %s for '%s'\n", srcaddr, peer->name);
+ return 0;
+ }
+}
+
+static void peer_destructor(void *obj)
+{
+ struct iax2_peer *peer = obj;
+ int callno = peer->callno;
+
+ ast_free_ha(peer->ha);
+
+ if (callno > 0) {
+ ast_mutex_lock(&iaxsl[callno]);
+ iax2_destroy(callno);
+ ast_mutex_unlock(&iaxsl[callno]);
+ }
+
+ register_peer_exten(peer, 0);
+
+ if (peer->dnsmgr)
+ ast_dnsmgr_release(peer->dnsmgr);
+
+ 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, "hasvoicemail")) {
+ if (ast_true(v->value) && ast_strlen_zero(peer->mailbox)) {
+ ast_string_field_set(peer, mailbox, name);
+ }
+ } 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 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, "notransfer")) {
+ ast_log(LOG_NOTICE, "The option 'notransfer' is deprecated in favor of 'transfer' which has options 'yes', 'no', and 'mediaonly'\n");
+ ast_clear_flag(peer, IAX_TRANSFERMEDIA);
+ ast_set2_flag(peer, ast_true(v->value), IAX_NOTRANSFER);
+ } 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 */
+ AST_SCHED_DEL(sched, peer->expire);
+ 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);
+ } 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, sizeof(name2), num2, sizeof(num2));
+ ast_string_field_set(peer, cid_name, name2);
+ ast_string_field_set(peer, cid_num, num2);
+ } else {
+ ast_string_field_set(peer, cid_name, "");
+ ast_string_field_set(peer, cid_num, "");
+ }
+ ast_set_flag(peer, IAX_HASCALLERID);
+ } else if (!strcasecmp(v->name, "fullname")) {
+ ast_string_field_set(peer, cid_name, S_OR(v->value, ""));
+ ast_set_flag(peer, IAX_HASCALLERID);
+ } else if (!strcasecmp(v->name, "cid_number")) {
+ ast_string_field_set(peer, cid_num, S_OR(v->value, ""));
+ ast_set_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);
+ 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);
+ } 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 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, "notransfer")) {
+ ast_log(LOG_NOTICE, "The option 'notransfer' is deprecated in favor of 'transfer' which has options 'yes', 'no', and 'mediaonly'\n");
+ ast_clear_flag(user, IAX_TRANSFERMEDIA);
+ ast_set2_flag(user, ast_true(v->value), IAX_NOTRANSFER);
+ } 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))) {
+ ast_sched_del(sched, reg->expire);
+ if (reg->callno) {
+ int callno = reg->callno;
+ ast_mutex_lock(&iaxsl[callno]);
+ if (iaxs[callno]) {
+ iaxs[callno]->reg = NULL;
+ iax2_destroy(callno);
+ }
+ ast_mutex_unlock(&iaxsl[callno]);
+ }
+ if (reg->dnsmgr)
+ ast_dnsmgr_release(reg->dnsmgr);
+ 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_DAHDI
+ int bs = trunkfreq * 8;
+ if (timingfd > -1) {
+ if (
+#ifdef DAHDI_TIMERACK
+ ioctl(timingfd, DAHDI_TIMERCONFIG, &bs) &&
+#endif
+ ioctl(timingfd, DAHDI_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, "");
+ 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;
+ struct iax2_user *user;
+ struct iax2_peer *peer;
+ struct ast_netsock *ns;
+#if 0
+ static unsigned short int last_port=0;
+#endif
+
+ cfg = ast_config_load(config_file);
+
+ if (!cfg) {
+ ast_log(LOG_ERROR, "Unable to load config %s\n", config_file);
+ return -1;
+ }
+
+ 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;
+
+ 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, see doc/ip-tos.txt for more information.\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, "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, socket_read, NULL))) {
+ ast_log(LOG_WARNING, "Unable apply binding to '%s' at line %d\n", v->value, v->lineno);
+ } else {
+ if (option_verbose > 1) {
+ if (strchr(v->value, ':'))
+ ast_verbose(VERBOSE_PREFIX_2 "Binding IAX2 to '%s'\n", v->value);
+ else
+ ast_verbose(VERBOSE_PREFIX_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, "notransfer")) {
+ ast_log(LOG_NOTICE, "The option 'notransfer' is deprecated in favor of 'transfer' which has options 'yes', 'no', and 'mediaonly'\n");
+ ast_clear_flag((&globalflags), IAX_TRANSFERMEDIA);
+ ast_set2_flag((&globalflags), ast_true(v->value), IAX_NOTRANSFER);
+ } 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, "allowfwdownload"))
+ ast_set2_flag((&globalflags), ast_true(v->value), IAX_ALLOWFWDOWNLOAD);
+ 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, "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, see doc/ip-tos.txt for more information.'\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(mohinterpret));
+ } else if (!strcasecmp(v->name, "mohsuggest")) {
+ ast_copy_string(mohsuggest, v->value, sizeof(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,"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, socket_read, NULL))) {
+ ast_log(LOG_ERROR, "Unable to create network socket: %s\n", strerror(errno));
+ } else {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_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;
+
+ ucfg = ast_config_load("users.conf");
+ 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, (MAX_PEER_BUCKETS == 1) ? 1 : 0);
+ 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, (MAX_PEER_BUCKETS == 1) ? 1 : 0);
+ 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, (MAX_PEER_BUCKETS == 1) ? 1 : 0);
+ 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, (MAX_PEER_BUCKETS == 1) ? 1 : 0);
+ 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();
+ 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();
+
+ return 0;
+}
+
+static int iax2_reload(int fd, int argc, char *argv[])
+{
+ return reload_config();
+}
+
+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 < ARRAY_LEN(iaxs); 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);
+
+ if (ast_strlen_zero(pds.peer)) {
+ ast_log(LOG_WARNING, "No peer provided in the IAX2 dial string '%s'\n", data);
+ return -1;
+ }
+
+ /* Populate our address from the given */
+ if (create_addr(pds.peer, NULL, &sin, &cai))
+ return -1;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "peer: %s, username: %s, password: %s, context: %s\n",
+ pds.peer, pds.username, pds.password, pds.context);
+
+ callno = find_callno_locked(0, 0, &sin, NEW_FORCE, cai.sockfd, 0);
+ if (callno < 1) {
+ ast_log(LOG_WARNING, "Unable to create call\n");
+ return -1;
+ }
+
+ 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, *prev = NULL, *next;
+ struct timeval tv;
+ int x;
+ int com[2];
+ int timeout;
+ int old=0;
+ int outfd;
+ int abort;
+ int callno;
+ struct ast_channel *c;
+ struct ast_frame *f;
+ gettimeofday(&tv, NULL);
+ dp = dpcache;
+ while(dp) {
+ next = dp->next;
+ /* Expire old caches */
+ if (ast_tvcmp(tv, dp->expiry) > 0) {
+ /* It's expired, let it disappear */
+ if (prev)
+ prev->next = dp->next;
+ else
+ dpcache = dp->next;
+ if (!dp->peer && !(dp->flags & CACHE_FLAG_PENDING) && !dp->callno) {
+ /* Free memory and go again */
+ free(dp);
+ } else {
+ ast_log(LOG_WARNING, "DP still has peer field or pending or callno (flags = %d, peer = %p callno = %d)\n", dp->flags, dp->peer, dp->callno);
+ }
+ dp = next;
+ continue;
+ }
+ /* We found an entry that matches us! */
+ if (!strcmp(dp->peercontext, data) && !strcmp(dp->exten, exten))
+ break;
+ prev = dp;
+ dp = next;
+ }
+ if (!dp) {
+ /* No matching entry. Create a new one. */
+ /* First, can we make a callno? */
+ callno = cache_get_callno_locked(data);
+ if (callno < 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));
+ gettimeofday(&dp->expiry, NULL);
+ dp->orig = dp->expiry;
+ /* Expires in 30 mins by default */
+ dp->expiry.tv_sec += iaxdefaultdpcache;
+ dp->next = dpcache;
+ dp->flags = CACHE_FLAG_PENDING;
+ for (x=0;x<sizeof(dp->waiters) / sizeof(dp->waiters[0]); x++)
+ dp->waiters[x] = -1;
+ dpcache = dp;
+ dp->peer = iaxs[callno]->dpentries;
+ iaxs[callno]->dpentries = dp;
+ /* 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_mutex_unlock(&dpcache_lock);
+ /* 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) {
+ f = ast_read(c);
+ if (f)
+ ast_frfree(f);
+ else {
+ /* Got hung up on, abort! */
+ break;
+ abort = 1;
+ }
+ }
+ }
+ if (!timeout) {
+ ast_log(LOG_WARNING, "Timeout waiting for %s exten %s\n", data, exten);
+ }
+ ast_mutex_lock(&dpcache_lock);
+ 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 < ARRAY_LEN(dp->waiters); x++) {
+ if (dp->waiters[x] > -1) {
+ if (write(dp->waiters[x], "asdf", 4) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ }
+ }
+ }
+ }
+ /* 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)
+{
+ struct iax2_dpcache *dp;
+ int res = 0;
+#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_mutex_lock(&dpcache_lock);
+ dp = find_cache(chan, data, context, exten, priority);
+ if (dp) {
+ if (dp->flags & CACHE_FLAG_EXISTS)
+ res= 1;
+ }
+ ast_mutex_unlock(&dpcache_lock);
+ if (!dp) {
+ ast_log(LOG_WARNING, "Unable to make DP cache\n");
+ }
+ 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;
+#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_mutex_lock(&dpcache_lock);
+ dp = find_cache(chan, data, context, exten, priority);
+ if (dp) {
+ if (dp->flags & CACHE_FLAG_CANEXIST)
+ res= 1;
+ }
+ ast_mutex_unlock(&dpcache_lock);
+ if (!dp) {
+ ast_log(LOG_WARNING, "Unable to make DP cache\n");
+ }
+ 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;
+#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_mutex_lock(&dpcache_lock);
+ dp = find_cache(chan, data, context, exten, priority);
+ if (dp) {
+ if (dp->flags & CACHE_FLAG_MATCHMORE)
+ res= 1;
+ }
+ ast_mutex_unlock(&dpcache_lock);
+ if (!dp) {
+ ast_log(LOG_WARNING, "Unable to make DP cache\n");
+ }
+ 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;
+ struct ast_app *dial;
+#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_mutex_lock(&dpcache_lock);
+ dp = find_cache(chan, data, context, exten, priority);
+ if (dp) {
+ 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);
+ }
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Executing Dial('%s')\n", req);
+ } else {
+ ast_mutex_unlock(&dpcache_lock);
+ ast_log(LOG_WARNING, "Can't execute nonexistent extension '%s[@%s]' in data '%s'\n", exten, context, data);
+ return -1;
+ }
+ }
+ ast_mutex_unlock(&dpcache_lock);
+ dial = pbx_findapp("Dial");
+ if (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, 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);
+ } else {
+ buf[0] = '\0';
+ }
+ } else {
+ buf[0] = '\0';
+ }
+
+ 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"
+};
+
+
+/*! \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)) {
+ ast_log(LOG_WARNING, "No peer provided in the IAX2 dial string '%s'\n", (char *) data);
+ return res;
+ }
+
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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,
+};
+
+static char show_stats_usage[] =
+"Usage: iax2 show stats\n"
+" Display statistics on IAX channel driver.\n";
+
+static char show_cache_usage[] =
+"Usage: iax2 show cache\n"
+" Display currently cached IAX Dialplan results.\n";
+
+static char show_peer_usage[] =
+"Usage: iax2 show peer <name>\n"
+" Display details on specific IAX peer\n";
+
+static char prune_realtime_usage[] =
+"Usage: iax2 prune realtime [<peername>|all]\n"
+" Prunes object(s) from the cache\n";
+
+static char iax2_reload_usage[] =
+"Usage: iax2 reload\n"
+" Reloads IAX configuration from iax.conf\n";
+
+static char show_prov_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";
+
+static char show_users_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";
+
+static char show_channels_usage[] =
+"Usage: iax2 show channels\n"
+" Lists all currently active IAX channels.\n";
+
+static char show_netstats_usage[] =
+"Usage: iax2 show netstats\n"
+" Lists network status for all currently active IAX channels.\n";
+
+static char show_threads_usage[] =
+"Usage: iax2 show threads\n"
+" Lists status of IAX helper threads\n";
+
+static char show_peers_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";
+
+static char show_firmware_usage[] =
+"Usage: iax2 show firmware\n"
+" Lists all known IAX firmware images.\n";
+
+static char show_reg_usage[] =
+"Usage: iax2 show registry\n"
+" Lists all registration requests and status.\n";
+
+static char debug_usage[] =
+"Usage: iax2 set debug\n"
+" Enables dumping of IAX packets for debugging purposes\n";
+
+static char no_debug_usage[] =
+"Usage: iax2 set debug off\n"
+" Disables dumping of IAX packets for debugging purposes\n";
+
+static char debug_trunk_usage[] =
+"Usage: iax2 set debug trunk\n"
+" Requests current status of IAX trunking\n";
+
+static char no_debug_trunk_usage[] =
+"Usage: iax2 set debug trunk off\n"
+" Requests current status of IAX trunking\n";
+
+static char debug_jb_usage[] =
+"Usage: iax2 set debug jb\n"
+" Enables jitterbuffer debugging information\n";
+
+static char no_debug_jb_usage[] =
+"Usage: iax2 set debug jb off\n"
+" Disables jitterbuffer debugging information\n";
+
+static char iax2_test_losspct_usage[] =
+"Usage: iax2 test losspct <percentage>\n"
+" For testing, throws away <percentage> percent of incoming packets\n";
+
+#ifdef IAXTESTS
+static char iax2_test_late_usage[] =
+"Usage: iax2 test late <ms>\n"
+" For testing, count the next frame as <ms> ms late\n";
+
+static char iax2_test_resync_usage[] =
+"Usage: iax2 test resync <ms>\n"
+" For testing, adjust all future frames by <ms> ms\n";
+
+static char iax2_test_jitter_usage[] =
+"Usage: iax2 test jitter <ms> <pct>\n"
+" For testing, simulate maximum jitter of +/- <ms> on <pct> percentage of packets. If <pct> is not specified, adds jitter to all packets.\n";
+#endif /* IAXTESTS */
+
+static struct ast_cli_entry cli_iax2_trunk_debug_deprecated = {
+ { "iax2", "trunk", "debug", NULL },
+ iax2_do_trunk_debug, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_iax2_jb_debug_deprecated = {
+ { "iax2", "jb", "debug", NULL },
+ iax2_do_jb_debug, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_iax2_no_debug_deprecated = {
+ { "iax2", "no", "debug", NULL },
+ iax2_no_debug, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_iax2_no_trunk_debug_deprecated = {
+ { "iax2", "no", "trunk", "debug", NULL },
+ iax2_no_trunk_debug, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_iax2_no_jb_debug_deprecated = {
+ { "iax2", "no", "jb", "debug", NULL },
+ iax2_no_jb_debug, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_iax2[] = {
+ { { "iax2", "show", "cache", NULL },
+ iax2_show_cache, "Display IAX cached dialplan",
+ show_cache_usage, NULL, },
+
+ { { "iax2", "show", "channels", NULL },
+ iax2_show_channels, "List active IAX channels",
+ show_channels_usage, NULL, },
+
+ { { "iax2", "show", "firmware", NULL },
+ iax2_show_firmware, "List available IAX firmwares",
+ show_firmware_usage, NULL, },
+
+ { { "iax2", "show", "netstats", NULL },
+ iax2_show_netstats, "List active IAX channel netstats",
+ show_netstats_usage, NULL, },
+
+ { { "iax2", "show", "peers", NULL },
+ iax2_show_peers, "List defined IAX peers",
+ show_peers_usage, NULL, },
+
+ { { "iax2", "show", "registry", NULL },
+ iax2_show_registry, "Display IAX registration status",
+ show_reg_usage, NULL, },
+
+ { { "iax2", "show", "stats", NULL },
+ iax2_show_stats, "Display IAX statistics",
+ show_stats_usage, NULL, },
+
+ { { "iax2", "show", "threads", NULL },
+ iax2_show_threads, "Display IAX helper thread info",
+ show_threads_usage, NULL, },
+
+ { { "iax2", "show", "users", NULL },
+ iax2_show_users, "List defined IAX users",
+ show_users_usage, NULL, },
+
+ { { "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, NULL, &cli_iax2_trunk_debug_deprecated },
+
+ { { "iax2", "set", "debug", "jb", NULL },
+ iax2_do_jb_debug, "Enable IAX jitterbuffer debugging",
+ debug_jb_usage, NULL, &cli_iax2_jb_debug_deprecated },
+
+ { { "iax2", "set", "debug", "off", NULL },
+ iax2_no_debug, "Disable IAX debugging",
+ no_debug_usage, NULL, &cli_iax2_no_debug_deprecated },
+
+ { { "iax2", "set", "debug", "trunk", "off", NULL },
+ iax2_no_trunk_debug, "Disable IAX trunk debugging",
+ no_debug_trunk_usage, NULL, &cli_iax2_no_trunk_debug_deprecated },
+
+ { { "iax2", "set", "debug", "jb", "off", NULL },
+ iax2_no_jb_debug, "Disable IAX jitterbuffer debugging",
+ no_debug_jb_usage, NULL, &cli_iax2_no_jb_debug_deprecated },
+
+ { { "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 /* IAXTESTS */
+};
+
+static int __unload_module(void)
+{
+ struct iax2_thread *thread = NULL;
+ 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(&iaxq.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(&iaxq.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);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&idle_list, thread, list) {
+ AST_LIST_REMOVE_CURRENT(&idle_list, list);
+ pthread_cancel(thread->threadid);
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ AST_LIST_UNLOCK(&idle_list);
+
+ AST_LIST_LOCK(&active_list);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&active_list, thread, list) {
+ AST_LIST_REMOVE_CURRENT(&active_list, list);
+ pthread_cancel(thread->threadid);
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ AST_LIST_UNLOCK(&active_list);
+
+ AST_LIST_LOCK(&dynamic_list);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&dynamic_list, thread, list) {
+ AST_LIST_REMOVE_CURRENT(&dynamic_list, list);
+ pthread_cancel(thread->threadid);
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ AST_LIST_UNLOCK(&dynamic_list);
+
+ AST_LIST_HEAD_DESTROY(&iaxq.queue);
+
+ /* Wait for threads to exit */
+ while(0 < iaxactivethreadcount)
+ usleep(10000);
+
+ ast_netsock_release(netsock);
+ ast_netsock_release(outsock);
+ for (x = 0; x < ARRAY_LEN(iaxs); x++) {
+ if (iaxs[x]) {
+ iax2_destroy(x);
+ }
+ }
+ ast_manager_unregister( "IAXpeers" );
+ 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);
+
+ ast_mutex_destroy(&waresl.lock);
+
+ for (x = 0; x < ARRAY_LEN(iaxsl); x++) {
+ ast_mutex_destroy(&iaxsl[x]);
+ }
+
+ ao2_ref(peers, -1);
+ ao2_ref(users, -1);
+ ao2_ref(iax_peercallno_pvts, -1);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_custom_function_unregister(&iaxpeer_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;
+}
+
+static int pvt_hash_cb(const void *obj, const int flags)
+{
+ const struct chan_iax2_pvt *pvt = obj;
+
+ return pvt->peercallno;
+}
+
+static int pvt_cmp_cb(void *obj, void *arg, int flags)
+{
+ struct chan_iax2_pvt *pvt = obj, *pvt2 = arg;
+
+ /* The frames_received field is used to hold whether we're matching
+ * against a full frame or not ... */
+
+ return match(&pvt2->addr, pvt2->peercallno, pvt2->callno, pvt,
+ pvt2->frames_received) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+/*! \brief Load IAX2 module, load configuraiton ---*/
+static int load_module(void)
+{
+ char *config = "iax.conf";
+ int res = 0;
+ int x;
+ 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;
+ }
+ iax_peercallno_pvts = ao2_container_alloc(IAX_MAX_CALLS, pvt_hash_cb, pvt_cmp_cb);
+ if (!iax_peercallno_pvts) {
+ ao2_ref(peers, -1);
+ ao2_ref(users, -1);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ ast_custom_function_register(&iaxpeer_function);
+
+ iax_set_output(iax_debug_output);
+ iax_set_error(iax_error_output);
+ jb_setoutput(jb_error_output, jb_warning_output, NULL);
+
+#ifdef HAVE_DAHDI
+#ifdef DAHDI_TIMERACK
+ timingfd = open(DAHDI_FILE_TIMER, O_RDWR);
+ if (timingfd < 0)
+#endif
+ timingfd = open(DAHDI_FILE_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 < ARRAY_LEN(iaxsl); x++) {
+ ast_mutex_init(&iaxsl[x]);
+ }
+
+ ast_cond_init(&sched_cond, NULL);
+
+ io = io_context_create();
+ sched = sched_context_create();
+
+ if (!io || !sched) {
+ ast_log(LOG_ERROR, "Out of memory\n");
+ return -1;
+ }
+
+ netsock = ast_netsock_list_alloc();
+ if (!netsock) {
+ ast_log(LOG_ERROR, "Could not allocate netsock list.\n");
+ return -1;
+ }
+ ast_netsock_init(netsock);
+
+ outsock = ast_netsock_list_alloc();
+ if (!outsock) {
+ ast_log(LOG_ERROR, "Could not allocate outsock list.\n");
+ return -1;
+ }
+ ast_netsock_init(outsock);
+
+ ast_mutex_init(&waresl.lock);
+
+ AST_LIST_HEAD_INIT(&iaxq.queue);
+
+ 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", 0, manager_iax2_show_peers, "List IAX Peers" );
+ ast_manager_register( "IAXnetstats", 0, 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 -1;
+ }
+
+ if (ast_register_switch(&iax2_switch))
+ ast_log(LOG_ERROR, "Unable to register IAX switch\n");
+
+ res = start_network_thread();
+ if (!res) {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "IAX Ready and Listening\n");
+ } else {
+ ast_log(LOG_ERROR, "Unable to start network thread\n");
+ ast_netsock_release(netsock);
+ ast_netsock_release(outsock);
+ }
+
+ 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();
+ return res;
+}
+
+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/1.4.23-rc4/channels/chan_local.c b/1.4.23-rc4/channels/chan_local.c
new file mode 100644
index 000000000..b2e3c72b8
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_local.c
@@ -0,0 +1,808 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <stdlib.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/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/lock.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_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_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;
+
+ 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';
+
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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;
+ else
+ return AST_DEVICE_UNKNOWN;
+}
+
+/*!
+ * \note Assumes the pvt is no longer in the pvts list
+ */
+static struct local_pvt *local_pvt_destroy(struct local_pvt *pvt)
+{
+ ast_mutex_destroy(&pvt->lock);
+ free(pvt);
+ return NULL;
+}
+
+static int local_queue_frame(struct local_pvt *p, int isoutbound, struct ast_frame *f,
+ struct ast_channel *us, int us_locked)
+{
+ struct ast_channel *other = NULL;
+
+ /* Recalculate outbound channel */
+ other = isoutbound ? p->owner : p->chan;
+
+ /* do not queue frame if generator is on both local channels */
+ if (us && us->generator && other->generator)
+ return 0;
+
+ /* 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);
+ p = local_pvt_destroy(p);
+ return -1;
+ }
+ if (!other) {
+ ast_clear_flag(p, LOCAL_GLARE_DETECT);
+ return 0;
+ }
+
+ /* Ensure that we have both channels locked */
+ while (other && ast_channel_trylock(other)) {
+ ast_mutex_unlock(&p->lock);
+ if (us && us_locked) {
+ do {
+ ast_channel_unlock(us);
+ usleep(1);
+ ast_channel_lock(us);
+ } while (ast_mutex_trylock(&p->lock));
+ } else {
+ usleep(1);
+ ast_mutex_lock(&p->lock);
+ }
+ other = isoutbound ? p->owner : p->chan;
+ }
+
+ if (other) {
+ 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, 1);
+ } 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)
+{
+ struct ast_channel_monitor *tmp;
+ 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_mutex_trylock(&(p->chan->_bridge)->lock)) {
+ if (!p->chan->_bridge->_softhangup) {
+ if (!ast_mutex_trylock(&p->owner->lock)) {
+ if (!p->owner->_softhangup) {
+ if (p->owner->monitor && !p->chan->_bridge->monitor) {
+ /* If a local channel is being monitored, we don't want a masquerade
+ * to cause the monitor to go away. Since the masquerade swaps the monitors,
+ * pre-swapping the monitors before the masquerade will ensure that the monitor
+ * ends up where it is expected.
+ */
+ tmp = p->owner->monitor;
+ p->owner->monitor = p->chan->_bridge->monitor;
+ p->chan->_bridge->monitor = tmp;
+ }
+ if (p->chan->audiohooks) {
+ struct ast_audiohook_list *audiohooks_swapper;
+ audiohooks_swapper = p->chan->audiohooks;
+ p->chan->audiohooks = p->owner->audiohooks;
+ p->owner->audiohooks = audiohooks_swapper;
+ }
+ ast_channel_masquerade(p->owner, p->chan->_bridge);
+ ast_set_flag(p, LOCAL_ALREADY_MASQED);
+ }
+ ast_mutex_unlock(&p->owner->lock);
+ }
+ ast_mutex_unlock(&(p->chan->_bridge)->lock);
+ }
+ }
+ /* 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 (!p->owner->_bridge->_softhangup) {
+ if (!ast_mutex_trylock(&p->chan->lock)) {
+ if (!p->chan->_softhangup) {
+ 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, 1);
+ else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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, 1)))
+ 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, 0)))
+ 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, 0)))
+ 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, 0)))
+ 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, 0)))
+ 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_dnid = ast_strdup(p->owner->cid.cid_dnid);
+ 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;
+ p->chan->cid.cid_ani2 = p->owner->cid.cid_ani2;
+ p->chan->cid.cid_ton = p->owner->cid.cid_ton;
+ p->chan->cid.cid_tns = p->owner->cid.cid_tns;
+ ast_string_field_set(p->chan, language, p->owner->language);
+ ast_string_field_set(p->chan, accountcode, p->owner->accountcode);
+ ast_string_field_set(p->chan, musicclass, p->owner->musicclass);
+ ast_cdr_update(p->chan);
+ p->chan->cdrflags = p->owner->cdrflags;
+
+ if (!ast_exists_extension(NULL, p->chan->context, p->chan->exten, 1, p->owner->cid.cid_num)) {
+ ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n", p->chan->exten, p->chan->context);
+ ast_mutex_unlock(&p->lock);
+ return -1;
+ }
+
+ /* 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;
+
+ while (ast_mutex_trylock(&p->lock)) {
+ ast_channel_unlock(ast);
+ usleep(1);
+ ast_channel_lock(ast);
+ }
+
+ isoutbound = IS_OUTBOUND(ast, p);
+ if (isoutbound) {
+ const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
+ if ((status) && (p->owner)) {
+ /* Deadlock avoidance */
+ while (p->owner && ast_channel_trylock(p->owner)) {
+ ast_mutex_unlock(&p->lock);
+ if (ast) {
+ ast_channel_unlock(ast);
+ }
+ usleep(1);
+ if (ast) {
+ ast_channel_lock(ast);
+ }
+ ast_mutex_lock(&p->lock);
+ }
+ if (p->owner) {
+ 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);
+ while (p->chan && ast_channel_trylock(p->chan)) {
+ DEADLOCK_AVOIDANCE(&p->lock);
+ }
+ if (p->chan) {
+ ast_queue_hangup(p->chan);
+ ast_channel_unlock(p->chan);
+ }
+ }
+
+ 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) {
+ p = local_pvt_destroy(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, 1);
+ 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));
+
+ /* Look for options */
+ if ((opts = strchr(tmp->exten, '/'))) {
+ *opts++ = '\0';
+ if (strchr(opts, 'n'))
+ ast_set_flag(tmp, LOCAL_NO_OPTIMIZATION);
+ }
+
+ /* 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 0
+ /* We can't do this check here, because we don't know the CallerID yet, and
+ * the CallerID could potentially affect what step is actually taken (or
+ * even if that step exists). */
+ 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);
+ tmp = local_pvt_destroy(tmp);
+ } else {
+#endif
+ /* Add to list */
+ AST_LIST_LOCK(&locals);
+ AST_LIST_INSERT_HEAD(&locals, tmp, list);
+ AST_LIST_UNLOCK(&locals);
+#if 0
+ }
+#endif
+
+ 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;
+
+ 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))) {
+ if (!(chan = local_new(p, AST_STATE_DOWN))) {
+ AST_LIST_LOCK(&locals);
+ AST_LIST_REMOVE(&locals, p, list);
+ AST_LIST_UNLOCK(&locals);
+ p = local_pvt_destroy(p);
+ }
+ }
+
+ return chan;
+}
+
+/*! \brief CLI command "local show channels" */
+static int locals_show(int fd, int argc, char **argv)
+{
+ struct local_pvt *p = NULL;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ AST_LIST_LOCK(&locals);
+ if (!AST_LIST_EMPTY(&locals)) {
+ AST_LIST_TRAVERSE(&locals, p, list) {
+ ast_mutex_lock(&p->lock);
+ ast_cli(fd, "%s -- %s@%s\n", p->owner ? p->owner->name : "<unowned>", p->exten, p->context);
+ ast_mutex_unlock(&p->lock);
+ }
+ } else
+ ast_cli(fd, "No local channels in use\n");
+ AST_LIST_UNLOCK(&locals);
+
+ return RESULT_SUCCESS;
+}
+
+static char show_locals_usage[] =
+"Usage: local show channels\n"
+" Provides summary information on active local proxy channels.\n";
+
+static struct ast_cli_entry cli_local[] = {
+ { { "local", "show", "channels", NULL },
+ locals_show, "List status of local channels",
+ show_locals_usage },
+};
+
+/*! \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 -1;
+ }
+ ast_cli_register_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
+ return 0;
+}
+
+/*! \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);
+ } 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 (Note: used internally by other modules)");
diff --git a/1.4.23-rc4/channels/chan_mgcp.c b/1.4.23-rc4/channels/chan_mgcp.c
new file mode 100644
index 000000000..1d5114bee
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_mgcp.c
@@ -0,0 +1,4430 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <errno.h>
+#include <stdlib.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/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/lock.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/causes.h"
+#include "asterisk/dsp.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/abstract_jb.h"
+
+#ifndef IPTOS_MINCOST
+#define IPTOS_MINCOST 0x02
+#endif
+
+/*
+ * 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 int tos = 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 call_forward[AST_MAX_EXTENSION]; /*!< Last Caller*ID */
+ char mailbox[AST_MAX_EXTENSION];
+ char musicclass[MAX_MUSICCLASS];
+ char curtone[80]; /*!< Current tone */
+ char dtmf_buf[AST_MAX_EXTENSION]; /*!< place to collect digits be */
+ 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 int mgcp_do_reload(void);
+static int mgcp_reload(int fd, int argc, char *argv[]);
+
+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 int has_voicemail(struct mgcp_endpoint *p)
+{
+ return ast_app_has_voicemail(p->mailbox, NULL);
+}
+
+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_log(LOG_DEBUG, "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;
+ free(cur);
+ }
+}
+
+static void mgcp_queue_frame(struct mgcp_subchannel *sub, struct ast_frame *f)
+{
+ for(;;) {
+ if (sub->owner) {
+ if (!ast_mutex_trylock(&sub->owner->lock)) {
+ ast_queue_frame(sub->owner, f);
+ ast_mutex_unlock(&sub->owner->lock);
+ break;
+ } else {
+ DEADLOCK_AVOIDANCE(&sub->lock);
+ }
+ } else
+ break;
+ }
+}
+
+static void mgcp_queue_hangup(struct mgcp_subchannel *sub)
+{
+ for(;;) {
+ if (sub->owner) {
+ if (!ast_mutex_trylock(&sub->owner->lock)) {
+ ast_queue_hangup(sub->owner);
+ ast_mutex_unlock(&sub->owner->lock);
+ break;
+ } else {
+ DEADLOCK_AVOIDANCE(&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;
+ 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 = malloc(sizeof(struct mgcp_message) + len);
+ struct mgcp_message *cur;
+ struct mgcp_gateway *gw = ((p && p->parent) ? p->parent : NULL);
+ struct timeval tv;
+
+ if (!msg) {
+ return -1;
+ }
+ if (!gw) {
+ 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;
+ }
+
+ if (gettimeofday(&tv, NULL) < 0) {
+ /* This shouldn't ever happen, but let's be sure */
+ ast_log(LOG_NOTICE, "gettimeofday() failed!\n");
+ } else {
+ 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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;
+ 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 = (struct mgcp_request *) malloc (sizeof(struct mgcp_request));
+ if (!r) {
+ ast_log(LOG_WARNING, "Cannot post MGCP request: insufficient memory\n");
+ ast_mutex_unlock(l);
+ return -1;
+ }
+ memcpy(r, req, sizeof(struct mgcp_request));
+
+ 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_verbose(VERBOSE_PREFIX_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_verbose(VERBOSE_PREFIX_3 "MGCP distinctive callwait %s\n", tone);
+ }
+ } else {
+ snprintf(tone, sizeof(tone), "L/wt");
+ if (mgcpdebug) {
+ ast_verbose(VERBOSE_PREFIX_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_verbose(VERBOSE_PREFIX_3 "MGCP distinctive ring %s\n", tone);
+ }
+ } else {
+ snprintf(tone, sizeof(tone), "L/rg");
+ if (mgcpdebug) {
+ ast_verbose(VERBOSE_PREFIX_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;
+
+ if (option_debug) {
+ ast_log(LOG_DEBUG, "mgcp_hangup(%s)\n", ast->name);
+ }
+ if (!ast->tech_pvt) {
+ ast_log(LOG_DEBUG, "Asked to hangup channel not connected\n");
+ return 0;
+ }
+ if (strcmp(sub->magic, MGCP_SUBCHANNEL_MAGIC)) {
+ ast_log(LOG_DEBUG, "Invalid magic. MGCP subchannel freed up already.\n");
+ return 0;
+ }
+ ast_mutex_lock(&sub->lock);
+ if (mgcpdebug) {
+ ast_verbose(VERBOSE_PREFIX_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_verbose(VERBOSE_PREFIX_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';
+ if (p) {
+ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf));
+ }
+ /* 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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Enabling call waiting on %s\n", ast->name);
+ p->callwaiting = -1;
+ }
+ if (has_voicemail(p)) {
+ if (mgcpdebug) {
+ ast_verbose(VERBOSE_PREFIX_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_verbose(VERBOSE_PREFIX_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 int mgcp_show_endpoints(int fd, int argc, char *argv[])
+{
+ struct mgcp_gateway *g;
+ struct mgcp_endpoint *e;
+ int hasendpoints = 0;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&gatelock);
+ g = gateways;
+ while(g) {
+ e = g->endpoints;
+ ast_cli(fd, "Gateway '%s' at %s (%s)\n", g->name, g->addr.sin_addr.s_addr ? ast_inet_ntoa(g->addr.sin_addr) : ast_inet_ntoa(g->defaddr.sin_addr), g->dynamic ? "Dynamic" : "Static");
+ while(e) {
+ /* Don't show wilcard endpoint */
+ if (strcmp(e->name, g->wcardep) !=0)
+ ast_cli(fd, " -- '%s@%s in '%s' is %s\n", e->name, g->name, e->context, e->sub->owner ? "active" : "idle");
+ hasendpoints = 1;
+ e = e->next;
+ }
+ if (!hasendpoints) {
+ ast_cli(fd, " << No Endpoints Defined >> ");
+ }
+ g = g->next;
+ }
+ ast_mutex_unlock(&gatelock);
+ return RESULT_SUCCESS;
+}
+
+static char show_endpoints_usage[] =
+"Usage: mgcp show endpoints\n"
+" Lists all endpoints known to the MGCP (Media Gateway Control Protocol) subsystem.\n";
+
+static char audit_endpoint_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";
+
+static char debug_usage[] =
+"Usage: mgcp set debug\n"
+" Enables dumping of MGCP packets for debugging purposes\n";
+
+static char no_debug_usage[] =
+"Usage: mgcp set debug off\n"
+" Disables dumping of MGCP packets for debugging purposes\n";
+
+static char mgcp_reload_usage[] =
+"Usage: mgcp reload\n"
+" Reloads MGCP configuration from mgcp.conf\n"
+" Deprecated: please use 'reload chan_mgcp.so' instead.\n";
+
+static int mgcp_audit_endpoint(int fd, int argc, char *argv[])
+{
+ struct mgcp_gateway *g;
+ struct mgcp_endpoint *e;
+ int found = 0;
+ char *ename,*gname, *c;
+
+ if (!mgcpdebug) {
+ return RESULT_SHOWUSAGE;
+ }
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ /* split the name into parts by null */
+ ename = 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);
+ g = gateways;
+ while(g) {
+ if (!strcasecmp(g->name, gname)) {
+ e = g->endpoints;
+ while(e) {
+ if (!strcasecmp(e->name, ename)) {
+ found = 1;
+ transmit_audit_endpoint(e);
+ break;
+ }
+ e = e->next;
+ }
+ if (found) {
+ break;
+ }
+ }
+ g = g->next;
+ }
+ if (!found) {
+ ast_cli(fd, " << Could not find endpoint >> ");
+ }
+ ast_mutex_unlock(&gatelock);
+ return RESULT_SUCCESS;
+}
+
+static int mgcp_do_debug(int fd, int argc, char *argv[])
+{
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ mgcpdebug = 1;
+ ast_cli(fd, "MGCP Debugging Enabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int mgcp_no_debug(int fd, int argc, char *argv[])
+{
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ mgcpdebug = 0;
+ ast_cli(fd, "MGCP Debugging Disabled\n");
+ return RESULT_SUCCESS;
+}
+
+static struct ast_cli_entry cli_mgcp[] = {
+ { { "mgcp", "audit", "endpoint", NULL },
+ mgcp_audit_endpoint, "Audit specified MGCP endpoint",
+ audit_endpoint_usage },
+
+ { { "mgcp", "show", "endpoints", NULL },
+ mgcp_show_endpoints, "List defined MGCP endpoints",
+ show_endpoints_usage },
+
+ { { "mgcp", "set", "debug", NULL },
+ mgcp_do_debug, "Enable MGCP debugging",
+ debug_usage },
+
+ { { "mgcp", "set", "debug", "off", NULL },
+ mgcp_no_debug, "Disable MGCP debugging",
+ no_debug_usage },
+
+ { { "mgcp", "reload", NULL },
+ mgcp_reload, "Reload MGCP configuration",
+ mgcp_reload_usage },
+};
+
+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);
+ }
+ /* verbose level check */
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_verbose(VERBOSE_PREFIX_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 AST_CONTROL_SRCUPDATE:
+ ast_rtp_new_source(sub->rtp);
+ 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)
+ tmp->fds[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;
+ }
+ }
+ /* verbose level check */
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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));
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Searching on %s@%s for subchannel\n",
+ p->name, g->name);
+ if (msgid) {
+#if 0 /* new transport mech */
+ sub = p->sub;
+ do {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Searching on %s@%s-%d for subchannel with lastout: %d\n",
+ p->name, g->name, sub->id, msgid);
+ if (sub->lastout == msgid) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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 */
+ snprintf(req->data + req->len, sizeof(req->data) - req->len, "\r\n");
+ 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 = malloc(sizeof(struct mgcp_response) + resp.len + 1);
+ if (mgr) {
+ /* Store MGCP response in case we have to retransmit */
+ memset(mgr, 0, sizeof(struct mgcp_response));
+ 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));
+ }
+ snprintf(v, sizeof(v), "v=0\r\n");
+ snprintf(o, sizeof(o), "o=root %d %d IN IP4 %s\r\n", (int)getpid(), (int)getpid(), ast_inet_ntoa(dest.sin_addr));
+ snprintf(s, sizeof(s), "s=session\r\n");
+ snprintf(c, sizeof(c), "c=IN IP4 %s\r\n", ast_inet_ntoa(dest.sin_addr));
+ snprintf(t, sizeof(t), "t=0 0\r\n");
+ snprintf(m, sizeof(m), "m=audio %d RTP/AVP", ntohs(dest.sin_port));
+ for (x = 1; x <= AST_FORMAT_MAX_AUDIO; 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;
+ }
+ snprintf(local, sizeof(local), "p:20");
+ for (x=1;x<= AST_FORMAT_MAX_AUDIO; 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;
+
+ snprintf(local, sizeof(local), "p:20");
+ for (x=1;x<= AST_FORMAT_MAX_AUDIO; 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_verbose(VERBOSE_PREFIX_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_verbose(VERBOSE_PREFIX_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;
+ time_t t;
+ struct tm tm;
+ struct mgcp_endpoint *p = sub->parent;
+
+ time(&t);
+ 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_verbose(VERBOSE_PREFIX_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_verbose(VERBOSE_PREFIX_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_verbose(VERBOSE_PREFIX_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_verbose(VERBOSE_PREFIX_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, 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, 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, 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, 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, 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) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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);
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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, "");
+
+ /* verbose level check */
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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, "");
+
+ /* verbose level check */
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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);
+ }
+ }
+ }
+
+ 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)
+ sub->owner->fds[0] = ast_rtp_fd(sub->rtp);
+ if (sub->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, DAHDI_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));
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Setting call forward to '%s' on channel %s\n",
+ p->call_forward, chan->name);
+ }
+ /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_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, DAHDI_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, DAHDI_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_log(LOG_DEBUG, "not enough digits (and no ambiguous match)...\n");
+ /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_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")) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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, DAHDI_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, DAHDI_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")) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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, DAHDI_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, DAHDI_TONE_DIALRECALL);*/
+ transmit_notify_request(sub, "L/sl");
+ break;
+ } else if (!strcmp(p->dtmf_buf, "*78")) {
+ /* Do not disturb */
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Enabled DND on channel %s\n", chan->name);
+ }
+ /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_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 */
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Disabled DND on channel %s\n", chan->name);
+ }
+ /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_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, DAHDI_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")) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Cancelling call forwarding on channel %s\n", chan->name);
+ }
+ /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_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);
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Parking call to '%s'\n", chan->name);
+ }
+ break;
+ } else if (!ast_strlen_zero(p->lastcallerid) && !strcmp(p->dtmf_buf, "*60")) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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, DAHDI_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")) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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, DAHDI_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))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Timeout...\n");
+ break;
+ }
+ if (res < 0) {
+ ast_log(LOG_DEBUG, "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);*/
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "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;
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ /* 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(&t, &attr, 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);*/
+ }
+ }
+ pthread_attr_destroy(&attr);
+}
+
+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")) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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 (option_verbose > 2 && (strcmp(p->name, p->parent->wcardep) != 0)) {
+ ast_verbose(VERBOSE_PREFIX_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;
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "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) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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;
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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 */
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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 */
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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;
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "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 {
+ /* verbose level check */
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Enabling call waiting on MGCP/%s@%s-%d\n", p->name, p->parent->name, sub->id);
+ p->callwaiting = -1;
+ }
+ if (has_voicemail(p)) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "MGCP handle_request(%s@%s) set vmwi(+)\n", p->name, p->parent->name);
+ }
+ transmit_notify_request(sub, "L/vmwi(+)");
+ } else {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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;
+ 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_log(LOG_DEBUG, "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) {
+ AST_SCHED_DEL(sched, gw->retransid);
+ }
+
+ ast_mutex_unlock(&gw->msgs_lock);
+ if (cur) {
+ handle_response(cur->owner_ep, cur->owner_sub, result, ident, &req);
+ 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) {
+ if (option_verbose > 0)
+ ast_verbose(VERBOSE_PREFIX_1 "Reloading MGCP\n");
+ mgcp_do_reload();
+ /* 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;
+ }
+
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "MGCP mgcp_request(%s)\n", tmp);
+ ast_verbose(VERBOSE_PREFIX_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 = malloc(sizeof(struct mgcp_gateway));
+
+ if (gw) {
+ if (!gw_reload) {
+ memset(gw, 0, sizeof(struct mgcp_gateway));
+ 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 */
+ AST_SCHED_DEL(sched, gw->expire);
+ gw->dynamic = 0;
+ if (ast_get_ip(&gw->addr, v->value)) {
+ if (!gw_reload) {
+ ast_mutex_destroy(&gw->msgs_lock);
+ 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);
+ 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);
+ } 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, "hasvoicemail")) {
+ if (ast_true(v->value) && ast_strlen_zero(mailbox)) {
+ ast_copy_string(mailbox, gw->name, 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 = malloc(sizeof(struct mgcp_endpoint));
+ 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));
+ 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 = malloc(sizeof(struct mgcp_subchannel));
+ if (sub) {
+ ast_verbose(VERBOSE_PREFIX_3 "Allocating subchannel '%d' on %s@%s\n", i, e->name, gw->name);
+ memset(sub, 0, sizeof(struct mgcp_subchannel));
+ 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 = malloc(sizeof(struct mgcp_endpoint));
+ 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;
+ }
+ /* 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_verbose(VERBOSE_PREFIX_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 = malloc(sizeof(struct mgcp_subchannel));
+ } else {
+ if (!sub)
+ sub = e->sub;
+ else
+ sub = sub->next;
+ }
+
+ if (sub) {
+ if (!ep_reload) {
+ ast_verbose(VERBOSE_PREFIX_3 "Allocating subchannel '%d' on %s@%s\n", i, e->name, gw->name);
+ memset(sub, 0, sizeof(struct mgcp_subchannel));
+ 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);
+ 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, 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);
+ free(s);
+ }
+ ast_mutex_destroy(&e->lock);
+ ast_mutex_destroy(&e->rqnt_queue_lock);
+ ast_mutex_destroy(&e->cmd_queue_lock);
+ free(e);
+}
+
+static void destroy_gateway(struct mgcp_gateway *g)
+{
+ if (g->ha)
+ ast_free_ha(g->ha);
+
+ dump_queue(g, NULL);
+
+ 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(void)
+{
+ 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;
+
+ if (gethostname(ourhost, sizeof(ourhost)-1)) {
+ ast_log(LOG_WARNING, "Unable to get hostname, MGCP disabled\n");
+ return 0;
+ }
+ cfg = ast_config_load(config);
+
+ /* 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;
+ }
+ 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 (sscanf(v->value, "%d", &format) == 1)
+ tos = format & 0xff;
+ else if (!strcasecmp(v->value, "lowdelay"))
+ tos = IPTOS_LOWDELAY;
+ else if (!strcasecmp(v->value, "throughput"))
+ tos = IPTOS_THROUGHPUT;
+ else if (!strcasecmp(v->value, "reliability"))
+ tos = IPTOS_RELIABILITY;
+ else if (!strcasecmp(v->value, "mincost"))
+ tos = IPTOS_MINCOST;
+ else if (!strcasecmp(v->value, "none"))
+ tos = 0;
+ else
+ ast_log(LOG_WARNING, "Invalid tos value at line %d, should be 'lowdelay', 'throughput', 'reliability', 'mincost', or 'none'\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) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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 {
+ if (option_verbose > 1) {
+ ast_verbose(VERBOSE_PREFIX_2 "MGCP Listening on %s:%d\n",
+ ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port));
+ ast_verbose(VERBOSE_PREFIX_2 "Using TOS bits %d\n", tos);
+ }
+ if (setsockopt(mgcpsock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)))
+ ast_log(LOG_WARNING, "Unable to set TOS to %d\n", tos);
+ }
+ }
+ 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_verbose(VERBOSE_PREFIX_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())
+ 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;
+}
+
+/*! \brief mgcp_do_reload: Reload module */
+static int mgcp_do_reload(void)
+{
+ reload_config();
+ return 0;
+}
+
+static int mgcp_reload(int fd, int argc, char *argv[])
+{
+ static int deprecated = 0;
+ if (!deprecated && 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 0;
+}
+
+static int reload(void)
+{
+ mgcp_reload(0, 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(0, 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(0, 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/1.4.23-rc4/channels/chan_misdn.c b/1.4.23-rc4/channels/chan_misdn.c
new file mode 100644
index 000000000..c5965a1c1
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_misdn.c
@@ -0,0 +1,5775 @@
+/*
+ * 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 <stdio.h>
+#include <pthread.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.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/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.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/translate.h"
+#include "asterisk/config.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/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 initialize 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 (buffer overrun). */
+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);
+
+
+/* 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 couldn't 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 came 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 {
+ /*!
+ * \brief Logical port the channel call record is HOLDED on
+ * because the B channel is no longer associated.
+ */
+ int port;
+
+ /*!
+ * \brief Original B channel number the HOLDED call was using.
+ * \note Used only for debug display messages.
+ */
+ int channel;
+};
+
+/*!
+ * \brief Channel call record structure
+ */
+struct chan_list {
+ /*!
+ * \brief The "allowed_bearers" string read in from /etc/asterisk/misdn.conf
+ */
+ char allowed_bearers[BUFFERSIZE + 1];
+
+ /*!
+ * \brief State of the channel
+ */
+ enum misdn_chan_state state;
+
+ /*!
+ * \brief TRUE if a hangup needs to be queued
+ * \note This is a debug flag only used to catch calls to hangup_chan() that are already hungup.
+ */
+ int need_queue_hangup;
+
+ /*!
+ * \brief TRUE if a channel can be hung up by calling asterisk directly when done.
+ */
+ int need_hangup;
+
+ /*!
+ * \brief TRUE if we could send an AST_CONTROL_BUSY if needed.
+ */
+ int need_busy;
+
+ /*!
+ * \brief Who originally created this channel. ORG_AST or ORG_MISDN
+ */
+ int originator;
+
+ /*!
+ * \brief TRUE of we are not to respond immediately to a SETUP message. Check the dialplan first.
+ * \note The "noautorespond_on_setup" boolean read in from /etc/asterisk/misdn.conf
+ */
+ int noautorespond_on_setup;
+
+ int norxtone; /* Boolean assigned values but the value is not used. */
+
+ /*!
+ * \brief TRUE if we are not to generate tones (Playtones)
+ */
+ int notxtone;
+
+ /*!
+ * \brief TRUE if echo canceller is enabled. Value is toggled.
+ */
+ int toggle_ec;
+
+ /*!
+ * \brief TRUE if you want to send Tone Indications to an incoming
+ * ISDN channel on a TE Port.
+ * \note The "incoming_early_audio" boolean read in from /etc/asterisk/misdn.conf
+ */
+ int incoming_early_audio;
+
+ /*!
+ * \brief TRUE if DTMF digits are to be passed inband only.
+ * \note It is settable by the misdn_set_opt() application.
+ */
+ int ignore_dtmf;
+
+ /*!
+ * \brief Pipe file descriptor handles array.
+ * Read from pipe[0], write to pipe[1]
+ */
+ int pipe[2];
+
+ /*!
+ * \brief Read buffer for inbound audio from pipe[0]
+ */
+ char ast_rd_buf[4096];
+
+ /*!
+ * \brief Inbound audio frame returned by misdn_read().
+ */
+ struct ast_frame frame;
+
+ /*!
+ * \brief Fax detection option. (0:no 1:yes 2:yes+nojump)
+ * \note The "faxdetect" option string read in from /etc/asterisk/misdn.conf
+ * \note It is settable by the misdn_set_opt() application.
+ */
+ int faxdetect;
+
+ /*!
+ * \brief Number of seconds to detect a Fax machine when detection enabled.
+ * \note 0 disables the timeout.
+ * \note The "faxdetect_timeout" value read in from /etc/asterisk/misdn.conf
+ */
+ int faxdetect_timeout;
+
+ /*!
+ * \brief Starting time of fax detection with timeout when nonzero.
+ */
+ struct timeval faxdetect_tv;
+
+ /*!
+ * \brief TRUE if a fax has been detected.
+ */
+ int faxhandled;
+
+ /*!
+ * \brief TRUE if we will use the Asterisk DSP to detect DTMF/Fax
+ * \note The "astdtmf" boolean read in from /etc/asterisk/misdn.conf
+ */
+ int ast_dsp;
+
+ /*!
+ * \brief Jitterbuffer length
+ * \note The "jitterbuffer" value read in from /etc/asterisk/misdn.conf
+ */
+ int jb_len;
+
+ /*!
+ * \brief Jitterbuffer upper threshold
+ * \note The "jitterbuffer_upper_threshold" value read in from /etc/asterisk/misdn.conf
+ */
+ int jb_upper_threshold;
+
+ /*!
+ * \brief Allocated jitterbuffer controller
+ * \note misdn_jb_init() creates the jitterbuffer.
+ * \note Must use misdn_jb_destroy() to clean up.
+ */
+ struct misdn_jb *jb;
+
+ /*!
+ * \brief Allocated DSP controller
+ * \note ast_dsp_new() creates the DSP controller.
+ * \note Must use ast_dsp_free() to clean up.
+ */
+ struct ast_dsp *dsp;
+
+ /*!
+ * \brief Allocated audio frame sample translator
+ * \note ast_translator_build_path() creates the translator path.
+ * \note Must use ast_translator_free_path() to clean up.
+ */
+ struct ast_trans_pvt *trans;
+
+ /*!
+ * \brief Associated Asterisk channel structure.
+ */
+ struct ast_channel * ast;
+
+ //int dummy; /* Not used */
+
+ /*!
+ * \brief Associated B channel structure.
+ */
+ struct misdn_bchannel *bc;
+
+ /*!
+ * \brief HOLDED channel information
+ */
+ struct hold_info hold_info;
+
+ /*!
+ * \brief From associated B channel: Layer 3 process ID
+ * \note Used to find the HOLDED channel call record when retrieving a call.
+ */
+ unsigned int l3id;
+
+ /*!
+ * \brief From associated B channel: B Channel mISDN driver layer ID from mISDN_get_layerid()
+ * \note Used only for debug display messages.
+ */
+ int addr;
+
+ /*!
+ * \brief Incoming call dialplan context identifier.
+ * \note The "context" string read in from /etc/asterisk/misdn.conf
+ */
+ char context[AST_MAX_CONTEXT];
+
+ /*!
+ * \brief The configured music-on-hold class to use for this call.
+ * \note The "musicclass" string read in from /etc/asterisk/misdn.conf
+ */
+ char mohinterpret[MAX_MUSICCLASS];
+
+ //int zero_read_cnt; /* Not used */
+
+ /*!
+ * \brief Number of outgoing audio frames dropped since last debug gripe message.
+ */
+ int dropped_frame_cnt;
+
+ /*!
+ * \brief TRUE if we must do the ringback tones.
+ * \note The "far_alerting" boolean read in from /etc/asterisk/misdn.conf
+ */
+ int far_alerting;
+
+ /*!
+ * \brief TRUE if NT should disconnect an overlap dialing call when a timeout occurs.
+ * \note The "nttimeout" boolean read in from /etc/asterisk/misdn.conf
+ */
+ int nttimeout;
+
+ /*!
+ * \brief Other channel call record PID
+ * \note Value imported from Asterisk environment variable MISDN_PID
+ */
+ int other_pid;
+
+ /*!
+ * \brief Bridged other channel call record
+ * \note Pointer set when other_pid imported from Asterisk environment
+ * variable MISDN_PID by either side.
+ */
+ struct chan_list *other_ch;
+
+ /*!
+ * \brief Tone zone sound used for dialtone generation.
+ * \note Used as a boolean. Non-NULL to prod generation if enabled.
+ */
+ const struct tone_zone_sound *ts;
+
+ /*!
+ * \brief Enables overlap dialing for the set amount of seconds. (0 = Disabled)
+ * \note The "overlapdial" value read in from /etc/asterisk/misdn.conf
+ */
+ int overlap_dial;
+
+ /*!
+ * \brief Overlap dialing timeout Task ID. -1 if not running.
+ */
+ int overlap_dial_task;
+
+ /*!
+ * \brief overlap_tv access lock.
+ */
+ ast_mutex_t overlap_tv_lock;
+
+ /*!
+ * \brief Overlap timer start time. Timer restarted for every digit received.
+ */
+ struct timeval overlap_tv;
+
+ //struct chan_list *peer; /* Not used */
+
+ /*!
+ * \brief Next channel call record in the list.
+ */
+ struct chan_list *next;
+ //struct chan_list *prev; /* Not used */
+ //struct chan_list *first; /* Not used */
+};
+
+
+
+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)
+ free(r->group);
+ 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 = (struct robin_list *) calloc(1, sizeof(struct robin_list));
+ new->group = strndup(group, strlen(group));
+ new->port = 0;
+ new->channel = 0;
+ 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, ...)
+ __attribute__((format(printf, 3, 4)));
+
+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;
+
+/*!
+ * \brief Global channel call record list head.
+ */
+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
+
+
+
+/*************** 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 {
+ char *name; /*!< Bearer capability name string used in /etc/misdn.conf allowed_bearers */
+ char *display; /*!< Bearer capability displayable name */
+ int cap; /*!< SETUP message bearer capability field code value */
+ int deprecated; /*!< TRUE if this entry is deprecated. (Misspelled or bad name to use) */
+};
+
+/* *INDENT-OFF* */
+static const struct allowed_bearers allowed_bearers_array[]= {
+ /* Name, Displayable Name Bearer Capability, Deprecated */
+ { "speech", "Speech", INFO_CAPABILITY_SPEECH, 0 },
+ { "3_1khz", "3.1KHz Audio", INFO_CAPABILITY_AUDIO_3_1K, 0 },
+ { "digital_unrestricted", "Unrestricted Digital", INFO_CAPABILITY_DIGITAL_UNRESTRICTED, 0 },
+ { "digital_restricted", "Restricted Digital", INFO_CAPABILITY_DIGITAL_RESTRICTED, 0 },
+ { "digital_restriced", "Restricted Digital", INFO_CAPABILITY_DIGITAL_RESTRICTED, 1 }, /* Allow misspelling for backwards compatibility */
+ { "video", "Video", INFO_CAPABILITY_VIDEO, 0 }
+};
+/* *INDENT-ON* */
+
+static const char *bearer2str(int cap)
+{
+ unsigned index;
+
+ for (index = 0; index < ARRAY_LEN(allowed_bearers_array); ++index) {
+ if (allowed_bearers_array[index].cap == cap) {
+ return allowed_bearers_array[index].display;
+ }
+ } /* end for */
+
+ return "Unknown Bearer";
+}
+
+
+static void print_facility(struct FacParm *fac, struct misdn_bchannel *bc)
+{
+ switch (fac->Function) {
+ case Fac_CD:
+ chan_misdn_log(1,bc->port," --> calldeflect to: %s, presentable: %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:%s 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:%s\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;
+ default:
+ chan_misdn_log(1,bc->port," --> unknown facility\n");
+ break;
+ }
+}
+
+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 (!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;
+ }
+}
+
+/*************** 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 = AST_CAUSE_UNALLOCATED;
+ 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_log(LOG_DEBUG, "Unable to handle DTMF tone '%c' for '%s'\n", digit, chan->name);
+ }
+}
+
+/*** CLI HANDLING ***/
+static int misdn_set_debug(int fd, int argc, char *argv[])
+{
+ int level;
+
+ if (argc != 4 && argc != 5 && argc != 6 && argc != 7)
+ return RESULT_SHOWUSAGE;
+
+ level = atoi(argv[3]);
+
+ switch (argc) {
+ case 4:
+ case 5:
+ {
+ int i;
+ int only = 0;
+ if (argc == 5) {
+ if (strncasecmp(argv[4], "only", strlen(argv[4])))
+ return RESULT_SHOWUSAGE;
+ else
+ only = 1;
+ }
+
+ for (i = 0; i <= max_ports; i++) {
+ misdn_debug[i] = level;
+ misdn_debug_only[i] = only;
+ }
+ ast_cli(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(argv[4], "port", strlen(argv[4])))
+ return RESULT_SHOWUSAGE;
+ port = atoi(argv[5]);
+ if (port <= 0 || port > max_ports) {
+ switch (max_ports) {
+ case 0:
+ ast_cli(fd, "port number not valid! no ports available so you won't get lucky with any number here...\n");
+ break;
+ case 1:
+ ast_cli(fd, "port number not valid! only port 1 is available.\n");
+ break;
+ default:
+ ast_cli(fd, "port number not valid! only ports 1 to %d are available.\n", max_ports);
+ }
+ return 0;
+ }
+ if (argc == 7) {
+ if (strncasecmp(argv[6], "only", strlen(argv[6])))
+ return RESULT_SHOWUSAGE;
+ else
+ misdn_debug_only[port] = 1;
+ } else
+ misdn_debug_only[port] = 0;
+ misdn_debug[port] = level;
+ ast_cli(fd, "changing debug level to %d%s for port %d\n", misdn_debug[port], misdn_debug_only[port]?" (only)":"", port);
+ }
+ }
+ return 0;
+}
+
+static int misdn_set_crypt_debug(int fd, int argc, char *argv[])
+{
+ if (argc != 5) return RESULT_SHOWUSAGE;
+
+ return 0;
+}
+
+static int misdn_port_block(int fd, int argc, char *argv[])
+{
+ int port;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ port = atoi(argv[3]);
+
+ misdn_lib_port_block(port);
+
+ return 0;
+}
+
+static int misdn_port_unblock(int fd, int argc, char *argv[])
+{
+ int port;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ port = atoi(argv[3]);
+
+ misdn_lib_port_unblock(port);
+
+ return 0;
+}
+
+
+static int misdn_restart_port (int fd, int argc, char *argv[])
+{
+ int port;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ port = atoi(argv[3]);
+
+ misdn_lib_port_restart(port);
+
+ return 0;
+}
+
+static int misdn_restart_pid (int fd, int argc, char *argv[])
+{
+ int pid;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ pid = atoi(argv[3]);
+
+ misdn_lib_pid_restart(pid);
+
+ return 0;
+}
+
+static int misdn_port_up (int fd, int argc, char *argv[])
+{
+ int port;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ port = atoi(argv[3]);
+
+ misdn_lib_get_port_up(port);
+
+ return 0;
+}
+
+static int misdn_port_down (int fd, int argc, char *argv[])
+{
+ int port;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ port = atoi(argv[3]);
+
+ misdn_lib_get_port_down(port);
+
+ return 0;
+}
+
+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);
+}
+
+static int misdn_show_config (int fd, int argc, char *argv[])
+{
+ char buffer[BUFFERSIZE];
+ enum misdn_cfg_elements elem;
+ int linebreak;
+ int onlyport = -1;
+ int ok = 0;
+
+ if (argc >= 4) {
+ if (!strcmp(argv[3], "description")) {
+ if (argc == 5) {
+ enum misdn_cfg_elements elem = misdn_cfg_get_elem(argv[4]);
+ if (elem == MISDN_CFG_FIRST)
+ ast_cli(fd, "Unknown element: %s\n", argv[4]);
+ else
+ show_config_description(fd, elem);
+ return 0;
+ }
+ return RESULT_SHOWUSAGE;
+ }
+ if (!strcmp(argv[3], "descriptions")) {
+ if ((argc == 4) || ((argc == 5) && !strcmp(argv[4], "general"))) {
+ for (elem = MISDN_GEN_FIRST + 1; elem < MISDN_GEN_LAST; ++elem) {
+ show_config_description(fd, elem);
+ ast_cli(fd, "\n");
+ }
+ ok = 1;
+ }
+ if ((argc == 4) || ((argc == 5) && !strcmp(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(fd, elem);
+ ast_cli(fd, "\n");
+ }
+ ok = 1;
+ }
+ return ok ? 0 : RESULT_SHOWUSAGE;
+ }
+ if (!sscanf(argv[3], "%d", &onlyport) || onlyport < 0) {
+ ast_cli(fd, "Unknown option: %s\n", argv[3]);
+ return RESULT_SHOWUSAGE;
+ }
+ }
+
+ if (argc == 3 || onlyport == 0) {
+ ast_cli(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, BUFFERSIZE);
+ ast_cli(fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : "");
+ }
+ ast_cli(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(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, BUFFERSIZE);
+ ast_cli(fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : "");
+ }
+ ast_cli(fd, "\n");
+ }
+ }
+
+ if (onlyport > 0) {
+ if (misdn_cfg_is_port_valid(onlyport)) {
+ ast_cli(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, BUFFERSIZE);
+ ast_cli(fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : "");
+ }
+ ast_cli(fd, "\n");
+ } else {
+ ast_cli(fd, "Port %d is not active!\n", onlyport);
+ }
+ }
+
+ return 0;
+}
+
+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 couldn't 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 came from misdn */
+ {MISDN_HOLDED,"HOLDED"}, /* when DISCONNECT/RELEASE/REL_COMP came from misdn */
+ {MISDN_HOLD_DISCONNECT,"HOLD_DISCONNECT"}, /* when DISCONNECT/RELEASE/REL_COMP came 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, BUFFERSIZE);
+ misdn_cfg_get(0, MISDN_GEN_DEBUG, &cfg_debug, sizeof(int));
+
+ for (i = 0; i <= max_ports; i++) {
+ misdn_debug[i] = cfg_debug;
+ misdn_debug_only[i] = 0;
+ }
+}
+
+static int misdn_reload (int fd, int argc, char *argv[])
+{
+ ast_cli(fd, "Reloading mISDN configuration\n");
+ reload_config();
+ return 0;
+}
+
+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 int misdn_show_cls (int fd, int argc, char *argv[])
+{
+ struct chan_list *help;
+
+ help = cl_te;
+
+ ast_cli(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 (!ast) {
+ if (!bc) {
+ ast_cli(fd, "chan_list obj. with l3id:%x has no bc and no ast Leg\n", help->l3id);
+ continue;
+ }
+ ast_cli(fd, "bc with pid:%d has no Ast Leg\n", bc->pid);
+ continue;
+ }
+
+ if (misdn_debug[0] > 2)
+ ast_cli(fd, "Bc:%p Ast:%p\n", bc, ast);
+ if (bc) {
+ print_bc_info(fd, help, bc);
+ } else {
+ if (help->state == MISDN_HOLDED) {
+ ast_cli(fd, "ITS A HOLDED BC:\n");
+ ast_cli(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(fd, "* Channel in unknown STATE !!! Exten:%s, Callerid:%s\n", ast->exten, ast->cid.cid_num);
+ }
+ }
+ }
+
+ misdn_dump_chanlist();
+
+ return 0;
+}
+
+static int misdn_show_cl (int fd, int argc, char *argv[])
+{
+ struct chan_list *help;
+
+ if (argc != 4)
+ return RESULT_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,argv[3])) {
+ print_bc_info(fd, help, bc);
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+ast_mutex_t lock;
+int MAXTICS = 8;
+
+static int misdn_set_tics (int fd, int argc, char *argv[])
+{
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ MAXTICS = atoi(argv[3]);
+
+ return 0;
+}
+
+static int misdn_show_stacks (int fd, int argc, char *argv[])
+{
+ int port;
+
+ ast_cli(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(fd, " %s Debug:%d%s\n", buf, misdn_debug[port], misdn_debug_only[port] ? "(only)" : "");
+ }
+
+ return 0;
+}
+
+static int misdn_show_ports_stats (int fd, int argc, char *argv[])
+{
+ int port;
+
+ ast_cli(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(fd, "%d\t%d\t\t%d\n", port, misdn_in_calls[port], misdn_out_calls[port]);
+ }
+ ast_cli(fd, "\n");
+
+ return 0;
+}
+
+static int misdn_show_port (int fd, int argc, char *argv[])
+{
+ int port;
+ char buf[128];
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ port = atoi(argv[3]);
+
+ ast_cli(fd, "BEGIN STACK_LIST:\n");
+ get_show_stack_details(port, buf);
+ ast_cli(fd, " %s Debug:%d%s\n", buf, misdn_debug[port], misdn_debug_only[port] ? "(only)" : "");
+
+ return 0;
+}
+
+static int misdn_send_cd (int fd, int argc, char *argv[])
+{
+ char *channame;
+ char *nr;
+ struct chan_list *tmp;
+
+ if (argc != 5)
+ return RESULT_SHOWUSAGE;
+
+
+ {
+ channame = argv[3];
+ nr = argv[4];
+
+ ast_cli(fd, "Sending Calldeflection (%s) to %s\n", nr, channame);
+ tmp = get_chan_by_ast_name(channame);
+ if (!tmp) {
+ ast_cli(fd, "Sending CD with nr %s to %s failed: Channel does not exist.\n",nr, channame);
+ return 0;
+ }
+
+ if (strlen(nr) >= 15) {
+ ast_cli(fd, "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);
+ }
+
+ return 0;
+}
+
+static int misdn_send_restart(int fd, int argc, char *argv[])
+{
+ int port;
+ int channel;
+
+ if (argc < 4 || argc > 5)
+ return RESULT_SHOWUSAGE;
+
+ port = atoi(argv[3]);
+
+ if (argc == 5) {
+ channel = atoi(argv[4]);
+ misdn_lib_send_restart(port, channel);
+ } else {
+ misdn_lib_send_restart(port, -1);
+ }
+
+ return 0;
+}
+
+static int misdn_send_digit (int fd, int argc, char *argv[])
+{
+ char *channame;
+ char *msg;
+ struct chan_list *tmp;
+ int i, msglen;
+
+ if (argc != 5)
+ return RESULT_SHOWUSAGE;
+
+ channame = argv[3];
+ msg = argv[4];
+ msglen = strlen(msg);
+
+ ast_cli(fd, "Sending %s to %s\n", msg, channame);
+
+ tmp = get_chan_by_ast_name(channame);
+ if (!tmp) {
+ ast_cli(fd, "Sending %s to %s failed Channel does not exist\n", msg, channame);
+ return 0;
+ }
+#if 1
+ for (i = 0; i < msglen; i++) {
+ ast_cli(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 0;
+}
+
+static int misdn_toggle_echocancel (int fd, int argc, char *argv[])
+{
+ char *channame;
+ struct chan_list *tmp;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ channame = argv[3];
+
+ ast_cli(fd, "Toggling EchoCancel on %s\n", channame);
+
+ tmp = get_chan_by_ast_name(channame);
+ if (!tmp) {
+ ast_cli(fd, "Toggling EchoCancel %s failed Channel does not exist\n", channame);
+ return 0;
+ }
+
+ 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 0;
+}
+
+static int misdn_send_display (int fd, int argc, char *argv[])
+{
+ char *channame;
+ char *msg;
+ struct chan_list *tmp;
+
+ if (argc != 5)
+ return RESULT_SHOWUSAGE;
+
+ channame = argv[3];
+ msg = argv[4];
+
+ ast_cli(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(fd, "No such channel %s\n", channame);
+ return RESULT_FAILURE;
+ }
+
+ return RESULT_SUCCESS;
+}
+
+static char *complete_ch_helper(const char *line, const char *word, int pos, int state, int rpos)
+{
+ struct ast_channel *c;
+ int which=0;
+ char *ret;
+ if (pos != rpos)
+ return NULL;
+ c = ast_channel_walk_locked(NULL);
+ while(c) {
+ if (!strncasecmp(word, c->name, strlen(word))) {
+ if (++which > state)
+ break;
+ }
+ ast_mutex_unlock(&c->lock);
+ c = ast_channel_walk_locked(c);
+ }
+ if (c) {
+ ret = strdup(c->name);
+ ast_mutex_unlock(&c->lock);
+ } else
+ ret = NULL;
+ return ret;
+}
+
+static char *complete_ch(const char *line, const char *word, int pos, int state)
+{
+ return complete_ch_helper(line, word, pos, state, 3);
+}
+
+static char *complete_debug_port (const char *line, const char *word, int pos, int state)
+{
+ if (state)
+ return NULL;
+
+ switch (pos) {
+ case 4:
+ if (*word == 'p')
+ return strdup("port");
+ else if (*word == 'o')
+ return strdup("only");
+ break;
+ case 6:
+ if (*word == 'o')
+ return strdup("only");
+ break;
+ }
+ return NULL;
+}
+
+static char *complete_show_config (const char *line, const char *word, int pos, int state)
+{
+ char buffer[BUFFERSIZE];
+ enum misdn_cfg_elements elem;
+ int wordlen = strlen(word);
+ int which = 0;
+ int port = 0;
+
+ switch (pos) {
+ case 3:
+ if ((!strncmp(word, "description", wordlen)) && (++which > state))
+ return strdup("description");
+ if ((!strncmp(word, "descriptions", wordlen)) && (++which > state))
+ return strdup("descriptions");
+ if ((!strncmp(word, "0", wordlen)) && (++which > state))
+ return strdup("0");
+ while ((port = misdn_cfg_get_next_port(port)) != -1) {
+ snprintf(buffer, sizeof(buffer), "%d", port);
+ if ((!strncmp(word, buffer, wordlen)) && (++which > state)) {
+ return strdup(buffer);
+ }
+ }
+ break;
+ case 4:
+ if (strstr(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, BUFFERSIZE);
+ if (!wordlen || !strncmp(word, buffer, wordlen)) {
+ if (++which > state)
+ return strdup(buffer);
+ }
+ }
+ } else if (strstr(line, "descriptions ")) {
+ if ((!wordlen || !strncmp(word, "general", wordlen)) && (++which > state))
+ return strdup("general");
+ if ((!wordlen || !strncmp(word, "ports", wordlen)) && (++which > state))
+ return strdup("ports");
+ }
+ break;
+ }
+ return NULL;
+}
+
+static struct ast_cli_entry chan_misdn_clis[] = {
+ { {"misdn","send","calldeflect", NULL}, misdn_send_cd, "Sends CallDeflection to mISDN Channel",
+ "Usage: misdn send calldeflect <channel> \"<nr>\" \n", complete_ch },
+ { {"misdn","send","digit", NULL}, misdn_send_digit, "Sends DTMF Digit to mISDN Channel",
+ "Usage: misdn send digit <channel> \"<msg>\" \n"
+ " Send <digit> to <channel> as DTMF Tone\n"
+ " when channel is a mISDN channel\n", complete_ch },
+ { {"misdn","toggle","echocancel", NULL}, misdn_toggle_echocancel, "Toggles EchoCancel on mISDN Channel",
+ "Usage: misdn toggle echocancel <channel>\n", complete_ch },
+ { {"misdn","send","display", NULL}, misdn_send_display, "Sends Text to mISDN Channel",
+ "Usage: misdn send display <channel> \"<msg>\" \n"
+ " Send <msg> to <channel> as Display Message\n"
+ " when channel is a mISDN channel\n", complete_ch },
+ { {"misdn","show","config", NULL}, misdn_show_config, "Shows internal mISDN config, read from cfg-file",
+ "Usage: misdn show config [<port> | description <config element> | descriptions [general|ports]]\n"
+ " Use 0 for <port> to only print the general config.\n", complete_show_config },
+ { {"misdn","reload", NULL}, misdn_reload, "Reloads internal mISDN config, read from cfg-file",
+ "Usage: misdn reload\n" },
+ { {"misdn","set","tics", NULL}, misdn_set_tics, "",
+ "\n" },
+ { {"misdn","show","channels", NULL}, misdn_show_cls, "Shows internal mISDN chan_list",
+ "Usage: misdn show channels\n" },
+ { {"misdn","show","channel", NULL}, misdn_show_cl, "Shows internal mISDN chan_list",
+ "Usage: misdn show channels\n", complete_ch },
+ { {"misdn","port","block", NULL}, misdn_port_block, "Blocks the given port",
+ "Usage: misdn port block\n" },
+ { {"misdn","port","unblock", NULL}, misdn_port_unblock, "Unblocks the given port",
+ "Usage: misdn port unblock\n" },
+ { {"misdn","restart","port", NULL}, misdn_restart_port, "Restarts the given port",
+ "Usage: misdn restart port\n" },
+ { {"misdn","restart","pid", NULL}, misdn_restart_pid, "Restarts the given pid",
+ "Usage: misdn restart pid\n" },
+ { {"misdn","send","restart", NULL}, misdn_send_restart,
+ "Sends a restart for every bchannel on the given port",
+ "Usage: misdn send restart <port>\n"},
+ { {"misdn","port","up", NULL}, misdn_port_up, "Tries to establish L1 on the given port",
+ "Usage: misdn port up <port>\n" },
+ { {"misdn","port","down", NULL}, misdn_port_down, "Tries to deactivate the L1 on the given port",
+ "Usage: misdn port down <port>\n" },
+ { {"misdn","show","stacks", NULL}, misdn_show_stacks, "Shows internal mISDN stack_list",
+ "Usage: misdn show stacks\n" },
+ { {"misdn","show","ports","stats", NULL}, misdn_show_ports_stats, "Shows chan_misdns call statistics per port",
+ "Usage: misdn show port stats\n" },
+ { {"misdn","show","port", NULL}, misdn_show_port, "Shows detailed information for given port",
+ "Usage: misdn show port <port>\n" },
+ { {"misdn","set","debug", NULL}, misdn_set_debug, "Sets Debuglevel of chan_misdn",
+ "Usage: misdn set debug <level> [only] | [port <port> [only]]\n", complete_debug_port },
+ { {"misdn","set","crypt","debug", NULL}, misdn_set_crypt_debug, "Sets CryptDebuglevel of chan_misdn, at the moment, level={1,2}",
+ "Usage: misdn set crypt debug <level>\n" }
+};
+
+/*! \brief Updates caller ID information from config */
+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 < 0 || 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 (1)\n");
+ break;
+ case AST_PRES_UNAVAILABLE:
+ bc->pres = 2;
+ chan_misdn_log(2, port, " --> PRES: Unavailable (2)\n");
+ break;
+ default:
+ bc->pres = 0;
+ chan_misdn_log(2, port, " --> PRES: Allowed (0)\n");
+ break;
+ }
+
+ switch (ast->cid.cid_pres & 0x3) {
+ default:
+ case AST_PRES_USER_NUMBER_UNSCREENED:
+ bc->screen = 0;
+ chan_misdn_log(2, port, " --> SCREEN: Unscreened (0)\n");
+ break;
+ case AST_PRES_USER_NUMBER_PASSED_SCREEN:
+ bc->screen = 1;
+ chan_misdn_log(2, port, " --> SCREEN: Passed Screen (1)\n");
+ break;
+ case AST_PRES_USER_NUMBER_FAILED_SCREEN:
+ bc->screen = 2;
+ chan_misdn_log(2, port, " --> SCREEN: Failed Screen (2)\n");
+ break;
+ case AST_PRES_NETWORK_NUMBER:
+ bc->screen = 3;
+ chan_misdn_log(2, port, " --> SCREEN: Network Nr. (3)\n");
+ break;
+ }
+ } 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(int));
+ 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(int));
+
+ 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;
+ int hdlc = 0;
+ char lang[BUFFERSIZE + 1];
+ char faxdetect[BUFFERSIZE + 1];
+ char buf[256];
+ char buf2[256];
+ ast_group_t pg;
+ ast_group_t 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, BUFFERSIZE);
+ ast_string_field_set(ast, language, lang);
+
+ misdn_cfg_get(port, MISDN_CFG_MUSICCLASS, ch->mohinterpret, sizeof(ch->mohinterpret));
+
+ misdn_cfg_get(port, MISDN_CFG_TXGAIN, &bc->txgain, sizeof(int));
+ misdn_cfg_get(port, MISDN_CFG_RXGAIN, &bc->rxgain, sizeof(int));
+
+ misdn_cfg_get(port, MISDN_CFG_INCOMING_EARLY_AUDIO, &ch->incoming_early_audio, sizeof(int));
+
+ misdn_cfg_get(port, MISDN_CFG_SENDDTMF, &bc->send_dtmf, sizeof(int));
+
+ 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(int));
+ misdn_cfg_get(port, MISDN_CFG_NTTIMEOUT, &ch->nttimeout, sizeof(int));
+
+ misdn_cfg_get(port, MISDN_CFG_NOAUTORESPOND_ON_SETUP, &ch->noautorespond_on_setup, sizeof(int));
+
+ misdn_cfg_get(port, MISDN_CFG_FAR_ALERTING, &ch->far_alerting, sizeof(int));
+
+ misdn_cfg_get(port, MISDN_CFG_ALLOWED_BEARERS, &ch->allowed_bearers, BUFFERSIZE);
+
+ misdn_cfg_get(port, MISDN_CFG_FAXDETECT, faxdetect, BUFFERSIZE);
+
+ 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;
+ }
+
+ }
+ /*Initialize new Jitterbuffer*/
+ misdn_cfg_get(port, MISDN_CFG_JITTERBUFFER, &ch->jb_len, sizeof(int));
+ misdn_cfg_get(port, MISDN_CFG_JITTERBUFFER_UPPER_THRESHOLD, &ch->jb_upper_threshold, sizeof(int));
+
+ 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
+
+ {
+ int eb3;
+
+ misdn_cfg_get( bc->port, MISDN_CFG_EARLY_BCONNECT, &eb3, sizeof(int));
+ bc->early_bconnect=eb3;
+ }
+
+ 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];
+
+ /* ORIGINATOR Asterisk (outgoing call) */
+
+ misdn_cfg_get(port, MISDN_CFG_TE_CHOOSE_CHANNEL, &(bc->te_choose_channel), sizeof(int));
+
+ 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, BUFFERSIZE);
+ 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(int));
+ misdn_cfg_get(port, MISDN_CFG_LOCALDIALPLAN, &bc->onumplan, sizeof(int));
+ misdn_cfg_get(port, MISDN_CFG_CPNDIALPLAN, &bc->cpnnumplan, sizeof(int));
+ 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 (incoming call) */
+ 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(int));
+ debug_numplan(port, bc->cpnnumplan, "CTON");
+
+ switch (bc->onumplan) {
+ case NUMPLAN_INTERNATIONAL:
+ misdn_cfg_get(bc->port, MISDN_CFG_INTERNATPREFIX, prefix, BUFFERSIZE);
+ break;
+
+ case NUMPLAN_NATIONAL:
+ misdn_cfg_get(bc->port, MISDN_CFG_NATPREFIX, prefix, BUFFERSIZE);
+ 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, BUFFERSIZE);
+ break;
+ case NUMPLAN_NATIONAL:
+ misdn_cfg_get(bc->port, MISDN_CFG_NATPREFIX, prefix, BUFFERSIZE);
+ 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)
+ free(ast->cid.cid_rdnis);
+ ast->cid.cid_rdnis = 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;
+ struct misdn_bchannel *newbc;
+ char *opts, *ext;
+ char *dest_cp;
+
+ 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;
+ }
+
+ ch = MISDN_ASTERISK_TECH_PVT(ast);
+ 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;
+ }
+
+ /*
+ * dest is ---v
+ * Dial(mISDN/g:group_name[/extension[/options]])
+ * Dial(mISDN/port[:preselected_channel][/extension[/options]])
+ *
+ * The dial extension could be empty if you are using MISDN_KEYPAD
+ * to control ISDN provider features.
+ */
+ dest_cp = ast_strdupa(dest);
+ strsep(&dest_cp, "/");/* Discard port/group token */
+ ext = strsep(&dest_cp, "/");
+ if (!ext) {
+ ext = "";
+ }
+ opts = dest_cp;
+
+ 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 there is 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;
+
+ 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 without having bchannel Object\n");
+ return -1;
+ }
+
+ switch (p->state ) {
+ case MISDN_CALLING:
+ {
+ int l;
+ char buf[8];
+ buf[0]=digit;
+ buf[1]=0;
+
+ l = sizeof(bc->infos_pending);
+ strncat(bc->infos_pending, buf, l - strlen(bc->infos_pending) - 1);
+ }
+ break;
+ case MISDN_CALLING_ACKNOWLEDGE:
+ {
+ bc->info_dad[0]=digit;
+ bc->info_dad[1]=0;
+
+ {
+ int l = sizeof(bc->dad);
+ strncat(bc->dad, bc->info_dad, l - strlen(bc->dad) - 1);
+ }
+ {
+ int l = sizeof(p->ast->exten);
+ strncpy(p->ast->exten, bc->dad, l);
+ p->ast->exten[l-1] = 0;
+ }
+
+ 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 ignoring 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_RING);
+
+ 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:
+ ast_moh_start(ast, data, p->mohinterpret);
+ chan_misdn_log(1, p->bc->port, " --> *\tHOLD pid:%d\n", p->bc ? p->bc->pid : -1);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_moh_stop(ast);
+ 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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]);
+ 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:
+ /* 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);
+
+ p->state = MISDN_CLEANING;
+ if (bc->need_release_complete)
+ misdn_lib_send_event( bc, EVENT_RELEASE_COMPLETE);
+ break;
+ case MISDN_HOLDED:
+ case MISDN_DIALING:
+ start_bc_tones(p);
+ hanguptone_indicate(p);
+
+ 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 Disconnect */
+ if (p->bc->nt) {
+ start_bc_tones(p);
+ hanguptone_indicate(p);
+ p->bc->progress_indicator = INFO_PI_INBAND_AVAILABLE;
+ }
+ 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_log(LOG_DEBUG, "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)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "Already in a fax extension, not redirecting\n");
+ }
+ break;
+ case 2:
+ ast_verbose(VERBOSE_PREFIX_3 "Not redirecting %s to fax extension, nojump is set.\n", ast->name);
+ break;
+ }
+ } else {
+ ast_log(LOG_DEBUG, "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 notxtone\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;
+
+ printf("write2mISDN %p %d bytes: ", p, frame->samples);
+
+ for (i = 0; i < max; i++)
+ printf("%2.2x ", ((char*) frame->data)[i]);
+ printf ("\n");
+ }
+#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) dropping: %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) dropping: %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 to MISDN\n", frame->samples);
+ if ( !ch->bc->nojitter && misdn_cap_is_speech(ch->bc->capability) ) {
+ /* Buffered Transmit (triggered 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(int));
+ misdn_cfg_get(ch2->bc->port, MISDN_CFG_BRIDGING, &p2_b, sizeof(int));
+
+ 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(int));
+ 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);
+ }
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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 Control 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 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 = calloc(1, sizeof(struct chan_list));
+ 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 dial_str[128];
+ char *buf2 = ast_strdupa(data);
+ char *ext;
+ char *port_str;
+ char *p = NULL;
+ int channel = 0;
+ int port = 0;
+ struct misdn_bchannel *newbc = NULL;
+ int dec = 0;
+
+ struct chan_list *cl = init_chan_list(ORG_AST);
+
+ snprintf(dial_str, sizeof(dial_str), "%s/%s", misdn_type, (char *) data);
+
+ /*
+ * data is ---v
+ * Dial(mISDN/g:group_name[/extension[/options]])
+ * Dial(mISDN/port[:preselected_channel][/extension[/options]])
+ *
+ * The dial extension could be empty if you are using MISDN_KEYPAD
+ * to control ISDN provider features.
+ */
+ port_str = strsep(&buf2, "/");
+ if (!ast_strlen_zero(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 : Dial(%s) WITHOUT Port or Group, check extensions.conf\n", dial_str);
+ return NULL;
+ }
+
+ ext = strsep(&buf2, "/");
+ if (!ext) {
+ ext = "";
+ }
+
+ if (misdn_cfg_is_group_method(group, METHOD_STANDARD_DEC)) {
+ chan_misdn_log(4, port, " --> STARTING STANDARD DEC...\n");
+ dec = 1;
+ }
+
+ if (!ast_strlen_zero(group)) {
+ char cfg_group[BUFFERSIZE + 1];
+ struct robin_list *rr = NULL;
+
+ /* Group dial */
+
+ 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 port_start = 0;
+ int port_bak = rr->port;
+ int chan_bak = rr->channel;
+
+ if (!rr->port)
+ rr->port = misdn_cfg_get_next_port_spin(rr->port);
+
+ for (; rr->port > 0; rr->port = misdn_cfg_get_next_port_spin(rr->port)) {
+ int port_up;
+ int check;
+ int max_chan;
+ int last_chance = 0;
+
+ misdn_cfg_get(rr->port, MISDN_CFG_GROUPNAME, cfg_group, BUFFERSIZE);
+ if (strcasecmp(cfg_group, group))
+ continue;
+
+ misdn_cfg_get(rr->port, MISDN_CFG_PMP_L1_CHECK, &check, sizeof(int));
+ port_up = misdn_lib_port_up(rr->port, check);
+
+ if (check && !port_up)
+ chan_misdn_log(1, rr->port, "L1 is not Up on this Port\n");
+
+ if (check && port_up < 0)
+ ast_log(LOG_WARNING,"This port (%d) is blocked\n", rr->port);
+
+ if ((port_start == rr->port) && (port_up <= 0))
+ break;
+
+ if (!port_start)
+ port_start = rr->port;
+
+ if (port_up <= 0)
+ continue;
+
+ max_chan = misdn_lib_get_maxchans(rr->port);
+
+ for (++rr->channel; !last_chance && rr->channel <= max_chan; ++rr->channel) {
+ if (rr->port == port_bak && rr->channel == chan_bak)
+ last_chance = 1;
+
+ chan_misdn_log(1, 0, "trying port:%d channel:%d\n", rr->port, rr->channel);
+ newbc = misdn_lib_get_free_bc(rr->port, rr->channel, 0, 0);
+ if (newbc) {
+ chan_misdn_log(4, rr->port, " Success! Found port:%d channel:%d\n", newbc->port, newbc->channel);
+ if (port_up)
+ chan_misdn_log(4, rr->port, "portup:%d\n", port_up);
+ port = rr->port;
+ break;
+ }
+ }
+
+ if (newbc || last_chance)
+ break;
+
+ rr->channel = 0;
+ }
+ if (!newbc) {
+ rr->port = port_bak;
+ rr->channel = chan_bak;
+ }
+ } 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, BUFFERSIZE);
+
+ 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(int));
+ 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(int));
+
+ 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)
+ perror("Pipe failed\n");
+ tmp->fds[0] = chlist->pipe[0];
+
+ if (state == AST_STATE_RING)
+ tmp->rings = 1;
+ else
+ tmp->rings = 0;
+
+ } 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 **/
+
+
+static 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);
+
+ /*releasing 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);
+
+ 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, "TRANSFERRING %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[256]="";
+ char *p = predial;
+
+ struct ast_frame fr;
+
+ strncpy(predial, ast->exten, sizeof(predial) -1 );
+
+ 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);
+
+ strcpy(ast->exten, "s");
+
+ 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(p) ) {
+ fr.frametype = AST_FRAME_DTMF;
+ fr.subclass = *p;
+ 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);
+ }
+ p++;
+ }
+}
+
+
+
+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 AST_CAUSE_UNALLOCATED:
+ case AST_CAUSE_NO_ROUTE_TRANSIT_NET:
+ case AST_CAUSE_NO_ROUTE_DESTINATION:
+ case 4: /* Send special information tone */
+ case AST_CAUSE_NUMBER_CHANGED:
+ case AST_CAUSE_DESTINATION_OUT_OF_ORDER:
+ /* Congestion Cases */
+ /*
+ * 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 AST_CAUSE_CALL_REJECTED:
+ case AST_CAUSE_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;
+ }
+}
+
+
+/*! \brief Import parameters from the dialplan environment variables */
+void import_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch)
+{
+ const char *tmp;
+
+ 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));
+ }
+}
+
+/*! \brief Export parameters to the dialplan environment variables */
+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 && (bc->uulen < sizeof(bc->uu))) {
+ bc->uu[bc->uulen] = 0;
+ 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)
+{
+ int msn_valid;
+ 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(int));
+ 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;
+
+ memset(&fr, 0, sizeof(fr));
+ 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, " --> Ignoring 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) - strlen(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).\n"
+ "\tMaybe 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;
+
+ 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;
+
+ memset(&fr, 0, sizeof(fr));
+ 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(int));
+ if (ch->state != MISDN_CONNECTED ) {
+ if (digits) {
+ strncat(bc->dad, bc->info_dad, sizeof(bc->dad) - strlen(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);
+ 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 */
+ }
+ }
+
+ msn_valid = misdn_cfg_is_msn_valid(bc->port, bc->dad);
+ 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 : AST_CAUSE_NORMAL_CLEARING;
+ 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: Unavailable (2)\n");
+ break;
+ default:
+ pres = AST_PRES_ALLOWED;
+ chan_misdn_log(2, bc->port, " --> PRES: Allowed (%d)\n", bc->pres);
+ break;
+ }
+
+ switch (bc->screen) {
+ default:
+ case 0:
+ screen = AST_PRES_USER_NUMBER_UNSCREENED;
+ chan_misdn_log(2, bc->port, " --> SCREEN: Unscreened (%d)\n", bc->screen);
+ 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;
+ }
+
+ 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 < ARRAY_LEN(allowed_bearers_array); ++i) {
+ if (allowed_bearers_array[i].cap == bc->capability) {
+ if (strstr(ch->allowed_bearers, allowed_bearers_array[i].name)) {
+ /* The bearer capability is allowed */
+ if (allowed_bearers_array[i].deprecated) {
+ chan_misdn_log(0, bc->port, "%s in allowed_bearers list is deprecated\n",
+ allowed_bearers_array[i].name);
+ }
+ break;
+ }
+ }
+ } /* end for */
+ if (i == ARRAY_LEN(allowed_bearers_array)) {
+ /* We did not find the bearer capability */
+ chan_misdn_log(0, bc->port, "Bearer capability not allowed: %s(%d)\n",
+ bearer2str(bc->capability), bc->capability);
+ 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).\n"
+ "\tMaybe 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 digits
+ * 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 (bc->channel)
+ update_name(ch->ast, bc->port, bc->channel);
+
+ 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:
+ {
+ if (bc->channel)
+ update_name(ch->ast, bc->port, bc->channel);
+
+ 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", tone_len);
+
+ 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 (!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 to 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 = INFO_PI_INBAND_AVAILABLE;
+ 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 = INFO_PI_INBAND_AVAILABLE;
+ 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 ignoring, the stack should clean it for us\n");
+ break;
+
+ default:
+ misdn_lib_send_event(bc,EVENT_RELEASE_COMPLETE);
+ }
+ }
+ break;
+
+
+ /****************************/
+ /** Supplementary 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;
+
+ misdn_cfg_get(bc->port, MISDN_CFG_HOLD_ALLOWED, &hold_allowed, sizeof(int));
+
+ if (!hold_allowed) {
+
+ chan_misdn_log(-1, bc->port, "Hold not allowed this port.\n");
+ misdn_lib_send_event(bc, EVENT_HOLD_REJECT);
+ break;
+ }
+
+ bridged = ast_bridged_channel(ch->ast);
+ 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:
+ if (!ch) {
+ /* This may come from a call we don't know nothing about, so we ignore it. */
+ chan_misdn_log(-1, bc->port, "Got EVENT_FACILITY but we don't have a ch!\n");
+ break;
+ }
+
+ print_facility(&(bc->fac_in), bc);
+
+ switch (bc->fac_in.Function) {
+ case Fac_CD:
+ {
+ 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:
+ {
+ bc->AOCDtype = Fac_AOCDCurrency;
+ memcpy(&(bc->AOCD.currency), &(bc->fac_in.u.AOCDcur), sizeof(struct FacAOCDCurrency));
+ export_aoc_vars(ch->originator, ch->ast, bc);
+ }
+ break;
+ case Fac_AOCDChargingUnit:
+ {
+ bc->AOCDtype = Fac_AOCDChargingUnit;
+ memcpy(&(bc->AOCD.chargingUnit), &(bc->fac_in.u.AOCDchu), sizeof(struct FacAOCDChargingUnit));
+ export_aoc_vars(ch->originator, ch->ast, bc);
+ }
+ break;
+ default:
+ chan_misdn_log(0, bc->port," --> not yet handled: facility type:%d\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)
+ free(misdn_debug);
+ if (misdn_debug_only)
+ free(misdn_debug_only);
+ 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)) {
+ ast_log(LOG_ERROR, "Unable to initialize misdn_config.\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ g_config_initialized = 1;
+
+ misdn_debug = (int *) 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 = (int *) 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(int));
+ for (i = 1; i <= max_ports; i++) {
+ misdn_debug[i] = misdn_debug[0];
+ misdn_ports[i] = i;
+ }
+ *misdn_ports = 0;
+ misdn_debug_only = (int *) calloc(max_ports + 1, sizeof(int));
+
+ misdn_cfg_get(0, MISDN_GEN_TRACEFILE, tempbuf, BUFFERSIZE);
+ if (!ast_strlen_zero(tempbuf))
+ tracing = 1;
+
+ misdn_in_calls = (int *) malloc(sizeof(int) * (max_ports + 1));
+ misdn_out_calls = (int *) 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(int));
+ misdn_cfg_get(0, MISDN_GEN_NTDEBUGFILE, &ntfile, BUFFERSIZE);
+ misdn_lib_nt_debug_init(ntflags, ntfile);
+
+ misdn_cfg_get( 0, MISDN_GEN_NTKEEPCALLS, &ntkc, sizeof(int));
+ misdn_lib_nt_keepcalls(ntkc);
+
+ if (ast_channel_register(&misdn_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class %s\n", misdn_type);
+ unload_module();
+ return -1;
+ }
+
+ 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"
+ " a - Have Asterisk detect DTMF tones on called channel\n"
+ " c - Make crypted outgoing call, optarg is keyindex\n"
+ " d - Send display text to called phone, text is the optarg\n"
+ " e - Perform echo cancelation on this channel,\n"
+ " takes taps as optarg (32,64,128,256)\n"
+ " e! - Disable echo cancelation on this channel\n"
+ " f - Enable fax detection\n"
+ " h - Make digital outgoing call\n"
+ " h1 - Make HDLC mode digital outgoing call\n"
+ " i - Ignore detected DTMF tones, don't signal them to Asterisk,\n"
+ " they will be transported inband.\n"
+ " jb - Set jitter buffer length, optarg is length\n"
+ " jt - Set jitter buffer upper threshold, optarg is threshold\n"
+ " jn - Disable jitter buffer\n"
+ " n - Disable mISDN DSP on channel.\n"
+ " Disables: echo cancel, DTMF detection, and volume control.\n"
+ " p - Caller ID presentation,\n"
+ " optarg is either 'allowed' or 'restricted'\n"
+ " s - Send Non-inband DTMF as inband\n"
+ " vr - Rx gain control, optarg is gain\n"
+ " vt - Tx gain control, optarg is gain\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, BUFFERSIZE);
+
+ /* 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 *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;
+ }
+
+ tok = strtok_r((char*) data, "|", &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 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;
+ }
+
+ AST_STANDARD_APP_ARGS(args, data);
+
+ 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, BUFFERSIZE);
+
+ 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);
+ sleep(timeout);
+ }
+
+ 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;
+ 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;
+ }
+
+ for (tok = strtok_r((char*) data, ":", &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, "restricted")) {
+ ch->bc->pres = 1;
+ } else if (strstr(tok, "not_screened")) {
+ chan_misdn_log(0, ch->bc->port, "SETOPT: callerpres: not_screened is deprecated\n");
+ 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 initialize the elements*/
+struct misdn_jb *misdn_jb_init(int size, int upper_threshold)
+{
+ int i;
+ struct misdn_jb *jb;
+
+ jb = malloc(sizeof(struct misdn_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 = malloc(size * sizeof(char));
+ if (!jb->samples) {
+ free(jb);
+ chan_misdn_log(-1, 0, "No free Mem for jb->samples\n");
+ return NULL;
+ }
+
+ jb->ok = malloc(size * sizeof(char));
+ if (!jb->ok) {
+ free(jb->samples);
+ 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);
+
+ free(jb->ok);
+ free(jb->samples);
+ free(jb);
+}
+
+/* fills the jitterbuffer with len data returns < 0 if there was an
+ error (buffer overflow). */
+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 | Buffer status:%d p:%p\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 | Buffer status:%d p:%p\n", len, jb->state_buffer, jb);
+
+ jb->rp = rp;
+ } else
+ chan_misdn_log(9, 0, "misdn_jb_empty: Wait...requested:%d p:%p\n", len, jb);
+
+ ast_mutex_unlock(&jb->mutexjb);
+
+ return read;
+}
+
+
+
+
+/*******************************************************/
+/*************** JITTERBUFFER END *********************/
+/*******************************************************/
+
+
+
+
+static 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, "%s", 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) ) {
+ time_t tm = time(NULL);
+ char *tmp = ctime(&tm), *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/1.4.23-rc4/channels/chan_nbs.c b/1.4.23-rc4/channels/chan_nbs.c
new file mode 100644
index 000000000..b231127ce
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_nbs.c
@@ -0,0 +1,302 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.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/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.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 */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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);
+ 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 = malloc(sizeof(struct nbs_pvt));
+ if (p) {
+ memset(p, 0, sizeof(struct nbs_pvt));
+ 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);
+ 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;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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;
+ tmp->fds[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/1.4.23-rc4/channels/chan_oss.c b/1.4.23-rc4/channels/chan_oss.c
new file mode 100644
index 000000000..ac62f2005
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_oss.c
@@ -0,0 +1,1899 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, 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.
+ */
+
+/*! \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 <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>
+
+#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/logger.h"
+#include "asterisk/callerid.h"
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/options.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"
+
+/* ringtones we use */
+#include "busy_tone.h"
+#include "ring_tone.h"
+#include "ring10.h"
+#include "answer.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.
+
+ */
+
+/*
+ * Helper macros to parse config arguments. They will go in a common
+ * header file if their usage is globally accepted. In the meantime,
+ * we define them here. Typical usage is as below.
+ * Remember to open a block right before M_START (as it declares
+ * some variables) and use the M_* macros WITHOUT A SEMICOLON:
+ *
+ * {
+ * M_START(v->name, v->value)
+ *
+ * M_BOOL("dothis", x->flag1)
+ * M_STR("name", x->somestring)
+ * M_F("bar", some_c_code)
+ * M_END(some_final_statement)
+ * ... other code in the block
+ * }
+ *
+ * XXX NOTE these macros should NOT be replicated in other parts of asterisk.
+ * Likely we will come up with a better way of doing config file parsing.
+ */
+#define M_START(var, val) \
+ char *__s = var; char *__val = val;
+#define M_END(x) x;
+#define M_F(tag, f) if (!strcasecmp((__s), tag)) { f; } else
+#define M_BOOL(tag, dst) M_F(tag, (dst) = ast_true(__val) )
+#define M_UINT(tag, dst) M_F(tag, (dst) = strtoul(__val, NULL, 0) )
+#define M_STR(tag, dst) M_F(tag, ast_copy_string(dst, __val, sizeof(dst)))
+
+/*
+ * 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;
+
+/*
+ * Each sound is made of 'datalen' samples of sound, repeated as needed to
+ * generate 'samplen' samples of data, then followed by 'silencelen' samples
+ * of silence. The loop is repeated if 'repeat' is set.
+ */
+struct sound {
+ int ind;
+ char *desc;
+ short *data;
+ int datalen;
+ int samplen;
+ int silencelen;
+ int repeat;
+};
+
+static struct sound sounds[] = {
+ { AST_CONTROL_RINGING, "RINGING", ringtone, sizeof(ringtone)/2, 16000, 32000, 1 },
+ { AST_CONTROL_BUSY, "BUSY", busy, sizeof(busy)/2, 4000, 4000, 1 },
+ { AST_CONTROL_CONGESTION, "CONGESTION", busy, sizeof(busy)/2, 2000, 2000, 1 },
+ { AST_CONTROL_RING, "RING10", ring10, sizeof(ring10)/2, 16000, 32000, 1 },
+ { AST_CONTROL_ANSWER, "ANSWER", answer, sizeof(answer)/2, 2200, 0, 0 },
+ { -1, NULL, 0, 0, 0, 0 }, /* end marker */
+};
+
+
+/*
+ * 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;
+ /*
+ * cursound indicates which in struct sound we play. -1 means nothing,
+ * any other value is a valid sound, in which case sampsent indicates
+ * the next sample to send in [0..samplen + silencelen]
+ * nosound is set to disable the audio data from the channel
+ * (so we can play the tones etc.).
+ */
+ int sndcmd[2]; /* Sound command pipe */
+ int cursound; /* index of sound to send */
+ int sampsent; /* # of sound samples sent */
+ int nosound; /* set to block audio from the PBX */
+
+ 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;
+ 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 */
+};
+
+static struct chan_oss_pvt oss_default = {
+ .cursound = -1,
+ .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 char *oss_active; /* the active device */
+
+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";
+
+static const struct ast_channel_tech oss_tech = {
+ .type = "Console",
+ .description = tdesc,
+ .capabilities = AST_FORMAT_SLINEAR,
+ .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,
+ .indicate = oss_indicate,
+ .fixup = oss_fixup,
+};
+
+/*
+ * 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;
+}
+
+/*
+ * 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.
+ */
+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;
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Handler for 'sound writable' events from the sound thread.
+ * Builds a frame from the high level description of the sounds,
+ * and passes it to the audio device.
+ * The actual sound is made of 1 or more sequences of sound samples
+ * (s->datalen, repeated to make s->samplen samples) followed by
+ * s->silencelen samples of silence. The position in the sequence is stored
+ * in o->sampsent, which goes between 0 .. s->samplen+s->silencelen.
+ * In case we fail to write a frame, don't update o->sampsent.
+ */
+static void send_sound(struct chan_oss_pvt *o)
+{
+ short myframe[FRAME_SIZE];
+ int ofs, l, start;
+ int l_sampsent = o->sampsent;
+ struct sound *s;
+
+ if (o->cursound < 0) /* no sound to send */
+ return;
+
+ s = &sounds[o->cursound];
+
+ for (ofs = 0; ofs < FRAME_SIZE; ofs += l) {
+ l = s->samplen - l_sampsent; /* # of available samples */
+ if (l > 0) {
+ start = l_sampsent % s->datalen; /* source offset */
+ if (l > FRAME_SIZE - ofs) /* don't overflow the frame */
+ l = FRAME_SIZE - ofs;
+ if (l > s->datalen - start) /* don't overflow the source */
+ l = s->datalen - start;
+ bcopy(s->data + start, myframe + ofs, l * 2);
+ if (0)
+ ast_log(LOG_WARNING, "send_sound sound %d/%d of %d into %d\n", l_sampsent, l, s->samplen, ofs);
+ l_sampsent += l;
+ } else { /* end of samples, maybe some silence */
+ static const short silence[FRAME_SIZE] = { 0, };
+
+ l += s->silencelen;
+ if (l > 0) {
+ if (l > FRAME_SIZE - ofs)
+ l = FRAME_SIZE - ofs;
+ bcopy(silence, myframe + ofs, l * 2);
+ l_sampsent += l;
+ } else { /* silence is over, restart sound if loop */
+ if (s->repeat == 0) { /* last block */
+ o->cursound = -1;
+ o->nosound = 0; /* allow audio data */
+ if (ofs < FRAME_SIZE) /* pad with silence */
+ bcopy(silence, myframe + ofs, (FRAME_SIZE - ofs) * 2);
+ }
+ l_sampsent = 0;
+ }
+ }
+ }
+ l = soundcard_writeframe(o, myframe);
+ if (l > 0)
+ o->sampsent = l_sampsent; /* update status */
+}
+
+static void *sound_thread(void *arg)
+{
+ char ign[4096];
+ struct chan_oss_pvt *o = (struct chan_oss_pvt *) arg;
+
+ /*
+ * Just in case, kick the driver by trying to read from it.
+ * Ignore errors - this read is almost guaranteed to fail.
+ */
+ if (read(o->sounddev, ign, sizeof(ign)) < 0) {
+ }
+ for (;;) {
+ fd_set rfds, wfds;
+ int maxfd, res;
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ FD_SET(o->sndcmd[0], &rfds);
+ maxfd = o->sndcmd[0]; /* pipe from the main process */
+ if (o->cursound > -1 && o->sounddev < 0)
+ setformat(o, O_RDWR); /* need the channel, try to reopen */
+ else if (o->cursound == -1 && o->owner == NULL)
+ setformat(o, O_CLOSE); /* can close */
+ if (o->sounddev > -1) {
+ if (!o->owner) { /* no one owns the audio, so we must drain it */
+ FD_SET(o->sounddev, &rfds);
+ maxfd = MAX(o->sounddev, maxfd);
+ }
+ if (o->cursound > -1) {
+ FD_SET(o->sounddev, &wfds);
+ maxfd = MAX(o->sounddev, maxfd);
+ }
+ }
+ /* ast_select emulates linux behaviour in terms of timeout handling */
+ res = ast_select(maxfd + 1, &rfds, &wfds, NULL, NULL);
+ if (res < 1) {
+ ast_log(LOG_WARNING, "select failed: %s\n", strerror(errno));
+ sleep(1);
+ continue;
+ }
+ if (FD_ISSET(o->sndcmd[0], &rfds)) {
+ /* read which sound to play from the pipe */
+ int i, what = -1;
+
+ if (read(o->sndcmd[0], &what, sizeof(what)) != sizeof(what)) {
+ ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
+ continue;
+ }
+ for (i = 0; sounds[i].ind != -1; i++) {
+ if (sounds[i].ind == what) {
+ o->cursound = i;
+ o->sampsent = 0;
+ o->nosound = 1; /* block audio from pbx */
+ break;
+ }
+ }
+ if (sounds[i].ind == -1)
+ ast_log(LOG_WARNING, "invalid sound index: %d\n", what);
+ }
+ if (o->sounddev > -1) {
+ if (FD_ISSET(o->sounddev, &rfds)) /* read and ignore errors */
+ if (read(o->sounddev, ign, sizeof(ign)) < 0) {
+ }
+ if (FD_ISSET(o->sounddev, &wfds))
+ send_sound(o);
+ }
+ }
+ return NULL; /* Never reached */
+}
+
+/*
+ * 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)
+ 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 = 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;
+}
+
+/* Play ringtone 'x' on device 'o' */
+static void ring(struct chan_oss_pvt *o, int x)
+{
+ if (write(o->sndcmd[1], &x, sizeof(x)) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+}
+
+
+/*
+ * 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_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 (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);
+ ring(o, AST_CONTROL_RING);
+ }
+ return 0;
+}
+
+/*
+ * remote side answered the phone
+ */
+static int oss_answer(struct ast_channel *c)
+{
+ struct chan_oss_pvt *o = c->tech_pvt;
+
+ ast_verbose(" << Console call has been answered >> \n");
+#if 0
+ /* play an answer tone (XXX do we really need it ?) */
+ ring(o, AST_CONTROL_ANSWER);
+#endif
+ ast_setstate(c, AST_STATE_UP);
+ o->cursound = -1;
+ o->nosound = 0;
+ return 0;
+}
+
+static int oss_hangup(struct ast_channel *c)
+{
+ struct chan_oss_pvt *o = c->tech_pvt;
+
+ o->cursound = -1;
+ o->nosound = 0;
+ c->tech_pvt = NULL;
+ o->owner = NULL;
+ ast_verbose(" << Hangup on console >> \n");
+ 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);
+ } else {
+ /* Make congestion noise */
+ ring(o, AST_CONTROL_CONGESTION);
+ }
+ }
+ return 0;
+}
+
+/* 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;
+
+ /* Immediately return if no sound is enabled */
+ if (o->nosound)
+ return 0;
+ /* Stop any currently playing sound */
+ o->cursound = -1;
+ /*
+ * 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 = -1;
+
+ switch (cond) {
+ case AST_CONTROL_BUSY:
+ case AST_CONTROL_CONGESTION:
+ case AST_CONTROL_RINGING:
+ res = cond;
+ break;
+
+ case -1:
+ o->cursound = -1;
+ o->nosound = 0; /* when cursound is -1 nosound must be 0 */
+ return 0;
+
+ case AST_CONTROL_VIDUPDATE:
+ res = -1;
+ 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_SRCUPDATE:
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, c->name);
+ return -1;
+ }
+
+ if (res > -1)
+ ring(o, res);
+
+ return 0;
+}
+
+/*
+ * 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, "Console/%s", o->device + 5);
+ if (c == NULL)
+ return NULL;
+ c->tech = &oss_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_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 ? */
+ /* XXX what about usecnt ? */
+ }
+ }
+
+ 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 = find_desc(data);
+
+ 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", (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 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 int console_autoanswer_deprecated(int fd, int argc, char *argv[])
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc == 1) {
+ ast_cli(fd, "Auto answer is %s.\n", o->autoanswer ? "on" : "off");
+ return RESULT_SUCCESS;
+ }
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+ if (o == NULL) {
+ ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n", oss_active);
+ return RESULT_FAILURE;
+ }
+ if (!strcasecmp(argv[1], "on"))
+ o->autoanswer = -1;
+ else if (!strcasecmp(argv[1], "off"))
+ o->autoanswer = 0;
+ else
+ return RESULT_SHOWUSAGE;
+ return RESULT_SUCCESS;
+}
+
+static int console_autoanswer(int fd, int argc, char *argv[])
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc == 2) {
+ ast_cli(fd, "Auto answer is %s.\n", o->autoanswer ? "on" : "off");
+ return RESULT_SUCCESS;
+ }
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ if (o == NULL) {
+ ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
+ oss_active);
+ return RESULT_FAILURE;
+ }
+ if (!strcasecmp(argv[2], "on"))
+ o->autoanswer = -1;
+ else if (!strcasecmp(argv[2], "off"))
+ o->autoanswer = 0;
+ else
+ return RESULT_SHOWUSAGE;
+ return RESULT_SUCCESS;
+}
+
+static char *autoanswer_complete_deprecated(const char *line, const char *word, int pos, int state)
+{
+ static char *choices[] = { "on", "off", NULL };
+
+ return (pos != 2) ? NULL : ast_cli_complete(word, choices, state);
+}
+
+static char *autoanswer_complete(const char *line, const char *word, int pos, int state)
+{
+ static char *choices[] = { "on", "off", NULL };
+
+ return (pos != 3) ? NULL : ast_cli_complete(word, choices, state);
+}
+
+static char autoanswer_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";
+
+/*
+ * answer command from the console
+ */
+static int console_answer_deprecated(int fd, int argc, char *argv[])
+{
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc != 1)
+ return RESULT_SHOWUSAGE;
+ if (!o->owner) {
+ ast_cli(fd, "No one is calling us\n");
+ return RESULT_FAILURE;
+ }
+ o->hookstate = 1;
+ o->cursound = -1;
+ o->nosound = 0;
+ ast_queue_frame(o->owner, &f);
+#if 0
+ /* XXX do we really need it ? considering we shut down immediately... */
+ ring(o, AST_CONTROL_ANSWER);
+#endif
+ return RESULT_SUCCESS;
+}
+
+static int console_answer(int fd, int argc, char *argv[])
+{
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+ if (!o->owner) {
+ ast_cli(fd, "No one is calling us\n");
+ return RESULT_FAILURE;
+ }
+ o->hookstate = 1;
+ o->cursound = -1;
+ o->nosound = 0;
+ ast_queue_frame(o->owner, &f);
+#if 0
+ /* XXX do we really need it ? considering we shut down immediately... */
+ ring(o, AST_CONTROL_ANSWER);
+#endif
+ return RESULT_SUCCESS;
+}
+
+static char answer_usage[] =
+ "Usage: console answer\n"
+ " Answers an incoming call on the console (OSS) channel.\n";
+
+/*
+ * concatenate all arguments into a single string. argv is NULL-terminated
+ * so we can use it right away
+ */
+static int console_sendtext_deprecated(int fd, int argc, char *argv[])
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ char buf[TEXT_SIZE];
+
+ if (argc < 2)
+ return RESULT_SHOWUSAGE;
+ if (!o->owner) {
+ ast_cli(fd, "Not in a call\n");
+ return RESULT_FAILURE;
+ }
+ ast_join(buf, sizeof(buf) - 1, argv + 2);
+ 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 RESULT_SUCCESS;
+}
+
+static int console_sendtext(int fd, int argc, char *argv[])
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ char buf[TEXT_SIZE];
+
+ if (argc < 3)
+ return RESULT_SHOWUSAGE;
+ if (!o->owner) {
+ ast_cli(fd, "Not in a call\n");
+ return RESULT_FAILURE;
+ }
+ ast_join(buf, sizeof(buf) - 1, argv + 3);
+ 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 RESULT_SUCCESS;
+}
+
+static char sendtext_usage[] =
+ "Usage: console send text <message>\n"
+ " Sends a text message for display on the remote terminal.\n";
+
+static int console_hangup_deprecated(int fd, int argc, char *argv[])
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc != 1)
+ return RESULT_SHOWUSAGE;
+ o->cursound = -1;
+ o->nosound = 0;
+ if (!o->owner && !o->hookstate) { /* XXX maybe only one ? */
+ ast_cli(fd, "No call to hang up\n");
+ return RESULT_FAILURE;
+ }
+ o->hookstate = 0;
+ if (o->owner)
+ ast_queue_hangup(o->owner);
+ setformat(o, O_CLOSE);
+ return RESULT_SUCCESS;
+}
+
+static int console_hangup(int fd, int argc, char *argv[])
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+ o->cursound = -1;
+ o->nosound = 0;
+ if (!o->owner && !o->hookstate) { /* XXX maybe only one ? */
+ ast_cli(fd, "No call to hang up\n");
+ return RESULT_FAILURE;
+ }
+ o->hookstate = 0;
+ if (o->owner)
+ ast_queue_hangup(o->owner);
+ setformat(o, O_CLOSE);
+ return RESULT_SUCCESS;
+}
+
+static char hangup_usage[] =
+ "Usage: console hangup\n"
+ " Hangs up any call currently placed on the console.\n";
+
+static int console_flash_deprecated(int fd, int argc, char *argv[])
+{
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_FLASH };
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc != 1)
+ return RESULT_SHOWUSAGE;
+ o->cursound = -1;
+ o->nosound = 0; /* when cursound is -1 nosound must be 0 */
+ if (!o->owner) { /* XXX maybe !o->hookstate too ? */
+ ast_cli(fd, "No call to flash\n");
+ return RESULT_FAILURE;
+ }
+ o->hookstate = 0;
+ if (o->owner) /* XXX must be true, right ? */
+ ast_queue_frame(o->owner, &f);
+ return RESULT_SUCCESS;
+}
+
+static int console_flash(int fd, int argc, char *argv[])
+{
+ struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_FLASH };
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+ o->cursound = -1;
+ o->nosound = 0; /* when cursound is -1 nosound must be 0 */
+ if (!o->owner) { /* XXX maybe !o->hookstate too ? */
+ ast_cli(fd, "No call to flash\n");
+ return RESULT_FAILURE;
+ }
+ o->hookstate = 0;
+ if (o->owner) /* XXX must be true, right ? */
+ ast_queue_frame(o->owner, &f);
+ return RESULT_SUCCESS;
+}
+
+static char flash_usage[] =
+ "Usage: console flash\n"
+ " Flashes the call currently placed on the console.\n";
+
+static int console_dial_deprecated(int fd, int argc, char *argv[])
+{
+ char *s = NULL, *mye = NULL, *myc = NULL;
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc != 1 && argc != 2)
+ return RESULT_SHOWUSAGE;
+ if (o->owner) { /* already in a call */
+ int i;
+ struct ast_frame f = { AST_FRAME_DTMF, 0 };
+
+ if (argc == 1) { /* argument is mandatory here */
+ ast_cli(fd, "Already in a call. You can only dial digits until you hangup.\n");
+ return RESULT_FAILURE;
+ }
+ s = argv[1];
+ /* 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 RESULT_SUCCESS;
+ }
+ /* if we have an argument split it into extension and context */
+ if (argc == 2)
+ s = ast_ext_ctx(argv[1], &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(fd, "No such extension '%s' in context '%s'\n", mye, myc);
+ if (s)
+ free(s);
+ return RESULT_SUCCESS;
+}
+
+static int console_dial(int fd, int argc, char *argv[])
+{
+ char *s = NULL, *mye = NULL, *myc = NULL;
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc != 2 && argc != 3)
+ return RESULT_SHOWUSAGE;
+ if (o->owner) { /* already in a call */
+ int i;
+ struct ast_frame f = { AST_FRAME_DTMF, 0 };
+
+ if (argc == 2) { /* argument is mandatory here */
+ ast_cli(fd, "Already in a call. You can only dial digits until you hangup.\n");
+ return RESULT_FAILURE;
+ }
+ s = argv[2];
+ /* 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 RESULT_SUCCESS;
+ }
+ /* if we have an argument split it into extension and context */
+ if (argc == 3)
+ s = ast_ext_ctx(argv[2], &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(fd, "No such extension '%s' in context '%s'\n", mye, myc);
+ if (s)
+ free(s);
+ return RESULT_SUCCESS;
+}
+
+static char dial_usage[] =
+ "Usage: console dial [extension[@context]]\n"
+ " Dials a given extension (and context if specified)\n";
+
+static int __console_mute_unmute(int mute)
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ o->mute = mute;
+ return RESULT_SUCCESS;
+}
+
+static int console_mute_deprecated(int fd, int argc, char *argv[])
+{
+ if (argc != 1)
+ return RESULT_SHOWUSAGE;
+
+ return __console_mute_unmute(1);
+}
+
+static int console_mute(int fd, int argc, char *argv[])
+{
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+
+ return __console_mute_unmute(1);
+}
+
+static char mute_usage[] =
+ "Usage: console mute\nMutes the microphone\n";
+
+static int console_unmute_deprecated(int fd, int argc, char *argv[])
+{
+ if (argc != 1)
+ return RESULT_SHOWUSAGE;
+
+ return __console_mute_unmute(0);
+}
+
+static int console_unmute(int fd, int argc, char *argv[])
+{
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+
+ return __console_mute_unmute(0);
+}
+
+static char unmute_usage[] =
+ "Usage: console unmute\nUnmutes the microphone\n";
+
+static int console_transfer_deprecated(int fd, int argc, char *argv[])
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ struct ast_channel *b = NULL;
+ char *tmp, *ext, *ctx;
+
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+ if (o == NULL)
+ return RESULT_FAILURE;
+ if (o->owner ==NULL || (b = ast_bridged_channel(o->owner)) == NULL) {
+ ast_cli(fd, "There is no call to transfer\n");
+ return RESULT_SUCCESS;
+ }
+
+ tmp = ast_ext_ctx(argv[1], &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(fd, "No such extension exists\n");
+ else {
+ ast_cli(fd, "Whee, transferring %s to %s@%s.\n",
+ b->name, ext, ctx);
+ if (ast_async_goto(b, ctx, ext, 1))
+ ast_cli(fd, "Failed to transfer :(\n");
+ }
+ if (tmp)
+ free(tmp);
+ return RESULT_SUCCESS;
+}
+
+static int console_transfer(int fd, int argc, char *argv[])
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+ struct ast_channel *b = NULL;
+ char *tmp, *ext, *ctx;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ if (o == NULL)
+ return RESULT_FAILURE;
+ if (o->owner == NULL || (b = ast_bridged_channel(o->owner)) == NULL) {
+ ast_cli(fd, "There is no call to transfer\n");
+ return RESULT_SUCCESS;
+ }
+
+ tmp = ast_ext_ctx(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(fd, "No such extension exists\n");
+ else {
+ ast_cli(fd, "Whee, transferring %s to %s@%s.\n", b->name, ext, ctx);
+ if (ast_async_goto(b, ctx, ext, 1))
+ ast_cli(fd, "Failed to transfer :(\n");
+ }
+ if (tmp)
+ free(tmp);
+ return RESULT_SUCCESS;
+}
+
+static char transfer_usage[] =
+ "Usage: console transfer <extension>[@context]\n"
+ " Transfers the currently connected call to the given extension (and\n"
+ "context if specified)\n";
+
+static int console_active_deprecated(int fd, int argc, char *argv[])
+{
+ if (argc == 1)
+ ast_cli(fd, "active console is [%s]\n", oss_active);
+ else if (argc != 2)
+ return RESULT_SHOWUSAGE;
+ else {
+ struct chan_oss_pvt *o;
+ if (strcmp(argv[1], "show") == 0) {
+ for (o = oss_default.next; o; o = o->next)
+ ast_cli(fd, "device [%s] exists\n", o->name);
+ return RESULT_SUCCESS;
+ }
+ o = find_desc(argv[1]);
+ if (o == NULL)
+ ast_cli(fd, "No device [%s] exists\n", argv[1]);
+ else
+ oss_active = o->name;
+ }
+ return RESULT_SUCCESS;
+}
+
+static int console_active(int fd, int argc, char *argv[])
+{
+ if (argc == 2)
+ ast_cli(fd, "active console is [%s]\n", oss_active);
+ else if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ else {
+ struct chan_oss_pvt *o;
+ if (strcmp(argv[2], "show") == 0) {
+ for (o = oss_default.next; o; o = o->next)
+ ast_cli(fd, "device [%s] exists\n", o->name);
+ return RESULT_SUCCESS;
+ }
+ o = find_desc(argv[2]);
+ if (o == NULL)
+ ast_cli(fd, "No device [%s] exists\n", argv[2]);
+ else
+ oss_active = o->name;
+ }
+ return RESULT_SUCCESS;
+}
+
+static char active_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";
+
+/*
+ * store the boost factor
+ */
+static void store_boost(struct chan_oss_pvt *o, 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 int do_boost(int fd, int argc, char *argv[])
+{
+ struct chan_oss_pvt *o = find_desc(oss_active);
+
+ if (argc == 2)
+ ast_cli(fd, "boost currently %5.1f\n", 20 * log10(((double) o->boost / (double) BOOST_SCALE)));
+ else if (argc == 3)
+ store_boost(o, argv[2]);
+ return RESULT_SUCCESS;
+}
+
+static struct ast_cli_entry cli_oss_answer_deprecated = {
+ { "answer", NULL },
+ console_answer_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_oss_hangup_deprecated = {
+ { "hangup", NULL },
+ console_hangup_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_oss_flash_deprecated = {
+ { "flash", NULL },
+ console_flash_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_oss_dial_deprecated = {
+ { "dial", NULL },
+ console_dial_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_oss_mute_deprecated = {
+ { "mute", NULL },
+ console_mute_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_oss_unmute_deprecated = {
+ { "unmute", NULL },
+ console_unmute_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_oss_transfer_deprecated = {
+ { "transfer", NULL },
+ console_transfer_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_oss_send_text_deprecated = {
+ { "send", "text", NULL },
+ console_sendtext_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_oss_autoanswer_deprecated = {
+ { "autoanswer", NULL },
+ console_autoanswer_deprecated, NULL,
+ NULL, autoanswer_complete_deprecated };
+
+static struct ast_cli_entry cli_oss_boost_deprecated = {
+ { "oss", "boost", NULL },
+ do_boost, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_oss_active_deprecated = {
+ { "console", NULL },
+ console_active_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_oss[] = {
+ { { "console", "answer", NULL },
+ console_answer, "Answer an incoming console call",
+ answer_usage, NULL, &cli_oss_answer_deprecated },
+
+ { { "console", "hangup", NULL },
+ console_hangup, "Hangup a call on the console",
+ hangup_usage, NULL, &cli_oss_hangup_deprecated },
+
+ { { "console", "flash", NULL },
+ console_flash, "Flash a call on the console",
+ flash_usage, NULL, &cli_oss_flash_deprecated },
+
+ { { "console", "dial", NULL },
+ console_dial, "Dial an extension on the console",
+ dial_usage, NULL, &cli_oss_dial_deprecated },
+
+ { { "console", "mute", NULL },
+ console_mute, "Disable mic input",
+ mute_usage, NULL, &cli_oss_mute_deprecated },
+
+ { { "console", "unmute", NULL },
+ console_unmute, "Enable mic input",
+ unmute_usage, NULL, &cli_oss_unmute_deprecated },
+
+ { { "console", "transfer", NULL },
+ console_transfer, "Transfer a call to a different extension",
+ transfer_usage, NULL, &cli_oss_transfer_deprecated },
+
+ { { "console", "send", "text", NULL },
+ console_sendtext, "Send text to the remote device",
+ sendtext_usage, NULL, &cli_oss_send_text_deprecated },
+
+ { { "console", "autoanswer", NULL },
+ console_autoanswer, "Sets/displays autoanswer",
+ autoanswer_usage, autoanswer_complete, &cli_oss_autoanswer_deprecated },
+
+ { { "console", "boost", NULL },
+ do_boost, "Sets/displays mic boost in dB",
+ NULL, NULL, &cli_oss_boost_deprecated },
+
+ { { "console", "active", NULL },
+ console_active, "Sets/displays active console",
+ active_usage, NULL, &cli_oss_active_deprecated },
+};
+
+/*
+ * 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, 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)
+ 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, char *s)
+{
+ ast_callerid_split(s, o->cid_name, sizeof(o->cid_name), o->cid_num, sizeof(o->cid_num));
+}
+
+/*
+ * 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) {
+ M_START(v->name, v->value);
+
+ /* handle jb conf */
+ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
+ continue;
+
+ M_BOOL("autoanswer", o->autoanswer)
+ M_BOOL("autohangup", o->autohangup)
+ M_BOOL("overridecontext", o->overridecontext)
+ M_STR("device", o->device)
+ M_UINT("frags", o->frags)
+ M_UINT("debug", oss_debug)
+ M_UINT("queuesize", o->queuesize)
+ M_STR("context", o->ctx)
+ M_STR("language", o->language)
+ M_STR("mohinterpret", o->mohinterpret)
+ M_STR("extension", o->ext)
+ M_F("mixer", store_mixer(o, v->value))
+ M_F("callerid", store_callerid(o, v->value))
+ M_F("boost", store_boost(o, v->value))
+ M_END(;
+ );
+ }
+ if (ast_strlen_zero(o->device))
+ ast_copy_string(o->device, DEV_DSP, sizeof(o->device));
+ if (o->mixer_cmd) {
+ char *cmd;
+
+ if (asprintf(&cmd, "mixer %s", o->mixer_cmd) < 0) {
+ ast_log(LOG_WARNING, "asprintf() failed: %s\n", strerror(errno));
+ } else {
+ ast_log(LOG_WARNING, "running [%s]\n", cmd);
+ if (system(cmd) < 0) {
+ ast_log(LOG_WARNING, "system() failed: %s\n", strerror(errno));
+ }
+ free(cmd);
+ }
+ }
+ if (o == &oss_default) /* we are done with the default */
+ return NULL;
+
+ openit:
+#if TRYOPEN
+ if (setformat(o, O_RDWR) < 0) { /* open device */
+ if (option_verbose > 0) {
+ ast_verbose(VERBOSE_PREFIX_2 "Device %s not detected\n", ctg);
+ ast_verbose(VERBOSE_PREFIX_2 "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 */
+ if (pipe(o->sndcmd) != 0) {
+ ast_log(LOG_ERROR, "Unable to create pipe\n");
+ goto error;
+ }
+ ast_pthread_create_background(&o->sthread, NULL, sound_thread, o);
+ /* link into list of devices */
+ if (o != &oss_default) {
+ o->next = oss_default.next;
+ oss_default.next = o;
+ }
+ return o;
+
+ error:
+ if (o != &oss_default)
+ free(o);
+ return NULL;
+}
+
+static int load_module(void)
+{
+ struct ast_config *cfg = NULL;
+ char *ctg = NULL;
+
+ /* 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))) {
+ 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;
+ }
+
+ 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->sndcmd[0] > 0) {
+ close(o->sndcmd[0]);
+ close(o->sndcmd[1]);
+ }
+ 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, "OSS Console Channel Driver");
diff --git a/1.4.23-rc4/channels/chan_phone.c b/1.4.23-rc4/channels/chan_phone.c
new file mode 100644
index 000000000..55bda8b72
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_phone.c
@@ -0,0 +1,1433 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.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/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.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_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,
+ .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_log(LOG_DEBUG, "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;
+ case AST_CONTROL_SRCUPDATE:
+ res = 0;
+ 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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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;
+ time_t UtcTime;
+ struct tm tm;
+ int start;
+
+ time(&UtcTime);
+ 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;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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);
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_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_G723_1) {
+ /* Prefer g723 */
+ 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_log(LOG_DEBUG, "ioctl(PHONE_PSTN_SET_STATE) failed on %s (%s)\n", ast->name, strerror(errno));
+ else
+ ast_log(LOG_DEBUG, "Took linejack off hook\n");
+ }
+ phone_setup(ast);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Hookstate changed\n");
+ res = ioctl(p->fd, PHONE_HOOKSTATE);
+ /* See if we've gone on hook, if so, notify by returning NULL */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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_MAX_AUDIO ?
+ 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)) &&
+ 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_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, 0, sizeof(tmpbuf));
+ 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;
+ tmp->fds[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_log(LOG_DEBUG, "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 */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "%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_log(LOG_DEBUG, "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(char *iface, int mode, int txgain, int rxgain)
+{
+ /* Make a phone_pvt structure for this interface */
+ struct phone_pvt *tmp;
+ int flags;
+
+ tmp = malloc(sizeof(struct phone_pvt));
+ if (tmp) {
+ tmp->fd = open(iface, O_RDWR);
+ if (tmp->fd < 0) {
+ ast_log(LOG_WARNING, "Unable to open '%s'\n", iface);
+ free(tmp);
+ return NULL;
+ }
+ if (mode == MODE_FXO) {
+ if (ioctl(tmp->fd, IXJCTL_PORT, PORT_PSTN))
+ ast_log(LOG_DEBUG, "Unable to set port to PSTN\n");
+ } else {
+ if (ioctl(tmp->fd, IXJCTL_PORT, PORT_POTS))
+ if (mode != MODE_FXS)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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_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_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(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;
+ }
+
+ /* 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 */
+ 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 */
+ cfg = ast_config_load(config);
+
+ /* 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, "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/1.4.23-rc4/channels/chan_sip.c b/1.4.23-rc4/channels/chan_sip.c
new file mode 100644
index 000000000..004aaaabe
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_sip.c
@@ -0,0 +1,18893 @@
+/*
+ * 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_request(), that parses a bit more.
+ * if it's a response to an outbound request, it's sent to handle_response().
+ * If it is a request, handle_request 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
+ *
+ * \par Deprecated stuff
+ * This is deprecated and will be removed after the 1.4 release
+ * - the SIPUSERAGENT dialplan variable
+ * - the ALERT_INFO dialplan variable
+ */
+
+/*** MODULEINFO
+ <depend>res_features</depend>
+ ***/
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <signal.h>
+#include <sys/signal.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include <regex.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.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/devicestate.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/monitor.h"
+#include "asterisk/localtime.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/compiler.h"
+#include "asterisk/threadstorage.h"
+#include "asterisk/translate.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define SIPBUFSIZE 512
+
+#define XMIT_ERROR -2
+
+#define VIDEO_CODEC_MASK 0x1fc0000 /*!< Video codecs from H.261 thru AST_FORMAT_MAX_VIDEO */
+#ifndef IPTOS_MINCOST
+#define IPTOS_MINCOST 0x02
+#endif
+
+/* #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_FREQ_OK 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_TRANS_TIMEOUT 32000 /*!< 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 SDP_MAX_RTPMAP_CODECS 32 /*!< Maximum number of codecs allowed in received SDP */
+
+#define INITIAL_CSEQ 101 /*!< our initial sip sequence number */
+
+/*! \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;
+
+static const char config[] = "sip.conf";
+static const char notify_config[] = "sip_notify.conf";
+
+#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/sent 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 */
+};
+
+/* 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 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
+};
+
+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 SIP Request methods known by Asterisk */
+enum sipmethod {
+ SIP_UNKNOWN, /* Unknown response */
+ SIP_RESPONSE, /* Not request, response to outbound request */
+ SIP_REGISTER,
+ SIP_OPTIONS,
+ SIP_NOTIFY,
+ SIP_INVITE,
+ SIP_ACK,
+ SIP_PRACK, /* Not supported at all */
+ SIP_BYE,
+ SIP_REFER,
+ SIP_SUBSCRIBE,
+ SIP_MESSAGE,
+ SIP_UPDATE, /* We can send UPDATE; but not accept it */
+ SIP_INFO,
+ SIP_CANCEL,
+ SIP_PUBLISH, /* Not supported at all */
+ SIP_PING, /* Not supported at all, no standard but still implemented out there */
+};
+
+/*! \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,
+ WWW_AUTH,
+};
+
+/*! \brief Authentication result from check_auth* functions */
+enum check_auth_result {
+ AUTH_SUCCESSFUL = 0,
+ AUTH_CHALLENGE_SENT = 1,
+ AUTH_SECRET_FAILED = -1,
+ AUTH_USERNAME_MISMATCH = -2,
+ AUTH_NOT_FOUND = -3,
+ 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 */
+ REG_STATE_REGSENT, /*!< Registration request sent */
+ REG_STATE_AUTHSENT, /*!< We have tried to authenticate */
+ REG_STATE_REGISTERED, /*!< Registred and done */
+ REG_STATE_REJECTED, /*!< Registration rejected */
+ REG_STATE_TIMEOUT, /*!< Registration timed out */
+ REG_STATE_NOAUTH, /*!< We have no accepted credentials */
+ REG_STATE_FAILED, /*!< Registration failed after several tries */
+};
+
+#define CAN_NOT_CREATE_DIALOG 0
+#define CAN_CREATE_DIALOG 1
+#define CAN_CREATE_DIALOG_UNSUPPORTED_METHOD 2
+
+/*! XXX 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;
+ int 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
+
+#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)
+
+/*! \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, NOT_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 */
+#define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY"
+
+/*! \brief SIP Extensions we support */
+#define SUPPORTED_EXTENSIONS "replaces"
+
+/*! \brief Standard SIP port from RFC 3261. DO NOT CHANGE THIS */
+#define STANDARD_SIP_PORT 5060
+/* 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.
+ */
+
+/* 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_MWITIME 10
+#define DEFAULT_ALLOWGUEST TRUE
+#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_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_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 */
+#endif
+
+
+/* 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 */
+
+/* Global settings only apply to the channel */
+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;
+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 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_relaxdtmf; /*!< Relax DTMF */
+static int global_rtptimeout; /*!< Time out call if no RTP */
+static int global_rtpholdtimeout;
+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_allowsubscribe; /*!< Flag for disabling ALL subscriptions, this is FALSE only if all peers are FALSE
+ the global setting is in globals_flags[1] */
+static int global_mwitime; /*!< Time between MWI checks for peers */
+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 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 int allow_external_domains; /*!< Accept calls to external SIP domains? */
+static int global_callevents; /*!< Whether we send manager events or not */
+static int global_t1min; /*!< T1 roundtrip time minimum */
+static int global_autoframing; /*!< Turn autoframing on or off. */
+static enum transfermodes global_allowtransfer; /*!< SIP Refer restriction scheme */
+
+static int global_matchexterniplocally; /*!< Match externip/externhost setting against localnet setting */
+
+/*! \brief Codecs that we support by default: */
+static int global_capability = AST_FORMAT_ULAW | AST_FORMAT_ALAW | AST_FORMAT_GSM | AST_FORMAT_H263;
+
+/*! \brief Global list of addresses dynamic peers are not allowed to use */
+static struct ast_ha *global_contact_ha = NULL;
+static int global_dynamic_exclude_static = 0;
+
+/* 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 */
+
+/*! \brief Protect the SIP dialog list (of sip_pvt's) */
+AST_MUTEX_DEFINE_STATIC(iflock);
+
+/*! \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(netlock);
+
+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
+
+/*! \brief sip_request: The data grabbed from the UDP socket */
+struct sip_request {
+ char *rlPart1; /*!< SIP Method Name or "SIP/2.0" protocol version */
+ char *rlPart2; /*!< The Request URI or Response Status */
+ int len; /*!< Length */
+ int headers; /*!< # of SIP Headers */
+ int method; /*!< Method of this request */
+ int lines; /*!< Body Content */
+ unsigned int flags; /*!< SIP_PKT Flags for this packet */
+ char *header[SIP_MAX_HEADERS];
+ char *line[SIP_MAX_LINES];
+ char data[SIP_MAX_PACKET];
+ unsigned int sdp_start; /*!< the line number where the SDP begins */
+ unsigned int sdp_end; /*!< the line number where the SDP ends */
+ AST_LIST_ENTRY(sip_request) next;
+};
+
+/*
+ * A sip packet is stored into the data[] buffer, with the header followed
+ * by an empty line and the body of the message.
+ * On outgoing packets, data is accumulated in data[] with len reflecting
+ * the next available byte, headers and lines count the number of lines
+ * in both parts. There are no '\0' in data[0..len-1].
+ *
+ * On received packet, the input read from the socket is copied into data[],
+ * len is set and the string is NUL-terminated. Then a parser fills up
+ * the other fields -header[] and line[] to point to the lines of the
+ * message, rlPart1 and rlPart2 parse the first lnie as below:
+ *
+ * Requests have in the first line METHOD URI SIP/2.0
+ * rlPart1 = method; rlPart2 = uri;
+ * Responses have in the first line SIP/2.0 code description
+ * rlPart1 = SIP/2.0; rlPart2 = code + description;
+ *
+ */
+
+/*! \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 {
+ const char *distinctive_ring; /*!< Distinctive ring header */
+ 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 */
+};
+
+/*--- Various flags for the flags field in the pvt structure */
+#define SIP_ALREADYGONE (1 << 0) /*!< Whether or not we've already been destroyed by our peer */
+#define SIP_NEEDDESTROY (1 << 1) /*!< if we need to be destroyed by the monitor thread */
+#define SIP_NOVIDEO (1 << 2) /*!< Didn't get video in invite, don't offer */
+#define SIP_RINGING (1 << 3) /*!< Have sent 180 ringing */
+#define SIP_PROGRESS_SENT (1 << 4) /*!< Have sent 183 message progress */
+#define SIP_NEEDREINVITE (1 << 5) /*!< Do we need to send another reinvite? */
+#define SIP_PENDINGBYE (1 << 6) /*!< Need to send bye after we ack? */
+#define SIP_GOTREFER (1 << 7) /*!< Got a refer? */
+#define SIP_PROMISCREDIR (1 << 8) /*!< Promiscuous redirection */
+#define SIP_TRUSTRPID (1 << 9) /*!< Trust RPID headers? */
+#define SIP_USEREQPHONE (1 << 10) /*!< Add user=phone to numeric URI. Default off */
+#define SIP_REALTIME (1 << 11) /*!< Flag for realtime users */
+#define SIP_USECLIENTCODE (1 << 12) /*!< Trust X-ClientCode info message */
+#define SIP_OUTGOING (1 << 13) /*!< Direction of the last transaction in this dialog */
+#define SIP_FREE_BIT (1 << 14) /*!< ---- */
+#define SIP_DEFER_BYE_ON_TRANSFER (1 << 15) /*!< Do not hangup at first ast_hangup */
+#define SIP_DTMF (3 << 16) /*!< DTMF Support: four settings, uses two bits */
+#define SIP_DTMF_RFC2833 (0 << 16) /*!< DTMF Support: RTP DTMF - "rfc2833" */
+#define SIP_DTMF_INBAND (1 << 16) /*!< DTMF Support: Inband audio, only for ULAW/ALAW - "inband" */
+#define SIP_DTMF_INFO (2 << 16) /*!< DTMF Support: SIP Info messages - "info" */
+#define SIP_DTMF_AUTO (3 << 16) /*!< DTMF Support: AUTO switch between rfc2833 and in-band DTMF */
+/* NAT settings */
+#define SIP_NAT (3 << 18) /*!< four settings, uses two bits */
+#define SIP_NAT_NEVER (0 << 18) /*!< No nat support */
+#define SIP_NAT_RFC3581 (1 << 18) /*!< NAT RFC3581 */
+#define SIP_NAT_ROUTE (2 << 18) /*!< NAT Only ROUTE */
+#define SIP_NAT_ALWAYS (3 << 18) /*!< NAT Both ROUTE and RFC3581 */
+/* re-INVITE related settings */
+#define SIP_REINVITE (7 << 20) /*!< three bits used */
+#define SIP_CAN_REINVITE (1 << 20) /*!< allow peers to be reinvited to send media directly p2p */
+#define SIP_CAN_REINVITE_NAT (2 << 20) /*!< allow media reinvite when new peer is behind NAT */
+#define SIP_REINVITE_UPDATE (4 << 20) /*!< use UPDATE (RFC3311) when reinviting this peer */
+/* "insecure" settings */
+#define SIP_INSECURE_PORT (1 << 23) /*!< don't require matching port for incoming requests */
+#define SIP_INSECURE_INVITE (1 << 24) /*!< don't require authentication for incoming INVITEs */
+/* Sending PROGRESS in-band settings */
+#define SIP_PROG_INBAND (3 << 25) /*!< 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_NO_HISTORY (1 << 27) /*!< Suppress recording request/response history */
+#define SIP_CALL_LIMIT (1 << 28) /*!< Call limit enforced for this call */
+#define SIP_SENDRPID (1 << 29) /*!< Remote Party-ID Support */
+#define SIP_INC_COUNT (1 << 30) /*!< Did this connection increment the counter of in-use calls? */
+#define SIP_G726_NONSTANDARD (1 << 31) /*!< Use non-standard packing for G726-32 data */
+
+#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_PORT | SIP_INSECURE_INVITE)
+
+/*--- a new page of flags (for flags[1] */
+/* realtime flags */
+#define SIP_PAGE2_RTCACHEFRIENDS (1 << 0)
+#define SIP_PAGE2_RTUPDATE (1 << 1)
+#define SIP_PAGE2_RTAUTOCLEAR (1 << 2)
+#define SIP_PAGE2_RT_FROMCONTACT (1 << 4)
+#define SIP_PAGE2_RTSAVE_SYSNAME (1 << 5)
+/* Space for addition of other realtime flags in the future */
+#define SIP_PAGE2_STATECHANGEQUEUE (1 << 9) /*!< D: Unsent state pending change exists */
+#define SIP_PAGE2_IGNOREREGEXPIRE (1 << 10)
+#define SIP_PAGE2_DEBUG (3 << 11)
+#define SIP_PAGE2_DEBUG_CONFIG (1 << 11)
+#define SIP_PAGE2_DEBUG_CONSOLE (1 << 12)
+#define SIP_PAGE2_DYNAMIC (1 << 13) /*!< Dynamic Peers register with Asterisk */
+#define SIP_PAGE2_SELFDESTRUCT (1 << 14) /*!< Automatic peers need to destruct themselves */
+#define SIP_PAGE2_VIDEOSUPPORT (1 << 15)
+#define SIP_PAGE2_ALLOWSUBSCRIBE (1 << 16) /*!< Allow subscriptions from this peer? */
+#define SIP_PAGE2_ALLOWOVERLAP (1 << 17) /*!< Allow overlap dialing ? */
+#define SIP_PAGE2_SUBSCRIBEMWIONLY (1 << 18) /*!< Only issue MWI notification if subscribed to */
+#define SIP_PAGE2_INC_RINGING (1 << 19) /*!< Did this connection increment the counter of in-use calls? */
+#define SIP_PAGE2_T38SUPPORT (7 << 20) /*!< T38 Fax Passthrough Support */
+#define SIP_PAGE2_T38SUPPORT_UDPTL (1 << 20) /*!< 20: T38 Fax Passthrough Support */
+#define SIP_PAGE2_T38SUPPORT_RTP (2 << 20) /*!< 21: T38 Fax Passthrough Support (not implemented) */
+#define SIP_PAGE2_T38SUPPORT_TCP (4 << 20) /*!< 22: T38 Fax Passthrough Support (not implemented) */
+#define SIP_PAGE2_CALL_ONHOLD (3 << 23) /*!< Call states */
+#define SIP_PAGE2_CALL_ONHOLD_ACTIVE (1 << 23) /*!< 23: Active hold */
+#define SIP_PAGE2_CALL_ONHOLD_ONEDIR (2 << 23) /*!< 23: One directional hold */
+#define SIP_PAGE2_CALL_ONHOLD_INACTIVE (3 << 23) /*!< 23: Inactive hold */
+#define SIP_PAGE2_RFC2833_COMPENSATE (1 << 25) /*!< 25: ???? */
+#define SIP_PAGE2_BUGGY_MWI (1 << 26) /*!< 26: Buggy CISCO MWI fix */
+#define SIP_PAGE2_OUTGOING_CALL (1 << 27) /*!< 27: Is this an outgoing call? */
+#define SIP_PAGE2_UDPTL_DESTINATION (1 << 28) /*!< 28: Use source IP of RTP as destination if NAT is enabled */
+#define SIP_PAGE2_DIALOG_ESTABLISHED (1 << 29) /*!< 29: Has a dialog been established? */
+
+#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_UDPTL_DESTINATION)
+
+/* SIP packet flags */
+#define SIP_PKT_DEBUG (1 << 0) /*!< Debug this packet */
+#define SIP_PKT_WITH_TOTAG (1 << 1) /*!< This packet has a to-tag */
+#define SIP_PKT_IGNORE (1 << 2) /*!< This is a re-transmit, ignore it */
+#define SIP_PKT_IGNORE_RESP (1 << 3) /*!< Resp ignore - ??? */
+#define SIP_PKT_IGNORE_REQ (1 << 4) /*!< Req ignore - ??? */
+
+/* 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 trancoding, 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;
+
+#define sipdebug ast_test_flag(&global_flags[1], SIP_PAGE2_DEBUG)
+#define sipdebug_config ast_test_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONFIG)
+#define sipdebug_console ast_test_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONSOLE)
+
+/*! \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 transferer */
+ REFER_CONFIRMED, /*!< Refer confirmed with a 100 TRYING */
+ 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 */
+};
+
+static const struct c_referstatusstring {
+ enum referstatus status;
+ char *text;
+} referstatusstrings[] = {
+ { REFER_IDLE, "<none>" },
+ { REFER_SENT, "Request sent" },
+ { REFER_RECEIVED, "Request received" },
+ { REFER_ACCEPTED, "Accepted" },
+ { REFER_RINGING, "Target ringing" },
+ { REFER_200OK, "Done" },
+ { REFER_FAILED, "Failed" },
+ { REFER_NOAUTH, "Failed - auth failure" }
+} ;
+
+/*! \brief Structure to handle SIP transfers. Dynamically allocated when needed */
+/* 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[SIPBUFSIZE]; /*!< Replace info: callid */
+ char replaces_callid_totag[SIPBUFSIZE/2]; /*!< Replace info: to-tag */
+ char replaces_callid_fromtag[SIPBUFSIZE/2]; /*!< Replace info: from-tag */
+ struct sip_pvt *refer_call; /*!< Call we are referring */
+ int attendedtransfer; /*!< Attended or blind transfer? */
+ int localtransfer; /*!< Transfer to local domain? */
+ enum referstatus status; /*!< REFER status */
+};
+
+/*! \brief sip_pvt: PVT structures are used for each SIP dialog, ie. a call, a registration, a subscribe */
+static struct sip_pvt {
+ ast_mutex_t lock; /*!< Dialog private lock */
+ int method; /*!< SIP method that opened this dialog */
+ enum invitestates invitestate; /*!< The state of the INVITE transaction only */
+ 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(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(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 */
+ AST_STRING_FIELD(our_contact); /*!< Our contact header */
+ AST_STRING_FIELD(rpid); /*!< Our RPID header */
+ AST_STRING_FIELD(rpid_from); /*!< Our RPID From header */
+ );
+ 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 */
+ int timer_t1; /*!< SIP timer T1, ms rtt */
+ unsigned int sipoptions; /*!< Supported 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 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 */
+ long invite_branch; /*!< The branch used when we sent the initial INVITE */
+ char tag[11]; /*!< Our tag for this session */
+ int sessionid; /*!< SDP Session ID */
+ int sessionversion; /*!< SDP Session Version */
+ 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 */
+ time_t lastrtprx; /*!< Last RTP received */
+ time_t lastrtptx; /*!< Last RTP sent */
+ int rtptimeout; /*!< RTP timeout time */
+ struct sockaddr_in recv; /*!< Received as */
+ struct in_addr ourip; /*!< Our IP */
+ 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 or state NOTIFY (in subscribe pvt's) ? (seqno of this) */
+ struct sip_request initreq; /*!< Request that opened the latest transaction
+ within this SIP dialog */
+
+ int maxtime; /*!< Max time for first response */
+ 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; /*!< Voice Activation 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 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 */
+ AST_LIST_HEAD_NOLOCK(request_queue, sip_request) request_queue; /*!< Requests that arrived but could not be processed immediately */
+ int request_queue_sched_id; /*!< Scheduler ID of any scheduled action to process queued requests */
+ struct sip_pvt *next; /*!< Next dialog in chain */
+ struct sip_invite_param *options; /*!< Options for INVITE */
+ int autoframing;
+} *iflist = NULL;
+
+/*! Max entires in the history list for a sip_pvt */
+#define MAX_HISTORY_ENTRIES 50
+
+#define FLAG_RESPONSE (1 << 0)
+#define FLAG_FATAL (1 << 1)
+
+/*! \brief sip packet - raw format for outbound packets that are sent or scheduled for transmission */
+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 */
+ unsigned int flags; /*!< non-zero if this is a response packet (e.g. 200 OK) */
+ 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 */
+ 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;
+};
+
+/*! \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 */
+ 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 */
+ enum transfermodes allowtransfer; /*! SIP Refer restriction scheme */
+ char vmexten[AST_MAX_EXTENSION]; /*!< Dialplan extension for MWI notify message*/
+ char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox setting for MWI checks */
+ 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;
+ time_t lastmsgcheck; /*!< Last time we checked for MWI */
+ unsigned int sipoptions; /*!< Supported SIP options */
+ struct ast_flags flags[2]; /*!< SIP_ flags */
+ 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 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 */
+ struct timeval ps; /*!< Ping send time */
+
+ struct sockaddr_in defaddr; /*!< Default IP address, used until registration */
+ struct ast_ha *ha; /*!< Access control list */
+ struct ast_ha *contactha; /*!< Restrict what IPs are allowed in the Contact header (for registration) */
+ struct ast_variable *chanvars; /*!< Variables to set for channel created by user */
+ struct sip_pvt *mwipvt; /*!< Subscription for MWI */
+ int lastmsg;
+ int autoframing;
+};
+
+
+
+/*! \brief Registrations with other SIP proxies */
+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(contact); /*!< Contact extension */
+ AST_STRING_FIELD(random);
+ );
+ int portno; /*!< Optional port override */
+ int expire; /*!< Sched ID of expiration */
+ 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) */
+ time_t regtime; /*!< Last succesful 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 */
+};
+
+/* --- Linked lists of various objects --------*/
+
+/*! \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 proxys we register with and place calls to */
+static struct ast_register_list {
+ ASTOBJ_CONTAINER_COMPONENTS(struct sip_registry);
+ int recheck;
+} regl;
+
+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);
+
+#ifdef LOW_MEMORY
+static void ts_ast_rtp_destroy(void *);
+
+AST_THREADSTORAGE_CUSTOM(ts_audio_rtp, ts_audio_rtp_init, ts_ast_rtp_destroy);
+AST_THREADSTORAGE_CUSTOM(ts_video_rtp, ts_video_rtp_init, ts_ast_rtp_destroy);
+#endif
+
+/*! \todo Move the sip_auth list to AST_LIST */
+static struct sip_auth *authl = NULL; /*!< Authentication list for realm authentication */
+
+
+/* --- Sockets and networking --------------*/
+static int sipsock = -1; /*!< Main socket for SIP network communication */
+static struct sockaddr_in bindaddr = { 0, }; /*!< The address we bind to */
+static struct sockaddr_in externip; /*!< External IP address if we are behind NAT */
+static char externhost[MAXHOSTNAMELEN]; /*!< External host name (possibly with dynamic DNS and DHCP */
+static time_t externexpire = 0; /*!< Expiration counter for re-resolving external host name in dynamic DNS */
+static int externrefresh = 10;
+static struct ast_ha *localaddr; /*!< List of local networks, on the same side of NAT as this Asterisk */
+static struct in_addr __ourip;
+static struct sockaddr_in outboundproxyip;
+static int ourport;
+static struct sockaddr_in debugaddr;
+
+static struct ast_config *notify_types; /*!< The list of manual NOTIFY types we know how to send */
+
+/*---------------------------- 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_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);
+
+/*--- 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);
+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);
+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);
+static int does_peer_need_mwi(struct sip_peer *peer);
+
+/*--- 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 int sip_cancel_destroy(struct sip_pvt *p);
+static void sip_destroy(struct sip_pvt *p);
+static int __sip_destroy(struct sip_pvt *p, int lockowner);
+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 *nothing);
+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,
+ char **m_buf, size_t *m_size, char **a_buf, size_t *a_size,
+ int debug, int *min_packet_size);
+static void add_noncodec_to_sdp(const struct sip_pvt *p, int format, int sample_rate,
+ char **m_buf, size_t *m_size, char **a_buf, size_t *a_size,
+ int debug);
+static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p);
+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, 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_send_mwi_to_peer(struct sip_peer *peer);
+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 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);
+
+/*--- Applications, functions, CLI and manager command helpers */
+static const char *sip_nat_mode(const struct sip_pvt *p);
+static int sip_show_inuse(int fd, int argc, char *argv[]);
+static char *transfermode2str(enum transfermodes mode) attribute_const;
+static char *nat2str(int nat) attribute_const;
+static int peer_status(struct sip_peer *peer, char *status, int statuslen);
+static int sip_show_users(int fd, int argc, char *argv[]);
+static int _sip_show_peers(int fd, int *total, struct mansession *s, const struct message *m, int argc, const char *argv[]);
+static int sip_show_peers(int fd, int argc, char *argv[]);
+static int sip_show_objects(int fd, int argc, char *argv[]);
+static void print_group(int fd, ast_group_t group, int crlf);
+static const char *dtmfmode2str(int mode) attribute_const;
+static const char *insecure2str(int port, int invite) 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 int sip_show_domains(int fd, int argc, char *argv[]);
+static int _sip_show_peer(int type, int fd, struct mansession *s, const struct message *m, int argc, const char *argv[]);
+static int sip_show_peer(int fd, int argc, char *argv[]);
+static int sip_show_user(int fd, int argc, char *argv[]);
+static int sip_show_registry(int fd, int argc, char *argv[]);
+static int sip_show_settings(int fd, int argc, char *argv[]);
+static const char *subscription_type2str(enum subscriptiontype subtype) attribute_pure;
+static const struct cfsubscription_types *find_subscription_type(enum subscriptiontype subtype);
+static int __sip_show_channels(int fd, int argc, char *argv[], int subscriptions);
+static int sip_show_channels(int fd, int argc, char *argv[]);
+static int sip_show_subscriptions(int fd, int argc, char *argv[]);
+static int __sip_show_channels(int fd, int argc, char *argv[], int subscriptions);
+static char *complete_sipch(const char *line, const char *word, int pos, int state);
+static char *complete_sip_peer(const char *word, int state, int flags2);
+static char *complete_sip_show_peer(const char *line, const char *word, int pos, int state);
+static char *complete_sip_debug_peer(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 *complete_sip_prune_realtime_peer(const char *line, const char *word, int pos, int state);
+static char *complete_sip_prune_realtime_user(const char *line, const char *word, int pos, int state);
+static int sip_show_channel(int fd, int argc, char *argv[]);
+static int sip_show_history(int fd, int argc, char *argv[]);
+static int sip_do_debug_ip(int fd, int argc, char *argv[]);
+static int sip_do_debug_peer(int fd, int argc, char *argv[]);
+static int sip_do_debug(int fd, int argc, char *argv[]);
+static int sip_no_debug(int fd, int argc, char *argv[]);
+static int sip_notify(int fd, int argc, char *argv[]);
+static int sip_do_history(int fd, int argc, char *argv[]);
+static int sip_no_history(int fd, int argc, char *argv[]);
+static int func_header_read(struct ast_channel *chan, char *function, char *data, char *buf, size_t len);
+static int func_check_sipdomain(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len);
+static int function_sippeer(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len);
+static int function_sipchaninfo_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len);
+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 int sip_reload(int fd, int argc, char *argv[]);
+static int acf_channel_read(struct ast_channel *chan, 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 LOG_DEBUG 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);
+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, struct ast_variable *alt, 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 int sip_poke_peer_s(const void *data);
+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, int devstate_only);
+static struct sip_user *find_user(const char *name, int realtime);
+static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *p, struct sip_request *req);
+static int expire_register(const void *data);
+static void reg_source_db(struct sip_peer *peer);
+static void destroy_association(struct sip_peer *peer);
+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 sip_peer *realtime_peer(const char *peername, struct sockaddr_in *sin, int devstate_only);
+static int sip_prune_realtime(int fd, int argc, char *argv[]);
+
+/*--- Internal UA client handling (outbound registrations) */
+static int ast_sip_ouraddrfor(struct in_addr *them, struct in_addr *us);
+static void sip_registry_destroy(struct sip_registry *reg);
+static int sip_register(char *value, int lineno);
+static 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 void set_insecure_flags(struct ast_flags *flags, const char *value, int lineno);
+static int find_sip_method(const char *msg);
+static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported);
+static int parse_request(struct sip_request *req);
+static const char *get_header(const struct sip_request *req, const char *name);
+static 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 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, const 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 void free_old_route(struct sip_route *route);
+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);
+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_request(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 ignore, 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 ignore, 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 ignore, int seqno);
+static void handle_response(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int ignore, int seqno);
+
+/*----- RTP interface functions */
+static int sip_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp, struct ast_rtp *vrtp, 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 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); /*!< T38 negotiation helper function */
+static int transmit_response_with_t38_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans);
+static int transmit_reinvite_with_t38_sdp(struct sip_pvt *p);
+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);
+
+/*! \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_MAX_AUDIO << 1) - 1),
+ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
+ .requester = sip_request_call,
+ .devicestate = sip_devicestate,
+ .call = sip_call,
+ .hangup = sip_hangup,
+ .answer = sip_answer,
+ .read = sip_read,
+ .write = sip_write,
+ .write_video = sip_write,
+ .indicate = sip_indicate,
+ .transfer = sip_transfer,
+ .fixup = sip_fixup,
+ .send_digit_begin = sip_senddigit_begin,
+ .send_digit_end = sip_senddigit_end,
+ .bridge = ast_rtp_bridge,
+ .send_text = sip_sendtext,
+ .func_channel_read = acf_channel_read,
+};
+
+/*! \brief This version of the sip channel tech has no send_digit_begin
+ * callback. This is for use with channels using SIP INFO DTMF so that
+ * the core knows that the channel doesn't want DTMF BEGIN frames. */
+static const struct ast_channel_tech sip_tech_info = {
+ .type = "SIP",
+ .description = "Session Initiation Protocol (SIP)",
+ .capabilities = ((AST_FORMAT_MAX_AUDIO << 1) - 1),
+ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
+ .requester = sip_request_call,
+ .devicestate = sip_devicestate,
+ .call = sip_call,
+ .hangup = sip_hangup,
+ .answer = sip_answer,
+ .read = sip_read,
+ .write = sip_write,
+ .write_video = sip_write,
+ .indicate = sip_indicate,
+ .transfer = sip_transfer,
+ .fixup = sip_fixup,
+ .send_digit_end = sip_senddigit_end,
+ .bridge = ast_rtp_bridge,
+ .send_text = sip_sendtext,
+ .func_channel_read = acf_channel_read,
+};
+
+/**--- some list management macros. **/
+
+#define UNLINK(element, head, prev) do { \
+ if (prev) \
+ (prev)->next = (element)->next; \
+ else \
+ (head) = (element)->next; \
+ } while (0)
+
+/*! \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,
+ set_rtp_peer: sip_set_rtp_peer,
+ get_codec: sip_get_codec,
+};
+
+/*! \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,
+};
+
+/*! \brief Convert transfer status to string */
+static char *referstatus2str(enum referstatus rstatus)
+{
+ int i = (sizeof(referstatusstrings) / sizeof(referstatusstrings[0]));
+ int x;
+
+ for (x = 0; x < i; x++) {
+ if (referstatusstrings[x].status == rstatus)
+ return (char *) referstatusstrings[x].text;
+ }
+ return "";
+}
+
+/*! \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 && option_debug) {
+ ast_log(LOG_DEBUG, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid);
+ }
+ /* Use this as the basis */
+ copy_request(&p->initreq, req);
+ parse_request(&p->initreq);
+ if (ast_test_flag(req, SIP_PKT_DEBUG))
+ ast_verbose("%d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
+}
+
+static void sip_alreadygone(struct sip_pvt *dialog)
+{
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Setting SIP_ALREADYGONE on dialog %s\n", dialog->callid);
+ ast_set_flag(&dialog->flags[0], SIP_ALREADYGONE);
+}
+
+
+/*! \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 (option_debug > 2 && sipdebug)
+ ast_log(LOG_DEBUG, "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 (option_debug > 2 && sipdebug)
+ ast_log(LOG_DEBUG, "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 (option_debug > 2 && sipdebug)
+ ast_log(LOG_DEBUG, "Matched SIP option: %s\n", next);
+ break;
+ }
+ }
+ if (!found && option_debug > 2 && sipdebug) {
+ if (!strncasecmp(next, "x-", 2))
+ ast_log(LOG_DEBUG, "Found private SIP option, not supported: %s\n", next);
+ else
+ ast_log(LOG_DEBUG, "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)
+{
+ 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));
+}
+
+/*! \brief Transmit SIP message */
+static int __sip_xmit(struct sip_pvt *p, char *data, int len)
+{
+ int res;
+ const struct sockaddr_in *dst = sip_real_dst(p);
+ res = sendto(sipsock, data, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
+
+ 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: /* Inteface down */
+ case ENETUNREACH: /* Network failure */
+ case ECONNREFUSED: /* ICMP port unreachable */
+ 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/UDP %s:%d;branch=z9hG4bK%08x%s",
+ ast_inet_ntoa(p->ourip), ourport, (int) 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
+ */
+static enum sip_result ast_sip_ouraddrfor(struct in_addr *them, struct in_addr *us)
+{
+ struct sockaddr_in theirs, ours;
+
+ /* Get our local information */
+ ast_ouraddrfor(them, us);
+ theirs.sin_addr = *them;
+ ours.sin_addr = *us;
+
+ if (localaddr && externip.sin_addr.s_addr &&
+ (ast_apply_ha(localaddr, &theirs)) &&
+ (!global_matchexterniplocally || !ast_apply_ha(localaddr, &ours))) {
+ if (externexpire && time(NULL) >= externexpire) {
+ struct ast_hostent ahp;
+ struct hostent *hp;
+
+ externexpire = time(NULL) + externrefresh;
+ if ((hp = ast_gethostbyname(externhost, &ahp))) {
+ memcpy(&externip.sin_addr, hp->h_addr, sizeof(externip.sin_addr));
+ } else
+ ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
+ }
+ *us = externip.sin_addr;
+ if (option_debug) {
+ ast_log(LOG_DEBUG, "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)
+ *us = bindaddr.sin_addr;
+ return AST_SUCCESS;
+}
+
+/*! \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, ...)
+ __attribute__((format(printf, 2, 3)));
+
+/*! \brief Append to SIP dialog history with arg list */
+static void __attribute__((format(printf, 2, 0))) 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)))) {
+ 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--;
+ 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 (ast_test_flag(&p->flags[0], SIP_NO_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 */
+ ast_mutex_lock(&pkt->owner->lock);
+
+ if (pkt->retrans < MAX_RETRANS) {
+ pkt->retrans++;
+ if (!pkt->timer_t1) { /* Re-schedule using timer_a and timer_t1 */
+ if (sipdebug && option_debug > 3)
+ ast_log(LOG_DEBUG, "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 && option_debug > 3)
+ ast_log(LOG_DEBUG, "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;
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "** 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);
+ ast_mutex_unlock(&pkt->owner->lock);
+ 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 (ast_test_flag(pkt, FLAG_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) -- See doc/sip-retransmit.txt.\n", pkt->owner->callid, pkt->seqno, (ast_test_flag(pkt, FLAG_FATAL)) ? "Critical" : "Non-critical", (ast_test_flag(pkt, FLAG_RESPONSE)) ? "Response" : "Request");
+ } else if ((pkt->method == SIP_OPTIONS) && sipdebug) {
+ ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) -- See doc/sip-retransmit.txt.\n", pkt->owner->callid);
+ }
+ if (xmitres == XMIT_ERROR) {
+ ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission of transaction in call id %s \n", pkt->owner->callid);
+ append_history(pkt->owner, "XmitErr", "%s", (ast_test_flag(pkt, FLAG_FATAL)) ? "(Critical)" : "(Non-critical)");
+ } else
+ append_history(pkt->owner, "MaxRetries", "%s", (ast_test_flag(pkt, FLAG_FATAL)) ? "(Critical)" : "(Non-critical)");
+
+ pkt->retransid = -1;
+
+ if (ast_test_flag(pkt, FLAG_FATAL)) {
+ while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
+ DEADLOCK_AVOIDANCE(&pkt->owner->lock); /* SIP_PVT, not channel */
+ }
+
+ 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 (see doc/sip-retransmit.txt).\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) {
+ ast_set_flag(&pkt->owner->flags[0], SIP_NEEDDESTROY);
+ sip_alreadygone(pkt->owner);
+ if (option_debug)
+ 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.");
+ ast_set_flag(&pkt->owner->flags[0], SIP_NEEDDESTROY);
+ }
+
+ /* In any case, go ahead and remove the packet */
+ for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
+ if (cur == pkt)
+ break;
+ }
+ if (cur) {
+ if (prev)
+ prev->next = cur->next;
+ else
+ pkt->owner->packets = cur->next;
+ ast_mutex_unlock(&pkt->owner->lock);
+ free(cur);
+ pkt = NULL;
+ } else
+ ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
+ if (pkt)
+ ast_mutex_unlock(&pkt->owner->lock);
+ 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;
+ int siptimer_a = DEFAULT_RETRANS;
+ int xmitres = 0;
+
+ if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
+ return AST_FAILURE;
+ memcpy(pkt->data, data, len);
+ pkt->method = sipmethod;
+ pkt->packetlen = len;
+ pkt->next = p->packets;
+ pkt->owner = p;
+ pkt->seqno = seqno;
+ if (resp)
+ ast_set_flag(pkt, FLAG_RESPONSE);
+ pkt->data[len] = '\0';
+ pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
+ pkt->retransid = -1;
+ if (fatal)
+ ast_set_flag(pkt, FLAG_FATAL);
+ if (pkt->timer_t1)
+ siptimer_a = pkt->timer_t1 * 2;
+
+ if (option_debug > 3 && sipdebug)
+ ast_log(LOG_DEBUG, "*** SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", pkt->retransid);
+ pkt->retransid = -1;
+ pkt->next = p->packets;
+ p->packets = pkt;
+ 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", (ast_test_flag(pkt, FLAG_FATAL)) ? "(Critical)" : "(Non-critical)");
+ return AST_FAILURE;
+ } else {
+ /* Schedule retransmission */
+ pkt->retransid = ast_sched_add_variable(sched, siptimer_a, retrans_pkt, pkt, 1);
+ return AST_SUCCESS;
+ }
+}
+
+/*! \brief Kill a SIP dialog (called by scheduler) */
+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");
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Re-scheduled destruction of SIP subsription %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 */
+ /* via bug 12101, the two usages of SIP_NEEDDESTROY in the following block
+ * of code make a sort of "safety relief valve", that allows sip channels
+ * that were created via INVITE, then thru some sequence were CANCELED,
+ * to die, rather than infinitely be rescheduled */
+ if (p->packets && !ast_test_flag(&p->flags[0], SIP_NEEDDESTROY)) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
+ append_history(p, "ReliableXmit", "timeout");
+ if (p->method == SIP_CANCEL || p->method == SIP_BYE) {
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ return 10000;
+ }
+
+ /* If we're destroying a subscription, dereference peer object too */
+ if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer)
+ ASTOBJ_UNREF(p->relatedpeer,sip_destroy_peer);
+
+ /* Reset schedule ID */
+ p->autokillid = -1;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Auto destroying SIP dialog '%s'\n", p->callid);
+ append_history(p, "AutoDestroy", "%s", p->callid);
+ 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);
+ } else if (p->refer && !ast_test_flag(&p->flags[0], SIP_ALREADYGONE)) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Finally hanging up channel after transfer: %s\n", p->callid);
+ transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ } else
+ sip_destroy(p);
+ 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 = 500; /* Set timer T1 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);
+ if (!ast_test_flag(&p->flags[0], SIP_NO_HISTORY))
+ append_history(p, "SchedDestroy", "%d ms", ms);
+
+ AST_SCHED_DEL(sched, p->autokillid);
+ p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, p);
+}
+
+/*! \brief Cancel destruction of SIP dialog */
+static int sip_cancel_destroy(struct sip_pvt *p)
+{
+ int res = 0;
+ if (p->autokillid > -1) {
+ if (!(res = ast_sched_del(sched, p->autokillid))) {
+ append_history(p, "CancelDestroy", "");
+ p->autokillid = -1;
+ }
+ }
+ return res;
+}
+
+/*! \brief Acknowledges receipt of a packet and stops retransmission
+ * called with p locked*/
+static void __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+{
+ struct sip_pkt *cur, *prev = NULL;
+
+ /* Just in case... */
+ char *msg;
+ int res = FALSE;
+
+ msg = sip_methods[sipmethod].text;
+
+ for (cur = p->packets; cur; prev = cur, cur = cur->next) {
+ if ((cur->seqno == seqno) && ((ast_test_flag(cur, FLAG_RESPONSE)) == resp) &&
+ ((ast_test_flag(cur, FLAG_RESPONSE)) ||
+ (!strncasecmp(msg, cur->data, strlen(msg)) && (cur->data[strlen(msg)] < 33)))) {
+ if (!resp && (seqno == p->pendinginvite)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Acked pending invite %d\n", p->pendinginvite);
+ p->pendinginvite = 0;
+ }
+ /* this is our baby */
+ res = TRUE;
+ UNLINK(cur, p->packets, prev);
+ if (cur->retransid > -1) {
+ if (sipdebug && option_debug > 3)
+ ast_log(LOG_DEBUG, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
+ }
+ /* This odd section is designed to thwart a
+ * race condition in the packet scheduler. There are
+ * two conditions under which deleting the packet from the
+ * scheduler can fail.
+ *
+ * 1. The packet has been removed from the scheduler because retransmission
+ * is being attempted. The problem is that if the packet is currently attempting
+ * retransmission and we are at this point in the code, then that MUST mean
+ * that retrans_pkt is waiting on p's lock. Therefore we will relinquish the
+ * lock temporarily to allow retransmission.
+ *
+ * 2. The packet has reached its maximum number of retransmissions and has
+ * been permanently removed from the packet scheduler. If this is the case, then
+ * the packet's retransid will be set to -1. The atomicity of the setting and checking
+ * of the retransid to -1 is ensured since in both cases p's lock is held.
+ */
+ while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) {
+ DEADLOCK_AVOIDANCE(&p->lock);
+ }
+ free(cur);
+ break;
+ }
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Stopping retransmission on '%s' of %s %d: Match %s\n", p->callid, resp ? "Response" : "Request", seqno, res == FALSE ? "Not Found" : "Found");
+}
+
+/*! \brief Pretend to ack all packets
+ * called with p locked */
+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, ast_test_flag(cur, FLAG_RESPONSE), 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 && ast_test_flag(cur, FLAG_RESPONSE) == resp &&
+ (ast_test_flag(cur, FLAG_RESPONSE) || method_match(sipmethod, cur->data))) {
+ /* this is our baby */
+ if (cur->retransid > -1) {
+ if (option_debug > 3 && sipdebug)
+ ast_log(LOG_DEBUG, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
+ }
+ AST_SCHED_DEL(sched, cur->retransid);
+ res = 0;
+ break;
+ }
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "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 */
+ snprintf(req->data + req->len, sizeof(req->data) - req->len, "\r\n");
+ 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 (!ast_test_flag(&p->flags[0], SIP_NO_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;
+
+ 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 (!ast_test_flag(&p->flags[0], SIP_NO_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:
+
+ "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
+ */
+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 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 *username, const char *fullcontact, int expirey)
+{
+ char port[10];
+ char ipaddr[INET_ADDRSTRLEN];
+ char regseconds[20];
+
+ char *sysname = ast_config_AST_SYSTEM_NAME;
+ char *syslabel = NULL;
+
+ time_t nowtime = time(NULL) + expirey;
+ const char *fc = fullcontact ? "fullcontact" : NULL;
+
+ 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 (ast_test_flag(&global_flags[1], SIP_PAGE2_RTSAVE_SYSNAME))
+ syslabel = "regserver";
+
+ if (fc)
+ ast_update_realtime("sippeers", "name", peername, "ipaddr", ipaddr,
+ "port", port, "regseconds", regseconds,
+ "username", username, fc, fullcontact, syslabel, sysname, NULL); /* note fc and syslabel _can_ be NULL */
+ else
+ ast_update_realtime("sippeers", "name", peername, "ipaddr", ipaddr,
+ "port", port, "regseconds", regseconds,
+ "username", username, 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) {
+ if (!ast_exists_extension(NULL, context, ext, 1, NULL)) {
+ ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop",
+ ast_strdup(peer->name), ast_free, "SIP");
+ }
+ } else {
+ ast_context_remove_extension(context, ext, 1, NULL);
+ }
+ }
+}
+
+/*! \brief Destroy peer object from memory */
+static void sip_destroy_peer(struct sip_peer *peer)
+{
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Destroying SIP peer %s\n", peer->name);
+
+ /* Delete it, it needs to disappear */
+ if (peer->call)
+ sip_destroy(peer->call);
+
+ if (peer->mwipvt) /* We have an active subscription, delete it */
+ sip_destroy(peer->mwipvt);
+
+ if (peer->chanvars) {
+ ast_variables_destroy(peer->chanvars);
+ peer->chanvars = NULL;
+ }
+
+ register_peer_exten(peer, FALSE);
+ ast_free_ha(peer->ha);
+ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_SELFDESTRUCT))
+ apeerobjs--;
+ else if (ast_test_flag(&peer->flags[0], SIP_REALTIME))
+ rpeerobjs--;
+ else
+ speerobjs--;
+ clear_realm_authentication(peer->auth);
+ peer->auth = NULL;
+ 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 (ast_test_flag(&global_flags[1], SIP_PAGE2_RTUPDATE) &&
+ (ast_test_flag(&p->flags[0], SIP_REALTIME) || rtcachefriends)) {
+ realtime_update_peer(p->name, &p->addr, p->username, rtcachefriends ? p->fullcontact : NULL, expiry);
+ }
+}
+
+
+/*! \brief realtime_peer: Get peer from realtime storage
+ * Checks the "sippeers" realtime family from extconfig.conf
+ * \todo Consider adding check of port address when matching here to follow the same
+ * algorithm as for static peers. Will we break anything by adding that?
+*/
+static struct sip_peer *realtime_peer(const char *newpeername, struct sockaddr_in *sin, int devstate_only)
+{
+ struct sip_peer *peer=NULL;
+ struct ast_variable *var = NULL;
+ struct ast_config *peerlist = NULL;
+ struct ast_variable *tmp;
+ struct ast_flags flags = {0};
+ const char *iabuf = NULL;
+ char portstring[6]; /*up to five digits plus null terminator*/
+ const char *insecure;
+ char *cat = NULL;
+ unsigned short portnum;
+
+ /* First check on peer name */
+ if (newpeername) {
+ 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 && sin) {
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "host")) {
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ if (!(hp = ast_gethostbyname(tmp->value, &ahp)) || (memcmp(&hp->h_addr, &sin->sin_addr, sizeof(hp->h_addr)))) {
+ /* No match */
+ ast_variables_destroy(var);
+ var = NULL;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!var && sin) { /* Then check on IP address */
+ iabuf = ast_inet_ntoa(sin->sin_addr);
+ portnum = ntohs(sin->sin_port);
+ sprintf(portstring, "%d", portnum);
+ var = ast_load_realtime("sippeers", "host", iabuf, "port", portstring, NULL); /* First check for fixed IP hosts */
+ if (!var)
+ var = ast_load_realtime("sippeers", "ipaddr", iabuf, "port", portstring, NULL); /* Then check for registered hosts */
+ if (!var) {
+ peerlist = ast_load_realtime_multientry("sippeers", "host", iabuf, NULL); /*No exact match, see if port is insecure, try host match first*/
+ if(peerlist){
+ while((cat = ast_category_browse(peerlist, cat)))
+ {
+ insecure = ast_variable_retrieve(peerlist, cat, "insecure");
+ set_insecure_flags(&flags, insecure, -1);
+ if(ast_test_flag(&flags, SIP_INSECURE_PORT)) {
+ var = ast_category_root(peerlist, cat);
+ break;
+ }
+ }
+ }
+ if(!var) {
+ ast_config_destroy(peerlist);
+ peerlist = NULL; /*for safety's sake*/
+ cat = NULL;
+ peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", iabuf, NULL); /*No exact match, see if port is insecure, now try ip address match*/
+ if(peerlist) {
+ while((cat = ast_category_browse(peerlist, cat)))
+ {
+ insecure = ast_variable_retrieve(peerlist, cat, "insecure");
+ set_insecure_flags(&flags, insecure, -1);
+ if(ast_test_flag(&flags, SIP_INSECURE_PORT)) {
+ var = ast_category_root(peerlist, cat);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ 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")) {
+ ast_variables_destroy(var);
+ 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", iabuf);
+ 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, NULL, 1);
+ if (!peer) {
+ if(peerlist)
+ ast_config_destroy(peerlist);
+ else
+ ast_variables_destroy(var);
+ return NULL;
+ }
+
+ if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && !devstate_only) {
+ /* 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)) {
+ if (!AST_SCHED_DEL(sched, peer->expire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ peer->expire = ast_sched_add(sched, (global_rtautoclear) * 1000, expire_register, ASTOBJ_REF(peer));
+ if (peer->expire == -1) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ }
+ ASTOBJ_CONTAINER_LINK(&peerl,peer);
+ }
+ ast_set_flag(&peer->flags[0], SIP_REALTIME);
+ if(peerlist)
+ ast_config_destroy(peerlist);
+ else
+ ast_variables_destroy(var);
+ 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 */
+static struct sip_peer *find_peer(const char *peer, struct sockaddr_in *sin, int realtime, int devstate_only)
+{
+ 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 || devstate_only))
+ p = realtime_peer(peer, sin, devstate_only);
+
+ return p;
+}
+
+/*! \brief Remove user object from in-memory storage */
+static void sip_destroy_user(struct sip_user *user)
+{
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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 (ast_test_flag(&user->flags[0], SIP_REALTIME))
+ ruserobjs--;
+ else
+ suserobjs--;
+ 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, NULL, !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++;
+ }
+ ast_set_flag(&user->flags[0], SIP_REALTIME);
+ 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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Setting NAT on RTP to %s\n", mode);
+ ast_rtp_setnat(p->rtp, natflags);
+ }
+ if (p->vrtp) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Setting NAT on VRTP to %s\n", mode);
+ ast_rtp_setnat(p->vrtp, natflags);
+ }
+ if (p->udptl) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Setting NAT on UDPTL to %s\n", mode);
+ ast_udptl_setnat(p->udptl, natflags);
+ }
+}
+
+/*! \brief Create address structure from peer reference.
+ * return -1 on error, 0 on success.
+ */
+static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
+{
+ 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;
+ }
+ dialog->prefs = peer->prefs;
+ if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_T38SUPPORT)) {
+ dialog->t38.capability = global_t38_capability;
+ if (dialog->udptl) {
+ if (ast_udptl_get_error_correction_scheme(dialog->udptl) == UDPTL_ERROR_CORRECTION_FEC )
+ dialog->t38.capability |= T38FAX_UDP_EC_FEC;
+ else if (ast_udptl_get_error_correction_scheme(dialog->udptl) == UDPTL_ERROR_CORRECTION_REDUNDANCY )
+ dialog->t38.capability |= T38FAX_UDP_EC_REDUNDANCY;
+ else if (ast_udptl_get_error_correction_scheme(dialog->udptl) == UDPTL_ERROR_CORRECTION_NONE )
+ dialog->t38.capability |= T38FAX_UDP_EC_NONE;
+ dialog->t38.capability |= T38FAX_RATE_MANAGEMENT_TRANSFERED_TCF;
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG,"Our T38 capability (%d)\n", dialog->t38.capability);
+ }
+ 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) {
+ 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) {
+ 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);
+ }
+
+ 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);
+ if (!dialog->initreq.headers && !ast_strlen_zero(peer->fromdomain)) {
+ char *tmpcall;
+ char *c;
+ 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(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 (!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);
+ dialog->maxtime = peer->maxms;
+ dialog->callgroup = peer->callgroup;
+ dialog->pickupgroup = peer->pickupgroup;
+ dialog->peerauth = peer->auth;
+ dialog->allowtransfer = peer->allowtransfer;
+ /* Set timer T1 to RTT for this peer (if known by qualify=) */
+ /* Minimum is settable or default to 100 ms */
+ if (peer->maxms && peer->lastms)
+ dialog->timer_t1 = peer->lastms < global_t1min ? global_t1min : peer->lastms;
+ 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;
+ dialog->jointnoncodeccapability = dialog->noncodeccapability;
+ ast_string_field_set(dialog, context, peer->context);
+ dialog->rtptimeout = peer->rtptimeout;
+ if (peer->call_limit)
+ ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT);
+ dialog->maxcallbitrate = peer->maxcallbitrate;
+
+ 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 *p;
+ char *port;
+ int portno;
+ char host[MAXHOSTNAMELEN], *hostn;
+ char peer[256];
+
+ ast_copy_string(peer, opeer, sizeof(peer));
+ port = strchr(peer, ':');
+ if (port)
+ *port++ = '\0';
+ dialog->sa.sin_family = AF_INET;
+ dialog->timer_t1 = 500; /* Default SIP retransmission timer T1 (RFC 3261) */
+ p = find_peer(peer, NULL, 1, 0);
+
+ if (p) {
+ int res = create_addr_from_peer(dialog, p);
+ if (port) {
+ portno = atoi(port);
+ dialog->sa.sin_port = dialog->recv.sin_port = htons(portno);
+ }
+ ASTOBJ_UNREF(p, sip_destroy_peer);
+ return res;
+ }
+ hostn = peer;
+ portno = port ? atoi(port) : STANDARD_SIP_PORT;
+ if (srvlookup) {
+ char service[MAXHOSTNAMELEN];
+ int tportno;
+ int ret;
+
+ snprintf(service, sizeof(service), "_sip._udp.%s", peer);
+ 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", peer);
+ return -1;
+ }
+ ast_string_field_set(dialog, tohost, peer);
+ 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 */
+static int auto_congest(const void *nothing)
+{
+ struct sip_pvt *p = (struct sip_pvt *)nothing;
+
+ ast_mutex_lock(&p->lock);
+ p->initid = -1;
+ 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);
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ 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, xmitres = 0;
+ struct sip_pvt *p;
+ struct varshead *headp;
+ struct ast_var_t *current;
+ const char *referer = NULL; /* SIP refererer */
+
+ p = ast->tech_pvt;
+ 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->distinctive_ring && !strcasecmp(ast_var_name(current), "ALERT_INFO")) {
+ /* Check whether there is a ALERT_INFO variable */
+ p->options->distinctive_ring = 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 referer */
+ 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;
+ if (option_debug)
+ ast_log(LOG_DEBUG,"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[SIPBUFSIZE/2];
+
+ if (referer) {
+ if (sipdebug && option_debug > 2)
+ ast_log(LOG_DEBUG, "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);
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Outgoing Call for %s\n", p->username);
+
+ res = update_call_counter(p, INC_CALL_RINGING);
+ if ( res != -1 ) {
+ 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 {
+ p->t38.jointcapability = p->t38.capability;
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG,"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; /* Transmission error */
+
+ p->invitestate = INV_CALLING;
+
+ /* Initialize auto-congest time */
+ AST_SCHED_DEL(sched, p->initid);
+ p->initid = ast_sched_add(sched, p->maxtime ? (p->maxtime * 4) : SIP_TRANS_TIMEOUT, auto_congest, p);
+ }
+ } else {
+ ast->hangupcause = AST_CAUSE_USER_BUSY;
+ }
+ 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 */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Destroying active SIP dialog for registry %s@%s\n", reg->username, reg->hostname);
+ sip_destroy(reg->call);
+ }
+ AST_SCHED_DEL(sched, reg->expire);
+ AST_SCHED_DEL(sched, reg->timeout);
+ ast_string_field_free_memory(reg);
+ regobjs--;
+ free(reg);
+
+}
+
+/*! \brief Execute destruction of SIP dialog structure, release memory */
+static int __sip_destroy(struct sip_pvt *p, int lockowner)
+{
+ struct sip_pvt *cur, *prev = NULL;
+ struct sip_pkt *cp;
+ struct sip_request *req;
+
+ /* We absolutely cannot destroy the rtp struct while a bridge is active or we WILL crash */
+ if (p->rtp && ast_rtp_get_bridged(p->rtp)) {
+ ast_verbose("Bridge still active. Delaying destroy of SIP dialog '%s' Method: %s\n", p->callid, sip_methods[p->method].text);
+ return -1;
+ }
+
+ if (p->vrtp && ast_rtp_get_bridged(p->vrtp)) {
+ ast_verbose("Bridge still active. Delaying destroy of SIP dialog '%s' Method: %s\n", p->callid, sip_methods[p->method].text);
+ return -1;
+ }
+
+ if (sip_debug_test_pvt(p) || option_debug > 2)
+ 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);
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "This call did not properly clean up call limits. Call ID %s\n", p->callid);
+ }
+
+ /* Unlink us from the owner if we have one */
+ if (p->owner) {
+ if (lockowner)
+ ast_channel_lock(p->owner);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Detaching from %s\n", p->owner->name);
+ p->owner->tech_pvt = NULL;
+ /* Make sure that the channel knows its backend is going away */
+ p->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ if (lockowner)
+ ast_channel_unlock(p->owner);
+ /* Give the channel a chance to react before deallocation */
+ usleep(1);
+ }
+
+ /* Remove link from peer to subscription of MWI */
+ if (p->relatedpeer) {
+ if (p->relatedpeer->mwipvt == p) {
+ p->relatedpeer->mwipvt = NULL;
+ }
+ ASTOBJ_UNREF(p->relatedpeer, sip_destroy_peer);
+ }
+
+ if (dumphistory)
+ sip_dump_history(p);
+
+ if (p->options)
+ free(p->options);
+
+ if (p->stateid > -1)
+ ast_extension_state_del(p->stateid, NULL);
+ AST_SCHED_DEL(sched, p->initid);
+ AST_SCHED_DEL(sched, p->waitid);
+ AST_SCHED_DEL(sched, p->autokillid);
+ AST_SCHED_DEL(sched, p->request_queue_sched_id);
+
+ if (p->rtp) {
+ ast_rtp_destroy(p->rtp);
+ }
+ if (p->vrtp) {
+ ast_rtp_destroy(p->vrtp);
+ }
+ if (p->udptl)
+ ast_udptl_destroy(p->udptl);
+ if (p->refer)
+ 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;
+ ASTOBJ_UNREF(p->registry, sip_registry_destroy);
+ }
+
+ /* Clear history */
+ if (p->history) {
+ struct sip_history *hist;
+ while ( (hist = AST_LIST_REMOVE_HEAD(p->history, list)) ) {
+ free(hist);
+ p->history_entries--;
+ }
+ free(p->history);
+ p->history = NULL;
+ }
+
+ while ((req = AST_LIST_REMOVE_HEAD(&p->request_queue, next))) {
+ ast_free(req);
+ }
+
+ for (prev = NULL, cur = iflist; cur; prev = cur, cur = cur->next) {
+ if (cur == p) {
+ UNLINK(cur, iflist, prev);
+ break;
+ }
+ }
+ if (!cur) {
+ ast_log(LOG_WARNING, "Trying to destroy \"%s\", not found in dialog list?!?! \n", p->callid);
+ return 0;
+ }
+
+ /* remove all current packets in this dialog */
+ while((cp = p->packets)) {
+ p->packets = p->packets->next;
+ AST_SCHED_DEL(sched, cp->retransid);
+ free(cp);
+ }
+ if (p->chanvars) {
+ ast_variables_destroy(p->chanvars);
+ p->chanvars = NULL;
+ }
+ ast_mutex_destroy(&p->lock);
+
+ ast_string_field_free_memory(p);
+
+ free(p);
+ return 0;
+}
+
+/*! \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 propably update storage with inuse counter...
+ *
+ * \return 0 if call is ok (no call limit, below treshold)
+ * -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 = ast_test_flag(&fup->flags[1], SIP_PAGE2_OUTGOING_CALL);
+ struct sip_user *u = NULL;
+ struct sip_peer *p = NULL;
+
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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, 0) ) ) { /* 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) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "%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:
+ if ( *inuse > 0 ) {
+ if (ast_test_flag(&fup->flags[0], SIP_INC_COUNT)) {
+ (*inuse)--;
+ ast_clear_flag(&fup->flags[0], SIP_INC_COUNT);
+ }
+ } else {
+ *inuse = 0;
+ }
+ if (inringing) {
+ if (ast_test_flag(&fup->flags[1], SIP_PAGE2_INC_RINGING)) {
+ if (*inringing > 0)
+ (*inringing)--;
+ else if (!ast_test_flag(&p->flags[0], SIP_REALTIME) || ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS))
+ ast_log(LOG_WARNING, "Inringing for peer '%s' < 0?\n", fup->peername);
+ ast_clear_flag(&fup->flags[1], SIP_PAGE2_INC_RINGING);
+ }
+ }
+ 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, 0);
+ }
+ if (option_debug > 1 || sipdebug) {
+ ast_log(LOG_DEBUG, "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 > 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)
+ ASTOBJ_UNREF(u, sip_destroy_user);
+ else
+ ASTOBJ_UNREF(p, sip_destroy_peer);
+ return -1;
+ }
+ }
+ if (inringing && (event == INC_CALL_RINGING)) {
+ if (!ast_test_flag(&fup->flags[1], SIP_PAGE2_INC_RINGING)) {
+ (*inringing)++;
+ ast_set_flag(&fup->flags[1], SIP_PAGE2_INC_RINGING);
+ }
+ }
+ /* Continue */
+ (*inuse)++;
+ ast_set_flag(&fup->flags[0], SIP_INC_COUNT);
+ if (option_debug > 1 || sipdebug) {
+ ast_log(LOG_DEBUG, "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) {
+ if (ast_test_flag(&fup->flags[1], SIP_PAGE2_INC_RINGING)) {
+ if (*inringing > 0)
+ (*inringing)--;
+ else if (!ast_test_flag(&p->flags[0], SIP_REALTIME) || ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS))
+ ast_log(LOG_WARNING, "Inringing for peer '%s' < 0?\n", p->name);
+ ast_clear_flag(&fup->flags[1], SIP_PAGE2_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);
+ ASTOBJ_UNREF(p, sip_destroy_peer);
+ } else /* u must be set */
+ ASTOBJ_UNREF(u, sip_destroy_user);
+ return 0;
+}
+
+/*! \brief Destroy SIP call structure */
+static void sip_destroy(struct sip_pvt *p)
+{
+ ast_mutex_lock(&iflock);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Destroying SIP dialog %s\n", p->callid);
+ __sip_destroy(p, 1);
+ ast_mutex_unlock(&iflock);
+}
+
+/*! \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: /* Ambigous */
+ 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 */
+ case AST_CAUSE_UNREGISTERED: /* 20 */
+ 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:
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Asked to hangup channel that was not connected\n");
+ return 0;
+ }
+
+ 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 (option_debug && sipdebug)
+ ast_log(LOG_DEBUG, "update_call_counter(%s) - decrement call limit counter on hangup\n", p->username);
+ update_call_counter(p, DEC_CALL_LIMIT);
+ }
+ if (option_debug >3)
+ ast_log(LOG_DEBUG, "SIP Transfer: Not hanging up right now... Rescheduling hangup for %s.\n", p->callid);
+ if (p->autokillid > -1 && sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Really hang up next time */
+ ast_clear_flag(&p->flags[0], SIP_NEEDDESTROY);
+ p->owner->tech_pvt = NULL;
+ p->owner = NULL; /* Owner will be gone after we return, so take it away */
+ return 0;
+ }
+ if (option_debug) {
+ if (ast_test_flag(ast, AST_FLAG_ZOMBIE) && p->refer && option_debug)
+ ast_log(LOG_DEBUG, "SIP Transfer: Hanging up Zombie channel %s after transfer ... Call-ID: %s\n", ast->name, p->callid);
+ else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Hangup call %s, SIP callid %s)\n", ast->name, p->callid);
+ }
+ }
+ if (option_debug && ast_test_flag(ast, AST_FLAG_ZOMBIE))
+ ast_log(LOG_DEBUG, "Hanging up zombie call. Be scared.\n");
+
+ ast_mutex_lock(&p->lock);
+ if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) {
+ if (option_debug && sipdebug)
+ ast_log(LOG_DEBUG, "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");
+ ast_mutex_unlock(&p->lock);
+ return 0;
+ }
+ /* If the call is not UP, we need to send CANCEL instead of BYE */
+ if (ast->_state == AST_STATE_RING || ast->_state == AST_STATE_RINGING || (p->invitestate < INV_COMPLETED && ast->_state != AST_STATE_UP)) {
+ needcancel = TRUE;
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "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 = NULL;
+
+ 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 (ast_test_flag(&p->flags[0], SIP_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 (!ast_test_flag(&p->flags[0], SIP_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);
+ p->invitestate = INV_CANCELLED;
+
+ /* 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? Yes we do or else we will get hung dialogs and those are no fun. */
+ 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->lastinvite, 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);
+ }
+ 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 = "";
+ if (p->rtp)
+ audioqos = ast_rtp_get_quality(p->rtp, NULL);
+ if (p->vrtp)
+ videoqos = ast_rtp_get_quality(p->vrtp, NULL);
+ /* Send a hangup */
+ transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
+
+ /* Get RTCP quality before end of call */
+ if (!ast_test_flag(&p->flags[0], SIP_NO_HISTORY)) {
+ if (p->rtp)
+ append_history(p, "RTCPaudio", "Quality:%s", audioqos);
+ if (p->vrtp)
+ append_history(p, "RTCPvideo", "Quality:%s", videoqos);
+ }
+ if (p->rtp && oldowner)
+ pbx_builtin_setvar_helper(oldowner, "RTPAUDIOQOS", audioqos);
+ if (p->vrtp && oldowner)
+ pbx_builtin_setvar_helper(oldowner, "RTPVIDEOQOS", videoqos);
+ } 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);
+ AST_SCHED_DEL(sched, p->waitid);
+ if (sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ }
+ }
+ }
+ if (needdestroy)
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ ast_mutex_unlock(&p->lock);
+ 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;
+
+ ast_mutex_lock(&p->lock);
+ if (ast->_state != AST_STATE_UP) {
+ try_suggested_sip_codec(p);
+
+ ast_setstate(ast, AST_STATE_UP);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "SIP answering channel: %s\n", ast->name);
+ if (p->t38.state == T38_PEER_DIRECT) {
+ p->t38.state = T38_ENABLED;
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG,"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);
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ } else {
+ res = transmit_response_with_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL);
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ 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) {
+ ast_mutex_lock(&p->lock);
+ 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)) {
+ ast_rtp_new_source(p->rtp);
+ p->invitestate = INV_EARLY_MEDIA;
+ transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE);
+ ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
+ }
+ p->lastrtptx = time(NULL);
+ 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) {
+ /* 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)) {
+ p->invitestate = INV_EARLY_MEDIA;
+ transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE);
+ ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
+ }
+ p->lastrtptx = time(NULL);
+ res = ast_rtp_write(p->vrtp, frame);
+ }
+ ast_mutex_unlock(&p->lock);
+ }
+ break;
+ case AST_FRAME_IMAGE:
+ return 0;
+ break;
+ case AST_FRAME_MODEM:
+ if (p) {
+ ast_mutex_lock(&p->lock);
+ /* 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);
+ ast_mutex_unlock(&p->lock);
+ }
+ 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) && option_debug)
+ ast_log(LOG_DEBUG, "New channel is zombie\n");
+ if (oldchan && ast_test_flag(oldchan, AST_FLAG_ZOMBIE) && option_debug)
+ ast_log(LOG_DEBUG, "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;
+
+ if (!p) {
+ ast_log(LOG_WARNING, "No pvt after masquerade. Strange things may happen\n");
+ return -1;
+ }
+
+ ast_mutex_lock(&p->lock);
+ 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;
+ /* Re-invite RTP back to Asterisk. Needed if channel is masqueraded out of a native
+ RTP bridge (i.e., RTP not going through Asterisk): RTP bridge code might not be
+ able to do this if the masquerade happens before the bridge breaks (e.g., AMI
+ redirect of both channels). Note that a channel can not be masqueraded *into*
+ a native bridge. So there is no danger that this breaks a native bridge that
+ should stay up. */
+ sip_set_rtp_peer(newchan, NULL, NULL, 0, 0);
+ ret = 0;
+ }
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "SIP Fixup: New owner for dialogue %s: %s (Old parent: %s)\n", p->callid, p->owner->name, oldchan->name);
+
+ ast_mutex_unlock(&p->lock);
+ return ret;
+}
+
+static int sip_senddigit_begin(struct ast_channel *ast, char digit)
+{
+ struct sip_pvt *p = ast->tech_pvt;
+ int res = 0;
+
+ ast_mutex_lock(&p->lock);
+ 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;
+ }
+ ast_mutex_unlock(&p->lock);
+
+ 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;
+
+ ast_mutex_lock(&p->lock);
+ switch (ast_test_flag(&p->flags[0], SIP_DTMF)) {
+ case SIP_DTMF_INFO:
+ 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;
+ }
+ ast_mutex_unlock(&p->lock);
+
+ 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 = "";
+ ast_mutex_lock(&p->lock);
+ if (ast->_state == AST_STATE_RING)
+ res = sip_sipredirect(p, dest);
+ else
+ res = transmit_refer(p, dest);
+ ast_mutex_unlock(&p->lock);
+ 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;
+
+ ast_mutex_lock(&p->lock);
+ 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_reliable(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_reliable(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);
+ ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
+ break;
+ }
+ res = -1;
+ break;
+ case AST_CONTROL_HOLD:
+ ast_rtp_new_source(p->rtp);
+ ast_moh_start(ast, data, p->mohinterpret);
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_rtp_new_source(p->rtp);
+ ast_moh_stop(ast);
+ break;
+ case AST_CONTROL_VIDUPDATE: /* Request a video frame update */
+ if (p->vrtp && !ast_test_flag(&p->flags[0], SIP_NOVIDEO)) {
+ transmit_info_with_vidupdate(p);
+ /* ast_rtcp_send_h261fur(p->vrtp); */
+ } else
+ res = -1;
+ break;
+ case AST_CONTROL_SRCUPDATE:
+ ast_rtp_new_source(p->rtp);
+ break;
+ case -1:
+ res = -1;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", condition);
+ res = -1;
+ break;
+ }
+ ast_mutex_unlock(&p->lock);
+ 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 needvideo = 0, video = 0;
+ 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;
+
+ ast_mutex_unlock(&i->lock);
+ /* 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");
+ ast_mutex_lock(&i->lock);
+ return NULL;
+ }
+ ast_mutex_lock(&i->lock);
+
+ if (ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_INFO)
+ tmp->tech = &sip_tech_info;
+ else
+ tmp->tech = &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;
+ } else if (i->capability) { /* Our configured capability for this peer */
+ what = i->capability;
+ video = i->capability & AST_FORMAT_VIDEO_MASK;
+ } else {
+ what = global_capability; /* Global codec support */
+ video = global_capability & AST_FORMAT_VIDEO_MASK;
+ }
+
+ /* Set the native formats for audio and merge in video */
+ tmp->nativeformats = ast_codec_choose(&i->prefs, what, 1) | video;
+ if (option_debug > 2) {
+ char buf[SIPBUFSIZE];
+ ast_log(LOG_DEBUG, "*** Our native formats are %s \n", ast_getformatname_multiple(buf, SIPBUFSIZE, tmp->nativeformats));
+ ast_log(LOG_DEBUG, "*** Joint capabilities are %s \n", ast_getformatname_multiple(buf, SIPBUFSIZE, i->jointcapability));
+ ast_log(LOG_DEBUG, "*** Our capabilities are %s \n", ast_getformatname_multiple(buf, SIPBUFSIZE, i->capability));
+ ast_log(LOG_DEBUG, "*** AST_CODEC_CHOOSE formats are %s \n", ast_getformatname_multiple(buf, SIPBUFSIZE, ast_codec_choose(&i->prefs, what, 1)));
+ if (i->prefcodec)
+ ast_log(LOG_DEBUG, "*** Our preferred formats from the incoming channel are %s \n", ast_getformatname_multiple(buf, SIPBUFSIZE, 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 (option_debug > 2) {
+ if (needvideo)
+ ast_log(LOG_DEBUG, "This channel can handle video! HOLLYWOOD next!\n");
+ else
+ ast_log(LOG_DEBUG, "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);
+ }
+ if (i->rtp) {
+ tmp->fds[0] = ast_rtp_fd(i->rtp);
+ tmp->fds[1] = ast_rtcp_fd(i->rtp);
+ }
+ if (needvideo && i->vrtp) {
+ tmp->fds[2] = ast_rtp_fd(i->vrtp);
+ tmp->fds[3] = ast_rtcp_fd(i->vrtp);
+ }
+ if (i->udptl) {
+ tmp->fds[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 = 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->useragent))
+ pbx_builtin_setvar_helper(tmp, "SIPUSERAGENT", i->useragent);
+ 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 (!ast_test_flag(&i->flags[0], SIP_NO_HISTORY))
+ append_history(i, "NewChan", "Channel %s - from %s", tmp->name, i->callid);
+
+ 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 */
+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 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)) {
+ if (option_debug) {
+ ast_log(LOG_DEBUG, "Bogus frame of format '%s' received from '%s'!\n",
+ ast_getformatname(f->subclass), p->owner->name);
+ }
+ return &ast_null_frame;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 (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') {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Fax CNG detected on %s\n", ast->name);
+ *faxdetect = 1;
+ } else if (option_debug) {
+ ast_log(LOG_DEBUG, "* 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;
+
+ ast_mutex_lock(&p->lock);
+ 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) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Sending reinvite on SIP (%s) for T.38 negotiation.\n",ast->name);
+ p->t38.state = T38_LOCAL_REINVITE;
+ transmit_reinvite_with_t38_sdp(p);
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "T38 state changed to %d on channel %s\n", p->t38.state, ast->name);
+ }
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Deferring reinvite on SIP (%s) - it will be re-negotiated for T.38\n", ast->name);
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ }
+
+ /* Only allow audio through if they sent progress with SDP, or if the channel is actually answered */
+ if (fr && fr->frametype == AST_FRAME_VOICE && p->invitestate != INV_EARLY_MEDIA && ast->_state != AST_STATE_UP) {
+ fr = &ast_null_frame;
+ }
+
+ ast_mutex_unlock(&p->lock);
+ 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));
+
+ 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 SIP_PVT structure and set defaults */
+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)) {
+ free(p);
+ return NULL;
+ }
+
+ ast_mutex_init(&p->lock);
+
+ p->method = intended_method;
+ p->initid = -1;
+ p->waitid = -1;
+ p->autokillid = -1;
+ p->request_queue_sched_id = -1;
+ p->subscribed = NONE;
+ p->stateid = -1;
+ p->prefs = default_prefs; /* Set default codecs for this call */
+
+ if (intended_method != SIP_OPTIONS) /* Peerpoke has it's own system */
+ p->timer_t1 = 500; /* Default SIP retransmission timer T1 (RFC 3261) */
+
+ if (sin) {
+ p->sa = *sin;
+ if (ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip))
+ p->ourip = __ourip;
+ } else
+ p->ourip = __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);
+
+ ast_set2_flag(&p->flags[0], !recordhistory, SIP_NO_HISTORY);
+
+ 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_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_log(LOG_WARNING, "Unable to create RTP audio %s session: %s\n",
+ ast_test_flag(&p->flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "and video" : "", strerror(errno));
+ ast_mutex_destroy(&p->lock);
+ if (p->chanvars) {
+ ast_variables_destroy(p->chanvars);
+ p->chanvars = NULL;
+ }
+ free(p);
+ return NULL;
+ }
+ 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_settos(p->rtp, global_tos_audio);
+ 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_settos(p->vrtp, global_tos_video);
+ 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->udptl)
+ ast_udptl_settos(p->udptl, global_tos_audio);
+ p->maxcallbitrate = default_maxcallbitrate;
+ p->autoframing = global_autoframing;
+ ast_rtp_codec_setpref(p->rtp, &p->prefs);
+ }
+
+ 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) {
+ p->t38.capability = global_t38_capability;
+ 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_FEC)
+ p->t38.capability |= T38FAX_UDP_EC_FEC;
+ 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;
+ p->t38.jointcapability = p->t38.capability;
+ }
+ ast_string_field_set(p, context, default_context);
+
+ AST_LIST_HEAD_INIT_NOLOCK(&p->request_queue);
+
+ /* Add to active dialog list */
+ ast_mutex_lock(&iflock);
+ p->next = iflist;
+ iflist = p;
+ ast_mutex_unlock(&iflock);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 Connect incoming SIP message to current dialog or create new dialog structure
+ Called by handle_request, 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)))
+ ast_set_flag(req, SIP_PKT_WITH_TOTAG); /* Used in handle_request/response */
+ gettag(req, "From", fromtag, sizeof(fromtag));
+
+ tag = (req->method == SIP_RESPONSE) ? totag : fromtag;
+
+ if (option_debug > 4 )
+ ast_log(LOG_DEBUG, "= Looking for Call ID: %s (Checking %s) --From tag %s --To-tag %s \n", callid, req->method==SIP_RESPONSE ? "To" : "From", fromtag, totag);
+ }
+
+ ast_mutex_lock(&iflock);
+ for (p = iflist; 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);
+ if (pedanticsipchecking && found) {
+ found = ast_strlen_zero(tag) || ast_strlen_zero(p->theirtag) || !ast_test_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED) || !strcmp(p->theirtag, tag);
+ }
+ }
+
+ if (option_debug > 4)
+ ast_log(LOG_DEBUG, "= %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 && option_debug > 4)
+ ast_log(LOG_DEBUG, "= 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 */
+ ast_mutex_lock(&p->lock);
+ ast_mutex_unlock(&iflock);
+ return p;
+ }
+ }
+ ast_mutex_unlock(&iflock);
+
+ /* 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 */
+ ast_mutex_lock(&p->lock);
+ } 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");
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Failed allocating SIP dialog, sending 500 Server internal error and giving up\n");
+ }
+ }
+ return p;
+ } 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");
+ } else if (intended_method != SIP_RESPONSE && intended_method != SIP_ACK) {
+ /* This is a request outside of a dialog that we don't know about
+ ...never reply to an ACK!
+ */
+ transmit_response_using_temp(callid, sin, 1, intended_method, req, "481 Call leg/transaction does not exist");
+ }
+ /* We do not respond to responses for dialogs that we don't know about, we just drop
+ the session quickly */
+
+ return p;
+}
+
+/*! \brief Parse register=> line in sip.conf and add to registry */
+static int sip_register(char *value, int lineno)
+{
+ struct sip_registry *reg;
+ int portnum = 0;
+ char username[256] = "";
+ char *hostname=NULL, *secret=NULL, *authuser=NULL;
+ char *porta=NULL;
+ char *contact=NULL;
+
+ if (!value)
+ return -1;
+ ast_copy_string(username, value, sizeof(username));
+ /* 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] */
+ contact = strchr(hostname, '/');
+ if (contact)
+ *contact++ = '\0';
+ if (ast_strlen_zero(contact))
+ contact = "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");
+ free(reg);
+ return -1;
+ }
+
+ regobjs++;
+ ASTOBJ_INIT(reg);
+ ast_string_field_set(reg, contact, contact);
+ 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->expire = -1;
+ 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 */
+ ASTOBJ_UNREF(reg,sip_registry_destroy);
+ 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 int parse_request(struct sip_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 (sipdebug && option_debug > 3)
+ ast_log(LOG_DEBUG, "Header %d: %s (%d)\n", f, req->header[f], (int) strlen(req->header[f]));
+ if (ast_strlen_zero(req->header[f])) {
+ /* Line by itself means we're now in content */
+ c++;
+ break;
+ }
+ if (f >= SIP_MAX_HEADERS - 1) {
+ ast_log(LOG_WARNING, "Too many SIP headers. Ignoring.\n");
+ } else {
+ f++;
+ req->header[f] = c + 1;
+ }
+ } else if (*c == '\r') {
+ /* Ignore but eliminate \r's */
+ *c = 0;
+ }
+ c++;
+ }
+
+ req->headers = f;
+
+ /* Check a non-newline-terminated last header */
+ if (!ast_strlen_zero(req->header[f])) {
+ if (sipdebug && option_debug > 3)
+ ast_log(LOG_DEBUG, "Header %d: %s (%d)\n", f, req->header[f], (int) strlen(req->header[f]));
+ req->headers++;
+ }
+
+ /* Now we process any body content */
+ f = 0;
+ req->line[f] = c;
+ while (*c) {
+ if (*c == '\n') {
+ /* We've got a new line */
+ *c = 0;
+ if (sipdebug && option_debug > 3)
+ ast_log(LOG_DEBUG, "Line: %s (%d)\n", req->line[f], (int) strlen(req->line[f]));
+ if (f == SIP_MAX_LINES - 1) {
+ ast_log(LOG_WARNING, "Too many SDP lines. Ignoring.\n");
+ break;
+ } else {
+ f++;
+ req->line[f] = c + 1;
+ }
+ } else if (*c == '\r') {
+ /* Ignore and eliminate \r's */
+ *c = 0;
+ }
+ c++;
+ }
+
+ req->lines = f;
+
+ /* Check a non-newline-terminated last line */
+ if (!ast_strlen_zero(req->line[f])) {
+ req->lines++;
+ }
+
+ if (*c)
+ ast_log(LOG_WARNING, "Odd content, extra stuff left over ('%s')\n", c);
+
+ /* Split up the first line parts */
+ return 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 *content_length;
+ const char *search;
+ char *boundary;
+ unsigned int x;
+ int boundaryisquoted = FALSE;
+ int found_application_sdp = FALSE;
+ int found_end_of_headers = FALSE;
+
+ content_length = get_header(req, "Content-Length");
+
+ if (!ast_strlen_zero(content_length)) {
+ if (sscanf(content_length, "%ud", &x) != 1) {
+ ast_log(LOG_WARNING, "Invalid Content-Length: %s\n", content_length);
+ return 0;
+ }
+
+ /* Content-Length of zero means there can't possibly be an
+ SDP here, even if the Content-Type says there is */
+ if (x == 0)
+ return 0;
+ }
+
+ content_type = get_header(req, "Content-Type");
+
+ /* if the body contains only SDP, this is easy */
+ if (!strncasecmp(content_type, "application/sdp", 15)) {
+ 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 Change hold state for a call */
+static void change_hold_state(struct sip_pvt *dialog, struct sip_request *req, int holdstate, int sendonly)
+{
+ if (global_notifyhold && (!holdstate || !ast_test_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD)))
+ sip_peer_hold(dialog, holdstate);
+ if (global_callevents)
+ manager_event(EVENT_FLAG_CALL, holdstate ? "Hold" : "Unhold",
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n",
+ dialog->owner->name,
+ dialog->owner->uniqueid);
+ append_history(dialog, holdstate ? "Hold" : "Unhold", "%s", req->data);
+ if (!holdstate) { /* Put off remote hold */
+ ast_clear_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD); /* Clear both flags */
+ return;
+ }
+ /* No address for RTP, we're on hold */
+
+ if (sendonly == 1) /* One directional hold (sendonly/recvonly) */
+ ast_set_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD_ONEDIR);
+ else if (sendonly == 2) /* Inactive stream */
+ ast_set_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD_INACTIVE);
+ else
+ ast_set_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD_ACTIVE);
+ return;
+}
+
+/*! \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;
+ char host[258];
+ int len = -1;
+ int portno = -1; /*!< RTP Audio port number */
+ int vportno = -1; /*!< RTP Video 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;
+ struct sockaddr_in sin; /*!< media socket address */
+ struct sockaddr_in vsin; /*!< Video socket address */
+
+ const char *codecs;
+ struct hostent *hp; /*!< RTP Audio host IP */
+ struct hostent *vhp = NULL; /*!< RTP video host IP */
+ struct ast_hostent audiohp;
+ struct ast_hostent videohp;
+ int codec;
+ int destiterator = 0;
+ int iterator;
+ int sendonly = -1;
+ int numberofports;
+ struct ast_rtp *newaudiortp, *newvideortp; /* 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[SDP_MAX_RTPMAP_CODECS];
+ int last_rtpmap_codec=0;
+
+ 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 */
+#ifdef LOW_MEMORY
+ newaudiortp = ast_threadstorage_get(&ts_audio_rtp, ast_rtp_alloc_size());
+#else
+ newaudiortp = alloca(ast_rtp_alloc_size());
+#endif
+ memset(newaudiortp, 0, ast_rtp_alloc_size());
+ ast_rtp_new_init(newaudiortp);
+ ast_rtp_pt_clear(newaudiortp);
+
+#ifdef LOW_MEMORY
+ newvideortp = ast_threadstorage_get(&ts_video_rtp, ast_rtp_alloc_size());
+#else
+ newvideortp = alloca(ast_rtp_alloc_size());
+#endif
+ memset(newvideortp, 0, ast_rtp_alloc_size());
+ ast_rtp_new_init(newvideortp);
+ ast_rtp_pt_clear(newvideortp);
+
+ /* Update our last rtprx when we receive an SDP, too */
+ p->lastrtprx = p->lastrtptx = time(NULL); /* XXX why both ? */
+
+
+ /* 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 */
+
+ iterator = req->sdp_start;
+ ast_set_flag(&p->flags[0], SIP_NOVIDEO);
+
+
+ /* Find media streams in this SDP offer */
+ while ((m = get_sdp_iterate(&iterator, req, "m"))[0] != '\0') {
+ int x;
+ int audio = FALSE;
+
+ numberofports = 1;
+ len = -1;
+ if ((sscanf(m, "audio %d/%d RTP/AVP %n", &x, &numberofports, &len) == 2 && len > 0) ||
+ (sscanf(m, "audio %d RTP/AVP %n", &x, &len) == 1 && len > 0)) {
+ 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 && len > 0) ||
+ (sscanf(m, "video %d RTP/AVP %n", &x, &len) == 1 && len >= 0)) {
+ /* If it is not audio - is it video ? */
+ ast_clear_flag(&p->flags[0], SIP_NOVIDEO);
+ 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 (p->udptl && ( (sscanf(m, "image %d udptl t38%n", &x, &len) == 1 && len > 0) ||
+ (sscanf(m, "image %d UDPTL t38%n", &x, &len) == 1 && len >= 0) )) {
+ 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 */
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "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 */
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "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 (!(vhp = ast_gethostbyname(host, &videohp))) {
+ ast_log(LOG_WARNING, "Unable to lookup RTP video host in secondary c= line, '%s'\n", c);
+ return -2;
+ }
+ }
+
+ }
+ }
+ if (portno == -1 && vportno == -1 && udptlportno == -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 > 2)
+ /* We have too many fax, audio and/or video 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;
+ memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+ if (vhp)
+ memcpy(&vsin.sin_addr, vhp->h_addr, sizeof(vsin.sin_addr));
+
+ /* Setup UDPTL port number */
+ if (p->udptl) {
+ if (udptlportno > 0) {
+ sin.sin_port = htons(udptlportno);
+ if (ast_test_flag(&p->flags[0], SIP_NAT) && ast_test_flag(&p->flags[1], SIP_PAGE2_UDPTL_DESTINATION)) {
+ struct sockaddr_in peer;
+ ast_rtp_get_peer(p->rtp, &peer);
+ if (peer.sin_addr.s_addr) {
+ memcpy(&sin.sin_addr, &peer.sin_addr, sizeof(sin.sin_addr));
+ if (debug) {
+ ast_log(LOG_DEBUG, "Peer T.38 UDPTL is set behind NAT and with destination, destination address now %s\n", ast_inet_ntoa(sin.sin_addr));
+ }
+ }
+ }
+ ast_udptl_set_peer(p->udptl, &sin);
+ if (debug)
+ ast_log(LOG_DEBUG,"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_log(LOG_DEBUG, "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 */
+ if (vportno != -1)
+ vsin.sin_port = htons(vportno);
+
+ /* Next, scan through each "a=rtpmap:" 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;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Can't read framing from SDP: %s\n", a);
+ }
+ }
+ if (framing && 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 < MAX_RTP_PT; codec_n++) {
+ format = ast_rtp_codec_getformat(codec_n);
+ if (!format) /* non-codec or not found */
+ continue;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Setting framing for %d to %ld\n", format, framing);
+ ast_codec_pref_setsize(pref, format, framing);
+ }
+ ast_rtp_codec_setpref(p->rtp, pref);
+ }
+ continue;
+ } else if (sscanf(a, "rtpmap: %u %[^/]/", &codec, mimeSubtype) == 2) {
+ /* We have a rtpmap to handle */
+ int found = FALSE;
+ /* We should propably check if this is an audio or video codec
+ so we know where to look */
+
+ if (last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) {
+ /* Note: should really look at the 'freq' and '#chans' params too */
+ 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++;
+ found = TRUE;
+
+ } else if (p->vrtp) {
+ 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++;
+ found = TRUE;
+ }
+ }
+ } else {
+ if (debug)
+ ast_verbose("Discarded description format %s for ID %d\n", mimeSubtype, codec);
+ }
+
+ if (!found) {
+ /* Remove this codec since it's an unknown media type for us */
+ /* XXX This is buggy since the media line for audio and video can have the
+ same numbers. We need to check as described above, but for testing this works... */
+ ast_rtp_unset_m_type(newaudiortp, codec);
+ ast_rtp_unset_m_type(newvideortp, 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;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "MaxBufferSize:%d\n",x);
+ } else if ((sscanf(a, "T38MaxBitRate:%d", &x) == 1) || (sscanf(a, "T38FaxMaxRate:%d", &x) == 1)) {
+ found = 1;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG,"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;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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) || (sscanf(a, "T38MaxDatagram:%d", &x) == 1)) {
+ found = 1;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "FaxMaxDatagram: %d\n",x);
+ ast_udptl_set_far_max_datagram(p->udptl, x);
+ ast_udptl_set_local_max_datagram(p->udptl, x);
+ } else if ((strncmp(a, "T38FaxFillBitRemoval", 20) == 0)) {
+ found = 1;
+ if ((sscanf(a, "T38FaxFillBitRemoval:%d", &x) == 1)) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "FillBitRemoval: %d\n",x);
+ if (x == 1)
+ peert38capability |= T38FAX_FILL_BIT_REMOVAL;
+ } else {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "FillBitRemoval\n");
+ peert38capability |= T38FAX_FILL_BIT_REMOVAL;
+ }
+ } else if ((strncmp(a, "T38FaxTranscodingMMR", 20) == 0)) {
+ found = 1;
+ if ((sscanf(a, "T38FaxTranscodingMMR:%d", &x) == 1)) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Transcoding MMR: %d\n",x);
+ if (x == 1)
+ peert38capability |= T38FAX_TRANSCODING_MMR;
+ } else {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Transcoding MMR\n");
+ peert38capability |= T38FAX_TRANSCODING_MMR;
+ }
+ } else if ((strncmp(a, "T38FaxTranscodingJBIG", 21) == 0)) {
+ found = 1;
+ if ((sscanf(a, "T38FaxTranscodingJBIG:%d", &x) == 1)) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Transcoding JBIG: %d\n",x);
+ if (x == 1)
+ peert38capability |= T38FAX_TRANSCODING_JBIG;
+ } else {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Transcoding JBIG\n");
+ peert38capability |= T38FAX_TRANSCODING_JBIG;
+ }
+ } else if ((sscanf(a, "T38FaxRateManagement:%255s", s) == 1)) {
+ found = 1;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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);
+
+ newjointcapability = p->capability & (peercapability | vpeercapability);
+ newpeercapability = (peercapability | vpeercapability);
+ newnoncodeccapability = p->noncodeccapability & peernoncodeccapability;
+
+
+ if (debug) {
+ /* shame on whoever coded this.... */
+ char s1[SIPBUFSIZE], s2[SIPBUFSIZE], s3[SIPBUFSIZE], s4[SIPBUFSIZE];
+
+ ast_verbose("Capabilities: us - %s, peer - audio=%s/video=%s, combined - %s\n",
+ ast_getformatname_multiple(s1, SIPBUFSIZE, p->capability),
+ ast_getformatname_multiple(s2, SIPBUFSIZE, newpeercapability),
+ ast_getformatname_multiple(s3, SIPBUFSIZE, vpeercapability),
+ ast_getformatname_multiple(s4, SIPBUFSIZE, newjointcapability));
+
+ ast_verbose("Non-codec capabilities (dtmf): us - %s, peer - %s, combined - %s\n",
+ ast_rtp_lookup_mime_multiple(s1, SIPBUFSIZE, p->noncodeccapability, 0, 0),
+ ast_rtp_lookup_mime_multiple(s2, SIPBUFSIZE, peernoncodeccapability, 0, 0),
+ ast_rtp_lookup_mime_multiple(s3, SIPBUFSIZE, newnoncodeccapability, 0, 0));
+ }
+ if (!newjointcapability) {
+ /* If T.38 was not negotiated either, totally bail out... */
+ if (!p->t38.jointcapability || !udptlportno) {
+ ast_log(LOG_NOTICE, "No compatible codecs, not accepting this offer!\n");
+ /* Do NOT Change current setting */
+ return -1;
+ } else {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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 (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));
+ }
+
+ /* Ok, we're going with this offer */
+ if (option_debug > 1) {
+ char buf[SIPBUFSIZE];
+ ast_log(LOG_DEBUG, "We're settling with these formats: %s\n", ast_getformatname_multiple(buf, SIPBUFSIZE, 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;
+
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "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[SIPBUFSIZE], s2[SIPBUFSIZE];
+ ast_log(LOG_DEBUG, "Oooh, we need to change our audio formats since our peer supports only %s and not %s\n",
+ ast_getformatname_multiple(s1, SIPBUFSIZE, p->jointcapability),
+ ast_getformatname_multiple(s2, SIPBUFSIZE, p->owner->nativeformats));
+ }
+ p->owner->nativeformats = ast_codec_choose(&p->prefs, p->jointcapability, 1) | (p->capability & vpeercapability);
+ 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);
+ } else if (!sin.sin_addr.s_addr || (sendonly && sendonly != -1)) {
+ 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);
+ }
+
+ /* Manager Hold and Unhold events must be generated, if necessary */
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) && sin.sin_addr.s_addr && (!sendonly || sendonly == -1))
+ change_hold_state(p, req, FALSE, sendonly);
+ else if (!sin.sin_addr.s_addr || (sendonly && sendonly != -1))
+ change_hold_state(p, req, TRUE, sendonly);
+ return 0;
+}
+
+#ifdef LOW_MEMORY
+static void ts_ast_rtp_destroy(void *data)
+{
+ struct ast_rtp *tmp = data;
+ ast_rtp_destroy(tmp);
+}
+#endif
+
+/*! \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 */
+ snprintf(req->data + req->len, sizeof(req->data) - req->len, "\r\n");
+ 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.
+ We always add ;received=<ip address> to the topmost via header.
+ 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[512];
+ 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[512], *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[SIPBUFSIZE*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]@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);
+ 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[SIPBUFSIZE];
+ 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);
+ }
+ 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 */
+
+ 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 (sipmethod == SIP_CANCEL) {
+ p->branch = p->invite_branch;
+ build_via(p);
+ } else 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_log(LOG_DEBUG, "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, (ast_test_flag(&p->flags[0], SIP_OUTGOING)) ? "To" : "From"),
+ sizeof(stripped));
+ n = get_in_brackets(stripped);
+ c = strsep(&n, ";"); /* trim ; and beyond */
+ }
+ 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);
+ }
+
+ 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 (ast_test_flag(&p->flags[0], SIP_OUTGOING) && !ast_strlen_zero(p->theirtag))
+ snprintf(newto, sizeof(newto), "%s;tag=%s", ot, p->theirtag);
+ else if (!ast_test_flag(&p->flags[0], SIP_OUTGOING))
+ snprintf(newto, sizeof(newto), "%s;tag=%s", ot, p->tag);
+ else
+ snprintf(newto, sizeof(newto), "%s", ot);
+ ot = newto;
+ }
+
+ if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ 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);
+ add_header(req, "Max-Forwards", DEFAULT_MAX_FORWARDS);
+
+ if (!ast_strlen_zero(p->rpid))
+ add_header(req, "Remote-Party-ID", p->rpid);
+
+ 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 void temp_pvt_cleanup(void *data)
+{
+ struct sip_pvt *p = data;
+
+ ast_string_field_free_memory(p);
+
+ 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;
+ }
+
+ /* if the structure was just allocated, initialize it */
+ if (!ast_test_flag(&p->flags[0], SIP_NO_HISTORY)) {
+ ast_set_flag(&p->flags[0], SIP_NO_HISTORY);
+ if (ast_string_field_init(p, 512))
+ return -1;
+ }
+
+ /* Initialize the bare minimum */
+ p->method = intended_method;
+
+ if (sin) {
+ p->sa = *sin;
+ if (ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip))
+ p->ourip = __ourip;
+ } else
+ p->ourip = __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);
+ }
+ check_via(p, req);
+
+ ast_string_field_set(p, fromdomain, default_fromdomain);
+ build_via(p);
+ ast_string_field_set(p, callid, callid);
+
+ /* 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_reset_all(p);
+
+ 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 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 */
+/* Always adds default duration 250 ms, regardless of what came in over the line */
+static int add_digit(struct sip_request *req, char digit, unsigned int duration)
+{
+ char tmp[256];
+
+ 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,
+ char **m_buf, size_t *m_size, char **a_buf, size_t *a_size,
+ 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_build_string(m_buf, m_size, " %d", rtp_code);
+ ast_build_string(a_buf, a_size, "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_build_string(a_buf, a_size, "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_build_string(a_buf, a_size, "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_build_string(a_buf, a_size, "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 less than zero */
+ if ((*min_packet_size) == 0 && fmt.cur_ms)
+ *min_packet_size = fmt.cur_ms;
+}
+
+/*! \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) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "T38MaxBitRate 14400 found\n");
+ return 14400;
+ } else if (maxrate & T38FAX_RATE_12000) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "T38MaxBitRate 12000 found\n");
+ return 12000;
+ } else if (maxrate & T38FAX_RATE_9600) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "T38MaxBitRate 9600 found\n");
+ return 9600;
+ } else if (maxrate & T38FAX_RATE_7200) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "T38MaxBitRate 7200 found\n");
+ return 7200;
+ } else if (maxrate & T38FAX_RATE_4800) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "T38MaxBitRate 4800 found\n");
+ return 4800;
+ } else if (maxrate & T38FAX_RATE_2400) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "T38MaxBitRate 2400 found\n");
+ return 2400;
+ } else {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Strange, T38MaxBitRate 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;
+ char v[256] = "";
+ char s[256] = "";
+ char o[256] = "";
+ char c[256] = "";
+ char t[256] = "";
+ char m_modem[256];
+ char a_modem[1024];
+ char *m_modem_next = m_modem;
+ size_t m_modem_left = sizeof(m_modem);
+ char *a_modem_next = a_modem;
+ size_t a_modem_left = sizeof(a_modem);
+ 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 = getpid();
+ 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;
+ udptldest.sin_port = udptlsin.sin_port;
+ }
+
+ if (debug)
+ ast_log(LOG_DEBUG, "T.38 UDPTL is at %s port %d\n", ast_inet_ntoa(p->ourip), 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_log(LOG_DEBUG, "Our T38 capability (%d), peer T38 capability (%d), joint capability (%d)\n",
+ p->t38.capability,
+ p->t38.peercapability,
+ p->t38.jointcapability);
+ }
+ snprintf(v, sizeof(v), "v=0\r\n");
+ snprintf(o, sizeof(o), "o=root %d %d IN IP4 %s\r\n", p->sessionid, p->sessionversion, ast_inet_ntoa(udptldest.sin_addr));
+ snprintf(s, sizeof(s), "s=session\r\n");
+ snprintf(c, sizeof(c), "c=IN IP4 %s\r\n", ast_inet_ntoa(udptldest.sin_addr));
+ snprintf(t, sizeof(t), "t=0 0\r\n");
+ ast_build_string(&m_modem_next, &m_modem_left, "m=image %d udptl t38\r\n", ntohs(udptldest.sin_port));
+
+ if ((p->t38.jointcapability & T38FAX_VERSION) == T38FAX_VERSION_0)
+ ast_build_string(&a_modem_next, &a_modem_left, "a=T38FaxVersion:0\r\n");
+ if ((p->t38.jointcapability & T38FAX_VERSION) == T38FAX_VERSION_1)
+ ast_build_string(&a_modem_next, &a_modem_left, "a=T38FaxVersion:1\r\n");
+ if ((x = t38_get_rate(p->t38.jointcapability)))
+ ast_build_string(&a_modem_next, &a_modem_left, "a=T38MaxBitRate:%d\r\n",x);
+ if ((p->t38.jointcapability & T38FAX_FILL_BIT_REMOVAL) == T38FAX_FILL_BIT_REMOVAL)
+ ast_build_string(&a_modem_next, &a_modem_left, "a=T38FaxFillBitRemoval\r\n");
+ if ((p->t38.jointcapability & T38FAX_TRANSCODING_MMR) == T38FAX_TRANSCODING_MMR)
+ ast_build_string(&a_modem_next, &a_modem_left, "a=T38FaxTranscodingMMR\r\n");
+ if ((p->t38.jointcapability & T38FAX_TRANSCODING_JBIG) == T38FAX_TRANSCODING_JBIG)
+ ast_build_string(&a_modem_next, &a_modem_left, "a=T38FaxTranscodingJBIG\r\n");
+ ast_build_string(&a_modem_next, &a_modem_left, "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_build_string(&a_modem_next, &a_modem_left, "a=T38FaxMaxBuffer:%d\r\n",x);
+ ast_build_string(&a_modem_next, &a_modem_left, "a=T38FaxMaxDatagram:%d\r\n",x);
+ if (p->t38.jointcapability != T38FAX_UDP_EC_NONE)
+ ast_build_string(&a_modem_next, &a_modem_left, "a=T38FaxUdpEC:%s\r\n", (p->t38.jointcapability & T38FAX_UDP_EC_REDUNDANCY) ? "t38UDPRedundancy" : "t38UDPFEC");
+ len = strlen(v) + strlen(s) + strlen(o) + strlen(c) + strlen(t) + strlen(m_modem) + strlen(a_modem);
+ add_header(resp, "Content-Type", "application/sdp");
+ add_header_contentLength(resp, 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_modem);
+ add_line(resp, a_modem);
+
+ /* 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,
+ char **m_buf, size_t *m_size, char **a_buf, size_t *a_size,
+ 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_build_string(m_buf, m_size, " %d", rtp_code);
+ ast_build_string(a_buf, a_size, "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_build_string(a_buf, a_size, "a=fmtp:%d 0-16\r\n", rtp_code);
+}
+
+/*!
+ * \note G.722 actually is supposed to specified as 8 kHz, even though it is
+ * really 16 kHz. Update this macro for other formats as they are added in
+ * the future.
+ */
+#define SDP_SAMPLE_RATE(x) 8000
+
+/*! \brief Add Session Description Protocol message */
+static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p)
+{
+ int len = 0;
+ int alreadysent = 0;
+
+ struct sockaddr_in sin;
+ struct sockaddr_in vsin;
+ struct sockaddr_in dest;
+ struct sockaddr_in vdest = { 0, };
+
+ /* SDP fields */
+ char *version = "v=0\r\n"; /* Protocol version */
+ char *subject = "s=session\r\n"; /* 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;
+ char m_audio[256]; /* Media declaration line for audio */
+ char m_video[256]; /* Media declaration line for video */
+ char a_audio[1024]; /* Attributes for audio */
+ char a_video[1024]; /* Attributes for video */
+ char *m_audio_next = m_audio;
+ char *m_video_next = m_video;
+ size_t m_audio_left = sizeof(m_audio);
+ size_t m_video_left = sizeof(m_video);
+ char *a_audio_next = a_audio;
+ char *a_video_next = a_video;
+ size_t a_audio_left = sizeof(a_audio);
+ size_t a_video_left = sizeof(a_video);
+
+ int x;
+ int capability;
+ int needvideo = FALSE;
+ int debug = sip_debug_test_pvt(p);
+ int min_audio_packet_size = 0;
+ int min_video_packet_size = 0;
+
+ m_video[0] = '\0'; /* Reset the video media string if it's not needed */
+
+ if (!p->rtp) {
+ ast_log(LOG_WARNING, "No way to add SDP without an RTP structure\n");
+ return AST_FAILURE;
+ }
+
+ /* Set RTP Session ID and version */
+ if (!p->sessionid) {
+ p->sessionid = getpid();
+ p->sessionversion = p->sessionid;
+ } else
+ p->sessionversion++;
+
+ /* Get our addresses */
+ ast_rtp_get_us(p->rtp, &sin);
+ if (p->vrtp)
+ ast_rtp_get_us(p->vrtp, &vsin);
+
+ /* Is this a re-invite to move the media out, then use the original offer from caller */
+ if (p->redirip.sin_addr.s_addr) {
+ dest.sin_port = p->redirip.sin_port;
+ dest.sin_addr = p->redirip.sin_addr;
+ } else {
+ dest.sin_addr = p->ourip;
+ dest.sin_port = sin.sin_port;
+ }
+
+ capability = p->jointcapability;
+
+
+ if (option_debug > 1) {
+ char codecbuf[SIPBUFSIZE];
+ ast_log(LOG_DEBUG, "** Our capability: %s Video flag: %s\n", ast_getformatname_multiple(codecbuf, sizeof(codecbuf), capability), ast_test_flag(&p->flags[0], SIP_NOVIDEO) ? "True" : "False");
+ ast_log(LOG_DEBUG, "** 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_build_string(&m_audio_next, &m_audio_left, " %d", 191);
+ ast_build_string(&a_audio_next, &a_audio_left, "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) && !ast_test_flag(&p->flags[0], SIP_NOVIDEO)) {
+ if (p->vrtp) {
+ needvideo = TRUE;
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "This call needs video offers!\n");
+ } else if (option_debug > 1)
+ ast_log(LOG_DEBUG, "This call needs video offers, but there's no video support enabled!\n");
+ }
+
+
+ /* 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) {
+ /* 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;
+ vdest.sin_port = vsin.sin_port;
+ }
+ ast_build_string(&m_video_next, &m_video_left, "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), ntohs(vsin.sin_port));
+ }
+
+ if (debug)
+ ast_verbose("Audio is at %s port %d\n", ast_inet_ntoa(p->ourip), ntohs(sin.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=root %d %d IN IP4 %s\r\n", 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_build_string(&m_audio_next, &m_audio_left, "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_next, &m_audio_left,
+ &a_audio_next, &a_audio_left,
+ 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_next, &m_audio_left,
+ &a_audio_next, &a_audio_left,
+ debug, &min_audio_packet_size);
+ alreadysent |= codec;
+ }
+
+ /* Now send any other common audio and video codecs, and non-codec formats: */
+ for (x = 1; x <= (needvideo ? AST_FORMAT_MAX_VIDEO : AST_FORMAT_MAX_AUDIO); x <<= 1) {
+ if (!(capability & x)) /* Codec not requested */
+ continue;
+
+ if (alreadysent & x) /* Already added to SDP */
+ continue;
+
+ if (x <= AST_FORMAT_MAX_AUDIO)
+ add_codec_to_sdp(p, x, SDP_SAMPLE_RATE(x),
+ &m_audio_next, &m_audio_left,
+ &a_audio_next, &a_audio_left,
+ debug, &min_audio_packet_size);
+ else
+ add_codec_to_sdp(p, x, 90000,
+ &m_video_next, &m_video_left,
+ &a_video_next, &a_video_left,
+ debug, &min_video_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_next, &m_audio_left,
+ &a_audio_next, &a_audio_left,
+ debug);
+ }
+
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "-- Done with adding codecs to SDP\n");
+
+ if (!p->owner || !ast_internal_timing_enabled(p->owner))
+ ast_build_string(&a_audio_next, &a_audio_left, "a=silenceSupp:off - - - -\r\n");
+
+ if (min_audio_packet_size)
+ ast_build_string(&a_audio_next, &a_audio_left, "a=ptime:%d\r\n", min_audio_packet_size);
+
+ if (min_video_packet_size)
+ ast_build_string(&a_video_next, &a_video_left, "a=ptime:%d\r\n", min_video_packet_size);
+
+ if ((m_audio_left < 2) || (m_video_left < 2) || (a_audio_left == 0) || (a_video_left == 0))
+ ast_log(LOG_WARNING, "SIP SDP may be truncated due to undersized buffer!!\n");
+
+ ast_build_string(&m_audio_next, &m_audio_left, "\r\n");
+ if (needvideo)
+ ast_build_string(&m_video_next, &m_video_left, "\r\n");
+
+ len = strlen(version) + strlen(subject) + strlen(owner) + strlen(connection) + strlen(stime) + strlen(m_audio) + strlen(a_audio) + strlen(hold);
+ if (needvideo) /* only if video response is appropriate */
+ len += strlen(m_video) + strlen(a_video) + strlen(bandwidth) + 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);
+ add_line(resp, a_audio);
+ add_line(resp, hold);
+ if (needvideo) { /* only if video response is appropriate */
+ add_line(resp, m_video);
+ add_line(resp, a_video);
+ add_line(resp, hold); /* Repeat hold for the video stream */
+ }
+
+ /* Update lastrtprx when we send our SDP */
+ p->lastrtprx = p->lastrtptx = time(NULL); /* XXX why both ? */
+
+ if (option_debug > 2) {
+ char buf[SIPBUFSIZE];
+ ast_log(LOG_DEBUG, "Done building SDP. Settling with this capability: %s\n", ast_getformatname_multiple(buf, SIPBUFSIZE, 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)
+{
+ 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)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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);
+ } 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_log(LOG_WARNING, "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_log(LOG_WARNING, "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.
+*/
+static int transmit_reinvite_with_sdp(struct sip_pvt *p)
+{
+ 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)
+ add_header(&req, "X-asterisk-Info", "SIP re-invite (External RTP bridge)");
+ if (!ast_test_flag(&p->flags[0], SIP_NO_HISTORY))
+ append_history(p, "ReInv", "Re-invite sent");
+ add_sdp(&req, p);
+ /* 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 Transmit reinvite with T38 SDP
+ We reinvite so that the T38 processing can take place.
+ SIP Signalling stays with * in the path.
+*/
+static int transmit_reinvite_with_t38_sdp(struct sip_pvt *p)
+{
+ 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)
+ add_header(&req, "X-asterisk-info", "SIP re-invite (T38 switchover)");
+ ast_udptl_offered_from_local(p->udptl, 1);
+ add_t38_sdp(&req, p);
+ /* Use this as the basis */
+ initialize_initreq(p, &req);
+ ast_set_flag(&p->flags[0], SIP_OUTGOING); /* Change direction of this dialog */
+ p->lastinvite = p->ocseq;
+ return send_request(p, &req, XMIT_CRITICAL, p->ocseq);
+}
+
+/*! \brief Check Contact: URI of SIP message */
+static void extract_uri(struct sip_pvt *p, struct sip_request *req)
+{
+ char stripped[SIPBUFSIZE];
+ char *c;
+
+ ast_copy_string(stripped, get_header(req, "Contact"), sizeof(stripped));
+ c = get_in_brackets(stripped);
+ c = strsep(&c, ";"); /* trim ; and beyond */
+ 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 (ourport != STANDARD_SIP_PORT)
+ ast_string_field_build(p, our_contact, "<sip:%s%s%s:%d>", p->exten, ast_strlen_zero(p->exten) ? "" : "@", ast_inet_ntoa(p->ourip), ourport);
+ else
+ ast_string_field_build(p, our_contact, "<sip:%s%s%s>", p->exten, ast_strlen_zero(p->exten) ? "" : "@", ast_inet_ntoa(p->ourip));
+}
+
+/*! \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));
+
+ 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)
+{
+ char invite_buf[256] = "";
+ char *invite = invite_buf;
+ size_t invite_max = sizeof(invite_buf);
+ char from[256];
+ char to[256];
+ char tmp[SIPBUFSIZE/2];
+ char tmp2[SIPBUFSIZE/2];
+ const char *l = NULL, *n = NULL;
+ 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, sizeof(tmp), 0);
+ n = tmp;
+ ast_uri_encode(l, tmp2, sizeof(tmp2), 0);
+ l = tmp2;
+ }
+
+ if (ourport != STANDARD_SIP_PORT && 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)), ourport, p->tag);
+ else
+ snprintf(from, sizeof(from), "\"%s\" <sip:%s@%s>;tag=%s", n, l, S_OR(p->fromdomain, ast_inet_ntoa(p->ourip)), 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_build_string(&invite, &invite_max, "%s", p->fullcontact);
+ } else {
+ /* Otherwise, use the username while waiting for registration */
+ ast_build_string(&invite, &invite_max, "sip:");
+ if (!ast_strlen_zero(p->username)) {
+ n = p->username;
+ if (pedanticsipchecking) {
+ ast_uri_encode(n, tmp, sizeof(tmp), 0);
+ n = tmp;
+ }
+ ast_build_string(&invite, &invite_max, "%s@", n);
+ }
+ ast_build_string(&invite, &invite_max, "%s", p->tohost);
+ if (ntohs(p->sa.sin_port) != STANDARD_SIP_PORT)
+ ast_build_string(&invite, &invite_max, ":%d", ntohs(p->sa.sin_port));
+ ast_build_string(&invite, &invite_max, "%s", urioptions);
+ }
+
+ /* If custom URI options have been provided, append them */
+ if (p->options && !ast_strlen_zero(p->options->uri_options))
+ ast_build_string(&invite, &invite_max, ";%s", p->options->uri_options);
+
+ ast_string_field_set(p, uri, invite_buf);
+
+ 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);
+ snprintf(tmp, sizeof(tmp), "%d %s", ++p->ocseq, sip_methods[sipmethod].text);
+
+ add_header(req, "Via", p->via);
+ /* 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);
+ if (!ast_strlen_zero(global_useragent))
+ add_header(req, "User-Agent", global_useragent);
+ add_header(req, "Max-Forwards", DEFAULT_MAX_FORWARDS);
+ if (!ast_strlen_zero(p->rpid))
+ add_header(req, "Remote-Party-ID", p->rpid);
+}
+
+/*! \brief Build REFER/INVITE/OPTIONS message and transmit it */
+static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init)
+{
+ struct sip_request req;
+
+ req.method = sipmethod;
+ if (init) { /* Seems like init always is 2 */
+ /* Bump branch even on initial requests */
+ p->branch ^= ast_random();
+ p->invite_branch = p->branch;
+ build_via(p);
+ if (init > 1)
+ initreqprep(&req, p, sipmethod);
+ else
+ reqprep(&req, p, sipmethod, 0, 1);
+ } 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[SIPBUFSIZE];
+ 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 && p->options->replaces && !ast_strlen_zero(p->options->replaces)) {
+ add_header(&req, "Replaces", p->options->replaces);
+ add_header(&req, "Require", "replaces");
+ }
+
+ 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_log(LOG_DEBUG, "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 || p->t38.state == T38_LOCAL_REINVITE)) {
+ ast_udptl_offered_from_local(p->udptl, 1);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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);
+ } else {
+ add_header_contentLength(&req, 0);
+ }
+
+ if (!p->initreq.headers || init > 2)
+ 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)
+{
+ char tmp[4000], from[256], to[256];
+ char *t = tmp, *c, *mfrom, *mto;
+ size_t maxbytes = sizeof(tmp);
+ 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));
+ memset(tmp, 0, sizeof(tmp));
+
+ 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_CLOSED;
+ 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)) {
+ ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c);
+ return -1;
+ }
+ mfrom = strsep(&c, ";"); /* trim ; and beyond */
+
+ ast_copy_string(to, get_header(&p->initreq, "To"), sizeof(to));
+ c = get_in_brackets(to);
+ if (strncasecmp(c, "sip:", 4)) {
+ ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c);
+ return -1;
+ }
+ mto = strsep(&c, ";"); /* trim ; and beyond */
+
+ 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_build_string(&t, &maxbytes, "<?xml version=\"1.0\"?>\n");
+ ast_build_string(&t, &maxbytes, "<!DOCTYPE presence PUBLIC \"-//IETF//DTD RFCxxxx XPIDF 1.0//EN\" \"xpidf.dtd\">\n");
+ ast_build_string(&t, &maxbytes, "<presence>\n");
+ ast_build_string(&t, &maxbytes, "<presentity uri=\"%s;method=SUBSCRIBE\" />\n", mfrom);
+ ast_build_string(&t, &maxbytes, "<atom id=\"%s\">\n", p->exten);
+ ast_build_string(&t, &maxbytes, "<address uri=\"%s;user=ip\" priority=\"0.800000\">\n", mto);
+ ast_build_string(&t, &maxbytes, "<status status=\"%s\" />\n", (local_state == NOTIFY_OPEN) ? "open" : (local_state == NOTIFY_INUSE) ? "inuse" : "closed");
+ ast_build_string(&t, &maxbytes, "<msnsubstatus substatus=\"%s\" />\n", (local_state == NOTIFY_OPEN) ? "online" : (local_state == NOTIFY_INUSE) ? "onthephone" : "offline");
+ ast_build_string(&t, &maxbytes, "</address>\n</atom>\n</presence>\n");
+ break;
+ case PIDF_XML: /* Eyebeam supports this format */
+ ast_build_string(&t, &maxbytes, "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n");
+ ast_build_string(&t, &maxbytes, "<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_build_string(&t, &maxbytes, "<pp:person><status>\n");
+ if (pidfstate[0] != '-')
+ ast_build_string(&t, &maxbytes, "<ep:activities><ep:%s/></ep:activities>\n", pidfstate);
+ ast_build_string(&t, &maxbytes, "</status></pp:person>\n");
+ ast_build_string(&t, &maxbytes, "<note>%s</note>\n", pidfnote); /* Note */
+ ast_build_string(&t, &maxbytes, "<tuple id=\"%s\">\n", p->exten); /* Tuple start */
+ ast_build_string(&t, &maxbytes, "<contact priority=\"1\">%s</contact>\n", mto);
+ if (pidfstate[0] == 'b') /* Busy? Still open ... */
+ ast_build_string(&t, &maxbytes, "<status><basic>open</basic></status>\n");
+ else
+ ast_build_string(&t, &maxbytes, "<status><basic>%s</basic></status>\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed");
+ ast_build_string(&t, &maxbytes, "</tuple>\n</presence>\n");
+ break;
+ case DIALOG_INFO_XML: /* SNOM subscribes in this format */
+ ast_build_string(&t, &maxbytes, "<?xml version=\"1.0\"?>\n");
+ ast_build_string(&t, &maxbytes, "<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_build_string(&t, &maxbytes, "<dialog id=\"%s\" direction=\"recipient\">\n", p->exten);
+ else
+ ast_build_string(&t, &maxbytes, "<dialog id=\"%s\">\n", p->exten);
+ ast_build_string(&t, &maxbytes, "<state>%s</state>\n", statestring);
+ if (state == AST_EXTENSION_ONHOLD) {
+ ast_build_string(&t, &maxbytes, "<local>\n<target uri=\"%s\">\n"
+ "<param pname=\"+sip.rendering\" pvalue=\"no\"/>\n"
+ "</target>\n</local>\n", mto);
+ }
+ ast_build_string(&t, &maxbytes, "</dialog>\n</dialog-info>\n");
+ break;
+ case NONE:
+ default:
+ break;
+ }
+
+ if (t > tmp + sizeof(tmp))
+ ast_log(LOG_WARNING, "Buffer overflow detected!! (Please file a bug report)\n");
+
+ add_header_contentLength(&req, strlen(tmp));
+ add_line(&req, tmp);
+ p->pendinginvite = p->ocseq; /* Remember that we have a pending NOTIFY in order not to confuse the NOTIFY subsystem */
+
+ 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;
+ char tmp[500];
+ char *t = tmp;
+ size_t maxbytes = sizeof(tmp);
+
+ initreqprep(&req, p, SIP_NOTIFY);
+ add_header(&req, "Event", "message-summary");
+ add_header(&req, "Content-Type", default_notifymime);
+
+ ast_build_string(&t, &maxbytes, "Messages-Waiting: %s\r\n", newmsgs ? "yes" : "no");
+ ast_build_string(&t, &maxbytes, "Message-Account: sip:%s@%s\r\n",
+ S_OR(vmexten, default_vmexten), S_OR(p->fromdomain, ast_inet_ntoa(p->ourip)));
+ /* 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_build_string(&t, &maxbytes, "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");
+ }
+
+ if (t > tmp + sizeof(tmp))
+ ast_log(LOG_WARNING, "Buffer overflow detected!! (Please file a bug report)\n");
+
+ add_header_contentLength(&req, strlen(tmp));
+ add_line(&req, tmp);
+
+ 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[SIPBUFSIZE/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);
+}
+
+/*! \brief Convert registration state status to string */
+static char *regstate2str(enum sipregistrystate regstate)
+{
+ switch(regstate) {
+ case REG_STATE_FAILED:
+ return "Failed";
+ 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";
+ }
+}
+
+/*! \brief Update registration with SIP Proxy */
+static int sip_reregister(const void *data)
+{
+ /* if we are here, we know that we need to reregister. */
+ struct sip_registry *r= ASTOBJ_REF((struct sip_registry *) data);
+
+ /* if we couldn't get a reference to the registry object, punt */
+ if (!r)
+ return 0;
+
+ if (r->call && !ast_test_flag(&r->call->flags[0], SIP_NO_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);
+ ASTOBJ_UNREF(r, sip_registry_destroy);
+ 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 */
+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 = ASTOBJ_REF((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 (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;
+ ast_mutex_lock(&p->lock);
+ if (p->registry)
+ ASTOBJ_UNREF(p->registry, sip_registry_destroy);
+ r->call = NULL;
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ /* Pretend to ACK anything just in case */
+ __sip_pretend_ack(p);
+ ast_mutex_unlock(&p->lock);
+ }
+ /* 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", "ChannelDriver: SIP\r\nUsername: %s\r\nDomain: %s\r\nStatus: %s\r\n", r->username, r->hostname, regstate2str(r->regstate));
+ ASTOBJ_UNREF(r, sip_registry_destroy);
+ return 0;
+}
+
+/*! \brief Transmit register to SIP proxy or UA */
+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;
+ char *fromdomain;
+
+ /* 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))) {
+ if (r) {
+ 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_free(p, theirtag); /* 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, __ourip, default_fromdomain);
+ r->callid_valid = TRUE;
+ }
+ /* Allocate SIP packet 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 (!ast_test_flag(&p->flags[0], SIP_NO_HISTORY))
+ append_history(p, "RegistryInit", "Account: %s@%s", r->username, r->hostname);
+ /* 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)
+ ast_log(LOG_WARNING, "Still have a registration timeout for %s@%s (create_addr() error), %d\n", r->username, r->hostname, r->timeout);
+ else
+ 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);
+
+ AST_SCHED_DEL(sched, r->timeout);
+ r->timeout = ast_sched_add(sched, global_reg_timeout * 1000, sip_reg_timeout, r);
+ 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=p; /* Save pointer to SIP packet */
+ p->registry = ASTOBJ_REF(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 */
+ ast_string_field_set(p, exten, r->contact);
+
+ /*
+ 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
+ */
+ if (ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip))
+ p->ourip = bindaddr.sin_addr;
+ 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);
+ AST_SCHED_DEL(sched, r->timeout);
+ r->timeout = ast_sched_add(sched, global_reg_timeout * 1000, sip_reg_timeout, r);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Scheduled a registration timeout for %s id #%d \n", r->hostname, r->timeout);
+ }
+
+ if ((fromdomain = strchr(r->username, '@'))) {
+ /* the domain name is just behind '@' */
+ fromdomain++ ;
+ /* We have a domain in the username for registration */
+ 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);
+
+ /* If the registration username contains '@', then the domain should be used as
+ the equivalent of "fromdomain" for the registration */
+ if (ast_strlen_zero(p->fromdomain)) {
+ ast_string_field_set(p, fromdomain, fromdomain);
+ }
+ } 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, "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);
+ add_header(&req, "Max-Forwards", DEFAULT_MAX_FORWARDS);
+
+
+ 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! */
+ if (sipdebug)
+ ast_log(LOG_DEBUG, " >>> 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);
+ r->noncecount++;
+ 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", default_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 */
+ if (option_debug > 3)
+ ast_verbose("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 (option_debug || sipdebug)
+ ast_log(LOG_DEBUG, "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))
+ ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n");
+ else
+ of += 4;
+ /* 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, "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);
+ 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);
+ add_header_contentLength(&resp, 0);
+ return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq);
+}
+
+/*! \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))) {
+ if (p->options && p->options->auth_type == PROXY_AUTH)
+ add_header(&resp, "Proxy-Authorization", digest);
+ else if (p->options && p->options->auth_type == WWW_AUTH)
+ add_header(&resp, "Authorization", digest);
+ else /* Default, to be backwards compatible (maybe being too careful, but leaving it for now) */
+ add_header(&resp, "Proxy-Authorization", 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)
+{
+ if (!ast_test_flag(&global_flags[1], SIP_PAGE2_IGNOREREGEXPIRE)) {
+ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_RT_FROMCONTACT))
+ ast_update_realtime("sippeers", "name", peer->name, "fullcontact", "", "ipaddr", "", "port", "", "regseconds", "0", "username", "", "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", "Peer: 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 (ast_test_flag(&peer->flags[1], SIP_PAGE2_SELFDESTRUCT) ||
+ ast_test_flag(&peer->flags[1], SIP_PAGE2_RTAUTOCLEAR)) {
+ struct sip_peer *peer_ptr = peer_ptr;
+ peer_ptr = ASTOBJ_CONTAINER_UNLINK(&peerl, peer);
+ if (peer_ptr) {
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ }
+
+ ASTOBJ_UNREF(peer, sip_destroy_peer);
+
+ 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);
+
+ ASTOBJ_UNREF(peer, sip_destroy_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 (ast_test_flag(&peer->flags[1], SIP_PAGE2_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));
+
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "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 */
+ if (!AST_SCHED_DEL(sched, peer->pokeexpire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ peer->pokeexpire = ast_sched_add(sched, ast_random() % 5000 + 1, sip_poke_peer_s, ASTOBJ_REF(peer));
+ if (peer->pokeexpire == -1) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ } else
+ sip_poke_peer(peer);
+ if (!AST_SCHED_DEL(sched, peer->expire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ peer->expire = ast_sched_add(sched, (expiry + 10) * 1000, expire_register, ASTOBJ_REF(peer));
+ if (peer->expire == -1) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_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[SIPBUFSIZE];
+ 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 sips:, tel:, mailto:,ldap: etc */
+ return TRUE;
+}
+
+static int __set_address_from_contact(const char *fullcontact, struct sockaddr_in *sin)
+{
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ int port;
+ char *c, *host, *pt;
+ char contact_buf[256];
+ char *contact;
+
+ /* Work on a copy */
+ ast_copy_string(contact_buf, fullcontact, sizeof(contact_buf));
+ contact = contact_buf;
+
+ /* Make sure it's a SIP URL */
+ if (strncasecmp(contact, "sip:", 4)) {
+ ast_log(LOG_NOTICE, "'%s' is not a valid SIP contact (missing sip:) trying to use anyway\n", contact);
+ } else
+ contact += 4;
+
+ /* Ditch arguments */
+ /* XXX this code is replicated also shortly below */
+
+ /* Grab host */
+ host = strchr(contact, '@');
+ if (!host) { /* No username part */
+ host = contact;
+ c = NULL;
+ } else {
+ *host++ = '\0';
+ }
+ pt = strchr(host, ':');
+ if (pt) {
+ *pt++ = '\0';
+ port = atoi(pt);
+ } else
+ port = STANDARD_SIP_PORT;
+
+ contact = strsep(&contact, ";"); /* trim ; and beyond in username part */
+ host = strsep(&host, ";"); /* trim ; and beyond in host/domain part */
+
+ /* 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;
+ }
+ sin->sin_family = AF_INET;
+ memcpy(&sin->sin_addr, hp->h_addr, sizeof(sin->sin_addr));
+ sin->sin_port = htons(port);
+
+ return 0;
+}
+
+/*! \brief Change the other partys IP address based on given contact */
+static int set_address_from_contact(struct sip_pvt *pvt)
+{
+ 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;
+ }
+
+ return __set_address_from_contact(pvt->fullcontact, &pvt->sa);
+}
+
+
+/*! \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[SIPBUFSIZE];
+ char data[SIPBUFSIZE];
+ const char *expires = get_header(req, "Expires");
+ int expiry = atoi(expires);
+ char *curi, *n, *pt;
+ int port;
+ const char *useragent;
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ struct sockaddr_in oldsin, testsin;
+
+ ast_copy_string(contact, get_header(req, "Contact"), sizeof(contact));
+
+ if (ast_strlen_zero(expires)) { /* No expires header */
+ expires = strcasestr(contact, ";expires=");
+ if (expires) {
+ /* XXX bug here, we overwrite the string */
+ expires = strsep((char **) &expires, ";"); /* trim ; and beyond */
+ if (sscanf(expires + 9, "%d", &expiry) != 1)
+ expiry = default_expiry;
+ } else {
+ /* Nothing has been specified */
+ expiry = default_expiry;
+ }
+ }
+
+ /* 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);
+
+ /* 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 (!AST_SCHED_DEL(sched, peer->expire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+
+ destroy_association(peer);
+
+ register_peer_exten(peer, 0); /* Add extension from regexten= setting in sip.conf */
+ peer->fullcontact[0] = '\0';
+ peer->useragent[0] = '\0';
+ peer->sipoptions = 0;
+ peer->lastms = 0;
+ pvt->expiry = 0;
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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 (strncasecmp(curi, "sip:", 4)) {
+ ast_log(LOG_NOTICE, "'%s' is not a valid SIP contact (missing sip:) trying to use anyway\n", curi);
+ } else
+ curi += 4;
+ /* Ditch q */
+ curi = strsep(&curi, ";");
+ /* Grab host */
+ n = strchr(curi, '@');
+ if (!n) {
+ n = curi;
+ curi = NULL;
+ } else
+ *n++ = '\0';
+ pt = strchr(n, ':');
+ if (pt) {
+ *pt++ = '\0';
+ port = atoi(pt);
+ } else
+ port = STANDARD_SIP_PORT;
+ oldsin = peer->addr;
+
+ /* Check that they're allowed to register at this IP */
+ /* XXX This could block for a long time XXX */
+ hp = ast_gethostbyname(n, &ahp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "Invalid host '%s'\n", n);
+ *peer->fullcontact = '\0';
+ ast_string_field_set(pvt, our_contact, "");
+ return PARSE_REGISTER_FAILED;
+ }
+ memcpy(&testsin.sin_addr, hp->h_addr, sizeof(testsin.sin_addr));
+ if ( ast_apply_ha(global_contact_ha, &testsin) != AST_SENSE_ALLOW ||
+ ast_apply_ha(peer->contactha, &testsin) != AST_SENSE_ALLOW) {
+ ast_log(LOG_WARNING, "Host '%s' disallowed by rule\n", n);
+ *peer->fullcontact = '\0';
+ ast_string_field_set(pvt, our_contact, "");
+ return PARSE_REGISTER_FAILED;
+ }
+
+ if (!ast_test_flag(&peer->flags[0], SIP_NAT_ROUTE)) {
+ 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 (curi && ast_strlen_zero(peer->username))
+ ast_copy_string(peer->username, curi, sizeof(peer->username));
+
+ if (!AST_SCHED_DEL(sched, peer->expire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ if (expiry > max_expiry)
+ expiry = max_expiry;
+ if (expiry < min_expiry)
+ expiry = min_expiry;
+ if (ast_test_flag(&peer->flags[0], SIP_REALTIME) && !ast_test_flag(&peer->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) {
+ peer->expire = -1;
+ } else {
+ peer->expire = ast_sched_add(sched, (expiry + 10) * 1000, expire_register, ASTOBJ_REF(peer));
+ if (peer->expire == -1) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_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);
+ if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_RT_FROMCONTACT))
+ ast_db_put("SIP/Registry", peer->name, data);
+ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: SIP/%s\r\nPeerStatus: Registered\r\n", peer->name);
+
+ /* Is this a new IP address for us? */
+ if (option_verbose > 2 && inaddrcmp(&peer->addr, &oldsin)) {
+ ast_verbose(VERBOSE_PREFIX_3 "Registered SIP '%s' at %s port %d\n", peer->name, ast_inet_ntoa(peer->addr.sin_addr), ntohs(peer->addr.sin_port));
+ }
+ sip_poke_peer(peer);
+ register_peer_exten(peer, 1);
+
+ /* 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));
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_3 "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;
+ 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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "build_route: Retaining previous route: <%s>\n", p->route->hop);
+ return;
+ }
+
+ if (p->route) {
+ free_old_route(p->route);
+ p->route = NULL;
+ }
+
+ /* We only want to create the route set the first time this is called */
+ p->route_persistant = 1;
+
+ /* 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);
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "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)) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "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, check_auth_buf_init);
+#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 = "407 Proxy Authentication Required";
+ const char *reqheader = "Proxy-Authorization";
+ const char *respheader = "Proxy-Authenticate";
+ 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_dynamic_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;
+ if (sipmethod == SIP_REGISTER || sipmethod == SIP_SUBSCRIBE) {
+ /* On a REGISTER, we have to use 401 and its family of headers instead of 407 and its family
+ of headers -- GO SIP! Whoo hoo! Two things that do the same thing but are used in
+ different circumstances! What a surprise. */
+ response = "401 Unauthorized";
+ reqheader = "Authorization";
+ respheader = "WWW-Authenticate";
+ }
+ 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_dynamic_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_dynamic_str_thread_set(&buf, 0, &check_auth_buf, "%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) {
+ 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 . */
+ ast_string_field_build(p, randdata, "%08lx", ast_random());
+ 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 (!ast_test_flag(req, SIP_PKT_IGNORE)) {
+ if (sipdebug)
+ ast_log(LOG_NOTICE, "Bad authentication received from '%s'\n", get_header(req, "To"));
+ ast_string_field_build(p, randdata, "%08lx", ast_random());
+ } else {
+ if (sipdebug)
+ ast_log(LOG_NOTICE, "Duplicate 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, 0);
+
+ if (!peer)
+ return;
+
+ /* If they put someone on hold, increment the value... otherwise decrement it */
+ if (hold)
+ peer->onHold++;
+ else
+ peer->onHold--;
+
+ /* Request device state update */
+ ast_device_state_changed("SIP/%s", peer->name);
+
+ return;
+}
+
+/*! \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;
+
+ ast_mutex_lock(&p->lock);
+
+ 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 */
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); /* Delete subscription in 32 secs */
+ ast_verbose(VERBOSE_PREFIX_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 */
+ if (!p->pendinginvite) {
+ transmit_state_notify(p, state, 1, FALSE);
+ } else {
+ /* We already have a NOTIFY sent that is not answered. Queue the state up.
+ if many state changes happen meanwhile, we will only send a notification of the last one */
+ ast_set_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE);
+ }
+ }
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_1 "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(state), p->username,
+ ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE) ? "(queued)" : "");
+
+
+ ast_mutex_unlock(&p->lock);
+
+ 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);
+}
+
+/*! \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 *t;
+ char *domain;
+
+ /* Terminate URI */
+ t = uri;
+ while(*t && (*t > 32) && (*t != ';'))
+ t++;
+ *t = '\0';
+
+ ast_copy_string(tmp, get_header(req, "To"), sizeof(tmp));
+ if (pedanticsipchecking)
+ ast_uri_decode(tmp);
+
+ c = get_in_brackets(tmp);
+ c = strsep(&c, ";"); /* Ditch ;user=phone */
+
+ if (!strncasecmp(c, "sip:", 4)) {
+ name = c + 4;
+ } 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));
+ }
+
+ /* 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;
+ }
+ }
+ }
+
+ ast_string_field_set(p, exten, name);
+ build_contact(p);
+ peer = find_peer(name, NULL, 1, 0);
+ if (!(peer && ast_apply_ha(peer->ha, sin))) {
+ /* Peer fails ACL check */
+ if (peer) {
+ ASTOBJ_UNREF(peer, sip_destroy_peer);
+ 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 (!ast_test_flag(&peer->flags[1], SIP_PAGE2_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);
+ transmit_response(p, "100 Trying", req);
+ if (!(res = check_auth(p, req, peer->name, peer->secret, peer->md5secret, SIP_REGISTER, uri, XMIT_UNRELIABLE, ast_test_flag(req, SIP_PKT_IGNORE)))) {
+ if (sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+
+ /* We have a succesful registration attemp 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);
+ if (sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ 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", "Peer: 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)
+ ASTOBJ_UNREF(peer, sip_destroy_peer);
+
+ return res;
+}
+
+/*! \brief Get referring dnis */
+static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq)
+{
+ char tmp[256], *c, *a;
+ struct sip_request *req;
+
+ req = oreq;
+ if (!req)
+ req = &p->initreq;
+ ast_copy_string(tmp, get_header(req, "Diversion"), sizeof(tmp));
+ if (ast_strlen_zero(tmp))
+ return 0;
+ c = get_in_brackets(tmp);
+ if (strncasecmp(c, "sip:", 4)) {
+ ast_log(LOG_WARNING, "Huh? Not an RDNIS SIP header (%s)?\n", c);
+ return -1;
+ }
+ c += 4;
+ a = c;
+ strsep(&a, "@;"); /* trim anything after @ or ; */
+ if (sip_debug_test_pvt(p))
+ ast_verbose("RDNIS is %s\n", c);
+ ast_string_field_set(p, rdnis, c);
+
+ return 0;
+}
+
+/*! \brief Find out who the call is for
+ We use the INVITE uri to find out
+*/
+static int get_destination(struct sip_pvt *p, struct sip_request *oreq)
+{
+ char tmp[256] = "", *uri, *a;
+ char tmpf[256] = "", *from;
+ struct sip_request *req;
+ char *colon;
+ char *decoded_uri;
+
+ 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)) {
+ ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", uri);
+ return -1;
+ }
+ uri += 4;
+
+ /* Now find the From: caller ID and name */
+ 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);
+ } else {
+ from = NULL;
+ }
+
+ if (!ast_strlen_zero(from)) {
+ if (strncasecmp(from, "sip:", 4)) {
+ ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", from);
+ return -1;
+ }
+ from += 4;
+ 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)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 {
+ decoded_uri = ast_strdupa(uri);
+ ast_uri_decode(decoded_uri);
+ /* 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 */
+ 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(decoded_uri, ast_pickup_ext())) {
+ if (!oreq)
+ ast_string_field_set(p, exten, decoded_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, decoded_uri, 1, S_OR(p->cid_num, from))) ||
+ !strncmp(decoded_uri, ast_pickup_ext(), strlen(decoded_uri))) {
+ return 1;
+ }
+
+ return -1;
+}
+
+/*! \brief Lock interface lock and find matching pvt lock
+*/
+static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *totag, const char *fromtag)
+{
+ struct sip_pvt *sip_pvt_ptr;
+
+ ast_mutex_lock(&iflock);
+
+ if (option_debug > 3 && totag)
+ ast_log(LOG_DEBUG, "Looking for callid %s (fromtag %s totag %s)\n", callid, fromtag ? fromtag : "<no fromtag>", totag ? totag : "<no totag>");
+
+ /* Search interfaces and find the match */
+ for (sip_pvt_ptr = iflist; sip_pvt_ptr; sip_pvt_ptr = sip_pvt_ptr->next) {
+ if (!strcmp(sip_pvt_ptr->callid, callid)) {
+ int match = 1;
+
+ /* Go ahead and lock it (and its owner) before returning */
+ ast_mutex_lock(&sip_pvt_ptr->lock);
+
+ /* 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) {
+ const char *pvt_fromtag, *pvt_totag;
+
+ if (ast_test_flag(&sip_pvt_ptr->flags[1], SIP_PAGE2_OUTGOING_CALL)) {
+ /* Outgoing call tags : from is "our", to is "their" */
+ pvt_fromtag = sip_pvt_ptr->tag ;
+ pvt_totag = sip_pvt_ptr->theirtag ;
+ } else {
+ /* Incoming call tags : from is "their", to is "our" */
+ pvt_fromtag = sip_pvt_ptr->theirtag ;
+ pvt_totag = sip_pvt_ptr->tag ;
+ }
+ if (ast_strlen_zero(fromtag) || strcmp(fromtag, pvt_fromtag) || (!ast_strlen_zero(totag) && strcmp(totag, pvt_totag)))
+ match = 0;
+ }
+
+ if (!match) {
+ ast_mutex_unlock(&sip_pvt_ptr->lock);
+ continue;
+ }
+
+ if (option_debug > 3 && totag)
+ ast_log(LOG_DEBUG, "Matched %s call - their tag is %s Our tag is %s\n",
+ ast_test_flag(&sip_pvt_ptr->flags[1], SIP_PAGE2_OUTGOING_CALL) ? "OUTGOING": "INCOMING",
+ sip_pvt_ptr->theirtag, sip_pvt_ptr->tag);
+
+ /* deadlock avoidance... */
+ while (sip_pvt_ptr->owner && ast_channel_trylock(sip_pvt_ptr->owner)) {
+ DEADLOCK_AVOIDANCE(&sip_pvt_ptr->lock);
+ }
+ break;
+ }
+ }
+ ast_mutex_unlock(&iflock);
+ if (option_debug > 3 && !sip_pvt_ptr)
+ ast_log(LOG_DEBUG, "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)) {
+ ast_log(LOG_WARNING, "Can't transfer to non-sip: URI. (Refer-to: %s)?\n", refer_to);
+ return -3;
+ }
+ refer_to += 4; /* Skip sip: */
+
+ /* Get referred by header if it exists */
+ p_referred_by = get_header(req, "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)) {
+ ast_log(LOG_WARNING, "Huh? Not a sip: header (Referred-by: %s). Skipping.\n", referred_by_uri);
+ referred_by_uri = (char *) NULL;
+ } else {
+ referred_by_uri += 4; /* Skip sip: */
+ }
+ }
+
+ /* 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 (option_debug > 1) {
+ if (!pedanticsipchecking)
+ ast_log(LOG_DEBUG,"Attended transfer: Will use Replace-Call-ID : %s (No check of from/to tags)\n", referdata->replaces_callid );
+ else
+ ast_log(LOG_DEBUG,"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)) {
+ ast_log(LOG_WARNING, "Huh? Not a SIP header in Also: transfer (%s)?\n", c);
+ return -1;
+ }
+ c += 4;
+ 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 */
+ if (option_debug)
+ ast_log(LOG_DEBUG,"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 = NULL;
+ /* 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 Via: header for hostname, port and rport request/answer */
+static void check_via(struct sip_pvt *p, const struct sip_request *req)
+{
+ char via[512];
+ 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")) {
+ 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;
+}
+
+
+/*! \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)
+{
+ struct sip_user *user = NULL;
+ struct sip_peer *peer;
+ char from[256], *c;
+ char *of;
+ char rpid_num[50];
+ const char *rpid;
+ enum check_auth_result res = AUTH_SUCCESSFUL;
+ char *t;
+ char calleridname[50];
+ int debug=sip_debug_test_addr(sin);
+ struct ast_variable *tmpvar = NULL, *v = NULL;
+ char *uri2 = ast_strdupa(uri);
+
+ /* Terminate URI */
+ t = uri2;
+ while (*t && *t > 32 && *t != ';')
+ t++;
+ *t = '\0';
+ ast_copy_string(from, get_header(req, "From"), sizeof(from)); /* XXX bug in original code, overwrote string */
+ 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)) {
+ t = uri2;
+ if (!strncasecmp(t, "sip:", 4))
+ t+= 4;
+ 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);
+ if (strncasecmp(of, "sip:", 4)) {
+ ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n");
+ } else
+ of += 4;
+ /* Get just the username part */
+ if ((c = strchr(of, '@'))) {
+ char *tmp;
+ *c = '\0';
+ if ((c = strchr(of, ':')))
+ *c = '\0';
+ 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 (!authpeer) /* If we are looking for a peer, don't check the user objects (or realtime) */
+ user = find_user(of, 1);
+
+ /* Find user based on user name in the from header */
+ if (user && ast_apply_ha(user->ha, sin)) {
+ 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);
+ if (sipmethod == SIP_INVITE) {
+ /* copy channel vars */
+ for (v = user->chanvars ; v ; v = v->next) {
+ if ((tmpvar = ast_variable_new(v->name, v->value))) {
+ tmpvar->next = p->chanvars;
+ p->chanvars = tmpvar;
+ }
+ }
+ }
+ p->prefs = user->prefs;
+ /* Set Frame packetization */
+ if (p->rtp) {
+ ast_rtp_codec_setpref(p->rtp, &p->prefs);
+ p->autoframing = user->autoframing;
+ }
+ /* replace callerid if rpid found, and not restricted */
+ if (!ast_strlen_zero(rpid_num) && ast_test_flag(&p->flags[0], SIP_TRUSTRPID)) {
+ char *tmp;
+ if (*calleridname)
+ ast_string_field_set(p, cid_name, calleridname);
+ tmp = ast_strdupa(rpid_num);
+ if (ast_is_shrinkable_phonenumber(tmp))
+ ast_shrink_phone_number(tmp);
+ ast_string_field_set(p, cid_num, tmp);
+ }
+
+ 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, ast_test_flag(req, SIP_PKT_IGNORE)))) {
+ if (sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ 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)) {
+ 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_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 (user && debug)
+ ast_verbose("Found user '%s'\n", user->name);
+ } else {
+ if (user) {
+ if (!authpeer && debug)
+ ast_verbose("Found user '%s', but fails host access\n", user->name);
+ ASTOBJ_UNREF(user,sip_destroy_user);
+ }
+ user = NULL;
+ }
+
+ if (!user) {
+ /* If we didn't find a user match, check for peers */
+ if (sipmethod == SIP_SUBSCRIBE)
+ /* For subscribes, match on peer name only */
+ peer = find_peer(of, NULL, 1, 0);
+ else
+ /* Look for peer based on the IP address we received data from */
+ /* If peer is registered from this IP address or have this as a default
+ IP address, this call is from the peer
+ */
+ peer = find_peer(NULL, &p->recv, 1, 0);
+
+ if (peer) {
+ /* Set Frame packetization */
+ if (p->rtp) {
+ ast_rtp_codec_setpref(p->rtp, &peer->prefs);
+ p->autoframing = peer->autoframing;
+ }
+ if (debug)
+ ast_verbose("Found peer '%s'\n", peer->name);
+
+ /* 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 */
+ if (p->sipoptions)
+ peer->sipoptions = p->sipoptions;
+
+ /* 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);
+ if (*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);
+ }
+ 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;
+ if (ast_test_flag(&peer->flags[0], SIP_INSECURE_INVITE)) {
+ /* Pretend there is no required authentication */
+ ast_string_field_free(p, peersecret);
+ ast_string_field_free(p, peermd5secret);
+ }
+ if (!(res = check_auth(p, req, peer->name, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable, ast_test_flag(req, SIP_PKT_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);
+
+ if (sipmethod == SIP_INVITE) {
+ /* copy channel vars */
+ for (v = peer->chanvars ; v ; v = v->next) {
+ if ((tmpvar = ast_variable_new(v->name, v->value))) {
+ tmpvar->next = p->chanvars;
+ p->chanvars = tmpvar;
+ }
+ }
+ }
+ 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)) {
+ 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_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[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;
+ }
+ ASTOBJ_UNREF(peer, sip_destroy_peer);
+ } else {
+ if (debug)
+ ast_verbose("Found no matching peer or user for '%s:%d'\n", ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port));
+
+ /* do we allow guests? */
+ if (!global_allowguest) {
+ 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 */
+ } else if (!ast_strlen_zero(rpid_num) && ast_test_flag(&p->flags[0], SIP_TRUSTRPID)) {
+ char *tmp = ast_strdupa(rpid_num);
+ if (*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);
+ }
+ }
+
+ }
+
+ if (user)
+ ASTOBJ_UNREF(user, sip_destroy_user);
+ 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 (strncmp(content_type, "text/plain", strlen("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 int sip_show_inuse(int fd, int argc, char *argv[])
+{
+#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;
+
+ if (argc < 3)
+ return RESULT_SHOWUSAGE;
+
+ if (argc == 4 && !strcmp(argv[3],"all"))
+ showall = TRUE;
+
+ ast_cli(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(fd, FORMAT2, iterator->name, iused, ilimits);
+ ASTOBJ_UNLOCK(iterator);
+ } while (0) );
+
+ ast_cli(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", iterator->inUse, iterator->inRinging);
+ if (showall || iterator->call_limit)
+ ast_cli(fd, FORMAT2, iterator->name, iused, ilimits);
+ ASTOBJ_UNLOCK(iterator);
+ } while (0) );
+
+ return RESULT_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";
+}
+
+/*! \brief Convert NAT setting to text string */
+static char *nat2str(int nat)
+{
+ switch(nat) {
+ case SIP_NAT_NEVER:
+ return "No";
+ case SIP_NAT_ROUTE:
+ return "Route";
+ case SIP_NAT_ALWAYS:
+ return "Always";
+ case SIP_NAT_RFC3581:
+ return "RFC3581";
+ default:
+ return "Unknown";
+ }
+}
+
+/*! \brief Report Peer status in character string
+ * \return 0 if peer is unreachable, 1 if peer is online, -1 if unmonitored
+ */
+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 CLI Command 'SIP Show Users' */
+static int sip_show_users(int fd, int argc, char *argv[])
+{
+ regex_t regexbuf;
+ int havepattern = FALSE;
+
+#define FORMAT "%-25.25s %-15.15s %-15.15s %-15.15s %-5.5s%-10.10s\n"
+
+ switch (argc) {
+ case 5:
+ if (!strcasecmp(argv[3], "like")) {
+ if (regcomp(&regexbuf, argv[4], REG_EXTENDED | REG_NOSUB))
+ return RESULT_SHOWUSAGE;
+ havepattern = TRUE;
+ } else
+ return RESULT_SHOWUSAGE;
+ case 3:
+ break;
+ default:
+ return RESULT_SHOWUSAGE;
+ }
+
+ ast_cli(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(fd, FORMAT, iterator->name,
+ iterator->secret,
+ iterator->accountcode,
+ iterator->context,
+ iterator->ha ? "Yes" : "No",
+ nat2str(ast_test_flag(&iterator->flags[0], SIP_NAT)));
+ ASTOBJ_UNLOCK(iterator);
+ } while (0)
+ );
+
+ if (havepattern)
+ regfree(&regexbuf);
+
+ return RESULT_SUCCESS;
+#undef FORMAT
+}
+
+static char mandescr_show_peers[] =
+"Description: Lists SIP peers in text format with details on current status.\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_ack(s, m, "Peer status list will follow");
+ /* 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"
+ "ListItems: %d\r\n"
+ "%s"
+ "\r\n", total, idtext);
+ return 0;
+}
+
+/*! \brief CLI Show Peers command */
+static int sip_show_peers(int fd, int argc, char *argv[])
+{
+ return _sip_show_peers(fd, NULL, NULL, NULL, argc, (const char **) argv);
+}
+
+/*! \brief _sip_show_peers: Execute sip show peers command */
+static int _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;
+
+#define FORMAT2 "%-25.25s %-15.15s %-3.3s %-3.3s %-3.3s %-8s %-10s %-10s\n"
+#define FORMAT "%-25.25s %-15.15s %-3.3s %-3.3s %-3.3s %-8d %-10s %-10s\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 RESULT_SHOWUSAGE;
+ havepattern = TRUE;
+ } else
+ return RESULT_SHOWUSAGE;
+ case 3:
+ break;
+ default:
+ return RESULT_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)",
+ ast_test_flag(&iterator->flags[1], SIP_PAGE2_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 ? (ast_test_flag(&iterator->flags[0], SIP_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)",
+ ast_test_flag(&iterator->flags[1], SIP_PAGE2_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 ? (ast_test_flag(&iterator->flags[0], SIP_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"
+ "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),
+ ast_test_flag(&iterator->flags[1], SIP_PAGE2_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? */
+ iterator->ha ? "yes" : "no", /* permit/deny */
+ status,
+ realtimepeers ? (ast_test_flag(&iterator->flags[0], SIP_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 RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+/*! \brief List all allocated SIP Objects (realtime or static) */
+static int sip_show_objects(int fd, int argc, char *argv[])
+{
+ char tmp[256];
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_cli(fd, "-= User objects: %d static, %d realtime =-\n\n", suserobjs, ruserobjs);
+ ASTOBJ_CONTAINER_DUMP(fd, tmp, sizeof(tmp), &userl);
+ ast_cli(fd, "-= Peer objects: %d static, %d realtime, %d autocreate =-\n\n", speerobjs, rpeerobjs, apeerobjs);
+ ASTOBJ_CONTAINER_DUMP(fd, tmp, sizeof(tmp), &peerl);
+ ast_cli(fd, "-= Registry objects: %d =-\n\n", regobjs);
+ ASTOBJ_CONTAINER_DUMP(fd, tmp, sizeof(tmp), &regl);
+ return RESULT_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 Convert DTMF mode to printable string */
+static const char *dtmfmode2str(int mode)
+{
+ switch (mode) {
+ case SIP_DTMF_RFC2833:
+ return "rfc2833";
+ case SIP_DTMF_INFO:
+ return "info";
+ case SIP_DTMF_INBAND:
+ return "inband";
+ case SIP_DTMF_AUTO:
+ return "auto";
+ }
+ return "<error>";
+}
+
+/*! \brief Convert Insecure setting to printable string */
+static const char *insecure2str(int port, int invite)
+{
+ if (port && invite)
+ return "port,invite";
+ else if (port)
+ return "port";
+ else if (invite)
+ return "invite";
+ else
+ return "no";
+}
+
+/*! \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 int sip_prune_realtime(int fd, int argc, char *argv[])
+{
+ struct sip_peer *peer;
+ struct sip_user *user;
+ int pruneuser = FALSE;
+ int prunepeer = FALSE;
+ int multi = FALSE;
+ char *name = NULL;
+ regex_t regexbuf;
+
+ switch (argc) {
+ case 4:
+ if (!strcasecmp(argv[3], "user"))
+ return RESULT_SHOWUSAGE;
+ if (!strcasecmp(argv[3], "peer"))
+ return RESULT_SHOWUSAGE;
+ if (!strcasecmp(argv[3], "like"))
+ return RESULT_SHOWUSAGE;
+ if (!strcasecmp(argv[3], "all")) {
+ multi = TRUE;
+ pruneuser = prunepeer = TRUE;
+ } else {
+ pruneuser = prunepeer = TRUE;
+ name = argv[3];
+ }
+ break;
+ case 5:
+ if (!strcasecmp(argv[4], "like"))
+ return RESULT_SHOWUSAGE;
+ if (!strcasecmp(argv[3], "all"))
+ return RESULT_SHOWUSAGE;
+ if (!strcasecmp(argv[3], "like")) {
+ multi = TRUE;
+ name = argv[4];
+ pruneuser = prunepeer = TRUE;
+ } else if (!strcasecmp(argv[3], "user")) {
+ pruneuser = TRUE;
+ if (!strcasecmp(argv[4], "all"))
+ multi = TRUE;
+ else
+ name = argv[4];
+ } else if (!strcasecmp(argv[3], "peer")) {
+ prunepeer = TRUE;
+ if (!strcasecmp(argv[4], "all"))
+ multi = TRUE;
+ else
+ name = argv[4];
+ } else
+ return RESULT_SHOWUSAGE;
+ break;
+ case 6:
+ if (strcasecmp(argv[4], "like"))
+ return RESULT_SHOWUSAGE;
+ if (!strcasecmp(argv[3], "user")) {
+ pruneuser = TRUE;
+ name = argv[5];
+ } else if (!strcasecmp(argv[3], "peer")) {
+ prunepeer = TRUE;
+ name = argv[5];
+ } else
+ return RESULT_SHOWUSAGE;
+ break;
+ default:
+ return RESULT_SHOWUSAGE;
+ }
+
+ if (multi && name) {
+ if (regcomp(&regexbuf, name, REG_EXTENDED | REG_NOSUB))
+ return RESULT_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(fd, "%d peers pruned.\n", pruned);
+ } else
+ ast_cli(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(fd, "%d users pruned.\n", pruned);
+ } else
+ ast_cli(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(fd, "Peer '%s' is not a Realtime peer, cannot be pruned.\n", name);
+ ASTOBJ_CONTAINER_LINK(&peerl, peer);
+ } else
+ ast_cli(fd, "Peer '%s' pruned.\n", name);
+ ASTOBJ_UNREF(peer, sip_destroy_peer);
+ } else
+ ast_cli(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(fd, "User '%s' is not a Realtime user, cannot be pruned.\n", name);
+ ASTOBJ_CONTAINER_LINK(&userl, user);
+ } else
+ ast_cli(fd, "User '%s' pruned.\n", name);
+ ASTOBJ_UNREF(user, sip_destroy_user);
+ } else
+ ast_cli(fd, "User '%s' not found.\n", name);
+ }
+ }
+
+ return RESULT_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 int sip_show_domains(int fd, int argc, char *argv[])
+{
+ struct domain *d;
+#define FORMAT "%-40.40s %-20.20s %-16.16s\n"
+
+ if (AST_LIST_EMPTY(&domain_list)) {
+ ast_cli(fd, "SIP Domain support not enabled.\n\n");
+ return RESULT_SUCCESS;
+ } else {
+ ast_cli(fd, FORMAT, "Our local SIP domains:", "Context", "Set by");
+ AST_LIST_LOCK(&domain_list);
+ AST_LIST_TRAVERSE(&domain_list, d, list)
+ ast_cli(fd, FORMAT, d->domain, S_OR(d->context, "(default)"),
+ domain_mode_to_text(d->mode));
+ AST_LIST_UNLOCK(&domain_list);
+ ast_cli(fd, "\n");
+ return RESULT_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;
+ int ret;
+
+ peer = astman_get_header(m,"Peer");
+ if (ast_strlen_zero(peer)) {
+ astman_send_error(s, m, "Peer: <name> missing.");
+ return 0;
+ }
+ a[0] = "sip";
+ a[1] = "show";
+ a[2] = "peer";
+ a[3] = peer;
+
+ ret = _sip_show_peer(1, -1, s, m, 4, a);
+ astman_append(s, "\r\n\r\n" );
+ return ret;
+}
+
+
+
+/*! \brief Show one peer in detail */
+static int sip_show_peer(int fd, int argc, char *argv[])
+{
+ return _sip_show_peer(0, fd, NULL, NULL, argc, (const char **) argv);
+}
+
+/*! \brief Show one peer in detail (main function) */
+static int _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 RESULT_SHOWUSAGE;
+
+ load_realtime = (argc == 5 && !strcmp(argv[4], "load")) ? TRUE : FALSE;
+ peer = find_peer(argv[3], NULL, load_realtime, 0);
+ 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.", argv[3]);
+ astman_send_error(s, m, cbuf);
+ return 0;
+ }
+ }
+ if (peer && type==0 ) { /* Normal listing */
+ 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", ast_test_flag(&peer->flags[0], SIP_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);
+ ast_cli(fd, " Mailbox : %s\n", peer->mailbox);
+ 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);
+ ast_cli(fd, " Dynamic : %s\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_DYNAMIC)?"Yes":"No"));
+ 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_PORT), ast_test_flag(&peer->flags[0], SIP_INSECURE_INVITE)));
+ ast_cli(fd, " Nat : %s\n", nat2str(ast_test_flag(&peer->flags[0], SIP_NAT)));
+ ast_cli(fd, " ACL : %s\n", (peer->ha?"Yes":"No"));
+ ast_cli(fd, " T38 pt UDPTL : %s\n", ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT_UDPTL)?"Yes":"No");
+#ifdef WHEN_WE_HAVE_T38_FOR_OTHER_TRANSPORTS
+ ast_cli(fd, " T38 pt RTP : %s\n", ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT_RTP)?"Yes":"No");
+ ast_cli(fd, " T38 pt TCP : %s\n", ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT_TCP)?"Yes":"No");
+#endif
+ ast_cli(fd, " CanReinvite : %s\n", ast_test_flag(&peer->flags[0], SIP_CAN_REINVITE)?"Yes":"No");
+ ast_cli(fd, " PromiscRedir : %s\n", ast_test_flag(&peer->flags[0], SIP_PROMISCREDIR)?"Yes":"No");
+ ast_cli(fd, " User=Phone : %s\n", ast_test_flag(&peer->flags[0], SIP_USEREQPHONE)?"Yes":"No");
+ ast_cli(fd, " Video Support: %s\n", ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT)?"Yes":"No");
+ ast_cli(fd, " Trust RPID : %s\n", ast_test_flag(&peer->flags[0], SIP_TRUSTRPID) ? "Yes" : "No");
+ ast_cli(fd, " Send RPID : %s\n", ast_test_flag(&peer->flags[0], SIP_SENDRPID) ? "Yes" : "No");
+ ast_cli(fd, " Subscriptions: %s\n", ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE) ? "Yes" : "No");
+ ast_cli(fd, " Overlap dial : %s\n", ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWOVERLAP) ? "Yes" : "No");
+
+ /* - is enumerated */
+ ast_cli(fd, " DTMFmode : %s\n", dtmfmode2str(ast_test_flag(&peer->flags[0], SIP_DTMF)));
+ ast_cli(fd, " LastMsg : %d\n", peer->lastmsg);
+ 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));
+ 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", peer->autoframing ? "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);
+ 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,"\n");
+ ASTOBJ_UNREF(peer,sip_destroy_peer);
+ } else if (peer && type == 1) { /* manager listing */
+ char buf[256];
+ 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));
+ astman_append(s, "VoiceMailbox: %s\r\n", peer->mailbox);
+ 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, "MaxCallBR: %d kbps\r\n", peer->maxcallbitrate);
+ astman_append(s, "Dynamic: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_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_PORT), ast_test_flag(&peer->flags[0], SIP_INSECURE_INVITE)));
+ 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"));
+
+ /* - is enumerated */
+ astman_append(s, "SIP-DTMFmode: %s\r\n", dtmfmode2str(ast_test_flag(&peer->flags[0], SIP_DTMF)));
+ astman_append(s, "SIPLastMsg: %d\r\n", peer->lastmsg);
+ 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);
+ 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);
+ }
+ }
+
+ ASTOBJ_UNREF(peer,sip_destroy_peer);
+
+ } else {
+ ast_cli(fd,"Peer %s not found.\n", argv[3]);
+ ast_cli(fd,"\n");
+ }
+
+ return RESULT_SUCCESS;
+}
+
+/*! \brief Show one user in detail */
+static int sip_show_user(int fd, int argc, char *argv[])
+{
+ char cbuf[256];
+ struct sip_user *user;
+ struct ast_variable *v;
+ int load_realtime;
+
+ if (argc < 4)
+ return RESULT_SHOWUSAGE;
+
+ /* Load from realtime storage? */
+ load_realtime = (argc == 5 && !strcmp(argv[4], "load")) ? TRUE : FALSE;
+
+ user = find_user(argv[3], load_realtime);
+ if (user) {
+ ast_cli(fd,"\n\n");
+ ast_cli(fd, " * Name : %s\n", user->name);
+ ast_cli(fd, " Secret : %s\n", ast_strlen_zero(user->secret)?"<Not set>":"<Set>");
+ ast_cli(fd, " MD5Secret : %s\n", ast_strlen_zero(user->md5secret)?"<Not set>":"<Set>");
+ ast_cli(fd, " Context : %s\n", user->context);
+ ast_cli(fd, " Language : %s\n", user->language);
+ if (!ast_strlen_zero(user->accountcode))
+ ast_cli(fd, " Accountcode : %s\n", user->accountcode);
+ ast_cli(fd, " AMA flags : %s\n", ast_cdr_flags2str(user->amaflags));
+ ast_cli(fd, " Transfer mode: %s\n", transfermode2str(user->allowtransfer));
+ ast_cli(fd, " MaxCallBR : %d kbps\n", user->maxcallbitrate);
+ ast_cli(fd, " CallingPres : %s\n", ast_describe_caller_presentation(user->callingpres));
+ ast_cli(fd, " Call limit : %d\n", user->call_limit);
+ ast_cli(fd, " Callgroup : ");
+ print_group(fd, user->callgroup, 0);
+ ast_cli(fd, " Pickupgroup : ");
+ print_group(fd, user->pickupgroup, 0);
+ ast_cli(fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), user->cid_name, user->cid_num, "<unspecified>"));
+ ast_cli(fd, " ACL : %s\n", (user->ha?"Yes":"No"));
+ ast_cli(fd, " Codec Order : (");
+ print_codec_to_cli(fd, &user->prefs);
+ ast_cli(fd, ")\n");
+
+ ast_cli(fd, " Auto-Framing: %s \n", user->autoframing ? "Yes" : "No");
+ if (user->chanvars) {
+ ast_cli(fd, " Variables :\n");
+ for (v = user->chanvars ; v ; v = v->next)
+ ast_cli(fd, " %s = %s\n", v->name, v->value);
+ }
+ ast_cli(fd,"\n");
+ ASTOBJ_UNREF(user,sip_destroy_user);
+ } else {
+ ast_cli(fd,"User %s not found.\n", argv[3]);
+ ast_cli(fd,"\n");
+ }
+
+ return RESULT_SUCCESS;
+}
+
+/*! \brief Show SIP Registry (registrations with other SIP proxies */
+static int sip_show_registry(int fd, int argc, char *argv[])
+{
+#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 tm tm;
+
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_cli(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) {
+ ast_localtime(&iterator->regtime, &tm, NULL);
+ strftime(tmpdat, sizeof(tmpdat), "%a, %d %b %Y %T", &tm);
+ } else {
+ tmpdat[0] = 0;
+ }
+ ast_cli(fd, FORMAT, host, iterator->username, iterator->refresh, regstate2str(iterator->regstate), tmpdat);
+ ASTOBJ_UNLOCK(iterator);
+ } while(0));
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+/*! \brief List global settings for the SIP channel */
+static int sip_show_settings(int fd, int argc, char *argv[])
+{
+ int realtimepeers;
+ int realtimeusers;
+ char codec_buf[SIPBUFSIZE];
+
+ realtimepeers = ast_check_realtime("sippeers");
+ realtimeusers = ast_check_realtime("sipusers");
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_cli(fd, "\n\nGlobal Settings:\n");
+ ast_cli(fd, "----------------\n");
+ ast_cli(fd, " SIP Port: %d\n", ntohs(bindaddr.sin_port));
+ ast_cli(fd, " Bindaddress: %s\n", ast_inet_ntoa(bindaddr.sin_addr));
+ ast_cli(fd, " Videosupport: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "Yes" : "No");
+ ast_cli(fd, " AutoCreatePeer: %s\n", autocreatepeer ? "Yes" : "No");
+ ast_cli(fd, " Allow unknown access: %s\n", global_allowguest ? "Yes" : "No");
+ ast_cli(fd, " Allow subscriptions: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_ALLOWSUBSCRIBE) ? "Yes" : "No");
+ ast_cli(fd, " Allow overlap dialing: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP) ? "Yes" : "No");
+ ast_cli(fd, " Promsic. redir: %s\n", ast_test_flag(&global_flags[0], SIP_PROMISCREDIR) ? "Yes" : "No");
+ ast_cli(fd, " SIP domain support: %s\n", AST_LIST_EMPTY(&domain_list) ? "No" : "Yes");
+ ast_cli(fd, " Call to non-local dom.: %s\n", allow_external_domains ? "Yes" : "No");
+ ast_cli(fd, " URI user is phone no: %s\n", ast_test_flag(&global_flags[0], SIP_USEREQPHONE) ? "Yes" : "No");
+ ast_cli(fd, " Our auth realm %s\n", global_realm);
+ ast_cli(fd, " Realm. auth: %s\n", authl ? "Yes": "No");
+ ast_cli(fd, " Always auth rejects: %s\n", global_alwaysauthreject ? "Yes" : "No");
+ ast_cli(fd, " Call limit peers only: %s\n", global_limitonpeers ? "Yes" : "No");
+ ast_cli(fd, " Direct RTP setup: %s\n", global_directrtpsetup ? "Yes" : "No");
+ ast_cli(fd, " User Agent: %s\n", global_useragent);
+ ast_cli(fd, " MWI checking interval: %d secs\n", global_mwitime);
+ ast_cli(fd, " Reg. context: %s\n", S_OR(global_regcontext, "(not set)"));
+ ast_cli(fd, " Caller ID: %s\n", default_callerid);
+ ast_cli(fd, " From: Domain: %s\n", default_fromdomain);
+ ast_cli(fd, " Record SIP history: %s\n", recordhistory ? "On" : "Off");
+ ast_cli(fd, " Call Events: %s\n", global_callevents ? "On" : "Off");
+ ast_cli(fd, " IP ToS SIP: %s\n", ast_tos2str(global_tos_sip));
+ ast_cli(fd, " IP ToS RTP audio: %s\n", ast_tos2str(global_tos_audio));
+ ast_cli(fd, " IP ToS RTP video: %s\n", ast_tos2str(global_tos_video));
+ ast_cli(fd, " T38 fax pt UDPTL: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT_UDPTL) ? "Yes" : "No");
+#ifdef WHEN_WE_HAVE_T38_FOR_OTHER_TRANSPORTS
+ ast_cli(fd, " T38 fax pt RTP: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT_RTP) ? "Yes" : "No");
+ ast_cli(fd, " T38 fax pt TCP: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT_TCP) ? "Yes" : "No");
+#endif
+ ast_cli(fd, " RFC2833 Compensation: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_RFC2833_COMPENSATE) ? "Yes" : "No");
+ if (!realtimepeers && !realtimeusers)
+ ast_cli(fd, " SIP realtime: Disabled\n" );
+ else
+ ast_cli(fd, " SIP realtime: Enabled\n" );
+
+ ast_cli(fd, "\nGlobal Signalling Settings:\n");
+ ast_cli(fd, "---------------------------\n");
+ ast_cli(fd, " Codecs: ");
+ ast_getformatname_multiple(codec_buf, sizeof(codec_buf) -1, global_capability);
+ ast_cli(fd, "%s\n", codec_buf);
+ ast_cli(fd, " Codec Order: ");
+ print_codec_to_cli(fd, &default_prefs);
+ ast_cli(fd, "\n");
+ ast_cli(fd, " T1 minimum: %d\n", global_t1min);
+ ast_cli(fd, " Relax DTMF: %s\n", global_relaxdtmf ? "Yes" : "No");
+ ast_cli(fd, " Compact SIP headers: %s\n", compactheaders ? "Yes" : "No");
+ ast_cli(fd, " RTP Keepalive: %d %s\n", global_rtpkeepalive, global_rtpkeepalive ? "" : "(Disabled)" );
+ ast_cli(fd, " RTP Timeout: %d %s\n", global_rtptimeout, global_rtptimeout ? "" : "(Disabled)" );
+ ast_cli(fd, " RTP Hold Timeout: %d %s\n", global_rtpholdtimeout, global_rtpholdtimeout ? "" : "(Disabled)");
+ ast_cli(fd, " MWI NOTIFY mime type: %s\n", default_notifymime);
+ ast_cli(fd, " DNS SRV lookup: %s\n", srvlookup ? "Yes" : "No");
+ ast_cli(fd, " Pedantic SIP support: %s\n", pedanticsipchecking ? "Yes" : "No");
+ ast_cli(fd, " Reg. min duration %d secs\n", min_expiry);
+ ast_cli(fd, " Reg. max duration: %d secs\n", max_expiry);
+ ast_cli(fd, " Reg. default duration: %d secs\n", default_expiry);
+ ast_cli(fd, " Outbound reg. timeout: %d secs\n", global_reg_timeout);
+ ast_cli(fd, " Outbound reg. attempts: %d\n", global_regattempts_max);
+ ast_cli(fd, " Notify ringing state: %s\n", global_notifyringing ? "Yes" : "No");
+ ast_cli(fd, " Notify hold state: %s\n", global_notifyhold ? "Yes" : "No");
+ ast_cli(fd, " SIP Transfer mode: %s\n", transfermode2str(global_allowtransfer));
+ ast_cli(fd, " Max Call Bitrate: %d kbps\r\n", default_maxcallbitrate);
+ ast_cli(fd, " Auto-Framing: %s \r\n", global_autoframing ? "Yes" : "No");
+ ast_cli(fd, "\nDefault Settings:\n");
+ ast_cli(fd, "-----------------\n");
+ ast_cli(fd, " Context: %s\n", default_context);
+ ast_cli(fd, " Nat: %s\n", nat2str(ast_test_flag(&global_flags[0], SIP_NAT)));
+ ast_cli(fd, " DTMF: %s\n", dtmfmode2str(ast_test_flag(&global_flags[0], SIP_DTMF)));
+ ast_cli(fd, " Qualify: %d\n", default_qualify);
+ ast_cli(fd, " Use ClientCode: %s\n", ast_test_flag(&global_flags[0], SIP_USECLIENTCODE) ? "Yes" : "No");
+ ast_cli(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(fd, " Language: %s\n", S_OR(default_language, "(Defaults to English)"));
+ ast_cli(fd, " MOH Interpret: %s\n", default_mohinterpret);
+ ast_cli(fd, " MOH Suggest: %s\n", default_mohsuggest);
+ ast_cli(fd, " Voice Mail Extension: %s\n", default_vmexten);
+
+
+ if (realtimepeers || realtimeusers) {
+ ast_cli(fd, "\nRealtime SIP Settings:\n");
+ ast_cli(fd, "----------------------\n");
+ ast_cli(fd, " Realtime Peers: %s\n", realtimepeers ? "Yes" : "No");
+ ast_cli(fd, " Realtime Users: %s\n", realtimeusers ? "Yes" : "No");
+ ast_cli(fd, " Cache Friends: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) ? "Yes" : "No");
+ ast_cli(fd, " Update: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_RTUPDATE) ? "Yes" : "No");
+ ast_cli(fd, " Ignore Reg. Expire: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_IGNOREREGEXPIRE) ? "Yes" : "No");
+ ast_cli(fd, " Save sys. name: %s\n", ast_test_flag(&global_flags[1], SIP_PAGE2_RTSAVE_SYSNAME) ? "Yes" : "No");
+ ast_cli(fd, " Auto Clear: %d\n", global_rtautoclear);
+ }
+ ast_cli(fd, "\n----\n");
+ return RESULT_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];
+}
+
+/*! \brief Show active SIP channels */
+static int sip_show_channels(int fd, int argc, char *argv[])
+{
+ return __sip_show_channels(fd, argc, argv, 0);
+}
+
+/*! \brief Show active SIP subscriptions */
+static int sip_show_subscriptions(int fd, int argc, char *argv[])
+{
+ return __sip_show_channels(fd, argc, argv, 1);
+}
+
+/*! \brief SIP show channels CLI (main function) */
+static int __sip_show_channels(int fd, int argc, char *argv[], int subscriptions)
+{
+#define FORMAT3 "%-15.15s %-10.10s %-11.11s %-15.15s %-13.13s %-15.15s %-10.10s\n"
+#define FORMAT2 "%-15.15s %-10.10s %-11.11s %-11.11s %-15.15s %-7.7s %-15.15s\n"
+#define FORMAT "%-15.15s %-10.10s %-11.11s %5.5d/%5.5d %-15.15s %-3.3s %-3.3s %-15.15s %-10.10s\n"
+ struct sip_pvt *cur;
+ int numchans = 0;
+ char *referstatus = NULL;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&iflock);
+ cur = iflist;
+ if (!subscriptions)
+ ast_cli(fd, FORMAT2, "Peer", "User/ANR", "Call ID", "Seq (Tx/Rx)", "Format", "Hold", "Last Message");
+ else
+ ast_cli(fd, FORMAT3, "Peer", "User", "Call ID", "Extension", "Last state", "Type", "Mailbox");
+ for (; cur; cur = cur->next) {
+ referstatus = "";
+ if (cur->refer) { /* SIP transfer in progress */
+ referstatus = referstatus2str(cur->refer->status);
+ }
+ if (cur->subscribed == NONE && !subscriptions) {
+ char formatbuf[SIPBUFSIZE/2];
+ ast_cli(fd, FORMAT, ast_inet_ntoa(cur->sa.sin_addr),
+ S_OR(cur->username, S_OR(cur->cid_num, "(None)")),
+ cur->callid,
+ cur->ocseq, cur->icseq,
+ ast_getformatname_multiple(formatbuf, sizeof(formatbuf), cur->owner ? cur->owner->nativeformats : 0),
+ ast_test_flag(&cur->flags[1], SIP_PAGE2_CALL_ONHOLD) ? "Yes" : "No",
+ ast_test_flag(&cur->flags[0], SIP_NEEDDESTROY) ? "(d)" : "",
+ cur->lastmsg ,
+ referstatus
+ );
+ numchans++;
+ }
+ if (cur->subscribed != NONE && subscriptions) {
+ ast_cli(fd, FORMAT3, ast_inet_ntoa(cur->sa.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 ? (cur->relatedpeer ? cur->relatedpeer->mailbox : "<none>") : "<none>"
+);
+ numchans++;
+ }
+ }
+ ast_mutex_unlock(&iflock);
+ if (!subscriptions)
+ ast_cli(fd, "%d active SIP channel%s\n", numchans, (numchans != 1) ? "s" : "");
+ else
+ ast_cli(fd, "%d active SIP subscription%s\n", numchans, (numchans != 1) ? "s" : "");
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+#undef FORMAT3
+}
+
+/*! \brief Support routine for 'sip show channel' CLI */
+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);
+
+ if (pos != 3) {
+ return NULL;
+ }
+
+ ast_mutex_lock(&iflock);
+ for (cur = iflist; cur; cur = cur->next) {
+ if (!strncasecmp(word, cur->callid, wordlen) && ++which > state) {
+ c = ast_strdup(cur->callid);
+ break;
+ }
+ }
+ ast_mutex_unlock(&iflock);
+ 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 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 debug peer' CLI */
+static char *complete_sip_debug_peer(const char *line, const char *word, int pos, int state)
+{
+ if (pos == 3)
+ return complete_sip_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 Support routine for 'sip prune realtime peer' CLI */
+static char *complete_sip_prune_realtime_peer(const char *line, const char *word, int pos, int state)
+{
+ if (pos == 4)
+ return complete_sip_peer(word, state, SIP_PAGE2_RTCACHEFRIENDS);
+ return NULL;
+}
+
+/*! \brief Support routine for 'sip prune realtime user' CLI */
+static char *complete_sip_prune_realtime_user(const char *line, const char *word, int pos, int state)
+{
+ if (pos == 4)
+ return complete_sip_user(word, state, SIP_PAGE2_RTCACHEFRIENDS);
+
+ return NULL;
+}
+
+/*! \brief Show details of one active dialog */
+static int sip_show_channel(int fd, int argc, char *argv[])
+{
+ struct sip_pvt *cur;
+ size_t len;
+ int found = 0;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ len = strlen(argv[3]);
+ ast_mutex_lock(&iflock);
+ for (cur = iflist; cur; cur = cur->next) {
+ if (!strncasecmp(cur->callid, argv[3], len)) {
+ char formatbuf[SIPBUFSIZE/2];
+ ast_cli(fd,"\n");
+ if (cur->subscribed != NONE)
+ ast_cli(fd, " * Subscription (type: %s)\n", subscription_type2str(cur->subscribed));
+ else
+ ast_cli(fd, " * SIP Call\n");
+ ast_cli(fd, " Curr. trans. direction: %s\n", ast_test_flag(&cur->flags[0], SIP_OUTGOING) ? "Outgoing" : "Incoming");
+ ast_cli(fd, " Call-ID: %s\n", cur->callid);
+ ast_cli(fd, " Owner channel ID: %s\n", cur->owner ? cur->owner->name : "<none>");
+ ast_cli(fd, " Our Codec Capability: %d\n", cur->capability);
+ ast_cli(fd, " Non-Codec Capability (DTMF): %d\n", cur->noncodeccapability);
+ ast_cli(fd, " Their Codec Capability: %d\n", cur->peercapability);
+ ast_cli(fd, " Joint Codec Capability: %d\n", cur->jointcapability);
+ ast_cli(fd, " Format: %s\n", ast_getformatname_multiple(formatbuf, sizeof(formatbuf), cur->owner ? cur->owner->nativeformats : 0) );
+ ast_cli(fd, " MaxCallBR: %d kbps\n", cur->maxcallbitrate);
+ ast_cli(fd, " Theoretical Address: %s:%d\n", ast_inet_ntoa(cur->sa.sin_addr), ntohs(cur->sa.sin_port));
+ ast_cli(fd, " Received Address: %s:%d\n", ast_inet_ntoa(cur->recv.sin_addr), ntohs(cur->recv.sin_port));
+ ast_cli(fd, " SIP Transfer mode: %s\n", transfermode2str(cur->allowtransfer));
+ ast_cli(fd, " NAT Support: %s\n", nat2str(ast_test_flag(&cur->flags[0], SIP_NAT)));
+ ast_cli(fd, " Audio IP: %s %s\n", ast_inet_ntoa(cur->redirip.sin_addr.s_addr ? cur->redirip.sin_addr : cur->ourip), cur->redirip.sin_addr.s_addr ? "(Outside bridge)" : "(local)" );
+ ast_cli(fd, " Our Tag: %s\n", cur->tag);
+ ast_cli(fd, " Their Tag: %s\n", cur->theirtag);
+ ast_cli(fd, " SIP User agent: %s\n", cur->useragent);
+ if (!ast_strlen_zero(cur->username))
+ ast_cli(fd, " Username: %s\n", cur->username);
+ if (!ast_strlen_zero(cur->peername))
+ ast_cli(fd, " Peername: %s\n", cur->peername);
+ if (!ast_strlen_zero(cur->uri))
+ ast_cli(fd, " Original uri: %s\n", cur->uri);
+ if (!ast_strlen_zero(cur->cid_num))
+ ast_cli(fd, " Caller-ID: %s\n", cur->cid_num);
+ ast_cli(fd, " Need Destroy: %d\n", ast_test_flag(&cur->flags[0], SIP_NEEDDESTROY));
+ ast_cli(fd, " Last Message: %s\n", cur->lastmsg);
+ ast_cli(fd, " Promiscuous Redir: %s\n", ast_test_flag(&cur->flags[0], SIP_PROMISCREDIR) ? "Yes" : "No");
+ ast_cli(fd, " Route: %s\n", cur->route ? cur->route->hop : "N/A");
+ ast_cli(fd, " DTMF Mode: %s\n", dtmfmode2str(ast_test_flag(&cur->flags[0], SIP_DTMF)));
+ ast_cli(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(fd, "%s ", sip_options[x].text);
+ }
+ } else
+ ast_cli(fd, "(none)\n");
+ ast_cli(fd, "\n\n");
+ found++;
+ }
+ }
+ ast_mutex_unlock(&iflock);
+ if (!found)
+ ast_cli(fd, "No such SIP Call ID starting with '%s'\n", argv[3]);
+ return RESULT_SUCCESS;
+}
+
+/*! \brief Show history details of one dialog */
+static int sip_show_history(int fd, int argc, char *argv[])
+{
+ struct sip_pvt *cur;
+ size_t len;
+ int found = 0;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ if (!recordhistory)
+ ast_cli(fd, "\n***Note: History recording is currently DISABLED. Use 'sip history' to ENABLE.\n");
+ len = strlen(argv[3]);
+ ast_mutex_lock(&iflock);
+ for (cur = iflist; cur; cur = cur->next) {
+ if (!strncasecmp(cur->callid, argv[3], len)) {
+ struct sip_history *hist;
+ int x = 0;
+
+ ast_cli(fd,"\n");
+ if (cur->subscribed != NONE)
+ ast_cli(fd, " * Subscription\n");
+ else
+ ast_cli(fd, " * SIP Call\n");
+ if (cur->history)
+ AST_LIST_TRAVERSE(cur->history, hist, list)
+ ast_cli(fd, "%d. %s\n", ++x, hist->event);
+ if (x == 0)
+ ast_cli(fd, "Call '%s' has no history\n", cur->callid);
+ found++;
+ }
+ }
+ ast_mutex_unlock(&iflock);
+ if (!found)
+ ast_cli(fd, "No such SIP Call ID starting with '%s'\n", argv[3]);
+ return RESULT_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_log(LOG_DEBUG, "\n---------- SIP HISTORY for '%s' \n", dialog->callid);
+ if (dialog->subscribed)
+ ast_log(LOG_DEBUG, " * Subscription\n");
+ else
+ ast_log(LOG_DEBUG, " * SIP Call\n");
+ if (dialog->history)
+ AST_LIST_TRAVERSE(dialog->history, hist, list)
+ ast_log(LOG_DEBUG, " %-3.3d. %s\n", ++x, hist->event);
+ if (!x)
+ ast_log(LOG_DEBUG, "Call '%s' has no history\n", dialog->callid);
+ ast_log(LOG_DEBUG, "\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;
+
+ /* 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 (!p->owner) { /* not a PBX call */
+ transmit_response(p, "481 Call leg/transaction does not exist", req);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return;
+ }
+
+ 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
+ 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 Unauthorized", 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 in CLI */
+static int sip_do_debug_ip(int fd, int argc, char *argv[])
+{
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ int port = 0;
+ char *p, *arg;
+
+ /* sip set debug ip <ip> */
+ if (argc != 5)
+ return RESULT_SHOWUSAGE;
+ p = arg = argv[4];
+ strsep(&p, ":");
+ if (p)
+ port = atoi(p);
+ hp = ast_gethostbyname(arg, &ahp);
+ if (hp == NULL)
+ return RESULT_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);
+
+ ast_set_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONSOLE);
+
+ return RESULT_SUCCESS;
+}
+
+/*! \brief sip_do_debug_peer: Turn on SIP debugging with peer mask */
+static int sip_do_debug_peer(int fd, int argc, char *argv[])
+{
+ struct sip_peer *peer;
+ if (argc != 5)
+ return RESULT_SHOWUSAGE;
+ peer = find_peer(argv[4], NULL, 1, 0);
+ if (peer) {
+ if (peer->addr.sin_addr.s_addr) {
+ 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));
+ ast_set_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONSOLE);
+ } else
+ ast_cli(fd, "Unable to get IP address of peer '%s'\n", argv[4]);
+ ASTOBJ_UNREF(peer,sip_destroy_peer);
+ } else
+ ast_cli(fd, "No such peer '%s'\n", argv[4]);
+ return RESULT_SUCCESS;
+}
+
+/*! \brief Turn on SIP debugging (CLI command) */
+static int sip_do_debug(int fd, int argc, char *argv[])
+{
+ int oldsipdebug = sipdebug_console;
+ if (argc != 3) {
+ if (argc != 5)
+ return RESULT_SHOWUSAGE;
+ else if (strcmp(argv[3], "ip") == 0)
+ return sip_do_debug_ip(fd, argc, argv);
+ else if (strcmp(argv[3], "peer") == 0)
+ return sip_do_debug_peer(fd, argc, argv);
+ else
+ return RESULT_SHOWUSAGE;
+ }
+ ast_set_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONSOLE);
+ memset(&debugaddr, 0, sizeof(debugaddr));
+ ast_cli(fd, "SIP Debugging %senabled\n", oldsipdebug ? "re-" : "");
+ return RESULT_SUCCESS;
+}
+
+static int sip_do_debug_deprecated(int fd, int argc, char *argv[])
+{
+ int oldsipdebug = sipdebug_console;
+ char *newargv[6] = { "sip", "set", "debug", NULL };
+ if (argc != 2) {
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ else if (strcmp(argv[2], "ip") == 0) {
+ newargv[3] = argv[2];
+ newargv[4] = argv[3];
+ return sip_do_debug_ip(fd, argc + 1, newargv);
+ } else if (strcmp(argv[2], "peer") == 0) {
+ newargv[3] = argv[2];
+ newargv[4] = argv[3];
+ return sip_do_debug_peer(fd, argc + 1, newargv);
+ } else
+ return RESULT_SHOWUSAGE;
+ }
+ ast_set_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONSOLE);
+ memset(&debugaddr, 0, sizeof(debugaddr));
+ ast_cli(fd, "SIP Debugging %senabled\n", oldsipdebug ? "re-" : "");
+ return RESULT_SUCCESS;
+}
+
+/*! \brief Cli command to send SIP notify to peer */
+static int sip_notify(int fd, int argc, char *argv[])
+{
+ struct ast_variable *varlist;
+ int i;
+
+ if (argc < 4)
+ return RESULT_SHOWUSAGE;
+
+ if (!notify_types) {
+ ast_cli(fd, "No %s file found, or no types listed there\n", notify_config);
+ return RESULT_FAILURE;
+ }
+
+ varlist = ast_variable_browse(notify_types, argv[2]);
+
+ if (!varlist) {
+ ast_cli(fd, "Unable to find notify type '%s'\n", argv[2]);
+ return RESULT_FAILURE;
+ }
+
+ for (i = 3; i < 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 RESULT_FAILURE;
+ }
+
+ if (create_addr(p, argv[i])) {
+ /* Maybe they're not registered, etc. */
+ sip_destroy(p);
+ ast_cli(fd, "Could not create address for '%s'\n", argv[i]);
+ continue;
+ }
+
+ initreqprep(&req, p, SIP_NOTIFY);
+
+ for (var = varlist; var; var = var->next)
+ add_header(&req, var->name, ast_unescape_semicolon(var->value));
+
+ /* Recalculate our side, and recalculate Call ID */
+ if (ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip))
+ p->ourip = __ourip;
+ build_via(p);
+ build_callid_pvt(p);
+ ast_cli(fd, "Sending NOTIFY of type '%s' to '%s'\n", argv[2], argv[i]);
+ transmit_sip_request(p, &req);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+
+ return RESULT_SUCCESS;
+}
+
+/*! \brief Disable SIP Debugging in CLI */
+static int sip_no_debug(int fd, int argc, char *argv[])
+{
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ ast_clear_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONSOLE);
+ ast_cli(fd, "SIP Debugging Disabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int sip_no_debug_deprecated(int fd, int argc, char *argv[])
+{
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_clear_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONSOLE);
+ ast_cli(fd, "SIP Debugging Disabled\n");
+ return RESULT_SUCCESS;
+}
+
+/*! \brief Enable SIP History logging (CLI) */
+static int sip_do_history(int fd, int argc, char *argv[])
+{
+ if (argc != 2) {
+ return RESULT_SHOWUSAGE;
+ }
+ recordhistory = TRUE;
+ ast_cli(fd, "SIP History Recording Enabled (use 'sip show history')\n");
+ return RESULT_SUCCESS;
+}
+
+/*! \brief Disable SIP History logging (CLI) */
+static int sip_no_history(int fd, int argc, char *argv[])
+{
+ if (argc != 3) {
+ return RESULT_SHOWUSAGE;
+ }
+ recordhistory = FALSE;
+ ast_cli(fd, "SIP History Recording Disabled\n");
+ return RESULT_SUCCESS;
+}
+
+/*! \brief Authenticate for outbound registration */
+static int do_register_auth(struct sip_pvt *p, struct sip_request *req, char *header, char *respheader)
+{
+ char digest[1024];
+ p->authtries++;
+ 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 (!ast_test_flag(&p->flags[0], SIP_NO_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, char *header, char *respheader, int sipmethod, int init)
+{
+ char digest[1024];
+
+ if (!p->options && !(p->options = ast_calloc(1, sizeof(*p->options))))
+ return -2;
+
+ p->authtries++;
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "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;
+ int field_index;
+ } *i, keys[] = {
+ { "realm=", ast_string_field_index(p, realm) },
+ { "nonce=", ast_string_field_index(p, nonce) },
+ { "opaque=", ast_string_field_index(p, opaque) },
+ { "qop=", ast_string_field_index(p, qop) },
+ { "domain=", ast_string_field_index(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_index_set(p, i->field_index, 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 opaque[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(p->peerauth, p->realm))) /* Start with peer list */
+ auth = find_realm_authentication(authl, p->realm); /* If not, global list */
+
+ if (auth) {
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG,"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);
+
+ /* only include the opaque string if it's set */
+ if (!ast_strlen_zero(p->opaque)) {
+ snprintf(opaque, sizeof(opaque), ", opaque=\"%s\"", p->opaque);
+ }
+
+ /* 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\"%s, qop=auth, cnonce=\"%s\", nc=%08x", username, p->realm, uri, p->nonce, resp_hash, opaque, cnonce, p->noncecount);
+ else
+ snprintf(digest, digest_len, "Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\"%s", username, p->realm, uri, p->nonce, resp_hash, opaque);
+
+ append_history(p, "AuthResp", "Auth response sent for %s in realm %s - nc %d", username, p->realm, p->noncecount);
+
+ return 0;
+}
+
+static char show_domains_usage[] =
+"Usage: sip show domains\n"
+" Lists all configured SIP local domains.\n"
+" Asterisk only responds to SIP messages to local domains.\n";
+
+static char notify_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";
+
+static char show_users_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";
+
+static char show_user_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";
+
+static char show_inuse_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";
+
+static char show_channels_usage[] =
+"Usage: sip show channels\n"
+" Lists all currently active SIP channels.\n";
+
+static char show_channel_usage[] =
+"Usage: sip show channel <channel>\n"
+" Provides detailed status on a given SIP channel.\n";
+
+static char show_history_usage[] =
+"Usage: sip show history <channel>\n"
+" Provides detailed dialog history on a given SIP channel.\n";
+
+static char show_peers_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";
+
+static char show_peer_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";
+
+static char prune_realtime_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";
+
+static char show_reg_usage[] =
+"Usage: sip show registry\n"
+" Lists all registration requests and status.\n";
+
+static char debug_usage[] =
+"Usage: sip set debug\n"
+" Enables dumping of SIP packets for debugging purposes\n\n"
+" sip set debug ip <host[:PORT]>\n"
+" Enables dumping of SIP packets to and from host.\n\n"
+" sip set debug peer <peername>\n"
+" Enables dumping of SIP packets to and from host.\n"
+" Require peer to be registered.\n";
+
+static char no_debug_usage[] =
+"Usage: sip set debug off\n"
+" Disables dumping of SIP packets for debugging purposes\n";
+
+static char no_history_usage[] =
+"Usage: sip history off\n"
+" Disables recording of SIP dialog history for debugging purposes\n";
+
+static char history_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";
+
+static char sip_reload_usage[] =
+"Usage: sip reload\n"
+" Reloads SIP configuration from sip.conf\n";
+
+static char show_subscriptions_usage[] =
+"Usage: sip show subscriptions\n"
+" Lists active SIP subscriptions for extension states\n";
+
+static char show_objects_usage[] =
+"Usage: sip show objects\n"
+" Lists status of known SIP objects\n";
+
+static char show_settings_usage[] =
+"Usage: sip show settings\n"
+" Provides detailed list of the configuration of the SIP channel.\n";
+
+/*! \brief Read SIP header (dialplan function) */
+static int func_header_read(struct ast_channel *chan, 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 (chan->tech != &sip_tech && chan->tech != &sip_tech_info) {
+ 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, 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, 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 */
+ *colname++ = '\0';
+ else if ((colname = strchr(data, '|')))
+ *colname++ = '\0';
+ else
+ colname = "ip";
+
+ if (!(peer = find_peer(data, NULL, 1, 0)))
+ 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, "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, "curcalls")) {
+ snprintf(buf, len, "%d", peer->inUse);
+ } else if (!strcasecmp(colname, "accountcode")) {
+ ast_copy_string(buf, peer->accountcode, len);
+ } else if (!strcasecmp(colname, "useragent")) {
+ ast_copy_string(buf, peer->useragent, 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->flags[1], SIP_PAGE2_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);
+ } else {
+ buf[0] = '\0';
+ }
+ } else {
+ buf[0] = '\0';
+ }
+
+ ASTOBJ_UNREF(peer, sip_destroy_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"
+ "- 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"
+ "- status Status (if qualify=yes).\n"
+ "- regexten Registration extension\n"
+ "- limit Call limit (call-limit)\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, 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 (chan->tech != &sip_tech && chan->tech != &sip_tech_info) {
+ 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[SIPBUFSIZE];
+ char *s, *e, *uri, *t;
+ char *domain;
+
+ ast_copy_string(tmp, get_header(req, "Contact"), sizeof(tmp));
+ if ((t = strchr(tmp, ',')))
+ *t = '\0';
+ s = get_in_brackets(tmp);
+ uri = ast_strdupa(s);
+ if (ast_test_flag(&p->flags[0], SIP_PROMISCREDIR)) {
+ if (!strncasecmp(s, "sip:", 4))
+ s += 4;
+ e = strchr(s, ';');
+ if (e)
+ *e = '\0';
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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(s, ';'); /* Strip of parameters in the username part */
+ if (e)
+ *e = '\0';
+ e = strchr(domain, ';'); /* Strip of parameters in the domain part */
+ if (e)
+ *e = '\0';
+
+ if (!strncasecmp(s, "sip:", 4))
+ s += 4;
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Received 302 Redirect to extension '%s' (domain %s)\n", s, domain);
+ if (p->owner) {
+ pbx_builtin_setvar_helper(p->owner, "SIPREDIRECTURI", uri);
+ 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->lastinvite, 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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "NOT Sending pending reinvite (yet) on '%s'\n", p->callid);
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Sending pending reinvite on '%s'\n", p->callid);
+ /* Didn't get to reinvite yet, so do it now */
+ transmit_reinvite_with_sdp(p);
+ 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;
+
+ if (option_debug > 3) {
+ if (reinvite)
+ ast_log(LOG_DEBUG, "SIP response %d to RE-invite on %s call %s\n", resp, outgoing ? "outgoing" : "incoming", p->callid);
+ else
+ ast_log(LOG_DEBUG, "SIP response %d to standard invite\n", resp);
+ }
+
+ if (ast_test_flag(&p->flags[0], SIP_ALREADYGONE)) { /* This call is already gone */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 */
+ /* Don't auto congest anymore since we've gotten something useful back */
+ AST_SCHED_DEL(sched, p->initid);
+
+ /* 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 (!ast_test_flag(req, SIP_PKT_IGNORE) && (p->invitestate != INV_CANCELLED) && sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ check_pendings(p);
+ break;
+
+ case 180: /* 180 Ringing */
+ case 182: /* 182 Queued */
+ if (!ast_test_flag(req, SIP_PKT_IGNORE) && (p->invitestate != INV_CANCELLED) && sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ if (!ast_test_flag(req, SIP_PKT_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)) {
+ if (p->invitestate != INV_CANCELLED)
+ p->invitestate = INV_EARLY_MEDIA;
+ res = process_sdp(p, req);
+ if (!ast_test_flag(req, SIP_PKT_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 (!ast_test_flag(req, SIP_PKT_IGNORE) && (p->invitestate != INV_CANCELLED) && sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ /* Ignore 183 Session progress without SDP */
+ if (find_sdp(req)) {
+ if (p->invitestate != INV_CANCELLED)
+ p->invitestate = INV_EARLY_MEDIA;
+ res = process_sdp(p, req);
+ if (!ast_test_flag(req, SIP_PKT_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 (!ast_test_flag(req, SIP_PKT_IGNORE) && (p->invitestate != INV_CANCELLED) && sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ p->authtries = 0;
+ if (find_sdp(req)) {
+ if ((res = process_sdp(p, req)) && !ast_test_flag(req, SIP_PKT_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);
+ /* Save Record-Route for any later requests we make on this dialogue */
+ if (!reinvite)
+ build_route(p, req, 1);
+
+ 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 */
+ if (!p->route && !ast_test_flag(req, SIP_PKT_IGNORE))
+ ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
+ }
+
+ }
+
+ 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 (bridgepeer->tech == &sip_tech || bridgepeer->tech == &sip_tech_info) {
+ 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 {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Strange... The other side of the bridge does not have a udptl struct\n");
+ ast_mutex_lock(&bridgepvt->lock);
+ bridgepvt->t38.state = T38_DISABLED;
+ ast_mutex_unlock(&bridgepvt->lock);
+ if (option_debug)
+ ast_log(LOG_DEBUG,"T38 state changed to %d on channel %s\n", bridgepvt->t38.state, bridgepeer->tech->type);
+ p->t38.state = T38_DISABLED;
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG,"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 (option_debug > 1)
+ ast_log(LOG_DEBUG, "Strange... The other side of the bridge is not a SIP channel\n");
+ p->t38.state = T38_DISABLED;
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG,"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;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "T38 changed state to %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
+ }
+
+ if (!ast_test_flag(req, SIP_PKT_IGNORE) && p->owner) {
+ if (!reinvite) {
+ ast_queue_control(p->owner, AST_CONTROL_ANSWER);
+ } 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 (!ast_test_flag(req, SIP_PKT_IGNORE))
+ ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
+ }
+ /* If I understand this right, the branch is different for a non-200 ACK only */
+ p->invitestate = INV_TERMINATED;
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ 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 == 401 ? WWW_AUTH : PROXY_AUTH);
+
+ /* Then we AUTH */
+ ast_string_field_free(p, theirtag); /* forget their old tag, so we don't match tags when getting response */
+ if (!ast_test_flag(req, SIP_PKT_IGNORE)) {
+ char *authenticate = (resp == 401 ? "WWW-Authenticate" : "Proxy-Authenticate");
+ char *authorization = (resp == 401 ? "Authorization" : "Proxy-Authorization");
+ if (p->authtries < MAX_AUTHTRIES)
+ p->invitestate = INV_CALLING;
+ if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, authenticate, authorization, SIP_INVITE, 1)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on INVITE to '%s'\n", get_header(&p->initreq, "From"));
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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 (!ast_test_flag(req, SIP_PKT_IGNORE) && p->owner)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ sip_alreadygone(p);
+ break;
+
+ case 404: /* Not found */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ if (p->owner && !ast_test_flag(req, SIP_PKT_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 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 && !ast_test_flag(req, SIP_PKT_IGNORE)) {
+ ast_queue_hangup(p->owner);
+ append_history(p, "Hangup", "Got 487 on CANCEL request from us. Queued AST hangup request");
+ } else if (!ast_test_flag(req, SIP_PKT_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.");
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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 && !ast_test_flag(req, SIP_PKT_IGNORE))
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ } 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 && !ast_test_flag(req, SIP_PKT_IGNORE))
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ sip_alreadygone(p);
+ } else {
+ /* We can't set up this call, so give up */
+ if (p->owner && !ast_test_flag(req, SIP_PKT_IGNORE))
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ /* If there's no dialog to end, then mark p as already gone */
+ if (!reinvite)
+ sip_alreadygone(p);
+ }
+ break;
+ case 491: /* Pending */
+ /* we really should have to wait a while, then retransmit
+ * We should support the retry-after at some point
+ * At this point, we treat this as a congestion if the call is not in UP state
+ */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ if (p->owner && !ast_test_flag(req, SIP_PKT_IGNORE)) {
+ if (p->owner->_state != AST_STATE_UP) {
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ } 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);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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)
+{
+ char *auth = "Proxy-Authenticate";
+ char *auth2 = "Proxy-Authorization";
+
+ /* 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 */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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));
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ if (resp == 401) {
+ auth = "WWW-Authenticate";
+ auth2 = "Authorization";
+ }
+ if ((p->authtries > 1) || do_proxy_auth(p, req, auth, auth2, 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;
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ 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);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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;
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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 ignore, 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, "WWW-Authenticate", "Authorization")) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on REGISTER to '%s@%s' (Tries %d)\n", p->registry->username, p->registry->hostname, p->authtries);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ 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);
+ if (global_regattempts_max)
+ p->registry->regattempts = global_regattempts_max+1;
+ AST_SCHED_DEL(sched, r->timeout);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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);
+ if (global_regattempts_max)
+ p->registry->regattempts = global_regattempts_max+1;
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ r->call = NULL;
+ AST_SCHED_DEL(sched, r->timeout);
+ break;
+ case 407: /* Proxy auth */
+ if ((p->authtries == MAX_AUTHTRIES) || do_register_auth(p, req, "Proxy-Authenticate", "Proxy-Authorization")) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on REGISTER to '%s' (tries '%d')\n", get_header(&p->initreq, "From"), p->authtries);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ break;
+ case 408: /* Request timeout */
+ /* Got a timeout response, so reset the counter of failed responses */
+ r->regattempts = 0;
+ 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);
+ if (global_regattempts_max)
+ p->registry->regattempts = global_regattempts_max+1;
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ r->call = NULL;
+ AST_SCHED_DEL(sched, r->timeout);
+ break;
+ case 200: /* 200 OK */
+ if (!r) {
+ ast_log(LOG_WARNING, "Got 200 OK on REGISTER, but there isn't a registry entry for '%s' (we probably already got the OK)\n", S_OR(p->peername, p->username));
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ return 0;
+ }
+
+ r->regstate = REG_STATE_REGISTERED;
+ r->regtime = time(NULL); /* Reset time of last succesful registration */
+ manager_event(EVENT_FLAG_SYSTEM, "Registry", "ChannelDriver: SIP\r\nDomain: %s\r\nStatus: %s\r\n", r->hostname, regstate2str(r->regstate));
+ r->regattempts = 0;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Registration successful\n");
+ if (r->timeout > -1) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Cancelling timeout %d\n", r->timeout);
+ }
+ AST_SCHED_DEL(sched, r->timeout);
+ r->call = NULL;
+ p->registry = NULL;
+ /* Let this one hang around until we have all the responses */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ /* ast_set_flag(&p->flags[0], SIP_NEEDDESTROY); */
+
+ /* set us up for re-registering */
+ /* figure out how long we got registered for */
+ 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 */
+ AST_SCHED_DEL(sched, r->expire);
+ r->expire = ast_sched_add(sched, expires_ms, sip_reregister, r);
+ ASTOBJ_UNREF(r, sip_registry_destroy);
+ }
+ 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 = NULL;
+ 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",
+ "Peer: SIP/%s\r\nPeerStatus: %s\r\nTime: %d\r\n",
+ peer->name, s, pingtime);
+ }
+
+ if (!AST_SCHED_DEL(sched, peer->pokeexpire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+
+ /* Try again eventually */
+ peer->pokeexpire = ast_sched_add(sched,
+ is_reachable ? DEFAULT_FREQ_OK : DEFAULT_FREQ_NOTOK,
+ sip_poke_peer_s, ASTOBJ_REF(peer));
+
+ if (peer->pokeexpire == -1) {
+ ASTOBJ_UNREF(peer, sip_destroy_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->udptl)
+ ast_udptl_stop(p->udptl);
+}
+
+/*! \brief Handle SIP response in dialogue */
+/* XXX only called by handle_request */
+static void handle_response(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int ignore, int seqno)
+{
+ struct ast_channel *owner;
+ int sipmethod;
+ int res = 1;
+ const char *c = get_header(req, "Cseq");
+ /* GCC 4.2 complains if I try to cast c as a char * when passing it to ast_skip_nonblanks, so make a copy of it */
+ char *c_copy = ast_strdupa(c);
+ /* Skip the Cseq and its subsequent spaces */
+ const char *msg = ast_skip_blanks(ast_skip_nonblanks(c_copy));
+
+ if (!msg)
+ 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);
+
+ /* If this is a NOTIFY for a subscription clear the flag that indicates that we have a NOTIFY pending */
+ if (!p->owner && sipmethod == SIP_NOTIFY && p->pendinginvite)
+ p->pendinginvite = 0;
+
+ /* 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);
+ }
+
+ /* RFC 3261 Section 15 specifies that if we receive a 408 or 481
+ * in response to a BYE, then we should end the current dialog
+ * and session. It is known that at least one phone manufacturer
+ * potentially will send a 404 in response to a BYE, so we'll be
+ * liberal in what we accept and end the dialog and session if we
+ * receive any of those responses to a BYE.
+ */
+ if ((resp == 404 || resp == 408 || resp == 481) && sipmethod == SIP_BYE) {
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ return;
+ }
+
+ 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 if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Got OK on REFER Notify message\n");
+ } else {
+ if (p->subscribed == NONE)
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE)) {
+ /* Ready to send the next state we have on queue */
+ ast_clear_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE);
+ cb_extensionstate((char *)p->context, (char *)p->exten, p->laststate, (void *) p);
+ }
+ }
+ } else if (sipmethod == SIP_REGISTER)
+ res = handle_response_register(p, resp, rest, req, ignore, seqno);
+ else if (sipmethod == SIP_BYE) { /* Ok, we're ready to go */
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ ast_clear_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ } else if (sipmethod == SIP_SUBSCRIBE)
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ 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 */
+ 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, ignore, seqno);
+ else if (sipmethod == SIP_BYE) {
+ 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));
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ } else if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, "WWW-Authenticate", "Authorization", sipmethod, 0)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on %s to '%s'\n", msg, get_header(&p->initreq, "From"));
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ /* We fail to auth bye on our own call, but still needs to tear down the call.
+ Life, they call it. */
+ }
+ } else {
+ ast_log(LOG_WARNING, "Got authentication request (401) on unknown %s to '%s'\n", sip_methods[sipmethod].text, get_header(req, "To"));
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ 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, ignore, seqno);
+ else {
+ ast_log(LOG_WARNING, "Forbidden - maybe wrong password on authentication for %s\n", msg);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ break;
+ case 404: /* Not found */
+ if (p->registry && sipmethod == SIP_REGISTER)
+ res = handle_response_register(p, resp, rest, req, ignore, 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 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, ignore, seqno);
+ else if (sipmethod == SIP_BYE) {
+ 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));
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ } else if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, "Proxy-Authenticate", "Proxy-Authorization", sipmethod, 0)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on %s to '%s'\n", msg, get_header(&p->initreq, "From"));
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ } else /* We can't handle this, giving up in a bad way */
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+
+ 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, ignore, seqno);
+ else if (sipmethod == SIP_BYE) {
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Got timeout on bye. Thanks for the answer. Now, kill this call\n");
+ } else {
+ if (owner)
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ 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 {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Got 491 on %s, unspported. Call ID %s\n", sip_methods[sipmethod].text, p->callid);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ 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 ((option_verbose > 2) && (resp != 487))
+ ast_verbose(VERBOSE_PREFIX_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 permenantly */
+ 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... */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 502: /* Bad gateway */
+ 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)
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ } else if ((resp >= 100) && (resp < 200)) {
+ if (sipmethod == SIP_INVITE) {
+ if (!ast_test_flag(req, SIP_PKT_IGNORE) && sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ 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 (ast_test_flag(req, SIP_PKT_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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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)
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE)) {
+ /* Ready to send the next state we have on queue */
+ ast_clear_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE);
+ cb_extensionstate((char *)p->context, (char *)p->exten, p->laststate, (void *) p);
+ }
+ }
+ } else if (sipmethod == SIP_BYE)
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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 */
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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) {
+ char *auth, *auth2;
+
+ auth = (resp == 407 ? "Proxy-Authenticate" : "WWW-Authenticate");
+ auth2 = (resp == 407 ? "Proxy-Authorization" : "Authorization");
+ if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, auth, auth2, sipmethod, 0)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on %s to '%s'\n", msg, get_header(&p->initreq, "From"));
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ }
+ 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) {
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ } else if (sipdebug) {
+ ast_log (LOG_DEBUG, "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 (!ast_test_flag(req, SIP_PKT_IGNORE) && sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ }
+ }
+ if ((resp >= 300) && (resp < 700)) {
+ if ((option_verbose > 2) && (resp != 487))
+ ast_verbose(VERBOSE_PREFIX_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 502: /* Bad gateway */
+ case 503: /* Service Unavailable */
+ case 504: /* Server timeout */
+
+ /* re-invite failed */
+ if (sipmethod == SIP_INVITE && sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ 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);
+
+ if (!transferee || !transferer) {
+ ast_log(LOG_ERROR, "Missing channels for parking! Transferer %s Transferee %s\n", transferer ? "<available>" : "<missing>", transferee ? "<available>" : "<missing>" );
+ return NULL;
+ }
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "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 */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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");
+ if (option_debug)
+ ast_log(LOG_DEBUG, "SIP Call parked failed \n");
+ /* Do not hangup call */
+ }
+ free(d);
+ 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;
+ DEADLOCK_AVOIDANCE(&pvt->lock);
+ }
+ 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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "No transferer channel, giving up parking\n");
+ }
+ if (!transferee) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "No transferee channel, giving up parking\n");
+ }
+ return -1;
+ }
+ if ((d = ast_calloc(1, sizeof(*d)))) {
+ pthread_attr_t attr;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ /* 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_background(&th, &attr, sip_park_thread, d) < 0) {
+ /* Could not start thread */
+ free(d); /* We don't need it anymore. If thread is created, d will be free'd
+ by sip_park_thread() */
+ pthread_attr_destroy(&attr);
+ return 0;
+ }
+ pthread_attr_destroy(&attr);
+ }
+ 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 (ast_test_flag(chan, AST_FLAG_MOH))
+ ast_moh_stop(chan);
+ else 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 */
+ if (option_debug > 3) {
+ ast_log(LOG_DEBUG, "Sip transfer:--------------------\n");
+ if (transferer->chan1)
+ ast_log(LOG_DEBUG, "-- Transferer to PBX channel: %s State %s\n", transferer->chan1->name, ast_state2str(transferer->chan1->_state));
+ else
+ ast_log(LOG_DEBUG, "-- No transferer first channel - odd??? \n");
+ if (target->chan1)
+ ast_log(LOG_DEBUG, "-- Transferer to PBX second channel (target): %s State %s\n", target->chan1->name, ast_state2str(target->chan1->_state));
+ else
+ ast_log(LOG_DEBUG, "-- No target first channel ---\n");
+ if (transferer->chan2)
+ ast_log(LOG_DEBUG, "-- Bridged call to transferee: %s State %s\n", transferer->chan2->name, ast_state2str(transferer->chan2->_state));
+ else
+ ast_log(LOG_DEBUG, "-- No bridged call to transferee\n");
+ if (target->chan2)
+ ast_log(LOG_DEBUG, "-- 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_log(LOG_DEBUG, "-- No target second channel ---\n");
+ ast_log(LOG_DEBUG, "-- 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 */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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 */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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);
+
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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 -2;
+ }
+ 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 (option_debug > 1 && sipdebug)
+ ast_log(LOG_DEBUG, "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...
+ */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "* 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 */
+static int handle_request_options(struct sip_pvt *p, struct sip_request *req)
+{
+ int res;
+
+
+ /* XXX Should we authenticate OPTIONS? XXX */
+
+ if (p->lastinvite) {
+ /* if this is a request in an active dialog, just confirm that the dialog exists. */
+ transmit_response_with_allow(p, "200 OK", req, 0);
+ return 0;
+ }
+
+ 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. */
+ 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 */
+static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, int debug, int ignore, 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 */
+
+ /* 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) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, " Attended transfer attempted to replace call with no bridge (maybe ringing). Channel %s!\n", replacecall->name);
+ oneleggedreplace = 1;
+ }
+ }
+ if (option_debug > 3 && targetcall && targetcall->_state == AST_STATE_RINGING)
+ ast_log(LOG_DEBUG, "SIP transfer: Target channel is in ringing state\n");
+
+ if (option_debug > 3) {
+ if (targetcall)
+ ast_log(LOG_DEBUG, "SIP transfer: Invite Replace incoming channel should bridge to channel %s while hanging up channel %s\n", targetcall->name, replacecall->name);
+ else
+ ast_log(LOG_DEBUG, "SIP transfer: Invite Replace incoming channel should replace and hang up channel %s (one call leg)\n", replacecall->name);
+ }
+
+ if (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);
+ /* Do something more clever here */
+ ast_channel_unlock(c);
+ ast_mutex_unlock(&p->refer->refer_call->lock);
+ 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);
+ ast_mutex_unlock(&p->refer->refer_call->lock);
+ 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);
+
+ ast_setstate(c, AST_STATE_UP);
+
+ /* Stop music on hold and other generators */
+ ast_quiet_chan(replacecall);
+ ast_quiet_chan(targetcall);
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "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 */
+ ast_mutex_unlock(&p->refer->refer_call->lock);
+
+ /* 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 if (option_debug > 3)
+ ast_log(LOG_DEBUG, "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;
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "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;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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);
+ }
+ ast_mutex_unlock(&p->refer->refer_call->lock);
+
+ ast_setstate(c, AST_STATE_DOWN);
+ if (option_debug > 3) {
+ struct ast_channel *test;
+ ast_log(LOG_DEBUG, "After transfer:----------------------------\n");
+ ast_log(LOG_DEBUG, " -- C: %s State %s\n", c->name, ast_state2str(c->_state));
+ if (replacecall)
+ ast_log(LOG_DEBUG, " -- replacecall: %s State %s\n", replacecall->name, ast_state2str(replacecall->_state));
+ if (p->owner) {
+ ast_log(LOG_DEBUG, " -- P->owner: %s State %s\n", p->owner->name, ast_state2str(p->owner->_state));
+ test = ast_bridged_channel(p->owner);
+ if (test)
+ ast_log(LOG_DEBUG, " -- Call bridged to P->owner: %s State %s\n", test->name, ast_state2str(test->_state));
+ else
+ ast_log(LOG_DEBUG, " -- No call bridged to C->owner \n");
+ } else
+ ast_log(LOG_DEBUG, " -- No channel yet \n");
+ ast_log(LOG_DEBUG, "End After transfer:----------------------------\n");
+ }
+
+ ast_channel_unlock(p->owner); /* Unlock new owner */
+ if (!oneleggedreplace)
+ ast_mutex_unlock(&p->lock); /* Unlock SIP structure */
+
+ /* The call should be down with no ast_channel, so hang it up */
+ c->tech_pvt = NULL;
+ ast_hangup(c);
+ return 0;
+}
+
+/*! \brief helper routine for sip_uri_cmp
+ *
+ * This takes the parameters from two SIP URIs and determines
+ * if the URIs match. The rules for parameters *suck*. Here's a breakdown
+ * 1. If a parameter appears in both URIs, then they must have the same value
+ * in order for the URIs to match
+ * 2. If one URI has a user, maddr, ttl, or method parameter, then the other
+ * URI must also have that parameter and must have the same value
+ * in order for the URIs to match
+ * 3. All other headers appearing in only one URI are not considered when
+ * determining if URIs match
+ *
+ * \param input1 Parameters from URI 1
+ * \param input2 Parameters from URI 2
+ * \return Return 0 if the URIs' parameters match, 1 if they do not
+ */
+static int sip_uri_params_cmp(const char *input1, const char *input2)
+{
+ char *params1 = ast_strdupa(input1);
+ char *params2 = ast_strdupa(input2);
+ char *pos1;
+ char *pos2;
+ int maddrmatch = 0;
+ int ttlmatch = 0;
+ int usermatch = 0;
+ int methodmatch = 0;
+
+ /*Quick optimization. If both params are zero-length, then
+ * they match
+ */
+ if (ast_strlen_zero(params1) && ast_strlen_zero(params2)) {
+ return 0;
+ }
+
+ pos1 = params1;
+ while (!ast_strlen_zero(pos1)) {
+ char *name1 = pos1;
+ char *value1 = strchr(pos1, '=');
+ char *semicolon1 = strchr(pos1, ';');
+ int matched = 0;
+ if (semicolon1) {
+ *semicolon1++ = '\0';
+ }
+ if (!value1) {
+ goto fail;
+ }
+ *value1++ = '\0';
+ /* Checkpoint reached. We have the name and value parsed for param1
+ * We have to duplicate params2 each time through the second loop
+ * or else we can't search and replace the semicolons with \0 each
+ * time
+ */
+ pos2 = ast_strdupa(params2);
+ while (!ast_strlen_zero(pos2)) {
+ char *name2 = pos2;
+ char *value2 = strchr(pos2, '=');
+ char *semicolon2 = strchr(pos2, ';');
+ if (semicolon2) {
+ *semicolon2++ = '\0';
+ }
+ if (!value2) {
+ goto fail;
+ }
+ *value2++ = '\0';
+ if (!strcasecmp(name1, name2)) {
+ if (strcasecmp(value1, value2)) {
+ goto fail;
+ } else {
+ matched = 1;
+ break;
+ }
+ }
+ pos2 = semicolon2;
+ }
+ /* Need to see if the parameter we're looking at is one of the 'must-match' parameters */
+ if (!strcasecmp(name1, "maddr")) {
+ if (matched) {
+ maddrmatch = 1;
+ } else {
+ goto fail;
+ }
+ } else if (!strcasecmp(name1, "ttl")) {
+ if (matched) {
+ ttlmatch = 1;
+ } else {
+ goto fail;
+ }
+ } else if (!strcasecmp(name1, "user")) {
+ if (matched) {
+ usermatch = 1;
+ } else {
+ goto fail;
+ }
+ } else if (!strcasecmp(name1, "method")) {
+ if (matched) {
+ methodmatch = 1;
+ } else {
+ goto fail;
+ }
+ }
+ pos1 = semicolon1;
+ }
+
+ /* We've made it out of that horrible O(m*n) construct and there are no
+ * failures yet. We're not done yet, though, because params2 could have
+ * an maddr, ttl, user, or method header and params1 did not.
+ */
+ pos2 = params2;
+ while (!ast_strlen_zero(pos2)) {
+ char *name2 = pos2;
+ char *value2 = strchr(pos2, '=');
+ char *semicolon2 = strchr(pos2, ';');
+ if (semicolon2) {
+ *semicolon2++ = '\0';
+ }
+ if (!value2) {
+ goto fail;
+ }
+ *value2++ = '\0';
+ if ((!strcasecmp(name2, "maddr") && !maddrmatch) ||
+ (!strcasecmp(name2, "ttl") && !ttlmatch) ||
+ (!strcasecmp(name2, "user") && !usermatch) ||
+ (!strcasecmp(name2, "method") && !methodmatch)) {
+ goto fail;
+ }
+ }
+ return 0;
+
+fail:
+ return 1;
+}
+
+/*! \brief helper routine for sip_uri_cmp
+ *
+ * This takes the "headers" from two SIP URIs and determines
+ * if the URIs match. The rules for headers is simple. If a header
+ * appears in one URI, then it must also appear in the other URI. The
+ * order in which the headers appear does not matter.
+ *
+ * \param input1 Headers from URI 1
+ * \param input2 Headers from URI 2
+ * \return Return 0 if the URIs' headers match, 1 if they do not
+ */
+static int sip_uri_headers_cmp(const char *input1, const char *input2)
+{
+ char *headers1 = ast_strdupa(input1);
+ char *headers2 = ast_strdupa(input2);
+ int zerolength1 = ast_strlen_zero(headers1);
+ int zerolength2 = ast_strlen_zero(headers2);
+ int different = 0;
+ char *header1;
+
+ if ((zerolength1 && !zerolength2) ||
+ (zerolength2 && !zerolength1))
+ return 1;
+
+ if (zerolength1 && zerolength2)
+ return 0;
+
+ /* At this point, we can definitively state that both inputs are
+ * not zero-length. First, one more optimization. If the length
+ * of the headers is not equal, then we definitely have no match
+ */
+ if (strlen(headers1) != strlen(headers2)) {
+ return 1;
+ }
+
+ for (header1 = strsep(&headers1, "&"); header1; header1 = strsep(&headers1, "&")) {
+ if (!strcasestr(headers2, header1)) {
+ different = 1;
+ break;
+ }
+ }
+
+ return different;
+}
+
+static int sip_uri_cmp(const char *input1, const char *input2)
+{
+ char *uri1 = ast_strdupa(input1);
+ char *uri2 = ast_strdupa(input2);
+ char *host1;
+ char *host2;
+ char *params1;
+ char *params2;
+ char *headers1;
+ char *headers2;
+
+ /* Strip off "sip:" from the URI. We know this is present
+ * because it was checked back in parse_request()
+ */
+ strsep(&uri1, ":");
+ strsep(&uri2, ":");
+
+ if ((host1 = strchr(uri1, '@'))) {
+ *host1++ = '\0';
+ }
+ if ((host2 = strchr(uri2, '@'))) {
+ *host2++ = '\0';
+ }
+
+ /* Check for mismatched username and passwords. This is the
+ * only case-sensitive comparison of a SIP URI
+ */
+ if ((host1 && !host2) ||
+ (host2 && !host1) ||
+ (host1 && host2 && strcmp(uri1, uri2))) {
+ return 1;
+ }
+
+ if (!host1)
+ host1 = uri1;
+ if (!host2)
+ host2 = uri2;
+
+ /* Strip off the parameters and headers so we can compare
+ * host and port
+ */
+
+ if ((params1 = strchr(host1, ';'))) {
+ *params1++ = '\0';
+ }
+ if ((params2 = strchr(host2, ';'))) {
+ *params2++ = '\0';
+ }
+
+ /* Headers come after parameters, but there may be headers without
+ * parameters, thus the S_OR
+ */
+ if ((headers1 = strchr(S_OR(params1, host1), '?'))) {
+ *headers1++ = '\0';
+ }
+ if ((headers2 = strchr(S_OR(params2, host2), '?'))) {
+ *headers2++ = '\0';
+ }
+
+ /* Now the host/port are properly isolated. We can get by with a string comparison
+ * because the SIP URI checking rules have some interesting exceptions that make
+ * this possible. I will note 2 in particular
+ * 1. hostnames which resolve to the same IP address as well as a hostname and its
+ * IP address are not considered a match with SIP URI's.
+ * 2. If one URI specifies a port and the other does not, then the URIs do not match.
+ * This includes if one URI explicitly contains port 5060 and the other implies it
+ * by not having a port specified.
+ */
+
+ if (strcasecmp(host1, host2)) {
+ return 1;
+ }
+
+ /* Headers have easier rules to follow, so do those first */
+ if (sip_uri_headers_cmp(headers1, headers2)) {
+ return 1;
+ }
+
+ /* And now the parameters. Ugh */
+ return sip_uri_params_cmp(params1, params2);
+}
+
+
+/*! \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;
+
+ /* 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) {
+ /* At this point we only support REPLACES */
+ 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;
+ }
+ }
+
+ /* 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. */
+ int different;
+ if (pedanticsipchecking)
+ different = sip_uri_cmp(p->initreq.rlPart2, req->rlPart2);
+ else
+ different = strcmp(p->initreq.rlPart2, req->rlPart2);
+ if (!different) {
+ transmit_response(p, "482 Loop Detected", req);
+ p->invitestate = INV_COMPLETED;
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return 0;
+ } else {
+ /* This is a spiral. What we need to do is to just change the outgoing INVITE
+ * so that it now routes to the new Request URI. Since we created the INVITE ourselves
+ * that should be all we need to do.
+ */
+ char *uri = ast_strdupa(req->rlPart2);
+ char *at = strchr(uri, '@');
+ char *peerorhost;
+ if (option_debug > 2) {
+ ast_log(LOG_DEBUG, "Potential spiral detected. Original RURI was %s, new RURI is %s\n", p->initreq.rlPart2, req->rlPart2);
+ }
+ if (at) {
+ *at = '\0';
+ }
+ /* Parse out "sip:" */
+ if ((peerorhost = strchr(uri, ':'))) {
+ *peerorhost++ = '\0';
+ }
+ ast_string_field_free(p, theirtag);
+ /* Treat this as if there were a call forward instead...
+ */
+ ast_string_field_set(p->owner, call_forward, peerorhost);
+ ast_queue_control(p->owner, AST_CONTROL_BUSY);
+ return 0;
+ }
+ }
+
+ if (!ast_test_flag(req, SIP_PKT_IGNORE) && p->pendinginvite) {
+ /* We already have a pending invite. Sorry. You are on hold. */
+ transmit_response_reliable(p, "491 Request Pending", req);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "INVITE w Replaces on existing call? Refusing action. [%s]\n", p->callid);
+ transmit_response_reliable(p, "400 Bad request", req); /* The best way to not not accept the transfer */
+ /* Do not destroy existing call */
+ return -1;
+ }
+
+ if (sipdebug && option_debug > 2)
+ ast_log(LOG_DEBUG, "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_reliable(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 && option_debug > 3)
+ ast_log(LOG_DEBUG,"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_reliable(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 = NULL;
+ transmit_response_reliable(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_reliable(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_reliable(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);
+ ast_mutex_unlock(&p->lock);
+ if (p->refer->refer_call) {
+ ast_mutex_unlock(&p->refer->refer_call->lock);
+ if (p->refer->refer_call->owner) {
+ 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 (!ast_test_flag(req, SIP_PKT_IGNORE)) {
+ int newcall = (p->initreq.headers ? TRUE : FALSE);
+
+ if (sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ /* 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 (!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_reliable(p, "488 Not acceptable here", req);
+ if (!p->lastinvite)
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ return -1;
+ }
+ } else {
+ p->jointcapability = p->capability;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Hm.... No sdp for the moment\n");
+ /* Some devices signal they want to be put off hold by sending a re-invite
+ *without* an SDP, which is supposed to mean "Go back to your state"
+ and since they put os on remote hold, we go back to off hold */
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD))
+ change_hold_state(p, req, FALSE, 0);
+ }
+ if (!ast_test_flag(&p->flags[0], SIP_NO_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 && !ast_test_flag(req, SIP_PKT_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_free(p, theirtag);
+ 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);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "No compatible codecs for this SIP call.\n");
+ return -1;
+ }
+ } else { /* No SDP in invite, call control session */
+ p->jointcapability = p->capability;
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "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 */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 {
+ char *decoded_exten = ast_strdupa(p->exten);
+
+ transmit_response_reliable(p, "404 Not Found", req);
+ ast_uri_decode(decoded_exten);
+ ast_log(LOG_NOTICE, "Call from '%s' to extension"
+ " '%s' rejected because extension not found.\n",
+ S_OR(p->username, p->peername), decoded_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 (option_debug > 1 && sipdebug) {
+ if (!ast_test_flag(req, SIP_PKT_IGNORE))
+ ast_log(LOG_DEBUG, "Got a SIP re-invite for call %s\n", p->callid);
+ else
+ ast_log(LOG_DEBUG, "Got a SIP re-transmit of INVITE for call %s\n", p->callid);
+ }
+ if (!ast_test_flag(req, SIP_PKT_IGNORE))
+ reinvite = 1;
+ c = p->owner;
+ }
+
+ if (!ast_test_flag(req, SIP_PKT_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 && option_debug > 3)
+ ast_log(LOG_DEBUG, "Sending this call to the invite/replcaes handler %s\n", p->callid);
+ return handle_invite_replaces(p, req, debug, ast_test_flag(req, SIP_PKT_IGNORE), 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:
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "%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 (ast_test_flag(req, SIP_PKT_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 (ast_test_flag(req, SIP_PKT_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_mutex_unlock(&c->lock);
+ ast_mutex_unlock(&p->lock);
+ ast_hangup(c);
+ ast_mutex_lock(&p->lock);
+ 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 (ast_test_flag(req, SIP_PKT_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 */
+ ast_mutex_unlock(&p->lock);
+ c->hangupcause = AST_CAUSE_CALL_REJECTED;
+ } else {
+ ast_mutex_unlock(&p->lock);
+ ast_setstate(c, AST_STATE_DOWN);
+ c->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ }
+ p->invitestate = INV_COMPLETED;
+ ast_hangup(c);
+ ast_mutex_lock(&p->lock);
+ 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:
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "%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 (bridgepeer->tech == &sip_tech || bridgepeer->tech == &sip_tech_info) {
+ 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");
+ ast_mutex_lock(&bridgepvt->lock);
+ bridgepvt->t38.state = T38_DISABLED;
+ ast_mutex_unlock(&bridgepvt->lock);
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG,"T38 state changed to %d on channel %s\n", bridgepvt->t38.state, bridgepeer->name);
+ if (ast_test_flag(req, SIP_PKT_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 */
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ transmit_response_with_t38_sdp(p, "200 OK", req, XMIT_CRITICAL);
+ p->t38.state = T38_ENABLED;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 (ast_test_flag(req, SIP_PKT_IGNORE))
+ transmit_response(p, "488 Not acceptable here", req);
+ else
+ transmit_response_reliable(p, "488 Not acceptable here", req);
+ p->t38.state = T38_DISABLED;
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG,"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 */
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ transmit_response_with_t38_sdp(p, "200 OK", req, XMIT_CRITICAL);
+ p->t38.state = T38_ENABLED;
+ if (option_debug)
+ ast_log(LOG_DEBUG,"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 ((bridgepeer->tech == &sip_tech || bridgepeer->tech == &sip_tech_info) && !ast_check_hangup(bridgepeer)) {
+ 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 (ast_test_flag(req, SIP_PKT_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 */
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ transmit_response_with_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (ast_test_flag(req, SIP_PKT_IGNORE) ? XMIT_UNRELIABLE : XMIT_CRITICAL)));
+ }
+ }
+ 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 (ast_test_flag(req, SIP_PKT_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 */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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 */
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "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;
+ ast_mutex_unlock(&targetcall_pvt->lock);
+ 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 (option_debug > 3) {
+ if (target.chan2)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "SIP attended transfer: Error: No target channel\n");
+ else
+ ast_log(LOG_DEBUG, "SIP attended transfer: Attempting transfer in ringing state\n");
+ }
+ }
+
+ /* Transfer */
+ if (option_debug > 3 && sipdebug) {
+ if (current->chan2) /* We have two bridges */
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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 */
+ res = attempt_transfer(current, &target);
+ ast_mutex_unlock(&targetcall_pvt->lock);
+ if (res) {
+ /* Failed transfer */
+ transmit_notify_with_sipfrag(transferer, seqno, "486 Busy Here", TRUE);
+ append_history(transferer, "Xfer", "Refer failed");
+ transferer->refer->status = 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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 ignore, int seqno, int *nounlock)
+{
+ struct sip_dual current; /* Chan1: Call between asterisk and transferer */
+ /* Chan2: Call between asterisk and transferee */
+
+ int res = 0;
+
+ if (ast_test_flag(req, SIP_PKT_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 */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Call %s: Declined REFER, outside of dialog...\n", p->callid);
+ transmit_response(p, "603 Declined (No dialog)", req);
+ if (!ast_test_flag(req, SIP_PKT_IGNORE)) {
+ append_history(p, "Xfer", "Refer failed. Outside of dialog.");
+ sip_alreadygone(p);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ 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(!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 (ast_test_flag(req, SIP_PKT_DEBUG) && option_debug)
+ ast_log(LOG_DEBUG, "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 (ast_test_flag(req, SIP_PKT_DEBUG) && option_debug)
+ ast_log(LOG_DEBUG, "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 (ast_test_flag(req, SIP_PKT_DEBUG) && option_debug)
+ ast_log(LOG_DEBUG, "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 && option_debug > 2)
+ ast_log(LOG_DEBUG, "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 && option_debug > 2)
+ ast_log(LOG_DEBUG, "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 (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 && option_debug > 2)
+ ast_log(LOG_DEBUG, "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 && option_debug > 2)
+ ast_log(LOG_DEBUG,"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 && option_debug > 3)
+ ast_log(LOG_DEBUG, "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 && option_debug > 3)
+ ast_log(LOG_DEBUG, "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.");
+ if (sipdebug && option_debug > 3)
+ ast_log(LOG_DEBUG, "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) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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 (p->refer->replaces_callid && !ast_strlen_zero(p->refer->replaces_callid)) {
+ char tempheader[SIPBUFSIZE];
+ 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) {
+ /* Success - we have a new channel */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "%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 */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "%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);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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, char *funcname, char *preparse, char *buf, size_t buflen)
+{
+ struct ast_rtp_quality qos;
+ struct sip_pvt *p = chan->tech_pvt;
+ char *all = "", *parse = ast_strdupa(preparse);
+ 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 (chan->tech != &sip_tech && chan->tech != &sip_tech_info) {
+ ast_log(LOG_ERROR, "Cannot call %s on a non-SIP channel\n", funcname);
+ return 0;
+ }
+
+ if (strcasecmp(args.param, "rtpqos"))
+ return 0;
+
+ /* Default arguments of audio,all */
+ if (ast_strlen_zero(args.type))
+ args.type = "audio";
+ if (ast_strlen_zero(args.field))
+ args.field = "all";
+
+ memset(buf, 0, buflen);
+ memset(&qos, 0, sizeof(qos));
+
+ 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);
+ }
+
+ 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, "%.0lf", 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, "%.0lf", 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, "%.0lf", 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;
+ }
+ return 0;
+}
+
+/*! \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) && !ast_test_flag(req, SIP_PKT_IGNORE) && !p->owner)
+ transmit_response_reliable(p, "487 Request Terminated", &p->initreq);
+
+ __sip_pretend_ack(p);
+
+ p->invitestate = INV_TERMINATED;
+
+ copy_request(&p->initreq, req);
+ check_via(p, req);
+ sip_alreadygone(p);
+
+ /* Get RTCP quality before end of call */
+ if (!ast_test_flag(&p->flags[0], SIP_NO_HISTORY) || p->owner) {
+ char *audioqos, *videoqos;
+ if (p->rtp) {
+ audioqos = ast_rtp_get_quality(p->rtp, NULL);
+ if (!ast_test_flag(&p->flags[0], SIP_NO_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 (!ast_test_flag(&p->flags[0], SIP_NO_HISTORY))
+ append_history(p, "RTCPvideo", "Quality:%s", videoqos);
+ if (p->owner)
+ pbx_builtin_setvar_helper(p->owner, "RTPVIDEOQOS", videoqos);
+ }
+ }
+
+ stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
+
+ 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);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Received bye, issuing owner hangup\n");
+ } else {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Received bye, no owner, selfdestruct soon.\n");
+ }
+ ast_clear_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ 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 (!ast_test_flag(req, SIP_PKT_IGNORE)) {
+ if (ast_test_flag(req, SIP_PKT_DEBUG))
+ ast_verbose("Receiving message!\n");
+ receive_message(p, req);
+ } else
+ transmit_response(p, "202 Accepted", req);
+ return 1;
+}
+
+/*! \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 */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 (ast_test_flag(req, SIP_PKT_DEBUG)) {
+ if (option_debug) {
+ if (resubscribe)
+ ast_log(LOG_DEBUG, "Got a re-subscribe on existing subscription %s\n", p->callid);
+ else
+ ast_log(LOG_DEBUG, "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);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ return 0;
+ }
+
+ if (!ast_test_flag(req, SIP_PKT_IGNORE) && !resubscribe) { /* Set up dialog, new subscription */
+ const char *to = get_header(req, "To");
+ char totag[128];
+
+ /* Check to see if a tag was provided, if so this is actually a resubscription of a dialog we no longer know about */
+ if (!ast_strlen_zero(to) && gettag(req, "To", totag, sizeof(totag))) {
+ if (ast_test_flag(req, SIP_PKT_DEBUG))
+ ast_verbose("Received resubscription for a dialog we no longer know about. Telling remote side to subscribe again.\n");
+ transmit_response(p, "481 Subscription does not exist", req);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ return 0;
+ }
+
+ /* Use this as the basis */
+ if (ast_test_flag(req, SIP_PKT_DEBUG))
+ ast_verbose("Creating new subscription\n");
+
+ copy_request(&p->initreq, req);
+ check_via(p, req);
+ } else if (ast_test_flag(req, SIP_PKT_DEBUG) && ast_test_flag(req, SIP_PKT_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);
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Received SIP subscribe for unknown event package: <none>\n");
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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) {
+ if (authpeer)
+ ASTOBJ_UNREF(authpeer, sip_destroy_peer);
+ 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);
+ }
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ if (authpeer)
+ ASTOBJ_UNREF(authpeer, sip_destroy_peer);
+ return 0;
+ }
+
+ /* 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);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ if (authpeer)
+ ASTOBJ_UNREF(authpeer, sip_destroy_peer);
+ 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);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ if (authpeer)
+ ASTOBJ_UNREF(authpeer, sip_destroy_peer);
+ 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) /* No need for authpeer here */
+ ASTOBJ_UNREF(authpeer, sip_destroy_peer);
+
+ /* 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);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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);
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Received SIP mailbox subscription for unknown format: %s\n", accept);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ if (authpeer) /* No need for authpeer here */
+ ASTOBJ_UNREF(authpeer, sip_destroy_peer);
+ 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_strlen_zero(authpeer->mailbox)) {
+ transmit_response(p, "404 Not found (no mailbox)", req);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ ast_log(LOG_NOTICE, "Received SIP subscribe for peer without mailbox: %s\n", authpeer->name);
+ if (authpeer) /* No need for authpeer here */
+ ASTOBJ_UNREF(authpeer, sip_destroy_peer);
+ return 0;
+ }
+
+ p->subscribed = MWI_NOTIFICATION;
+ 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 = ASTOBJ_REF(authpeer); /* Link from pvt to peer */
+ } else { /* At this point, Asterisk does not understand the specified event */
+ transmit_response(p, "489 Bad Event", req);
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Received SIP subscribe for unknown event package: %s\n", event);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ if (authpeer) /* No need for authpeer here */
+ ASTOBJ_UNREF(authpeer, sip_destroy_peer);
+ return 0;
+ }
+
+ 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 (!ast_test_flag(req, SIP_PKT_IGNORE) && p)
+ p->lastinvite = seqno;
+ if (p && !ast_test_flag(&p->flags[0], SIP_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 || option_debug > 1) {
+ if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer)
+ ast_log(LOG_DEBUG, "Adding subscription for mailbox notification - peer %s Mailbox %s\n", p->relatedpeer->name, p->relatedpeer->mailbox);
+ else
+ ast_log(LOG_DEBUG, "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 */
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
+ if (p->expiry > 0)
+ sip_scheddestroy(p, (p->expiry + 10) * 1000); /* Set timer for destruction of call at expiration */
+
+ if (p->subscribed == MWI_NOTIFICATION) {
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ transmit_response(p, "200 OK", req);
+ if (p->relatedpeer) { /* Send first notification */
+ ASTOBJ_WRLOCK(p->relatedpeer);
+ sip_send_mwi_to_peer(p->relatedpeer);
+ 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);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ return 0;
+ }
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ 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)
+ */
+ ast_mutex_lock(&iflock);
+ for (p_old = iflist; 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;
+ ast_mutex_lock(&p_old->lock);
+ if (!strcmp(p_old->username, p->username)) {
+ if (!strcmp(p_old->exten, p->exten) &&
+ !strcmp(p_old->context, p->context)) {
+ ast_set_flag(&p_old->flags[0], SIP_NEEDDESTROY);
+ ast_mutex_unlock(&p_old->lock);
+ break;
+ }
+ }
+ ast_mutex_unlock(&p_old->lock);
+ }
+ ast_mutex_unlock(&iflock);
+ }
+ if (!p->expiry)
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ }
+ 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 */
+ if (ast_test_flag(req, SIP_PKT_DEBUG))
+ ast_verbose("Using latest REGISTER request as basis request\n");
+ copy_request(&p->initreq, req);
+ 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_request(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 ignore = FALSE;
+ 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 */
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY); /* 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 */
+ /* Response to our request -- Do some sanity checks */
+ if (!p->initreq.headers) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "That's odd... Got a response on a call we dont know about. Cseq %d Cmd %s\n", seqno, cmd);
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ return 0;
+ } else if (p->ocseq && (p->ocseq < seqno) && (seqno != p->lastnoninvite)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Ignoring out of order response %d (expecting %d)\n", seqno, p->ocseq);
+ return -1;
+ } else if (p->ocseq && (p->ocseq != seqno) && (seqno != p->lastnoninvite)) {
+ /* ignore means "don't do anything with it" but still have to
+ respond appropriately */
+ ignore = TRUE;
+ ast_set_flag(req, SIP_PKT_IGNORE);
+ ast_set_flag(req, SIP_PKT_IGNORE_RESP);
+ 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);
+ } else {
+ if (respid <= 0) {
+ ast_log(LOG_WARNING, "Invalid SIP response code: '%d'\n", respid);
+ return 0;
+ }
+ /* 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, ignore, 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 */
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "**** 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) ) {
+ if (p->pendinginvite && seqno == p->pendinginvite && (req->method == SIP_ACK || req->method == SIP_CANCEL)) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Got CANCEL or ACK on INVITE with transactions in between.\n");
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 || ast_test_flag(&p->flags[0], SIP_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 */
+ ignore = 2;
+ ast_set_flag(req, SIP_PKT_IGNORE);
+ ast_set_flag(req, SIP_PKT_IGNORE_REQ);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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 && ast_test_flag(req, SIP_PKT_WITH_TOTAG)) {
+ /* If this is a first request and it got a to-tag, it is not for us */
+ if (!ast_test_flag(req, SIP_PKT_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);
+ }
+ 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, ignore, 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 (ast_test_flag(req, SIP_PKT_DEBUG))
+ ast_verbose("Receiving INFO!\n");
+ if (!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, FLAG_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))
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ 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)
+ ast_set_flag(&p->flags[0], SIP_NEEDDESTROY);
+ break;
+ }
+ return res;
+}
+
+static void process_request_queue(struct sip_pvt *p, int *recount, int *nounlock)
+{
+ struct sip_request *req;
+
+ while ((req = AST_LIST_REMOVE_HEAD(&p->request_queue, next))) {
+ if (handle_request(p, req, &p->recv, recount, nounlock) == -1) {
+ /* Request failed */
+ if (option_debug) {
+ ast_log(LOG_DEBUG, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : "<no callid>");
+ }
+ }
+ ast_free(req);
+ }
+}
+
+static int scheduler_process_request_queue(const void *data)
+{
+ struct sip_pvt *p = (struct sip_pvt *) data;
+ int recount = 0;
+ int nounlock = 0;
+ int lockretry;
+
+ for (lockretry = 10; lockretry > 0; lockretry--) {
+ ast_mutex_lock(&p->lock);
+
+ /* lock the owner if it has one -- we may need it */
+ /* because this is deadlock-prone, we need to try and unlock if failed */
+ if (!p->owner || !ast_channel_trylock(p->owner)) {
+ break; /* locking succeeded */
+ }
+
+ if (lockretry != 1) {
+ ast_mutex_unlock(&p->lock);
+ /* Sleep for a very short amount of time */
+ usleep(1);
+ }
+ }
+
+ if (!lockretry) {
+ int retry = !AST_LIST_EMPTY(&p->request_queue);
+
+ /* we couldn't get the owner lock, which is needed to process
+ the queued requests, so return a non-zero value, which will
+ cause the scheduler to run this request again later if there
+ still requests to be processed
+ */
+ ast_mutex_unlock(&p->lock);
+ return retry;
+ };
+
+ process_request_queue(p, &recount, &nounlock);
+ p->request_queue_sched_id = -1;
+
+ if (p->owner && !nounlock) {
+ ast_channel_unlock(p->owner);
+ }
+ ast_mutex_unlock(&p->lock);
+
+ if (recount) {
+ ast_update_use_count();
+ }
+
+ return 0;
+}
+
+static int queue_request(struct sip_pvt *p, const struct sip_request *req)
+{
+ struct sip_request *newreq;
+
+ if (!(newreq = ast_calloc(1, sizeof(*newreq)))) {
+ return -1;
+ }
+
+ copy_request(newreq, req);
+ AST_LIST_INSERT_TAIL(&p->request_queue, newreq, next);
+ if (p->request_queue_sched_id == -1) {
+ p->request_queue_sched_id = ast_sched_add(sched, 10, scheduler_process_request_queue, p);
+ }
+
+ return 0;
+}
+
+/*! \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_request()
+*/
+static int sipsock_read(int *id, int fd, short events, void *ignore)
+{
+ struct sip_request req;
+ struct sockaddr_in sin = { 0, };
+ struct sip_pvt *p;
+ int res;
+ socklen_t len = sizeof(sin);
+ int nounlock = 0;
+ int recount = 0;
+ int lockretry;
+
+ memset(&req, 0, sizeof(req));
+ res = recvfrom(sipsock, 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 (option_debug && res == sizeof(req.data) - 1)
+ ast_log(LOG_DEBUG, "Received packet exceeds buffer. Data is possibly lost\n");
+
+ req.data[res] = '\0';
+ req.len = res;
+ if(sip_debug_test_addr(&sin)) /* Set the debug flag early on packet level */
+ ast_set_flag(&req, SIP_PKT_DEBUG);
+ if (pedanticsipchecking)
+ req.len = lws2sws(req.data, req.len); /* Fix multiline headers */
+ if (ast_test_flag(&req, SIP_PKT_DEBUG))
+ ast_verbose("\n<--- SIP read from %s:%d --->\n%s\n<------------->\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), req.data);
+
+ if(parse_request(&req) == -1) /* Bad packet, can't parse */
+ return 1;
+
+ req.method = find_sip_method(req.rlPart1);
+
+ if (ast_test_flag(&req, SIP_PKT_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 = 10; 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) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Invalid SIP message - rejected , no callid, len %d\n", req.len);
+ ast_mutex_unlock(&netlock);
+ return 1;
+ }
+ /* Go ahead and lock the owner if it has one -- we may need it */
+ /* because this is deadlock-prone, we need to try and unlock if failed */
+ if (!p->owner || !ast_channel_trylock(p->owner))
+ break; /* locking succeeded */
+ if (lockretry != 1) {
+ ast_mutex_unlock(&p->lock);
+ ast_mutex_unlock(&netlock);
+ /* Sleep for a very short amount of time */
+ usleep(1);
+ }
+ }
+ p->recv = sin;
+
+ if (!ast_test_flag(&p->flags[0], SIP_NO_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 (!queue_request(p, &req)) {
+ /* the request has been queued for later handling */
+ ast_mutex_unlock(&p->lock);
+ ast_mutex_unlock(&netlock);
+ return 1;
+ }
+
+ /* This is unsafe, since p->owner is not locked. */
+ if (p->owner)
+ ast_log(LOG_ERROR, "Channel lock for %s could not be obtained, and request was unable to be queued.\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.");
+ ast_mutex_unlock(&p->lock);
+ ast_mutex_unlock(&netlock);
+ return 1;
+ }
+
+ /* if there are queued requests on this sip_pvt, process them first, so that everything is
+ handled in order
+ */
+ if (!AST_LIST_EMPTY(&p->request_queue)) {
+ AST_SCHED_DEL(sched, p->request_queue_sched_id);
+ process_request_queue(p, &recount, &nounlock);
+ }
+
+ if (handle_request(p, &req, &sin, &recount, &nounlock) == -1) {
+ /* Request failed */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : "<no callid>");
+ }
+
+ if (p->owner && !nounlock)
+ ast_channel_unlock(p->owner);
+ ast_mutex_unlock(&p->lock);
+ ast_mutex_unlock(&netlock);
+ if (recount)
+ ast_update_use_count();
+
+ return 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)
+{
+ /* Called with peerl lock, but releases it */
+ struct sip_pvt *p;
+ int newmsgs, oldmsgs;
+
+ /* 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;
+
+ /* Check for messages */
+ ast_app_inboxcount(peer->mailbox, &newmsgs, &oldmsgs);
+
+ peer->lastmsgcheck = time(NULL);
+
+ /* Return now if it's the same thing we told them last time */
+ if (((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)) == peer->lastmsgssent) {
+ return 0;
+ }
+
+
+ peer->lastmsgssent = ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs));
+
+ if (peer->mwipvt) {
+ /* Base message on subscription */
+ p = 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 */
+ if (ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip))
+ p->ourip = __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 Check whether peer needs a new MWI notification check */
+static int does_peer_need_mwi(struct sip_peer *peer)
+{
+ time_t t = time(NULL);
+
+ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_SUBSCRIBEMWIONLY) &&
+ !peer->mwipvt) { /* We don't have a subscription */
+ peer->lastmsgcheck = t; /* Reset timer */
+ return FALSE;
+ }
+
+ if (!ast_strlen_zero(peer->mailbox) && (t - peer->lastmsgcheck) > global_mwitime)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+/*! \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 *sip;
+ struct sip_peer *peer = NULL;
+ time_t t;
+ int fastrestart = FALSE;
+ int lastpeernum = -1;
+ int curpeernum;
+ 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) {
+ if (option_verbose > 0)
+ ast_verbose(VERBOSE_PREFIX_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;
+ }
+ }
+restartsearch:
+ /* Check for interfaces needing to be killed */
+ ast_mutex_lock(&iflock);
+ t = time(NULL);
+ /* don't scan the interface 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 (sip = iflist; !fastrestart && sip; sip = sip->next) {
+ /*! \note If we can't get a lock on an interface, skip it and come
+ * back later. Note that there is the possibility of a deadlock with
+ * sip_hangup otherwise, because sip_hangup is called with the channel
+ * locked first, and the iface lock is attempted second.
+ */
+ if (ast_mutex_trylock(&sip->lock))
+ continue;
+
+ /* Check RTP timeouts and kill calls if we have a timeout set and do not get RTP */
+ if (sip->rtp && sip->owner &&
+ (sip->owner->_state == AST_STATE_UP) &&
+ !sip->redirip.sin_addr.s_addr &&
+ sip->t38.state != T38_ENABLED) {
+ if (sip->lastrtptx &&
+ ast_rtp_get_rtpkeepalive(sip->rtp) &&
+ (t > sip->lastrtptx + ast_rtp_get_rtpkeepalive(sip->rtp))) {
+ /* Need to send an empty RTP packet */
+ sip->lastrtptx = time(NULL);
+ ast_rtp_sendcng(sip->rtp, 0);
+ }
+ if (sip->lastrtprx &&
+ (ast_rtp_get_rtptimeout(sip->rtp) || ast_rtp_get_rtpholdtimeout(sip->rtp)) &&
+ (t > sip->lastrtprx + ast_rtp_get_rtptimeout(sip->rtp))) {
+ /* Might be a timeout now -- see if we're on hold */
+ struct sockaddr_in sin;
+ ast_rtp_get_peer(sip->rtp, &sin);
+ if (sin.sin_addr.s_addr ||
+ (ast_rtp_get_rtpholdtimeout(sip->rtp) &&
+ (t > sip->lastrtprx + ast_rtp_get_rtpholdtimeout(sip->rtp)))) {
+ /* Needs a hangup */
+ if (ast_rtp_get_rtptimeout(sip->rtp)) {
+ while (sip->owner && ast_channel_trylock(sip->owner)) {
+ DEADLOCK_AVOIDANCE(&sip->lock);
+ }
+ if (sip->owner) {
+ ast_log(LOG_NOTICE,
+ "Disconnecting call '%s' for lack of RTP activity in %ld seconds\n",
+ sip->owner->name,
+ (long) (t - sip->lastrtprx));
+ /* Issue a softhangup */
+ ast_softhangup_nolock(sip->owner, AST_SOFTHANGUP_DEV);
+ ast_channel_unlock(sip->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(sip->rtp, 0);
+ ast_rtp_set_rtpholdtimeout(sip->rtp, 0);
+ if (sip->vrtp) {
+ ast_rtp_set_rtptimeout(sip->vrtp, 0);
+ ast_rtp_set_rtpholdtimeout(sip->vrtp, 0);
+ }
+ }
+ }
+ }
+ }
+ }
+ /* If we have sessions that needs to be destroyed, do it now */
+ if (ast_test_flag(&sip->flags[0], SIP_NEEDDESTROY) && !sip->packets &&
+ !sip->owner) {
+ ast_mutex_unlock(&sip->lock);
+ __sip_destroy(sip, 1);
+ ast_mutex_unlock(&iflock);
+ usleep(1);
+ goto restartsearch;
+ }
+ ast_mutex_unlock(&sip->lock);
+ }
+ ast_mutex_unlock(&iflock);
+
+ /* XXX TODO The scheduler usage in this module does not have sufficient
+ * synchronization being done between running the scheduler and places
+ * scheduling tasks. As it is written, any scheduled item may not run
+ * any sooner than about 1 second, regardless of whether a sooner time
+ * was asked for. */
+
+ pthread_testcancel();
+ /* Wait for sched or io */
+ res = ast_sched_wait(sched);
+ if ((res < 0) || (res > 1000))
+ res = 1000;
+ /* If we might need to send more mailboxes, don't wait long at all.*/
+ if (fastrestart)
+ res = 1;
+ res = ast_io_wait(io, res);
+ if (option_debug && res > 20)
+ ast_log(LOG_DEBUG, "chan_sip: ast_io_wait ran %d all at once\n", res);
+ ast_mutex_lock(&monlock);
+ res = ast_sched_runq(sched);
+ if (option_debug && res >= 20)
+ ast_log(LOG_DEBUG, "chan_sip: ast_sched_runq ran %d all at once\n", res);
+
+ /* Send MWI notifications to peers - static and cached realtime peers */
+ t = time(NULL);
+ fastrestart = FALSE;
+ curpeernum = 0;
+ peer = NULL;
+ /* Find next peer that needs mwi */
+ ASTOBJ_CONTAINER_TRAVERSE(&peerl, !peer, do {
+ if ((curpeernum > lastpeernum) && does_peer_need_mwi(iterator)) {
+ fastrestart = TRUE;
+ lastpeernum = curpeernum;
+ peer = ASTOBJ_REF(iterator);
+ };
+ curpeernum++;
+ } while (0)
+ );
+ /* Send MWI to the peer */
+ if (peer) {
+ ASTOBJ_WRLOCK(peer);
+ sip_send_mwi_to_peer(peer);
+ ASTOBJ_UNLOCK(peer);
+ ASTOBJ_UNREF(peer,sip_destroy_peer);
+ } else {
+ /* Reset where we come from */
+ lastpeernum = -1;
+ }
+ 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 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", "Peer: SIP/%s\r\nPeerStatus: Unreachable\r\nTime: %d\r\n", peer->name, -1);
+ }
+ if (peer->call)
+ sip_destroy(peer->call);
+ peer->call = NULL;
+ peer->lastms = -1;
+ ast_device_state_changed("SIP/%s", peer->name);
+
+ /* This function gets called one place outside of the scheduler ... */
+ if (!AST_SCHED_DEL(sched, peer->pokeexpire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+
+ /* There is no need to ASTOBJ_REF() here. Just let the scheduled callback
+ * inherit the reference that the current callback already has. */
+ peer->pokeexpire = ast_sched_add(sched, DEFAULT_FREQ_NOTOK, sip_poke_peer_s, peer);
+ if (peer->pokeexpire == -1) {
+ ASTOBJ_UNREF(peer, sip_destroy_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
+ imeediately after clearing things out */
+ if (!AST_SCHED_DEL(sched, peer->pokeexpire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ peer->lastms = 0;
+ peer->call = NULL;
+ return 0;
+ }
+ if (peer->call) {
+ if (sipdebug)
+ ast_log(LOG_NOTICE, "Still have a QUALIFY dialog active, deleting\n");
+ 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;
+ 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 */
+ if (ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip))
+ p->ourip = __ourip;
+ build_via(p);
+ build_callid_pvt(p);
+
+ if (!AST_SCHED_DEL(sched, peer->pokeexpire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+
+ p->relatedpeer = ASTOBJ_REF(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
+ gettimeofday(&peer->ps, NULL);
+ if (xmitres == XMIT_ERROR) {
+ sip_poke_noanswer(ASTOBJ_REF(peer)); /* Immediately unreachable, network problems */
+ } else {
+ if (!AST_SCHED_DEL(sched, peer->pokeexpire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ peer->pokeexpire = ast_sched_add(sched, peer->maxms * 2, sip_poke_noanswer, ASTOBJ_REF(peer));
+ if (peer->pokeexpire == -1) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_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 hostent *hp;
+ struct ast_hostent ahp;
+ 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;
+
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Checking device state for peer %s\n", host);
+
+ /* If find_peer asks for a realtime peer, then this breaks rtautoclear. This
+ * is because when a peer tries to autoexpire, the last thing it does is to
+ * queue up an event telling the system that the devicestate has changed
+ * (presumably to unavailable). If we ask for a realtime peer here, this would
+ * load it BACK into memory, thus defeating the point of trying to trying to
+ * clear dead hosts out of memory.
+ */
+ if ((p = find_peer(host, NULL, 0, 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->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;
+ }
+ ASTOBJ_UNREF(p,sip_destroy_peer);
+ } else {
+ char *port = strchr(host, ':');
+ if (port)
+ *port = '\0';
+ hp = ast_gethostbyname(host, &ahp);
+ if (hp)
+ res = AST_DEVICE_UNKNOWN;
+ }
+
+ return res;
+}
+
+/*! \brief PBX interface function -build SIP pvt structure
+ SIP calls initiated by the PBX arrive here */
+static struct ast_channel *sip_request_call(const char *type, int format, void *data, int *cause)
+{
+ int oldformat;
+ struct sip_pvt *p;
+ struct ast_channel *tmpc = NULL;
+ char *ext, *host;
+ char tmp[256];
+ char *dest = data;
+
+ oldformat = format;
+ if (!(format &= ((AST_FORMAT_MAX_AUDIO << 1) - 1))) {
+ 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;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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", (char *)data);
+ *cause = AST_CAUSE_SWITCH_CONGESTION;
+ return NULL;
+ }
+
+ ast_set_flag(&p->flags[1], SIP_PAGE2_OUTGOING_CALL);
+
+ 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;
+ }
+
+ ast_copy_string(tmp, dest, sizeof(tmp));
+ host = strchr(tmp, '@');
+ if (host) {
+ *host++ = '\0';
+ ext = tmp;
+ } else {
+ ext = strchr(tmp, '/');
+ if (ext)
+ *ext++ = '\0';
+ host = tmp;
+ }
+
+ if (create_addr(p, host)) {
+ *cause = AST_CAUSE_UNREGISTERED;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "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 */
+ if (ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip))
+ p->ourip = __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_free(p, fullcontact);
+ }
+#if 0
+ printf("Setting up to call extension '%s' at '%s'\n", ext ? ext : "<none>", host);
+#endif
+ p->prefcodec = oldformat; /* Format for this call */
+ ast_mutex_lock(&p->lock);
+ tmpc = sip_new(p, AST_STATE_DOWN, host); /* Place the call */
+ ast_mutex_unlock(&p->lock);
+ if (!tmpc)
+ sip_destroy(p);
+ ast_update_use_count();
+ restart_monitor();
+ return tmpc;
+}
+
+/*!
+ * \brief Parse the "insecure" setting from sip.conf or from realtime.
+ * \param flags a pointer to an ast_flags structure
+ * \param value the value of the SIP insecure setting
+ * \param lineno linenumber in sip.conf or -1 for realtime
+ */
+static void set_insecure_flags(struct ast_flags *flags, const char *value, int lineno)
+{
+ static int dep_insecure_very = 0;
+ static int dep_insecure_yes = 0;
+
+ if (ast_strlen_zero(value))
+ return;
+
+ if (!strcasecmp(value, "very")) {
+ ast_set_flag(flags, SIP_INSECURE_PORT | SIP_INSECURE_INVITE);
+ if(!dep_insecure_very) {
+ if(lineno != -1)
+ ast_log(LOG_WARNING, "insecure=very at line %d is deprecated; use insecure=port,invite instead\n", lineno);
+ else
+ ast_log(LOG_WARNING, "insecure=very is deprecated; use insecure=port,invite instead\n");
+ dep_insecure_very = 1;
+ }
+ }
+ else if (ast_true(value)) {
+ ast_set_flag(flags, SIP_INSECURE_PORT);
+ if(!dep_insecure_yes) {
+ if(lineno != -1)
+ ast_log(LOG_WARNING, "insecure=%s at line %d is deprecated; use insecure=port instead\n", value, lineno);
+ else
+ ast_log(LOG_WARNING, "insecure=%s is deprecated; use insecure=port instead\n", value);
+ dep_insecure_yes = 1;
+ }
+ }
+ else 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, SIP_INSECURE_PORT);
+ else if (!strcasecmp(word, "invite"))
+ ast_set_flag(flags, 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, "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_PORT | SIP_INSECURE_INVITE);
+ ast_clear_flag(&flags[0], SIP_INSECURE_PORT | SIP_INSECURE_INVITE);
+ set_insecure_flags(flags, 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, "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 if (!strcasecmp(v->name, "t38pt_usertpsource")) {
+ ast_set_flag(&mask[1], SIP_PAGE2_UDPTL_DESTINATION);
+ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_UDPTL_DESTINATION);
+ } 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_log(LOG_DEBUG, "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)))
+ free(d);
+ AST_LIST_UNLOCK(&domain_list);
+}
+
+
+/*! \brief Add realm authentication in list */
+static struct sip_auth *add_realm_authentication(struct sip_auth *authlist, 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;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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;
+
+ if (option_verbose > 2)
+ ast_verbose("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;
+ 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;
+}
+
+/*! \brief Initiate a SIP user structure from configuration (configuration or realtime) */
+static struct sip_user *build_user(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime)
+{
+ struct sip_user *user;
+ int format;
+ struct ast_ha *oldha = NULL;
+ char *varname = NULL, *varval = NULL;
+ struct ast_variable *tmpvar = 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;
+ user->prefs = default_prefs;
+ /* set default context */
+ strcpy(user->context, default_context);
+ strcpy(user->language, default_language);
+ strcpy(user->mohinterpret, default_mohinterpret);
+ strcpy(user->mohsuggest, default_mohsuggest);
+ /* First we walk through the v parameters list and then the alt parameters list */
+ for (; v || ((v = alt) && !(alt=NULL)); 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")) {
+ varname = ast_strdupa(v->value);
+ if ((varval = strchr(varname,'='))) {
+ *varval++ = '\0';
+ if ((tmpvar = ast_variable_new(varname, varval))) {
+ tmpvar->next = user->chanvars;
+ user->chanvars = tmpvar;
+ }
+ }
+ } else if (!strcasecmp(v->name, "permit") ||
+ !strcasecmp(v->name, "deny")) {
+ user->ha = ast_append_ha(v->name, v->value, user->ha);
+ } 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")
+ || !strcasecmp(v->name, "musicclass") || !strcasecmp(v->name, "musiconhold")) {
+ 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, "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")) {
+ 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, "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;
+ }
+ /* 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;
+ 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->mailbox[0] = '\0';
+ peer->callgroup = 0;
+ peer->pickupgroup = 0;
+ peer->maxms = default_qualify;
+ peer->prefs = default_prefs;
+}
+
+/*! \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));
+
+ ast_set_flag(&peer->flags[1], SIP_PAGE2_SELFDESTRUCT);
+ ast_set_flag(&peer->flags[1], SIP_PAGE2_DYNAMIC);
+ peer->prefs = default_prefs;
+ reg_source_db(peer);
+
+ return peer;
+}
+
+/*! \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 obproxyfound=0;
+ int found=0;
+ int firstpass=1;
+ int format=0; /* Ama flags */
+ time_t regseconds = 0;
+ char *varname = NULL, *varval = NULL;
+ struct ast_variable *tmpvar = NULL;
+ struct ast_flags peerflags[2] = {{(0)}};
+ struct ast_flags mask[2] = {{(0)}};
+ char fullcontact[sizeof(peer->fullcontact)] = "";
+
+ if (!realtime || ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS))
+ /* 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 = 1;
+ if (!(peer->objflags & ASTOBJ_FLAG_MARKED))
+ firstpass = 0;
+ } else {
+ if (!(peer = ast_calloc(1, sizeof(*peer))))
+ return NULL;
+
+ if (realtime && !ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS))
+ 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 (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")) {
+ /* Reconstruct field, because realtime separates our value at the ';' */
+ if (!ast_strlen_zero(fullcontact)) {
+ strncat(fullcontact, ";", sizeof(fullcontact) - strlen(fullcontact) - 1);
+ strncat(fullcontact, v->value, sizeof(fullcontact) - strlen(fullcontact) - 1);
+ } else {
+ ast_copy_string(fullcontact, v->value, sizeof(fullcontact));
+ ast_set_flag(&peer->flags[1], SIP_PAGE2_RT_FROMCONTACT);
+ }
+ } 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, "host") || !strcasecmp(v->name, "outboundproxy")) {
+ if (!strcasecmp(v->value, "dynamic")) {
+ if (!strcasecmp(v->name, "outboundproxy") || obproxyfound) {
+ ast_log(LOG_WARNING, "You can't have a dynamic outbound proxy, you big silly head at line %d.\n", v->lineno);
+ } else {
+ /* They'll register with us */
+ if (!found || !ast_test_flag(&peer->flags[1], SIP_PAGE2_DYNAMIC)) {
+ /* Initialize stuff if this is a new peer, or if it used to be
+ * non-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;
+ }
+ }
+ ast_set_flag(&peer->flags[1], SIP_PAGE2_DYNAMIC);
+ }
+ } else {
+ /* Non-dynamic. Make sure we become that way if we're not */
+ if (!AST_SCHED_DEL(sched, peer->expire)) {
+ struct sip_peer *peer_ptr = peer;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ ast_clear_flag(&peer->flags[1], SIP_PAGE2_DYNAMIC);
+ if (!obproxyfound || !strcasecmp(v->name, "outboundproxy")) {
+ if (ast_get_ip_or_srv(&peer->addr, v->value, srvlookup ? "_sip._udp" : NULL)) {
+ ASTOBJ_UNREF(peer, sip_destroy_peer);
+ return NULL;
+ }
+ }
+ if (!strcasecmp(v->name, "outboundproxy"))
+ obproxyfound=1;
+ else {
+ ast_copy_string(peer->tohost, v->value, sizeof(peer->tohost));
+ if (!peer->addr.sin_port)
+ peer->addr.sin_port = htons(STANDARD_SIP_PORT);
+ }
+ if (global_dynamic_exclude_static) {
+ global_contact_ha = ast_append_ha("deny", (char *)ast_inet_ntoa(peer->addr.sin_addr), global_contact_ha);
+ }
+ }
+ } else if (!strcasecmp(v->name, "defaultip")) {
+ if (ast_get_ip(&peer->defaddr, v->value)) {
+ ASTOBJ_UNREF(peer, sip_destroy_peer);
+ return NULL;
+ }
+ } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) {
+ peer->ha = ast_append_ha(v->name, v->value, peer->ha);
+ } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny")) {
+ peer->contactha = ast_append_ha(v->name + 7, v->value, peer->contactha);
+ } else if (!strcasecmp(v->name, "port")) {
+ if (!realtime && ast_test_flag(&peer->flags[1], SIP_PAGE2_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")) {
+ 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, "call-limit") || !strcasecmp(v->name, "incominglimit")) {
+ peer->call_limit = atoi(v->value);
+ if (peer->call_limit < 0)
+ peer->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 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")
+ || !strcasecmp(v->name, "musicclass") || !strcasecmp(v->name, "musiconhold")) {
+ 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")) {
+ ast_copy_string(peer->mailbox, v->value, sizeof(peer->mailbox));
+ } else if (!strcasecmp(v->name, "hasvoicemail")) {
+ /* People expect that if 'hasvoicemail' is set, that the mailbox will
+ * be also set, even if not explicitly specified. */
+ if (ast_true(v->value) && ast_strlen_zero(peer->mailbox)) {
+ ast_copy_string(peer->mailbox, name, sizeof(peer->mailbox));
+ }
+ } 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")) {
+ 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, "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, "setvar")) {
+ /* Set peer channel variable */
+ varname = ast_strdupa(v->value);
+ if ((varval = strchr(varname, '='))) {
+ *varval++ = '\0';
+ if ((tmpvar = ast_variable_new(varname, varval))) {
+ tmpvar->next = peer->chanvars;
+ peer->chanvars = tmpvar;
+ }
+ }
+ } 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;
+ }
+ if (realtime && !ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && peer->maxms > 0) {
+ /* This would otherwise cause a network storm, where the
+ * qualify response refreshes the peer from the database,
+ * which in turn causes another qualify to be sent, ad
+ * infinitum. */
+ ast_log(LOG_WARNING, "Qualify is incompatible with dynamic uncached realtime. Please either turn rtcachefriends on or turn qualify off on peer '%s'\n", peer->name);
+ peer->maxms = 0;
+ }
+ } else if (!strcasecmp(v->name, "maxcallbitrate")) {
+ peer->maxcallbitrate = atoi(v->value);
+ if (peer->maxcallbitrate < 0)
+ peer->maxcallbitrate = default_maxcallbitrate;
+ }
+ }
+ if (!ast_strlen_zero(fullcontact)) {
+ ast_copy_string(peer->fullcontact, fullcontact, sizeof(peer->fullcontact));
+ /* We have a hostname in the fullcontact, but if we don't have an
+ * address listed on the entry (or if it's 'dynamic'), then we need to
+ * parse the entry to obtain the IP address, so a dynamic host can be
+ * contacted immediately after reload (as opposed to waiting for it to
+ * register once again). */
+ __set_address_from_contact(fullcontact, &peer->addr);
+ }
+
+ if (!ast_test_flag(&global_flags[1], SIP_PAGE2_IGNOREREGEXPIRE) && ast_test_flag(&peer->flags[1], SIP_PAGE2_DYNAMIC) && realtime) {
+ time_t nowtime = time(NULL);
+
+ if ((nowtime - regseconds) > 0) {
+ destroy_association(peer);
+ memset(&peer->addr, 0, sizeof(peer->addr));
+ if (option_debug)
+ ast_log(LOG_DEBUG, "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 && ast_test_flag(&peer->flags[1], SIP_PAGE2_DYNAMIC) && !ast_test_flag(&peer->flags[0], SIP_REALTIME))
+ reg_source_db(peer);
+ ASTOBJ_UNMARK(peer);
+ ast_free_ha(oldha);
+ 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;
+ struct ast_hostent ahp;
+ char *cat, *stringp, *context, *oldregcontext;
+ char newcontexts[AST_MAX_CONTEXT], oldcontexts[AST_MAX_CONTEXT];
+ struct hostent *hp;
+ int format;
+ struct ast_flags dummy[2];
+ int auto_sip_domains = FALSE;
+ struct sockaddr_in old_bindaddr = bindaddr;
+ int registry_count = 0, peer_count = 0, user_count = 0;
+ unsigned int temp_tos = 0;
+ struct ast_flags debugflag = {0};
+
+ cfg = ast_config_load(config);
+
+ /* We *must* have a config file otherwise stop immediately */
+ if (!cfg) {
+ ast_log(LOG_NOTICE, "Unable to load config %s\n", config);
+ return -1;
+ }
+
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "--------------- SIP reload started\n");
+
+ clear_realm_authentication(authl);
+ clear_sip_domains();
+ authl = NULL;
+
+ ast_free_ha(global_contact_ha);
+ global_contact_ha = 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) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Destroying active SIP dialog for registry %s@%s\n", iterator->username, iterator->hostname);
+ /* This will also remove references to the registry */
+ sip_destroy(iterator->call);
+ }
+ ASTOBJ_UNLOCK(iterator);
+
+ } while(0));
+
+ /* Then, actually destroy users and registry */
+ ASTOBJ_CONTAINER_DESTROYALL(&userl, sip_destroy_user);
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "--------------- Done destroying user list\n");
+ ASTOBJ_CONTAINER_DESTROYALL(&regl, sip_registry_destroy);
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "--------------- Done destroying registry list\n");
+ ASTOBJ_CONTAINER_MARKALL(&peerl);
+
+ /* 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 */
+ ast_copy_flags(&debugflag, &global_flags[1], SIP_PAGE2_DEBUG_CONSOLE);
+ ast_clear_flag(&global_flags[0], AST_FLAGS_ALL);
+ ast_clear_flag(&global_flags[1], AST_FLAGS_ALL);
+ ast_copy_flags(&global_flags[1], &debugflag, SIP_PAGE2_DEBUG_CONSOLE);
+
+ /* Reset IP addresses */
+ memset(&bindaddr, 0, sizeof(bindaddr));
+ ast_free_ha(localaddr);
+ memset(&localaddr, 0, sizeof(localaddr));
+ memset(&externip, 0, sizeof(externip));
+ memset(&default_prefs, 0 , sizeof(default_prefs));
+ outboundproxyip.sin_port = htons(STANDARD_SIP_PORT);
+ outboundproxyip.sin_family = AF_INET; /* Type of address: IPv4 */
+ ourport = STANDARD_SIP_PORT;
+ srvlookup = DEFAULT_SRVLOOKUP;
+ global_tos_sip = DEFAULT_TOS_SIP;
+ global_tos_audio = DEFAULT_TOS_AUDIO;
+ global_tos_video = DEFAULT_TOS_VIDEO;
+ externhost[0] = '\0'; /* External host name (for behind NAT DynDNS support) */
+ externexpire = 0; /* Expiration for DNS re-issuing */
+ externrefresh = 10;
+ memset(&outboundproxyip, 0, sizeof(outboundproxyip));
+
+ /* Reset channel settings to default before re-configuring */
+ allow_external_domains = DEFAULT_ALLOW_EXT_DOM; /* Allow external invites */
+ global_regcontext[0] = '\0';
+ expiry = DEFAULT_EXPIRY;
+ global_notifyringing = DEFAULT_NOTIFYRINGING;
+ global_limitonpeers = FALSE;
+ global_directrtpsetup = FALSE; /* Experimental feature, disabled by default */
+ global_notifyhold = FALSE;
+ global_alwaysauthreject = 0;
+ global_allowsubscribe = FALSE;
+ ast_copy_string(global_useragent, DEFAULT_USERAGENT, sizeof(global_useragent));
+ ast_copy_string(default_notifymime, DEFAULT_NOTIFYMIME, sizeof(default_notifymime));
+ if (ast_strlen_zero(ast_config_AST_SYSTEM_NAME))
+ ast_copy_string(global_realm, DEFAULT_REALM, sizeof(global_realm));
+ else
+ ast_copy_string(global_realm, ast_config_AST_SYSTEM_NAME, 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;
+ global_mwitime = DEFAULT_MWITIME;
+ autocreatepeer = DEFAULT_AUTOCREATEPEER;
+ global_autoframing = 0;
+ global_allowguest = DEFAULT_ALLOWGUEST;
+ 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 */
+ ast_set_flag(&global_flags[1], SIP_PAGE2_RTUPDATE);
+
+ /* 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;
+ ast_clear_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONFIG);
+
+ /* Misc settings for the channel */
+ global_relaxdtmf = FALSE;
+ global_callevents = FALSE;
+ global_t1min = DEFAULT_T1MIN;
+
+ 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);
+
+ /* 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 interface 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, "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));
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Setting SIP channel User-Agent Name to %s\n", global_useragent);
+ } 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")) {
+ ast_set2_flag(&global_flags[1], ast_true(v->value), SIP_PAGE2_RTSAVE_SYSNAME);
+ } else if (!strcasecmp(v->name, "rtupdate")) {
+ ast_set2_flag(&global_flags[1], ast_true(v->value), SIP_PAGE2_RTUPDATE);
+ } else if (!strcasecmp(v->name, "ignoreregexpire")) {
+ ast_set2_flag(&global_flags[1], ast_true(v->value), SIP_PAGE2_IGNOREREGEXPIRE);
+ } else if (!strcasecmp(v->name, "t1min")) {
+ global_t1min = atoi(v->value);
+ } else if (!strcasecmp(v->name, "dynamic_exclude_static") || !strcasecmp(v->name, "dynamic_excludes_static")) {
+ global_dynamic_exclude_static = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny")) {
+ global_contact_ha = ast_append_ha(v->name + 7, v->value, global_contact_ha);
+ } 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, "checkmwi")) {
+ if ((sscanf(v->value, "%d", &global_mwitime) != 1) || (global_mwitime < 0)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid MWI time setting at line %d. Using default (10).\n", v->value, v->lineno);
+ global_mwitime = DEFAULT_MWITIME;
+ }
+ } 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)) {
+ 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")
+ || !strcasecmp(v->name, "musicclass") || !strcasecmp(v->name, "musiconhold")) {
+ 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, "&"))) {
+ 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, "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")) {
+ if (ast_get_ip_or_srv(&outboundproxyip, v->value, srvlookup ? "_sip._udp" : NULL) < 0)
+ ast_log(LOG_WARNING, "Unable to locate host '%s'\n", v->value);
+ } else if (!strcasecmp(v->name, "outboundproxyport")) {
+ /* Port needs to be after IP */
+ sscanf(v->value, "%d", &format);
+ outboundproxyip.sin_port = htons(format);
+ } else if (!strcasecmp(v->name, "autocreatepeer")) {
+ autocreatepeer = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "srvlookup")) {
+ 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")) { /* XXX maybe ast_set2_flags ? */
+ if (ast_true(v->value))
+ ast_set_flag(&global_flags[1], SIP_PAGE2_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, "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, "localnet")) {
+ struct ast_ha *na;
+ if (!(na = ast_append_ha("d", v->value, localaddr)))
+ ast_log(LOG_WARNING, "Invalid localnet value: %s\n", v->value);
+ else
+ localaddr = na;
+ } else if (!strcasecmp(v->name, "localmask")) {
+ ast_log(LOG_WARNING, "Use of localmask is no long supported -- use localnet with mask syntax\n");
+ } else if (!strcasecmp(v->name, "externip")) {
+ if (!(hp = ast_gethostbyname(v->value, &ahp)))
+ ast_log(LOG_WARNING, "Invalid address for externip keyword: %s\n", v->value);
+ else
+ memcpy(&externip.sin_addr, hp->h_addr, sizeof(externip.sin_addr));
+ externexpire = 0;
+ } else if (!strcasecmp(v->name, "externhost")) {
+ ast_copy_string(externhost, v->value, sizeof(externhost));
+ if (!(hp = ast_gethostbyname(externhost, &ahp)))
+ ast_log(LOG_WARNING, "Invalid address for externhost keyword: %s\n", externhost);
+ else
+ memcpy(&externip.sin_addr, hp->h_addr, sizeof(externip.sin_addr));
+ 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")) {
+ ast_parse_allow_disallow(&default_prefs, &global_capability, v->value, 1);
+ } else if (!strcasecmp(v->name, "disallow")) {
+ ast_parse_allow_disallow(&default_prefs, &global_capability, v->value, 0);
+ } 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 (option_debug && ast_strlen_zero(context))
+ ast_log(LOG_DEBUG, "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")) {
+ if (!ast_str2tos(v->value, &temp_tos)) {
+ global_tos_sip = temp_tos;
+ global_tos_audio = temp_tos;
+ global_tos_video = temp_tos;
+ ast_log(LOG_WARNING, "tos value at line %d is deprecated. See doc/ip-tos.txt for more information.\n", v->lineno);
+ } else
+ ast_log(LOG_WARNING, "Invalid tos value at line %d, See doc/ip-tos.txt for more information.\n", v->lineno);
+ } 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, recommended value is 'cs3'. See doc/ip-tos.txt.\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, recommended value is 'ef'. See doc/ip-tos.txt.\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, recommended value is 'af41'. See doc/ip-tos.txt.\n", v->lineno);
+ } else if (!strcasecmp(v->name, "bindport")) {
+ 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);
+ }
+ } 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, "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);
+ }
+ }
+
+ 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);
+ }
+
+ ucfg = ast_config_load("users.conf");
+ 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)) {
+ user = build_user(cat, gen, ast_variable_browse(ucfg, cat), 0);
+ if (user) {
+ ASTOBJ_CONTAINER_LINK(&userl,user);
+ ASTOBJ_UNREF(user, sip_destroy_user);
+ user_count++;
+ }
+ 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);
+ ASTOBJ_UNREF(peer, sip_destroy_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), NULL, 0);
+ if (user) {
+ ASTOBJ_CONTAINER_LINK(&userl,user);
+ ASTOBJ_UNREF(user, sip_destroy_user);
+ user_count++;
+ }
+ }
+ if (is_peer) {
+ peer = build_peer(cat, ast_variable_browse(cfg, cat), NULL, 0);
+ if (peer) {
+ ASTOBJ_CONTAINER_LINK(&peerl,peer);
+ ASTOBJ_UNREF(peer, sip_destroy_peer);
+ peer_count++;
+ }
+ }
+ }
+ }
+ if (ast_find_ourip(&__ourip, bindaddr)) {
+ ast_log(LOG_WARNING, "Unable to get own IP address, SIP disabled\n");
+ ast_config_destroy(cfg);
+ return 0;
+ }
+ if (!ntohs(bindaddr.sin_port))
+ bindaddr.sin_port = ntohs(STANDARD_SIP_PORT);
+ bindaddr.sin_family = AF_INET;
+ 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 {
+ if (option_verbose > 1) {
+ ast_verbose(VERBOSE_PREFIX_2 "SIP Listening on %s:%d\n",
+ ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port));
+ ast_verbose(VERBOSE_PREFIX_2 "Using SIP TOS: %s\n", ast_tos2str(global_tos_sip));
+ }
+ if (setsockopt(sipsock, IPPROTO_IP, IP_TOS, &global_tos_sip, sizeof(global_tos_sip)))
+ ast_log(LOG_WARNING, "Unable to set SIP TOS to %s\n", ast_tos2str(global_tos_sip));
+ }
+ }
+ }
+ 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);
+
+ /* Done, tell the manager */
+ manager_event(EVENT_FLAG_SYSTEM, "ChannelReload", "Channel: 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;
+
+ ast_mutex_lock(&p->lock);
+ if (p->udptl && ast_test_flag(&p->flags[0], SIP_CAN_REINVITE))
+ udptl = p->udptl;
+ ast_mutex_unlock(&p->lock);
+ 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;
+ ast_mutex_lock(&p->lock);
+ 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) {
+ if (option_debug > 2) {
+ ast_log(LOG_DEBUG, "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), udptl ? ntohs(p->udptlredirip.sin_port) : 0);
+ }
+ transmit_reinvite_with_t38_sdp(p);
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ if (option_debug > 2) {
+ ast_log(LOG_DEBUG, "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), udptl ? ntohs(p->udptlredirip.sin_port) : 0);
+ }
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ }
+ /* Reset lastrtprx timer */
+ p->lastrtprx = p->lastrtptx = time(NULL);
+ ast_mutex_unlock(&p->lock);
+ 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 */
+ ast_mutex_lock(&p->lock);
+
+ /*! \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 (option_debug > 2) {
+ if (flag)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Sending reinvite on SIP '%s' - It's UDPTL soon redirected to us (IP %s)\n", p->callid, ast_inet_ntoa(p->ourip));
+ }
+ transmit_reinvite_with_t38_sdp(p);
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ if (option_debug > 2) {
+ if (flag)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Deferring reinvite on SIP '%s' - It's UDPTL will be redirected to us (IP %s)\n", p->callid, ast_inet_ntoa(p->ourip));
+ }
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ }
+ /* Reset lastrtprx timer */
+ p->lastrtprx = p->lastrtptx = time(NULL);
+ ast_mutex_unlock(&p->lock);
+ 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 (option_debug > 2) {
+ if (flag)
+ ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Responding 200 OK on SIP '%s' - It's UDPTL soon redirected to us (IP %s)\n", p->callid, ast_inet_ntoa(p->ourip));
+ }
+ pvt->t38.state = T38_ENABLED;
+ p->t38.state = T38_ENABLED;
+ if (option_debug > 1) {
+ ast_log(LOG_DEBUG, "T38 changed state to %d on channel %s\n", pvt->t38.state, pvt->owner ? pvt->owner->name : "<none>");
+ ast_log(LOG_DEBUG, "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);
+ ast_mutex_unlock(&p->lock);
+ 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;
+
+ ast_mutex_lock(&p->lock);
+ if (!(p->rtp)) {
+ ast_mutex_unlock(&p->lock);
+ 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;
+
+ ast_mutex_unlock(&p->lock);
+
+ 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;
+
+ ast_mutex_lock(&p->lock);
+ if (!(p->vrtp)) {
+ ast_mutex_unlock(&p->lock);
+ return AST_RTP_GET_FAILED;
+ }
+
+ *rtp = p->vrtp;
+
+ if (ast_test_flag(&p->flags[0], SIP_CAN_REINVITE))
+ res = AST_RTP_TRY_NATIVE;
+
+ ast_mutex_unlock(&p->lock);
+
+ 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, 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;
+
+ ast_mutex_lock(&p->lock);
+ if (ast_test_flag(&p->flags[0], SIP_ALREADYGONE)) {
+ /* If we're destroyed, don't bother */
+ ast_mutex_unlock(&p->lock);
+ 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)) {
+ ast_mutex_unlock(&p->lock);
+ 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 (codecs) {
+ if ((p->redircodecs != codecs)) {
+ p->redircodecs = codecs;
+ changed = 1;
+ }
+ if ((p->capability & codecs) != p->capability) {
+ p->jointcapability &= codecs;
+ p->capability &= 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 (!ast_test_flag(&p->flags[0], SIP_NO_HISTORY))
+ append_history(p, "ExtInv", "Initial invite sent with remote bridge proposal.");
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Early remote bridge setting SIP '%s' - Sending media to %s\n", p->callid, ast_inet_ntoa(rtp ? p->redirip.sin_addr : p->ourip));
+ } else if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
+ if (option_debug > 2) {
+ ast_log(LOG_DEBUG, "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));
+ }
+ transmit_reinvite_with_sdp(p);
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ if (option_debug > 2) {
+ ast_log(LOG_DEBUG, "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));
+ }
+ /* 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);
+ ast_mutex_unlock(&p->lock);
+ 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;
+ if (data)
+ mode = (char *)data;
+ else {
+ ast_log(LOG_WARNING, "This application requires the argument: info, inband, rfc2833\n");
+ return 0;
+ }
+ ast_channel_lock(chan);
+ if (chan->tech != &sip_tech && chan->tech != &sip_tech_info) {
+ 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;
+ }
+ ast_mutex_lock(&p->lock);
+ 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,"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;
+ }
+ }
+ ast_mutex_unlock(&p->lock);
+ 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 = (char *) 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 underscores */
+ if( (pbx_builtin_getvar_helper(chan, (const char *) varbuf + 2) == (const char *) NULL) )
+ ok = TRUE;
+ }
+ if (ok) {
+ pbx_builtin_setvar_helper (chan, varbuf, inbuf);
+ if (sipdebug)
+ ast_log(LOG_DEBUG,"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 = 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);
+ 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->jointcapability ? p->jointcapability : 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);
+ if (!AST_SCHED_DEL(sched, iterator->pokeexpire)) {
+ struct sip_peer *peer_ptr = iterator;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ ms += 100;
+ iterator->pokeexpire = ast_sched_add(sched, ms, sip_poke_peer_s, ASTOBJ_REF(iterator));
+ if (iterator->pokeexpire == -1) {
+ struct sip_peer *peer_ptr = iterator;
+ ASTOBJ_UNREF(peer_ptr, sip_destroy_peer);
+ }
+ 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);
+ AST_SCHED_DEL(sched, iterator->expire);
+ ms += regspacing;
+ iterator->expire = ast_sched_add(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);
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "--------------- Done destroying pruned peers\n");
+
+ /* Send qualify (OPTIONS) to all peers */
+ sip_poke_all_peers();
+
+ /* Register with all services */
+ sip_send_all_registers();
+
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "--------------- SIP reload done\n");
+
+ return 0;
+}
+
+/*! \brief Force reload of module from cli */
+static int sip_reload(int fd, int argc, char *argv[])
+{
+ ast_mutex_lock(&sip_reload_lock);
+ if (sip_reloading)
+ ast_verbose("Previous SIP reload not yet done\n");
+ else {
+ sip_reloading = TRUE;
+ if (fd)
+ sip_reloadreason = CHANNEL_CLI_RELOAD;
+ else
+ sip_reloadreason = CHANNEL_MODULE_RELOAD;
+ }
+ ast_mutex_unlock(&sip_reload_lock);
+ restart_monitor();
+
+ return 0;
+}
+
+/*! \brief Part of Asterisk module interface */
+static int reload(void)
+{
+ return sip_reload(0, 0, NULL);
+}
+
+static struct ast_cli_entry cli_sip_debug_deprecated =
+ { { "sip", "debug", NULL },
+ sip_do_debug_deprecated, "Enable SIP debugging",
+ debug_usage };
+
+static struct ast_cli_entry cli_sip_no_debug_deprecated =
+ { { "sip", "no", "debug", NULL },
+ sip_no_debug_deprecated, "Disable SIP debugging",
+ debug_usage };
+
+static struct ast_cli_entry cli_sip[] = {
+ { { "sip", "show", "channels", NULL },
+ sip_show_channels, "List active SIP channels",
+ show_channels_usage },
+
+ { { "sip", "show", "domains", NULL },
+ sip_show_domains, "List our local SIP domains.",
+ show_domains_usage },
+
+ { { "sip", "show", "inuse", NULL },
+ sip_show_inuse, "List all inuse/limits",
+ show_inuse_usage },
+
+ { { "sip", "show", "objects", NULL },
+ sip_show_objects, "List all SIP object allocations",
+ show_objects_usage },
+
+ { { "sip", "show", "peers", NULL },
+ sip_show_peers, "List defined SIP peers",
+ show_peers_usage },
+
+ { { "sip", "show", "registry", NULL },
+ sip_show_registry, "List SIP registration status",
+ show_reg_usage },
+
+ { { "sip", "show", "settings", NULL },
+ sip_show_settings, "Show SIP global settings",
+ show_settings_usage },
+
+ { { "sip", "show", "subscriptions", NULL },
+ sip_show_subscriptions, "List active SIP subscriptions",
+ show_subscriptions_usage },
+
+ { { "sip", "show", "users", NULL },
+ sip_show_users, "List defined SIP users",
+ show_users_usage },
+
+ { { "sip", "notify", NULL },
+ sip_notify, "Send a notify packet to a SIP peer",
+ notify_usage, complete_sipnotify },
+
+ { { "sip", "show", "channel", NULL },
+ sip_show_channel, "Show detailed SIP channel info",
+ show_channel_usage, complete_sipch },
+
+ { { "sip", "show", "history", NULL },
+ sip_show_history, "Show SIP dialog history",
+ show_history_usage, complete_sipch },
+
+ { { "sip", "show", "peer", NULL },
+ sip_show_peer, "Show details on specific SIP peer",
+ show_peer_usage, complete_sip_show_peer },
+
+ { { "sip", "show", "user", NULL },
+ sip_show_user, "Show details on specific SIP user",
+ show_user_usage, complete_sip_show_user },
+
+ { { "sip", "prune", "realtime", NULL },
+ sip_prune_realtime, "Prune cached Realtime object(s)",
+ prune_realtime_usage },
+
+ { { "sip", "prune", "realtime", "peer", NULL },
+ sip_prune_realtime, "Prune cached Realtime peer(s)",
+ prune_realtime_usage, complete_sip_prune_realtime_peer },
+
+ { { "sip", "prune", "realtime", "user", NULL },
+ sip_prune_realtime, "Prune cached Realtime user(s)",
+ prune_realtime_usage, complete_sip_prune_realtime_user },
+
+ { { "sip", "set", "debug", NULL },
+ sip_do_debug, "Enable SIP debugging",
+ debug_usage, NULL, &cli_sip_debug_deprecated },
+
+ { { "sip", "set", "debug", "ip", NULL },
+ sip_do_debug, "Enable SIP debugging on IP",
+ debug_usage },
+
+ { { "sip", "set", "debug", "peer", NULL },
+ sip_do_debug, "Enable SIP debugging on Peername",
+ debug_usage, complete_sip_debug_peer },
+
+ { { "sip", "set", "debug", "off", NULL },
+ sip_no_debug, "Disable SIP debugging",
+ no_debug_usage, NULL, &cli_sip_no_debug_deprecated },
+
+ { { "sip", "history", NULL },
+ sip_do_history, "Enable SIP history",
+ history_usage },
+
+ { { "sip", "history", "off", NULL },
+ sip_no_history, "Disable SIP history",
+ no_history_usage },
+
+ { { "sip", "reload", NULL },
+ sip_reload, "Reload SIP configuration",
+ sip_reload_usage },
+};
+
+/*! \brief PBX load module - initialization */
+static int load_module(void)
+{
+ 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;
+
+ /* 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, manager_sip_show_peers,
+ "List SIP peers (text format)", mandescr_show_peers);
+ ast_manager_register2("SIPshowpeer", EVENT_FLAG_SYSTEM, manager_sip_show_peer,
+ "Show SIP peer (text format)", mandescr_show_peer);
+
+ 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;
+
+ /* 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_mutex_lock(&iflock);
+ /* Hangup all interfaces if they have an owner */
+ for (p = iflist; p ; p = p->next) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ }
+ 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);
+
+restartdestroy:
+ ast_mutex_lock(&iflock);
+ /* Destroy all the interfaces and free their memory */
+ p = iflist;
+ while (p) {
+ pl = p;
+ p = p->next;
+ if (__sip_destroy(pl, TRUE) < 0) {
+ /* Something is still bridged, let it react to getting a hangup */
+ iflist = p;
+ ast_mutex_unlock(&iflock);
+ usleep(1);
+ goto restartdestroy;
+ }
+ }
+ iflist = NULL;
+ ast_mutex_unlock(&iflock);
+
+ /* Free memory for local network address mask */
+ ast_free_ha(localaddr);
+
+ 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);
+
+ 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/1.4.23-rc4/channels/chan_skinny.c b/1.4.23-rc4/channels/chan_skinny.c
new file mode 100644
index 000000000..657163398
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_skinny.c
@@ -0,0 +1,5016 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <errno.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/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/lock.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/dsp.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/astobj.h"
+#include "asterisk/abstract_jb.h"
+#include "asterisk/threadstorage.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 int keep_alive = 120;
+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(SOLARIS) || defined(__Darwin__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__)
+#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))
+#else
+#include <bits/byteswap.h>
+#endif
+#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
+
+/*! 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, device2str_threadbuf_init);
+#define DEVICE2STR_BUFSIZE 15
+
+AST_THREADSTORAGE(control2str_threadbuf, control2str_threadbuf_init);
+#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 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_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_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 */
+#define BT_CUST_HINT 0xB1 /* pipe dream */
+
+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
+
+struct soft_key_template_definition soft_key_template_default[] = {
+ { "Redial", 0x01 },
+ { "NewCall", 0x02 },
+ { "Hold", 0x03 },
+ { "Trnsfer", 0x04 },
+ { "CFwdAll", 0x05 },
+ { "CFwdBusy", 0x06 },
+ { "CFwdNoAnswer", 0x07 },
+ { "<<", 0x08 },
+ { "EndCall", 0x09 },
+ { "Resume", 0x0A },
+ { "Answer", 0x0B },
+ { "Info", 0x0C },
+ { "Confrn", 0x0D },
+ { "Park", 0x0E },
+ { "Join", 0x0F },
+ { "MeetMe", 0x10 },
+ { "PickUp", 0x11 },
+ { "GPickUp", 0x12 },
+};
+
+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_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;
+};
+
+/* 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 int amaflags = 0;
+static int callnums = 1;
+
+#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_7914 124 /* Expansion module */
+#define SKINNY_DEVICE_7985 302
+#define SKINNY_DEVICE_7911 307
+#define SKINNY_DEVICE_7961GE 308
+#define SKINNY_DEVICE_7941GE 309
+#define SKINNY_DEVICE_7931 348
+#define SKINNY_DEVICE_7921 365
+#define SKINNY_DEVICE_7906 369
+#define SKINNY_DEVICE_7962 404 /* Not found */
+#define SKINNY_DEVICE_7937 431
+#define SKINNY_DEVICE_7942 434
+#define SKINNY_DEVICE_7945 435
+#define SKINNY_DEVICE_7965 436
+#define SKINNY_DEVICE_7975 437
+#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_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 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 */
+ char call_forward[AST_MAX_EXTENSION];
+ char mailbox[AST_MAX_EXTENSION];
+ 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 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;
+
+ struct ast_codec_pref prefs;
+ struct skinny_subchannel *sub;
+ struct skinny_line *next;
+ struct skinny_device *parent;
+};
+
+struct skinny_speeddial {
+ ast_mutex_t lock;
+ char label[42];
+ char exten[AST_MAX_EXTENSION];
+ int instance;
+
+ 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];
+ int type;
+ int registered;
+ int lastlineinstance;
+ int lastcallreference;
+ int capability;
+ int earlyrtp;
+ char exten[AST_MAX_EXTENSION];
+ 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_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_MAX_AUDIO << 1) - 1),
+ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
+ .requester = skinny_request,
+ .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 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_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_LINE;
+ (btn++)->buttonDefinition = BT_REDIAL;
+ for (i = 0; i < 3; i++)
+ (btn++)->buttonDefinition = BT_SPEEDDIAL;
+ (btn++)->buttonDefinition = BT_HOLD;
+ (btn++)->buttonDefinition = BT_TRANSFER;
+ (btn++)->buttonDefinition = BT_FORWARDALL;
+ (btn++)->buttonDefinition = BT_CALLPARK;
+ (btn++)->buttonDefinition = BT_VOICEMAIL;
+ (btn++)->buttonDefinition = BT_CONFERENCE;
+ 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:
+ case SKINNY_DEVICE_7962:
+ case SKINNY_DEVICE_7965:
+ for (i = 0; i < 6; i++)
+ (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL;
+ break;
+ case SKINNY_DEVICE_7940:
+ case SKINNY_DEVICE_7941:
+ case SKINNY_DEVICE_7941GE:
+ case SKINNY_DEVICE_7942:
+ case SKINNY_DEVICE_7945:
+ 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_7975:
+ 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:
+ /* XXX I don't know if this is right. */
+ for (i = 0; i < 4; i++)
+ (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL;
+ break;
+ case SKINNY_DEVICE_7921:
+ for (i = 0; i < 6; 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_7906:
+ ast_log(LOG_WARNING, "Unsupported device type '%d (7906)' found.\n", d->type);
+ break;
+ case SKINNY_DEVICE_7931:
+ ast_log(LOG_WARNING, "Unsupported device type '%d (7931)' found.\n", d->type);
+ break;
+ case SKINNY_DEVICE_7937:
+ ast_log(LOG_WARNING, "Unsupported device type '%d (7937)' found.\n", d->type);
+ break;
+ case SKINNY_DEVICE_7914:
+ ast_log(LOG_WARNING, "Unsupported device type '%d (7914)' found. Expansion module registered by itself?\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;
+
+ 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_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';
+ device = at;
+ ast_mutex_lock(&devicelock);
+ for (d = devices; d; d = d->next) {
+ if (!strcasecmp(d->name, device)) {
+ if (skinnydebug)
+ ast_verbose("Found device: %s\n", d->name);
+ /* Found the device */
+ for (l = d->lines; l; l = l->next) {
+ /* Search for the right line */
+ if (!strcasecmp(l->name, line)) {
+ ast_mutex_unlock(&devicelock);
+ return l;
+ }
+ }
+ }
+ }
+ /* Device not found */
+ ast_mutex_unlock(&devicelock);
+ return NULL;
+}
+
+/* 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;
+ }
+
+ 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)
+{
+ struct skinny_speeddial *sd;
+
+ for (sd = d->speeddials; sd; sd = sd->next) {
+ if (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 skinny_register(struct skinny_req *req, struct skinnysession *s)
+{
+ struct skinny_device *d;
+ 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;
+ 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;
+
+ d = s->device;
+
+ if (d) {
+ d->session = NULL;
+ d->registered = 0;
+ }
+
+ 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");
+ ast_mutex_unlock(&s->lock);
+ 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 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;
+ struct skinny_device *d = s->device;
+ struct skinny_line *l;
+*/
+
+ /* Update time on device */
+ handle_time_date_req_message(NULL, s);
+
+/*
+ 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);
+ } else {
+ transmit_lamp_indication(s, STIMULUS_VOICEMAIL, l->instance, 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;
+
+ if (!(sub = c->tech_pvt) || !(sub->rtp))
+ return AST_RTP_GET_FAILED;
+
+ *rtp = sub->rtp;
+
+ return AST_RTP_TRY_NATIVE;
+}
+
+static int skinny_set_rtp_peer(struct ast_channel *c, struct ast_rtp *rtp, struct ast_rtp *vrtp, int codecs, int nat_active)
+{
+ struct skinny_subchannel *sub;
+ sub = c->tech_pvt;
+ if (sub) {
+ /* transmit_modify_with_sdp(sub, rtp); @@FIXME@@ if needed */
+ return 0;
+ }
+ return -1;
+}
+
+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 int skinny_do_debug(int fd, int argc, char *argv[])
+{
+ if (argc != 3) {
+ return RESULT_SHOWUSAGE;
+ }
+ skinnydebug = 1;
+ ast_cli(fd, "Skinny Debugging Enabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int skinny_no_debug(int fd, int argc, char *argv[])
+{
+ if (argc != 4) {
+ return RESULT_SHOWUSAGE;
+ }
+ skinnydebug = 0;
+ ast_cli(fd, "Skinny Debugging Disabled\n");
+ return RESULT_SUCCESS;
+}
+
+static char *complete_skinny_reset(const char *line, const char *word, int pos, int state)
+{
+ struct skinny_device *d;
+
+ char *result = NULL;
+ int wordlen = strlen(word);
+ int which = 0;
+
+ if (pos == 2) {
+ for (d = devices; d && !result; d = d->next) {
+ if (!strncasecmp(word, d->id, wordlen) && ++which > state)
+ result = ast_strdup(d->id);
+ }
+ }
+
+ return result;
+}
+
+static int skinny_reset_device(int fd, int argc, char *argv[])
+{
+ struct skinny_device *d;
+ struct skinny_req *req;
+
+ if (argc < 3 || argc > 4) {
+ return RESULT_SHOWUSAGE;
+ }
+ ast_mutex_lock(&devicelock);
+
+ for (d = devices; d; d = d->next) {
+ int fullrestart = 0;
+ if (!strcasecmp(argv[2], d->id) || !strcasecmp(argv[2], "all")) {
+ if (!(d->session))
+ continue;
+
+ if (!(req = req_alloc(sizeof(struct reset_message), RESET_MESSAGE)))
+ continue;
+
+ if (argc == 4 && !strcasecmp(argv[3], "restart"))
+ fullrestart = 1;
+
+ if (fullrestart)
+ req->data.reset.resetType = 2;
+ else
+ req->data.reset.resetType = 1;
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "%s device %s.\n", (fullrestart) ? "Restarting" : "Resetting", d->id);
+ transmit_response(d->session, req);
+ }
+ }
+ ast_mutex_unlock(&devicelock);
+ return RESULT_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_7914:
+ return "7914";
+ 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_7931:
+ return "7931";
+ case SKINNY_DEVICE_7921:
+ return "7921";
+ case SKINNY_DEVICE_7906:
+ return "7906";
+ case SKINNY_DEVICE_7962:
+ return "7962";
+ case SKINNY_DEVICE_7937:
+ return "7937";
+ case SKINNY_DEVICE_7942:
+ return "7942";
+ case SKINNY_DEVICE_7945:
+ return "7945";
+ case SKINNY_DEVICE_7965:
+ return "7965";
+ case SKINNY_DEVICE_7975:
+ return "7975";
+ 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;
+ }
+}
+
+static int skinny_show_devices(int fd, int argc, char *argv[])
+{
+ struct skinny_device *d;
+ struct skinny_line *l;
+ int numlines = 0;
+
+ if (argc != 3) {
+ return RESULT_SHOWUSAGE;
+ }
+ ast_mutex_lock(&devicelock);
+
+ ast_cli(fd, "Name DeviceId IP Type R NL\n");
+ ast_cli(fd, "-------------------- ---------------- --------------- --------------- - --\n");
+ for (d = devices; d; d = d->next) {
+ numlines = 0;
+ for (l = d->lines; l; l = l->next) {
+ numlines++;
+ }
+
+ ast_cli(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 RESULT_SUCCESS;
+}
+
+static int skinny_show_lines(int fd, int argc, char *argv[])
+{
+ struct skinny_device *d;
+ struct skinny_line *l;
+
+ if (argc != 3) {
+ return RESULT_SHOWUSAGE;
+ }
+ ast_mutex_lock(&devicelock);
+
+ ast_cli(fd, "Device Name Instance Name Label \n");
+ ast_cli(fd, "-------------------- -------- -------------------- --------------------\n");
+ for (d = devices; d; d = d->next) {
+ for (l = d->lines; l; l = l->next) {
+ ast_cli(fd, "%-20s %8d %-20s %-20s\n",
+ d->name,
+ l->instance,
+ l->name,
+ l->label);
+ }
+ }
+
+ ast_mutex_unlock(&devicelock);
+ return RESULT_SUCCESS;
+}
+
+static char show_devices_usage[] =
+"Usage: skinny show devices\n"
+" Lists all devices known to the Skinny subsystem.\n";
+
+static char show_lines_usage[] =
+"Usage: skinny show lines\n"
+" Lists all lines known to the Skinny subsystem.\n";
+
+static char debug_usage[] =
+"Usage: skinny set debug\n"
+" Enables dumping of Skinny packets for debugging purposes\n";
+
+static char no_debug_usage[] =
+"Usage: skinny set debug off\n"
+" Disables dumping of Skinny packets for debugging purposes\n";
+
+static char reset_usage[] =
+"Usage: skinny reset <DeviceId|all> [restart]\n"
+" Causes a Skinny device to reset itself, optionally with a full restart\n";
+
+static struct ast_cli_entry cli_skinny[] = {
+ { { "skinny", "show", "devices", NULL },
+ skinny_show_devices, "List defined Skinny devices",
+ show_devices_usage },
+
+ { { "skinny", "show", "lines", NULL },
+ skinny_show_lines, "List defined Skinny lines per device",
+ show_lines_usage },
+
+ { { "skinny", "set", "debug", NULL },
+ skinny_do_debug, "Enable Skinny debugging",
+ debug_usage },
+
+ { { "skinny", "set", "debug", "off", NULL },
+ skinny_no_debug, "Disable Skinny debugging",
+ no_debug_usage },
+
+ { { "skinny", "reset", NULL },
+ skinny_reset_device, "Reset Skinny device(s)",
+ reset_usage, complete_skinny_reset },
+};
+
+#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;
+ int lineInstance = 1;
+ int speeddialInstance = 1;
+ int y = 0;
+
+ if (!(d = ast_calloc(1, sizeof(struct skinny_device)))) {
+ return NULL;
+ } else {
+ ast_copy_string(d->name, cat, sizeof(d->name));
+ d->lastlineinstance = 1;
+ d->capability = default_capability;
+ d->prefs = default_prefs;
+ d->earlyrtp = 1;
+ while(v) {
+ if (!strcasecmp(v->name, "host")) {
+ if (ast_get_ip(&d->addr, v->value)) {
+ 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);
+ } else if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(context, v->value, sizeof(context));
+ } 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, "earlyrtp")) {
+ d->earlyrtp = 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, "hasvoicemail")) {
+ if (ast_true(v->value) && ast_strlen_zero(mailbox)) {
+ ast_copy_string(mailbox, cat, 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, "speeddial")) {
+ if (!(sd = ast_calloc(1, sizeof(struct skinny_speeddial)))) {
+ return NULL;
+ } else {
+ char *stringp, *exten, *label;
+ stringp = v->value;
+ exten = strsep(&stringp, ",");
+ label = strsep(&stringp, ",");
+ ast_mutex_init(&sd->lock);
+ ast_copy_string(sd->exten, exten, sizeof(sd->exten));
+ if (label)
+ ast_copy_string(sd->label, label, sizeof(sd->label));
+ else
+ ast_copy_string(sd->label, exten, sizeof(sd->label));
+ sd->instance = speeddialInstance++;
+
+ sd->parent = d;
+
+ sd->next = d->speeddials;
+ d->speeddials = sd;
+ }
+ } else if (!strcasecmp(v->name, "addon")) {
+ if (!(a = ast_calloc(1, sizeof(struct skinny_addon)))) {
+ 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(struct skinny_line)))) {
+ 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->mailbox, mailbox, sizeof(l->mailbox));
+ if (!ast_strlen_zero(mailbox)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Setting mailbox '%s' on %s@%s\n", mailbox, d->name, l->name);
+ }
+ 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->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->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) {
+ sub->owner->fds[0] = ast_rtp_fd(sub->rtp);
+ sub->owner->fds[1] = ast_rtcp_fd(sub->rtp);
+ }
+ if (hasvideo && sub->vrtp && sub->owner) {
+ sub->owner->fds[2] = ast_rtp_fd(sub->vrtp);
+ sub->owner->fds[3] = ast_rtcp_fd(sub->vrtp);
+ }
+ if (sub->rtp) {
+ ast_rtp_setnat(sub->rtp, l->nat);
+ }
+ if (sub->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);
+ if (!sub->rtp) {
+ start_rtp(sub);
+ }
+ 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 getforward=0;
+ int loop_pause = 100;
+
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_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 we will get a digit */
+ while (strlen(d->exten) == len) {
+ ast_safe_sleep(c, loop_pause);
+ timeout -= loop_pause;
+ if (timeout <= 0){
+ res = 0;
+ break;
+ }
+ }
+
+ 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 (getforward) {
+ /* Record this as the forwarding extension */
+ ast_copy_string(l->call_forward, d->exten, sizeof(l->call_forward));
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Setting call forward to '%s' on channel %s\n",
+ l->call_forward, c->name);
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ if (res) {
+ break;
+ }
+ ast_safe_sleep(c, 500);
+ ast_indicate(c, -1);
+ ast_safe_sleep(c, 1000);
+ memset(d->exten, 0, sizeof(d->exten));
+ transmit_tone(s, SKINNY_DIALTONE, l->instance, sub->callid);
+ len = 0;
+ getforward = 0;
+ } 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_log(LOG_DEBUG, "Not enough digits (and no ambiguous match)...\n");
+ 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_verbose(VERBOSE_PREFIX_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_log(LOG_DEBUG, "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_dialednumber(s, exten, l->instance, sub->callid);
+ 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_log(LOG_DEBUG, "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 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_verbose(VERBOSE_PREFIX_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) {
+ if (!d->earlyrtp) {
+ 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;
+ if (!d->earlyrtp) {
+ break;
+ }
+ }
+ }
+ return -1; /* Tell asterisk to provide inband signalling */
+ case AST_CONTROL_BUSY:
+ if (ast->_state != AST_STATE_UP) {
+ if (!d->earlyrtp) {
+ 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);
+ if (!d->earlyrtp) {
+ break;
+ }
+ }
+ return -1; /* Tell asterisk to provide inband signalling */
+ case AST_CONTROL_CONGESTION:
+ if (ast->_state != AST_STATE_UP) {
+ if (!d->earlyrtp) {
+ 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);
+ if (!d->earlyrtp) {
+ break;
+ }
+ }
+ return -1; /* Tell asterisk to provide inband signalling */
+ case AST_CONTROL_PROGRESS:
+ if ((ast->_state != AST_STATE_UP) && !sub->progress && !sub->outgoing) {
+ if (!d->earlyrtp) {
+ 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;
+ if (!d->earlyrtp) {
+ break;
+ }
+ }
+ return -1; /* Tell asterisk to provide inband signalling */
+ case -1: /* STOP_TONE */
+ 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;
+ case AST_CONTROL_SRCUPDATE:
+ ast_rtp_new_source(sub->rtp);
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", ind);
+ return -1; /* Tell asterisk to provide inband signalling */
+ }
+ 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;
+ 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(struct skinny_subchannel));
+ 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) {
+ tmp->fds[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;
+ ast_string_field_set(tmp, call_forward, l->call_forward);
+ 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);
+
+ 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_BLINK);
+ 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;
+ }
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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_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 0
+ 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;
+ 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);
+ }
+ }
+#endif
+ break;
+ case STIMULUS_SPEEDDIAL:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: SpeedDial(%d/%d)\n", instance, callreference);
+
+#if 0
+ if (!(sd = find_speeddial_by_instance(d, instance))) {
+ return 0;
+ }
+
+ 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) {
+ sub = c->tech_pvt;
+ l = sub->parent;
+ 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)) {
+ if (!ast_matchmore_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));
+ skinny_newcall(c);
+ break;
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+ }
+#endif
+ 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);
+ /* XXX Find and dial voicemail extension */
+ break;
+ case STIMULUS_CALLPARK:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Park Call(%d/%d)\n", instance, callreference);
+ /* XXX Park the call */
+ break;
+ case STIMULUS_FORWARDALL:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Forward All(%d/%d)\n", instance, callreference);
+ /* Why is DND under FORWARDALL? */
+ /* Because it's the same thing. */
+
+ /* Do not disturb */
+ if (l->dnd != 0){
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Disabling DND on %s@%s\n", l->name, d->name);
+ l->dnd = 0;
+ transmit_lamp_indication(s, STIMULUS_FORWARDALL, 1, SKINNY_LAMP_ON);
+ transmit_displaynotify(s, "DnD disabled", 10);
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Enabling DND on %s@%s\n", l->name, d->name);
+ l->dnd = 1;
+ transmit_lamp_indication(s, STIMULUS_FORWARDALL, 1, SKINNY_LAMP_OFF);
+ transmit_displaynotify(s, "DnD enabled", 10);
+ }
+ break;
+ case STIMULUS_FORWARDBUSY:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Forward Busy (%d/%d)\n", instance, callreference);
+ break;
+ case STIMULUS_FORWARDNOANSWER:
+ if (skinnydebug)
+ ast_verbose("Received Stimulus: Forward No Answer (%d/%d)\n", instance, callreference);
+ 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(s->device, 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;
+
+ 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_log(LOG_DEBUG, "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;
+ }
+ 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;
+
+ 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_log(LOG_DEBUG, "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;
+
+ 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);
+
+ 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;
+ int instance;
+
+ instance = letohl(req->data.line.lineNumber);
+
+ ast_mutex_lock(&devicelock);
+
+ l = find_line_by_instance(d, instance);
+
+ if (!l) {
+ 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);
+ 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)
+{
+ time_t timer;
+ struct tm *cmtime;
+
+ if (!(req = req_alloc(sizeof(struct definetimedate_message), DEFINETIMEDATE_MESSAGE)))
+ return -1;
+
+ timer = time(NULL);
+ cmtime = localtime(&timer);
+ 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(0);
+ req->data.definetimedate.timestamp = htolel(timer);
+ 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_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->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->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_CUST_HINT:
+ 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));
+ ast_verbose("ourip = %s:%d\n", ast_inet_ntoa(d->ourip), ntohs(us.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;
+ }
+
+ 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 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;
+ 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);
+ }
+ }
+#endif
+ 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;
+ }
+
+ 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_CFWDALL:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Forward All(%d/%d)\n", instance, callreference);
+
+ /* Do not disturb */
+ if (l->dnd != 0){
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Disabling DND on %s@%s\n", l->name, d->name);
+ l->dnd = 0;
+ transmit_lamp_indication(s, STIMULUS_FORWARDALL, 1, SKINNY_LAMP_ON);
+ transmit_displaynotify(s, "DnD disabled", 10);
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Enabling DND on %s@%s\n", l->name, d->name);
+ l->dnd = 1;
+ transmit_lamp_indication(s, STIMULUS_FORWARDALL, 1, SKINNY_LAMP_OFF);
+ transmit_displaynotify(s, "DnD enabled", 10);
+ }
+ break;
+ case SOFTKEY_CFWDBUSY:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Forward Busy (%d/%d)\n", instance, callreference);
+ break;
+ case SOFTKEY_CFWDNOANSWER:
+ if (skinnydebug)
+ ast_verbose("Received Softkey Event: Forward No Answer (%d/%d)\n", instance, callreference);
+ 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;
+ }
+ 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;
+ struct skinny_device *d = s->device;
+ struct skinny_subchannel *sub;
+ int lineInstance;
+ int callReference;
+
+ 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);
+ 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:
+ 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);
+ size_t len;
+
+ 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);
+ }
+
+ len = strlen(d->exten);
+ if (len < sizeof(d->exten) - 1) {
+ d->exten[len] = dgt;
+ d->exten[len+1] = '\0';
+ } else {
+ ast_log(LOG_WARNING, "Dropping digit with value %d because digit queue is full\n", dgt);
+ }
+ } 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)
+ 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);
+ 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);
+ free(req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static void *skinny_session(void *data)
+{
+ int res;
+ struct skinny_req *req;
+ struct skinnysession *s = data;
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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_attr_t attr;
+ pthread_t tcp_thread;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ 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(&tcp_thread, &attr, skinny_session, s)) {
+ destroy_session(s);
+ }
+ }
+ if (skinnydebug)
+ ast_verbose("killing accept thread\n");
+ close(as);
+ pthread_attr_destroy(&attr);
+ 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 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_MAX_AUDIO << 1) - 1))) {
+ 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;
+ }
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_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);
+
+ if (gethostname(ourhost, sizeof(ourhost))) {
+ ast_log(LOG_WARNING, "Unable to get hostname, Skinny disabled\n");
+ return 0;
+ }
+ cfg = ast_config_load(config);
+
+ /* 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));
+
+ /* 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, "dateformat")) {
+ memcpy(date_format, v->value, sizeof(date_format));
+ } 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") || !strcasecmp(v->name, "port")) {
+ 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);
+ }
+ if (!strcasecmp(v->name, "port")) { /*! \todo Remove 'port' option after 1.4 */
+ ast_log(LOG_WARNING, "Option 'port' at line %d of %s has been deprecated. Please use 'bindport' instead.\n", 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) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_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);
+ ast_mutex_unlock(&netlock);
+ 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);
+ ast_mutex_unlock(&netlock);
+ 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);
+ ast_mutex_unlock(&netlock);
+ return 0;
+ }
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Skinny listening on %s:%d\n",
+ ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port));
+ 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);
+ free(llast);
+ }
+ /* Delete all speeddials for this device */
+ for (sd=d->speeddials;sd;) {
+ sdlast = sd;
+ sd = sd->next;
+ ast_mutex_destroy(&sdlast->lock);
+ free(sdlast);
+ }
+ /* Delete all addons for this device */
+ for (a=d->addons;a;) {
+ alast = a;
+ a = a->next;
+ ast_mutex_destroy(&alast->lock);
+ free(alast);
+ }
+ dlast = d;
+ d = d->next;
+ 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;
+
+ 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);
+ 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);
+
+ 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/1.4.23-rc4/channels/chan_vpb.cc b/1.4.23-rc4/channels/chan_vpb.cc
new file mode 100644
index 000000000..56467a6ce
--- /dev/null
+++ b/1.4.23-rc4/channels/chan_vpb.cc
@@ -0,0 +1,2905 @@
+/*
+ * 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>
+ ***/
+
+#include <vpbapi.h>
+
+extern "C" {
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/utils.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.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 <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <ctype.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";
+
+/* Backwards compatibility from trunk */
+#define ast_verb(level, ...) do { \
+ if (option_verbose >= level) { \
+ if (level >= 4) \
+ ast_verbose(VERBOSE_PREFIX_4 __VA_ARGS__); \
+ else if (level == 3) \
+ ast_verbose(VERBOSE_PREFIX_3 __VA_ARGS__); \
+ else if (level == 2) \
+ ast_verbose(VERBOSE_PREFIX_2 __VA_ARGS__); \
+ else if (level == 1) \
+ ast_verbose(VERBOSE_PREFIX_1 __VA_ARGS__); \
+ else \
+ ast_verbose(__VA_ARGS__); \
+ } \
+} while (0)
+
+#define ast_debug(level, ...) do { \
+ if (option_debug >= (level)) \
+ ast_log(LOG_DEBUG, __VA_ARGS__); \
+} while (0)
+
+/* 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 short 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_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_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \
+ |VPB_MRING_OFF|VPB_MSTATION_FLASH)
+#define VPB_EVENTS_NODTMF (VPB_MRING|VPB_MDIGIT|VPB_MTONEDETECT|VPB_MTIMEREXP \
+ |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_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, VPB_CALL_DISCONNECT, 0 },
+ { VPB_DIAL, VPB_CALL_DIALTONE, 0 },
+ { VPB_RINGBACK, VPB_CALL_RINGBACK, 0 },
+ { VPB_BUSY, 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, const 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_debug(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_debug(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);
+ 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 0
+ 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;
+#endif
+ /* 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);
+ }
+
+ #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), "%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 - %d\n", p->dev, rc);
+ ast_copy_string(p->callerid, "unknown", sizeof(p->callerid));
+ }
+ 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 couldn't 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_debug(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), "%s %s", number, name);
+ } else {
+ ast_copy_string(p->callerid, number, sizeof(p->callerid));
+ }
+ 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_debug(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_channel_trylock(p->owner) == 0) {
+ ast_queue_frame(p->owner, &f);
+ ast_channel_unlock(p->owner);
+ 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. */
+ }
+#if 0
+ /* 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;
+ }
+#endif
+ } 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, sizeof(p->callerid));
+ } 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 %d\n", res );
+ ast_verbose("Monitor get event error %d\n", res );
+ continue;
+ }
+
+ str[0] = 0;
+
+ p = NULL;
+
+ ast_mutex_lock(&monlock);
+ if (e.type == VPB_NULL_EVENT) {
+ ast_verb(4, "Monitor got null event\n");
+ } else {
+ vpb_translate_event(&e, str);
+ if (*str && *(str + 1)) {
+ str[strlen(str) - 1] = '\0';
+ }
+
+ ast_mutex_lock(&iflock);
+ for (p = iflist; 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_EVT_NONE;
+ 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 = (vpb_bridge_t *)ast_calloc(1, max_bridges * sizeof(vpb_bridge_t));
+ if (!bridges) {
+ ast_log(LOG_ERROR, "Failed to initialize bridges\n");
+ } else {
+ int i;
+ for (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 do 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) {
+ vpb_echo_canc_set_sup_thresh(0, &ec_supp_threshold);
+ ast_log(LOG_NOTICE, "Voicetronix EC Sup Thres set\n");
+ }
+ } else {
+ /* need to do 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 = (vpb_pvt *)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;
+
+ ast_copy_string(tmp->language, language, sizeof(tmp->language));
+ ast_copy_string(tmp->context, context, sizeof(tmp->context));
+
+ tmp->callerid_type = 0;
+ if (callerid) {
+ if (strcasecmp(callerid, "on") == 0) {
+ tmp->callerid_type = 1;
+ ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid));
+ } else if (strcasecmp(callerid, "v23") == 0) {
+ tmp->callerid_type = 2;
+ ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid));
+ } else if (strcasecmp(callerid, "bell") == 0) {
+ tmp->callerid_type = 3;
+ ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid));
+ } else {
+ ast_copy_string(tmp->callerid, callerid, sizeof(tmp->callerid));
+ }
+ } else {
+ ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid));
+ }
+
+ /* 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(tmp->handle, 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) - strlen(p->play_dtmf) - 1);
+ 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;
+ ast_copy_string(dialstring, s, sizeof(dialstring));
+ 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. */
+ vpb_ring_station_async(p->handle, 2);
+ else {
+ VPB_CALL call;
+ int j;
+
+ /* 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);
+
+ 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: %d\n", ast->name, s, 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, 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 AudioCompress 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 VPB_RAW;
+ }
+}
+
+static inline const 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_SLINEAR:
+ return 16;
+ case AST_FORMAT_ADPCM:
+ return 4;
+ case AST_FORMAT_ALAW:
+ case AST_FORMAT_ULAW:
+ 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;
+ AudioCompress fmt = VPB_RAW;
+ 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, trycnt=0;
+ AudioCompress fmt;
+ 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);
+ } 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_channel_trylock(p->owner);
+ trycnt++;
+ } while ((res !=0 ) && (trycnt < 300));
+ if (res == 0) {
+ ast_queue_frame(p->owner, fr);
+ ast_channel_unlock(p->owner);
+ } 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, const 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, "%s", 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;
+
+ ast_copy_string(tmp->context, context, sizeof(tmp->context));
+ if (!ast_strlen_zero(me->ext))
+ ast_copy_string(tmp->exten, me->ext, sizeof(tmp->exten));
+ else
+ strcpy(tmp->exten, "s");
+ if (!ast_strlen_zero(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 *vdata, int *cause)
+{
+ int oldformat;
+ struct vpb_pvt *p;
+ struct ast_channel *tmp = NULL;
+ char *sepstr, *data = (char *)vdata, *name;
+ const char *s;
+ 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;
+ }
+
+ name = ast_strdup(S_OR(data, ""));
+
+ 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);
+ for (p = iflist; p; p = p->next) {
+ 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;
+ }
+ }
+ }
+ 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(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;
+ }
+
+
+ /* percentage? */
+ /*if (value[strlen(value) - 1] == '%') */
+ /* return gain / (float)100; */
+
+ return gain;
+}
+
+
+static int unload_module(void)
+{
+ 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 */
+ for (p = iflist; p; p = p->next) {
+ if (p->owner)
+ ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
+ }
+ 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);
+
+ if (bridges) {
+ 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 enum ast_module_load_result load_module()
+{
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ 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;
+ enum ast_module_load_result error = AST_MODULE_LOAD_SUCCESS; /* Error flag */
+ int bal1 = -1; /* Special value - means do not set */
+ int bal2 = -1;
+ int bal3 = -1;
+ char * callerid = NULL;
+
+ int num_cards = 0;
+ try {
+ num_cards = vpb_get_num_cards();
+ } catch (VpbException e) {
+ ast_log(LOG_ERROR, "No Voicetronix cards detected\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ int ports_per_card[num_cards];
+ for (int i = 0; i < num_cards; ++i)
+ ports_per_card[i] = vpb_get_ports_per_card(i);
+
+ cfg = ast_config_load(config);
+
+ /* 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;
+ }
+
+ 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 = (short)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);
+ if (board >= num_cards || board < 0 || channel < 0 || channel >= ports_per_card[board]) {
+ ast_log(LOG_ERROR, "Invalid board/channel (%d/%d) for channel '%s'\n", board, channel, v->value);
+ error = AST_MODULE_LOAD_FAILURE;
+ goto done;
+ }
+ 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 = AST_MODULE_LOAD_FAILURE;
+ goto done;
+ }
+ } else if (strcasecmp(v->name, "language") == 0) {
+ ast_copy_string(language, v->value, sizeof(language));
+ } 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")) {
+ ast_copy_string(context, v->value, sizeof(context));
+ } 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_MODULE_LOAD_SUCCESS && ast_channel_register(&vpb_tech_indicate) != 0) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'vpb'\n");
+ error = AST_MODULE_LOAD_FAILURE;
+ } else {
+ ast_log(LOG_NOTICE, "VPB driver Registered (w/AstIndication)\n");
+ }
+ } else {
+ if (error == AST_MODULE_LOAD_SUCCESS && ast_channel_register(&vpb_tech) != 0) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'vpb'\n");
+ error = AST_MODULE_LOAD_FAILURE;
+ } else {
+ ast_log(LOG_NOTICE, "VPB driver Registered )\n");
+ }
+ }
+
+
+ if (error != AST_MODULE_LOAD_SUCCESS)
+ 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/1.4.23-rc4/channels/gentone-ulaw.c b/1.4.23-rc4/channels/gentone-ulaw.c
new file mode 100644
index 000000000..b290d76c7
--- /dev/null
+++ b/1.4.23-rc4/channels/gentone-ulaw.c
@@ -0,0 +1,157 @@
+/* Generate a header file for a particular
+ single or double frequency */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#define CLIP 32635
+#define BIAS 0x84
+static float loudness=16384.0;
+
+static int calc_samples(int freq)
+{
+ int x, samples;
+ /* Calculate the number of samples at 8000hz sampling
+ we need to have this wave form */
+ samples = 8000;
+ /* Take out common 2's up to six times */
+ for (x=0;x<6;x++)
+ if (!(freq % 2)) {
+ freq /= 2;
+ samples /= 2;
+ }
+ /* Take out common 5's (up to three times */
+ for (x=0;x<3;x++)
+ if (!(freq % 5)) {
+ freq /= 5;
+ samples /=5;
+ }
+ /* No more common factors. */
+ return samples;
+}
+
+/*
+** This routine converts from linear to ulaw
+**
+** Craig Reese: IDA/Supercomputing Research Center
+** Joe Campbell: Department of Defense
+** 29 September 1989
+**
+** References:
+** 1) CCITT Recommendation G.711 (very difficult to follow)
+** 2) "A New Digital Technique for Implementation of Any
+** Continuous PCM Companding Law," Villeret, Michel,
+** et al. 1973 IEEE Int. Conf. on Communications, Vol 1,
+** 1973, pg. 11.12-11.17
+** 3) MIL-STD-188-113,"Interoperability and Performance Standards
+** for Analog-to_Digital Conversion Techniques,"
+** 17 February 1987
+**
+** Input: Signed 16 bit linear sample
+** Output: 8 bit ulaw sample
+*/
+
+#define ZEROTRAP /* turn on the trap as per the MIL-STD */
+#define BIAS 0x84 /* define the add-in bias for 16 bit samples */
+#define CLIP 32635
+
+static unsigned char linear2ulaw(short sample) {
+static int exp_lut[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
+ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
+ 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+ 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+ 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+ 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+ 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+ 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7};
+ int sign, exponent, mantissa;
+ unsigned char ulawbyte;
+
+ /* Get the sample into sign-magnitude. */
+ sign = (sample >> 8) & 0x80; /* set aside the sign */
+ if (sign != 0) sample = -sample; /* get magnitude */
+ if (sample > CLIP) sample = CLIP; /* clip the magnitude */
+
+ /* Convert from 16 bit linear to ulaw. */
+ sample = sample + BIAS;
+ exponent = exp_lut[(sample >> 7) & 0xFF];
+ mantissa = (sample >> (exponent + 3)) & 0x0F;
+ ulawbyte = ~(sign | (exponent << 4) | mantissa);
+#ifdef ZEROTRAP
+ if (ulawbyte == 0) ulawbyte = 0x02; /* optional CCITT trap */
+#endif
+
+ return(ulawbyte);
+}
+
+int main(int argc, char *argv[])
+{
+ FILE *f;
+ int freq1, freq2;
+ float wlen1, wlen2;
+ float val;
+ int x, samples1, samples2, samples=0;
+ char fn[256];
+ if (argc < 3) {
+ fprintf(stderr, "Usage: gensound <name> <freq1> [freq2]\n");
+ exit(1);
+ }
+ freq1 = atoi(argv[2]);
+ if (argc > 3)
+ freq2 = atoi(argv[3]);
+ else
+ freq2 = 0;
+ wlen1 = 8000.0/(float)freq1;
+ samples1 = calc_samples(freq1);
+ printf("Wavelength 1 (in samples): %10.5f\n", wlen1);
+ printf("Minimum samples (1): %d (%f.3 wavelengths)\n", samples1, samples1 / wlen1);
+ if (freq2) {
+ wlen2 = 8000.0/(float)freq2;
+ samples2 = calc_samples(freq2);
+ printf("Wavelength 1 (in samples): %10.5f\n", wlen2);
+ printf("Minimum samples (1): %d (%f.3 wavelengths)\n", samples2, samples2 / wlen2);
+ }
+ samples = samples1;
+ if (freq2) {
+ while(samples % samples2)
+ samples += samples1;
+ }
+ printf("Need %d samples\n", samples);
+ snprintf(fn, sizeof(fn), "%s.h", argv[1]);
+ if ((f = fopen(fn, "w"))) {
+ if (freq2)
+ fprintf(f, "/* %s: Generated from frequencies %d and %d \n"
+ " by gentone. %d samples */\n", fn, freq1, freq2, samples);
+ else
+ fprintf(f, "/* %s: Generated from frequency %d\n"
+ " by gentone. %d samples */\n", fn, freq1, samples);
+ fprintf(f, "static unsigned char %s[%d] = {\n\t", argv[1], samples);
+ for (x=0;x<samples;x++) {
+ val = loudness * sin((freq1 * 2.0 * M_PI * x)/8000.0);
+ if (freq2)
+ val += loudness * sin((freq2 * 2.0 * M_PI * x)/8000.0);
+ fprintf(f, "%3d, ", (int) linear2ulaw(val));
+ if (!((x+1) % 8))
+ fprintf(f, "\n\t");
+ }
+ if (x % 15)
+ fprintf(f, "\n");
+ fprintf(f, "};\n");
+ fclose(f);
+ printf("Wrote %s\n", fn);
+ } else {
+ fprintf(stderr, "Unable to open %s for writing\n", fn);
+ return 1;
+ }
+ return 0;
+}
diff --git a/1.4.23-rc4/channels/gentone.c b/1.4.23-rc4/channels/gentone.c
new file mode 100644
index 000000000..29bd88e91
--- /dev/null
+++ b/1.4.23-rc4/channels/gentone.c
@@ -0,0 +1,95 @@
+/* Generate a header file for a particular
+ single or double frequency */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#define CLIP 32635
+#define BIAS 0x84
+static float loudness=16384.0;
+
+static int calc_samples(int freq)
+{
+ int x, samples;
+ /* Calculate the number of samples at 8000hz sampling
+ we need to have this wave form */
+ samples = 8000;
+ /* Take out common 2's up to six times */
+ for (x=0;x<6;x++)
+ if (!(freq % 2)) {
+ freq /= 2;
+ samples /= 2;
+ }
+ /* Take out common 5's (up to three times */
+ for (x=0;x<3;x++)
+ if (!(freq % 5)) {
+ freq /= 5;
+ samples /=5;
+ }
+ /* No more common factors. */
+ return samples;
+}
+
+int main(int argc, char *argv[])
+{
+ FILE *f;
+ int freq1, freq2;
+ float wlen1, wlen2;
+ float val;
+ int x, samples1, samples2=0, samples=0;
+ char fn[256];
+ if (argc < 3) {
+ fprintf(stderr, "Usage: gensound <name> <freq1> [freq2]\n");
+ exit(1);
+ }
+ freq1 = atoi(argv[2]);
+ if (argc > 3)
+ freq2 = atoi(argv[3]);
+ else
+ freq2 = 0;
+ wlen1 = 8000.0/(float)freq1;
+ samples1 = calc_samples(freq1);
+ printf("Wavelength 1 (in samples): %10.5f\n", wlen1);
+ printf("Minimum samples (1): %d (%f.3 wavelengths)\n", samples1, samples1 / wlen1);
+ if (freq2) {
+ wlen2 = 8000.0/(float)freq2;
+ samples2 = calc_samples(freq2);
+ printf("Wavelength 1 (in samples): %10.5f\n", wlen2);
+ printf("Minimum samples (1): %d (%f.3 wavelengths)\n", samples2, samples2 / wlen2);
+ }
+ samples = samples1;
+ if (freq2) {
+ while(samples % samples2)
+ samples += samples1;
+ }
+ printf("Need %d samples\n", samples);
+ snprintf(fn, sizeof(fn), "%s.h", argv[1]);
+ if ((f = fopen(fn, "w"))) {
+ if (freq2)
+ fprintf(f, "/* %s: Generated from frequencies %d and %d \n"
+ " by gentone. %d samples */\n", fn, freq1, freq2, samples);
+ else
+ fprintf(f, "/* %s: Generated from frequency %d\n"
+ " by gentone. %d samples */\n", fn, freq1, samples);
+ fprintf(f, "static short %s[%d] = {\n\t", argv[1], samples);
+ for (x=0;x<samples;x++) {
+ val = loudness * sin((freq1 * 2.0 * M_PI * x)/8000.0);
+ if (freq2)
+ val += loudness * sin((freq2 * 2.0 * M_PI * x)/8000.0);
+ fprintf(f, "%5d, ", (int)val);
+ if (!((x+1) % 8))
+ fprintf(f, "\n\t");
+ }
+ if (x % 15)
+ fprintf(f, "\n");
+ fprintf(f, "};\n");
+ fclose(f);
+ printf("Wrote %s\n", fn);
+ } else {
+ fprintf(stderr, "Unable to open %s for writing\n", fn);
+ return 1;
+ }
+ return 0;
+}
diff --git a/1.4.23-rc4/channels/h323/ChangeLog b/1.4.23-rc4/channels/h323/ChangeLog
new file mode 100644
index 000000000..ddbf08193
--- /dev/null
+++ b/1.4.23-rc4/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/1.4.23-rc4/channels/h323/INSTALL.openh323 b/1.4.23-rc4/channels/h323/INSTALL.openh323
new file mode 100644
index 000000000..f46c37905
--- /dev/null
+++ b/1.4.23-rc4/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/1.4.23-rc4/channels/h323/Makefile.in b/1.4.23-rc4/channels/h323/Makefile.in
new file mode 100644
index 000000000..083250f55
--- /dev/null
+++ b/1.4.23-rc4/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/1.4.23-rc4/channels/h323/README b/1.4.23-rc4/channels/h323/README
new file mode 100644
index 000000000..875bf3668
--- /dev/null
+++ b/1.4.23-rc4/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/1.4.23-rc4/channels/h323/TODO b/1.4.23-rc4/channels/h323/TODO
new file mode 100644
index 000000000..1e114ca3b
--- /dev/null
+++ b/1.4.23-rc4/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/1.4.23-rc4/channels/h323/ast_h323.cxx b/1.4.23-rc4/channels/h323/ast_h323.cxx
new file mode 100644
index 000000000..32b674dec
--- /dev/null
+++ b/1.4.23-rc4/channels/h323/ast_h323.cxx
@@ -0,0 +1,2487 @@
+#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/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"
+
+#include <ptbuildopts.h>
+
+#if PWLIB_MAJOR * 10000 + PWLIB_MINOR * 100 + PWLIB_BUILD >= 1 * 10000 + 12 * 100 + 0
+#define SKIP_PWLIB_PIPE_BUG_WORKAROUND 1
+#endif
+
+/* 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;
+
+#ifndef SKIP_PWLIB_PIPE_BUG_WORKAROUND
+static int _timerChangePipe[2];
+#endif
+
+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()
+{
+#ifndef SKIP_PWLIB_PIPE_BUG_WORKAROUND
+ _timerChangePipe[0] = timerChangePipe[0];
+ _timerChangePipe[1] = timerChangePipe[1];
+#endif
+}
+
+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)
+{
+ cause = -1;
+ sessionId = 0;
+ bridging = FALSE;
+ progressSetup = progressAlert = 0;
+ dtmfMode = 0;
+ dtmfCodec = (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;
+ dtmfCodec = (RTP_DataFrame::PayloadTypes)opts->dtmfcodec;
+ dtmfMode = opts->dtmfmode;
+
+ if (isIncoming) {
+ fastStartState = (opts->fastStart ? FastStartInitiate : FastStartDisabled);
+ h245Tunneling = (opts->h245Tunneling ? TRUE : FALSE);
+ } else {
+ sourceE164 = PString(opts->cid_num);
+ SetLocalPartyName(PString(opts->cid_name));
+ 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(sourceE164, (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)
+{
+ if (dtmfMode == H323_DTMF_RFC2833) {
+ 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;
+// on_set_rfc2833_payload(GetCallReference(), (const char *)GetCallToken(), (int)dtmfCodec);
+#ifdef PTRACING
+ if (h323debug) {
+ cout << "\t-- Transmitting 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;
+ };
+ 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 },
+#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;
+
+ if (!H323Connection::OnReceivedCapabilitySet(remoteCaps, muxCap, reject)) {
+ return FALSE;
+ }
+
+ const H323Capability * cap = remoteCaps.FindCapability(H323_UserInputCapability::SubTypeNames[H323_UserInputCapability::SignalToneRFC2833]);
+ if (cap != NULL) {
+ RTP_DataFrame::PayloadTypes pt = ((H323_UserInputCapability*)cap)->GetPayloadType();
+ on_set_rfc2833_payload(GetCallReference(), (const char *)GetCallToken(), (int)pt);
+ if ((dtmfMode == H323_DTMF_RFC2833) && (sendUserInputMode == SendUserInputAsTone))
+ sendUserInputMode = SendUserInputAsInlineRFC2833;
+#ifdef PTRACING
+ if (h323debug) {
+ cout << "\t-- Inbound RFC2833 on payload " << pt << endl;
+ }
+#endif
+ }
+ 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) {
+ 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;
+#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 cap, 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;
+
+ localCapabilities.RemoveAll();
+
+ if (h323debug) {
+ cout << "Setting capabilities to " << ast_getformatname_multiple(caps_str, sizeof(caps_str), cap) << 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 if (y == AST_FORMAT_MAX_AUDIO)
+ break;
+ else
+ y <<= 1;
+ codec = y;
+ }
+ if (!(cap & 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;
+ default:
+ alreadysent &= ~codec;
+ break;
+ }
+ }
+
+ lastcap++;
+ lastcap = localCapabilities.SetCapability(0, lastcap, new H323_UserInputCapability(H323_UserInputCapability::HookFlashH245));
+
+ lastcap++;
+ dtmfMode = dtmf_mode;
+ if (dtmf_mode == H323_DTMF_INBAND) {
+ localCapabilities.SetCapability(0, lastcap, new H323_UserInputCapability(H323_UserInputCapability::BasicString));
+ sendUserInputMode = SendUserInputAsString;
+ } else {
+ lastcap = localCapabilities.SetCapability(0, lastcap, new H323_UserInputCapability(H323_UserInputCapability::SignalToneRFC2833));
+ /* Cisco sends DTMF only through h245-alphanumeric or h245-signal, no support for RFC2833 */
+ lastcap = localCapabilities.SetCapability(0, lastcap, new H323_UserInputCapability(H323_UserInputCapability::SignalToneH245));
+ sendUserInputMode = SendUserInputAsTone; /* RFC2833 transmission handled at Asterisk level */
+ }
+
+ if (h323debug) {
+ cout << "Allowed Codecs:\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;
+}
+
+/* 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;
+}
+
+
+/** 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)
+{
+ 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;
+}
+
+/**
+ * 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;
+
+}
+
+#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;
+#ifndef SKIP_PWLIB_PIPE_BUG_WORKAROUND
+ close(_timerChangePipe[0]);
+ close(_timerChangePipe[1]);
+#endif
+ }
+ if (logstream) {
+ PTrace::SetLevel(0);
+ PTrace::SetStream(&cout);
+ delete logstream;
+ logstream = NULL;
+ }
+}
+
+} /* extern "C" */
+
diff --git a/1.4.23-rc4/channels/h323/ast_h323.h b/1.4.23-rc4/channels/h323/ast_h323.h
new file mode 100644
index 000000000..c4f24c529
--- /dev/null
+++ b/1.4.23-rc4/channels/h323/ast_h323.h
@@ -0,0 +1,166 @@
+/*
+ * 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
+
+ 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 progressSetup;
+ unsigned progressAlert;
+ int cause;
+
+ RTP_DataFrame::PayloadTypes dtmfCodec;
+ 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();
+};
+
+#include "compat_h323.h"
+
+#endif /* !defined AST_H323_H */
diff --git a/1.4.23-rc4/channels/h323/caps_h323.cxx b/1.4.23-rc4/channels/h323/caps_h323.cxx
new file mode 100644
index 000000000..a420825a3
--- /dev/null
+++ b/1.4.23-rc4/channels/h323/caps_h323.cxx
@@ -0,0 +1,239 @@
+#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);
+
+/*
+ * 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;
+}
diff --git a/1.4.23-rc4/channels/h323/caps_h323.h b/1.4.23-rc4/channels/h323/caps_h323.h
new file mode 100644
index 000000000..be63e0230
--- /dev/null
+++ b/1.4.23-rc4/channels/h323/caps_h323.h
@@ -0,0 +1,124 @@
+#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;
+};
+#endif /* __AST_H323CAPS_H */
diff --git a/1.4.23-rc4/channels/h323/chan_h323.h b/1.4.23-rc4/channels/h323/chan_h323.h
new file mode 100644
index 000000000..0fd94561f
--- /dev/null
+++ b/1.4.23-rc4/channels/h323/chan_h323.h
@@ -0,0 +1,254 @@
+/*
+ * 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)
+
+/** 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;
+ int dtmfmode;
+ int capability;
+ int bridge;
+ int nat;
+ int tunnelOptions;
+ 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);
+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;
+
+/* debug flag */
+extern int h323debug;
+
+#define H323_DTMF_RFC2833 (1 << 0)
+#define H323_DTMF_INBAND (1 << 1)
+
+#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);
+ 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[]);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/1.4.23-rc4/channels/h323/cisco-h225.asn b/1.4.23-rc4/channels/h323/cisco-h225.asn
new file mode 100644
index 000000000..1372e67d5
--- /dev/null
+++ b/1.4.23-rc4/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/1.4.23-rc4/channels/h323/cisco-h225.cxx b/1.4.23-rc4/channels/h323/cisco-h225.cxx
new file mode 100644
index 000000000..37adc4e87
--- /dev/null
+++ b/1.4.23-rc4/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/1.4.23-rc4/channels/h323/cisco-h225.h b/1.4.23-rc4/channels/h323/cisco-h225.h
new file mode 100644
index 000000000..7595b4b65
--- /dev/null
+++ b/1.4.23-rc4/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/1.4.23-rc4/channels/h323/compat_h323.cxx b/1.4.23-rc4/channels/h323/compat_h323.cxx
new file mode 100644
index 000000000..eec7361b2
--- /dev/null
+++ b/1.4.23-rc4/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/1.4.23-rc4/channels/h323/compat_h323.h b/1.4.23-rc4/channels/h323/compat_h323.h
new file mode 100644
index 000000000..63da8ac8c
--- /dev/null
+++ b/1.4.23-rc4/channels/h323/compat_h323.h
@@ -0,0 +1,80 @@
+#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)
+
+#endif /* !defined AST_H323_H */
diff --git a/1.4.23-rc4/channels/h323/noexport.map b/1.4.23-rc4/channels/h323/noexport.map
new file mode 100644
index 000000000..b51f84263
--- /dev/null
+++ b/1.4.23-rc4/channels/h323/noexport.map
@@ -0,0 +1,5 @@
+{
+ global:
+ _Z11PAssertFuncPKc;
+ local: *;
+}; \ No newline at end of file
diff --git a/1.4.23-rc4/channels/iax2-parser.c b/1.4.23-rc4/channels/iax2-parser.c
new file mode 100644
index 000000000..67bffa897
--- /dev/null
+++ b/1.4.23-rc4/channels/iax2-parser.c
@@ -0,0 +1,1053 @@
+/*
+ * 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/types.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "asterisk/frame.h"
+#include "asterisk/utils.h"
+#include "asterisk/unaligned.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, frame_cache_init, 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_frame_list, iax_frame);
+
+struct iax_frames {
+ struct iax_frame_list list;
+ size_t size;
+};
+
+#define FRAME_CACHE_MAX_SIZE 20
+#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 {
+ snprintf(output, maxlen, "Invalid Address");
+ }
+}
+
+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 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;
+ 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 },
+};
+
+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];
+ 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;
+ 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;
+
+ /* 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->list, fr, list) {
+ if (fr->afdatalen >= datalen) {
+ size_t afdatalen = fr->afdatalen;
+ AST_LIST_REMOVE_CURRENT(&iax_frames->list, list);
+ iax_frames->size--;
+ 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;
+#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)))) {
+ free(fr);
+ return;
+ }
+
+ if (iax_frames->size < FRAME_CACHE_MAX_SIZE) {
+ fr->direction = 0;
+ AST_LIST_INSERT_HEAD(&iax_frames->list, fr, list);
+ iax_frames->size++;
+ return;
+ }
+#endif
+ free(fr);
+}
+
+#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, list)))
+ free(cur);
+
+ 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/1.4.23-rc4/channels/iax2-parser.h b/1.4.23-rc4/channels/iax2-parser.h
new file mode 100644
index 000000000..0f3e18c00
--- /dev/null
+++ b/1.4.23-rc4/channels/iax2-parser.h
@@ -0,0 +1,160 @@
+/*
+ * 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;
+};
+
+#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/1.4.23-rc4/channels/iax2-provision.c b/1.4.23-rc4/channels/iax2-provision.c
new file mode 100644
index 000000000..b6137a88b
--- /dev/null
+++ b/1.4.23-rc4/channels/iax2-provision.c
@@ -0,0 +1,540 @@
+/*
+ * 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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#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/logger.h"
+#include "asterisk/cli.h"
+#include "asterisk/lock.h"
+#include "asterisk/frame.h"
+#include "asterisk/options.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"
+
+#ifndef IPTOS_MINCOST
+#define IPTOS_MINCOST 0x02
+#endif
+
+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);
+
+ ast_mutex_lock(&provlock);
+ for (c = templates; c; c = c->next) {
+ if (!strncasecmp(word, c->name, wordlen) && ++which > state) {
+ ret = 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_log(LOG_DEBUG, "Unable to create provisioning packet for '%s'\n", template);
+ } else
+ ret = -1;
+ } else if (option_debug)
+ ast_log(LOG_DEBUG, "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, see doc/ip-tos.txt for more information.\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 = malloc(sizeof(struct iax_template));
+ if (!cur) {
+ ast_log(LOG_WARNING, "Out of memory!\n");
+ return -1;
+ }
+ /* Initialize entry */
+ memset(cur, 0, sizeof(*cur));
+ 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 char show_provisioning_usage[] =
+"Usage: iax list provisioning [template]\n"
+" Lists all known IAX provisioning templates or a\n"
+" specific one if specified.\n";
+
+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 int iax_show_provisioning(int fd, int argc, char *argv[])
+{
+ 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;
+ if ((argc != 3) && (argc != 4))
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&provlock);
+ for (cur = templates;cur;cur = cur->next) {
+ if ((argc == 3) || (!strcasecmp(argv[3], cur->name))) {
+ if (found)
+ ast_cli(fd, "\n");
+ ast_copy_string(server, iax_server(cur->server), sizeof(server));
+ ast_copy_string(alternate, iax_server(cur->altserver), sizeof(alternate));
+ ast_cli(fd, "== %s ==\n", cur->name);
+ ast_cli(fd, "Base Templ: %s\n", strlen(cur->src) ? cur->src : "<none>");
+ ast_cli(fd, "Username: %s\n", ifthere(cur->user));
+ ast_cli(fd, "Secret: %s\n", ifthere(cur->pass));
+ ast_cli(fd, "Language: %s\n", ifthere(cur->lang));
+ ast_cli(fd, "Bind Port: %d\n", cur->port);
+ ast_cli(fd, "Server: %s\n", server);
+ ast_cli(fd, "Server Port: %d\n", cur->serverport);
+ ast_cli(fd, "Alternate: %s\n", alternate);
+ ast_cli(fd, "Flags: %s\n", iax_provflags2str(flags, sizeof(flags), cur->flags));
+ ast_cli(fd, "Format: %s\n", ast_getformatname(cur->format));
+ ast_cli(fd, "TOS: 0x%x\n", cur->tos);
+ found++;
+ }
+ }
+ ast_mutex_unlock(&provlock);
+ if (!found) {
+ if (argc == 3)
+ ast_cli(fd, "No provisioning templates found\n");
+ else
+ ast_cli(fd, "No provisioning template matching '%s' found\n", argv[3]);
+ }
+ return RESULT_SUCCESS;
+}
+
+static struct ast_cli_entry cli_iax2_provision[] = {
+ { { "iax2", "show", "provisioning", NULL },
+ iax_show_provisioning, "Display iax provisioning",
+ show_provisioning_usage, iax_prov_complete_template, },
+};
+
+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(void)
+{
+ struct ast_config *cfg;
+ struct iax_template *cur, *prev, *next;
+ char *cat;
+ int found = 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");
+ if (cfg) {
+ /* Load as appropriate */
+ cat = ast_category_browse(cfg, NULL);
+ while(cat) {
+ if (strcasecmp(cat, "general")) {
+ iax_process_template(cfg, cat, found ? "default" : NULL);
+ found++;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Loaded provisioning template '%s'\n", cat);
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+ ast_config_destroy(cfg);
+ } 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;
+ 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/1.4.23-rc4/channels/iax2-provision.h b/1.4.23-rc4/channels/iax2-provision.h
new file mode 100644
index 000000000..d95150253
--- /dev/null
+++ b/1.4.23-rc4/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(void);
+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/1.4.23-rc4/channels/iax2.h b/1.4.23-rc4/channels/iax2.h
new file mode 100644
index 000000000..960dec8bb
--- /dev/null
+++ b/1.4.23-rc4/channels/iax2.h
@@ -0,0 +1,237 @@
+/*
+ * 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
+ */
+
+#ifndef _IAX2_H
+#define _IAX2_H
+
+/* Max version of IAX protocol we support */
+#define IAX_PROTO_VERSION 2
+
+/* NOTE: IT IS CRITICAL THAT IAX_MAX_CALLS BE A POWER OF 2. */
+#if defined(LOW_MEMORY)
+#define IAX_MAX_CALLS 2048
+#else
+#define IAX_MAX_CALLS 32768
+#endif
+
+#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 */
+#define IAX_COMMAND_NEW 1
+#define IAX_COMMAND_PING 2
+#define IAX_COMMAND_PONG 3
+#define IAX_COMMAND_ACK 4
+#define IAX_COMMAND_HANGUP 5
+#define IAX_COMMAND_REJECT 6
+#define IAX_COMMAND_ACCEPT 7
+#define IAX_COMMAND_AUTHREQ 8
+#define IAX_COMMAND_AUTHREP 9
+#define IAX_COMMAND_INVAL 10
+#define IAX_COMMAND_LAGRQ 11
+#define IAX_COMMAND_LAGRP 12
+#define IAX_COMMAND_REGREQ 13 /* Registration request */
+#define IAX_COMMAND_REGAUTH 14 /* Registration authentication required */
+#define IAX_COMMAND_REGACK 15 /* Registration accepted */
+#define IAX_COMMAND_REGREJ 16 /* Registration rejected */
+#define IAX_COMMAND_REGREL 17 /* Force release of registration */
+#define IAX_COMMAND_VNAK 18 /* If we receive voice before valid first voice frame, send this */
+#define IAX_COMMAND_DPREQ 19 /* Request status of a dialplan entry */
+#define IAX_COMMAND_DPREP 20 /* Request status of a dialplan entry */
+#define IAX_COMMAND_DIAL 21 /* Request a dial on channel brought up TBD */
+#define IAX_COMMAND_TXREQ 22 /* Transfer Request */
+#define IAX_COMMAND_TXCNT 23 /* Transfer Connect */
+#define IAX_COMMAND_TXACC 24 /* Transfer Accepted */
+#define IAX_COMMAND_TXREADY 25 /* Transfer ready */
+#define IAX_COMMAND_TXREL 26 /* Transfer release */
+#define IAX_COMMAND_TXREJ 27 /* Transfer reject */
+#define IAX_COMMAND_QUELCH 28 /* Stop audio/video transmission */
+#define IAX_COMMAND_UNQUELCH 29 /* Resume audio/video transmission */
+#define IAX_COMMAND_POKE 30 /* Like ping, but does not require an open connection */
+#define IAX_COMMAND_PAGE 31 /* Paging description */
+#define IAX_COMMAND_MWI 32 /* Stand-alone message waiting indicator */
+#define IAX_COMMAND_UNSUPPORT 33 /* Unsupported message received */
+#define IAX_COMMAND_TRANSFER 34 /* Request remote transfer */
+#define IAX_COMMAND_PROVISION 35 /* Provision device */
+#define IAX_COMMAND_FWDOWNL 36 /* Download firmware */
+#define IAX_COMMAND_FWDATA 37 /* Firmware Data */
+#define IAX_COMMAND_TXMEDIA 38 /* Transfer media only */
+
+#define IAX_DEFAULT_REG_EXPIRE 60 /* By default require re-registration once per minute */
+
+#define IAX_LINGER_TIMEOUT 10 /* How long to wait before closing bridged call */
+
+#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_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/1.4.23-rc4/channels/misdn/Makefile b/1.4.23-rc4/channels/misdn/Makefile
new file mode 100644
index 000000000..e277636e6
--- /dev/null
+++ b/1.4.23-rc4/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 *.i
diff --git a/1.4.23-rc4/channels/misdn/chan_misdn_config.h b/1.4.23-rc4/channels/misdn/chan_misdn_config.h
new file mode 100644
index 000000000..f675704c0
--- /dev/null
+++ b/1.4.23-rc4/channels/misdn/chan_misdn_config.h
@@ -0,0 +1,152 @@
+/*
+ * 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
+ */
+
+
+
+#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);
+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
+
+#endif
diff --git a/1.4.23-rc4/channels/misdn/ie.c b/1.4.23-rc4/channels/misdn/ie.c
new file mode 100644
index 000000000..2e7fae998
--- /dev/null
+++ b/1.4.23-rc4/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
+ */
+
+/*
+ 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>
+
+
+
+#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 tm *tm;
+
+ tm = localtime(&ti);
+ if (!tm)
+ {
+ printf("%s: ERROR: gettimeofday() returned NULL.\n", __FUNCTION__);
+ return;
+ }
+
+ 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/1.4.23-rc4/channels/misdn/isdn_lib.c b/1.4.23-rc4/channels/misdn/isdn_lib.c
new file mode 100644
index 000000000..b21c794e7
--- /dev/null
+++ b/1.4.23-rc4/channels/misdn/isdn_lib.c
@@ -0,0 +1,4661 @@
+/*
+ * 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 <sys/time.h>
+#include <mISDNuser/isdn_debug.h>
+
+#include "isdn_lib_intern.h"
+#include "isdn_lib.h"
+
+/*
+ * Define ARRAY_LEN() because I cannot
+ * #include "asterisk/utils.h"
+ */
+#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
+
+#include "asterisk/causes.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;
+}
+
+static 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;
+ 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) {
+ 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 {
+ /*! \brief mISDN device handle returned by mISDN_open() */
+ int midev;
+ int midev_nt; /* Not used */
+
+ 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 */
+ /* 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 (!bc->early_bconnect) {
+ /* We have opted to never receive any available inband recorded messages */
+ 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 D channel ;) 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 D channel ;) 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 = AST_CAUSE_NORMAL_CIRCUIT_CONGESTION;
+ 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 = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
+ 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->orig=0;
+
+ bc->cause = AST_CAUSE_NORMAL_CLEARING;
+ bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
+ bc->pres = 0; /* allowed */
+
+ 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;
+
+ gettimeofday(&bc->last_used, NULL);
+}
+
+
+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 new_te_id = 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 proc_id;
+ struct misdn_stack *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 (proc_id = 0; proc_id < MAXPROCS; ++proc_id) {
+ if (stack->procids[proc_id] == 0) {
+ break;
+ }
+ } /* end for */
+ if (proc_id == MAXPROCS) {
+ cb_log(0, stack->port, "Couldn't Create New ProcId.\n");
+ return -1;
+ }
+
+ stack->procids[proc_id] = 1;
+
+ l3_id = 0xff00 | proc_id;
+ 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 (++new_te_id > 0xffff) {
+ new_te_id = 0x0001;
+ }
+
+ l3_id = (entity << 16) | new_te_id;
+ bc->l3_id = l3_id;
+ cb_log(3, stack->port, "--> new_l3id %x\n", l3_id);
+
+ /* send message */
+ ncr.prim = CC_NEW_CR | REQUEST;
+ ncr.addr = (stack->upper_id | FLG_MSG_DOWN);
+ ncr.dinfo = l3_id;
+ ncr.len = 0;
+ 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;
+ int channel;
+ int b_stid;
+ int 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 setup 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 already 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 **/
+static 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));
+ if (!bc->send_lock) {
+ return -1;
+ }
+ 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));
+ if (!ibuf->rsem) {
+ return -1;
+ }
+
+ 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;
+}
+
+
+
+static 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;
+}
+
+
+static 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;
+ }
+
+ if (!bc->channel)
+ cb_log(0, stack->port, "Any Channel Requested, but we have no more!!\n");
+ else
+ cb_log(0, stack->port, "Requested Channel Already in Use releasing this call with cause 34!!!!\n");
+
+ /* when the channel is already in use, we can't
+ * simply clear it, we need to make sure that
+ * it will still be marked as in_use in the
+ * available channels list.*/
+ bc->channel=0;
+
+ 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 find BC so temporarily 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, "REMOVING 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;
+}
+
+
+/* Empties 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 Release_cr for %x l3id:%x\n",frm.addr, frm.dinfo);
+ /** removing procid **/
+ if (!bc) {
+ cb_log(4, stack->port, " --> Didn't find BC so temporarily 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, "Couldn't find BC so I couldn't 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;
+ struct misdn_bchannel *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, "REMOVING Holder\n");
+
+ /*swap the backup to our new channel back*/
+ stack_holder_remove(stack, hold_bc);
+ memcpy(bc, hold_bc, sizeof(struct misdn_bchannel));
+ 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->l1link = 1;
+ 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 Attempts!!!\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 find BC so temporarily 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 AST_CAUSE_USER_BUSY:
+ 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;
+ }
+
+ if (!bc && (frm->prim==(CC_SETUP|INDICATION)) ) {
+ misdn_make_dummy(&dummybc, stack->port, MISDN_ID_GLOBAL, stack->nt, 0);
+ dummybc.port=stack->port;
+ dummybc.l3_id=frm->dinfo;
+ bc=&dummybc;
+
+ misdn_lib_send_event(bc,EVENT_RELEASE_COMPLETE);
+
+ free_msg(msg);
+ return 1;
+ }
+
+
+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, "TOTALLY IGNORING SETUP\n");
+
+ break;
+ case RESPONSE_IGNORE_SETUP:
+ /* I think we should send CC_RELEASE_CR, but am not sure*/
+ bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
+
+ 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 announced 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 == AST_CAUSE_REQUESTED_CHAN_UNAVAIL) {
+ cb_log(0,stack->port,"**** Received CAUSE:%d, so not cleaning up channel %d\n", AST_CAUSE_REQUESTED_CHAN_UNAVAIL, 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;
+ cb_log(0, stack->port, " --> Didn't find BC so temporarily creating dummy BC (l3id:%x) on this port.\n", frm->dinfo);
+ 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, "Firing 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");
+
+ /*when the L2 goes UP, L1 needs to be UP too*/
+ stack->l1link=1;
+ 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, cause usually 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);
+ }
+ }
+}
+
+/* This is a thread */
+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 these 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, "Entity 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 int test_inuse(struct misdn_bchannel *bc)
+{
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ if (!bc->in_use) {
+ if (misdn_lib_port_is_pri(bc->port) && bc->last_used.tv_sec == now.tv_sec ) {
+ cb_log(2,bc->port, "channel with stid:%x for one second still in use! (n:%d lu:%d)\n", bc->b_stid, (int) now.tv_sec, (int) bc->last_used.tv_sec);
+ return 1;
+ }
+
+
+ cb_log(3,bc->port, "channel with stid:%x not in use!\n", bc->b_stid);
+ return 0;
+ }
+
+ cb_log(2,bc->port, "channel with stid:%x in use!\n", bc->b_stid);
+ return 1;
+}
+
+
+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 = AST_CAUSE_NORMAL_CLEARING;
+
+ 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;
+ }
+
+ usleep(1000);
+
+ 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].channel == channel) {
+ if (test_inuse(&stack->bc[i])) {
+ cb_log(0,port,"Requested channel:%d on port:%d is already in use\n",channel, port);
+ return NULL;
+
+ } else {
+ prepare_bc(&stack->bc[i], channel);
+ return &stack->bc[i];
+ }
+ }
+ }
+ } 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 (!test_inuse(&stack->bc[i])) {
+ /* 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 (!test_inuse(&stack->bc[i])) {
+ /* 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;
+}
+
+
+
+
+/* ******************************************************************* */
+/*!
+ * \internal
+ * \brief Convert the facility function enum value into a string.
+ *
+ * \return String version of the enum value
+ */
+static const char *fac2str(enum FacFunction facility)
+{
+ static const struct {
+ enum FacFunction facility;
+ char *name;
+ } arr[] = {
+/* *INDENT-OFF* */
+ { Fac_None, "Fac_None" },
+ { Fac_GetSupportedServices, "Fac_GetSupportedServices" },
+ { Fac_Listen, "Fac_Listen" },
+ { Fac_Suspend, "Fac_Suspend" },
+ { Fac_Resume, "Fac_Resume" },
+ { Fac_CFActivate, "Fac_CFActivate" },
+ { Fac_CFDeactivate, "Fac_CFDeactivate" },
+ { Fac_CFInterrogateParameters, "Fac_CFInterrogateParameters" },
+ { Fac_CFInterrogateNumbers, "Fac_CFInterrogateNumbers" },
+ { Fac_CD, "Fac_CD" },
+ { Fac_AOCDCurrency, "Fac_AOCDCurrency" },
+ { Fac_AOCDChargingUnit, "Fac_AOCDChargingUnit" },
+/* *INDENT-ON* */
+ };
+
+ unsigned index;
+
+ for (index = 0; index < ARRAY_LEN(arr); ++index) {
+ if (arr[index].facility == facility) {
+ return arr[index].name;
+ }
+ } /* end for */
+
+ return "unknown";
+} /* end fac2str() */
+
+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:%p h:%d sh:%d\n", bc, bc->holded, bc->stack_holder);
+}
+
+
+#define RETURN(a,b) {retval=a; goto b;}
+
+static 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);
+}
+
+static 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->upperid:%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 cleanup 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) {
+ static int unhandled_bmsg_count=1000;
+ if (handle_bchan(msg)) {
+ return 0 ;
+ }
+
+ if (unhandled_bmsg_count==1000) {
+ cb_log(0, 0, "received 1k Unhandled Bchannel Messages: prim %x len %d from addr %x, dinfo %x on this port.\n",frm->prim, frm->len, frm->addr, frm->dinfo);
+ unhandled_bmsg_count=0;
+ }
+
+ unhandled_bmsg_count++;
+ free_msg(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, "misdn_lib_get_port_info: 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, "queue_cleanup_bc: 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 bchannel*/
+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;
+
+/* This is a thread */
+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 :
+ /* Warning: memory leak here if we get this message */
+ 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) {
+ struct misdn_bchannel dummybc;
+ misdn_make_dummy(&dummybc, stack->port, MISDN_ID_GLOBAL, stack->nt, 0);
+ send_msg(glob_mgr->midev, &dummybc, msg);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+int misdn_lib_maxports_get(void)
+{
+ /* 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 )
+{
+ static int 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("stack_init");
+ 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(void)
+{
+ 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;
+ 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 %lx\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/1.4.23-rc4/channels/misdn/isdn_lib.h b/1.4.23-rc4/channels/misdn/isdn_lib.h
new file mode 100644
index 000000000..451876888
--- /dev/null
+++ b/1.4.23-rc4/channels/misdn/isdn_lib.h
@@ -0,0 +1,674 @@
+/*
+ * 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 {
+ /*! \brief B channel send locking structure */
+ struct send_lock *send_lock;
+
+ /*! \brief TRUE if this is a dummy BC record */
+ int dummy;
+
+ /*! \brief TRUE if NT side of protocol (TE otherwise) */
+ int nt;
+
+ /*! \brief TRUE if ISDN-PRI (ISDN-BRI otherwise) */
+ int pri;
+
+ /*! \brief Logical Layer 1 port associated with this B channel */
+ int port;
+
+ /** init stuff **/
+ /*! \brief B Channel mISDN driver stack ID */
+ int b_stid;
+
+ /* int b_addr; */
+
+ /*! \brief B Channel mISDN driver layer ID from mISDN_new_layer() */
+ int layer_id;
+
+ /*! \brief B channel layer; set to 3 or 4 */
+ int layer;
+
+ /* state stuff */
+ /*! \brief TRUE if DISCONNECT needs to be sent to clear a call */
+ int need_disconnect;
+
+ /*! \brief TRUE if RELEASE needs to be sent to clear a call */
+ int need_release;
+
+ /*! \brief TRUE if RELEASE_COMPLETE needs to be sent to clear a call */
+ int need_release_complete;
+
+ /*! \brief TRUE if allocate higher B channels first */
+ int dec;
+
+ /* var stuff */
+ /*! \brief Layer 3 process ID */
+ int l3_id;
+
+ /*! \brief B channel process ID (1-5000) */
+ int pid;
+
+ /*! \brief Not used. Saved mISDN stack CONNECT_t ces value */
+ int ces;
+
+ /*! \brief B channel to restart if received a RESTART message */
+ int restart_channel;
+
+ /*! \brief Assigned B channel number B1, B2... 0 if not assigned */
+ int channel;
+
+ /*! \brief TRUE if the B channel number is preselected */
+ int channel_preselected;
+
+ /*! \brief TRUE if B channel record is in use */
+ int in_use;
+
+ /*! \brief Time when empty_bc() last called on this record */
+ struct timeval last_used;
+
+ /*! \brief TRUE if call waiting */
+ int cw;
+
+ /*! \brief B Channel mISDN driver layer ID from mISDN_get_layerid() */
+ int addr;
+
+ /*! \brief B channel speech sample data buffer */
+ char *bframe;
+
+ /*! \brief B channel speech sample data buffer size */
+ int bframe_len;
+ int time_usec; /* Not used */
+
+ /*! \brief Not used. Contents are setup but not used. */
+ void *astbuf;
+
+ void *misdnbuf; /* Not used */
+
+ /*! \brief TRUE if the TE side should choose the B channel to use
+ * \note This value is user configurable in /etc/asterisk/misdn.conf
+ */
+ int te_choose_channel;
+
+ /*! \brief TRUE if the call progress indicators can indicate an inband audio message for the user to listen to
+ * \note This value is user configurable in /etc/asterisk/misdn.conf
+ */
+ int early_bconnect;
+
+ /*! \brief Last decoded DTMF digit from mISDN driver */
+ int dtmf;
+
+ /*! \brief TRUE if we should produce DTMF tones ourselves
+ * \note This value is user configurable in /etc/asterisk/misdn.conf
+ */
+ int send_dtmf;
+
+ /*! \brief TRUE if we send SETUP_ACKNOWLEDGE on incoming calls anyway (instead of PROCEEDING).
+ *
+ * This requests additional INFORMATION messages, so we can
+ * wait for digits without issues.
+ * \note This value is user configurable in /etc/asterisk/misdn.conf
+ */
+ int need_more_infos;
+
+ /*! \brief TRUE if all digits necessary to complete the call are available.
+ * No more INFORMATION messages are needed.
+ */
+ int sending_complete;
+
+
+ /*! \brief TRUE if we will not use jollys dsp */
+ int nodsp;
+
+ /*! \brief TRUE if we will not use the jitter buffer system */
+ int nojitter;
+
+ /*! \brief Type-of-number in ISDN terms for the dialed/called number
+ * \note This value is set to "dialplan" in /etc/asterisk/misdn.conf for outgoing calls
+ */
+ enum mISDN_NUMBER_PLAN dnumplan;
+
+ /*! \brief Type-of-number in ISDN terms for the redirecting number which a call diversion or transfer was invoked.
+ * \note Collected from the incoming SETUP message but not used.
+ */
+ enum mISDN_NUMBER_PLAN rnumplan;
+
+ /*! \brief Type-of-number in ISDN terms for the originating/calling number (Caller-ID)
+ * \note This value is set to "localdialplan" in /etc/asterisk/misdn.conf for outgoing calls
+ */
+ enum mISDN_NUMBER_PLAN onumplan;
+
+ /*! \brief Type-of-number in ISDN terms for the connected party number
+ * \note This value is set to "cpndialplan" in /etc/asterisk/misdn.conf for outgoing calls
+ */
+ enum mISDN_NUMBER_PLAN cpnnumplan;
+
+ /*! \brief Progress Indicator IE coding standard field.
+ * \note Collected from the incoming messages but not used.
+ */
+ int progress_coding;
+
+ /*! \brief Progress Indicator IE location field.
+ * \note Collected from the incoming messages but not used.
+ */
+ int progress_location;
+
+ /*! \brief Progress Indicator IE progress description field.
+ * Used to determine if there is an inband audio message present.
+ */
+ int progress_indicator;
+
+ /*! \brief Inbound FACILITY message function type and contents */
+ struct FacParm fac_in;
+
+ /*! \brief Outbound FACILITY message function type and contents.
+ * \note Filled in by misdn facility commands before FACILITY message sent.
+ */
+ struct FacParm fac_out;
+
+ /* storing the current AOCD info here */
+ enum FacFunction AOCDtype;
+ union {
+ struct FacAOCDCurrency currency;
+ struct FacAOCDChargingUnit chargingUnit;
+ } AOCD;
+
+ /*! \brief Event waiting for Layer 1 to come up */
+ enum event_e evq;
+
+ /*** CRYPTING STUFF ***/
+ int crypt; /* Initialized, Not used */
+ int curprx; /* Initialized, Not used */
+ int curptx; /* Initialized, Not used */
+
+ /*! \brief Blowfish encryption key string (secret) */
+ char crypt_key[255];
+
+ int crypt_state; /* Not used */
+ /*** CRYPTING STUFF END***/
+
+ /*! \brief Seems to have been intended for something to do with the jitter buffer.
+ * \note Used as a boolean. Only initialized to 0 and referenced in a couple places
+ */
+ int active;
+ int upset; /* Not used */
+
+ /*! \brief TRUE if tone generator allowed to start */
+ int generate_tone;
+
+ /*! \brief Number of tone samples to generate */
+ int tone_cnt;
+
+ /*! \brief Current B Channel state */
+ enum bchannel_state bc_state;
+
+ /*! \brief This is used as a pending bridge join request for when bc_state becomes BCHAN_ACTIVATED */
+ enum bchannel_state next_bc_state;
+
+ /*! \brief Bridging conference ID */
+ int conf_id;
+
+ /*! \brief TRUE if this channel is on hold */
+ int holded;
+
+ /*! \brief TRUE if this channel is on the misdn_stack->holding list
+ * \note If TRUE this implies that the structure is also malloced.
+ */
+ int stack_holder;
+
+ /*! \brief Caller ID presentation restriction code
+ * 0=Allowed, 1=Restricted, 2=Unavailable
+ * \note It is settable by the misdn_set_opt() application.
+ */
+ int pres;
+
+ /*! \brief Caller ID screening code
+ * 0=Unscreened, 1=Passed Screen, 2=Failed Screen, 3=Network Number
+ */
+ int screen;
+
+ /*! \brief SETUP message bearer capability field code value */
+ int capability;
+
+ /*! \brief Companding ALaw/uLaw encoding (INFO_CODEC_ALAW / INFO_CODEC_ULAW) */
+ int law;
+
+ /* V110 Stuff */
+ /*! \brief Q.931 Bearer Capability IE Information Transfer Rate field. Initialized to 0x10 (64kbit). Altered by incoming SETUP messages. */
+ int rate;
+
+ /*! \brief Q.931 Bearer Capability IE Transfer Mode field. Initialized to 0 (Circuit). Altered by incoming SETUP messages. */
+ int mode;
+
+ /*! \brief Q.931 Bearer Capability IE User Information Layer 1 Protocol field code.
+ * \note Collected from the incoming SETUP message but not used.
+ */
+ int user1;
+
+ /*! \brief Q.931 Bearer Capability IE Layer 1 User Rate field.
+ * \note Collected from the incoming SETUP message and exported to Asterisk variable MISDN_URATE.
+ */
+ int urate;
+
+ /*! \brief TRUE if call made in digital HDLC mode
+ * \note This value is user configurable in /etc/asterisk/misdn.conf.
+ * It is also settable by the misdn_set_opt() application.
+ */
+ int hdlc;
+ /* V110 */
+
+ /*! \brief Display message that can be displayed by the user phone.
+ * \note Maximum displayable length is 34 or 82 octets.
+ * It is also settable by the misdn_set_opt() application.
+ */
+ char display[84];
+
+ /*! \brief Not used. Contents are setup but not used. */
+ char msn[32];
+
+ /*! \brief Originating/Calling Phone Number (Address)
+ * \note This value can be set to "callerid" in /etc/asterisk/misdn.conf for outgoing calls
+ */
+ char oad[32];
+
+ /*! \brief Redirecting Phone Number (Address) where a call diversion or transfer was invoked */
+ char rad[32];
+
+ /*! \brief Dialed/Called Phone Number (Address) */
+ char dad[32];
+
+ /*! \brief Connected Party/Line Phone Number (Address) */
+ char cad[32];
+
+ /*! \brief Original Dialed/Called Phone Number (Address) before national/international dialing prefix added.
+ * \note Not used. Contents are setup but not used.
+ */
+ char orig_dad[32];
+
+ /*! \brief Q.931 Keypad Facility IE contents
+ * \note Contents exported and imported to Asterisk variable MISDN_KEYPAD
+ */
+ char keypad[32];
+
+ /*! \brief Current overlap dialing digits to/from INFORMATION messages */
+ char info_dad[64];
+
+ /*! \brief Collected digits to go into info_dad[] while waiting for a SETUP_ACKNOWLEDGE to come in. */
+ char infos_pending[64];
+
+/* unsigned char info_keypad[32]; */
+/* unsigned char clisub[24]; */
+/* unsigned char cldsub[24]; */
+
+ /*! \brief User-User information string.
+ * \note Contents exported and imported to Asterisk variable MISDN_USERUSER
+ * \note We only support ASCII strings (IA5 characters).
+ */
+ char uu[256];
+
+ /*! \brief User-User information string length in uu[] */
+ int uulen;
+
+ /*! \brief Q.931 Cause for disconnection code (received)
+ * \note Need to use the AST_CAUSE_xxx code definitions in causes.h
+ */
+ int cause;
+
+ /*! \brief Q.931 Cause for disconnection code (sent)
+ * \note Need to use the AST_CAUSE_xxx code definitions in causes.h
+ * \note -1 is used to suppress including the cause code in the RELEASE message.
+ */
+ int out_cause;
+
+ /* struct misdn_bchannel hold_bc; */
+
+ /** list stuf **/
+
+#ifdef MISDN_1_2
+ /*! \brief The configuration string for the mISDN dsp pipeline in /etc/asterisk/misdn.conf. */
+ char pipeline[128];
+#else
+ /*! \brief TRUE if the echo cancellor is enabled */
+ int ec_enable;
+
+ /*! \brief Number of taps in the echo cancellor when enabled.
+ * \note This value is user configurable in /etc/asterisk/misdn.conf (echocancel)
+ */
+ int ec_deftaps;
+#endif
+
+ /*! \brief TRUE if the channel was allocated from the available B channels */
+ int channel_found;
+
+ /*! \brief Who originated the call (ORG_AST, ORG_MISDN)
+ * \note Set but not used when the misdn_set_opt() application enables echo cancellation.
+ */
+ int orig;
+
+ /*! \brief Tx gain setting (range -8 to 8)
+ * \note This value is user configurable in /etc/asterisk/misdn.conf.
+ * It is also settable by the misdn_set_opt() application.
+ */
+ int txgain;
+
+ /*! \brief Rx gain setting (range -8 to 8)
+ * \note This value is user configurable in /etc/asterisk/misdn.conf.
+ * It is also settable by the misdn_set_opt() application.
+ */
+ int rxgain;
+
+ /*! \brief Next node in the misdn_stack.holding list */
+ 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, ...)
+ __attribute__ ((format (printf, 3, 4)));
+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, ...)
+ __attribute__ ((format (printf, 3, 4)));
+ 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_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);
+
+#endif
diff --git a/1.4.23-rc4/channels/misdn/isdn_lib_intern.h b/1.4.23-rc4/channels/misdn/isdn_lib_intern.h
new file mode 100644
index 000000000..93d879744
--- /dev/null
+++ b/1.4.23-rc4/channels/misdn/isdn_lib_intern.h
@@ -0,0 +1,142 @@
+#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"
+
+#if !defined MISDNUSER_VERSION_CODE || (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; /* Not used */
+ibuffer_t *misdnbuf; /* Not used */
+
+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;
+
+ /*! \brief D Channel mISDN driver stack ID (Parent stack ID) */
+ int d_stid;
+
+ /*! /brief Number of B channels supported by this port */
+ int b_num;
+
+ /*! \brief B Channel mISDN driver stack IDs (Child stack IDs) */
+ int b_stids[MAX_BCHANS + 1];
+
+ /*! \brief TRUE if Point-To-Point(PTP) (Point-To-Multipoint(PTMP) otherwise) */
+ int ptp;
+
+ /*! \brief Number of consecutive times PTP Layer 2 declared down */
+ int l2upcnt;
+
+ int l2_id; /* Not used */
+
+ /*! \brief Lower layer mISDN ID (addr) (Layer 1/3) */
+ int lower_id;
+
+ /*! \brief Upper layer mISDN ID (addr) (Layer 2/4) */
+ int upper_id;
+
+ /*! \brief TRUE if port is blocked */
+ int blocked;
+
+ /*! \brief TRUE if Layer 2 is UP */
+ int l2link;
+
+ time_t l2establish; /* Not used */
+
+ /*! \brief TRUE if Layer 1 is UP */
+ int l1link;
+
+ /*! \brief TRUE if restart has been sent to the other side after stack startup */
+ int restart_sent;
+
+ /*! \brief mISDN device handle returned by mISDN_open() */
+ int midev;
+
+ /*! \brief TRUE if NT side of protocol (TE otherwise) */
+ int nt;
+
+ /*! \brief TRUE if ISDN-PRI (ISDN-BRI otherwise) */
+ int pri;
+
+ /*! \brief CR Process ID allocation table. TRUE if ID allocated */
+ int procids[0x100+1];
+
+ /*! \brief Queue of Event messages to send to mISDN */
+ msg_queue_t downqueue;
+ msg_queue_t upqueue; /* No code puts anything on this queue */
+ int busy; /* Not used */
+
+ /*! \brief Logical Layer 1 port associated with this stack */
+ int port;
+
+ /*! \brief B Channel record pool array */
+ struct misdn_bchannel bc[MAX_BCHANS + 1];
+
+ struct misdn_bchannel* bc_list; /* Not used */
+
+ /*! \brief Array of B channels in use (a[0] = B1). TRUE if B channel in use */
+ int channels[MAX_BCHANS + 1];
+
+ /*! \brief List of holded channels */
+ struct misdn_bchannel *holding;
+
+ /*! \brief Next stack in the list of stacks */
+ 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/1.4.23-rc4/channels/misdn/isdn_msg_parser.c b/1.4.23-rc4/channels/misdn/isdn_msg_parser.c
new file mode 100644
index 000000000..a587f8eae
--- /dev/null
+++ b/1.4.23-rc4/channels/misdn/isdn_msg_parser.c
@@ -0,0 +1,1348 @@
+/*
+ * 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
+ */
+
+
+#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);
+
+
+#if 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);
+
+
+#if 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);
+
+#if 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);
+#if 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);
+
+#if 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));
+
+#if 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);
+
+#if 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);
+ }
+
+#if 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);
+ */
+
+#if 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);
+ }
+
+#if 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);
+#if 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);
+
+#if 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)
+{
+#if 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);
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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);
+#if 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)
+{
+#if 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));
+
+#if 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);
+#if 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);
+ }
+
+#if 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);
+
+#if 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));
+
+#if 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;
+#if 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);
+ }
+
+#if 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;
+
+#if 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);
+ }
+
+#if 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;
+
+#if 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(1, 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;
+
+#if 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)
+{
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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);
+ }
+#if 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);
+ }
+ }
+
+#if 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;
+ ;
+
+#if 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));
+
+#if 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)
+{
+#if 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));
+
+#if 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/1.4.23-rc4/channels/misdn/portinfo.c b/1.4.23-rc4/channels/misdn/portinfo.c
new file mode 100644
index 000000000..bcb9f0313
--- /dev/null
+++ b/1.4.23-rc4/channels/misdn/portinfo.c
@@ -0,0 +1,198 @@
+
+
+#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/1.4.23-rc4/channels/misdn_config.c b/1.4.23-rc4/channels/misdn_config.c
new file mode 100644
index 000000000..0924ad983
--- /dev/null
+++ b/1.4.23-rc4/channels/misdn_config.c
@@ -0,0 +1,1160 @@
+/*
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "chan_misdn_config.h"
+
+#include "asterisk/config.h"
+#include "asterisk/channel.h"
+#include "asterisk/logger.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))
+
+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 list which bearer capabilities should be allowed:\n"
+ "\t all - allow any bearer capability\n"
+ "\t speech - allow speech\n"
+ "\t 3_1khz - allow 3.1KHz audio\n"
+ "\t digital_unrestricted - allow unrestricted digital\n"
+ "\t digital_restricted - allow restricted digital\n"
+ "\t video - allow video" },
+ { "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 especially 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 making 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." },
+ { "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 either value, the presentation indicators are used from\n"
+ "\tAsterisk's SetCallerPres application.\n"
+ "\n"
+ "\tscreen=0, presentation=0 -> callerid presented\n"
+ "\tscreen=1, presentation=1 -> callerid restricted (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 either value, the presentation indicators are used from\n"
+ "\tAsterisk's SetCallerPres application.\n"
+ "\n"
+ "\tscreen=0, presentation=0 -> callerid presented\n"
+ "\tscreen=1, presentation=1 -> callerid restricted (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 echo cancellation with the given number of taps.\n"
+ "\tBe aware: Move this setting only to outgoing portgroups!\n"
+ "\tA value of zero turns echo cancellation 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."},
+ { "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 variable\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"
+ "when 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." },
+ { "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 separated 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,
+ "Whether to append overlapdialed Digits to Extension or not." },
+ { "dynamic_crypt", MISDN_GEN_DYNAMIC_CRYPT, MISDN_CTYPE_BOOL, "no", NONE,
+ "Whether 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" },
+ { "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 (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)
+ free(iter->msn);
+ 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
+ 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)
+ 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) {
+ if (!memccpy(buf, port_cfg[port][place].str, 0, bufsize))
+ memset(buf, 0, 1);
+ } else if (port_cfg[0][place].str) {
+ if (!memccpy(buf, port_cfg[0][place].str, 0, bufsize))
+ memset(buf, 0, 1);
+ } else
+ memset(buf, 0, 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:
+ if (!general_cfg[place].str || !memccpy(buf, general_cfg[place].str, 0, bufsize))
+ memset(buf, 0, 1);
+ 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" element 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" element 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;
+
+ if (!spec || !memccpy(buf, spec[place].name, 0, bufsize))
+ memset(buf, 0, 1);
+}
+
+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" element with the "ports" element */
+ if (elem == MISDN_CFG_GROUPNAME) {
+ if (!memccpy(buf, ports_description, 0, bufsize))
+ memset(buf, 0, 1);
+ 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 {
+ if (!memccpy(buf, spec[place].desc, 0, bufsize))
+ memset(buf, 0, 1);
+ if (buf_default && bufsize) {
+ if (!strcmp(spec[place].def, NO_DEFAULT))
+ memset(buf_default, 0, 1);
+ else if (!memccpy(buf_default, spec[place].def, 0, bufsize_default))
+ memset(buf_default, 0, 1);
+ }
+ }
+}
+
+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;
+}
+
+/*!
+ * \brief Generate a comma separated list of all active ports
+ */
+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))) {
+ /* Strip trailing ',' */
+ 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) {
+ strncat(tempbuf, iter->msn, sizeof(tempbuf) - strlen(tempbuf) - 1);
+ }
+ 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, char *value, enum misdn_cfg_type type, int boolint_def)
+{
+ int re = 0;
+ int len, tmp;
+ char *valtmp;
+
+ switch (type) {
+ case MISDN_CTYPE_STR:
+ if ((len = strlen(value))) {
+ dest->str = (char *)malloc((len + 1) * sizeof(char));
+ strncpy(dest->str, value, len);
+ dest->str[len] = 0;
+ } else {
+ dest->str = (char *)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 = (int *)malloc(sizeof(int));
+ memcpy(dest->num, &tmp, sizeof(int));
+ } else
+ re = -1;
+ }
+ break;
+ case MISDN_CTYPE_BOOL:
+ dest->num = (int *)malloc(sizeof(int));
+ *(dest->num) = (ast_true(value) ? 1 : 0);
+ break;
+ case MISDN_CTYPE_BOOLINT:
+ dest->num = (int *)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(&value, ","); valtmp; valtmp = strsep(&value, ",")) {
+ if ((len = strlen(valtmp))) {
+ struct msn_list *ml = (struct msn_list *)malloc(sizeof(struct msn_list));
+ ml->msn = (char *)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_group_t *)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 (((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;
+ char ptpbuf[BUFFERSIZE] = "";
+ int start, end;
+ for (token = strsep(&v->value, ","); token; token = strsep(&v->value, ","), *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);
+}
+
+void misdn_cfg_destroy (void)
+{
+ misdn_cfg_lock();
+
+ _free_port_cfg();
+ _free_general_cfg();
+
+ free(port_cfg);
+ free(general_cfg);
+ free(ptp);
+ free(map);
+
+ misdn_cfg_unlock();
+ ast_mutex_destroy(&config_mutex);
+}
+
+int misdn_cfg_init (int this_max_ports)
+{
+ char config[] = "misdn.conf";
+ char *cat, *p;
+ int i;
+ struct ast_config *cfg;
+ struct ast_variable *v;
+
+ if (!(cfg = AST_LOAD_CFG(config))) {
+ ast_log(LOG_WARNING, "missing file: misdn.conf\n");
+ return -1;
+ }
+
+ ast_mutex_init(&config_mutex);
+
+ misdn_cfg_lock();
+
+ if (this_max_ports) {
+ /* this is the first run */
+ max_ports = this_max_ports;
+ map = (int *)calloc(MISDN_GEN_LAST + 1, sizeof(int));
+ if (_enum_array_map())
+ return -1;
+ p = (char *)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 = (union misdn_cfg_pt *)calloc(1, sizeof(union misdn_cfg_pt *) * NUM_GEN_ELEMENTS);
+ ptp = (int *)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;
+}
+
+
diff --git a/1.4.23-rc4/channels/ring10.h b/1.4.23-rc4/channels/ring10.h
new file mode 100644
index 000000000..f45f8dbb9
--- /dev/null
+++ b/1.4.23-rc4/channels/ring10.h
@@ -0,0 +1,1752 @@
+/*! \file
+ * \brief Signed 16-bit audio data
+ *
+ * Source: /home/markster/ring10.raw
+ *
+ * Copyright (C) 1999-2005, Digium, Inc.
+ *
+ * Distributed under the terms of the GNU General Public License
+ *
+ */
+
+static signed short ring10[] = {
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 0x82a0, 0x8dc7, 0x607e, 0xc0c6, 0x2bd3,
+0x8df5, 0x828d, 0x0716, 0xaca6, 0xcefe, 0x41df, 0xd185, 0x8aa3, 0x8b1a, 0x789b,
+0x82a2, 0x804f, 0x7ea8, 0x8113, 0x7f7d, 0x7fff, 0x801e, 0x801d, 0x7f32, 0x82ec,
+0x83e1, 0x7fb0, 0x7f71, 0x80de, 0x7f3d, 0x7fe3, 0x81b4, 0x7c37, 0x8553, 0x7b29,
+0x7ede, 0xde6e, 0x0e64, 0xf9f4, 0x015e, 0x00f6, 0xfe56, 0x0019, 0xf8bb, 0xfd90,
+0x08cc, 0x05ab, 0xfd0b, 0xf9c6, 0xf875, 0xf789, 0xfc74, 0x032e, 0xf97a, 0xf4bb,
+0x0212, 0x006e, 0x03df, 0x17c5, 0x0f50, 0xfb23, 0xfdbd, 0xf7cf, 0xdf5b, 0xe2d3,
+0xf111, 0xef27, 0x11c5, 0x33a4, 0x168d, 0x0145, 0x0494, 0xe85c, 0xdac3, 0xf0c7,
+0xeea8, 0x0023, 0x3036, 0x252a, 0xffb7, 0x01d1, 0xf637, 0xd506, 0xe8eb, 0xf5ff,
+0xe5ca, 0x1ec5, 0x3fa4, 0x0e3c, 0x1570, 0x2b37, 0xea23, 0xca43, 0xf392, 0xdf0e,
+0xde40, 0x2e7c, 0x276f, 0x035c, 0x2ccc, 0x1acf, 0xcf4a, 0xeb5b, 0x0fb1, 0xe01a,
+0x0c69, 0x3a97, 0xfb54, 0x0751, 0x20f1, 0xdce9, 0xd2a2, 0x19b3, 0x096f, 0xf1b6,
+0x38de, 0x1f70, 0xf32b, 0x2569, 0x0650, 0xc3d7, 0xf1ad, 0x1aa5, 0xe87e, 0x0c7f,
+0x406d, 0xffaa, 0x0ba8, 0x2e02, 0xe545, 0xcebb, 0x10fc, 0x0102, 0xded8, 0x2b7c,
+0x2053, 0xec6f, 0x266e, 0x1770, 0xcb63, 0xf18e, 0x2015, 0xe6ef, 0xfe64, 0x3700,
+0xf628, 0xfb00, 0x2e43, 0xee48, 0xcd4a, 0x1867, 0x0ec3, 0xdd77, 0x2291, 0x1c80,
+0xe325, 0x19b7, 0x1719, 0xcb88, 0xeded, 0x258c, 0xe7e8, 0xf0c6, 0x2d21, 0xf3d5,
+0xf494, 0x290d, 0xef7b, 0xca28, 0x12c8, 0x0d8d, 0xd5f3, 0x171d, 0x1994, 0xe0c0,
+0x1348, 0x1929, 0xcf9b, 0xe6fb, 0x20ae, 0xe921, 0xed2b, 0x2c54, 0xf96e, 0xf19f,
+0x21b6, 0xf12e, 0xc8b4, 0x0907, 0x0964, 0xd049, 0x0eb8, 0x1fa6, 0xe6b5, 0x0cec,
+0x16b6, 0xcd0c, 0xda57, 0x17c9, 0xe440, 0xe2a2, 0x2b4d, 0xffa2, 0xec7e, 0x1ee9,
+0xf674, 0xbfcb, 0xf769, 0x0402, 0xcfe8, 0x104b, 0x2734, 0xe7e9, 0x07d9, 0x19f4,
+0xd032, 0xd00b, 0x0e46, 0xe17d, 0xe2d8, 0x3456, 0x0781, 0xed01, 0x238d, 0xfa72,
+0xbb51, 0xf543, 0x050b, 0xccd5, 0x1491, 0x3358, 0xedad, 0x10c4, 0x283b, 0xd051,
+0xc9e9, 0x11f8, 0xe2cb, 0xe534, 0x43aa, 0x1090, 0xf11b, 0x3267, 0x02c3, 0xb72d,
+0xf9ac, 0x0fbd, 0xce45, 0x1d7b, 0x4389, 0xef2e, 0x1593, 0x348e, 0xd0cb, 0xca8c,
+0x1f61, 0xe981, 0xdef7, 0x4774, 0x15ae, 0xefab, 0x3b28, 0x0a9e, 0xb2f6, 0xf9e9,
+0x1976, 0xcc08, 0x15ab, 0x4534, 0xee6c, 0x159b, 0x3753, 0xcf09, 0xc69a, 0x2270,
+0xf15c, 0xdee6, 0x48ce, 0x1af4, 0xf169, 0x3da0, 0x0d68, 0xb573, 0xff9e, 0x20ba,
+0xcbfe, 0x142d, 0x4879, 0xed49, 0x1434, 0x3d96, 0xd714, 0xca99, 0x298b, 0xf708,
+0xd92c, 0x4632, 0x1acc, 0xea6e, 0x3d2c, 0x1412, 0xb534, 0xfbfa, 0x24f9, 0xcd72,
+0x0df9, 0x48f8, 0xeb87, 0x0bca, 0x3dd5, 0xd6cc, 0xc015, 0x2605, 0xfa87, 0xd1a9,
+0x40d0, 0x1c59, 0xe0de, 0x34f9, 0x14c6, 0xaf61, 0xf2a5, 0x23e6, 0xc929, 0x01be,
+0x4423, 0xe53b, 0x0182, 0x3c3a, 0xd758, 0xbb9d, 0x1fa9, 0xf454, 0xc611, 0x36e8,
+0x18f7, 0xdac9, 0x2e8a, 0x126d, 0xac14, 0xead6, 0x2215, 0xc990, 0xf9f5, 0x43cb,
+0xea01, 0xfcbf, 0x38fd, 0xd9f3, 0xb7cd, 0x1bc4, 0xfd41, 0xca56, 0x31e3, 0x1d4b,
+0xdca2, 0x2a9f, 0x1c24, 0xb8aa, 0xeb59, 0x25d5, 0xd2d0, 0xfa10, 0x44fa, 0xefe0,
+0xfced, 0x3ef4, 0xe9a1, 0xbdf0, 0x19ac, 0x0198, 0xca6f, 0x2f04, 0x25b6, 0xe187,
+0x29ba, 0x250a, 0xbe42, 0xe40e, 0x24ef, 0xd75d, 0xf476, 0x44f8, 0xf719, 0xf7a1,
+0x3c94, 0xf20e, 0xbcdf, 0x16a3, 0x07e8, 0xc8d4, 0x2a3e, 0x2b3f, 0xdf4e, 0x235d,
+0x2c92, 0xc2c7, 0xdf39, 0x2873, 0xd790, 0xea2a, 0x47fd, 0xfd0e, 0xf0e3, 0x3bd8,
+0xf4e9, 0xb265, 0x0c2c, 0x0751, 0xc302, 0x29bb, 0x37bd, 0xe138, 0x1e0c, 0x2d09,
+0xbddb, 0xd246, 0x24c4, 0xd87a, 0xe5df, 0x4ff6, 0x08d6, 0xf0d8, 0x3d61, 0xf8bf,
+0xaede, 0x0a36, 0x0df3, 0xc0f5, 0x23ec, 0x3e92, 0xe3d7, 0x1cad, 0x348e, 0xc0d6,
+0xcd4e, 0x265c, 0xd9b6, 0xdf83, 0x510e, 0x0c41, 0xeece, 0x4153, 0xfeeb, 0xa9f6,
+0x04b3, 0x12a4, 0xbf2f, 0x20d1, 0x42f4, 0xe1b1, 0x1b1e, 0x3980, 0xc2b4, 0xcb50,
+0x2b74, 0xded0, 0xd835, 0x4e7a, 0x0b46, 0xe555, 0x4015, 0x0517, 0xaa54, 0x0504,
+0x1932, 0xbc34, 0x1a77, 0x48b1, 0xe0bb, 0x149b, 0x3ba7, 0xc34a, 0xc481, 0x2bc2,
+0xe401, 0xd20e, 0x4f53, 0x1389, 0xe3b7, 0x418b, 0x0a15, 0xa70d, 0x0024, 0x1f9f,
+0xbf65, 0x142d, 0x4a81, 0xe0ca, 0x1152, 0x4325, 0xcb03, 0xc18a, 0x2b95, 0xeb45,
+0xcf92, 0x4c54, 0x18ad, 0xe08b, 0x3f12, 0x1264, 0xa9fc, 0xfd97, 0x246f, 0xbf86,
+0x0ce2, 0x4e7c, 0xe4f3, 0x0c20, 0x44e0, 0xd069, 0xbdcb, 0x2b8e, 0xf32d, 0xcad4,
+0x464f, 0x1e76, 0xdf62, 0x3b07, 0x17ea, 0xaafb, 0xf5a0, 0x2835, 0xc7c2, 0x0842,
+0x4d2b, 0xe634, 0x03ef, 0x42bc, 0xd7f2, 0xbb73, 0x2662, 0xf892, 0xc8b3, 0x3e30,
+0x1f20, 0xdcca, 0x354a, 0x1c6b, 0xaf75, 0xf0f7, 0x2963, 0xc908, 0xfdbf, 0x4c3c,
+0xebe5, 0x00e3, 0x44c4, 0xdf15, 0xb9e9, 0x243b, 0x00e9, 0xcb76, 0x3b53, 0x248e,
+0xdc27, 0x2fcb, 0x22e5, 0xb66c, 0xec96, 0x2b19, 0xd0ef, 0xf97b, 0x48ae, 0xecc0,
+0xf8b4, 0x411d, 0xe769, 0xb9f7, 0x1c41, 0x0022, 0xc369, 0x2ced, 0x23ac, 0xd8eb,
+0x2522, 0x232a, 0xb611, 0xe19f, 0x2738, 0xd013, 0xece5, 0x434c, 0xf00e, 0xefcc,
+0x3b79, 0xeb32, 0xb19c, 0x135e, 0x04ef, 0xc1b9, 0x27a8, 0x2992, 0xd7b3, 0x1ba5,
+0x2481, 0xb8c5, 0xd97d, 0x246f, 0xd113, 0xe45d, 0x4486, 0xf7f7, 0xeb36, 0x395a,
+0xf122, 0xaea5, 0x0c28, 0x05eb, 0xbde4, 0x2585, 0x36a2, 0xde67, 0x1b86, 0x2dac,
+0xbd03, 0xd2b8, 0x2624, 0xd8b8, 0xe802, 0x521b, 0x0855, 0xefbc, 0x4048, 0xfad2,
+0xafe2, 0x0fb1, 0x12b2, 0xc62c, 0x2c2a, 0x43f5, 0xe562, 0x1fcb, 0x3791, 0xc2ac,
+0xd4d1, 0x2dfd, 0xde0a, 0xe53f, 0x5578, 0x0f49, 0xf2b6, 0x4609, 0x0105, 0xabf5,
+0x09a8, 0x157e, 0xc286, 0x23e7, 0x425f, 0xe36a, 0x1d93, 0x3580, 0xbf80, 0xcaf2,
+0x2a04, 0xf16e, 0xd92b, 0x0eaa, 0xf1a7, 0x1ddb, 0x5b52, 0x0665, 0xd2e3, 0x15f8,
+0xf606, 0x9d42, 0xdba7, 0xf312, 0xd349, 0x21ed, 0x576a, 0x34e8, 0x2450, 0x2679,
+0xdc01, 0xb506, 0xcb0f, 0xa454, 0xccf3, 0x2c13, 0x1673, 0xf8ca, 0x4ff1, 0x63ac,
+0xec26, 0xd77c, 0xf1f9, 0xc268, 0xb11a, 0xdfe4, 0x02e7, 0x10f5, 0x3512, 0x19dd,
+0x0edc, 0x3568, 0xf6f7, 0xbe10, 0xda93, 0xf4fe, 0xda03, 0xe293, 0x15dd, 0x15f3,
+0x1ba5, 0x1521, 0x12e8, 0x23ab, 0x0fc3, 0xdb3e, 0xb671, 0xe960, 0xe13c, 0xc695,
+0x1a81, 0x3d23, 0x1c56, 0x190d, 0x4234, 0x1970, 0xd784, 0xd86b, 0xb5e8, 0xc9f3,
+0xeb89, 0xe344, 0x17ae, 0x5713, 0x37fc, 0xffe2, 0x36b3, 0x1dfe, 0xb963, 0xbf9c,
+0xc9a1, 0xcc7b, 0xe409, 0x08a6, 0x2077, 0x3b4d, 0x3cba, 0x0553, 0x220e, 0x226e,
+0xd219, 0xb7ec, 0xcb8b, 0xdf2a, 0xd0c7, 0xf5be, 0x2ff0, 0x42a6, 0x3c24, 0x25ae,
+0x2d6d, 0x0d94, 0xde80, 0xb78b, 0xb12b, 0xdf7a, 0xde33, 0x0046, 0x47b1, 0x5170,
+0x29c0, 0x2945, 0x3ab5, 0xf08f, 0xc806, 0xc229, 0xbbf4, 0xe40d, 0xf365, 0x0bfe,
+0x448d, 0x5cd8, 0x1e52, 0x10ba, 0x3908, 0xefa4, 0xc243, 0xcf89, 0xd02d, 0xde92,
+0xf8e0, 0x191e, 0x2f7b, 0x48e6, 0x1e38, 0x1074, 0x3785, 0xf8be, 0xbd1c, 0xc06b,
+0xdc36, 0xdb97, 0xe3c0, 0x2042, 0x37c5, 0x36ff, 0x1b73, 0x2064, 0x2c9a, 0xefa2,
+0xbf0c, 0xb7f0, 0xe221, 0xe243, 0xd998, 0x2263, 0x4bae, 0x3596, 0x18aa, 0x3763,
+0x27d0, 0xdcc6, 0xcacc, 0xc06f, 0xd83d, 0xecfe, 0xeefa, 0x1ffa, 0x5052, 0x393f,
+0x0af5, 0x3c9e, 0x316b, 0xd2df, 0xc575, 0xd3c8, 0xddd2, 0xdf98, 0xfbd7, 0x2929,
+0x4879, 0x4052, 0x160c, 0x3708, 0x2b31, 0xdac6, 0xc0c3, 0xcfc0, 0xe71d, 0xddec,
+0x0145, 0x3847, 0x457c, 0x356b, 0x214a, 0x3a5f, 0x1474, 0xd892, 0xc579, 0xc6a7,
+0xe77a, 0xe4dc, 0x00ab, 0x3b89, 0x4eba, 0x290a, 0x16ea, 0x3dc6, 0x0956, 0xcc12,
+0xc3bd, 0xc9e9, 0xe4be, 0xe60b, 0x0561, 0x3707, 0x4c82, 0x2444, 0x1406, 0x3a8e,
+0xff5b, 0xc494, 0xbf9f, 0xcb26, 0xdfef, 0xe755, 0x1060, 0x334f, 0x40e5, 0x1f87,
+0x16b9, 0x33e8, 0xfa6e, 0xc670, 0xb774, 0xcc17, 0xe18f, 0xdd0f, 0x102c, 0x3f0d,
+0x4098, 0x1b95, 0x24b2, 0x315a, 0xe9d8, 0xc459, 0xb314, 0xc524, 0xe2a6, 0xe1cf,
+0x100a, 0x44af, 0x455c, 0x1551, 0x264f, 0x2ab1, 0xd681, 0xb90c, 0xb4d6, 0xc68d,
+0xddac, 0xef74, 0x1f57, 0x4357, 0x4192, 0x0e60, 0x1bcb, 0x20fd, 0xd477, 0xb435,
+0xb3e3, 0xcdc3, 0xd9c4, 0xef97, 0x2384, 0x3b60, 0x34c9, 0x119d, 0x1f15, 0x0fb3,
+0xd15d, 0xb30d, 0xa9e3, 0xd431, 0xdc02, 0xe98a, 0x2987, 0x4204, 0x290c, 0x1181,
+0x2d0c, 0x0800, 0xcb55, 0xb8f5, 0xaaa6, 0xd49f, 0xe57c, 0xf063, 0x281c, 0x4c65,
+0x2d19, 0x0cd2, 0x2ddb, 0xfefe, 0xc171, 0xbd4c, 0xb7c2, 0xd4c5, 0xe6f3, 0x0040,
+0x2b86, 0x4b6d, 0x2ed1, 0x0ce3, 0x2d97, 0x01f9, 0xc2ad, 0xb8fc, 0xc53e, 0xe1cf,
+0xea35, 0x0eb0, 0x38b8, 0x4a3b, 0x2a1e, 0x1457, 0x2a1e, 0xfbca, 0xcdf1, 0xbc93,
+0xcc0b, 0xec27, 0xeb05, 0x144b, 0x4443, 0x496d, 0x2233, 0x2180, 0x30b2, 0xf03c,
+0xcced, 0xbf0d, 0xcc55, 0xeec3, 0xf367, 0x186f, 0x45cd, 0x4e7d, 0x215a, 0x2485,
+0x3122, 0xe7a8, 0xc40a, 0xbf85, 0xd4dd, 0xebe8, 0xf32b, 0x2121, 0x49bb, 0x4c61,
+0x1af5, 0x1f88, 0x2c32, 0xe8c5, 0xc512, 0xc0b7, 0xdbf9, 0xe9ea, 0xf2f4, 0x2584,
+0x43e2, 0x3e1b, 0x19cf, 0x28d2, 0x2442, 0xe27b, 0xc589, 0xbe8a, 0xdddc, 0xe567,
+0xed4e, 0x27f2, 0x48cd, 0x3505, 0x0e88, 0x2cd5, 0x207d, 0xda54, 0xc1cf, 0xb8c1,
+0xd925, 0xe569, 0xefd0, 0x2723, 0x4dd1, 0x38b2, 0x0de5, 0x2d90, 0x155b, 0xca06,
+0xbab6, 0xbf37, 0xdd46, 0xe3fd, 0xfb50, 0x2e5d, 0x487b, 0x343e, 0x0abe, 0x25e9,
+0x0f65, 0xcb83, 0xb474, 0xbc50, 0xe2ab, 0xe1df, 0xfd3e, 0x3672, 0x458b, 0x294e,
+0x10fd, 0x2afa, 0x027f, 0xcae8, 0xb95b, 0xbc6f, 0xe536, 0xe3af, 0xfd1c, 0x3b18,
+0x4cb1, 0x23ff, 0x13eb, 0x3353, 0xfb34, 0xc4aa, 0xb71a, 0xb9f2, 0xe1d7, 0xe97f,
+0x058d, 0x3a0f, 0x4fcd, 0x2408, 0x11a3, 0x2fb9, 0xf271, 0xbb7f, 0xb447, 0xc317,
+0xde44, 0xe56a, 0x110a, 0x3ccc, 0x494a, 0x1f80, 0x11af, 0x26a1, 0xeb09, 0xbcd0,
+0xaf90, 0xc8d4, 0xe63f, 0xe47d, 0x1435, 0x3f4f, 0x3fbe, 0x17c7, 0x1a4f, 0x2393,
+0xe191, 0xbfa1, 0xb0e4, 0xc7c9, 0xe2d9, 0xe363, 0x1625, 0x4320, 0x3da9, 0x11c4,
+0x1e02, 0x1d1b, 0xd6be, 0xbe96, 0xb123, 0xc8a4, 0xe6ce, 0xef2e, 0x1c03, 0x4584,
+0x3fd1, 0x1006, 0x20d8, 0x197b, 0xcf64, 0xb99e, 0xb693, 0xd396, 0xe8eb, 0xfb01,
+0x2aca, 0x4b38, 0x3f87, 0x0de0, 0x1f2f, 0x1503, 0xd574, 0xba46, 0xb72d, 0xf07a,
+0xfa16, 0xf608, 0x29c0, 0x3a7e, 0x42a7, 0x43ac, 0x2717, 0xec6f, 0xd732, 0xc1ac,
+0xa146, 0xef37, 0x122b, 0x05c1, 0x5c67, 0x8e8c, 0x3d5e, 0x0043, 0x00d0, 0xb9ef,
+0xa38d, 0xc8dd, 0xc921, 0x15c9, 0x5fe3, 0x531a, 0x477d, 0x5852, 0x1b9f, 0xb930,
+0xd1b6, 0xde60, 0xbcce, 0xe7f7, 0x16b1, 0x2aeb, 0x4605, 0x3592, 0xfe8c, 0x0c1d,
+0x1b24, 0xd084, 0xd667, 0x2736, 0x06f7, 0xdfa7, 0x1976, 0x0df9, 0xc5e8, 0x032b,
+0x324e, 0xea0e, 0x1ab4, 0x46e4, 0xf72e, 0x0369, 0x0ef3, 0xbe35, 0xbd17, 0x10fd,
+0xfb35, 0xeb3f, 0x4e43, 0x2da4, 0xfe31, 0x2f50, 0xf64c, 0xafd6, 0xe267, 0xfd01,
+0xca77, 0x1087, 0x48c1, 0xfcf4, 0x1bb0, 0x31af, 0xd234, 0xc0cb, 0x054e, 0xec6b,
+0xce29, 0x29db, 0x1bb4, 0xf0fd, 0x3608, 0x12eb, 0xbb40, 0xeaa8, 0x190f, 0xce00,
+0xed59, 0x39ef, 0xf1f0, 0xfb2a, 0x3535, 0xe3b3, 0xbf33, 0x1a9b, 0x013b, 0xc2ab,
+0x2976, 0x21e0, 0xd3d8, 0x1ca6, 0x14ae, 0xb242, 0xe538, 0x2958, 0xd98c, 0xf279,
+0x4106, 0xf13e, 0xf68b, 0x3379, 0xe023, 0xb4a8, 0x104b, 0x0685, 0xcca4, 0x2e61,
+0x2d96, 0xe2b8, 0x26ac, 0x2510, 0xc114, 0xd9e5, 0x1f91, 0xdbc9, 0xe515, 0x40bd,
+0x0693, 0xff44, 0x3c5e, 0xf664, 0xb8dc, 0x0b37, 0x1314, 0xc29c, 0x161f, 0x3582,
+0xe32e, 0x17c0, 0x2de6, 0xc7c1, 0xcfeb, 0x23a6, 0xe644, 0xe65f, 0x4256, 0xf765,
+0xe698, 0x4148, 0xfbe1, 0xa6b4, 0x03fa, 0x1c92, 0xcb85, 0x1a54, 0x37af, 0xe830,
+0x1b0b, 0x255d, 0xc13f, 0xd3d9, 0x205e, 0xde69, 0xe2ab, 0x48d5, 0x0931, 0xee2f,
+0x3d79, 0x0658, 0xb36c, 0xf59e, 0x11f4, 0xd042, 0x110b, 0x2e1b, 0xe763, 0x2269,
+0x3bda, 0xcefb, 0xd37b, 0x2d7f, 0xe9d7, 0xd48e, 0x3fd2, 0x0e86, 0xea62, 0x3cd5,
+0x11e0, 0xc1dc, 0x08e0, 0x1f68, 0xd3f1, 0x1fc8, 0x3da6, 0xe12f, 0x1d62, 0x4060,
+0xccb6, 0xd211, 0x316f, 0xf370, 0xe20e, 0x4657, 0x1280, 0xf30a, 0x3df0, 0x07fc,
+0xb956, 0x023e, 0x1978, 0xcbba, 0x137d, 0x3ff7, 0xecbc, 0x1698, 0x3f29, 0xdf9f,
+0xcc1c, 0x1bdc, 0xef17, 0xd3da, 0x346b, 0x1296, 0xeb25, 0x3885, 0x190f, 0xbf13,
+0xfb71, 0x1df2, 0xc509, 0xffa2, 0x3a66, 0xe5fd, 0x04f6, 0x36be, 0xda99, 0xc67e,
+0x1fc2, 0xef95, 0xcfa8, 0x39df, 0x0f1a, 0xd986, 0x2d7b, 0x0e88, 0xb2a2, 0xf40f,
+0x1bd3, 0xc95c, 0x0511, 0x408d, 0xec48, 0x03d2, 0x3281, 0xd7a0, 0xb9a0, 0x13ab,
+0xf02d, 0xc92c, 0x3af6, 0x26c0, 0xe5f8, 0x2de7, 0x18b9, 0xafd8, 0xddbf, 0x15bc,
+0xc4d3, 0xf6dc, 0x4b73, 0xf89f, 0x018a, 0x3c4e, 0xdf11, 0xb20d, 0x12d7, 0xf511,
+0xbf7e, 0x33aa, 0x286f, 0xe309, 0x3107, 0x1f74, 0xb1c3, 0xe10f, 0x1fd3, 0xc7d4,
+0xef6e, 0x4b78, 0xf32f, 0xf8e5, 0x43cb, 0xe7da, 0xaf46, 0x115a, 0xfeb2, 0xbf7a,
+0x2e9a, 0x2ed7, 0xde2f, 0x2807, 0x259c, 0xb09f, 0xd3d4, 0x2606, 0xd544, 0xeb3d,
+0x5107, 0xfecf, 0xf63f, 0x4304, 0xedfe, 0xae0d, 0x0d7f, 0x0957, 0xc47d, 0x2f62,
+0x3b51, 0xdfea, 0x2a01, 0x3390, 0xb825, 0xd3e9, 0x29f1, 0xd82e, 0xe2e3, 0x509a,
+0x061c, 0xf530, 0x48b1, 0xf740, 0xabeb, 0x0d93, 0x0ed4, 0xbed0, 0x274e, 0x3e3b,
+0xddc2, 0x2168, 0x35a1, 0xbbb0, 0xcedb, 0x2b94, 0xdd5b, 0xdd2d, 0x4e6a, 0x068d,
+0xe741, 0x3eef, 0xfe34, 0xad12, 0x0bb7, 0x1a73, 0xbea5, 0x1c31, 0x4269, 0xdc1a,
+0x1611, 0x37d6, 0xc048, 0xcaa3, 0x2f7e, 0xe59c, 0xd94c, 0x4ed8, 0x0af6, 0xe225,
+0x3c84, 0xfd49, 0xa4b2, 0x048d, 0x1ed5, 0xc496, 0x1caa, 0x4641, 0xddd4, 0x1578,
+0x37dc, 0xc13b, 0xcab7, 0x30dc, 0xfec0, 0xd462, 0x1387, 0x07dd, 0x14c1, 0x4b92,
+0x0d74, 0xda49, 0x12de, 0x02fe, 0xb8fe, 0xeaae, 0x0363, 0xdab0, 0x23b0, 0x68fb,
+0x3681, 0x1351, 0x29fc, 0xf22e, 0xb781, 0xd225, 0xc11d, 0xd7d8, 0x354d, 0x26b8,
+0x09af, 0x60fa, 0x5f8c, 0xe302, 0xde80, 0xff6a, 0xbb95, 0xafec, 0x029f, 0x161d,
+0x0fee, 0x3924, 0x2b6c, 0x1ed5, 0x24fe, 0xec7b, 0xc1fe, 0xe22b, 0xfbcd, 0xdc4d,
+0xf3f7, 0x210f, 0x1d01, 0x1305, 0x1342, 0x1f6c, 0x0852, 0xfea5, 0xdd42, 0xc083,
+0xf243, 0xde95, 0xd818, 0x23f7, 0x3eab, 0x0891, 0x1381, 0x52fd, 0xff10, 0xc983,
+0xe091, 0xc3b8, 0xcafc, 0xe7d7, 0xfc8d, 0x2043, 0x559d, 0x2c2e, 0x0418, 0x4485,
+0x0b4c, 0xb4e5, 0xc68e, 0xddbf, 0xd0b6, 0xdc81, 0x1e4b, 0x2d10, 0x365b, 0x2c50,
+0x170a, 0x303e, 0x0a60, 0xcc89, 0xb88a, 0xdbc7, 0xe3e7, 0xcdd2, 0x0b38, 0x3c7e,
+0x392b, 0x254c, 0x3272, 0x2fc9, 0xf0ee, 0xd4d8, 0xb5b4, 0xc03b, 0xdef0, 0xd8e9,
+0x0edc, 0x533e, 0x46e4, 0x0fc4, 0x358a, 0x34b8, 0xd1c3, 0xbf29, 0xbb64, 0xbeea,
+0xdb1c, 0xf31b, 0x17f1, 0x44fa, 0x4bfb, 0x0a36, 0x1fe2, 0x2ce9, 0xcf0d, 0xb605,
+0xc6c6, 0xcc96, 0xcf30, 0xf9cd, 0x25fb, 0x36d1, 0x4086, 0x1499, 0x21d8, 0x287f,
+0xde77, 0xb0fd, 0xba6d, 0xe0f5, 0xd3e4, 0xee77, 0x3561, 0x4077, 0x2baa, 0x1d38,
+0x3753, 0x1587, 0xd2e2, 0xb252, 0xb44b, 0xe5a7, 0xdbb5, 0xe778, 0x3790, 0x55cb,
+0x234e, 0x10ab, 0x42e9, 0x083e, 0xc15a, 0xc2a9, 0xbe30, 0xd7d1, 0xe76a, 0xfa22,
+0x2b37, 0x53cb, 0x29a6, 0x0950, 0x4086, 0x0f68, 0xbba0, 0xb824, 0xcc9c, 0xd743,
+0xd665, 0x06ae, 0x3597, 0x44f1, 0x2854, 0x19d4, 0x3395, 0xfe8f, 0xc1b9, 0xad2d,
+0xc39d, 0xde05, 0xd850, 0x0bf2, 0x4266, 0x457f, 0x1d4b, 0x2284, 0x337f, 0xe442,
+0xbc43, 0xb8ba, 0xc33a, 0xe0e4, 0xe8f8, 0x10b5, 0x4262, 0x4afc, 0x1744, 0x1d2b,
+0x3125, 0xe2b5, 0xbcb6, 0xbdea, 0xccfd, 0xdfe5, 0xefed, 0x1bae, 0x3f5e, 0x451d,
+0x167c, 0x1ea7, 0x2848, 0xdf70, 0xbb35, 0xbbfc, 0xd959, 0xe266, 0xec2b, 0x20e3,
+0x435c, 0x3878, 0x0fee, 0x25e8, 0x1ba1, 0xdaf0, 0xc061, 0xb76f, 0xdd9c, 0xe727,
+0xece4, 0x247e, 0x48ee, 0x303d, 0x099a, 0x320b, 0x19b9, 0xd0b8, 0xc508, 0xbe20,
+0xd52c, 0xe430, 0xf5f1, 0x21d1, 0x4aae, 0x3670, 0x0bc4, 0x349a, 0x16c6, 0xc9e1,
+0xbb8f, 0xc44e, 0xdbed, 0xde26, 0x03b2, 0x34c9, 0x4689, 0x30a8, 0x17ea, 0x33bd,
+0x0b87, 0xcd79, 0xb9b9, 0xc3c1, 0xe227, 0xdffc, 0x07ae, 0x3deb, 0x4732, 0x25e8,
+0x1ef9, 0x370f, 0xfb29, 0xcc78, 0xbf32, 0xc5c0, 0xe807, 0xe571, 0x074b, 0x4121,
+0x4902, 0x1968, 0x206c, 0x3da5, 0xf467, 0xc9c7, 0xc240, 0xc6d8, 0xe2b1, 0xeca9,
+0x0f7d, 0x3a80, 0x4ac1, 0x1bda, 0x1cdc, 0x3836, 0xee35, 0xc32e, 0xc0a2, 0xce3e,
+0xdfd7, 0xe9c8, 0x162c, 0x3eb5, 0x48b0, 0x1a61, 0x1e8f, 0x2cf5, 0xe5c6, 0xbb80,
+0xb378, 0xd228, 0xe3dd, 0xeba5, 0x2266, 0x46f5, 0x3e1f, 0x13fa, 0x26ea, 0x21ec,
+0xd925, 0xbdc7, 0xb66d, 0xd76b, 0xe81e, 0xf025, 0x269d, 0x4d69, 0x3d40, 0x1027,
+0x2c58, 0x1cc9, 0xd265, 0xbfd4, 0xbabe, 0xd919, 0xe822, 0xf931, 0x2bc9, 0x4c69,
+0x3d20, 0x158d, 0x31ca, 0x1821, 0xce8a, 0xb8af, 0xba0f, 0xdfb6, 0xe677, 0xfd3b,
+0x385e, 0x53d9, 0x3764, 0x14a8, 0x30af, 0x0a51, 0xcb95, 0xbad7, 0xbc48, 0xe366,
+0xea7e, 0x06cf, 0x3f08, 0x53c7, 0x2fe0, 0x189d, 0x383f, 0x00fd, 0xc5f3, 0xbf0d,
+0xc38f, 0xe4a3, 0xecee, 0x0ef5, 0x432c, 0x54a7, 0x2a15, 0x190d, 0x3675, 0xf7bc,
+0xc3e0, 0xbc22, 0xc381, 0xe210, 0xec59, 0x15ed, 0x4300, 0x4fd8, 0x269c, 0x1bda,
+0x324a, 0xed57, 0xbb9c, 0xb705, 0xceb8, 0xeb30, 0xed72, 0x1baa, 0x48ad, 0x4bd3,
+0x1fde, 0x1ea9, 0x2826, 0xe505, 0xc2b3, 0xb577, 0xceec, 0xeeb9, 0xef73, 0x1fd5,
+0x4c99, 0x41f7, 0x12c7, 0x24ad, 0x22eb, 0xd504, 0xbfe3, 0xba2a, 0xd063, 0xea6f,
+0xf037, 0x1c9c, 0x4acf, 0x430c, 0x0b68, 0x200d, 0x1c9e, 0xcce4, 0xb9ad, 0xbc29,
+0xd211, 0xe475, 0xfc21, 0x2910, 0x443b, 0x3a83, 0x0ef1, 0x2295, 0x15ac, 0xd00d,
+0xb774, 0xbaff, 0xded3, 0xe41e, 0xf945, 0x331e, 0x49b7, 0x3276, 0x128d, 0x28c7,
+0x08f2, 0xce8e, 0xbb2e, 0xb907, 0xe4c0, 0xe9f8, 0xf98a, 0x3323, 0x4a75, 0x2718,
+0x0ddc, 0x3369, 0x0795, 0xc936, 0xc192, 0xc3cc, 0xe2b9, 0xe583, 0xfce9, 0x312f,
+0x4951, 0x266f, 0x0ffe, 0x3698, 0x0679, 0xca63, 0xc301, 0xc844, 0xde4c, 0xe26e,
+0x076e, 0x3283, 0x4507, 0x259a, 0x11af, 0x30ff, 0xfd1c, 0xc1b2, 0xb384, 0xc924,
+0xe414, 0xde8f, 0x0781, 0x295f, 0x51b4, 0x5b09, 0x17c9, 0xf17b, 0xd9cd, 0xb11a,
+0x8396, 0xbd98, 0x073f, 0x0598, 0x5258, 0x7bf8, 0x3dd3, 0x096d, 0xe7f8, 0xa966,
+0x9271, 0xc3c7, 0xb173, 0xf5d9, 0x6db2, 0x3b89, 0x2231, 0x4aaf, 0x1c3b, 0xc115,
+0xcb06, 0xd460, 0xbb98, 0x03f6, 0xf9d7, 0xecaf, 0x4aa5, 0x27cf, 0xcf8c, 0x0764,
+0x3489, 0xd9cb, 0xf31b, 0x39b5, 0xebc3, 0xeb7f, 0x1192, 0xceee, 0xbd72, 0x16f9,
+0x1b5a, 0xf888, 0x4a44, 0x34a8, 0xedd8, 0x18bb, 0xf8d6, 0xa74c, 0xd19c, 0x139c,
+0xeaf7, 0x0d0b, 0x5317, 0x0e81, 0x0c44, 0x35bd, 0xe010, 0xb51d, 0x075b, 0xfc77,
+0xc9ae, 0x2b95, 0x35a1, 0xf0e8, 0x2c61, 0x2481, 0xc370, 0xe826, 0x20b5, 0xd95a,
+0xf832, 0x43e0, 0xf261, 0xf7ef, 0x414e, 0xf14b, 0xbf9e, 0x1c6c, 0x1380, 0xd3d1,
+0x2650, 0x1f52, 0xd592, 0x1ddb, 0x2414, 0xc347, 0xebd1, 0x3e70, 0xf240, 0xeb89,
+0x3d66, 0xf738, 0xe57b, 0x2fe8, 0xf22d, 0xbd68, 0x1e7b, 0x2466, 0xd858, 0x2613,
+0x3122, 0xdc86, 0x16b9, 0x277b, 0xc324, 0xdb13, 0x2c78, 0xe8ab, 0xed0b, 0x49bb,
+0x0342, 0xf02a, 0x3b6c, 0xf7d9, 0xb9c6, 0x0fd8, 0x1192, 0xc763, 0x12e5, 0x2738,
+0xe26c, 0x1a89, 0x2a72, 0xcd72, 0xdca7, 0x27a9, 0xe962, 0xd98a, 0x271e, 0xf948,
+0xe783, 0x29f0, 0x000e, 0xc137, 0x064c, 0x17e6, 0xcd48, 0x0efb, 0x329b, 0xdc50,
+0xf9d6, 0x28fd, 0xd866, 0xc34b, 0x13df, 0xefa3, 0xdcbf, 0x3578, 0x09a7, 0xe33f,
+0x2c3f, 0x02a6, 0xaa76, 0xf3eb, 0x1870, 0xc21d, 0x029e, 0x3d07, 0xedbb, 0x0a92,
+0x33dc, 0xd94f, 0xc985, 0x15a5, 0xdf1c, 0xd3f5, 0x3f5e, 0x0fca, 0xe50f, 0x3b04,
+0x1a3d, 0xb99b, 0xf6d1, 0x1c75, 0xcc21, 0x0987, 0x3e95, 0xed51, 0x0dcf, 0x3b32,
+0xd980, 0xc6f7, 0x280e, 0xf587, 0xd3c2, 0x4871, 0x233c, 0xe02f, 0x3039, 0x183d,
+0xaecf, 0xf137, 0x2776, 0xcc66, 0x0bf0, 0x5162, 0xeddf, 0x088c, 0x4536, 0xd457,
+0xb205, 0x2315, 0xf51a, 0xc60d, 0x4281, 0x2682, 0xe5d9, 0x3aad, 0x1cab, 0xb02d,
+0xf294, 0x20af, 0xbecb, 0x0084, 0x4c16, 0xeaf2, 0x054e, 0x449f, 0xdf02, 0xbd48,
+0x25bf, 0xfda9, 0xcb15, 0x3a93, 0x1e9b, 0xddd4, 0x3408, 0x1f70, 0xb333, 0xf3df,
+0x32ab, 0xd133, 0x014e, 0x52b2, 0xf138, 0xfe00, 0x4260, 0xe1f2, 0xbbac, 0x28bf,
+0x0404, 0xcc81, 0x4649, 0x2e56, 0xdee8, 0x3677, 0x23ef, 0xabc0, 0xea62, 0x3159,
+0xcf59, 0xfdcf, 0x575a, 0xf403, 0xfe40, 0x4759, 0xe094, 0xb225, 0x1ffa, 0xfefc,
+0xc26f, 0x3a61, 0x2be1, 0xdb44, 0x2efe, 0x2504, 0xadcb, 0xe074, 0x2713, 0xc6d3,
+0xecc2, 0x48d6, 0xea4d, 0xf2ec, 0x43a0, 0xe1fc, 0xaa5f, 0x1825, 0xffd7, 0xba38,
+0x2bdb, 0x24a3, 0xce10, 0x1cd8, 0x1cfc, 0xab2e, 0xdc4d, 0x276d, 0xca22, 0xeb01,
+0x4a08, 0xeb0e, 0xe94c, 0x3cd7, 0xe45f, 0xa6c6, 0x0f8c, 0x066f, 0xc2d2, 0x2a01,
+0x2aea, 0xd9bf, 0x251a, 0x2933, 0xb64d, 0xd9b5, 0x26a1, 0xd2ba, 0xe822, 0x4af1,
+0xfec7, 0xf323, 0x3fdf, 0xf78e, 0xb4c1, 0x0f7f, 0x0e1c, 0xc7ce, 0x25fb, 0x3129,
+0xdeb5, 0x2268, 0x3385, 0xc266, 0xd9b6, 0x2efc, 0xddb0, 0xe00f, 0x45ac, 0x0168,
+0xea8d, 0x3cd9, 0xfeda, 0xb603, 0x13c5, 0x166b, 0xc192, 0x1f5a, 0x3804, 0xda94,
+0x15a3, 0x35b3, 0xc729, 0xd3ae, 0x2e68, 0xe359, 0xde70, 0x4764, 0x0725, 0xe6b1,
+0x3882, 0xfed4, 0xad23, 0x0819, 0x16c2, 0xc15b, 0x1c91, 0x4358, 0xe49c, 0x1162,
+0x35d1, 0xc8f8, 0xc74f, 0x2676, 0xe0e9, 0xd0d7, 0x4b09, 0x1cea, 0xedea, 0x3f3b,
+0x11f4, 0xb09a, 0xfc73, 0x177b, 0xba40, 0x109f, 0x4fcb, 0xf285, 0x1d0f, 0x3dc2,
+0xc588, 0xc98d, 0x329a, 0xfd8a, 0xcc33, 0x1573, 0x1810, 0x1532, 0x434e, 0x102d,
+0xd555, 0x08d4, 0x0011, 0xb77c, 0xec37, 0x098c, 0xd4fc, 0x2033, 0x7926, 0x32be,
+0xfe95, 0x28ab, 0xef9c, 0xa428, 0xcffc, 0xcbdf, 0xd07c, 0x3681, 0x2f87, 0x0680,
+0x626e, 0x5e9c, 0xd624, 0xd9e4, 0x080a, 0xadfe, 0xa2f5, 0x12af, 0x142c, 0xffde,
+0x3703, 0x3570, 0x269e, 0x24fb, 0xe8a1, 0xb7ec, 0xe753, 0xf82a, 0xce4d, 0x001a,
+0x2e98, 0x1f84, 0x0eb8, 0x1beb, 0x2603, 0xfcff, 0xfd98, 0xd8c0, 0xc719, 0xfc52,
+0xddd2, 0xe3ec, 0x2ee0, 0x4393, 0x042b, 0x1929, 0x569a, 0xef83, 0xc35d, 0xd9e5,
+0xc6ce, 0xd1e0, 0xed86, 0x0b2a, 0x23b2, 0x504c, 0x20ad, 0x029d, 0x3b72, 0xf5a5,
+0xad6b, 0xbe54, 0xddfc, 0xd162, 0xddcd, 0x2952, 0x324b, 0x3156, 0x1d3f, 0x12f5,
+0x235b, 0xf27f, 0xc001, 0xb250, 0xdfdd, 0xe3f3, 0xd455, 0x14e4, 0x3c06, 0x3326,
+0x1a60, 0x30e7, 0x24a0, 0xe2c3, 0xcd08, 0xb21c, 0xc75c, 0xdc66, 0xe088, 0x1e09,
+0x54ef, 0x4197, 0x0dca, 0x356f, 0x22ce, 0xcaf2, 0xc0ce, 0xbc3d, 0xcfda, 0xe59b,
+0xfe5c, 0x27b1, 0x4caa, 0x45a1, 0x0add, 0x274f, 0x1c6c, 0xcde6, 0xc343, 0xd011,
+0xdf48, 0xe021, 0x0b0c, 0x335a, 0x3c8e, 0x345d, 0x0d86, 0x278e, 0x1b8f, 0xdc12,
+0xbc28, 0xc6ff, 0xead3, 0xdba0, 0xfdee, 0x39b8, 0x3f03, 0x2143, 0x1858, 0x376b,
+0x021b, 0xcaa8, 0xbb59, 0xc6f6, 0xef67, 0xe041, 0xf9ba, 0x3cd7, 0x4cfd, 0x168d,
+0x1037, 0x3fec, 0xf71d, 0xbed3, 0xc8d6, 0xcbc6, 0xdd8f, 0xea8c, 0x09b1, 0x2e92,
+0x4701, 0x1829, 0x091c, 0x3ad7, 0xfd4b, 0xb999, 0xbe68, 0xdc50, 0xdc39, 0xd663,
+0x1009, 0x330e, 0x37f3, 0x1ad3, 0x1cca, 0x3476, 0xf66a, 0xc5e8, 0xbb6f, 0xd5e8,
+0xe008, 0xd5ea, 0x10b5, 0x3f63, 0x3725, 0x1102, 0x2911, 0x3855, 0xe9cb, 0xc610,
+0xc44b, 0xd547, 0xdd89, 0xe4aa, 0x195d, 0x4084, 0x3d9b, 0x10ab, 0x2a1f, 0x3667,
+0xe38c, 0xc1a0, 0xc4c5, 0xdd94, 0xe21b, 0xe99c, 0x1f49, 0x4312, 0x3b69, 0x0f14,
+0x2b3d, 0x2eaa, 0xdeab, 0xc00a, 0xc634, 0xe225, 0xe0e3, 0xf311, 0x2b9a, 0x44fd,
+0x3881, 0x11ee, 0x2f2a, 0x2428, 0xdc87, 0xc347, 0xc2d0, 0xe6e0, 0xe5b0, 0xf196,
+0x2d4e, 0x4a97, 0x3366, 0x1388, 0x3ae0, 0x1bf8, 0xd058, 0xc212, 0xc09b, 0xdbf3,
+0xe25c, 0xfa1f, 0x3093, 0x4e94, 0x31f0, 0x12fe, 0x3bde, 0x11ad, 0xc841, 0xb8bd,
+0xbe0b, 0xdd25, 0xdd7e, 0x0138, 0x3ac9, 0x4ccb, 0x2ba2, 0x1359, 0x3033, 0xfbc6,
+0xc14d, 0xb543, 0xbd7e, 0xdcf8, 0xde2b, 0x0754, 0x388b, 0x409a, 0x1e0d, 0x134a,
+0x2ba8, 0xeee4, 0xbe5b, 0xafaf, 0xbb90, 0xe059, 0xde74, 0x0736, 0x3d1c, 0x4310,
+0x16f1, 0x1686, 0x2f2a, 0xe9de, 0xbe17, 0xb446, 0xc0ca, 0xdd27, 0xe1a0, 0x0ccd,
+0x3c13, 0x4661, 0x1949, 0x196a, 0x2a50, 0xdf8d, 0xb6a7, 0xb25a, 0xc8aa, 0xdf0e,
+0xe6dc, 0x19c8, 0x411d, 0x423f, 0x15db, 0x1a0d, 0x2316, 0xdf69, 0xba97, 0xb2a9,
+0xd092, 0xe29a, 0xebf2, 0x2556, 0x4a3b, 0x3fb1, 0x16cb, 0x2603, 0x1c1b, 0xd67d,
+0xbcf3, 0xb765, 0xdad1, 0xea4b, 0xf176, 0x27e4, 0x4d31, 0x3b4c, 0x0fab, 0x2ad5,
+0x1a4d, 0xd10b, 0xbc49, 0xba78, 0xda1f, 0xe903, 0xffda, 0x308c, 0x4a2f, 0x38ce,
+0x11cc, 0x299c, 0x105c, 0xcdc3, 0xba31, 0xc03e, 0xe616, 0xe849, 0xfec7, 0x37e1,
+0x4e98, 0x3198, 0x12d8, 0x2aeb, 0x03e5, 0xcb29, 0xbccb, 0xc232, 0xe734, 0xeb9c,
+0x07ed, 0x3d12, 0x4b48, 0x2515, 0x14e8, 0x330c, 0xfd19, 0xc86d, 0xc241, 0xca17,
+0xe64b, 0xe918, 0x09ed, 0x3ba3, 0x4eee, 0x25e2, 0x167a, 0x38a6, 0xffcf, 0xcb0e,
+0xc615, 0xd055, 0xe3d3, 0xeafc, 0x1602, 0x3e86, 0x49c2, 0x257e, 0x2166, 0x3bcd,
+0xfc55, 0xc4ad, 0xbb87, 0xd6e3, 0xe7a1, 0xe64f, 0x1ddd, 0x4682, 0x4516, 0x1dee,
+0x1dc5, 0x2b0d, 0xed86, 0xc590, 0xb97d, 0xd84f, 0xec06, 0xe368, 0x1c7f, 0x4cee,
+0x3f07, 0x13c3, 0x289d, 0x28fb, 0xdcf0, 0xc5b7, 0xbdb7, 0xd4a7, 0xec46, 0xecd0,
+0x1bda, 0x48cc, 0x4019, 0x0cd7, 0x23a7, 0x2698, 0xd5be, 0xbc3e, 0xb90e, 0xcc6c,
+0xddf3, 0xf12b, 0x24f1, 0x4448, 0x3b5c, 0x118e, 0x2441, 0x1c2b, 0xd270, 0xb368,
+0xb6c7, 0xdd46, 0xdf51, 0xefbb, 0x3138, 0x49d2, 0x3667, 0x1864, 0x2b86, 0x1073,
+0xd35e, 0xbbf4, 0xb47a, 0xdfb4, 0xe8bb, 0xf6b2, 0x353a, 0x4fd4, 0x2e9f, 0x12a8,
+0x323d, 0x07c8, 0xcb04, 0xc1fa, 0xbd57, 0xdf17, 0xe6e8, 0xfa82, 0x3052, 0x4c63,
+0x2d83, 0x12bf, 0x3366, 0x0318, 0xc6dd, 0xbd85, 0xbc4f, 0xd9e1, 0xe604, 0x0814,
+0x34df, 0x4a09, 0x2d9d, 0x148f, 0x2e79, 0xfba1, 0xc12c, 0xb277, 0xbf95, 0xdeb7,
+0xe211, 0x10bc, 0x423c, 0x4956, 0x24f7, 0x1571, 0x2434, 0xebbb, 0xc333, 0xb587,
+0xc733, 0xedc0, 0xebba, 0x0854, 0x3f2e, 0x6f7e, 0x4a81, 0x0e5e, 0x03fe, 0xcb43,
+0xa313, 0xa8c3, 0xd5fe, 0x0fe0, 0x3ce3, 0x6922, 0x5cb3, 0x4541, 0x10b8, 0xc5c1,
+0xb7c8, 0xca44, 0xca38, 0xd7f7, 0x38e7, 0x4e53, 0x1cb5, 0x3dfb, 0x3d19, 0x06ab,
+0xeda0, 0xe9ef, 0xd83d, 0xf9bf, 0x0ebe, 0xca2a, 0x0a79, 0x510e, 0xf01d, 0xe7eb,
+0x3be6, 0x1a2e, 0xf989, 0x353e, 0x099c, 0xe538, 0x16d7, 0xd697, 0xa93c, 0x0407,
+0x1bee, 0xf75f, 0x46c6, 0x50e3, 0xf430, 0x1813, 0x169b, 0xaf5d, 0xc57c, 0x0fb5,
+0xe638, 0xf7e9, 0x459c, 0x122e, 0x0654, 0x352e, 0xfc93, 0xc262, 0xf99c, 0x072c,
+0xc8f1, 0x0bf2, 0x32cd, 0xf85b, 0x1790, 0x25c1, 0xde96, 0xd882, 0x18ea, 0xe834,
+0xd77c, 0x3995, 0x1231, 0xeab1, 0x28e3, 0x0cfc, 0xc5ef, 0xf7bd, 0x18f1, 0xd03e,
+0x057e, 0x320a, 0xe5e4, 0x07c7, 0x2d5d, 0xd88a, 0xc884, 0x2072, 0xfd6c, 0xd3d0,
+0x2a59, 0x1066, 0xe564, 0x22a2, 0x047a, 0xb2ee, 0xeaad, 0x1c28, 0xcc6c, 0xf7e5,
+0x3c36, 0xee88, 0xfbfb, 0x3085, 0xddf6, 0xb28a, 0x0aaf, 0xf456, 0xbc45, 0x223a,
+0x21ed, 0xe31c, 0x2214, 0x1882, 0xb51a, 0xdbdc, 0x18ba, 0xc1ce, 0xe1c3, 0x397c,
+0xed39, 0xf426, 0x3690, 0xe68c, 0xb498, 0x0e83, 0x00c5, 0xc494, 0x1ef7, 0x1b31,
+0xdc33, 0x1f84, 0x1b7f, 0xb9c1, 0xdc07, 0x2246, 0xd5fb, 0xeaf7, 0x4a41, 0x042a,
+0xed8c, 0x29a9, 0xf4c4, 0xbbf5, 0xffbd, 0x02b3, 0xd089, 0x2a98, 0x349a, 0xe5f4,
+0x231d, 0x3682, 0xc042, 0xc757, 0x28ee, 0xe56b, 0xda47, 0x3eac, 0x0d7c, 0xfc17,
+0x4512, 0x05d1, 0xbb63, 0x0fc8, 0x0da5, 0xb98f, 0x1ecc, 0x3aea, 0xdbbd, 0x1bd6,
+0x4041, 0xd007, 0xd35e, 0x2aaf, 0xea38, 0xe711, 0x3e4a, 0xfa47, 0xef65, 0x3f02,
+0xf730, 0xae98, 0x0fae, 0x1e19, 0xc953, 0x1ea3, 0x3db2, 0xe20d, 0x1043, 0x2d87,
+0xc8ab, 0xca8a, 0x2270, 0xe769, 0xe021, 0x4751, 0x0a42, 0xed5b, 0x468e, 0x0a0e,
+0xa823, 0xfc1e, 0x19ce, 0xc19e, 0x0fd8, 0x3c33, 0xe854, 0x1d91, 0x3f3f, 0xd03c,
+0xd290, 0x2d60, 0xe292, 0xd443, 0x4229, 0x0590, 0xe446, 0x3c78, 0x0d2e, 0xbba0,
+0x0b10, 0x1fdc, 0xccc6, 0x142f, 0x3174, 0xdc18, 0x1061, 0x371b, 0xd368, 0xd200,
+0x2d59, 0xf1a5, 0xdc23, 0x3edd, 0x0fb6, 0xe812, 0x2f6f, 0x0a77, 0xb941, 0xfe00,
+0x1d36, 0xcc9c, 0x11f2, 0x404a, 0xe70a, 0x0abc, 0x3965, 0xd3be, 0xbed2, 0x1fe7,
+0xee09, 0xd03e, 0x3f07, 0x1799, 0xe33b, 0x342f, 0x1443, 0xb44b, 0xf906, 0x1979,
+0xbeaf, 0x0a9f, 0x45b3, 0xe73c, 0x0a6f, 0x3e78, 0xd655, 0xc03d, 0x2087, 0xeb0b,
+0xcf7a, 0x4395, 0x1915, 0xe1a6, 0x34d0, 0x134e, 0xac96, 0xeeeb, 0x1aa0, 0xc459,
+0x0965, 0x4852, 0xe8bd, 0x0151, 0x3569, 0xd16f, 0xb855, 0x1c6f, 0xed54, 0xcabb,
+0x428e, 0x1dfb, 0xdf3c, 0x323b, 0x15f0, 0xab84, 0xe597, 0x18a2, 0xc34f, 0x0120,
+0x48dd, 0xedf1, 0x07d1, 0x3f6b, 0xd521, 0xb0ac, 0x1903, 0xf0c7, 0xc122, 0x3959,
+0x22ad, 0xe010, 0x2ec7, 0x1837, 0xaaf6, 0xe170, 0x1e2b, 0xc7b9, 0xfdb5, 0x4c53,
+0xebef, 0xfb34, 0x3ee4, 0xdef9, 0xb297, 0x1b34, 0xfd76, 0xc42a, 0x391e, 0x2b29,
+0xde3f, 0x2dc8, 0x2491, 0xb2a5, 0xe544, 0x2b06, 0xcad8, 0xf507, 0x54ba, 0xf673,
+0xfa92, 0x48ca, 0xe9a8, 0xafd3, 0x1ef7, 0x084a, 0xc383, 0x3a6e, 0x3354, 0xdbff,
+0x2c3f, 0x2b2a, 0xb115, 0xe1c4, 0x348d, 0xd2d5, 0xf3b1, 0x5801, 0xf84b, 0xf5dc,
+0x4897, 0xeba9, 0xac5a, 0x1d11, 0x0bb4, 0xbcd7, 0x32ed, 0x3809, 0xdc0a, 0x2aa8,
+0x3035, 0xb63c, 0xddf9, 0x3359, 0xe1e6, 0xdc8c, 0x1666, 0xf007, 0x2428, 0x5dbe,
+0x00c2, 0xd781, 0x22f0, 0xf405, 0xa1af, 0xed74, 0xf64b, 0xd808, 0x304e, 0x5c2f,
+0x2e87, 0x2aaf, 0x32b5, 0xdc5e, 0xbf90, 0xd852, 0xad5b, 0xdde3, 0x38e2, 0x1923,
+0x04d1, 0x62c4, 0x5bb8, 0xe529, 0xeaaf, 0xfd61, 0xc422, 0xc0df, 0xfa6b, 0x0d7b,
+0x16ff, 0x3f3e, 0x1d78, 0x1a46, 0x3b1d, 0xf55f, 0xc829, 0xeeb5, 0x0157, 0xdd55,
+0xf41a, 0x20b5, 0x1533, 0x2329, 0x1f7c, 0x1523, 0x1e43, 0x1419, 0xdc00, 0xbfc2,
+0xfbcf, 0xe187, 0xd1ef, 0x2a18, 0x3c8b, 0x1862, 0x2179, 0x4380, 0x06a1, 0xdc67,
+0xe0e4, 0xb854, 0xda4e, 0xf2b5, 0xe744, 0x1f54, 0x57ea, 0x2932, 0xfa55, 0x3961,
+0x0fd9, 0xbac6, 0xcdda, 0xd258, 0xcf89, 0xe3ab, 0x0707, 0x1a3e, 0x3967, 0x30cc,
+0x0264, 0x2d4f, 0x1e6c, 0xcc01, 0xb70b, 0xd1c4, 0xdac0, 0xca7b, 0xfba8, 0x26c2,
+0x371b, 0x359b, 0x23c6, 0x2fb4, 0x0da1, 0xd9bd, 0xae9d, 0xb89c, 0xdb61, 0xcb90,
+0x0132, 0x482d, 0x42cd, 0x1e49, 0x2e1f, 0x3691, 0xe7fa, 0xc93c, 0xba66, 0xbb0a,
+0xe251, 0xe472, 0x0383, 0x3f08, 0x4cc8, 0x1215, 0x1c80, 0x3b71, 0xe4f3, 0xc431,
+0xcbb0, 0xc403, 0xd2f9, 0xf12b, 0x1313, 0x2f50, 0x4774, 0x1313, 0x18c4, 0x4058,
+0xed5d, 0xb643, 0xc25a, 0xd92a, 0xcdc3, 0xe267, 0x25f0, 0x37bf, 0x38e3, 0x1db0,
+0x2909, 0x2dab, 0xe635, 0xb3fb, 0xb52f, 0xe71a, 0xdad1, 0xdbe5, 0x2dd4, 0x4a45,
+0x2f11, 0x15fc, 0x375b, 0x1edc, 0xd95e, 0xc7dd, 0xb91d, 0xdb9a, 0xe8d2, 0xea7d,
+0x243c, 0x50aa, 0x347a, 0x0b10, 0x3f2c, 0x24ae, 0xc984, 0xc48d, 0xccec, 0xd726,
+0xda77, 0xf845, 0x2782, 0x491b, 0x3bb7, 0x1233, 0x3698, 0x200c, 0xcd29, 0xb25f,
+0xc2ef, 0xdd85, 0xd457, 0xfe96, 0x3830, 0x43f0, 0x2e94, 0x1a60, 0x3369, 0x07e9,
+0xcb4f, 0xb645, 0xba73, 0xdbb8, 0xdb8f, 0xffae, 0x3a33, 0x48d7, 0x2211, 0x15da,
+0x362f, 0xfbd5, 0xc39a, 0xb7fb, 0xc2cf, 0xe1cd, 0xe2d2, 0x0604, 0x36c9, 0x495d,
+0x209d, 0x15d2, 0x37e6, 0xf91a, 0xc431, 0xbe00, 0xccf7, 0xe3be, 0xe90a, 0x14f8,
+0x3bed, 0x4775, 0x22c5, 0x1a55, 0x2eb8, 0xf7ab, 0xcbee, 0xbb0a, 0xd4b7, 0xea3e,
+0xe43a, 0x18fd, 0x455e, 0x429e, 0x1d75, 0x27b4, 0x2e04, 0xeadd, 0xca0c, 0xb834,
+0xcf7e, 0xebe8, 0xec12, 0x1d2b, 0x4ce6, 0x4388, 0x1410, 0x2882, 0x2863, 0xdbfa,
+0xc201, 0xbc64, 0xd1a8, 0xe40d, 0xf23c, 0x21f6, 0x44ac, 0x3ed5, 0x105b, 0x1f33,
+0x1ab9, 0xd393, 0xb879, 0xb8a2, 0xd559, 0xdf87, 0xf31b, 0x2555, 0x3b7c, 0x3066,
+0x0f9b, 0x206c, 0x1043, 0xd352, 0xb6fe, 0xb4ae, 0xdacb, 0xdd79, 0xed28, 0x2944,
+0x40cc, 0x26e3, 0x1073, 0x2c6d, 0x0a8e, 0xd1fe, 0xbe7c, 0xb79c, 0xdbb0, 0xe28f,
+0xf1b8, 0x2782, 0x45d6, 0x288a, 0x146e, 0x3752, 0x08d7, 0xccea, 0xc003, 0xbd89,
+0xda98, 0xe4f4, 0x0193, 0x3341, 0x4c65, 0x29eb, 0x1665, 0x385c, 0x0502, 0xcaf3,
+0xbfdd, 0xc859, 0xe1b4, 0xe85f, 0x0f40, 0x394b, 0x49b1, 0x2907, 0x1adb, 0x324c,
+0xf922, 0xc6e9, 0xb961, 0xcbdd, 0xe7b2, 0xe6bd, 0x11f6, 0x3fef, 0x46fb, 0x1df6,
+0x1d0f, 0x2f40, 0xeef5, 0xc4a6, 0xb575, 0xcb9f, 0xe637, 0xea22, 0x1afc, 0x450f,
+0x486c, 0x1ca2, 0x2081, 0x27c1, 0xe170, 0xbe81, 0xb610, 0xd4bd, 0xe951, 0xed82,
+0x22d8, 0x4920, 0x44fd, 0x1967, 0x24be, 0x230d, 0xdd5b, 0xbd48, 0xb2b2, 0xd74f,
+0xebd6, 0xf51e, 0x2d0e, 0x4f15, 0x406d, 0x18ea, 0x2d32, 0x1b37, 0xd68b, 0xc044,
+0xb935, 0xdd2b, 0xe8ca, 0xf474, 0x309b, 0x54b6, 0x3c42, 0x14ba, 0x347b, 0x1910,
+0xd0a8, 0xbcba, 0xb8c9, 0xdc46, 0xe9fe, 0xfff4, 0x3532, 0x5389, 0x3988, 0x14af,
+0x3504, 0x102d, 0xc848, 0xb929, 0xbe6f, 0xdd0e, 0xe430, 0x059c, 0x3ba7, 0x4f58,
+0x2e33, 0x10f0, 0x2a2f, 0xfdb9, 0xc2f6, 0xafbb, 0xbb99, 0xe4f6, 0xe5f2, 0x07f0,
+0x3ce6, 0x4606, 0x2206, 0x1803, 0x2b80, 0xee52, 0xc24f, 0xb46d, 0xbb4a, 0xe32e,
+0xe633, 0x0953, 0x4238, 0x4b00, 0x1a28, 0x1723, 0x280d, 0xe191, 0xbf29, 0xb512,
+0xbb95, 0xe083, 0xee77, 0x11e1, 0x3d02, 0x4905, 0x18fb, 0x175c, 0x25d3, 0xdaba,
+0xb4bd, 0xb51c, 0xcc2b, 0xe1c5, 0xed1f, 0x1d15, 0x42ae, 0x42e0, 0x12aa, 0x15c7,
+0x1ce6, 0xdd86, 0xbb2e, 0xb339, 0xd4c6, 0xe6a3, 0xee5c, 0x246d, 0x4599, 0x378a,
+0x117f, 0x247f, 0x1587, 0xd40f, 0xc211, 0xba12, 0xda04, 0xe9d2, 0xf097, 0x2593,
+0x4a0d, 0x33f2, 0x0e58, 0x2f92, 0x1796, 0xd23e, 0xc5d1, 0xbd1d, 0xd6a8, 0xea03,
+0xfd4b, 0x2b76, 0x4d07, 0x372b, 0x12c1, 0x3610, 0x1455, 0xc9fa, 0xc082, 0xc65f,
+0xdbf3, 0xe5fa, 0x0864, 0x3695, 0x4d6d, 0x3441, 0x13be, 0x2f2f, 0x090c, 0xce34,
+0xb830, 0xc505, 0xfada, 0xec81, 0xfb68, 0x2eba, 0x319e, 0x3ce2, 0x44fb, 0x1d01,
+0xdd9d, 0xd66a, 0xb232, 0xa016, 0xf64b, 0xfff6, 0x09d5, 0x7376, 0x8570, 0x20f6,
+0xfe82, 0xf200, 0x9a52, 0xa325, 0xc4f5, 0xcbde, 0x2c79, 0x67bb, 0x4c8f, 0x46a8,
+0x523c, 0xf79d, 0xabd0, 0xda12, 0xcc15, 0xb71c, 0xf62d, 0x1e60, 0x327a, 0x4b18,
+0x2770, 0xf598, 0x157f, 0x094e, 0xbe89, 0xebf7, 0x2a77, 0xf098, 0xe9ee, 0x28e2,
+0xf32a, 0xc056, 0x237c, 0x271d, 0xe4a4, 0x3978, 0x35ba, 0xe536, 0x10f5, 0xfdaa,
+0xa68b, 0xd3ea, 0x212f, 0xea07, 0x08a6, 0x5e96, 0x0ae2, 0x07c6, 0x37b6, 0xd70c,
+0xb092, 0xfe7d, 0xf21a, 0xcad9, 0x34d2, 0x36e9, 0xf083, 0x37da, 0x22a5, 0xbc01,
+0xdc00, 0x1395, 0xd553, 0xe5e3, 0x3bac, 0xfd08, 0xfe92, 0x3f5c, 0xee23, 0xbc45,
+0x0d39, 0x0a9c, 0xc61a, 0x146f, 0x2951, 0xdb39, 0x171e, 0x293d, 0xc2e7, 0xdd20,
+0x32b9, 0xe330, 0xd7b9, 0x3cc5, 0xf7d9, 0xdcad, 0x311b, 0xf1df, 0xb0b8, 0x105b,
+0x1a8c, 0xca51, 0x1be5, 0x2f80, 0xd616, 0x11ca, 0x2652, 0xbc4e, 0xcb96, 0x21a7,
+0xe4b4, 0xe253, 0x4487, 0x0196, 0xe674, 0x3516, 0xfb04, 0xb3bc, 0xfeb8, 0x1079,
+0xc24e, 0x088e, 0x3298, 0xe80b, 0x14c1, 0x2ed3, 0xd1a8, 0xcf82, 0x2207, 0xee50,
+0xcf02, 0x2dec, 0x08af, 0xe42a, 0x344d, 0x0fb2, 0xb971, 0xfa3f, 0x1df7, 0xd32e,
+0x11cc, 0x3722, 0xda30, 0x07ac, 0x3d3f, 0xd7ab, 0xc34d, 0x271b, 0xfe76, 0xdbb3,
+0x3d61, 0x18ed, 0xed9b, 0x3389, 0x0c14, 0xba7c, 0xf987, 0x187e, 0xcd50, 0x1066,
+0x476b, 0xf480, 0x1314, 0x42bb, 0xe5fe, 0xc3be, 0x1389, 0xf39f, 0xd575, 0x3648,
+0x1e47, 0xf2fa, 0x3e74, 0x234a, 0xbf4c, 0xf288, 0x221b, 0xc710, 0xfa3c, 0x5035,
+0xfb9e, 0x0502, 0x44eb, 0xeee6, 0xc43e, 0x1d61, 0xfb1c, 0xc9de, 0x3c74, 0x2aae,
+0xe190, 0x3674, 0x2619, 0xaff2, 0xe8b6, 0x2937, 0xc78c, 0xf5df, 0x540b, 0xfb05,
+0x020b, 0x434b, 0xe0e6, 0xacc1, 0x10fc, 0xf91d, 0xbcee, 0x3592, 0x3566, 0xe36e,
+0x337d, 0x30bf, 0xb32f, 0xd426, 0x2162, 0xc820, 0xe042, 0x4c56, 0xfe46, 0xf61f,
+0x4a89, 0xf803, 0xad4d, 0x11a6, 0x0a6c, 0xb6d1, 0x253a, 0x3311, 0xd5a0, 0x2667,
+0x3498, 0xb800, 0xd872, 0x2f83, 0xd41e, 0xe4e1, 0x5463, 0xf738, 0xea30, 0x492c,
+0xf007, 0xaab2, 0x16ec, 0x11da, 0xc262, 0x2e37, 0x3e3b, 0xdd03, 0x22dd, 0x30e8,
+0xb674, 0xd170, 0x2e2c, 0xdcd6, 0xe399, 0x56b9, 0x04f1, 0xea52, 0x4a44, 0xfd31,
+0xa60d, 0x09bf, 0x162d, 0xbcc9, 0x1f55, 0x4352, 0xde68, 0x1cd9, 0x3c0d, 0xbf98,
+0xc8d8, 0x2a1f, 0xdc64, 0xd503, 0x4d6c, 0x0785, 0xe7a8, 0x476e, 0xff01, 0xa5cd,
+0x064f, 0x17bf, 0xbe55, 0x1800, 0x3b7b, 0xd4d9, 0x10f0, 0x3690, 0xbf8d, 0xc71e,
+0x297a, 0xe393, 0xd729, 0x47fe, 0x0285, 0xda51, 0x391b, 0xfeb3, 0xa48e, 0xfbc3,
+0x19a9, 0xc3a0, 0x108e, 0x3eae, 0xdebc, 0x0f12, 0x36a2, 0xc4c0, 0xbf91, 0x1e2f,
+0xe5de, 0xd395, 0x4354, 0x10d5, 0xe319, 0x39c0, 0x0a87, 0xace4, 0xf7d2, 0x18b7,
+0xc582, 0x0b6f, 0x3da5, 0xe290, 0x0dde, 0x3c3e, 0xcfe6, 0xc55b, 0x253d, 0xeca1,
+0xcf94, 0x3b7a, 0x0f41, 0xdf74, 0x366b, 0x1370, 0xb644, 0xfb14, 0x1f1e, 0xc7c9,
+0x07b4, 0x41e9, 0xe70d, 0x071d, 0x3d77, 0xdb66, 0xc478, 0x265f, 0xf916, 0xd180,
+0x3ee6, 0x1beb, 0xdeb9, 0x31d9, 0x191a, 0xb479, 0xf52c, 0x2801, 0xccf7, 0x03d4,
+0x4bd7, 0xedac, 0x016a, 0x3a65, 0xd6fc, 0xbe27, 0x2266, 0x07f4, 0xd0c6, 0x0dcf,
+0x1281, 0x0eb0, 0x45e8, 0x1d9a, 0xd8e3, 0x05c1, 0x084a, 0xb5be, 0xdaec, 0x09a2,
+0xe1b9, 0x11ee, 0x6086, 0x43d5, 0x17db, 0x2892, 0xfb50, 0xb60e, 0xc9f7, 0xc054,
+0xc68d, 0x23e0, 0x2ffe, 0x062b, 0x4e99, 0x701f, 0xf80e, 0xd329, 0xff2c, 0xcb90,
+0xa2d6, 0xed8c, 0x1910, 0x0699, 0x2f80, 0x3089, 0x1d1c, 0x374f, 0x03b2, 0xc14c,
+0xd8b6, 0xfb3d, 0xd617, 0xe1a7, 0x22a2, 0x2021, 0x1a55, 0x1dcb, 0x2025, 0x150c,
+0x0753, 0xe11c, 0xb5ad, 0xeb4f, 0xe450, 0xcc01, 0x1b1d, 0x3faa, 0x18aa, 0x0e6d,
+0x4970, 0x17e5, 0xcba3, 0xd80b, 0xbb23, 0xc5d9, 0xe755, 0xf01c, 0x158e, 0x5028,
+0x3e7d, 0xfcdb, 0x3482, 0x21eb, 0xbae5, 0xbfbe, 0xd2fb, 0xcfc4, 0xd821, 0x0aca,
+0x27bb, 0x3924, 0x3d1e, 0x0e86, 0x271a, 0x205b, 0xd16c, 0xaf53, 0xc9df, 0xe377,
+0xce6f, 0xfa21, 0x34f1, 0x4083, 0x3410, 0x259d, 0x319d, 0x06b6, 0xd9dc, 0xb750,
+0xb2df, 0xde42, 0xd8e4, 0xfe96, 0x4970, 0x54f6, 0x25ba, 0x2520, 0x3c2e, 0xedea,
+0xc061, 0xba0d, 0xba48, 0xde9c, 0xeb84, 0x0af2, 0x401e, 0x56b2, 0x1aa5, 0x0f66,
+0x36cf, 0xe844, 0xb80e, 0xc1dd, 0xc246, 0xcece, 0xe9fe, 0x177a, 0x32d2, 0x4547,
+0x1818, 0x0c52, 0x30b6, 0xec12, 0xaf1a, 0xb2f2, 0xcfa8, 0xd2b1, 0xdf9e, 0x2116,
+0x3ab2, 0x35ab, 0x1b48, 0x247c, 0x25e1, 0xdedd, 0xb1e5, 0xaf2b, 0xdb4a, 0xe17b,
+0xdfc7, 0x27da, 0x4f71, 0x34be, 0x0ed1, 0x2d58, 0x1ef5, 0xd091, 0xc1bb, 0xbe47,
+0xd524, 0xeaee, 0xf469, 0x2112, 0x4cf4, 0x3d3e, 0x0aff, 0x304c, 0x281a, 0xcdc7,
+0xbbed, 0xd088, 0xe04a, 0xdeea, 0xfee9, 0x2f94, 0x47fc, 0x3dff, 0x1a27, 0x31e9,
+0x1deb, 0xd894, 0xbec6, 0xc6e8, 0xe5fc, 0xe340, 0x014d, 0x3cd1, 0x4e2f, 0x32bd,
+0x1d0c, 0x372e, 0x0af1, 0xce88, 0xc502, 0xc97d, 0xe495, 0xeb9c, 0x0798, 0x374a,
+0x4e4d, 0x2a7e, 0x16b3, 0x3ddd, 0x08ed, 0xc949, 0xc5ee, 0xce08, 0xdf28, 0xe8b7,
+0x0fe9, 0x3689, 0x490a, 0x2817, 0x1714, 0x38c1, 0x0109, 0xc527, 0xc1af, 0xd087,
+0xdced, 0xe41e, 0x12dc, 0x3476, 0x3cd2, 0x201b, 0x1cbb, 0x2e40, 0xf269, 0xc5fe,
+0xb844, 0xcaac, 0xdf7f, 0xe02f, 0x0f09, 0x373a, 0x383b, 0x13eb, 0x21ef, 0x2cbc,
+0xe41a, 0xc568, 0xb82e, 0xc319, 0xdca2, 0xe63b, 0x0f93, 0x3ca2, 0x4202, 0x11af,
+0x2392, 0x2ac6, 0xd906, 0xb7d3, 0xb715, 0xce35, 0xdb89, 0xef49, 0x2213, 0x3ff6,
+0x3f4b, 0x14d9, 0x2398, 0x1f27, 0xd7ab, 0xb866, 0xb50a, 0xd74b, 0xe11e, 0xf58b,
+0x2ece, 0x467f, 0x3963, 0x1933, 0x2caa, 0x1426, 0xd5d8, 0xbe81, 0xb620, 0xdf76,
+0xe789, 0xf65e, 0x349f, 0x505c, 0x3366, 0x16ea, 0x3646, 0x0f5a, 0xd18a, 0xc1b2,
+0xb7f7, 0xdd2a, 0xeb2f, 0xfd79, 0x30f5, 0x516e, 0x31d6, 0x12af, 0x388a, 0x0be7,
+0xca82, 0xbdf0, 0xbc89, 0xda3c, 0xe687, 0x06e8, 0x3695, 0x5204, 0x3323, 0x158c,
+0x3447, 0x05f1, 0xca02, 0xb88d, 0xc1cf, 0xe381, 0xea5c, 0x110d, 0x40c6, 0x4e27,
+0x2a69, 0x1e8c, 0x30b5, 0xf349, 0xc5eb, 0xb6f0, 0xc440, 0xe7cd, 0xebf2, 0x1281,
+0x4308, 0x4ce7, 0x1f25, 0x1bf5, 0x2d5c, 0xe897, 0xc291, 0xb85f, 0xc6be, 0xe556,
+0xf257, 0x1cc0, 0x442d, 0x4d14, 0x200a, 0x1f36, 0x26a6, 0xdf96, 0xbef2, 0xb898,
+0xd0e1, 0xe9a1, 0xf513, 0x2536, 0x486a, 0x4666, 0x1bd4, 0x216e, 0x1a61, 0xd966,
+0xbfd6, 0xb46f, 0xd414, 0xeb0f, 0xf5c1, 0x2917, 0x4b03, 0x3b01, 0x120a, 0x279d,
+0x127b, 0xce60, 0xc0b2, 0xbaa1, 0xd93a, 0xea16, 0xf79d, 0x29e1, 0x4e28, 0x3952,
+0x0c9b, 0x2918, 0x0da3, 0xc8e3, 0xbf5d, 0xbd5b, 0xd7be, 0xe6b5, 0xff78, 0x2bfe,
+0x474e, 0x30ea, 0x0afb, 0x2996, 0x09da, 0xc618, 0xb66b, 0xc002, 0xde8f, 0xe009,
+0x0068, 0x34b1, 0x479e, 0x290d, 0x1057, 0x2cf4, 0x0037, 0xc901, 0xba19, 0xc161,
+0xe4c1, 0xe626, 0x06b7, 0x3bce, 0x46e6, 0x1fc6, 0x1c43, 0x37dd, 0xf4a3, 0xc6e9,
+0xc0da, 0xc57a, 0xe45d, 0xe96e, 0x0b68, 0x3e09, 0x4c53, 0x1c9e, 0x1978, 0x350c,
+0xec82, 0xc2fd, 0xc069, 0xc683, 0xdf64, 0xee42, 0x1619, 0x39da, 0x4685, 0x1dc8,
+0x1c78, 0x2de4, 0xe52f, 0xbb56, 0xb88c, 0xd10f, 0xe424, 0xed39, 0x226e, 0x45c9,
+0x4373, 0x1798, 0x1d16, 0x2213, 0xe050, 0xbf9c, 0xb315, 0xd32f, 0xe7c5, 0xec31,
+0x23d2, 0x48dd, 0x38f4, 0x0de0, 0x25fc, 0x1996, 0xcef8, 0xbc82, 0xb55a, 0xd59c,
+0xe89f, 0xefe0, 0x23a4, 0x4993, 0x37a4, 0x0c32, 0x2be5, 0x17d1, 0xcddd, 0xc07a,
+0xb8e9, 0xd329, 0xe54b, 0xfb4e, 0x2cfc, 0x4d3a, 0x3bad, 0x0fee, 0x2b2b, 0x1047,
+0xc712, 0xb4f7, 0xbbf0, 0xdc5f, 0xe2f3, 0xfef6, 0x224e, 0x4c36, 0x6c76, 0x2b5c,
+0xfa57, 0xe594, 0xbdb1, 0x8b0a, 0xa7ca, 0x0160, 0x0786, 0x400c, 0x8424, 0x5796,
+0x1be9, 0xf540, 0xbdf9, 0x9768, 0xc4df, 0xbd3d, 0xdd3d, 0x63d3, 0x4e65, 0x1fea,
+0x4e57, 0x38bb, 0xd91b, 0xcd56, 0xe3ad, 0xbffd, 0xfbd6, 0x0607, 0xdb8e, 0x3ccb,
+0x4192, 0xd6b0, 0xf73b, 0x42bf, 0xeff1, 0xe330, 0x3dbd, 0xfa84, 0xdccf, 0x13df,
+0xde74, 0xb718, 0x0b45, 0x28d2, 0xfb25, 0x3fa0, 0x4276, 0xedb2, 0x1335, 0x0954,
+0xadc9, 0xc6fc, 0x1522, 0xf53c, 0xfe45, 0x4b87, 0x18e7, 0x04a7, 0x3412, 0xee3f,
+0xb321, 0xfd48, 0x04c9, 0xc508, 0x15a8, 0x372c, 0xeb2f, 0x165c, 0x2ca0, 0xce55,
+0xd34d, 0x1c95, 0xe2fd, 0xdd7e, 0x3294, 0xf905, 0xe452, 0x34a4, 0x04d0, 0xbdd7,
+0x0a01, 0x1d14, 0xce45, 0x0c02, 0x2888, 0xd716, 0x03dc, 0x2b4a, 0xd5cd, 0xd68c,
+0x2ef2, 0xfc10, 0xda6f, 0x316f, 0x0773, 0xda8e, 0x2215, 0x0507, 0xb6f3, 0xfe70,
+0x2a5d, 0xd6c8, 0x0b84, 0x4188, 0xe8c4, 0xfdd0, 0x2ec5, 0xd794, 0xc3a2, 0x2279,
+0xf779, 0xd09d, 0x423d, 0x27fc, 0xe6f8, 0x2f40, 0x1b5a, 0xbc8b, 0xf157, 0x1fb4,
+0xcaa0, 0xfa3a, 0x4232, 0xf922, 0x0ecb, 0x3f09, 0xe3ee, 0xc318, 0x1cef, 0xfdc1,
+0xca4c, 0x2a22, 0x20f1, 0xe87d, 0x279c, 0x1afc, 0xbe72, 0xe720, 0x2220, 0xd1d4,
+0xf6a7, 0x4e3e, 0xf5f4, 0xecca, 0x3b54, 0xf567, 0xb06f, 0x0a45, 0x0b98, 0xc973,
+0x28f0, 0x2f21, 0xdfeb, 0x24a0, 0x2810, 0xaef4, 0xd207, 0x2ab3, 0xcd51, 0xdc82,
+0x4ca3, 0xfdde, 0xef82, 0x40ab, 0xf143, 0xac33, 0x082b, 0xfdac, 0xb9b7, 0x28f3,
+0x2b71, 0xd054, 0x2723, 0x3651, 0xb6cf, 0xd176, 0x2ba8, 0xd75d, 0xdb92, 0x450f,
+0xfd8f, 0xec9c, 0x3e23, 0xf598, 0xaf02, 0x111a, 0x135a, 0xbd2d, 0x2334, 0x3d0a,
+0xd3d8, 0x1768, 0x3bb0, 0xbab9, 0xc676, 0x311a, 0xe06f, 0xd889, 0x5018, 0x070b,
+0xe756, 0x4942, 0xfd09, 0x9d71, 0x0bcc, 0x1bed, 0xb4ce, 0x1c66, 0x47ec, 0xdc5f,
+0x1b74, 0x4238, 0xc481, 0xcf1b, 0x32f5, 0xe1ce, 0xda85, 0x4e4d, 0x0437, 0xe474,
+0x4777, 0x07ed, 0xaff9, 0x127d, 0x24cd, 0xc370, 0x199d, 0x3e29, 0xd8bf, 0x14fd,
+0x3d8b, 0xc8ea, 0xd1b6, 0x3766, 0xecd3, 0xda6f, 0x4fa7, 0x0ce7, 0xdddd, 0x4019,
+0x0c05, 0xaedb, 0x0dbc, 0x2b4c, 0xcdd3, 0x1ddc, 0x470d, 0xe283, 0x1764, 0x40d4,
+0xcabb, 0xcd66, 0x3585, 0xf1c2, 0xdda9, 0x4ffb, 0x11e4, 0xe28a, 0x415d, 0x12d2,
+0xb486, 0x055e, 0x1fd4, 0xc66e, 0x12ca, 0x417e, 0xe4e2, 0x1229, 0x3e2a, 0xd10c,
+0xc800, 0x29e0, 0xed4e, 0xd10c, 0x3fcc, 0x11a3, 0xe1f9, 0x3ad7, 0x16ca, 0xb727,
+0x0155, 0x2400, 0xc8c6, 0x0c22, 0x40bf, 0xe1dc, 0x06e9, 0x3e89, 0xd924, 0xc59a,
+0x27a2, 0xf22c, 0xcfad, 0x3f51, 0x14cd, 0xda51, 0x2f15, 0x1235, 0xae68, 0xedef,
+0x1983, 0xc602, 0x06d9, 0x46e6, 0xe9de, 0x01cd, 0x3928, 0xd470, 0xb512, 0x15ff,
+0xec31, 0xc9cd, 0x3f78, 0x243a, 0xe15e, 0x29ed, 0x1245, 0xaba1, 0xe5e9, 0x199e,
+0xc296, 0xfe39, 0x4ac5, 0xeb5f, 0xfa56, 0x3c11, 0xd94f, 0xae50, 0x1511, 0xf001,
+0xbde3, 0x36c2, 0x230d, 0xd7fe, 0x2c17, 0x1e3a, 0xaa06, 0xe31f, 0x226e, 0xc144,
+0xf626, 0x4f4e, 0xeb1e, 0xf4dd, 0x3e8c, 0xdc0a, 0xaf47, 0x1e56, 0xfed8, 0xc48e,
+0x3d56, 0x2b0f, 0xd682, 0x2918, 0x1dec, 0xa955, 0xe5bb, 0x2b6b, 0xc9b8, 0xfa77,
+0x56f8, 0xf481, 0xfb61, 0x4479, 0xdf2e, 0xabca, 0x1c70, 0xffe7, 0xbc88, 0x3a59,
+0x3826, 0xe054, 0x2f4b, 0x2c11, 0xb1dd, 0xe03e, 0x2b29, 0xc998, 0xf18d, 0x59ee,
+0xf7ac, 0xf73d, 0x4d8e, 0xed67, 0xb1d8, 0x21a9, 0x1848, 0xccda, 0x07a8, 0xffcd,
+0xfa23, 0x5aea, 0x3797, 0xd62f, 0x0ab0, 0x245e, 0xb225, 0xc27c, 0x09d2, 0xd863,
+0xfe56, 0x5bb6, 0x4ddb, 0x2aa0, 0x3bda, 0x0b4a, 0xc172, 0xdcc2, 0xc178, 0xb04a,
+0x211f, 0x3a69, 0xf99a, 0x3316, 0x7dfa, 0x1afe, 0xd954, 0x046d, 0xdf49, 0xb051,
+0xdb3e, 0x09de, 0x0f9f, 0x347e, 0x2f84, 0x0af2, 0x3895, 0x1ade, 0xc5b6, 0xd468,
+0xfe63, 0xe6d0, 0xddae, 0x14b5, 0x175e, 0x16c3, 0x1fae, 0x122e, 0x1fcb, 0x16f5,
+0xef7f, 0xbe99, 0xdd8a, 0xf61b, 0xc6ce, 0x019a, 0x43c3, 0x2909, 0x1168, 0x39ad,
+0x2f76, 0xde14, 0xddd2, 0xc963, 0xbd2a, 0xea45, 0xea62, 0x0266, 0x4616, 0x4e1c,
+0x015e, 0x18de, 0x36f6, 0xcf20, 0xb656, 0xd210, 0xd017, 0xd8b3, 0xfb2f, 0x1950,
+0x2b27, 0x3c69, 0x095e, 0x0ec5, 0x2d9c, 0xe85e, 0xb75b, 0xc4ac, 0xe1a3, 0xcfe0,
+0xdf2a, 0x1a10, 0x3183, 0x376b, 0x1d4c, 0x237d, 0x185c, 0xe4f3, 0xbf7b, 0xab18,
+0xcfc2, 0xd346, 0xe11c, 0x2903, 0x45ea, 0x24aa, 0x102b, 0x353d, 0x080f, 0xc677,
+0xbdeb, 0xb32e, 0xd477, 0xe331, 0xef66, 0x2325, 0x4c5c, 0x27b3, 0xfaf5, 0x3106,
+0x1252, 0xc709, 0xca81, 0xcbe5, 0xd04d, 0xdd4e, 0xfd3c, 0x1a5f, 0x3c21, 0x2935,
+0x0252, 0x3750, 0x1e8e, 0xc8e4, 0xb83e, 0xd48f, 0xdc6e, 0xce04, 0x0585, 0x31bc,
+0x360a, 0x2009, 0x14c2, 0x394e, 0x10a3, 0xc93d, 0xafa5, 0xd6b1, 0xef04, 0xcd94,
+0x0467, 0x48be, 0x4051, 0x1437, 0x1fb5, 0x3cd3, 0xf79e, 0xcc8f, 0xc153, 0xcd1f,
+0xeeb7, 0xe37c, 0x03cf, 0x4174, 0x4958, 0x1000, 0x1d3b, 0x4853, 0xf289, 0xbea9,
+0xc9e2, 0xd4e7, 0xde7b, 0xe633, 0x1461, 0x3e75, 0x4911, 0x1b5a, 0x2106, 0x4059,
+0xf467, 0xbf92, 0xc1f8, 0xd995, 0xdcf6, 0xe0ba, 0x1eb5, 0x4600, 0x4167, 0x1d25,
+0x2ef9, 0x3639, 0xe56b, 0xc105, 0xc027, 0xd70a, 0xe1eb, 0xea01, 0x247e, 0x4c66,
+0x3f10, 0x14c8, 0x3245, 0x3094, 0xdd32, 0xc132, 0xc147, 0xd9a1, 0xe18c, 0xeeb8,
+0x2824, 0x4a69, 0x395c, 0x1015, 0x3040, 0x238d, 0xd48a, 0xc21b, 0xc53b, 0xdc6d,
+0xe1ce, 0xf6ef, 0x2ad6, 0x46b7, 0x34da, 0x1114, 0x34eb, 0x1e8c, 0xd361, 0xbe8c,
+0xc1c6, 0xe012, 0xdf71, 0xf81f, 0x3359, 0x4a06, 0x2d71, 0x1454, 0x38da, 0x0f58,
+0xccf4, 0xbea1, 0xbbb5, 0xdb25, 0xe2c4, 0xfd77, 0x3627, 0x5121, 0x2c35, 0x15a0,
+0x3989, 0x0018, 0xc03f, 0xbaf5, 0xc3cd, 0xe02b, 0xe6c9, 0x0e3d, 0x3eb5, 0x4d58,
+0x26cb, 0x1318, 0x2fe0, 0xf891, 0xc27d, 0xb828, 0xc71e, 0xe21d, 0xe699, 0x15c5,
+0x4058, 0x45ef, 0x2262, 0x1985, 0x28bc, 0xeca7, 0xc32c, 0xb7e4, 0xcfc1, 0xea07,
+0xe4ac, 0x173a, 0x461c, 0x3eba, 0x15fa, 0x2087, 0x2703, 0xe29a, 0xc4b3, 0xb600,
+0xc8f7, 0xe687, 0xe9ba, 0x15c4, 0x426b, 0x3e74, 0x0edd, 0x1f75, 0x23b8, 0xd925,
+0xc0ab, 0xba1f, 0xcb2d, 0xe095, 0xeca2, 0x1990, 0x40e5, 0x3c5d, 0x1023, 0x24d2,
+0x1ebd, 0xd280, 0xbab7, 0xba30, 0xd226, 0xe0d0, 0xf39a, 0x22d6, 0x4063, 0x34fc,
+0x0f3b, 0x2825, 0x190f, 0xd310, 0xbcd9, 0xbd2e, 0xd80b, 0xdd5a, 0xf3ce, 0x2b22,
+0x4783, 0x32fb, 0x13a4, 0x3361, 0x1484, 0xcf84, 0xc067, 0xc184, 0xdb6a, 0xe3e8,
+0x0230, 0x3122, 0x4768, 0x320a, 0x16c1, 0x3427, 0x0aed, 0xc9bb, 0xba81, 0xc328,
+0xe12c, 0xe1e3, 0x07e1, 0x3ec1, 0x4f67, 0x2ddc, 0x15f0, 0x2f32, 0xfe24, 0xc964,
+0xbbfd, 0xc754, 0xe848, 0xe9ef, 0x1196, 0x41b3, 0x4b5f, 0x2760, 0x1f91, 0x35ce,
+0xf3d5, 0xc722, 0xbfad, 0xcade, 0xe85a, 0xeaa5, 0x1660, 0x4809, 0x4c60, 0x1f11,
+0x212f, 0x36b0, 0xefd1, 0xc764, 0xbc28, 0xc837, 0xe455, 0xec50, 0x1c88, 0x4b3c,
+0x4de3, 0x2041, 0x28c6, 0x32d7, 0xe1ea, 0xbea7, 0xbd29, 0xd1a9, 0xe60c, 0xf02d,
+0x2433, 0x4c51, 0x4811, 0x1816, 0x23ce, 0x292b, 0xdf35, 0xbcec, 0xb6b3, 0xd50d,
+0xe49a, 0xee6a, 0x2b0c, 0x4b27, 0x39f9, 0x15f5, 0x29d8, 0x1859, 0xd36e, 0xc04c,
+0xb5ab, 0xd4b0, 0xe461, 0xec0c, 0x2803, 0x4fb2, 0x371f, 0x0f49, 0x2de8, 0x1063,
+0xc7dd, 0xbbcc, 0xb195, 0xcdad, 0xe2b5, 0xf6ed, 0x29d0, 0x4ac5, 0x31e4, 0x0aa9,
+0x2acb, 0x0735, 0xbe72, 0xb39c, 0xb620, 0xd1d9, 0xdc5c, 0xfcdd, 0x307a, 0x46be,
+0x2c7c, 0x0a2b, 0x22ab, 0xfca8, 0xc0ac, 0xafc4, 0xb815, 0xde74, 0xe13f, 0x0269,
+0x370e, 0x4107, 0x21c4, 0x13c8, 0x26a3, 0xf061, 0xc3ac, 0xb5ad, 0xbbea, 0xe2b6,
+0xe314, 0x03cd, 0x3a8a, 0x453e, 0x1c0b, 0x167a, 0x2afd, 0xe8ae, 0xc330, 0xbb1d,
+0xbd71, 0xdff6, 0xea8c, 0x0cd6, 0x3cc1, 0x4b59, 0x1e08, 0x1762, 0x288a, 0xe327,
+0xbcf5, 0xbc04, 0xcc41, 0xe3b8, 0xf214, 0x1f63, 0x40b7, 0x4556, 0x1932, 0x14e9,
+0x22d4, 0xe5af, 0xc176, 0xbaf1, 0xd516, 0xeaa3, 0xed1a, 0x10a5, 0x4490, 0x6d0e,
+0x387d, 0x06ad, 0xf979, 0xc011, 0x9e3c, 0xad92, 0xe582, 0x1234, 0x4531, 0x6a71,
+0x4d3b, 0x3443, 0xfe14, 0xbf35, 0xb76f, 0xcbe7, 0xc8b4, 0xe9aa, 0x501f, 0x43d8,
+0x1d18, 0x4560, 0x2e0d, 0xf4de, 0xea75, 0xe7b7, 0xd73f, 0x0fdc, 0x11cd, 0xd145,
+0x29c3, 0x44d4, 0xdad6, 0xf4f0, 0x3e51, 0x092e, 0x07b6, 0x43ce, 0xfd5a, 0xf591,
+0x1f19, 0xc2ea, 0xb2d7, 0x198e, 0x140f, 0xfa53, 0x5e12, 0x4564, 0xf1ae, 0x2e33,
+0x0b49, 0xa4bb, 0xdaed, 0x1485, 0xdc90, 0x0cbf, 0x4f0d, 0x0854, 0x15b5, 0x3b75,
+0xe6b5, 0xbe4e, 0x0981, 0xfc23, 0xc6c6, 0x22a2, 0x2984, 0xf1db, 0x27f9, 0x1ab0,
+0xc60e, 0xe49e, 0x21a8, 0xd681, 0xe953, 0x41f4, 0xf337, 0xeb9d, 0x3560, 0xf514,
+0xbd12, 0x135a, 0x1144, 0xc99f, 0x21e0, 0x2271, 0xd155, 0x1a35, 0x25a3, 0xbec7,
+0xdc7d, 0x31ed, 0xe802, 0xe46d, 0x3ef6, 0xfc72, 0xeb7d, 0x2fc5, 0xecf1, 0xb132,
+0x0d6d, 0x171d, 0xcaf3, 0x20be, 0x36cd, 0xdeff, 0x1430, 0x2c97, 0xc49c, 0xcab8,
+0x247c, 0xe2b5, 0xd3f1, 0x3d93, 0x0c22, 0xee37, 0x3907, 0x01ea, 0xb32c, 0x07e5,
+0x153f, 0xb7d0, 0x0a61, 0x333d, 0xdb8a, 0x1138, 0x3892, 0xcfca, 0xccd1, 0x2831,
+0xeb12, 0xd73f, 0x357e, 0xfe1d, 0xe21b, 0x300a, 0xfded, 0xaf91, 0xffaf, 0x1ba1,
+0xc5b1, 0x0c52, 0x3c73, 0xe33f, 0xfac8, 0x2338, 0xd7bc, 0xc7f4, 0x0f99, 0xe739,
+0xd9e0, 0x3980, 0x0e6d, 0xe382, 0x335e, 0x15f9, 0xae82, 0xe698, 0x1c86, 0xca1d,
+0xf6f1, 0x34d2, 0xf14d, 0x0fec, 0x3c9a, 0xdef1, 0xc8df, 0x20f3, 0xea68, 0xc50a,
+0x33ef, 0x1185, 0xd7d4, 0x2ebc, 0x2178, 0xc1c1, 0xf72b, 0x2354, 0xd2eb, 0x0168,
+0x30db, 0xe0a6, 0x05a6, 0x3bad, 0xdc09, 0xc45f, 0x276d, 0xfd11, 0xcf0a, 0x363e,
+0x204b, 0xe4d0, 0x2851, 0x199c, 0xbc3c, 0xe9df, 0x1c38, 0xcf05, 0x03b3, 0x4c0e,
+0xf6ee, 0x068d, 0x44a0, 0xe3e0, 0xb163, 0x17fa, 0xfdee, 0xc709, 0x37cd, 0x2e63,
+0xea1c, 0x3531, 0x2ca0, 0xbf9e, 0xeeec, 0x2b06, 0xc5f7, 0xf1c8, 0x5385, 0xfc84,
+0x0002, 0x49cb, 0xf52b, 0xbfbf, 0x204d, 0x04c6, 0xc649, 0x35e9, 0x30b9, 0xe658,
+0x31b2, 0x2cb7, 0xb96b, 0xe5a8, 0x2f24, 0xd410, 0xf6ab, 0x5771, 0x04b9, 0xfc36,
+0x3ec2, 0xecd7, 0xb28a, 0x1904, 0x0954, 0xc24d, 0x3764, 0x3ade, 0xddf2, 0x29ce,
+0x3445, 0xb4d5, 0xd352, 0x2d79, 0xd037, 0xe09a, 0x535a, 0x01a5, 0xf095, 0x48ef,
+0xf767, 0xa8aa, 0x11b3, 0x097b, 0xb15c, 0x2a36, 0x3db4, 0xd6cd, 0x24b8, 0x3712,
+0xb430, 0xcdb4, 0x2b60, 0xd3aa, 0xe09b, 0x53eb, 0xfc4a, 0xea6b, 0x4b2a, 0xf90e,
+0xa8c8, 0x1124, 0x129b, 0xbaac, 0x269d, 0x4030, 0xda96, 0x20e1, 0x3a2e, 0xbd1c,
+0xd0d8, 0x3158, 0xdd88, 0xdee5, 0x57dc, 0x0a22, 0xeb70, 0x4be8, 0x027e, 0xa602,
+0x0680, 0x1714, 0xbfed, 0x242e, 0x47b1, 0xe011, 0x1d8e, 0x3eca, 0xc029, 0xc616,
+0x2e06, 0xe28f, 0xd4aa, 0x4f9e, 0x0f06, 0xe649, 0x438d, 0x0564, 0xa6f1, 0x02f6,
+0x1bd3, 0xc020, 0x18b7, 0x4204, 0xd71b, 0x0fc8, 0x3e52, 0xc2b8, 0xbf78, 0x2bd7,
+0xe6d3, 0xce7b, 0x47d7, 0x0a5d, 0xd7d4, 0x39c7, 0x074e, 0xa273, 0xfc77, 0x1fef,
+0xbcf9, 0x0cec, 0x426a, 0xd4f2, 0x044f, 0x3d93, 0xc6ea, 0xbcdf, 0x2bd1, 0xed0b,
+0xce10, 0x43d0, 0x0ca2, 0xd5d2, 0x35c4, 0x0c15, 0xa646, 0xfa2d, 0x2097, 0xc0dd,
+0x0c2b, 0x439b, 0xdc9c, 0x059f, 0x3b30, 0xca4a, 0xbbcb, 0x2337, 0xe7a3, 0xc717,
+0x3dae, 0x1491, 0xdc09, 0x3229, 0x0f3e, 0xac38, 0xf62a, 0x25d7, 0xcda9, 0xe86a,
+0x0ccd, 0xe87d, 0x2a51, 0x46ee, 0xea42, 0xe40d, 0x22b1, 0xd631, 0xa84b, 0xf868,
+0xe4f9, 0xdc35, 0x395d, 0x50be, 0x1fd6, 0x25ac, 0x1e57, 0xc641, 0xc358, 0xd0f2,
+0xac56, 0xf1aa, 0x385d, 0x08e0, 0x0c0c, 0x6c90, 0x3d9a, 0xd534, 0xf575, 0xf32f,
+0xb660, 0xc715, 0x00c3, 0x060a, 0x1cc0, 0x3bee, 0x0fef, 0x2648, 0x390e, 0xe520,
+0xcaa4, 0xfc07, 0xfa8c, 0xcfc3, 0xff3b, 0x2146, 0x0eaa, 0x22f7, 0x21eb, 0x22a5,
+0x2b3f, 0x140f, 0xd035, 0xce0d, 0x018b, 0xcdfd, 0xe402, 0x4024, 0x35a0, 0x11b4,
+0x30f7, 0x48c6, 0xfc36, 0xe429, 0xd9e4, 0xb819, 0xe9ae, 0xecf6, 0xeaac, 0x339b,
+0x5af6, 0x18c2, 0x0a7d, 0x49f9, 0xfaf8, 0xba8d, 0xd5ae, 0xd0dc, 0xd43b, 0xee6d,
+0x117b, 0x23d1, 0x46ee, 0x29ad, 0x0339, 0x3a96, 0x1314, 0xc110, 0xbad0, 0xd9cf,
+0xd706, 0xcd74, 0x0d91, 0x315e, 0x3f9c, 0x359b, 0x2190, 0x2aef, 0x0433, 0xcdb2,
+0xa574, 0xc711, 0xe12e, 0xcf40, 0x197f, 0x5324, 0x3d77, 0x20a5, 0x375c, 0x2d03,
+0xe262, 0xca54, 0xb0b0, 0xc640, 0xed6e, 0xe3a9, 0x14a7, 0x559a, 0x4789, 0x0af5,
+0x2c0d, 0x3078, 0xd574, 0xc8f1, 0xc60c, 0xc4a3, 0xddbb, 0xf434, 0x12bf, 0x3a4f,
+0x46b5, 0x0de9, 0x28f8, 0x3a3e, 0xd862, 0xb36e, 0xc4b9, 0xd290, 0xcc9e, 0xf150,
+0x28e5, 0x36b7, 0x3727, 0x1942, 0x296a, 0x1f3b, 0xd304, 0xaa50, 0xb7e0, 0xe064,
+0xcd60, 0xe774, 0x36d2, 0x4354, 0x256f, 0x16b9, 0x32a9, 0x04e9, 0xc94b, 0xbd89,
+0xb4e8, 0xde63, 0xe6b3, 0xefc2, 0x2c99, 0x4d30, 0x244c, 0x0dd9, 0x4013, 0x0a25,
+0xbf81, 0xc647, 0xc8d3, 0xd098, 0xddd7, 0x0729, 0x2f46, 0x49fe, 0x3186, 0x11dd,
+0x33c9, 0x0a1a, 0xc455, 0xb4fe, 0xcbf9, 0xe01f, 0xd9c3, 0x0d63, 0x3ca9, 0x40e4,
+0x2597, 0x1f85, 0x2fee, 0xf6c7, 0xc763, 0xb733, 0xcbd2, 0xe734, 0xe337, 0x11ef,
+0x4379, 0x4377, 0x1865, 0x216c, 0x32c8, 0xee0c, 0xca47, 0xc04a, 0xd076, 0xe70b,
+0xebed, 0x16a9, 0x3f81, 0x44ae, 0x16f6, 0x2266, 0x31f0, 0xe6d6, 0xc30d, 0xc333,
+0xd87e, 0xe495, 0xf14b, 0x1f91, 0x3ab4, 0x3a37, 0x1822, 0x291b, 0x2bcf, 0xea2c,
+0xc762, 0xbf57, 0xdd08, 0xe181, 0xec7b, 0x28f2, 0x456d, 0x366d, 0x1d72, 0x3891,
+0x2266, 0xe229, 0xcd55, 0xbe26, 0xdad8, 0xe6b2, 0xf1ad, 0x2a59, 0x4ce7, 0x346f,
+0x18da, 0x3eb3, 0x1a5c, 0xd0e6, 0xc45c, 0xbfe1, 0xd4d3, 0xe11f, 0x014a, 0x2f81,
+0x4863, 0x3408, 0x1268, 0x31f4, 0x0ec8, 0xca3c, 0xb9fc, 0xc1dc, 0xdd52, 0xe093,
+0x05ff, 0x33c3, 0x43d7, 0x2da7, 0x1671, 0x2bf0, 0x0034, 0xc97d, 0xb0c3, 0xbe5e,
+0xe43b, 0xdb68, 0x0387, 0x3ae6, 0x3ee7, 0x1b43, 0x174b, 0x2d76, 0xf26e, 0xc68d,
+0xb289, 0xbb76, 0xe0fe, 0xde57, 0x0305, 0x3b0e, 0x44f7, 0x17e8, 0x1ae0, 0x2e9e,
+0xe6b9, 0xc23e, 0xb5b3, 0xbe35, 0xde8a, 0xe55c, 0x0e04, 0x3e2d, 0x4749, 0x1891,
+0x1cce, 0x2c46, 0xe31a, 0xbc10, 0xb2be, 0xc7d4, 0xe1b0, 0xec58, 0x1b5f, 0x42a4,
+0x4577, 0x18af, 0x206d, 0x2610, 0xe0cf, 0xbee7, 0xb63b, 0xd3ec, 0xe4ab, 0xed6c,
+0x253f, 0x4a99, 0x42a8, 0x18da, 0x2a90, 0x2130, 0xd967, 0xc013, 0xb623, 0xd3d0,
+0xe7d4, 0xf7c7, 0x2d1b, 0x4df4, 0x3dfe, 0x18d3, 0x2e94, 0x12e1, 0xceb7, 0xbc8b,
+0xb721, 0xd9e6, 0xe7b6, 0xfe42, 0x37c7, 0x5256, 0x3916, 0x14fa, 0x2b19, 0x0829,
+0xcbbc, 0xbc11, 0xba4e, 0xe0c8, 0xec3c, 0x04ba, 0x39d7, 0x4f48, 0x314f, 0x1b2a,
+0x320f, 0xfbd4, 0xc7ad, 0xbe46, 0xbcb0, 0xdfec, 0xea9d, 0x0a76, 0x3eae, 0x51ce,
+0x28b8, 0x15c7, 0x32b1, 0xf736, 0xc436, 0xbbe2, 0xc2bd, 0xe357, 0xecf4, 0x10c4,
+0x40f6, 0x524e, 0x25cd, 0x19ce, 0x301e, 0xec1e, 0xbc6a, 0xbace, 0xd1d8, 0xe825,
+0xec23, 0x1b1b, 0x4400, 0x482a, 0x1bce, 0x1896, 0x260d, 0xe7f6, 0xc325, 0xb5e4,
+0xd101, 0xeabf, 0xea0d, 0x1d52, 0x47b3, 0x3cf0, 0x1282, 0x235d, 0x2050, 0xd9ae,
+0xc4d9, 0xb970, 0xd086, 0xe87d, 0xea86, 0x1b23, 0x48b1, 0x3c02, 0x0e92, 0x2cb2,
+0x2472, 0xd2cb, 0xbff8, 0xbc43, 0xce22, 0xe2d3, 0xf745, 0x2364, 0x47bb, 0x3e11,
+0x1209, 0x2f53, 0x1fb0, 0xcfac, 0xba0d, 0xbeea, 0xd8e3, 0xde1c, 0xf854, 0x2f24,
+0x49ea, 0x387f, 0x14be, 0x2de7, 0x1304, 0xd01b, 0xb948, 0xbc56, 0xe0d1, 0xe484,
+0xff93, 0x3655, 0x488a, 0x2bf5, 0x16ba, 0x3637, 0x07aa, 0xcf32, 0xc24d, 0xc063,
+0xe449, 0xe713, 0x01e8, 0x3b25, 0x4f83, 0x26c5, 0x1640, 0x3ab8, 0xff8c, 0xc83c,
+0xbf7c, 0xc052, 0xdf32, 0xe807, 0x09ef, 0x390f, 0x4d01, 0x25f5, 0x19b1, 0x3685,
+0xf201, 0xbb2e, 0xb561, 0xc59a, 0xdeac, 0xe81d, 0x110d, 0x2f0d, 0x5faf, 0x5bc4,
+0x12fe, 0xf615, 0xda50, 0xa9ae, 0x886f, 0xcd18, 0x0718, 0x09f1, 0x5bb7, 0x7af7,
+0x3e15, 0x0a73, 0xe799, 0xa715, 0x9c71, 0xc971, 0xb0a1, 0x07a7, 0x6f82, 0x3163,
+0x2a82, 0x5398, 0x14c9, 0xbfc2, 0xd8b3, 0xd540, 0xc5ce, 0x0fda, 0xf099, 0xf1a7,
+0x518e, 0x1c60, 0xcd84, 0x19da, 0x35b8, 0xd346, 0x03a1, 0x377b, 0xdf14, 0xefc5,
+0x0c59, 0xc206, 0xc448, 0x2196, 0x11ee, 0xfe70, 0x4f48, 0x1fa8, 0xeb2d, 0x1d5b,
+0xeaf2, 0x9f51, 0xdaef, 0x10e0, 0xe2b8, 0x140e, 0x4a7c, 0x0548, 0x11c6, 0x2b5c,
+0xd063, 0xb9e9, 0x0c49, 0xee9f, 0xcd6b, 0x32da, 0x2204, 0xe702, 0x2c36, 0x195d,
+0xbc4d, 0xef56, 0x1ea6, 0xd1a8, 0xfb24, 0x33f8, 0xe2fa, 0xffb5, 0x3ccd, 0xe4cb,
+0xca25, 0x2563, 0x0355, 0xd2da, 0x2bb0, 0x137a, 0xd6ec, 0x2218, 0x1b95, 0xc3b7,
+0xf854, 0x3722, 0xe6e8, 0xf671, 0x3596, 0xec18, 0xf030, 0x2d9c, 0xe6e7, 0xc2e9,
+0x26d5, 0x1a49, 0xd733, 0x2bbd, 0x26a1, 0xdab2, 0x1ba0, 0x23f0, 0xc355, 0xe703,
+0x2bce, 0xdc49, 0xf190, 0x496d, 0xfc6e, 0xf386, 0x3dfc, 0xf7e9, 0xbcff, 0x1444,
+0x0996, 0xc579, 0x1fc6, 0x2a9c, 0xe722, 0x261f, 0x2af6, 0xc74c, 0xe3b8, 0x2a8b,
+0xe105, 0xe8c4, 0x3bef, 0xfd9f, 0xefc2, 0x313f, 0xf888, 0xbeed, 0x0f0d, 0x10ba,
+0xcf13, 0x2814, 0x3a0d, 0xdf51, 0x0dca, 0x2f8f, 0xccfa, 0xc831, 0x1d1f, 0xe940,
+0xe8cf, 0x4a93, 0x0d52, 0xef83, 0x3c76, 0xfaba, 0xa5dd, 0x0126, 0x112f, 0xbdc7,
+0x1a61, 0x422a, 0xe9fb, 0x1b1a, 0x3694, 0xc9c9, 0xce2b, 0x1ef2, 0xd732, 0xdc7b,
+0x46da, 0xff7f, 0xe9fc, 0x46e1, 0x05ac, 0xaeb6, 0x0663, 0x159d, 0xc012, 0x1316,
+0x34ba, 0xdd94, 0x12fd, 0x31f6, 0xc596, 0xcbff, 0x2ae2, 0xe4d5, 0xd6c1, 0x4927,
+0x0b3d, 0xdd77, 0x3926, 0x07d1, 0xa2d2, 0xfb2e, 0x211d, 0xc317, 0x1769, 0x4b14,
+0xe280, 0x1326, 0x411c, 0xc1e2, 0xbac5, 0x2cf6, 0xe603, 0xcb4b, 0x4ed8, 0x191b,
+0xe401, 0x458d, 0x1545, 0xac87, 0x005f, 0x1e4a, 0xba9b, 0x0cc0, 0x467e, 0xe1cf,
+0x11da, 0x478e, 0xd350, 0xc70d, 0x31c1, 0xf201, 0xce1e, 0x434c, 0x12d6, 0xdd0b,
+0x3986, 0x1337, 0xaf6d, 0x006c, 0x2ca7, 0xc8ff, 0x0c23, 0x4bde, 0xe11d, 0x0318,
+0x41ef, 0xd0e5, 0xbd29, 0x3042, 0xf8a0, 0xd014, 0x4ad2, 0x1b53, 0xda3d, 0x3aa3,
+0x14be, 0xa4bb, 0xf7a1, 0x2b27, 0xc43a, 0x0948, 0x507e, 0xe296, 0x0297, 0x45db,
+0xd510, 0xb990, 0x273a, 0xf430, 0xc750, 0x3f4c, 0x1a85, 0xdb6a, 0x3993, 0x1ab4,
+0xadf3, 0xf5f6, 0x280d, 0xc5fc, 0xfe8a, 0x4668, 0xe58d, 0x011c, 0x457a, 0xdfc7,
+0xbebe, 0x255e, 0xfae1, 0xcb02, 0x3a3a, 0x1b49, 0xd635, 0x2c90, 0x1a6d, 0xb0f1,
+0xf0b7, 0x2851, 0xcb11, 0xfd3b, 0x4815, 0xe902, 0xf73a, 0x3af3, 0xde3a, 0xb633,
+0x1a27, 0xfae7, 0xc8e7, 0x3373, 0x1efa, 0xdab3, 0x2af8, 0x1f54, 0xb3a4, 0xe5bd,
+0x2010, 0xc657, 0xef33, 0x4332, 0xf32a, 0xf8bc, 0x3a43, 0xe716, 0xb380, 0x1025,
+0xf94b, 0xbff8, 0x2cbe, 0x2722, 0xd958, 0x2238, 0x24de, 0xb4e0, 0xd8e0, 0x219f,
+0xcbf4, 0xec3e, 0x4cc4, 0xf5db, 0xee65, 0x3cb1, 0xe88f, 0xad6b, 0x15fb, 0x0312,
+0xbe65, 0x3207, 0x3441, 0xd731, 0x1ee6, 0x2923, 0xb321, 0xd7af, 0x26e1, 0xce3a,
+0xeb73, 0x52d6, 0xfb3a, 0xed11, 0x3fa2, 0xeb93, 0xa537, 0x0fde, 0x078b, 0xbcc7,
+0x2dd2, 0x39bf, 0xde73, 0x2216, 0x2ba5, 0xb204, 0xd113, 0x2873, 0xd059, 0xe433,
+0x5331, 0x00f5, 0xede6, 0x41c9, 0xf3f8, 0xaa8c, 0x0f24, 0x06be, 0xb5e6, 0x29e9,
+0x39f4, 0xda5d, 0x25b6, 0x2b84, 0xb07f, 0xd942, 0x36fe, 0xe8ec, 0xcdd1, 0x1733,
+0x0849, 0x1aa1, 0x4558, 0xfb48, 0xd3ef, 0x11bd, 0xef48, 0xb142, 0xf697, 0xfdea,
+0xd64a, 0x32a6, 0x6fed, 0x25f8, 0x0b17, 0x2a5c, 0xdff3, 0xaef4, 0xd4ec, 0xbcba,
+0xe25d, 0x44cb, 0x234b, 0x1321, 0x73ba, 0x4da9, 0xced4, 0xe817, 0x007f, 0xa908,
+0xb5df, 0x1df6, 0x1875, 0x0e2c, 0x3ec1, 0x2fcd, 0x272d, 0x2290, 0xe25c, 0xc07e,
+0xf134, 0xfb4d, 0xd6f2, 0x0ad3, 0x3447, 0x223c, 0x13f1, 0x2272, 0x269f, 0x025a,
+0x0161, 0xd722, 0xd224, 0x0308, 0xde38, 0xef78, 0x3c18, 0x42ed, 0x0555, 0x290a,
+0x5400, 0xe6be, 0xca2d, 0xdae9, 0xc68f, 0xd7d0, 0xf47c, 0x10ed, 0x3184, 0x5941,
+0x1cf6, 0x0891, 0x3a35, 0xeb6f, 0xae39, 0xc817, 0xe6f7, 0xd6ef, 0xebb0, 0x3372,
+0x35f1, 0x34dd, 0x1e90, 0x16f7, 0x21a1, 0xf074, 0xc287, 0xb8c4, 0xe82d, 0xe6eb,
+0xdb90, 0x1e19, 0x40af, 0x3208, 0x1a9a, 0x320f, 0x1b9a, 0xdeff, 0xcf46, 0xb4a3,
+0xcbd7, 0xdf68, 0xe59a, 0x22fe, 0x55b5, 0x3892, 0x0be2, 0x396b, 0x1a98, 0xc635,
+0xc146, 0xc092, 0xd361, 0xe7eb, 0x0383, 0x2879, 0x4c6a, 0x3c0d, 0x05ca, 0x2976,
+0x14a9, 0xc798, 0xc116, 0xd2d8, 0xdd50, 0xdcd3, 0x0b09, 0x2bce, 0x3653, 0x2ab8,
+0x0a52, 0x2aa9, 0x14b7, 0xd401, 0xb890, 0xcc60, 0xea88, 0xd5e4, 0xfe7d, 0x383b,
+0x3ade, 0x1b88, 0x1ad7, 0x3ed1, 0x075f, 0xcd9c, 0xbbc4, 0xcbe8, 0xeb85, 0xd822,
+0xfbc3, 0x3e44, 0x459b, 0x10ea, 0x17ee, 0x43df, 0xf7f9, 0xc0a0, 0xc419, 0xcf1f,
+0xdea4, 0xe05f, 0x0678, 0x330f, 0x42d4, 0x111c, 0x0fba, 0x3f57, 0xf9a8, 0xb669,
+0xbe1b, 0xe146, 0xdd9a, 0xd4be, 0x11a3, 0x389d, 0x37d6, 0x13b2, 0x1c70, 0x3370,
+0xf10c, 0xc063, 0xb6e6, 0xd48a, 0xdc66, 0xd809, 0x1789, 0x3f92, 0x3566, 0x0e0f,
+0x231e, 0x2ea3, 0xde28, 0xbb31, 0xba09, 0xcf05, 0xd866, 0xdfc8, 0x18f1, 0x3ecd,
+0x369b, 0x0a8f, 0x2386, 0x2af2, 0xda2a, 0xb79e, 0xb7d0, 0xd687, 0xde35, 0xe721,
+0x22ba, 0x4574, 0x3572, 0x0dd3, 0x2e06, 0x263b, 0xd7ab, 0xbfc7, 0xc2f6, 0xdf1b,
+0xe1c3, 0xf1d5, 0x2cb4, 0x4e1c, 0x37cd, 0x115d, 0x35cd, 0x1fe6, 0xd779, 0xc301,
+0xc58e, 0xe72d, 0xe5ce, 0xf960, 0x335a, 0x4fb4, 0x357b, 0x1898, 0x426e, 0x1cc2,
+0xd473, 0xc727, 0xc831, 0xe1fb, 0xe68f, 0x072e, 0x3da1, 0x58d4, 0x39ac, 0x1d25,
+0x4338, 0x1401, 0xcf2b, 0xbee6, 0xca41, 0xe875, 0xe64f, 0x1316, 0x4982, 0x5236,
+0x2f67, 0x2085, 0x38b8, 0xfd01, 0xc7c8, 0xba93, 0xc979, 0xebc7, 0xea26, 0x168d,
+0x477d, 0x4817, 0x212b, 0x1fdd, 0x335a, 0xef3e, 0xc484, 0xb895, 0xc9b8, 0xec42,
+0xe805, 0x136b, 0x4a61, 0x4971, 0x17b6, 0x1f6a, 0x2f97, 0xe4b1, 0xc086, 0xb999,
+0xcac9, 0xe5fd, 0xee0c, 0x1807, 0x412f, 0x460f, 0x166d, 0x1f4c, 0x29b3, 0xdc64,
+0xb965, 0xb861, 0xd211, 0xe147, 0xeb99, 0x2193, 0x450f, 0x3ce4, 0x113e, 0x1f63,
+0x1c84, 0xd769, 0xb874, 0xb487, 0xd682, 0xe1c9, 0xed74, 0x24b0, 0x43a4, 0x33ba,
+0x0e1c, 0x2413, 0x10f7, 0xcdd3, 0xb977, 0xb8d7, 0xdbc4, 0xe37c, 0xf175, 0x272e,
+0x457d, 0x2d0a, 0x07d0, 0x297e, 0x0f41, 0xca51, 0xb9f7, 0xbc9a, 0xdaa7, 0xddfe,
+0xf766, 0x2c64, 0x4532, 0x2d7b, 0x0dd9, 0x2cf4, 0x0c17, 0xc90f, 0xb4bc, 0xc07f,
+0xe270, 0xdce1, 0xfc63, 0x371a, 0x4749, 0x2579, 0x1187, 0x2dc1, 0xff1f, 0xc7eb,
+0xb7f0, 0xc150, 0xe209, 0xe0bb, 0x0562, 0x39a6, 0x419b, 0x18df, 0x11d0, 0x32b0,
+0xf640, 0xc162, 0xbaa8, 0xc92a, 0xe355, 0xdf42, 0x07ce, 0x3de6, 0x485f, 0x1a8d,
+0x15e0, 0x345d, 0xf2a7, 0xc2b4, 0xbc48, 0xcb43, 0xe331, 0xe771, 0x171d, 0x41c7,
+0x46e8, 0x1e6a, 0x1d99, 0x3169, 0xea47, 0xbb34, 0xb612, 0xcfe5, 0xe372, 0xe4f4,
+0x1dce, 0x4978, 0x4538, 0x1abb, 0x1ff9, 0x2726, 0xe2ee, 0xbf5a, 0xb642, 0xd6a7,
+0xeb65, 0xe981, 0x26c2, 0x5246, 0x3d0f, 0x142e, 0x2f6c, 0x28fc, 0xda88, 0xc3aa,
+0xbdbe, 0xd749, 0xeb54, 0xf1e0, 0x25cd, 0x526c, 0x41ba, 0x10bf, 0x2fe6, 0x2449,
+0xd374, 0xbfc4, 0xbbe2, 0xd283, 0xe25c, 0xf99f, 0x2cef, 0x4ac7, 0x3ddf, 0x17d9,
+0x3082, 0x1925, 0xce89, 0xb6a1, 0xbc50, 0xe283, 0xe474, 0xfd8d, 0x3bcc, 0x4e96,
+0x33c3, 0x167a, 0x2e47, 0x0a43, 0xcef1, 0xbc29, 0xba3d, 0xe1a3, 0xe5f7, 0xfe7c,
+0x39db, 0x4bea, 0x270e, 0x128b, 0x30de, 0xfb59, 0xc505, 0xbdab, 0xbd60, 0xe0c5,
+0xe64a, 0x0171, 0x35c3, 0x47fc, 0x21b5, 0x11b5, 0x30fa, 0xf4fc, 0xc06e, 0xbaa3,
+0xbc7a, 0xd83f, 0xe4e1, 0x0b66, 0x373d, 0x497a, 0x2421, 0x109c, 0x26f1, 0xecb6,
+0xbadf, 0xb11f, 0xc45d, 0xdf59, 0xe4b2, 0x1641, 0x3f59, 0x42bc, 0x1c1c, 0x129c,
+0x1bee, 0xe133, 0xbe64, 0xb1ad, 0xcc9f, 0xec32, 0xe986, 0x19ae, 0x44e5, 0x3a19,
+0x0e88, 0x1ade, 0x1f2c, 0xdec4, 0xc3cc, 0xb2ef, 0xdaab, 0xfd08, 0xe6a7, 0x1123,
+0x3704, 0x3898, 0x3ad4, 0x346f, 0xfe92, 0xd1d3, 0xcc04, 0x9cbd, 0xc135, 0x02eb,
+0xf63b, 0x365b, 0x8b58, 0x58ad, 0x0415, 0x0488, 0xcf1c, 0x8e64, 0xb611, 0xc2c4,
+0xeda1, 0x4ea8, 0x63da, 0x49bb, 0x50ec, 0x3618, 0xc859, 0xb818, 0xe010, 0xbc83,
+0xd14a, 0x1ad8, 0x32a9, 0x4030, 0x4434, 0x0f51, 0xf80f, 0x1895, 0xeaf8, 0xc48d,
+0x175a, 0x2702, 0xe895, 0x0fd2, 0x2756, 0xcf30, 0xdfad, 0x3a11, 0xfb80, 0xfa5f,
+0x565f, 0x121d, 0xf290, 0x2352, 0xddf3, 0xa5f4, 0xfe06, 0x187b, 0xde96, 0x3a13,
+0x5105, 0xf698, 0x255a, 0x212e, 0xb7a3, 0xc5a7, 0x10d7, 0xdeff, 0xe90b, 0x551a,
+0x1708, 0xfd81, 0x42e8, 0xf9ae, 0xb328, 0xf8c8, 0x0cd4, 0xc910, 0x1398, 0x3cf8,
+0xe89b, 0x1c9d, 0x36c1, 0xcb0b, 0xcca6, 0x2759, 0xf1e2, 0xd264, 0x3d80, 0x146d,
+0xe372, 0x36c2, 0x0ab3, 0xb2ef, 0x0a0e, 0x2e96, 0xc7be, 0x0dcf, 0x4ba5, 0xdcd8,
+0xff1f, 0x391f, 0xcf51, 0xc4c3, 0x314b, 0xfef3, 0xd680, 0x41c6, 0x156e, 0xdd3b,
+0x2eeb, 0x0886, 0xa9c9, 0xf3a6, 0x247e, 0xc927, 0x069f, 0x4a37, 0xe895, 0x0061,
+0x378e, 0xd6a1, 0xb348, 0x1688, 0xf810, 0xc498, 0x323f, 0x1fc5, 0xe066, 0x29b3,
+0x139d, 0xadbb, 0xe71a, 0x2c44, 0xcae3, 0xe8e1, 0x3f89, 0xe822, 0xf034, 0x367a,
+0xdf30, 0xb32c, 0x1aba, 0x0281, 0xcc1b, 0x36c4, 0x14ca, 0xc6d5, 0x2590, 0x1c08,
+0xa351, 0xd7ce, 0x2a74, 0xd59f, 0xf3fa, 0x477e, 0xf4e0, 0xf8ff, 0x3121, 0xdc01,
+0xb775, 0x150c, 0xfcd8, 0xcc0d, 0x38c8, 0x2ece, 0xe423, 0x2849, 0x29b5, 0xc302,
+0xda74, 0x1f47, 0xdd2f, 0xf247, 0x3ab2, 0xf2d7, 0x006a, 0x4787, 0xf2b8, 0xbc9f,
+0x1f1c, 0x0ac7, 0xbde9, 0x23df, 0x2b4d, 0xd869, 0x1cf2, 0x2e59, 0xc8ac, 0xe6ca,
+0x2cef, 0xd907, 0xf197, 0x4b23, 0xf20c, 0xef15, 0x4469, 0xf156, 0xb2a9, 0x1bfd,
+0x127b, 0xca28, 0x306d, 0x3438, 0xdf08, 0x20cf, 0x27a1, 0xb9c7, 0xd8d7, 0x2741,
+0xd309, 0xe588, 0x5056, 0x03d5, 0xef96, 0x4207, 0xfd8d, 0xae5f, 0x03b5, 0x0589,
+0xbd38, 0x23af, 0x3a76, 0xe095, 0x2023, 0x3839, 0xc165, 0xd4ca, 0x2de1, 0xd781,
+0xe09b, 0x50bd, 0x0340, 0xea0c, 0x4022, 0xf840, 0xad2c, 0x1331, 0x12e3, 0xc260,
+0x2f2a, 0x3b6c, 0xd12c, 0x18a4, 0x3197, 0xb77c, 0xd13d, 0x2bd2, 0xd972, 0xe616,
+0x52eb, 0x041e, 0xeb26, 0x3bdc, 0xf119, 0xa2b2, 0x06d5, 0x1049, 0xbd97, 0x289f,
+0x44ee, 0xdf14, 0x1d7f, 0x34ac, 0xb7bb, 0xc690, 0x278a, 0xda25, 0xdff9, 0x579a,
+0x080c, 0xe6ec, 0x4374, 0xfcbd, 0xa5be, 0x078c, 0x146b, 0xbb5b, 0x2164, 0x4262,
+0xdc47, 0x1e50, 0x39f8, 0xbce7, 0xcbb4, 0x2afe, 0xdc45, 0xdb5e, 0x5409, 0x0b3a,
+0xe53c, 0x4358, 0x02ae, 0xa896, 0x0857, 0x1af8, 0xc23a, 0x2352, 0x496b, 0xdfb5,
+0x197e, 0x3ce3, 0xbe54, 0xc506, 0x3012, 0xe677, 0xd704, 0x5217, 0x11ee, 0xe2e7,
+0x3fe4, 0x06fa, 0xa5a3, 0xfdcd, 0x181f, 0xbb44, 0x1747, 0x4a1f, 0xdca9, 0x10d6,
+0x4291, 0xc4a6, 0xbd25, 0x2a87, 0xe605, 0xcbf6, 0x4b38, 0x15fb, 0xe0ad, 0x3f35,
+0x0d36, 0xa652, 0x0068, 0x234a, 0xbde1, 0x11e2, 0x4aeb, 0xdbe0, 0x0a9d, 0x4344,
+0xcb41, 0xbfb5, 0x3005, 0xef5d, 0xcc1b, 0x497b, 0x1516, 0xd825, 0x3b8a, 0x1677,
+0xabc7, 0xfeb9, 0x2dcb, 0xc484, 0x08d9, 0x4f69, 0xe275, 0x041d, 0x4846, 0xd81a,
+0xc055, 0x3307, 0xfcca, 0xce29, 0x4a68, 0x20cb, 0xd8f3, 0x379a, 0x1b24, 0xaf02,
+0xfaf5, 0x36a2, 0xdc0c, 0xf2c4, 0x1be5, 0xefab, 0x3121, 0x54a4, 0xf198, 0xe7b1,
+0x2ba4, 0xe144, 0xad6e, 0x0105, 0xf0a8, 0xe331, 0x4379, 0x5911, 0x2529, 0x2cc9,
+0x279d, 0xcce3, 0xc4aa, 0xd6d0, 0xaebe, 0xee7a, 0x3b83, 0x0e08, 0x0c23, 0x6c6b,
+0x4249, 0xd2d0, 0xee99, 0xf0dc, 0xb1d9, 0xc315, 0x0097, 0x05b2, 0x198e, 0x3a21,
+0x0a87, 0x19af, 0x3001, 0xda73, 0xbf3b, 0xf320, 0xf1e8, 0xce48, 0xfde9, 0x1c97,
+0x092a, 0x1c03, 0x1996, 0x174f, 0x1e06, 0x0784, 0xc7d4, 0xc266, 0xf6c0, 0xcc3e,
+0xdd41, 0x35c9, 0x3070, 0x0e9b, 0x2df6, 0x46cc, 0xf777, 0xdb3b, 0xd86d, 0xb2ad,
+0xdad5, 0xea97, 0xeb13, 0x2dfd, 0x5bf5, 0x1d45, 0x0c80, 0x4c6b, 0xf9d2, 0xb332,
+0xd3b1, 0xcca7, 0xc90a, 0xeeb9, 0x1637, 0x2142, 0x4501, 0x2b65, 0x034c, 0x35ee,
+0x0df0, 0xbcaf, 0xb962, 0xda48, 0xd509, 0xd169, 0x125a, 0x2f7e, 0x3ce9, 0x31e9,
+0x1f9f, 0x25c3, 0xf9ce, 0xc7fa, 0xa4bc, 0xc4e4, 0xde14, 0xd114, 0x1820, 0x4ec1,
+0x3955, 0x1716, 0x2ffb, 0x23e1, 0xd401, 0xc118, 0xaea8, 0xc267, 0xe761, 0xe650,
+0x166b, 0x5317, 0x4508, 0x02ef, 0x256f, 0x2a58, 0xc9bf, 0xbfb1, 0xc690, 0xc4c0,
+0xdb5a, 0xfc3e, 0x1d56, 0x406f, 0x4672, 0x07f9, 0x258b, 0x3563, 0xd0af, 0xad4b,
+0xc62c, 0xd7ec, 0xd118, 0xfacb, 0x32c8, 0x3cf8, 0x3893, 0x1827, 0x2a88, 0x2056,
+0xd2e0, 0xa9a8, 0xbc95, 0xe82d, 0xd209, 0xed42, 0x4049, 0x4bca, 0x27a4, 0x17b5,
+0x33c7, 0x048c, 0xc896, 0xbbc4, 0xb5e8, 0xde55, 0xe670, 0xf26e, 0x319d, 0x5284,
+0x20bb, 0x0a37, 0x3f54, 0x0425, 0xb7b6, 0xbeac, 0xc4a7, 0xd10a, 0xdf90, 0x067a,
+0x32d8, 0x4def, 0x29e9, 0x0de0, 0x33b8, 0x00f9, 0xbb53, 0xb44c, 0xca99, 0xdcb3,
+0xdf0d, 0x140a, 0x4211, 0x47c0, 0x2741, 0x1ffe, 0x3026, 0xf2ee, 0xc44d, 0xb8c2,
+0xcbde, 0xe86b, 0xed77, 0x1b4d, 0x47ee, 0x4766, 0x1ae9, 0x2386, 0x33e9, 0xea57,
+0xc4ae, 0xc182, 0xd3ec, 0xeae1, 0xf2fa, 0x1c99, 0x46ce, 0x4849, 0x16e5, 0x2357,
+0x2d0d, 0xe171, 0xc2b4, 0xc619, 0xdb43, 0xe861, 0xf96a, 0x2580, 0x41a1, 0x3fcc,
+0x14f6, 0x245e, 0x2692, 0xe3d9, 0xc4b2, 0xc084, 0xdcfb, 0xe481, 0xf277, 0x26e8,
+0x4453, 0x3677, 0x1401, 0x2eb5, 0x1c61, 0xda3a, 0xc536, 0xbbc2, 0xd95f, 0xe409,
+0xf088, 0x24e3, 0x4a50, 0x36ef, 0x1343, 0x3794, 0x1bd7, 0xd0b1, 0xbf04, 0xc0c0,
+0xd999, 0xe102, 0xfe57, 0x307b, 0x4c60, 0x3446, 0x110b, 0x34af, 0x1719, 0xd246,
+0xbe92, 0xc661, 0xdf01, 0xde95, 0x04b9, 0x34ae, 0x44b2, 0x2c3e, 0x140f, 0x311d,
+0x09e5, 0xcd57, 0xb673, 0xc4ea, 0xe8e6, 0xdfae, 0x02f2, 0x3c51, 0x460d, 0x2037,
+0x160e, 0x33a4, 0xffd5, 0xd044, 0xbb77, 0xbff6, 0xe547, 0xe2d3, 0x02a5, 0x3927,
+0x483e, 0x1e84, 0x18a8, 0x35a4, 0xf428, 0xc4b4, 0xb810, 0xbe31, 0xdf3f, 0xe3b9,
+0x070a, 0x3a6e, 0x4a01, 0x1d3d, 0x19b7, 0x312f, 0xe9ed, 0xb9d2, 0xb185, 0xc27d,
+0xdf74, 0xeaac, 0x163c, 0x40b2, 0x480f, 0x196b, 0x1a68, 0x2946, 0xe387, 0xbd44,
+0xb687, 0xcdd3, 0xe1c0, 0xe73f, 0x19c6, 0x454e, 0x4471, 0x1517, 0x1f37, 0x2415,
+0xde12, 0xbc28, 0xb193, 0xce92, 0xe199, 0xeb2a, 0x208d, 0x45c9, 0x3dfd, 0x140f,
+0x2554, 0x1b8d, 0xd3e4, 0xb631, 0xaeee, 0xd2a0, 0xe032, 0xeefc, 0x2bad, 0x4d46,
+0x3a27, 0x0f3b, 0x24c9, 0x111e, 0xcdbf, 0xb6b5, 0xb194, 0xd7c9, 0xe60a, 0xf93b,
+0x2f30, 0x4968, 0x3246, 0x1306, 0x2d46, 0x06e7, 0xc785, 0xba49, 0xb9ab, 0xde3a,
+0xe629, 0xff26, 0x3a02, 0x5456, 0x30f0, 0x10ea, 0x3149, 0x0578, 0xc92c, 0xbe07,
+0xc00c, 0xe26f, 0xec57, 0x0aaf, 0x3ec0, 0x560f, 0x2fbd, 0x16da, 0x3543, 0xfe12,
+0xc441, 0xbe1e, 0xce80, 0xec2a, 0xeeda, 0x1761, 0x467e, 0x531c, 0x2945, 0x16f3,
+0x30b3, 0xfa85, 0xc82a, 0xb95b, 0xd0b9, 0xf0bf, 0xecf0, 0x1a23, 0x4acc, 0x48e2,
+0x1c65, 0x1e4d, 0x2902, 0xe8e0, 0xc9c2, 0xbc2f, 0xd2c0, 0xf22e, 0xed1c, 0x1844,
+0x4b01, 0x4776, 0x146e, 0x2125, 0x2832, 0xde1c, 0xc52b, 0xbd52, 0xcf76, 0xec76,
+0xf496, 0x1c9f, 0x4726, 0x432c, 0x116f, 0x25e7, 0x2766, 0xd731, 0xbc99, 0xc162,
+0xd7c6, 0xe12d, 0xf3c2, 0x27ac, 0x489f, 0x3fb7, 0x11f9, 0x23e3, 0x1dd1, 0xd9f9,
+0xbc81, 0xbc41, 0xe051, 0xe6f9, 0xf6af, 0x2df2, 0x4613, 0x2fe6, 0x12b9, 0x303c,
+0x120f, 0xd130, 0xc1da, 0xbc62, 0xdb80, 0xe279, 0xf4be, 0x2c52, 0x4660, 0x275e,
+0x0d1c, 0x3149, 0x075f, 0xc931, 0xbf24, 0xbaf3, 0xd888, 0xe2cc, 0xfb8b, 0x2cb6,
+0x4a52, 0x29ea, 0x0caa, 0x3097, 0xff78, 0xbdf9, 0xb45b, 0xc1cb, 0xdd4e, 0xe310,
+0x068a, 0x24f2, 0x54ac, 0x60d2, 0x1650, 0xf4c6, 0xe059, 0xb014, 0x83b1, 0xbc91,
+0x02f9, 0x0157, 0x4fca, 0x7cb3, 0x41bb, 0x0df7, 0xea9f, 0xace6, 0x957f, 0xc6c1,
+0xb25c, 0xf25c, 0x6d4a, 0x3c23, 0x2274, 0x5314, 0x2754, 0xc59a, 0xd13d, 0xdf25,
+0xc110, 0x097d, 0xfee9, 0xe7e9, 0x4a58, 0x3073, 0xd0cf, 0x0834, 0x3f47, 0xde37,
+0xefad, 0x3de0, 0xef24, 0xe80f, 0x1519, 0xd4ff, 0xbae7, 0x160b, 0x1ed5, 0xf6f3,
+0x4a32, 0x391f, 0xed02, 0x1a95, 0x0194, 0xa798, 0xcdb4, 0x1870, 0xed7d, 0x0805,
+0x5609, 0x1649, 0x0c10, 0x37fb, 0xe698, 0xb602, 0x0ab1, 0x02bc, 0xc982, 0x29a0,
+0x3592, 0xeb4c, 0x26a0, 0x2b39, 0xc51e, 0xe028, 0x25df, 0xdd3d, 0xeb89, 0x3c7d,
+0xf25b, 0xf2a8, 0x40cd, 0xf67a, 0xbd91, 0x18e1, 0x1367, 0xcbf7, 0x1e2b, 0x2469,
+0xd608, 0x16f8, 0x2a0b, 0xc927, 0xe2cc, 0x37b1, 0xf346, 0xe694, 0x382d, 0xf93e,
+0xe10a, 0x2a89, 0xf6d1, 0xba9a, 0x1681, 0x2591, 0xd24e, 0x187c, 0x2fad, 0xd8f6,
+0x0b46, 0x2ae5, 0xc8c2, 0xd38a, 0x2a7a, 0xe7b1, 0xdcb3, 0x3e95, 0x03d8, 0xe5e1,
+0x3718, 0x0451, 0xb6fc, 0x05d7, 0x1636, 0xc589, 0x0c0d, 0x2d31, 0xe2a5, 0x145e,
+0x2ff3, 0xd16c, 0xd748, 0x2961, 0xeee4, 0xd89a, 0x2f77, 0x0413, 0xe432, 0x2a18,
+0x07e4, 0xbf90, 0xffac, 0x1a5c, 0xce43, 0x0eae, 0x3e2a, 0xe525, 0xfcdf, 0x3412,
+0xe19f, 0xc0d0, 0x160c, 0xf679, 0xdaf7, 0x3d0f, 0x184f, 0xe643, 0x3314, 0x0d97,
+0xa9d1, 0xf210, 0x1e09, 0xc374, 0x0591, 0x4995, 0xef24, 0x05bf, 0x3aa6, 0xdaf6,
+0xc1a1, 0x198b, 0xe5e1, 0xce1f, 0x40dc, 0x134c, 0xddc5, 0x3b68, 0x1d73, 0xb2c0,
+0xf385, 0x2228, 0xc6a9, 0xff0d, 0x4056, 0xea16, 0x071a, 0x3ca3, 0xd74b, 0xbcb1,
+0x210f, 0xf2c1, 0xcb2c, 0x414d, 0x1f07, 0xd958, 0x2f24, 0x195a, 0xa747, 0xe84d,
+0x2605, 0xc501, 0xff0e, 0x4e0a, 0xea48, 0x02a4, 0x46c5, 0xd723, 0xafda, 0x23af,
+0xf6a4, 0xbc7c, 0x3c45, 0x26f8, 0xdc74, 0x367d, 0x2543, 0xb0bd, 0xed3e, 0x27e5,
+0xc333, 0xf8e2, 0x4d51, 0xeb5f, 0xfefc, 0x4865, 0xe34b, 0xb937, 0x2748, 0x0266,
+0xc562, 0x3a6b, 0x25b1, 0xd774, 0x2daa, 0x2313, 0xb311, 0xef4a, 0x32e8, 0xcf60,
+0xf8b2, 0x51fb, 0xee07, 0xf368, 0x44ab, 0xe548, 0xb2da, 0x24f2, 0x07b3, 0xc446,
+0x3bc4, 0x2db3, 0xd779, 0x2ee4, 0x281e, 0xace1, 0xe4c5, 0x335a, 0xceab, 0xf17b,
+0x55c7, 0xf4d4, 0xf24d, 0x4603, 0xeb3d, 0xaf65, 0x1bbf, 0x08f3, 0xbf31, 0x300f,
+0x30d1, 0xd9cf, 0x29ba, 0x2ead, 0xb5b6, 0xdd53, 0x2dff, 0xcf0d, 0xe2d2, 0x4b14,
+0xf7cf, 0xf104, 0x48fe, 0xf5f9, 0xb102, 0x175c, 0x0bd9, 0xbcb2, 0x27b4, 0x336a,
+0xd7be, 0x2088, 0x3408, 0xbf37, 0xdc20, 0x3264, 0xdb9f, 0xe60e, 0x5013, 0xffb1,
+0xe856, 0x4281, 0xff01, 0xb10f, 0x1094, 0x176a, 0xc863, 0x243b, 0x390b, 0xdf6d,
+0x1d95, 0x3900, 0xc65f, 0xd25c, 0x2c3a, 0xe187, 0xdbeb, 0x465f, 0x0986, 0xeb79,
+0x4044, 0x0711, 0xb0da, 0x0496, 0x1513, 0xc287, 0x187b, 0x3c78, 0xe032, 0x1521,
+0x3c5f, 0xccc3, 0xcce1, 0x2b96, 0xe4ca, 0xd2ba, 0x430a, 0x0aa2, 0xdffc, 0x3ad9,
+0x0be5, 0xaea0, 0x03b6, 0x1ba6, 0xbbed, 0x0db5, 0x4113, 0xdc05, 0x05bb, 0x3a65,
+0xcc55, 0xc14b, 0x25ec, 0xe783, 0xd1d2, 0x4965, 0x1616, 0xdbb4, 0x3263, 0x0a8b,
+0xa569, 0xf7cf, 0x1f57, 0xc4d2, 0x1559, 0x517f, 0xea47, 0x0848, 0x3a1f, 0xc9f1,
+0xbb1e, 0x266f, 0xea60, 0xcd1c, 0x4d59, 0x23a6, 0xe328, 0x3b46, 0x19d2, 0xabe2,
+0xf47d, 0x1e1a, 0xbcd3, 0x09ed, 0x4fb3, 0xe964, 0x0dbc, 0x485b, 0xd3bb, 0xbbbe,
+0x2722, 0xffc5, 0xce39, 0x0d09, 0xfe2c, 0x112d, 0x596c, 0x16f5, 0xd170, 0x12a7,
+0x07ab, 0xa521, 0xd945, 0x0290, 0xcf7b, 0x0c60, 0x5af6, 0x3c64, 0x1c78, 0x2c17,
+0xf082, 0xb62e, 0xcf0c, 0xac7d, 0xba75, 0x27ac, 0x23af, 0xf129, 0x473d, 0x6f3b,
+0xefd4, 0xd0fc, 0xfcb5, 0xc5fa, 0xa532, 0xe2aa, 0x08ff, 0x0704, 0x304a, 0x21e6,
+0x0f85, 0x38c1, 0x010f, 0xbcdf, 0xd894, 0xf8ab, 0xd8c7, 0xe36a, 0x1b7a, 0x178e,
+0x1a72, 0x1741, 0x157f, 0x1dd4, 0x0dfe, 0xe47e, 0xbdd3, 0xf139, 0xe9ae, 0xc861,
+0x198e, 0x432e, 0x1c93, 0x1246, 0x4ca3, 0x23d4, 0xd5a3, 0xdda1, 0xc097, 0xcd1a,
+0xeddd, 0xed1a, 0x1738, 0x5658, 0x41b1, 0xfba0, 0x3319, 0x2a1d, 0xc048, 0xc0cf,
+0xd583, 0xd4c9, 0xdb7a, 0x070a, 0x2588, 0x3798, 0x3aa9, 0x0add, 0x25af, 0x2337,
+0xd4c2, 0xb504, 0xca8a, 0xe4dd, 0xd039, 0xf1df, 0x2bb9, 0x3c1d, 0x3426, 0x22ac,
+0x331f, 0x0f10, 0xdc7d, 0xbbe1, 0xb47c, 0xdb63, 0xd984, 0xfcbd, 0x450f, 0x5431,
+0x271a, 0x210b, 0x3c62, 0xf43d, 0xc565, 0xc201, 0xc114, 0xe4f6, 0xee9b, 0x085b,
+0x3cf5, 0x5653, 0x1e82, 0x0d52, 0x38a8, 0xf608, 0xc456, 0xce8b, 0xcfd1, 0xda08,
+0xf0f6, 0x16f4, 0x3014, 0x4610, 0x1a46, 0x0c35, 0x36d1, 0xfb60, 0xbc9c, 0xbcea,
+0xdb0c, 0xda74, 0xdb6c, 0x17ce, 0x3715, 0x33b3, 0x13c8, 0x1e7d, 0x290c, 0xe55b,
+0xb756, 0xb135, 0xdad6, 0xde70, 0xd336, 0x1782, 0x43a0, 0x29e5, 0x03f8, 0x2c8f,
+0x2768, 0xd396, 0xc0e1, 0xbc23, 0xccce, 0xdf32, 0xe829, 0x16bf, 0x4610, 0x3567,
+0x012e, 0x312c, 0x3059, 0xcf4c, 0xbc48, 0xcf7d, 0xdb0c, 0xd4f7, 0xefe3, 0x2327,
+0x3f5e, 0x34bf, 0x11d9, 0x3885, 0x2a09, 0xd700, 0xbd59, 0xc753, 0xde26, 0xd765,
+0xf612, 0x31cc, 0x45d3, 0x2d56, 0x17c3, 0x3e94, 0x16b7, 0xce82, 0xc389, 0xc8ab,
+0xdd9d, 0xdd82, 0xfe36, 0x3550, 0x4d5b, 0x2a45, 0x1384, 0x3ff2, 0x0ef3, 0xc81d,
+0xbe61, 0xcce9, 0xe4aa, 0xe0ab, 0x0699, 0x385e, 0x486b, 0x2294, 0x15f0, 0x3d85,
+0x01db, 0xc686, 0xc116, 0xcff5, 0xe34d, 0xe14b, 0x10c9, 0x3f07, 0x466b, 0x1f0d,
+0x1d05, 0x3ab5, 0xf8ef, 0xc8ce, 0xc05e, 0xd357, 0xe7d4, 0xe29f, 0x13df, 0x471a,
+0x4a47, 0x1c4c, 0x297f, 0x3e3f, 0xed74, 0xc6f8, 0xbfdc, 0xccde, 0xe3d5, 0xeaa0,
+0x19ee, 0x4b1b, 0x4d49, 0x1865, 0x2976, 0x37a0, 0xe1a2, 0xb9e8, 0xba35, 0xd299,
+0xde2b, 0xecfc, 0x27c6, 0x4ae2, 0x4564, 0x19a4, 0x28b5, 0x2426, 0xd885, 0xbc0a,
+0xb90e, 0xd777, 0xe284, 0xf34a, 0x2d9a, 0x4813, 0x38b9, 0x14ee, 0x2c67, 0x1894,
+0xd324, 0xbc7e, 0xb3bc, 0xda55, 0xe3e2, 0xef0a, 0x2fd7, 0x4da0, 0x2e6a, 0x0ee2,
+0x326e, 0x0fc1, 0xcb23, 0xbee7, 0xb82c, 0xdaac, 0xe70d, 0xf676, 0x2c96, 0x4efe,
+0x3180, 0x0f59, 0x3430, 0x0900, 0xc464, 0xbdf3, 0xbfd4, 0xdc18, 0xe54f, 0x02ac,
+0x346a, 0x4ca2, 0x2bdb, 0x0ba0, 0x2ca5, 0x00e4, 0xc0c2, 0xb4ae, 0xbea2, 0xdd3b,
+0xe1b5, 0x05e5, 0x36cd, 0x455d, 0x228c, 0x0f39, 0x2721, 0xf06a, 0xbe3c, 0xb625,
+0xc315, 0xe0db, 0xe130, 0x0824, 0x3af9, 0x4450, 0x1a04, 0x1254, 0x2a20, 0xeab7,
+0xbec0, 0xb857, 0xc3f1, 0xdf5a, 0xe831, 0x122e, 0x3daa, 0x44fa, 0x18f6, 0x1739,
+0x28bb, 0xe41d, 0xbbf3, 0xb89c, 0xcff6, 0xe5c1, 0xea00, 0x1c60, 0x45d5, 0x43ef,
+0x1c06, 0x2434, 0x292a, 0xe39e, 0xc2cb, 0xbd71, 0xd5c4, 0xe95c, 0xf5d3, 0x2a36,
+0x4b75, 0x403d, 0x1c75, 0x32bf, 0x27ef, 0xdda2, 0xcb76, 0xc7c5, 0xdb1a, 0xe84b,
+0xf612, 0x2b74, 0x5112, 0x4137, 0x18a7, 0x3889, 0x2791, 0xd8ed, 0xc90a, 0xc6c8,
+0xd83c, 0xe5cf, 0x01d1, 0x32e1, 0x4e08, 0x3e77, 0x1bcc, 0x39cc, 0x1bcb, 0xcf7c,
+0xbfad, 0xc693, 0xe1af, 0xe44a, 0x0379, 0x3b80, 0x5122, 0x34da, 0x12cd, 0x2ff7,
+0x0b3f, 0xcb7c, 0xb95e, 0xc0f4, 0xe555, 0xe1ce, 0x01a1, 0x3cc9, 0x475e, 0x2196,
+0x156a, 0x33d5, 0xf97d, 0xc4c1, 0xb9ed, 0xbd90, 0xe1db, 0xe24b, 0x021d, 0x3cfa,
+0x4dca, 0x1ea3, 0x12f7, 0x33e9, 0xf159, 0xbf15, 0xb984, 0xbdf4, 0xd9dc, 0xe483,
+0x0d28, 0x3bb5, 0x4aee, 0x1fcf, 0x1792, 0x2e75, 0xe773, 0xb55d, 0xaeed, 0xc525,
+0xdc07, 0xe07a, 0x162d, 0x403a, 0x41df, 0x18e6, 0x1816, 0x22ca, 0xe0d7, 0xba3f,
+0xab7d, 0xc6d9, 0xe296, 0xe464, 0x1a6b, 0x45ec, 0x3cb4, 0x1203, 0x21d7, 0x1e19,
+0xd553, 0xc042, 0xb44a, 0xca17, 0xe29f, 0xe893, 0x1a35, 0x457e, 0x3ca4, 0x0eee,
+0x274f, 0x1dc0, 0xd010, 0xbe2a, 0xb43a, 0xca4c, 0xe2e4, 0xf448, 0x246d, 0x4965,
+0x3fdb, 0x10f7, 0x25b8, 0x162e, 0xcca7, 0xb882, 0xb947, 0xd7a5, 0xe438, 0xfea1,
+0x33b7, 0x4cfe, 0x3b14, 0x1175, 0x24b7, 0x0bf7, 0xcecd, 0xbc06, 0xbdfc, 0xe81e,
+0xee24, 0x01b0, 0x3af8, 0x476e, 0x28b0, 0x20d9, 0x3412, 0xf8f1, 0xc8c9, 0xbcf1,
+0xbe75, 0xed66, 0xf061, 0x0477, 0x2e04, 0x4a13, 0x58d9, 0x3473, 0x0a86, 0xde23,
+0xc927, 0xa391, 0xa85a, 0xfd01, 0x01c0, 0x26a1, 0x8016, 0x6b60, 0x1f2e, 0x05f0,
+0xdab1, 0x948b, 0xb8c2, 0xc47d, 0xcb07, 0x3e6c, 0x61b3, 0x38d1, 0x421e, 0x423b,
+0xe147, 0xbac6, 0xe0e9, 0xbe22, 0xd9dd, 0x0b3a, 0x03de, 0x31f4, 0x40dc, 0xf8e0,
+0xf398, 0x2cdd, 0xec3a, 0xc582, 0x25ea, 0x1c58, 0xd8cd, 0x0700, 0x13d5, 0xbffb,
+0xdfbf, 0x2eeb, 0xf5a6, 0x0a90, 0x5506, 0x0dfc, 0xf5d4, 0x194f, 0xd0b1, 0xa469,
+0xf944, 0x0659, 0xe225, 0x3e8d, 0x4455, 0xf804, 0x285d, 0x16e8, 0xac54, 0xcbe6,
+0x0dc1, 0xcfa2, 0xef3a, 0x51ba, 0x0cea, 0x02d9, 0x4169, 0xec8e, 0xb42c, 0x04b3,
+0xfdcb, 0xc5c1, 0x233b, 0x2fa0, 0xe530, 0x27ec, 0x2937, 0xc096, 0xdeaa, 0x2435,
+0xe010, 0xe6f7, 0x3e0a, 0xfd91, 0xeb2e, 0x333b, 0xf30c, 0xb2be, 0x1250, 0x1fd9,
+0xc7fa, 0x1877, 0x3b5d, 0xdbdb, 0x0644, 0x23bb, 0xbdc0, 0xcc54, 0x2f0c, 0xf1f3,
+0xe57b, 0x48e5, 0x082c, 0xe598, 0x360e, 0xf80f, 0xa876, 0x0659, 0x1b42, 0xc57b,
+0x1fb9, 0x48cb, 0xe657, 0x1591, 0x3682, 0xcb26, 0xccd4, 0x2692, 0xe8fa, 0xda38,
+0x445f, 0x1220, 0xf0e0, 0x3d8f, 0x09d0, 0xb6bc, 0x00e1, 0x260a, 0xd188, 0x0576,
+0x3935, 0xef31, 0x1179, 0x33e9, 0xd401, 0xcde0, 0x2d56, 0xfa21, 0xdbb0, 0x4a3c,
+0x1c17, 0xdc92, 0x334e, 0x1ceb, 0xb17d, 0xebbf, 0x27e0, 0xd919, 0x0f7e, 0x4a4b,
+0xf20c, 0x107b, 0x3a20, 0xcab0, 0xba0a, 0x24cb, 0xf1fa, 0xcaf2, 0x3f6a, 0x2005,
+0xe495, 0x30ba, 0x16ef, 0xb7a7, 0xec01, 0x1401, 0xc9b7, 0x05b6, 0x393b, 0xe12d,
+0x07bf, 0x44cb, 0xdee8, 0xbd85, 0x23f1, 0xfc20, 0xc5f2, 0x2bd6, 0x194b, 0xdfc0,
+0x26e3, 0x160b, 0xba3e, 0xf25d, 0x2457, 0xcdc4, 0x004b, 0x4634, 0xe4dc, 0xf4a7,
+0x3d04, 0xdcc3, 0xb397, 0x1fb0, 0x0021, 0xcbc7, 0x37a0, 0x223d, 0xdd2e, 0x2e4a,
+0x1e22, 0xae9d, 0xea89, 0x28db, 0xc94f, 0xf4c9, 0x4a0c, 0xf565, 0xfefa, 0x4525,
+0xee89, 0xbfb1, 0x1b8b, 0xfa5c, 0xc436, 0x2f51, 0x24ca, 0xdfcd, 0x2cf4, 0x2a31,
+0xbd55, 0xe94e, 0x2b96, 0xd147, 0xee2f, 0x4235, 0xf2bf, 0xf601, 0x3b75, 0xe821,
+0xb876, 0x1f6c, 0x056a, 0xc597, 0x378b, 0x2e60, 0xd377, 0x1e9d, 0x283c, 0xb892,
+0xdee1, 0x2781, 0xd1f0, 0xf4ba, 0x554c, 0x0123, 0xf9be, 0x420b, 0xea52, 0xaa76,
+0x121d, 0x04f0, 0xbdca, 0x333f, 0x4315, 0xe753, 0x27d8, 0x325a, 0xb873, 0xd272,
+0x2388, 0xcf65, 0xe913, 0x57fe, 0x07f4, 0xf542, 0x4773, 0xf6c7, 0xab18, 0x11a2,
+0x0c42, 0xb926, 0x2816, 0x3c96, 0xde7c, 0x28fb, 0x3ab1, 0xb843, 0xd1f6, 0x2e08,
+0xd30e, 0xdd67, 0x552d, 0x04b1, 0xe6f1, 0x4408, 0xfac7, 0xa436, 0x0956, 0x1138,
+0xb9e0, 0x267a, 0x42e3, 0xd676, 0x199d, 0x3443, 0xb086, 0xc200, 0x28e5, 0xda7a,
+0xd827, 0x53d9, 0x0bab, 0xe3c1, 0x40a6, 0xfc98, 0xa29e, 0x018f, 0x1105, 0xb6e7,
+0x1b43, 0x498c, 0xdd00, 0x1541, 0x4138, 0xc0de, 0xbf2e, 0x283d, 0xe09c, 0xce50,
+0x4aa8, 0x1053, 0xdfdb, 0x3f0a, 0x0676, 0xa0c4, 0xfe21, 0x1b86, 0xb753, 0x10a7,
+0x4958, 0xd934, 0x0821, 0x3f0a, 0xc34f, 0xba57, 0x2b9e, 0xe817, 0xc7b8, 0x46a9,
+0x0faa, 0xd39d, 0x38e6, 0x0b67, 0x9f2e, 0xfa87, 0x2298, 0xbafd, 0x0a31, 0x466b,
+0xd41d, 0xfd51, 0x3a2b, 0xc101, 0xb40d, 0x2b20, 0xef1f, 0xc9c0, 0x46b9, 0x10a8,
+0xce9c, 0x3371, 0x097d, 0x9c55, 0xf5c4, 0x3090, 0xd2df, 0xecd4, 0x147e, 0xe806,
+0x2e2d, 0x4ebd, 0xe525, 0xe179, 0x26a7, 0xd61d, 0xaa44, 0x0053, 0xe8dc, 0xe136,
+0x46b6, 0x57fc, 0x24b5, 0x2da9, 0x235d, 0xcd31, 0xca81, 0xd1c7, 0xaf26, 0xfdb0,
+0x3bad, 0x09ac, 0x1da0, 0x74cf, 0x3a9c, 0xd9e6, 0xf662, 0xf1b3, 0xb74e, 0xc78b,
+0x079d, 0x1303, 0x25f7, 0x3f88, 0x14f4, 0x2459, 0x2c9b, 0xdd69, 0xc8da, 0xfb0c,
+0xfc56, 0xd94a, 0x0b6d, 0x29c0, 0x150a, 0x22e5, 0x1d92, 0x18ef, 0x1cfc, 0x0c23,
+0xcdc3, 0xcd7b, 0x0116, 0xd225, 0xea05, 0x3ecf, 0x321c, 0x114b, 0x3429, 0x4818,
+0xf80f, 0xe0f6, 0xd86d, 0xb88f, 0xe34c, 0xec85, 0xf335, 0x37f0, 0x5dfb, 0x1c42,
+0x11db, 0x4f23, 0xfc26, 0xba44, 0xd5aa, 0xd324, 0xd1fd, 0xf17c, 0x1856, 0x276a,
+0x4692, 0x286a, 0x0ba3, 0x3d7b, 0x1150, 0xc1ac, 0xbdf0, 0xe136, 0xd542, 0xcf26,
+0x1384, 0x2faf, 0x3870, 0x2e2c, 0x2318, 0x28d4, 0xfdee, 0xcb16, 0xa730, 0xc88d,
+0xdb00, 0xceb6, 0x1995, 0x4d9d, 0x384a, 0x1a5c, 0x3253, 0x2473, 0xd8f1, 0xc53c,
+0xafa7, 0xc440, 0xe9ed, 0xe807, 0x176e, 0x51f8, 0x4372, 0x074b, 0x2912, 0x2b6d,
+0xcf1e, 0xc37a, 0xc518, 0xc3a3, 0xdc9c, 0xfa3e, 0x1a98, 0x40fa, 0x4783, 0x0983,
+0x2539, 0x3545, 0xd15a, 0xad3e, 0xc7ca, 0xd8f8, 0xd06d, 0xf96a, 0x3340, 0x3d0f,
+0x375f, 0x19a1, 0x2cbe, 0x2480, 0xd619, 0xaa0f, 0xbfde, 0xe9c8, 0xd157, 0xf05e,
+0x42af, 0x4a8c, 0x2ada, 0x1e55, 0x372a, 0x09e1, 0xcd35, 0xbbbe, 0xb8fb, 0xe193,
+0xe480, 0xf329, 0x3476, 0x51aa, 0x233d, 0x1102, 0x44ce, 0x08c4, 0xbbca, 0xc158,
+0xc692, 0xd1e1, 0xdde2, 0x0656, 0x33cd, 0x4f74, 0x2ddc, 0x0f4d, 0x394e, 0x09e6,
+0xbe5c, 0xb5ad, 0xcc4d, 0xdc27, 0xdab2, 0x1043, 0x3ea4, 0x479b, 0x2b41, 0x1d61,
+0x308b, 0xf8bb, 0xc464, 0xb735, 0xcd64, 0xe75e, 0xe437, 0x161f, 0x4870, 0x48ad,
+0x1c9b, 0x1ea2, 0x33d1, 0xf0d7, 0xc828, 0xc0cf, 0xd2aa, 0xe99c, 0xeb83, 0x1a23,
+0x47fe, 0x4a7b, 0x1905, 0x1e38, 0x324a, 0xe958, 0xc1e9, 0xc325, 0xd7e4, 0xe635,
+0xf2dc, 0x2267, 0x3faa, 0x415f, 0x1a59, 0x202f, 0x2852, 0xe889, 0xc4bd, 0xbe6c,
+0xdab6, 0xe56a, 0xef10, 0x26eb, 0x4556, 0x3a6e, 0x180a, 0x2a09, 0x1fb9, 0xde8b,
+0xc5cf, 0xb9e8, 0xd7a7, 0xe7b9, 0xedd9, 0x2300, 0x49df, 0x37d1, 0x12a8, 0x2f9a,
+0x18aa, 0xce1b, 0xbdb0, 0xbc6b, 0xd29e, 0xdeee, 0xf71a, 0x2781, 0x45f4, 0x339d,
+0x0ccf, 0x2b8f, 0x12d6, 0xc8e8, 0xb6b6, 0xbda1, 0xd3af, 0xd912, 0xfe42, 0x2d6d,
+0x3ef8, 0x304b, 0x1689, 0x29cf, 0x067c, 0xcba1, 0xb57d, 0xbede, 0xdf90, 0xdc36,
+0xfffb, 0x3465, 0x3c40, 0x2128, 0x1a44, 0x31e1, 0xfd23, 0xcc9c, 0xbac3, 0xbc48,
+0xdd17, 0xde23, 0xfd31, 0x3137, 0x4362, 0x1eee, 0x18fc, 0x32b8, 0xf20e, 0xc5cc,
+0xbb44, 0xbdcc, 0xd9bd, 0xe2e3, 0x076b, 0x353c, 0x4892, 0x1e7b, 0x18f6, 0x30cf,
+0xea76, 0xbae7, 0xb387, 0xc4fa, 0xdcc0, 0xe7b8, 0x15fe, 0x3d5b, 0x45ed, 0x199f,
+0x192d, 0x2670, 0xe39c, 0xbd23, 0xb49a, 0xce79, 0xe000, 0xe7b3, 0x1c0b, 0x42fb,
+0x4231, 0x1500, 0x214f, 0x24b7, 0xddd0, 0xbdaf, 0xb443, 0xd1d6, 0xe488, 0xf035,
+0x248c, 0x481a, 0x412e, 0x17e3, 0x2a09, 0x1e55, 0xd730, 0xbb6d, 0xb458, 0xd6fe,
+0xe4f4, 0xf65d, 0x30dd, 0x50e1, 0x3ee0, 0x15ff, 0x2a77, 0x133f, 0xd208, 0xbb41,
+0xb5d0, 0xdc9b, 0xea0f, 0xfe2e, 0x33e8, 0x4ec6, 0x3729, 0x17b0, 0x327e, 0x0ae7,
+0xcbb1, 0xbd40, 0xbbb0, 0xe021, 0xe92f, 0x0274, 0x3a54, 0x53fa, 0x3201, 0x1417,
+0x340a, 0x073d, 0xcaf8, 0xbd21, 0xbddc, 0xdfe9, 0xebda, 0x0cee, 0x3df0, 0x5410,
+0x3025, 0x16c7, 0x32e0, 0xfb90, 0xc1f6, 0xbac8, 0xcb01, 0xe852, 0xeb00, 0x14bb,
+0x4362, 0x4e09, 0x267e, 0x175e, 0x2ea6, 0xf673, 0xc679, 0xb7ca, 0xce42, 0xef38,
+0xea88, 0x19da, 0x4c19, 0x4976, 0x1f9b, 0x2155, 0x2984, 0xe747, 0xc939, 0xbc60,
+0xced0, 0xf05b, 0xef4a, 0x1a1b, 0x4d06, 0x49db, 0x1707, 0x21a4, 0x261f, 0xdb58,
+0xc42f, 0xbd07, 0xcd03, 0xeb57, 0xf6f1, 0x1fe4, 0x4885, 0x45cb, 0x1391, 0x2121,
+0x232c, 0xd7ec, 0xbd29, 0xbf59, 0xd799, 0xe575, 0xf6b7, 0x28c6, 0x4774, 0x3e39,
+0x1137, 0x1ef7, 0x1744, 0xd610, 0xbc35, 0xb9e4, 0xdd70, 0xe860, 0xf41e, 0x2929,
+0x4423, 0x2e1c, 0x110a, 0x2d77, 0x0e41, 0xced2, 0xc1cd, 0xbb1d, 0xd9b8, 0xe41e,
+0xf4ed, 0x2b8f, 0x4959, 0x2b92, 0x0f36, 0x32ac, 0x0962, 0xc983, 0xc063, 0xbadd,
+0xd65d, 0xe557, 0xfe42, 0x2c4a, 0x49b0, 0x2cc4, 0x11af, 0x335e, 0x0099, 0xbfb6,
+0xb950, 0xc282, 0xda13, 0xe2c3, 0x0c6b, 0x2301, 0x3d0d, 0x5b32, 0x2bf1, 0xf816,
+0xd811, 0xc694, 0x94e5, 0xa76b, 0x02b3, 0xf90f, 0x23f0, 0x863b, 0x6a8b, 0x1164,
+0xf826, 0xd412, 0x8d7c, 0xade7, 0xbb44, 0xce06, 0x446e, 0x60cd, 0x37d3, 0x464b,
+0x4603, 0xdb1a, 0xadc9, 0xd748, 0xbb79, 0xd2d1, 0x0130, 0x0928, 0x3b0a, 0x4390,
+0x0002, 0xef18, 0x25c7, 0xf662, 0xbcac, 0x1201, 0x1f14, 0xda32, 0xff28, 0x1fbe,
+0xd1ed, 0xd6db, 0x389e, 0x0dbf, 0xf3fb, 0x488e, 0x1c7e, 0xe70e, 0x1374, 0xed9e,
+0xa6a3, 0xedff, 0x24fa, 0xe3d7, 0x2454, 0x55ff, 0xfbf6, 0x19ad, 0x27e2, 0xbe48,
+0xc206, 0x1296, 0xe45b, 0xdd35, 0x4db6, 0x1c33, 0xef46, 0x4348, 0x0b24, 0xb5eb,
+0xf732, 0x126a, 0xcd2b, 0x063c, 0x37c5, 0xec01, 0x18d9, 0x3cc8, 0xd616, 0xd2bc,
+0x273a, 0xf84b, 0xd381, 0x30e7, 0x1385, 0xdff9, 0x30f4, 0x18b0, 0xc10e, 0x0696,
+0x364d, 0xd762, 0xfbf8, 0x3d8c, 0xe397, 0xf63d, 0x3932, 0xe0e0, 0xc7fb, 0x31c6,
+0x0e47, 0xd472, 0x33f1, 0x19cb, 0xd9b6, 0x297e, 0x174d, 0xb770, 0xf385, 0x2b80,
+0xd844, 0x020b, 0x44ae, 0xecdc, 0xffc6, 0x3c84, 0xe59b, 0xc239, 0x1b9c, 0x03d8,
+0xcdfc, 0x284d, 0x2050, 0xe856, 0x29ba, 0x1da6, 0xc79c, 0xf13e, 0x2a8b, 0xdb89,
+0xe7cf, 0x315a, 0xf321, 0xf5f9, 0x332c, 0xf230, 0xc508, 0x1566, 0x093d, 0xce9a,
+0x23e7, 0x1dac, 0xd1bc, 0x1aa1, 0x2c3b, 0xbf9a, 0xd5cb, 0x286f, 0xe374, 0xeccb,
+0x3bb8, 0xfa4b, 0xf5e8, 0x2f2d, 0xe9fe, 0xbb23, 0x0ce2, 0xff3a, 0xc469, 0x2484,
+0x2c01, 0xe186, 0x1b3d, 0x26db, 0xc966, 0xd43c, 0x0cf6, 0xd3cc, 0xeaf1, 0x349d,
+0xf49e, 0xfa1b, 0x3da3, 0xf2ae, 0xb5a2, 0x09f1, 0x0303, 0xbee6, 0x1c15, 0x2f77,
+0xe137, 0x187e, 0x2a96, 0xc3d0, 0xd414, 0x1e88, 0xd685, 0xe774, 0x4a59, 0xfa19,
+0xe6e2, 0x3f71, 0xf365, 0xa657, 0x0aac, 0x0b27, 0xbbf9, 0x22a4, 0x3896, 0xdde2,
+0x1ea6, 0x2ddb, 0xb6a3, 0xcf13, 0x234d, 0xd209, 0xdfcd, 0x4de4, 0x0530, 0xf235,
+0x458e, 0xfe1c, 0xaf9c, 0x04ff, 0x0ba8, 0xc04a, 0x1dfe, 0x3a8f, 0xe44e, 0x1ebd,
+0x3786, 0xc838, 0xd340, 0x2d49, 0xe462, 0xdc75, 0x4606, 0x05e5, 0xe8d6, 0x3e07,
+0x0367, 0xb349, 0x0d09, 0x1be3, 0xc56a, 0x2148, 0x4004, 0xd8d7, 0x1595, 0x3a7d,
+0xc56d, 0xd126, 0x3261, 0xe7bb, 0xe124, 0x5308, 0x0eb8, 0xeb26, 0x437a, 0x042c,
+0xaf26, 0x0eb2, 0x218d, 0xc6ad, 0x22c2, 0x4ab9, 0xe2d2, 0x1874, 0x40cd, 0xca8d,
+0xc838, 0x2e68, 0xedb9, 0xd7fa, 0x4c7a, 0x1526, 0xe7cb, 0x4150, 0x0c9e, 0xae99,
+0x0430, 0x20bb, 0xc1b5, 0x1187, 0x47d4, 0xe4da, 0x1440, 0x42ec, 0xcdff, 0xc6df,
+0x2c13, 0xec08, 0xd2d5, 0x46ed, 0x0ef1, 0xdd36, 0x3d9e, 0x0f6f, 0xadf9, 0x0331,
+0x2434, 0xc516, 0x0f43, 0x4485, 0xde32, 0x0700, 0x3d61, 0xce91, 0xbeff, 0x2926,
+0xf4fa, 0xd134, 0x41a9, 0x1664, 0xdc49, 0x34e2, 0x1333, 0xace3, 0xf361, 0x1f70,
+0xc693, 0x0699, 0x47e9, 0xe934, 0x0537, 0x4024, 0xd7ae, 0xba09, 0x1da8, 0xf1cd,
+0xcbb4, 0x3981, 0x188e, 0xddeb, 0x326b, 0x1892, 0xb095, 0xf130, 0x20bc, 0xc29d,
+0xf9a7, 0x3f9b, 0xe3d4, 0xfb7a, 0x3e64, 0xdf3d, 0xbe59, 0x2395, 0xf6d6, 0xc4b9,
+0x3386, 0x1936, 0xd767, 0x2b7a, 0x1dd1, 0xb547, 0xeefe, 0x2814, 0xcaa5, 0xf817,
+0x487c, 0xeb89, 0xf26b, 0x397c, 0xdf7f, 0xb39b, 0x1c04, 0xfe68, 0xc54d, 0x349f,
+0x2a68, 0xdbdd, 0x22d6, 0x1ddd, 0xb0a0, 0xe0fa, 0x21cc, 0xc993, 0xf898, 0x52ec,
+0xf689, 0xf70a, 0x426a, 0xe741, 0xaf61, 0x151d, 0xf921, 0xc0cc, 0x3886, 0x3002,
+0xdf54, 0x2dc5, 0x25fe, 0xb022, 0xe019, 0x2b01, 0xdc46, 0xdf34, 0x113b, 0xf3ef,
+0x2b9e, 0x5380, 0xf6a9, 0xdedb, 0x2143, 0xe2d9, 0xa1ed, 0xf09b, 0xf0dc, 0xdb67,
+0x3407, 0x5e55, 0x32ca, 0x27e1, 0x25c5, 0xd709, 0xc36a, 0xcfc9, 0xa7f8, 0xea51,
+0x3bd7, 0x0e16, 0x0801, 0x6978, 0x4f5b, 0xdcee, 0xefc1, 0xfe47, 0xbe36, 0xbc03,
+0xf6fb, 0x0a54, 0x1b49, 0x3bd0, 0x15ec, 0x2382, 0x3cb2, 0xeb19, 0xc5aa, 0xedb7,
+0xf957, 0xd7a6, 0xf768, 0x22e7, 0x17a3, 0x2061, 0x1ba7, 0x1fd9, 0x23d8, 0x0c01,
+0xd667, 0xc4b5, 0xfb1e, 0xd994, 0xd7e9, 0x3398, 0x3e92, 0x166b, 0x27af, 0x4ec2,
+0x0515, 0xd7d3, 0xde1a, 0xb994, 0xdcbe, 0xf079, 0xeced, 0x2d31, 0x5f61, 0x253a,
+0x040a, 0x4aa6, 0x0dcf, 0xb7f1, 0xcf3e, 0xd1b1, 0xcf61, 0xe63f, 0x105c, 0x26cc,
+0x431a, 0x31ef, 0x089d, 0x3279, 0x1505, 0xc9c8, 0xbc77, 0xd253, 0xdbe3, 0xd185,
+0x028c, 0x2ef7, 0x3d5b, 0x2ff3, 0x2436, 0x2ecc, 0xffae, 0xd27d, 0xae72, 0xb7bb,
+0xda8b, 0xd2ea, 0x0b5f, 0x4d03, 0x43e6, 0x19f3, 0x2a63, 0x2e3d, 0xdc83, 0xc2d3,
+0xb8a6, 0xbf1e, 0xe681, 0xe9fe, 0x0c02, 0x47ed, 0x4b9b, 0x0b96, 0x1c09, 0x3193,
+0xdad3, 0xc2e7, 0xcba3, 0xc7a2, 0xd979, 0xf765, 0x1768, 0x34eb, 0x4272, 0x0e6d,
+0x1cba, 0x35bf, 0xe2f7, 0xb4ec, 0xc2dc, 0xdbc9, 0xd4b0, 0xeaf5, 0x2a04, 0x3d45,
+0x3135, 0x163c, 0x29bf, 0x1de9, 0xd862, 0xb512, 0xbc25, 0xe60f, 0xdb2d, 0xe4e1,
+0x2e4c, 0x46ed, 0x2280, 0x0c95, 0x3390, 0x0dba, 0xc912, 0xc227, 0xbe99, 0xd9c1,
+0xe290, 0xed33, 0x2239, 0x44f4, 0x1ed4, 0x0166, 0x3720, 0x122c, 0xc1e5, 0xbe0b,
+0xcc00, 0xd5f1, 0xd17f, 0xf3b3, 0x23a2, 0x3db4, 0x2624, 0x0cc4, 0x33e1, 0x1023,
+0xc9ae, 0xb631, 0xc8cd, 0xdd8a, 0xceea, 0xfaf7, 0x3621, 0x3ded, 0x1ca5, 0x1b0d,
+0x3ba6, 0x0216, 0xce92, 0xc394, 0xc90e, 0xdd1a, 0xd9c7, 0x0438, 0x3a3b, 0x43f5,
+0x193b, 0x1b93, 0x427f, 0xfe8c, 0xc66d, 0xc591, 0xd58c, 0xe393, 0xe1bc, 0x0fe2,
+0x3df6, 0x448e, 0x1c8a, 0x21c0, 0x3e71, 0xf9bd, 0xc96f, 0xc456, 0xd5d9, 0xe4b5,
+0xe922, 0x1ce5, 0x418b, 0x417b, 0x1d09, 0x2518, 0x341d, 0xf03a, 0xcbea, 0xc1e1,
+0xd6e8, 0xe7f3, 0xe55f, 0x1d2e, 0x4954, 0x3f9e, 0x1b23, 0x31e7, 0x3163, 0xe580,
+0xcb7b, 0xbe90, 0xd27b, 0xe98e, 0xede5, 0x22f3, 0x51cc, 0x440f, 0x174a, 0x3744,
+0x308b, 0xdab4, 0xc2e8, 0xc03f, 0xd50d, 0xe3f9, 0xf766, 0x2e0e, 0x4fbc, 0x4269,
+0x17f2, 0x2ec2, 0x1f55, 0xd524, 0xbca2, 0xbb93, 0xda21, 0xe34e, 0xfad1, 0x31b6,
+0x47da, 0x36e7, 0x19ff, 0x2ef6, 0x0ef3, 0xd0a0, 0xb94c, 0xb54a, 0xdd97, 0xe171,
+0xf86b, 0x35bc, 0x47ba, 0x2983, 0x1694, 0x3324, 0x023b, 0xc961, 0xbc03, 0xb2d4,
+0xd7d9, 0xe2e7, 0xf9f7, 0x3391, 0x4b1a, 0x23f3, 0x1255, 0x2f9f, 0xf2dd, 0xbf1b,
+0xb8c8, 0xb595, 0xd67b, 0xe49e, 0x04eb, 0x3553, 0x491e, 0x20bc, 0x101c, 0x2d09,
+0xefb9, 0xbc9b, 0xb456, 0xbc74, 0xdcb8, 0xe7e3, 0x1085, 0x3e2d, 0x4862, 0x20a4,
+0x178e, 0x24e7, 0xe5be, 0xbfc5, 0xb5ac, 0xc6ce, 0xe4c1, 0xe9df, 0x156b, 0x41e6,
+0x4347, 0x169b, 0x1dcf, 0x2537, 0xdbdb, 0xbd60, 0xb4ac, 0xc74f, 0xe58f, 0xf1d1,
+0x1bdb, 0x438d, 0x41e3, 0x1215, 0x1c5f, 0x197b, 0xd34e, 0xbb05, 0xb7f2, 0xd3b2,
+0xe362, 0xf0f9, 0x2581, 0x46a8, 0x3c22, 0x115f, 0x2009, 0x122f, 0xd304, 0xbd34,
+0xb54b, 0xd890, 0xe911, 0xf7d8, 0x2be6, 0x4644, 0x3124, 0x11c4, 0x2d7b, 0x0ee0,
+0xcfc2, 0xc445, 0xbf59, 0xdf01, 0xe6a3, 0xf6fe, 0x2fa7, 0x4f24, 0x32c6, 0x10fe,
+0x360f, 0x14d0, 0xd1fe, 0xc5f6, 0xc3cd, 0xdf16, 0xe84d, 0x00f9, 0x3302, 0x4f34,
+0x330f, 0x190f, 0x3c38, 0x0f70, 0xcd2f, 0xc1c9, 0xccf8, 0xe5e1, 0xe528, 0x0d0a,
+0x3fdc, 0x4c89, 0x2965, 0x1572, 0x3558, 0x0773, 0xcf30, 0xbe56, 0xcfa1, 0xeeb4,
+0xe390, 0x0d40, 0x446b, 0x47d6, 0x22c9, 0x1f4d, 0x33f7, 0xf611, 0xcd9c, 0xbd57,
+0xc66d, 0xec7d, 0xe7bf, 0x0df9, 0x474d, 0x4b43, 0x1b2f, 0x1f2d, 0x35f1, 0xea3c,
+0xc455, 0xbc6b, 0xc16f, 0xe41e, 0xef9d, 0x16e5, 0x457b, 0x4d9b, 0x1df1, 0x2052,
+0x310e, 0xe35e, 0xbd3b, 0xbe4a, 0xd17b, 0xe63a, 0xf0dd, 0x2445, 0x4bde, 0x4c1f,
+0x20d2, 0x25a1, 0x2da1, 0xe9a8, 0xc288, 0xb909, 0xd9c6, 0xee3e, 0xf6a6, 0x2ef9,
+0x50a3, 0x43ee, 0x1dd3, 0x2eed, 0x2309, 0xe027, 0xcabc, 0xbd16, 0xdbde, 0xee25,
+0xf339, 0x2c27, 0x5390, 0x40a0, 0x1b96, 0x375c, 0x1d2b, 0xd734, 0xc78b, 0xb807,
+0xd541, 0xecee, 0xfe54, 0x2fa0, 0x5361, 0x3f28, 0x1522, 0x3393, 0x1266, 0xc88d,
+0xbc85, 0xb8d2, 0xd4a8, 0xe5ab, 0x0438, 0x3396, 0x4c0c, 0x33cd, 0x0a79, 0x2169,
+0xfe45, 0xc2f1, 0xb2f9, 0xb954, 0xe0e9, 0xe165, 0x029e, 0x2aa5, 0x318e, 0x465f,
+0x33d0, 0x0444, 0xd0f3, 0xc734, 0xa46c, 0x9309, 0xec1b, 0xfbdf, 0x010e, 0x6440,
+0x7e13, 0x1ed8, 0xeedd, 0xe7dd, 0x9c36, 0x94bd, 0xaeea, 0xb4e6, 0x08d9, 0x4ccc,
+0x44d3, 0x3995, 0x43b3, 0xfbc8, 0xa9f8, 0xbc47, 0xb7e9, 0xb05c, 0xd4b9, 0x0205,
+0x2fad, 0x38f1, 0x145d, 0xf759, 0x0aec, 0xf6cf, 0xb93a, 0xdb0a, 0x14b9, 0xe52f,
+0xdb64, 0x2275, 0xfc04, 0xc169, 0x1482, 0x2708, 0xda58, 0x13ad, 0x34f3, 0xe68c,
+0xf5c6, 0x0b27, 0xbfca, 0xc11f, 0x1a39, 0xf895, 0xf15a, 0x535e, 0x1fbe, 0xfd1c,
+0x2fab, 0xeb46, 0xb207, 0xf114, 0xfe7d, 0xd0f4, 0x2a5f, 0x4be3, 0xf98f, 0x2db3,
+0x365b, 0xcbe5, 0xd12f, 0x160e, 0xe93e, 0xe29c, 0x4478, 0x1d6f, 0x0413, 0x49f1,
+0x0cdb, 0xc582, 0x08ff, 0x1ce3, 0xd295, 0x10d4, 0x44fb, 0xeff9, 0x15bb, 0x4310,
+0xddfd, 0xd8c9, 0x34f7, 0xfaaf, 0xd716, 0x4457, 0x1cff, 0xe28a, 0x3514, 0x1237,
+0xb5a9, 0x01cc, 0x3075, 0xd90a, 0x1130, 0x4b8f, 0xf1cd, 0x0fee, 0x37f4, 0xcf77,
+0xc32c, 0x262d, 0xfb1f, 0xde11, 0x4cfa, 0x220f, 0xe7b6, 0x379f, 0x1630, 0xb473,
+0xeb2b, 0x1f40, 0xcdde, 0xfb49, 0x44ba, 0xf7a8, 0x0b44, 0x3ad4, 0xdf56, 0xbc11,
+0x192e, 0xfdae, 0xc1e6, 0x2be2, 0x260a, 0xe13b, 0x285b, 0x1e09, 0xb912, 0xe471,
+0x2223, 0xd36d, 0xfe2f, 0x46c5, 0xe36b, 0xf7a8, 0x4755, 0xdf55, 0xa7b3, 0x1a23,
+0x0b84, 0xc893, 0x2faf, 0x2991, 0xe2bb, 0x2481, 0x13cb, 0xb383, 0xe0a3, 0x1702,
+0xc7e4, 0xf45d, 0x4b67, 0xf6a6, 0xf97a, 0x3e6f, 0xecd9, 0xadf3, 0xfeb4, 0xfd06,
+0xc788, 0x1f7a, 0x200a, 0xe5ba, 0x31ba, 0x2511, 0xb3da, 0xe100, 0x2776, 0xc990,
+0xe1ba, 0x44f0, 0xef0a, 0xe6e3, 0x3c2c, 0xf024, 0xb397, 0x1376, 0x054e, 0xc2a5,
+0x2c7f, 0x2067, 0xc5d1, 0x227f, 0x2507, 0xa6ee, 0xd8b4, 0x2da2, 0xd0d2, 0xe5d2,
+0x4ae3, 0xf3c8, 0xec01, 0x3d1d, 0xe4c8, 0xa76d, 0x0cd7, 0x02fe, 0xc051, 0x2a3f,
+0x2ff4, 0xd96d, 0x206e, 0x2cdb, 0xbcc2, 0xcfce, 0x1d9e, 0xd936, 0xe286, 0x3c62,
+0xf899, 0xf161, 0x4188, 0xfacc, 0xb89c, 0x13b2, 0x0f18, 0xbfc2, 0x1908, 0x2b9b,
+0xdd40, 0x1c58, 0x31c8, 0xcc18, 0xe1de, 0x3029, 0xe1a2, 0xeab8, 0x4814, 0xf8bb,
+0xec3b, 0x41cd, 0xfd3d, 0xbb42, 0x1ab1, 0x1bc5, 0xd03f, 0x2b94, 0x39e6, 0xe56b,
+0x20f6, 0x302f, 0xc7e3, 0xe07e, 0x304b, 0xe35d, 0xef37, 0x556b, 0x0a77, 0xf3f3,
+0x471e, 0x0715, 0xb6bf, 0x0a15, 0x15a7, 0xc8f0, 0x27ce, 0x49e1, 0xef3f, 0x24d8,
+0x3fc8, 0xcf85, 0xd3a9, 0x2981, 0xe391, 0xe1e2, 0x5568, 0x1a44, 0xf6f8, 0x47a5,
+0x0bb1, 0xb4f6, 0x0758, 0x180f, 0xc264, 0x1c7f, 0x4997, 0xe7b6, 0x1790, 0x3eec,
+0xcc71, 0xc989, 0x25c5, 0xe0ff, 0xd5eb, 0x4cb5, 0x1585, 0xe9fb, 0x3f4e, 0x079f,
+0xa4e8, 0xf8b6, 0x18b7, 0xbe0e, 0x140f, 0x4dcd, 0xe6f8, 0x106e, 0x3e48, 0xc659,
+0xb8d1, 0x1fc6, 0xe2f7, 0xcafc, 0x4b21, 0x1aac, 0xddf9, 0x38ef, 0x0d86, 0xa172,
+0xefb8, 0x1ae3, 0xb8b9, 0x022b, 0x4952, 0xe3b7, 0x0763, 0x3fb6, 0xcaa1, 0xb546,
+0x2034, 0xe7b3, 0xc26e, 0x4341, 0x1b44, 0xd872, 0x37f9, 0x158c, 0xa302, 0xed16,
+0x212e, 0xbda6, 0x0112, 0x4eb5, 0xe481, 0x0180, 0x448c, 0xd2be, 0xb34a, 0x2659,
+0xf969, 0xc3e7, 0x4253, 0x2498, 0xd3ac, 0x2f84, 0x1c93, 0xa86a, 0xeae4, 0x2be9,
+0xc824, 0xfb1e, 0x5254, 0xeb1f, 0xf85a, 0x40c6, 0xd602, 0xacc6, 0x20d2, 0xfcc1,
+0xbfa1, 0x3d9b, 0x2b83, 0xd669, 0x2fcc, 0x202d, 0xa65f, 0xe170, 0x2e20, 0xdd4c,
+0xe4e8, 0x1128, 0xe9c6, 0x2b2e, 0x54f5, 0xec68, 0xd881, 0x25b3, 0xe44f, 0x9e7f,
+0xf130, 0xeb8e, 0xd492, 0x3413, 0x5451, 0x26e6, 0x289d, 0x2623, 0xcf51, 0xbd9d,
+0xcfb5, 0xa20d, 0xdfe7, 0x36b7, 0x0cc4, 0x056d, 0x65fe, 0x4dbe, 0xdc03, 0xeb0b,
+0xf3c5, 0xb95e, 0xbfab, 0xf463, 0x0575, 0x1cd4, 0x3d59, 0x1332, 0x1c9e, 0x36b2,
+0xe6ff, 0xc1d2, 0xebab, 0xf875, 0xd9ed, 0xf751, 0x1ec8, 0x177a, 0x1fdf, 0x14b0,
+0x158a, 0x1d8b, 0x09a1, 0xd3cc, 0xc301, 0xfa03, 0xda41, 0xd913, 0x31d2, 0x3bcd,
+0x1398, 0x2276, 0x47f0, 0x04ad, 0xdb13, 0xde05, 0xbc40, 0xe0b4, 0xf233, 0xedf8,
+0x2b4d, 0x5c62, 0x258d, 0x04db, 0x4756, 0x0b9d, 0xbaa9, 0xd2dc, 0xd3e6, 0xd2e5,
+0xeb10, 0x1044, 0x260b, 0x4492, 0x307c, 0x0989, 0x37e3, 0x1952, 0xc9b1, 0xbe5d,
+0xd980, 0xdd51, 0xcfb1, 0x0665, 0x3440, 0x3f66, 0x32e5, 0x2caa, 0x3828, 0x04a2,
+0xd6f3, 0xb98c, 0xc152, 0xdc75, 0xd7fc, 0x131d, 0x50a8, 0x4584, 0x218f, 0x39e9,
+0x3a8c, 0xe3c0, 0xca56, 0xc147, 0xc3c0, 0xe3a0, 0xec33, 0x13df, 0x4aaa, 0x4c83,
+0x117c, 0x2896, 0x399a, 0xdc86, 0xc6f6, 0xcd47, 0xc6ce, 0xd9cc, 0xf86e, 0x19ad,
+0x3a91, 0x491f, 0x123e, 0x24d9, 0x3cde, 0xe11b, 0xb37c, 0xc2df, 0xd7a4, 0xd130,
+0xef29, 0x2c49, 0x3d20, 0x37d9, 0x16ea, 0x291c, 0x26ac, 0xda1f, 0xaaed, 0xb607,
+0xe686, 0xd568, 0xe17d, 0x337f, 0x4b43, 0x2881, 0x1389, 0x38c7, 0x12c8, 0xcd26,
+0xbcc3, 0xb56e, 0xdd1b, 0xe483, 0xea42, 0x2a90, 0x51ed, 0x261e, 0x0805, 0x4126,
+0x1494, 0xbddc, 0xbaf3, 0xc450, 0xd324, 0xd5cd, 0xf712, 0x2ef6, 0x4d20, 0x2af3,
+0x0e0c, 0x3762, 0x0ac6, 0xbe79, 0xae49, 0xc076, 0xd8d1, 0xd3ec, 0x0029, 0x3a43,
+0x4756, 0x22ca, 0x1958, 0x35d2, 0xf721, 0xbeb2, 0xb3fe, 0xbef0, 0xda1f, 0xdc92,
+0x07d3, 0x4050, 0x4bd0, 0x19d3, 0x154a, 0x3522, 0xefeb, 0xbbc4, 0xb715, 0xc994,
+0xdf48, 0xe0b3, 0x1046, 0x40b6, 0x462c, 0x16f6, 0x1aab, 0x3230, 0xe981, 0xbd2a,
+0xbab3, 0xd2a2, 0xe5c9, 0xe97f, 0x1c0b, 0x466f, 0x4272, 0x167d, 0x2642, 0x3042,
+0xe930, 0xc684, 0xbe13, 0xdccc, 0xed63, 0xea9d, 0x2413, 0x5441, 0x43f9, 0x180c,
+0x36e2, 0x2e3f, 0xdd7b, 0xc957, 0xc358, 0xdaa2, 0xeec1, 0xf6ba, 0x27c0, 0x5560,
+0x448c, 0x1635, 0x38ef, 0x285a, 0xd524, 0xc336, 0xc6ef, 0xdd82, 0xe86a, 0x024a,
+0x3514, 0x5243, 0x4024, 0x1679, 0x30ad, 0x19cb, 0xd2e3, 0xbea4, 0xc57f, 0xe4e0,
+0xe5d6, 0xfff4, 0x3873, 0x4af7, 0x2f8e, 0x188b, 0x3243, 0x0881, 0xcd5e, 0xbb93,
+0xbf31, 0xe52f, 0xe3f9, 0xfc94, 0x39c7, 0x4714, 0x1d41, 0x169d, 0x38c9, 0xfecd,
+0xcb1a, 0xc0fc, 0xbdbe, 0xdc75, 0xe1b5, 0xfaa7, 0x3213, 0x48a1, 0x1b68, 0x15dc,
+0x3a99, 0xf55f, 0xc01b, 0xbf71, 0xc2a6, 0xd4e4, 0xdf4d, 0x066a, 0x3170, 0x40f1,
+0x1a26, 0x18e0, 0x342c, 0xedf9, 0xba41, 0xb3b4, 0xc38e, 0xd795, 0xe02b, 0x10cf,
+0x387d, 0x3bf9, 0x151b, 0x1afe, 0x254b, 0xde99, 0xbc43, 0xb3bd, 0xc921, 0xde4a,
+0xe189, 0x1332, 0x3ec6, 0x3af6, 0x0d82, 0x1fcf, 0x23ba, 0xd6bf, 0xb847, 0xb180,
+0xcc73, 0xe0cc, 0xeb2b, 0x1cb5, 0x417e, 0x395b, 0x0e37, 0x260e, 0x1c37, 0xceea,
+0xb693, 0xb466, 0xd44d, 0xde55, 0xeccf, 0x28e9, 0x4c64, 0x3a26, 0x1085, 0x2a16,
+0x12d5, 0xcc04, 0xb7f6, 0xb4e4, 0xda25, 0xe424, 0xf801, 0x3387, 0x4c82, 0x2ff0,
+0x133d, 0x33da, 0x0a6a, 0xc653, 0xba9f, 0xb9a4, 0xdb0d, 0xe2d3, 0xfd70, 0x3932,
+0x535b, 0x2ec6, 0x131d, 0x36f2, 0x041c, 0xc4de, 0xbc13, 0xbb88, 0xdb49, 0xe632,
+0x0792, 0x3bf9, 0x5215, 0x2df5, 0x197b, 0x38ba, 0xfc1c, 0xc000, 0xb9c1, 0xc559,
+0xe3b9, 0xea55, 0x13bb, 0x45e0, 0x506f, 0x2598, 0x1a8a, 0x31d4, 0xf25d, 0xc2c1,
+0xb752, 0xc926, 0xeaa3, 0xebad, 0x18ac, 0x4b3a, 0x4b14, 0x1dc9, 0x246e, 0x2f24,
+0xe2ff, 0xc4be, 0xbca8, 0xccb7, 0xee7a, 0xf1d7, 0x1fad, 0x5446, 0x4e8c, 0x1a55,
+0x2bfd, 0x2de3, 0xdbcc, 0xc7c4, 0xc19e, 0xcdf0, 0xeebf, 0x0347, 0x2b46, 0x50aa,
+0x4efc, 0x1ae7, 0x2bc2, 0x29fc, 0xd661, 0xbcdd, 0xc496, 0xddc3, 0xe917, 0xff6c,
+0x336f, 0x4f83, 0x433f, 0x1820, 0x2877, 0x176c, 0xd63d, 0xbf37, 0xbd41, 0xe3e4,
+0xec70, 0xfccc, 0x35e8, 0x4cad, 0x2f68, 0x12a7, 0x2fac, 0x08d7, 0xcd76, 0xc4f5,
+0xbf59, 0xdfbb, 0xe9f3, 0xfd34, 0x3194, 0x4aa3, 0x2767, 0x0f36, 0x33c2, 0x0048,
+0xc54f, 0xc2fd, 0xbd10, 0xd605, 0xe41c, 0xff51, 0x2a83, 0x4801, 0x28e6, 0x0aff,
+0x2f62, 0xfdb7, 0xbd39, 0xb775, 0xc18e, 0xd7dc, 0xe021, 0x0a7d, 0x33af, 0x423a,
+0x2081, 0x0d26, 0x28f2, 0xf6aa, 0xc604, 0xb20f, 0xca22, 0xf929, 0xe0c9, 0xfe41,
+0x2c91, 0x285c, 0x3479, 0x40e1, 0x11dd, 0xd23c, 0xd207, 0xa861, 0xa807, 0xfe96,
+0xf904, 0x0d6b, 0x7934, 0x78f8, 0x0f08, 0xfce1, 0xe859, 0x8e1b, 0xa714, 0xc4a3,
+0xd05d, 0x3229, 0x6379, 0x4af3, 0x48ea, 0x4a2d, 0xe67d, 0xa875, 0xd782, 0xc120,
+0xb7cb, 0xfb9f, 0x1f2e, 0x34e8, 0x4bf0, 0x20cd, 0xf0b2, 0x1643, 0x0563, 0xbcd0,
+0xf27f, 0x2741, 0xe7cd, 0xef58, 0x2d5f, 0xebc6, 0xc657, 0x2de0, 0x1cb6, 0xe47e,
+0x4360, 0x2d6e, 0xe23e, 0x16ee, 0xf98d, 0xa445, 0xddbb, 0x207f, 0xe2c0, 0x123e,
+0x5bce, 0x016d, 0x1099, 0x3582, 0xc93d, 0xb242, 0x0573, 0xeaa3, 0xcd42, 0x3ec8,
+0x2edf, 0xedf6, 0x39f9, 0x19b8, 0xb85b, 0xe3bc, 0x1369, 0xd1a8, 0xf1db, 0x3e71,
+0xf679, 0x05fa, 0x4211, 0xe5a8, 0xbfbb, 0x17fb, 0x0686, 0xc767, 0x1e4b, 0x24e5,
+0xdea8, 0x2460, 0x26bf, 0xbfbf, 0xebef, 0x3662, 0xdb5b, 0xe735, 0x478f, 0xf36f,
+0xe6aa, 0x3a12, 0xedab, 0xb885, 0x2168, 0x1a11, 0xd137, 0x2b7c, 0x29a9, 0xd6be,
+0x2004, 0x24d0, 0xb744, 0xddad, 0x2f52, 0xe201, 0xeeef, 0x4b3b, 0xfc02, 0xef7b,
+0x3d92, 0xf6b9, 0xb6be, 0x0b17, 0x0d77, 0xc534, 0x191c, 0x30b3, 0xe945, 0x23b0,
+0x2f3b, 0xc9bb, 0xd7dc, 0x269c, 0xe46c, 0xd902, 0x3766, 0x0212, 0xeb25, 0x39af,
+0x03e0, 0xb8e7, 0x077c, 0x169d, 0xcc6f, 0x1b10, 0x2e0e, 0xd19a, 0x0fd8, 0x38f6,
+0xca91, 0xc8f9, 0x29d6, 0xf078, 0xdd5c, 0x3b96, 0x09ef, 0xf001, 0x3507, 0xfbb1,
+0xb660, 0x03d5, 0x0e0a, 0xc387, 0x17f0, 0x3e5b, 0xe93c, 0x15a4, 0x38a5, 0xd46a,
+0xc5fe, 0x1360, 0xe4bc, 0xda9b, 0x39bc, 0x0c8d, 0xef2a, 0x3f9f, 0x0fdb, 0xb193,
+0xf8d2, 0x1694, 0xb855, 0xffea, 0x4558, 0xed39, 0x083b, 0x3cd1, 0xda45, 0xc36d,
+0x1e6e, 0xe476, 0xc765, 0x430f, 0x19a5, 0xde45, 0x3c55, 0x16d3, 0xa619, 0xf171,
+0x1f0a, 0xbe1b, 0x04f9, 0x4d9d, 0xeec4, 0x0d1f, 0x3e78, 0xccbe, 0xb49f, 0x1d9b,
+0xeb29, 0xc2b6, 0x448d, 0x28e1, 0xe2a4, 0x3aab, 0x21b9, 0xad2c, 0xe5d9, 0x1d14,
+0xbe21, 0xf5ec, 0x4db1, 0xef99, 0x030b, 0x4e45, 0xe3cc, 0xb105, 0x20c5, 0xfc50,
+0xb9ac, 0x35e2, 0x2898, 0xd9cf, 0x3397, 0x236d, 0xace6, 0xea90, 0x2c38, 0xc65d,
+0xf7eb, 0x5313, 0xe39f, 0xf211, 0x477a, 0xdbe9, 0xad7a, 0x1ffa, 0x0017, 0xc201,
+0x3906, 0x27ff, 0xd56a, 0x2d70, 0x20b7, 0xa843, 0xe22b, 0x2d3a, 0xc8d1, 0xeff9,
+0x5809, 0xf171, 0xf1ca, 0x4aa1, 0xe752, 0xa7f6, 0x1531, 0x03a8, 0xbd38, 0x3515,
+0x35e4, 0xd873, 0x2cec, 0x2fa3, 0xaedb, 0xd92e, 0x2cc6, 0xccea, 0xe531, 0x5116,
+0xf947, 0xf6dd, 0x4ce8, 0xef45, 0xaeac, 0x190f, 0x0bfc, 0xbc68, 0x2afb, 0x3612,
+0xd723, 0x2687, 0x3899, 0xbdfb, 0xda26, 0x3069, 0xdbaf, 0xe6bf, 0x5016, 0x0057,
+0xf1d5, 0x480e, 0xf64b, 0xad29, 0x13a0, 0x16f4, 0xc33e, 0x2614, 0x4041, 0xe193,
+0x201a, 0x3648, 0xc1be, 0xd35e, 0x2aee, 0xe031, 0xe3b6, 0x52de, 0x0b18, 0xedc3,
+0x4b67, 0x084a, 0xaf95, 0x0ccc, 0x1af7, 0xc089, 0x19da, 0x4369, 0xe6d2, 0x1d97,
+0x422d, 0xcfe2, 0xd1f7, 0x2e93, 0xe6d4, 0xd904, 0x4acc, 0x0d46, 0xe47d, 0x425b,
+0x0eb4, 0xb1bb, 0x06af, 0x2009, 0xc469, 0x12a4, 0x3fee, 0xdd98, 0x0e0f, 0x3d40,
+0xcdb1, 0xc9b2, 0x2f38, 0xedc8, 0xcf7d, 0x42ef, 0x1026, 0xd9af, 0x3710, 0x1033,
+0xad12, 0xfd62, 0x2285, 0xc358, 0x0c72, 0x48be, 0xe51c, 0x0a9a, 0x2ff8, 0xbdc8,
+0xc28d, 0x2cc9, 0xfec9, 0xc7ca, 0x13e5, 0x17e1, 0x02ca, 0x35c6, 0x0fac, 0xcd93,
+0xf8c0, 0xfbac, 0xbba6, 0xe67f, 0x0735, 0xd6a6, 0x1bde, 0x7350, 0x2eb9, 0xfad9,
+0x2334, 0xeee4, 0xa104, 0xc9e5, 0xcf89, 0xd376, 0x2f8b, 0x2f58, 0x13f9, 0x6199,
+0x5172, 0xd760, 0xdae6, 0x0109, 0xaaab, 0x9f01, 0x11f6, 0x1c74, 0xff8b, 0x303b,
+0x3d89, 0x2a49, 0x1900, 0xe5d6, 0xbb94, 0xe00a, 0xeeb6, 0xce23, 0x0253, 0x3291,
+0x20e8, 0x0d77, 0x2650, 0x2ac1, 0xf46d, 0xf971, 0xdbe5, 0xc599, 0xf13a, 0xdd86,
+0xebfb, 0x30a4, 0x422d, 0x0726, 0x20e1, 0x53cf, 0xe990, 0xbf8d, 0xd717, 0xc738,
+0xcc12, 0xed69, 0x1207, 0x2679, 0x4b59, 0x2072, 0x0f6e, 0x3b3c, 0xef93, 0xaf82,
+0xc07e, 0xdcf5, 0xccea, 0xe17b, 0x31b5, 0x3ad1, 0x3673, 0x24ae, 0x1fad, 0x2260,
+0xf0dd, 0xc29e, 0xb494, 0xe43c, 0xe377, 0xdb2b, 0x2318, 0x45fc, 0x34e0, 0x20c4,
+0x3ea5, 0x232d, 0xdb31, 0xcc1d, 0xb320, 0xc65a, 0xdf18, 0xea47, 0x25f0, 0x5cd4,
+0x4386, 0x10e7, 0x3d78, 0x214b, 0xc538, 0xbca9, 0xbe8b, 0xcd16, 0xe049, 0x05f5,
+0x2fe8, 0x5197, 0x45f2, 0x0fc2, 0x2f2f, 0x1971, 0xc5a7, 0xb7fc, 0xca80, 0xd975,
+0xd7df, 0x0c99, 0x3a4e, 0x3fc6, 0x30e6, 0x13a0, 0x2b87, 0x0f79, 0xcd82, 0xaf27,
+0xc107, 0xe381, 0xd5f3, 0x035c, 0x44e6, 0x44fc, 0x1db9, 0x1e95, 0x3a4e, 0xf713,
+0xc17f, 0xb545, 0xc44b, 0xea43, 0xe0b2, 0x034f, 0x458e, 0x4e96, 0x1557, 0x1819,
+0x3caa, 0xeb53, 0xb81f, 0xc29d, 0xcb34, 0xd827, 0xea2e, 0x153f, 0x3683, 0x4599,
+0x17a8, 0x1025, 0x3238, 0xefae, 0xb3df, 0xb7d0, 0xd811, 0xd6f7, 0xd85a, 0x1792,
+0x3a94, 0x35ea, 0x1657, 0x21c8, 0x243d, 0xdfb1, 0xbb30, 0xb0e9, 0xceed, 0xddbf,
+0xdff2, 0x1be1, 0x44e0, 0x3265, 0x0879, 0x260d, 0x1f6a, 0xcd4c, 0xba1b, 0xbafd,
+0xcb3d, 0xd899, 0xeb07, 0x19d6, 0x3c13, 0x3544, 0x08ed, 0x2407, 0x1daa, 0xcd68,
+0xb792, 0xbf01, 0xd1c2, 0xd611, 0xf34b, 0x259b, 0x3b2f, 0x2f39, 0x1053, 0x2d78,
+0x1a05, 0xd265, 0xba97, 0xc02a, 0xdab7, 0xd903, 0xf86d, 0x2ff9, 0x403d, 0x28d3,
+0x14a1, 0x34af, 0x0a0e, 0xcda4, 0xc3ac, 0xc13a, 0xdd8d, 0xe2ea, 0xfd27, 0x2eb2,
+0x45ac, 0x26be, 0x14ec, 0x3dd9, 0x06bc, 0xc81d, 0xc6e2, 0xc719, 0xd8c3, 0xe5c7,
+0x0b66, 0x32c9, 0x4b92, 0x2cb4, 0x19ce, 0x3c10, 0x0272, 0xc5eb, 0xbb96, 0xccd7,
+0xe07a, 0xdfbb, 0x160a, 0x4341, 0x4749, 0x2676, 0x21c7, 0x2fd3, 0xf0d0, 0xc6ee,
+0xb624, 0xcad8, 0xe85f, 0xe7c5, 0x1d81, 0x4b84, 0x45af, 0x1eba, 0x2c0d, 0x30c1,
+0xe4ba, 0xca8d, 0xbdbe, 0xcc89, 0xed50, 0xf3dd, 0x2203, 0x5159, 0x4cd1, 0x1b22,
+0x2e08, 0x3255, 0xe28f, 0xc7e6, 0xc2e3, 0xd510, 0xe8de, 0xfa14, 0x2846, 0x4a33,
+0x4c04, 0x1fed, 0x2f64, 0x2cdb, 0xe195, 0xc51d, 0xc13f, 0xdb62, 0xe696, 0xfa3e,
+0x31bd, 0x4d3c, 0x4355, 0x2150, 0x33b3, 0x204a, 0xe0d7, 0xc877, 0xbdd9, 0xdeff,
+0xeb46, 0x0024, 0x3a0c, 0x5485, 0x3c57, 0x1fe4, 0x3a57, 0x110e, 0xd1f0, 0xc4f8,
+0xbfae, 0xe303, 0xf0e8, 0x0757, 0x3795, 0x525c, 0x364d, 0x163e, 0x3411, 0x078d,
+0xc95e, 0xbed3, 0xc146, 0xdd79, 0xe8cb, 0x0fb0, 0x3b8b, 0x4b40, 0x2f52, 0x1708,
+0x2c9b, 0xf6f7, 0xc33e, 0xb8f8, 0xc0a0, 0xe27b, 0xe6d8, 0x0c22, 0x3d9b, 0x4878,
+0x24a6, 0x186c, 0x2467, 0xe600, 0xc014, 0xb323, 0xbc0b, 0xe0eb, 0xe8da, 0x0f11,
+0x3cef, 0x40c5, 0x12df, 0x1430, 0x227a, 0xdb2e, 0xb8c4, 0xb5de, 0xc528, 0xdd7a,
+0xe824, 0x13d4, 0x3e15, 0x4384, 0x124e, 0x1469, 0x1e10, 0xd6df, 0xb7fb, 0xb8e3,
+0xcdaf, 0xdd42, 0xecbb, 0x1e7d, 0x3c6f, 0x378a, 0x1149, 0x1e8a, 0x1b08, 0xd447,
+0xb3c7, 0xb0b3, 0xd341, 0xe16d, 0xebda, 0x22db, 0x4450, 0x345d, 0x0fcd, 0x27aa,
+0x137c, 0xcfd2, 0xc241, 0xbb1b, 0xd538, 0xe771, 0xf62c, 0x297f, 0x4c37, 0x3271,
+0x103e, 0x360c, 0x143c, 0xc8cb, 0xc2e2, 0xc46f, 0xd919, 0xe83f, 0x0316, 0x2e44,
+0x49f7, 0x320d, 0x1102, 0x34eb, 0x0f20, 0xc998, 0xc1e5, 0xc68b, 0xd8b7, 0xe3aa,
+0x0e51, 0x3839, 0x473d, 0x306d, 0x1936, 0x313b, 0x023a, 0xca3b, 0xb9cb, 0xc4e8,
+0xe9a8, 0xe8e8, 0x0e4f, 0x4390, 0x4a72, 0x23d2, 0x1d52, 0x31ca, 0xf0b0, 0xc813,
+0xbd25, 0xc202, 0xe6ea, 0xee18, 0x12c8, 0x4476, 0x4b2e, 0x198b, 0x19a1, 0x2fcc,
+0xe518, 0xbf8d, 0xbe9e, 0xcb6d, 0xe622, 0xee0e, 0x167e, 0x3fbf, 0x46fb, 0x1a7d,
+0x1f4e, 0x2d08, 0xe17f, 0xbedd, 0xbaa3, 0xcc16, 0xe196, 0xeedc, 0x21cc, 0x44ed,
+0x4348, 0x1a18, 0x2363, 0x242e, 0xdc65, 0xbd61, 0xb300, 0xcfa0, 0xe712, 0xe5c7,
+0x169f, 0x5f17, 0x7299, 0x2bb1, 0x0b65, 0xe2fd, 0x9b1c, 0xa3b5, 0xaeb5, 0xe147,
+0x3787, 0x593a, 0x5290, 0x4a3c, 0x2e69, 0xcbb4, 0xa91f, 0xc5a3, 0xbd85, 0xd07b,
+0x1055, 0x3d01, 0x2411, 0x222c, 0x2b1a, 0x0b05, 0xffdc, 0xe3f2, 0xcf8c, 0xf390,
+0x15ad, 0xd9f5, 0xc944, 0x2d37, 0x09de, 0xc93f, 0x0d00, 0x229d, 0x0eb5, 0x31fb,
+0x193d, 0xdefd, 0x0b6e, 0xe2b8, 0x80c1, 0xd2cb, 0x1719, 0xe9c2, 0x3008, 0x6a8a,
+0xfee8, 0x01bc, 0x299a, 0xb39d, 0xa48f, 0x0798, 0xe5e9, 0xddfa, 0x41e1, 0x17dc,
+0xf43b, 0x3829, 0x05c3, 0xbd89, 0xf1bc, 0x0dab, 0xcd37, 0xfad8, 0x2e14, 0xf080,
+0x107b, 0x24ea, 0xd852, 0xd4a0, 0x17fd, 0xfd24, 0xd818, 0x2b57, 0x168d, 0xdb81,
+0x1759, 0x0cdb, 0xc867, 0xf2e6, 0x257d, 0xe349, 0xfff1, 0x39a1, 0xe33d, 0xee45,
+0x2b9d, 0xe388, 0xc702, 0x21a8, 0x103e, 0xda0f, 0x2f4b, 0x1b70, 0xd6c1, 0x1b55,
+0x104d, 0xb577, 0xe811, 0x28c6, 0xe0aa, 0xfee3, 0x4403, 0xf56f, 0xf711, 0x2c32,
+0xe60a, 0xbd96, 0x0d61, 0x048f, 0xce3d, 0x272f, 0x325d, 0xf1c7, 0x2094, 0x1ff0,
+0xc860, 0xe30a, 0x2515, 0xdac5, 0xe92d, 0x441f, 0x0548, 0xfb76, 0x3d79, 0xfd5c,
+0xbecb, 0x1149, 0x13f9, 0xd214, 0x2a6f, 0x3388, 0xe6f8, 0x2190, 0x2948, 0xc4a5,
+0xdd42, 0x2f43, 0xe6af, 0xf03f, 0x54db, 0x13d9, 0xfca6, 0x3215, 0xf506, 0xc57a,
+0x12c0, 0x0f9f, 0xd3cc, 0x322e, 0x4301, 0xf273, 0x23ab, 0x356e, 0xd3b2, 0xd051,
+0x21b7, 0xf281, 0xecda, 0x4489, 0x12f0, 0x0242, 0x4407, 0x06d1, 0xbafe, 0x0b6c,
+0x1d26, 0xc6ee, 0x19bc, 0x44e9, 0xe86d, 0x13b7, 0x39ca, 0xd446, 0xd119, 0x2941,
+0xee93, 0xe7c5, 0x4bc2, 0x0643, 0xe908, 0x4535, 0x0444, 0xaaed, 0x0a10, 0x21a1,
+0xc807, 0x1964, 0x41d6, 0xe77b, 0x16b3, 0x34e4, 0xc869, 0xc8dc, 0x2196, 0xe4ba,
+0xdb97, 0x4973, 0x106a, 0xe7d9, 0x3cc5, 0x0a39, 0xa769, 0xf083, 0x1925, 0xc4c2,
+0x075d, 0x3c4a, 0xe3ca, 0x0d28, 0x38d3, 0xc8d1, 0xbd34, 0x29a4, 0xea91, 0xbfc6,
+0x3ad0, 0x0fbe, 0xd348, 0x3125, 0x0f03, 0xa6a3, 0xf4e3, 0x222b, 0xbe73, 0x00e2,
+0x3cfa, 0xd671, 0xff6d, 0x3893, 0xc753, 0xb6c9, 0x236e, 0xec95, 0xc571, 0x3925,
+0x1324, 0xdbd6, 0x2c09, 0x07a8, 0xaa2d, 0xf242, 0x1fa6, 0xc716, 0x042c, 0x441c,
+0xe490, 0xffd3, 0x3dc4, 0xdc2a, 0xb9f6, 0x1f30, 0xfbb0, 0xca2e, 0x371c, 0x2003,
+0xddf1, 0x31fa, 0x2307, 0xb4d8, 0xf004, 0x2cbc, 0xc878, 0xf7c6, 0x4caf, 0xed6b,
+0xfe78, 0x48e2, 0xe5f3, 0xb861, 0x209c, 0xffdd, 0xc990, 0x3a8f, 0x2235, 0xd8df,
+0x3307, 0x26fb, 0xb85c, 0xf04e, 0x2cb8, 0xd049, 0xfa7c, 0x49ae, 0xedac, 0xfa25,
+0x3f7d, 0xe587, 0xbae6, 0x1de2, 0x03f4, 0xc964, 0x35b0, 0x2bf3, 0xdcc1, 0x287a,
+0x2509, 0xb53e, 0xdd34, 0x21f5, 0xd014, 0xf4e6, 0x5327, 0xfbed, 0xf63d, 0x422a,
+0xebde, 0xadff, 0x109b, 0x03c0, 0xc66e, 0x34a1, 0x371f, 0xe1e5, 0x266b, 0x298a,
+0xb6ec, 0xdb6e, 0x2815, 0xd421, 0xf019, 0x5352, 0xff26, 0xf3f2, 0x43a2, 0xf389,
+0xb155, 0x11bc, 0x093e, 0xc3cf, 0x2cd7, 0x37d3, 0xe04d, 0x22e3, 0x2ecd, 0xb8a8,
+0xd4f6, 0x2b5c, 0xd879, 0xe9c1, 0x5345, 0x0074, 0xeb1b, 0x3d0d, 0xf0a8, 0xa78e,
+0x0f83, 0x1369, 0xc523, 0x2e80, 0x3d4c, 0xdb13, 0x1e41, 0x2ecc, 0xb34d, 0xd013,
+0x2f65, 0xd81e, 0xe458, 0x57d2, 0x0406, 0xebe0, 0x457a, 0xf7e1, 0xa66e, 0x0b6f,
+0x0e7a, 0xb8d1, 0x251b, 0x3fe1, 0xdd7d, 0x20b9, 0x3687, 0xbb17, 0xd0b9, 0x2e06,
+0xd6de, 0xd8c9, 0x50ce, 0x0665, 0xe605, 0x3fce, 0xfc63, 0xac6f, 0x0f4a, 0x249e,
+0xcfcc, 0xfc68, 0x0488, 0xeb03, 0x4794, 0x43ec, 0xda83, 0xf3bd, 0x2644, 0xc019,
+0xac19, 0xfdb7, 0xdae0, 0xebc8, 0x4a7e, 0x4842, 0x204b, 0x2f4a, 0x0bd7, 0xb466,
+0xc7b6, 0xc25a, 0x9ff5, 0xfeaa, 0x3147, 0xf5d6, 0x1606, 0x7098, 0x2536, 0xcb1f,
+0xef7c, 0xd8bb, 0xa610, 0xc6a6, 0xf94d, 0x01cf, 0x2326, 0x2e92, 0xfa36, 0x1b23,
+0x1fee, 0xc9e0, 0xbf64, 0xf0f7, 0xebf9, 0xcd60, 0xfed7, 0x1727, 0x0a34, 0x1723,
+0x0a36, 0x0e7b, 0x18aa, 0xfc64, 0xb854, 0xc985, 0xfdd8, 0xc68b, 0xea58, 0x3e80,
+0x2a34, 0x0ba2, 0x2d28, 0x367f, 0xec1b, 0xe1de, 0xce20, 0xb654, 0xf084, 0xed7d,
+0xf556, 0x44c2, 0x5de8, 0x118c, 0x103a, 0x43c2, 0xea97, 0xbbb3, 0xd781, 0xd5a7,
+0xe105, 0xfd04, 0x1bc6, 0x2dbd, 0x4a4e, 0x2262, 0x090c, 0x37f3, 0x0697, 0xc337,
+0xc445, 0xe70d, 0xe217, 0xdb53, 0x1953, 0x38c9, 0x40c1, 0x2ede, 0x22c3, 0x260c,
+0xfc8c, 0xcfd4, 0xb0e1, 0xd39e, 0xe34c, 0xdae1, 0x253f, 0x508f, 0x342f, 0x1aa5,
+0x3628, 0x1e6e, 0xd753, 0xc9cd, 0xb7c4, 0xd23c, 0xef41, 0xeb30, 0x1cc2, 0x523c,
+0x3c38, 0x02b5, 0x29f9, 0x28a2, 0xd759, 0xd1f8, 0xd1bf, 0xd245, 0xe508, 0xfe91,
+0x19fc, 0x3b30, 0x3d9b, 0x0a4c, 0x2fd0, 0x37f4, 0xdc8c, 0xbe49, 0xd7b4, 0xe591,
+0xd3af, 0xfd0f, 0x3070, 0x36f1, 0x325e, 0x18a4, 0x3386, 0x27ca, 0xdc73, 0xb513,
+0xcdc8, 0xf809, 0xd5d9, 0xf013, 0x4334, 0x4964, 0x2395, 0x1b7b, 0x3c5a, 0x0fe4,
+0xd7a3, 0xc6c6, 0xc6b6, 0xf0cf, 0xeb7d, 0xf80b, 0x3914, 0x525a, 0x2445, 0x1417,
+0x4928, 0x0ff5, 0xc35d, 0xc78a, 0xd244, 0xdff9, 0xe35e, 0x07df, 0x35ae, 0x4ba7,
+0x2974, 0x1106, 0x3cf3, 0x0fec, 0xc4b4, 0xb9c7, 0xd562, 0xe489, 0xd970, 0x0e52,
+0x3f61, 0x43d6, 0x27aa, 0x2131, 0x364b, 0xfd16, 0xc955, 0xb961, 0xcde0, 0xe8ce,
+0xe024, 0x0e4f, 0x4544, 0x4582, 0x18cd, 0x22be, 0x3a77, 0xf22e, 0xc4f3, 0xbabf,
+0xcce0, 0xe32e, 0xe352, 0x12f7, 0x43a1, 0x4666, 0x1401, 0x1c0c, 0x30e6, 0xe619,
+0xbb56, 0xbb95, 0xd491, 0xe00b, 0xe61a, 0x17a4, 0x3b47, 0x3b00, 0x10e7, 0x1c7e,
+0x2707, 0xe361, 0xb750, 0xb2d2, 0xd7be, 0xdb41, 0xe145, 0x2096, 0x40ed, 0x319a,
+0x118b, 0x2924, 0x1a2e, 0xd6af, 0xba5f, 0xad6c, 0xd2c9, 0xe064, 0xe67c, 0x25df,
+0x4ea4, 0x3357, 0x0fc7, 0x312c, 0x1020, 0xc418, 0xb5ff, 0xb68a, 0xd4f7, 0xe2ef,
+0xfb14, 0x2e11, 0x4c3e, 0x3280, 0x08fc, 0x2966, 0x0e36, 0xc69d, 0xb3ba, 0xbf26,
+0xde72, 0xe461, 0x0918, 0x3887, 0x4967, 0x313c, 0x1236, 0x2763, 0x0526, 0xcb44,
+0xb35a, 0xc8b5, 0xed8f, 0xdf83, 0x05bf, 0x41b2, 0x4548, 0x20ab, 0x1a30, 0x2bd9,
+0xf884, 0xce0f, 0xb377, 0xc10c, 0xede8, 0xe5b7, 0x0520, 0x42f2, 0x4b64, 0x19c0,
+0x1a50, 0x2fd7, 0xee52, 0xc8d4, 0xbcea, 0xc661, 0xe5d5, 0xe9b8, 0x0e4b, 0x42da,
+0x4c23, 0x187a, 0x1ba9, 0x2fdd, 0xe89e, 0xbcad, 0xb84a, 0xd201, 0xe619, 0xeba8,
+0x1a61, 0x40ef, 0x41b7, 0x151a, 0x1cbf, 0x2b05, 0xea44, 0xc1f8, 0xbc6c, 0xdb3c,
+0xe422, 0xe69f, 0x208e, 0x4881, 0x3cc7, 0x1503, 0x2aa9, 0x27ed, 0xe431, 0xc5cc,
+0xbdfa, 0xdd9a, 0xe8e9, 0xf074, 0x2496, 0x4a59, 0x3e74, 0x1374, 0x2e8f, 0x252c,
+0xd82c, 0xba66, 0xbeda, 0xe1cc, 0xe3a5, 0xf456, 0x3043, 0x4df4, 0x382a, 0x0d8b,
+0x25d9, 0x1516, 0xd249, 0xb805, 0xbbdd, 0xe317, 0xe31f, 0xf717, 0x307f, 0x4438,
+0x2ce7, 0x1089, 0x29b8, 0x060d, 0xc757, 0xb6c7, 0xb848, 0xdd4c, 0xde36, 0xf60e,
+0x341a, 0x45f6, 0x1fc8, 0x0b03, 0x2ed5, 0xff75, 0xc14a, 0xb3bb, 0xb4dc, 0xd6d2,
+0xdc36, 0xfbb0, 0x356c, 0x49c3, 0x2300, 0x0fe9, 0x2f18, 0xf499, 0xb802, 0xb1b2,
+0xc06f, 0xdc85, 0xde22, 0x0805, 0x3cc1, 0x488e, 0x1f2e, 0x11d7, 0x2c64, 0xf3b1,
+0xbd78, 0xadbb, 0xc5eb, 0xe451, 0xde57, 0x0e82, 0x4140, 0x4137, 0x1916, 0x1b84,
+0x28d4, 0xe639, 0xc1a0, 0xb2e7, 0xc697, 0xe4be, 0xdf87, 0x10dc, 0x49b4, 0x457d,
+0x1479, 0x2405, 0x2b0f, 0xdcbe, 0xbefc, 0xb3a9, 0xc6dd, 0xe60b, 0xed45, 0x1b13,
+0x497b, 0x4531, 0x1324, 0x24b0, 0x243c, 0xd210, 0xb83d, 0xb984, 0xd0bc, 0xe2a6,
+0xf3b7, 0x2766, 0x4a82, 0x4204, 0x133f, 0x22b4, 0x1b4e, 0xd4a3, 0xba37, 0xbaaf,
+0xdf26, 0xea12, 0xf84b, 0x3177, 0x4e17, 0x39af, 0x193b, 0x308a, 0x13c1, 0xd6b0,
+0xc5d3, 0xbffc, 0xe599, 0xeec6, 0xff4d, 0x38ac, 0x5167, 0x346a, 0x1b9d, 0x376a,
+0x0ce4, 0xd4a8, 0xc78f, 0xc195, 0xe4f4, 0xf0f5, 0x07bb, 0x3cdc, 0x5981, 0x3510,
+0x1b26, 0x3a31, 0x03fb, 0xca72, 0xc53e, 0xcd25, 0xecab, 0xf768, 0x193e, 0x437d,
+0x53af, 0x2c6f, 0x15b8, 0x31bf, 0xff10, 0xcc99, 0xc147, 0xd348, 0xee05, 0xee95,
+0x1782, 0x2f2b, 0x4b20, 0x5a80, 0x2b14, 0xfd25, 0xdd6a, 0xccb5, 0x9c77, 0xbb91,
+0x0abb, 0xfa6d, 0x308d, 0x8b3b, 0x6386, 0x0f1b, 0x0366, 0xd9a6, 0x969a, 0xbb36,
+0xbe42, 0xd650, 0x4a43, 0x5e11, 0x39f6, 0x4e5f, 0x3f3f, 0xd1f7, 0xbac7, 0xda01,
+0xb75e, 0xdb85, 0x0689, 0x0f50, 0x402d, 0x3a48, 0xf492, 0xf3ba, 0x232e, 0xe3c2,
+0xc221, 0x1c7e, 0x103b, 0xda32, 0x0d99, 0x12a9, 0xc374, 0xe4a9, 0x2fee, 0xef59,
+0xf571, 0x4551, 0x09ce, 0xee4d, 0x190c, 0xdd87, 0xa5eb, 0xf5c9, 0x0d02, 0xd8dd,
+0x32e0, 0x4556, 0xf27b, 0x272d, 0x1a0f, 0xac2d, 0xc929, 0x0d48, 0xd065, 0xe73f,
+0x4fb6, 0x0a4b, 0xfcc1, 0x4512, 0xed54, 0xb2b9, 0x0350, 0xfc0b, 0xbed7, 0x1926,
+0x2f0c, 0xe1a6, 0x2871, 0x338a, 0xc365, 0xd8fc, 0x2261, 0xe12a, 0xda5e, 0x3836,
+0x02a6, 0xeb2d, 0x3c29, 0xfc95, 0xb47f, 0x126c, 0x2240, 0xc4df, 0x1301, 0x42d9,
+0xddbd, 0x05c8, 0x2ff4, 0xc609, 0xc9ef, 0x2ebd, 0xf193, 0xda07, 0x46da, 0x0cd3,
+0xe052, 0x39eb, 0x0085, 0xa3df, 0xfdbd, 0x1d67, 0xc36d, 0x1321, 0x461a, 0xe3db,
+0x121d, 0x3bc5, 0xce7e, 0xc611, 0x1e92, 0xe869, 0xd146, 0x3d6e, 0x12f6, 0xe781,
+0x3a2e, 0x100e, 0xb4c0, 0xf87f, 0x2451, 0xce12, 0xfd54, 0x3b61, 0xe9af, 0x0485,
+0x363a, 0xd7a5, 0xc531, 0x26b6, 0xfb26, 0xd3af, 0x4024, 0x13d3, 0xcf38, 0x31ca,
+0x1fe6, 0xacba, 0xe830, 0x28d0, 0xd67b, 0x03f4, 0x4390, 0xeff5, 0x0d9b, 0x3cdc,
+0xd2ef, 0xbce5, 0x1df5, 0xf194, 0xcc0a, 0x3c7b, 0x22be, 0xe9d0, 0x37e4, 0x1f21,
+0xbf6a, 0xed14, 0x13f9, 0xccad, 0xfeed, 0x36f2, 0xe931, 0x0bd9, 0x4870, 0xe9a7,
+0xc649, 0x2406, 0x009e, 0xc7de, 0x28c6, 0x1d14, 0xe19d, 0x2d2e, 0x24eb, 0xc1ae,
+0xf293, 0x2948, 0xd034, 0xfc36, 0x4c80, 0xed33, 0xf76a, 0x4574, 0xe546, 0xb51f,
+0x205e, 0x01e4, 0xc8f6, 0x3c9b, 0x2dfd, 0xe000, 0x3231, 0x23e4, 0xac3f, 0xe1e2,
+0x2470, 0xc806, 0xf279, 0x51f9, 0xfc93, 0xfd3d, 0x471f, 0xf14b, 0xb564, 0x0d8d,
+0xf7a1, 0xbaf7, 0x2ae2, 0x3330, 0xe3cf, 0x2876, 0x2f9d, 0xbe99, 0xd8ed, 0x2066,
+0xcc5b, 0xe037, 0x444b, 0xfc55, 0xf1ae, 0x3dee, 0xedb1, 0xa9f3, 0x0f58, 0x052a,
+0xb88b, 0x286b, 0x32e9, 0xd191, 0x1a2d, 0x2b19, 0xb1ba, 0xd055, 0x2574, 0xcd9d,
+0xe17a, 0x4f5d, 0xf991, 0xe9c8, 0x4424, 0xee22, 0xa03f, 0x0b3e, 0x0833, 0xb2f6,
+0x25d7, 0x3fb5, 0xd971, 0x1eca, 0x3552, 0xb55f, 0xc5ed, 0x2341, 0xd2b5, 0xd7c1,
+0x523e, 0x069d, 0xe63d, 0x453d, 0xfaec, 0xa137, 0x0414, 0x12c4, 0xb981, 0x1bb8,
+0x4054, 0xdb21, 0x1feb, 0x3f6a, 0xbc6b, 0xcb19, 0x2eaa, 0xdddc, 0xd782, 0x53f0,
+0x0db9, 0xe5f5, 0x4ae5, 0x09f1, 0xaaaa, 0x08ea, 0x1b96, 0xc280, 0x22c4, 0x4ce0,
+0xe24f, 0x1cfe, 0x4491, 0xc23a, 0xc424, 0x2faf, 0xe9a8, 0xd606, 0x50b8, 0x16c3,
+0xe5a3, 0x41e4, 0x0b1a, 0xa940, 0xfef6, 0x1bdf, 0xbfb1, 0x1607, 0x4e4e, 0xe252,
+0x116e, 0x44af, 0xc71f, 0xbeed, 0x2c56, 0xed57, 0xd13f, 0x46da, 0x1220, 0xdf44,
+0x3fcf, 0x0e72, 0xa687, 0xfe99, 0x22c2, 0xc0f4, 0x10f5, 0x4ab0, 0xdcc9, 0x0862,
+0x42d3, 0xcce8, 0xbf2b, 0x2c4a, 0xf130, 0xcf23, 0x47a0, 0x1557, 0xd894, 0x3b17,
+0x15eb, 0xac91, 0xfc3e, 0x2839, 0xc889, 0x0a6b, 0x4a0a, 0xe524, 0x0571, 0x40a3,
+0xd642, 0xc2d3, 0x2b1c, 0xfa19, 0xd4f9, 0x465b, 0x1ef6, 0xe106, 0x371e, 0x15ec,
+0xb185, 0xfc2f, 0x2898, 0xcc97, 0x0b55, 0x4c1f, 0xebc4, 0x08bc, 0x3f08, 0xd705,
+0xc599, 0x2bb5, 0x081d, 0xcd96, 0x0d3b, 0x0ffa, 0x0823, 0x3f09, 0x1a45, 0xdad9,
+0x0662, 0x07b7, 0xbb6a, 0xde9a, 0x0740, 0xda46, 0x0e1e, 0x6549, 0x38b1, 0x04bd,
+0x2510, 0xf943, 0xad98, 0xc9a0, 0xca5c, 0xcaba, 0x22c9, 0x2b10, 0x01f0, 0x511a,
+0x637c, 0xe409, 0xcfea, 0xff1b, 0xbae4, 0x9b34, 0xfafc, 0x154f, 0xfdb5, 0x2da1,
+0x2e82, 0x1b20, 0x2472, 0xf46c, 0xbc6b, 0xd8c3, 0xf895, 0xcd65, 0xe4ee, 0x20c1,
+0x1821, 0x08d6, 0x178c, 0x2a03, 0x07e8, 0xfe05, 0xe136, 0xbccf, 0xeb99, 0xdae7,
+0xd3aa, 0x1d70, 0x3e94, 0x094b, 0x0900, 0x5434, 0x0901, 0xc4b1, 0xdc6e, 0xc832,
+0xca7f, 0xe370, 0xfc55, 0x19fb, 0x5037, 0x3174, 0xfe1c, 0x3bdc, 0x1079, 0xb5a5,
+0xbd97, 0xde91, 0xd7dc, 0xd8e9, 0x1ed3, 0x2fd7, 0x3373, 0x2e1c, 0x130d, 0x2861,
+0x0ada, 0xcec0, 0xaf81, 0xd3f1, 0xe991, 0xcbf7, 0x0636, 0x408c, 0x3d14, 0x22da,
+0x2e7f, 0x334f, 0xf091, 0xd23f, 0xb6f6, 0xbe39, 0xe0ea, 0xdac6, 0x0ab7, 0x52de,
+0x5241, 0x1a28, 0x36a7, 0x3ee4, 0xdc10, 0xbfb1, 0xc06c, 0xc69f, 0xdf82, 0xf62e,
+0x1f54, 0x4e25, 0x578d, 0x17a6, 0x26c4, 0x37fc, 0xe05d, 0xc099, 0xcd8c, 0xd886,
+0xd8c3, 0xfcba, 0x2d83, 0x3fa6, 0x48bb, 0x1e77, 0x2a31, 0x31eb, 0xe98e, 0xbcf5,
+0xc196, 0xe4e4, 0xdb34, 0xf4b5, 0x398d, 0x47e8, 0x3526, 0x2168, 0x3d9a, 0x20eb,
+0xddcb, 0xbe09, 0xb85c, 0xe775, 0xe3f6, 0xedd1, 0x3731, 0x576a, 0x2d99, 0x1229,
+0x4011, 0x0fb4, 0xc58a, 0xc2e0, 0xc163, 0xd8b3, 0xe485, 0xfbc4, 0x28a2, 0x4aba,
+0x2b57, 0x06a4, 0x385f, 0x150e, 0xc2cd, 0xb65c, 0xcd33, 0xde60, 0xd4fe, 0x0096,
+0x3507, 0x45bc, 0x2781, 0x142c, 0x33ba, 0x049c, 0xc775, 0xb39c, 0xc6ec, 0xe39b,
+0xd75b, 0x0203, 0x3c34, 0x4589, 0x1c2f, 0x171d, 0x336b, 0xeff6, 0xbf31, 0xb8c6,
+0xc21c, 0xde19, 0xe412, 0x0b3a, 0x399a, 0x4631, 0x1799, 0x116c, 0x3085, 0xeefe,
+0xbcec, 0xb922, 0xcdc6, 0xe051, 0xe2d7, 0x10d0, 0x38e1, 0x3dfb, 0x1356, 0x1516,
+0x2876, 0xe6f8, 0xbb6a, 0xb636, 0xd128, 0xdda0, 0xdf16, 0x11df, 0x387e, 0x338c,
+0x0c5c, 0x211c, 0x2422, 0xdca8, 0xbf10, 0xb435, 0xcd32, 0xde18, 0xe25f, 0x144e,
+0x3fe0, 0x3472, 0x0753, 0x2b41, 0x27e8, 0xd555, 0xc117, 0xbddb, 0xcb8c, 0xdaa8,
+0xee16, 0x185e, 0x406a, 0x394d, 0x0e4c, 0x3224, 0x2283, 0xcee6, 0xb78f, 0xbe46,
+0xd600, 0xd73a, 0xf882, 0x2db5, 0x4253, 0x3500, 0x1558, 0x2eee, 0x13ba, 0xd1b3,
+0xb80a, 0xbd32, 0xe28d, 0xdf23, 0xfd44, 0x3980, 0x4ace, 0x2d3f, 0x18f4, 0x34ae,
+0x039f, 0xcc97, 0xbb5e, 0xbe2a, 0xe6fa, 0xe555, 0x023b, 0x3de8, 0x4db4, 0x2192,
+0x1578, 0x3a68, 0xfe29, 0xc84a, 0xbd0b, 0xc1ef, 0xe3a9, 0xe936, 0x0a3b, 0x3ab4,
+0x505a, 0x260d, 0x1903, 0x39c0, 0xf9ac, 0xc53b, 0xbc41, 0xcc45, 0xe5fc, 0xe843,
+0x15b8, 0x436d, 0x4dae, 0x24c5, 0x212b, 0x33f2, 0xf1b2, 0xc3a3, 0xb509, 0xcd57,
+0xe8db, 0xeb7c, 0x1d1e, 0x4aef, 0x4a01, 0x1af5, 0x24c7, 0x2df1, 0xe4a5, 0xc1ae,
+0xb8ad, 0xd2c8, 0xe887, 0xee7b, 0x21d0, 0x4d5f, 0x4806, 0x171f, 0x2854, 0x27e5,
+0xdd26, 0xc075, 0xb9c1, 0xd6dc, 0xe94e, 0xf760, 0x2a99, 0x4d0c, 0x4405, 0x1751,
+0x2a63, 0x1e7d, 0xd670, 0xba60, 0xb674, 0xde00, 0xe8fc, 0xf57e, 0x3121, 0x51b2,
+0x3a66, 0x11d4, 0x29d9, 0x107f, 0xd01f, 0xbbd1, 0xb864, 0xe08f, 0xea2a, 0xfafd,
+0x34de, 0x5038, 0x3061, 0x1294, 0x3316, 0x07d3, 0xc6ab, 0xbd82, 0xc0f2, 0xe421,
+0xebe4, 0x0398, 0x39c6, 0x54a2, 0x2e8b, 0x0e55, 0x3308, 0x0648, 0xc5ad, 0xbd8e,
+0xc53c, 0xdfb2, 0xe8b4, 0x0f4a, 0x3d14, 0x4caa, 0x2bf1, 0x15bf, 0x2e89, 0xf945,
+0xc136, 0xb890, 0xcaeb, 0xe950, 0xe7bf, 0x115d, 0x451b, 0x4aa1, 0x20cd, 0x1684,
+0x2a91, 0xf0a3, 0xc5b7, 0xb815, 0xc9bf, 0xee16, 0xea12, 0x117d, 0x470b, 0x42c7,
+0x113f, 0x1b7c, 0x2b91, 0xe191, 0xc212, 0xbd24, 0xc84b, 0xe419, 0xe8c1, 0x104f,
+0x440c, 0x4662, 0x1122, 0x20a6, 0x2d8e, 0xda8b, 0xbcf3, 0xc089, 0xcbf5, 0xde1c,
+0xf3c0, 0x2135, 0x414e, 0x3fd9, 0x15e1, 0x26f5, 0x292e, 0xdc7e, 0xb937, 0xb924,
+0xd769, 0xe06d, 0xee92, 0x2aa0, 0x48ae, 0x379d, 0x15e3, 0x2afa, 0x167d, 0xd370,
+0xbfeb, 0xb8aa, 0xd8da, 0xe695, 0xf331, 0x2abe, 0x4918, 0x2e1c, 0x0f19, 0x32cf,
+0x0f46, 0xc822, 0xbf6b, 0xbca2, 0xdadc, 0xe628, 0xf835, 0x2cf1, 0x4b03, 0x2c10,
+0x0df9, 0x358e, 0x0a2c, 0xc678, 0xc06b, 0xc05f, 0xd849, 0xe170, 0x02e1, 0x3324,
+0x4bd0, 0x2fc8, 0x13c8, 0x3314, 0x0029, 0xbf2e, 0xb455, 0xc1d3, 0xe027, 0xe480,
+0x0a7f, 0x2c7a, 0x5506, 0x634d, 0x2129, 0xfadf, 0xe1b5, 0xb620, 0x8bd2, 0xbfae,
+0x0a2a, 0x0d5d, 0x5240, 0x8109, 0x4e83, 0x15cf, 0xf367, 0xba2c, 0x9d5f, 0xcd2e,
+0xbdc8, 0xf85d, 0x6f35, 0x4484, 0x29e8, 0x54f0, 0x2a04, 0xcc06, 0xd6d5, 0xe253,
+0xc387, 0x09da, 0xff4a, 0xebc6, 0x4eb3, 0x2edb, 0xcfcc, 0x0b9a, 0x3d1b, 0xdc1f,
+0xf4a1, 0x3fae, 0xeb9b, 0xe915, 0x13be, 0xcb86, 0xb940, 0x1643, 0x18ee, 0xf7d9,
+0x4acf, 0x30df, 0xe6dc, 0x16fe, 0xf4d5, 0x9c55, 0xcc79, 0x10aa, 0xe384, 0x0496,
+0x4c2c, 0x0504, 0xff4c, 0x2d7e, 0xd93d, 0xacb4, 0x00e4, 0xf5af, 0xc18e, 0x2289,
+0x2a64, 0xe0a9, 0x2089, 0x21b8, 0xbaf3, 0xdd89, 0x1ea4, 0xd2e4, 0xe648, 0x37ea,
+0xec35, 0xe993, 0x35d6, 0xed2e, 0xbadd, 0x15b4, 0x0a9d, 0xc841, 0x1a7c, 0x191c,
+0xcaef, 0x0f4e, 0x1fff, 0xbfae, 0xe155, 0x3460, 0xeb55, 0xe0b6, 0x3048, 0xf2f2,
+0xddca, 0x235a, 0xee7f, 0xb7cc, 0x15dd, 0x2097, 0xce52, 0x180f, 0x2dcf, 0xd499,
+0x0857, 0x29b8, 0xc4c2, 0xd000, 0x28ff, 0xe652, 0xde94, 0x43c5, 0x05e6, 0xe425,
+0x356a, 0xfee7, 0xb2c1, 0x0914, 0x1584, 0xbe7b, 0x0e5a, 0x368a, 0xe276, 0x1274,
+0x3391, 0xd008, 0xd357, 0x26c2, 0xec06, 0xdd61, 0x39e2, 0x0868, 0xe6f9, 0x3207,
+0x091c, 0xbc0e, 0x0582, 0x1f6d, 0xd133, 0x1cab, 0x4d57, 0xeb81, 0x02e3, 0x3712,
+0xde41, 0xc71f, 0x212b, 0xfb67, 0xe7a1, 0x504a, 0x23ac, 0xedd8, 0x3eb6, 0x157e,
+0xad36, 0xfc30, 0x2995, 0xcb86, 0x10c5, 0x529b, 0xf7a7, 0x15fe, 0x4546, 0xdc21,
+0xcaba, 0x26eb, 0xe984, 0xd493, 0x4c8e, 0x16e6, 0xe56c, 0x463b, 0x1cf9, 0xb4a9,
+0xfd71, 0x24ab, 0xc8a4, 0x09c8, 0x41c1, 0xe4ff, 0x0a9c, 0x3d28, 0xd226, 0xc16f,
+0x2d0a, 0xf732, 0xcb2f, 0x42b2, 0x1d8f, 0xd5f3, 0x2c26, 0x1649, 0xa7c7, 0xed21,
+0x286f, 0xc688, 0x04e2, 0x51dd, 0xe73b, 0x0013, 0x46f0, 0xd1e8, 0xab29, 0x260f,
+0xf7aa, 0xbdee, 0x402d, 0x25c0, 0xda8b, 0x3989, 0x21df, 0xa95c, 0xf0ed, 0x290b,
+0xbd3d, 0xfb44, 0x4eba, 0xe63a, 0xfecd, 0x4a1d, 0xe177, 0xbc6f, 0x2af3, 0xfdf2,
+0xc4cf, 0x3b3d, 0x1ebb, 0xd4a0, 0x319d, 0x221e, 0xaf94, 0xf1b0, 0x3313, 0xcaf9,
+0xf7f2, 0x4fd1, 0xeb5b, 0xf54e, 0x3faa, 0xd925, 0xb016, 0x2624, 0x012e, 0xc2f3,
+0x3e16, 0x255d, 0xce61, 0x2b67, 0x1e8d, 0xa020, 0xe075, 0x2e2f, 0xc930, 0xf3f3,
+0x4f96, 0xe6da, 0xef8a, 0x43fb, 0xdd85, 0xab7b, 0x2022, 0xfe5f, 0xba39, 0x346d,
+0x27b4, 0xd20a, 0x2bfd, 0x2882, 0xae6f, 0xe3d3, 0x2b38, 0xc9c6, 0xef8e, 0x49d8,
+0xeade, 0xf2c2, 0x46de, 0xe8ca, 0xb2fe, 0x208e, 0x0855, 0xc5c4, 0x338e, 0x29eb,
+0xd645, 0x257e, 0x260c, 0xb7e2, 0xebef, 0x3365, 0xd6d8, 0xf811, 0x518d, 0xf457,
+0xf134, 0x4558, 0xf41a, 0xb66e, 0x1a28, 0x0bd1, 0xcaa3, 0x31f9, 0x3045, 0xe17a,
+0x2ad4, 0x2f2d, 0xbd2b, 0xe13e, 0x2cfe, 0xd3bd, 0xea52, 0x4fb4, 0x020e, 0xf40a,
+0x4278, 0xf8f7, 0xb45e, 0x127c, 0x0af1, 0xc2d1, 0x2b03, 0x36f1, 0xdd10, 0x20d2,
+0x35ec, 0xc074, 0xd7bd, 0x2eef, 0xd9db, 0xe543, 0x52c9, 0x05cf, 0xec53, 0x41bb,
+0xfa65, 0xaf2a, 0x15c6, 0x1274, 0xbe0e, 0x2c97, 0x448e, 0xdd59, 0x1c74, 0x3817,
+0xbb2b, 0xcf5b, 0x2f9a, 0xdc0c, 0xe4f0, 0x586d, 0x096c, 0xe913, 0x42c2, 0xf921,
+0xa4d2, 0x0f94, 0x157d, 0xbee7, 0x2a97, 0x46c2, 0xe0bf, 0x1d38, 0x3786, 0xb9af,
+0xcb2a, 0x2dd9, 0xd952, 0xdcbe, 0x5596, 0x0bab, 0xea4b, 0x4531, 0xffe2, 0xa78b,
+0x0c61, 0x136f, 0xb532, 0x2148, 0x44ae, 0xd96e, 0x195c, 0x392c, 0xb898, 0xc8e9,
+0x3388, 0xef92, 0xcd85, 0x0ef6, 0xf796, 0x13e1, 0x506a, 0x028d, 0xcd42, 0x14f5,
+0xf58e, 0x9d81, 0xe29f, 0xfb3c, 0xcd66, 0x1bbf, 0x6302, 0x30d7, 0x14e4, 0x29bc,
+0xdf14, 0xae5e, 0xce80, 0xabdc, 0xca7e, 0x3503, 0x1ee4, 0xf688, 0x5aac, 0x62a7,
+0xd87d, 0xd4d5, 0xfc76, 0xb6dd, 0xa5b5, 0xf6e7, 0x0b99, 0x0665, 0x381b, 0x1f19,
+0x1206, 0x2ed5, 0xebdd, 0xb56d, 0xde2e, 0xf87e, 0xce4d, 0xed47, 0x2439, 0x121c,
+0x1266, 0x1483, 0x16c8, 0x10fd, 0x0650, 0xd754, 0xbaff, 0xf7e2, 0xda39, 0xcd45,
+0x2986, 0x40dc, 0x0e53, 0x182a, 0x50a1, 0x01de, 0xcb6e, 0xdc62, 0xb781, 0xcfc4,
+0xee54, 0xf542, 0x2240, 0x5981, 0x2c5e, 0xf8e5, 0x3e3b, 0x0d29, 0xaf98, 0xc3b7,
+0xd787, 0xd3b1, 0xe2e2, 0x19c0, 0x28df, 0x3943, 0x2fe3, 0x081e, 0x2922, 0x0f78,
+0xc962, 0xb6cc, 0xd979, 0xe6a6, 0xd121, 0x07cf, 0x3500, 0x3ad0, 0x2c2a, 0x2624,
+0x2c95, 0xfb6f, 0xd7be, 0xb61f, 0xc05e, 0xe300, 0xd931, 0x0c0c, 0x4eae, 0x4878,
+0x15a7, 0x2a43, 0x31b1, 0xdd24, 0xc4ea, 0xbda7, 0xc680, 0xe6be, 0xf0c6, 0x13f8,
+0x45f0, 0x4cdc, 0x09ee, 0x185b, 0x3039, 0xdd40, 0xc562, 0xd19d, 0xd51e, 0xdcc7,
+0xfbf1, 0x1f9a, 0x3311, 0x4108, 0x10a0, 0x1d6d, 0x368a, 0xebc0, 0xbacd, 0xc61c,
+0xe6be, 0xd8c6, 0xecc7, 0x2b4f, 0x3ad6, 0x3234, 0x16aa, 0x312b, 0x2c20, 0xe5cf,
+0xbf0c, 0xc2d3, 0xef02, 0xde96, 0xe42b, 0x3264, 0x4f6e, 0x2b87, 0x1502, 0x434e,
+0x2169, 0xd824, 0xcd4a, 0xcbe5, 0xe67d, 0xe8bc, 0xf5fe, 0x2bd2, 0x4fbf, 0x2b2a,
+0x05aa, 0x4482, 0x2857, 0xcb20, 0xc28b, 0xdaa2, 0xe5a8, 0xd616, 0xfaa0, 0x2fc3,
+0x4640, 0x2ce5, 0x0f5a, 0x3ab7, 0x1944, 0xcda3, 0xbbcb, 0xcda1, 0xe22a, 0xd491,
+0x0143, 0x3873, 0x3fbe, 0x22bb, 0x1736, 0x3ae6, 0x0407, 0xc3a8, 0xb9c4, 0xc437,
+0xdc67, 0xd7ba, 0x00f1, 0x3801, 0x41a0, 0x1951, 0x11c6, 0x35de, 0xf7fb, 0xbeac,
+0xb811, 0xc658, 0xdc21, 0xd8e4, 0x060e, 0x39e5, 0x4146, 0x14d2, 0x16dd, 0x36f4,
+0xed6d, 0xba86, 0xb9b4, 0xcb20, 0xdd7e, 0xdfe1, 0x0f15, 0x3b82, 0x3e9b, 0x1158,
+0x1b8a, 0x323f, 0xe8e2, 0xbdfb, 0xb835, 0xd09f, 0xddf2, 0xdd36, 0x148a, 0x40d3,
+0x3e5f, 0x13a7, 0x2794, 0x2f4a, 0xde17, 0xbd25, 0xb6e3, 0xcb99, 0xdc56, 0xe6b9,
+0x1ce9, 0x4836, 0x40a4, 0x1189, 0x2c70, 0x2966, 0xd436, 0xb74c, 0xb722, 0xd074,
+0xdc0f, 0xf37b, 0x2f67, 0x4c6c, 0x3aa1, 0x13df, 0x2e82, 0x1863, 0xcd4e, 0xbc3f,
+0xbce0, 0xd9c4, 0xe1be, 0xfb8d, 0x359d, 0x4c01, 0x3625, 0x17b8, 0x32ce, 0x1032,
+0xcc89, 0xbc30, 0xbdba, 0xe2ec, 0xe81c, 0x005e, 0x3db9, 0x512c, 0x2c97, 0x17fc,
+0x380c, 0x06f5, 0xcdb1, 0xc0d4, 0xbe6b, 0xe241, 0xead9, 0x05a4, 0x3a27, 0x518a,
+0x2c10, 0x187d, 0x3809, 0xfd0c, 0xc4ac, 0xbff0, 0xc939, 0xe3de, 0xe907, 0x0ded,
+0x3e36, 0x4e75, 0x2456, 0x1759, 0x31b3, 0xf547, 0xc449, 0xba76, 0xcb7c, 0xe629,
+0xeb17, 0x17cc, 0x43ae, 0x4970, 0x1dfb, 0x1c5c, 0x2c1b, 0xe97c, 0xc40d, 0xbedd,
+0xd492, 0xe90c, 0xea14, 0x18aa, 0x44ec, 0x43bf, 0x15aa, 0x2421, 0x2f68, 0xe453,
+0xc2c4, 0xbef4, 0xd4c2, 0xe5c8, 0xf22d, 0x2430, 0x475e, 0x4461, 0x187f, 0x2916,
+0x2d8a, 0xe382, 0xc1ee, 0xc2fa, 0xe144, 0xe6ea, 0xf400, 0x30a7, 0x50eb, 0x4265,
+0x1b39, 0x2f67, 0x2260, 0xde78, 0xc61b, 0xc3e2, 0xe4c5, 0xea6e, 0xf9b7, 0x32e4,
+0x4b24, 0x330a, 0x1529, 0x377b, 0x1ad4, 0xd298, 0xc471, 0xc39e, 0xdfcf, 0xe493,
+0xf83e, 0x339f, 0x5169, 0x3178, 0x0fe4, 0x3634, 0x10e7, 0xcad8, 0xc188, 0xc0cb,
+0xd97a, 0xe36b, 0x01cf, 0x3255, 0x49ca, 0x2dc9, 0x1019, 0x33a3, 0x059a, 0xbd42,
+0xb33f, 0xbe30, 0xd9b3, 0xdc31, 0x0338, 0x37ee, 0x44ae, 0x224f, 0x0cf2, 0x27ab,
+0xf5dc, 0xbd67, 0xaf02, 0xbe02, 0xde1c, 0xd738, 0x02b2, 0x3eba, 0x4074, 0x174f,
+0x1829, 0x2c82, 0xe72a, 0xbf61, 0xb6b3, 0xbe85, 0xe2b1, 0xe25f, 0x05c1, 0x4181,
+0x4a2b, 0x1512, 0x1a96, 0x326a, 0xe3c7, 0xbc8c, 0xb7af, 0xbd6f, 0xd849, 0xe90a,
+0x150e, 0x3ed0, 0x469e, 0x176f, 0x1b23, 0x2908, 0xdb26, 0xb2f3, 0xb1f8, 0xca6a,
+0xdb06, 0xe6a5, 0x1fb9, 0x4383, 0x3e5d, 0x1596, 0x1e94, 0x1cb0, 0xd8cf, 0xba95,
+0xaec4, 0xcef4, 0xe43f, 0xeaf7, 0x2443, 0x4766, 0x336a, 0x0e32, 0x27a3, 0x1356,
+0xcd5d, 0xc150, 0xb6ba, 0xd1d8, 0xe528, 0xee84, 0x2426, 0x4adf, 0x3667, 0x10cd,
+0x310e, 0x1388, 0xcbbb, 0xc47e, 0xbd1f, 0xd491, 0xe98e, 0x0244, 0x3289, 0x53b3,
+0x3eb9, 0x1566, 0x3290, 0x11e3, 0xcc07, 0xc004, 0xc4fd, 0xe223, 0xed0a, 0x10ae,
+0x3ff0, 0x5037, 0x36e6, 0x164f, 0x29a1, 0x0284, 0xcdb8, 0xbdcd, 0xc637, 0xed96,
+0xeeb1, 0x0cd0, 0x4050, 0x49bb, 0x239a, 0x1780, 0x2eab, 0xf6b6, 0xce3d, 0xbc38,
+0xc8a0, 0x00e6, 0xefad, 0x0021, 0x33f9, 0x3ae3, 0x3cd3, 0x3fb1, 0x1838, 0xd924,
+0xd541, 0xaecf, 0xa5de, 0xf7be, 0xfc4c, 0x140b, 0x790a, 0x7937, 0x1616, 0xfecc,
+0xe9f4, 0x93de, 0xa330, 0xc0f0, 0xcf7e, 0x2cbc, 0x6286, 0x4ccf, 0x462d, 0x450f,
+0xe74f, 0xa99c, 0xd6b1, 0xc669, 0xba55, 0xfefe, 0x25a9, 0x3149, 0x43ec, 0x1f26,
+0xede2, 0x0adf, 0xff6f, 0xbdad, 0xf121, 0x2c23, 0xf18d, 0xf0cc, 0x2718, 0xe6c1,
+0xbbba, 0x1d92, 0x14c7, 0xde85, 0x3e57, 0x31f4, 0xe295, 0x0f8d, 0xf992, 0xa267,
+0xd117, 0x1bec, 0xe179, 0x07c9, 0x5af9, 0x03a7, 0x02e9, 0x3014, 0xd04d, 0xa794,
+0xfb7e, 0xf282, 0xc715, 0x36b9, 0x3764, 0xea25, 0x307a, 0x1cb0, 0xb2ec, 0xd406,
+0x117c, 0xd2a9, 0xea54, 0x43e9, 0xfc21, 0xfaa3, 0x3fc1, 0xeafd, 0xb060, 0x0b4c,
+0x0ff9, 0xc461, 0x1954, 0x3040, 0xd984, 0x1601, 0x2825, 0xbc3f, 0xdbce, 0x3875,
+0xe248, 0xdd2e, 0x4e02, 0xfd2c, 0xd91c, 0x3749, 0xf658, 0xaa69, 0x13e1, 0x2498,
+0xcc14, 0x21ba, 0x3a26, 0xdb15, 0x1623, 0x2c3d, 0xba40, 0xcd2e, 0x2cf5, 0xe4ae,
+0xe186, 0x52e0, 0x0d31, 0xe5cf, 0x3778, 0xffc1, 0xac3d, 0xfddf, 0x1bac, 0xc8ec,
+0x14d4, 0x4029, 0xe79d, 0x15d6, 0x3792, 0xcc34, 0xca86, 0x3225, 0xf50a, 0xcf32,
+0x3e28, 0x12b9, 0xe181, 0x364b, 0x1002, 0xb4c9, 0x031c, 0x26d6, 0xd0a9, 0x1e6a,
+0x443a, 0xd350, 0x08d3, 0x41ed, 0xc7a2, 0xb99c, 0x2fe3, 0xff21, 0xdc75, 0x4839,
+0x19bb, 0xe84c, 0x32f7, 0x01b7, 0xaddf, 0xfef1, 0x1e1c, 0xcae8, 0x16e4, 0x4bda,
+0xecbb, 0x0caf, 0x3f24, 0xe0dc, 0xc4bc, 0x18da, 0xf67e, 0xdd77, 0x3908, 0x10f4,
+0xed5b, 0x4369, 0x1b8d, 0xb980, 0x0190, 0x297a, 0xc6e3, 0x009e, 0x4462, 0xea33,
+0x0155, 0x3cba, 0xe27f, 0xcbef, 0x2bda, 0xf9a0, 0xd4bc, 0x439f, 0x150b, 0xd559,
+0x344d, 0x1a5b, 0xaf57, 0xf935, 0x2de0, 0xcf9a, 0x0979, 0x49a9, 0xea95, 0x051a,
+0x3cdb, 0xd6df, 0xbc25, 0x2174, 0xf28b, 0xc89b, 0x4222, 0x2645, 0xdfc1, 0x315e,
+0x201e, 0xb2fc, 0xe3d4, 0x181b, 0xc442, 0xfd80, 0x4a43, 0xed31, 0xff1f, 0x4381,
+0xdf06, 0xb4dc, 0x1fd7, 0xf6dc, 0xbffe, 0x3a30, 0x24f3, 0xd6d6, 0x297b, 0x1c5d,
+0xaf37, 0xedb8, 0x286b, 0xc61d, 0xfe95, 0x515b, 0xe559, 0xf3c9, 0x4090, 0xd830,
+0xae85, 0x1db8, 0xf7ec, 0xc607, 0x3f9f, 0x264d, 0xd94a, 0x2a8a, 0x1485, 0xa002,
+0xdeaf, 0x235b, 0xc381, 0xfa49, 0x5796, 0xefa6, 0xf6b9, 0x4056, 0xd904, 0xa813,
+0x17bf, 0xf931, 0xbf26, 0x3e31, 0x2c1d, 0xd50a, 0x2d55, 0x22b9, 0xa846, 0xdf9f,
+0x28c5, 0xc598, 0xef6b, 0x51e5, 0xf17d, 0xfa6e, 0x46ce, 0xddcb, 0xac63, 0x1bcf,
+0xff42, 0xc08e, 0x3b8a, 0x30ab, 0xd540, 0x2b2a, 0x2b14, 0xb01e, 0xe003, 0x2eb4,
+0xd207, 0xf705, 0x5a04, 0xf5f1, 0xf4f2, 0x49c1, 0xe735, 0xac7d, 0x1ff2, 0x0bc1,
+0xc150, 0x3a15, 0x3cd0, 0xdc44, 0x29ee, 0x3079, 0xb504, 0xdcbb, 0x2cd7, 0xcfca,
+0xeed8, 0x5c84, 0xfe19, 0xf346, 0x4ec0, 0xf24e, 0xa9a2, 0x1b01, 0x120e, 0xbf61,
+0x31bd, 0x3ed0, 0xdca8, 0x27d7, 0x3553, 0xb79f, 0xddc0, 0x376a, 0xd743, 0xea45,
+0x592a, 0xf9f4, 0xe94c, 0x4a1d, 0xf552, 0xa9a1, 0x19d2, 0x14c6, 0xbc9b, 0x2b11,
+0x3913, 0xd2dc, 0x1f02, 0x340a, 0xb6b6, 0xd426, 0x33f8, 0xd9a8, 0xdc7f, 0x5123,
+0xfd3a, 0xde46, 0x40ce, 0xfa10, 0xa997, 0x14a8, 0x1bb4, 0xc08d, 0x2376, 0x3d24,
+0xd503, 0x166b, 0x35f1, 0xbc86, 0xd0fa, 0x36e8, 0xf1a1, 0xd628, 0x16ed, 0xf145,
+0x0d1a, 0x55e9, 0x09b3, 0xd0ea, 0x189e, 0xfae5, 0xa1d9, 0xe581, 0xfd1f, 0xd241,
+0x1ffa, 0x5b9f, 0x2d05, 0x1f94, 0x316b, 0xe330, 0xba22, 0xd9ab, 0xb354, 0xcfaa,
+0x3059, 0x1e34, 0xfcfc, 0x565e, 0x623a, 0xe738, 0xe027, 0xfe57, 0xc099, 0xb327,
+0xf470, 0x0c3c, 0x0e38, 0x38fd, 0x1a11, 0x0892, 0x3339, 0xf5c3, 0xbab1, 0xe297,
+0xf843, 0xce95, 0xe620, 0x1c2a, 0x09ee, 0x1283, 0x1b02, 0x1187, 0x188b, 0x0e86,
+0xd87e, 0xb652, 0xeca9, 0xdb2d, 0xc7dd, 0x1fe5, 0x379e, 0x0fd2, 0x1c45, 0x4c2e,
+0x1316, 0xd85d, 0xdfc8, 0xb6df, 0xc87f, 0xeb35, 0xe54a, 0x1628, 0x57e4, 0x3419,
+0xff6c, 0x4156, 0x1c74, 0xb5b4, 0xca06, 0xd49b, 0xc6db, 0xdff3, 0x0f42, 0x1c9b,
+0x38c2, 0x3cc3, 0x059b, 0x2a27, 0x2699, 0xce85, 0xb4f9, 0xd393, 0xdea9, 0xcdb3,
+0x03e7, 0x3281, 0x3d54, 0x3c61, 0x2583, 0x2ce2, 0x0c86, 0xdb4d, 0xb3bf, 0xbb9c,
+0xe353, 0xd3fa, 0x034f, 0x4cdb, 0x4ab4, 0x21fa, 0x2b59, 0x391f, 0xeb27, 0xc6cb,
+0xb8e4, 0xba9a, 0xe811, 0xec0e, 0x0c31, 0x4bdb, 0x5772, 0x163d, 0x1a19, 0x3f4d,
+0xe9e8, 0xc3a2, 0xce2a, 0xc788, 0xd9f4, 0xf83e, 0x1bc6, 0x3ce8, 0x5548, 0x1db0,
+0x1a55, 0x4521, 0xf1e2, 0xb3ab, 0xc2fd, 0xdbe1, 0xd800, 0xed52, 0x2c30, 0x402d,
+0x430b, 0x23ff, 0x260b, 0x2f76, 0xea68, 0xb3d7, 0xb28e, 0xe14a, 0xdd06, 0xe066,
+0x33b3, 0x51ec, 0x33b4, 0x1667, 0x3052, 0x1b77, 0xd41e, 0xc1bd, 0xb6c4, 0xd7a4,
+0xeae0, 0xe9c9, 0x2119, 0x50ee, 0x3348, 0x0504, 0x35a7, 0x2224, 0xc555, 0xbad4,
+0xc362, 0xd0c4, 0xdade, 0xf6c3, 0x247a, 0x463c, 0x3726, 0x098e, 0x2ae9, 0x1a2f,
+0xc9d7, 0xb0fe, 0xc012, 0xdaac, 0xd5e1, 0xf9b0, 0x351f, 0x47da, 0x2cc6, 0x10fb,
+0x2d9f, 0x068e, 0xc5e3, 0xb7fe, 0xc297, 0xe2b0, 0xe422, 0x01db, 0x36ef, 0x48cb,
+0x23b7, 0x10de, 0x3507, 0x0307, 0xc7bf, 0xbff3, 0xc77c, 0xe128, 0xe56e, 0x0857,
+0x3924, 0x4aaf, 0x2249, 0x103e, 0x31eb, 0xfa7b, 0xc42a, 0xbff2, 0xca69, 0xdf50,
+0xe715, 0x1070, 0x37d6, 0x4303, 0x1eac, 0x16ba, 0x3184, 0xf739, 0xc4f5, 0xba46,
+0xcd7e, 0xe4d0, 0xe592, 0x10ee, 0x3d09, 0x4044, 0x17ce, 0x2016, 0x2f3f, 0xe9e5,
+0xc633, 0xbad5, 0xc6f9, 0xdeac, 0xe624, 0x121c, 0x413b, 0x4301, 0x1783, 0x29fd,
+0x2ba5, 0xdaa4, 0xbe2e, 0xbb8e, 0xcbc8, 0xdde4, 0xf1e9, 0x21db, 0x439b, 0x4190,
+0x172e, 0x27a0, 0x21f8, 0xd636, 0xbad8, 0xb8bf, 0xd096, 0xdf64, 0xf8a8, 0x2b93,
+0x443b, 0x3b96, 0x185e, 0x26da, 0x10a6, 0xd167, 0xbdb5, 0xb977, 0xdb3a, 0xe708,
+0xfa86, 0x2f52, 0x4806, 0x3265, 0x17a3, 0x31bc, 0x0e05, 0xd510, 0xc461, 0xbd66,
+0xe192, 0xed77, 0x0119, 0x32ba, 0x50ed, 0x31ea, 0x16d3, 0x36ec, 0x0732, 0xcfc7,
+0xc460, 0xc184, 0xdf83, 0xea58, 0x0626, 0x3550, 0x5246, 0x2def, 0x16cd, 0x3427,
+0xfd3a, 0xc953, 0xbd3c, 0xc544, 0xe4ec, 0xf153, 0x166b, 0x3e10, 0x4e6d, 0x26d4,
+0x1968, 0x2f16, 0xf428, 0xcae0, 0xbedd, 0xccf8, 0xe956, 0xefa2, 0x1950, 0x4297,
+0x4a75, 0x1de5, 0x1955, 0x29ba, 0xeb91, 0xc61c, 0xbb12, 0xce33, 0xe7ce, 0xefe6,
+0x18c6, 0x3e32, 0x43c5, 0x1922, 0x1cc3, 0x20fd, 0xde2b, 0xbf55, 0xb70b, 0xd16f,
+0xe788, 0xef8e, 0x20c4, 0x4654, 0x3d93, 0x11d7, 0x1d2e, 0x15c5, 0xd810, 0xc122,
+0xb7df, 0xd5bd, 0xe934, 0xf5a0, 0x24cd, 0x4321, 0x356b, 0x10be, 0x2522, 0x0e25,
+0xcdbf, 0xc120, 0xbd1c, 0xd9ef, 0xe950, 0xf8ec, 0x2940, 0x4a04, 0x31e9, 0x09bb,
+0x2935, 0x0de2, 0xcd32, 0xc3e0, 0xc0ad, 0xd96f, 0xebee, 0x0617, 0x2f65, 0x4a9d,
+0x32b8, 0x104d, 0x2b20, 0x042c, 0xc6fe, 0xc0fc, 0xccb1, 0xe4c6, 0xe7e5, 0x0a2f,
+0x3646, 0x44a9, 0x27ed, 0x0f84, 0x2482, 0xf9ba, 0xc9c9, 0xbc46, 0xc6f1, 0xe7c9,
+0xe7b4, 0x0935, 0x38ab, 0x3dfb, 0x17f4, 0x1202, 0x2366, 0xe8cf, 0xc894, 0xc02b,
+0xc4e7, 0xe682, 0xe9b4, 0x07dc, 0x39bc, 0x4323, 0x1255, 0x11a3, 0x2383, 0xde38,
+0xc201, 0xc28c, 0xc797, 0xe30e, 0xf319, 0x11aa, 0x3329, 0x3e07, 0x11d9, 0x1242,
+0x2173, 0xdd6c, 0xbc12, 0xbfc4, 0xd3cc, 0xe301, 0xf14f, 0x1ed7, 0x3c0b, 0x3bbe,
+0x150a, 0x16d9, 0x1504, 0xded6, 0xc844, 0xc0c3, 0xdedd, 0xf0d7, 0xf666, 0x25d8,
+0x4424, 0x33d0, 0x130e, 0x2876, 0x1370, 0xd7a6, 0xcf17, 0xc604, 0xdfb9, 0xf3d2,
+0xfde9, 0x29bc, 0x4a3a, 0x328f, 0x0b59, 0x293f, 0x0f84, 0xd398, 0xd04b, 0xc938,
+0xdc96, 0xf077, 0x05ff, 0x2a1d, 0x46e9, 0x33e3, 0x0e06, 0x27af, 0x084b, 0xcbee,
+0xc5ed, 0xce50, 0xe4ec, 0xee95, 0x0fc4, 0x3481, 0x4291, 0x2ab8, 0x0ee7, 0x217a,
+0xfbca, 0xd072, 0xc584, 0xcdc2, 0xeeae, 0xf284, 0x0beb, 0x2a91, 0x499c, 0x4ab9,
+0x1751, 0xfb4e, 0xdff4, 0xc548, 0xaa11, 0xcb25, 0x079b, 0x0c5f, 0x3e98, 0x6247,
+0x3e07, 0x0f43, 0xf0e4, 0xca5f, 0xb34f, 0xd401, 0xc893, 0xf3d1, 0x4c26, 0x308b,
+0x22b3, 0x41c0, 0x2279, 0xdc8b, 0xdff8, 0xe310, 0xcceb, 0x027f, 0xf615, 0xee06,
+0x3db0, 0x2392, 0xe01d, 0x1366, 0x3415, 0xe34c, 0xf6fc, 0x29c3, 0xe8c4, 0xee7a,
+0x0fa2, 0xddee, 0xd84b, 0x1d1b, 0x1722, 0xff58, 0x3ac2, 0x1f74, 0xf03f, 0x1870,
+0xfcd6, 0xbff1, 0xe68f, 0x1180, 0xed1f, 0x0a12, 0x3895, 0x07ec, 0x0a31, 0x24b3,
+0xe648, 0xc9fc, 0x0734, 0xf757, 0xd412, 0x1f4f, 0x1e8b, 0xee37, 0x1f0e, 0x1be3,
+0xd43a, 0xefa3, 0x194b, 0xddce, 0xf237, 0x287d, 0xeedf, 0xf88e, 0x2f7f, 0xf704,
+0xd874, 0x1c88, 0x0972, 0xd8d5, 0x1702, 0x0f28, 0xe05e, 0x15f4, 0x1a7c, 0xd9ff,
+0xf6eb, 0x29b0, 0xf002, 0xf068, 0x23d5, 0xf4b8, 0xf046, 0x1d23, 0xf157, 0xd110,
+0x1519, 0x14cc, 0xdede, 0x142d, 0x1a12, 0xe515, 0x0b1c, 0x1636, 0xd4d5, 0xe629,
+0x1cf8, 0xe7c6, 0xea58, 0x2b1f, 0xfc3e, 0xeeea, 0x236a, 0xfb53, 0xcfca, 0x089d,
+0x0488, 0xd160, 0x0a55, 0x141d, 0xe5cd, 0x107d, 0x186d, 0xd8c8, 0xeaf7, 0x1870,
+0xe826, 0xe955, 0x18d7, 0xf4b0, 0xf2f4, 0x198f, 0xf637, 0xd898, 0x0a60, 0x099d,
+0xe121, 0x112c, 0x19f0, 0xe9f0, 0x0257, 0x16f3, 0xea68, 0xe825, 0x11db, 0xf73d,
+0xf784, 0x22c2, 0x01a7, 0xfa94, 0x2329, 0xff98, 0xd3ac, 0x066b, 0x0f20, 0xe092,
+0x0cc3, 0x1ddc, 0xf6f5, 0x1320, 0x1de5, 0xe877, 0xf111, 0x167b, 0xeabe, 0xf59b,
+0x2682, 0xf99e, 0xf763, 0x2689, 0x0359, 0xdfbd, 0x0e98, 0x0da2, 0xe78d, 0x0e20,
+0x0ddb, 0xeff0, 0x11e2, 0x1312, 0xe459, 0xf4b0, 0x1704, 0xee92, 0xf4ab, 0x1ec2,
+0xfc20, 0xf450, 0x169f, 0xfcad, 0xdbac, 0x0468, 0x089e, 0xe794, 0x0e53, 0x118e,
+0xefe9, 0x0e4a, 0x169a, 0xe1da, 0xebc0, 0x15eb, 0xefcb, 0xf2ee, 0x1beb, 0xfce8,
+0xfa9c, 0x1c17, 0xffdb, 0xe6b1, 0x0fc5, 0x065c, 0xe6be, 0x10a6, 0x0c31, 0xefee,
+0x1306, 0x13f9, 0xe94c, 0xfe45, 0x19f0, 0xf32a, 0xfe85, 0x1905, 0xf62a, 0xfd6b,
+0x1747, 0xf729, 0xeaea, 0x11d1, 0x0593, 0xf301, 0x1786, 0x0b5f, 0xf124, 0x0940,
+0x0668, 0xe6ec, 0xfe96, 0x1506, 0xf474, 0x04f3, 0x1923, 0xf708, 0xfe00, 0x1197,
+0xf0a5, 0xe784, 0x0dcb, 0xff73, 0xf084, 0x1476, 0x091e, 0xf640, 0x0af0, 0x0286,
+0xe773, 0xfd6b, 0x0efa, 0xf175, 0xf89a, 0x07af, 0xf9a6, 0x0516, 0x128d, 0x00ff,
+0xfb4e, 0x0ed0, 0xfc70, 0xe60e, 0xf53d, 0xf46c, 0xf494, 0x0bff, 0x1551, 0x06e1,
+0x0c9f, 0x0f55, 0xefab, 0xed70, 0xf731, 0xf0fe, 0xfd13, 0x0d56, 0x068f, 0x0174,
+0x0fee, 0xffe4, 0xea15, 0xfb9a, 0xffd4, 0xf166, 0xfe33, 0x0e6f, 0xfeb8, 0x0068,
+0x0cad, 0xf543, 0xef47, 0x0118, 0xf5be, 0xeb4f, 0x04dc, 0x09b3, 0xf586, 0x0081,
+0x05bb, 0xf123, 0xf60d, 0x03d9, 0xf8d9, 0xfe28, 0x0dac, 0xfb79, 0xf27c, 0xfe7f,
+0xf2c5, 0xeeb0, 0x04d2, 0x030d, 0xfb07, 0x0e9e, 0x0d11, 0xf686, 0xfc94, 0xffbf,
+0xed59, 0xf589, 0x05ab, 0xfa48, 0xffa7, 0x10c1, 0x014b, 0xfa25, 0x09af, 0xfdfc,
+0xf0ef, 0x03b9, 0x0400, 0xf635, 0x063f, 0x0b0b, 0xf9ca, 0x03dd, 0x0d8d, 0xfce6,
+0x00df, 0x0f4b, 0x024e, 0xff59, 0x0dc5, 0x0159, 0xf654, 0x067a, 0x0580, 0xfe5c,
+0x0e74, 0x0f97, 0x0220, 0x099e, 0x09f1, 0xf6cc, 0xfa19, 0x04a0, 0xfb69, 0x0199,
+0x10a2, 0x0726, 0x02f2, 0x0c0d, 0x00c5, 0xf7e4, 0x056d, 0x020c, 0xf921, 0x0880,
+0x0a05, 0xfc9b, 0x05b1, 0x0ba8, 0xfd73, 0x0126, 0x0afa, 0xfedb, 0xfe8e, 0x0754,
+0xff08, 0x010e, 0x0a12, 0xff2a, 0xfc7a, 0x0965, 0x0306, 0xfbf9, 0x0735, 0x05c2,
+0xfdbe, 0x020b, 0x01d4, 0xfd23, 0x035d, 0x0690, 0x0006, 0x0536, 0x0b42, 0x02fd,
+0x0048, 0x04d9, 0xffac, 0xfb96, 0x0522, 0x06ea, 0x01a3, 0x0640, 0x0721, 0x02e7,
+0x04ad, 0x0494, 0xfe70, 0x009e, 0x0380, 0xfafb, 0xfecf, 0x0890, 0x0224, 0xffd6,
+0x0613, 0x005f, 0xfada, 0x0129, 0xfdf3, 0xf949, 0x014d, 0x006d, 0xfa91, 0x0029,
+0x002c, 0xf7d8, 0xff02, 0x0615, 0xfbc4, 0xfc2b, 0x035b, 0xfb89, 0xf601, 0xfa4f,
+0xfabf, 0xfbd9, 0x00a3, 0xfe0a, 0xfbd4, 0x0009, 0xfe8b, 0xf866, 0xf96c, 0xfcbd,
+0xf750, 0xf6d5, 0xfee5, 0xfea6, 0xfc47, 0x00a5, 0x013f, 0xfaa4, 0xf910, 0xf98a,
+0xf6f0, 0xfa55, 0xfc01, 0xfac1, 0xff53, 0x001d, 0xfac1, 0xfb9d, 0xfef2, 0xf9c9,
+0xf9ce, 0xff10, 0xfabf, 0xfab8, 0xfe59, 0xfa05, 0xfa90, 0x00a1, 0xff54, 0xfdbe,
+0x0243, 0xff6b, 0xfac6, 0xfde2, 0xfc78, 0xf927, 0xfc12, 0xfe2f, 0xfc8a, 0xfe15,
+0x00a9, 0xff55, 0x0011, 0x0049, 0xfbfb, 0xfa18, 0xfd23, 0xfbc7, 0xf7ac, 0xfd25,
+0x019e, 0x0030, 0x02a9, 0x0344, 0x0053, 0x0036, 0x003f, 0xfc42, 0xfd77, 0x047f,
+0x022f, 0xffd5, 0x0747, 0x065b, 0xffcd, 0x058c, 0x0801, 0x00b8, 0x029f, 0x057c,
+0x0228, 0x02a9, 0x03ae, 0x0336, 0x052e, 0x06d3, 0x0355, 0x0367, 0x097b, 0x0767,
+0x040b, 0x0708, 0x062b, 0x00f3, 0x010e, 0x0624, 0x0661, 0x0641, 0x0870, 0x08cf,
+0x095c, 0x0672, 0x0114, 0x0064, 0x0450, 0x0372, 0x00f0, 0x074e, 0x0927, 0x0375,
+0x04d0, 0x06fa, 0x0278, 0x00e0, 0x023e, 0x0105, 0x0392, 0x0478, 0x01dc, 0x0633,
+0x06ad, 0x0137, 0x035b, 0x071e, 0x039a, 0x00b9, 0x0238, 0x0257, 0x03b3, 0x032c,
+0x00b5, 0x062b, 0x0920, 0x0425, 0x05d7, 0x09b0, 0x0457, 0x011e, 0x02cb, 0x0112,
+0x01e7, 0x03e0, 0x04a0, 0x09f0, 0x0993, 0x025d, 0x03ab, 0x06f0, 0x01ce, 0xffa9,
+0x0343, 0x03bf, 0x02bc, 0x0192, 0x0175, 0x03f8, 0x025f, 0xfe13, 0xffaf, 0x037b,
+0x017f, 0xff0d, 0x010c, 0x01cc, 0xff47, 0xfda7, 0xff79, 0xffc8, 0xfd08, 0xfdcd,
+0x01e5, 0x03c0, 0x00fa, 0xfdf8, 0xfdb9, 0xff87, 0xff9b, 0xfbe2, 0xfeae, 0x0380,
+0x0014, 0xfe6f, 0x0115, 0x0048, 0xfe1d, 0xff3b, 0xff7b, 0xff0a, 0x0012, 0xfed0,
+0xfed6, 0xffe0, 0xfcbf, 0xfbdb, 0x005d, 0xffb1, 0xf9ae, 0xfb2b, 0x0004, 0xff44,
+0xfc6c, 0xfb62, 0xfdf8, 0xff0d, 0xfbbb, 0xfae7, 0xfd8a, 0xfd70, 0xfaee, 0xfbe3,
+0xfdf3, 0xfc89, 0xfa9a, 0xfb6e, 0xfd82, 0xfcb8, 0xfaa3, 0xfc2a, 0xfeb9, 0xfc5c,
+0xf8a1, 0xfb85, 0x000c, 0xfee7, 0xfbc5, 0xfc1f, 0xfee8, 0xfdac, 0xfae0, 0xfc8b,
+0xfef5, 0xfebd, 0xfdf5, 0xfec1, 0x0023, 0xff0f, 0xfc4a, 0xfdd7, 0x00f5, 0xfea6,
+0xfd0c, 0x002f, 0x0087, 0xfc77, 0xfc93, 0x0024, 0x013b, 0x00dc, 0xfdb0, 0xfcbd,
+0x0060, 0xfed8, 0xfc65, 0xfff5, 0x0160, 0xfef8, 0x0045, 0x0243, 0x0065, 0xfff6,
+0x0124, 0x00f9, 0x00f5, 0x00ae, 0xff8e, 0x00df, 0x02e1, 0xffa3, 0xfea2, 0x0390,
+0x035d, 0xff35, 0xff1f, 0x009b, 0x0048, 0xffed, 0x0005, 0xfff7, 0x0019, 0xff23,
+0xfe36, 0xff91, 0x0112, 0x0088, 0xfff6, 0x00fa, 0x00a3, 0xfe18, 0xfe82, 0x01b6,
+0x00ae, 0xfead, 0x018f, 0x02ef, 0x0126, 0x002b, 0xffb6, 0x01e3, 0x0382, 0x00b0,
+0x00bd, 0x0402, 0x024e, 0x0002, 0x02e3, 0x0327, 0x00b6, 0x01cf, 0x02ca, 0x0250,
+0x0280, 0x01bd, 0x021d, 0x04f6, 0x0340, 0xfee9, 0x0095, 0x0305, 0x00bc, 0x012c,
+0x04fc, 0x0469, 0x0257, 0x027e, 0x0242, 0x01ad, 0x014b, 0x00c3, 0x0147, 0x037a,
+0x036a, 0x00b5, 0x0068, 0x0121, 0xff9a, 0xfec8, 0x0143, 0x0151, 0xfd18, 0xfced,
+0xffbd, 0x0066, 0x007d, 0xff3b, 0xfdd4, 0xfe9a, 0xff0d, 0xfdc1, 0xfe33, 0xffd7,
+0xfd45, 0xfbe9, 0xffc6, 0xff2d, 0xfb3e, 0xfd69, 0x0057, 0xff59, 0xffc4, 0x00ad,
+0x0017, 0xffc4, 0xfde4, 0xfc22, 0xfe08, 0x004b, 0xffd4, 0x0028, 0x0265, 0x00fa,
+0xfdc5, 0xfe0b, 0xff0e, 0xfe78, 0xfdca, 0xfe74, 0xff9b, 0x005c, 0x0106, 0x00eb,
+0x015c, 0x01a8, 0xff52, 0xfee2, 0x00ee, 0xffbe, 0xfec8, 0x010d, 0x01ab, 0x00d9,
+0x0106, 0x010d, 0x00e8, 0x0118, 0x00e8, 0x012b, 0x02b4, 0x0211, 0xff7a, 0xff8e,
+0x0115, 0x0100, 0x00de, 0x02dd, 0x0379, 0x00f9, 0xffd1, 0x0018, 0xfff6, 0xfffa,
+0x000c, 0xfff1, 0x0004, 0x0012, 0xffc4, 0x009c, 0x010c, 0xfffb, 0xffe4, 0x001a,
+0xfff2, 0xfffc, 0x0012, 0xffe2, 0x0015, 0x015a, 0x0257, 0x01c9, 0x004d, 0xfff9,
+0xffdd, 0x0059, 0x019b, 0x004f, 0x0095, 0x030f, 0x0191, 0xff96, 0x015d, 0x0296,
+0x01ee, 0x0205, 0x01f5, 0x0220, 0x0303, 0x0276, 0x01b6, 0x02a2, 0x02f4, 0x01f9,
+0x0209, 0x01e3, 0x0073, 0x00ad, 0x0219, 0x0205, 0x00f2, 0x000a, 0xffc0, 0x00c9,
+0x0105, 0xffac, 0x007c, 0x0198, 0x0051, 0xffcf, 0x001b, 0xfff4, 0x0001, 0x0002,
+0xfffe, 0xfffb, 0x0005, 0xfffd, 0xfff3, 0x0025, 0xff14, 0xfe3b, 0xffa5, 0xfffc,
+0xfee0, 0xff17, 0xffe8, 0x0010, 0xffec, 0x0017, 0xffe5, 0xff21, 0xfef3, 0xfef8,
+0xff4b, 0x002c, 0xffc0, 0xfee9, 0xffc3, 0x001f, 0xff65, 0xffbc, 0x001d, 0xfff2,
+000000, 0x0003, 0xfffc, 0xfffc, 0x000a, 0xffec, 0x0015, 0x0095, 0x002c, 0xffc9,
+0x006a, 0x0078, 0xfff2, 0xfffb, 0x0006, 0xfffa, 0x0002, 0xfffd, 0x0004, 0xfff4,
+0x0010, 0xfff2, 0xfff7, 0x003a, 0xfeb1, 0xfe3a, 0xfff4, 0x0020, 0xffde, 0x0015,
+0xfff6, 000000, 0x0002, 0xfffc, 0x0001, 0xfffe, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000,
+000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000 };
diff --git a/1.4.23-rc4/channels/ring_tone.h b/1.4.23-rc4/channels/ring_tone.h
new file mode 100644
index 000000000..559c42a7b
--- /dev/null
+++ b/1.4.23-rc4/channels/ring_tone.h
@@ -0,0 +1,30 @@
+/* ringtone.h: Generated from frequencies 440 and 480
+ by gentone. 200 samples */
+static short ringtone[200] = {
+ 0, 11581, 21659, 28927, 32445, 31764, 26981, 18727,
+ 8084, -3559, -14693, -23875, -29927, -32083, -30088, -24228,
+ -15290, -4453, 6864, 17195, 25212, 29902, 30693, 27526,
+ 20856, 11585, 944, -9673, -18899, -25560, -28837, -28357,
+ -24244, -17089, -7868, 2192, 11780, 19667, 24872, 26779,
+ 25212, 20450, 13179, 4396, -4731, -13019, -19421, -23164,
+ -23839, -21446, -16384, -9384, -1408, 6484, 13281, 18145,
+ 20517, 20182, 17286, 12301, 5951, -887, -7314, -12519,
+ -15886, -17068, -16017, -12983, -8458, -3109, 2327, 7142,
+ 10750, 12757, 13007, 11585, 8793, 5095, 1044, -2800,
+ -5951, -8053, -8921, -8560, -7141, -4967, -2421, 104,
+ 2260, 3791, 4567, 4589, 3977, 2941, 1733, 600,
+ -257, -722, -772, -481, 0, 481, 772, 722,
+ 257, -600, -1733, -2941, -3977, -4589, -4567, -3791,
+ -2260, -104, 2421, 4967, 7141, 8560, 8921, 8053,
+ 5951, 2800, -1044, -5095, -8793, -11585, -13007, -12757,
+ -10750, -7142, -2327, 3109, 8458, 12983, 16017, 17068,
+ 15886, 12519, 7314, 887, -5951, -12301, -17286, -20182,
+ -20517, -18145, -13281, -6484, 1408, 9384, 16384, 21446,
+ 23839, 23164, 19421, 13019, 4731, -4396, -13179, -20450,
+ -25212, -26779, -24872, -19667, -11780, -2192, 7868, 17089,
+ 24244, 28357, 28837, 25560, 18899, 9673, -944, -11585,
+ -20856, -27526, -30693, -29902, -25212, -17195, -6864, 4453,
+ 15290, 24228, 30088, 32083, 29927, 23875, 14693, 3559,
+ -8084, -18727, -26981, -31764, -32445, -28927, -21659, -11581,
+
+};