diff options
Diffstat (limited to 'channels')
54 files changed, 94470 insertions, 0 deletions
diff --git a/channels/DialTone.h b/channels/DialTone.h new file mode 100644 index 000000000..098ed44d7 --- /dev/null +++ b/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/channels/Makefile b/channels/Makefile new file mode 100644 index 000000000..8f067f6b0 --- /dev/null +++ b/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/channels/answer.h b/channels/answer.h new file mode 100644 index 000000000..4168c5012 --- /dev/null +++ b/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/channels/busy_tone.h b/channels/busy_tone.h new file mode 100644 index 000000000..6e5db8e47 --- /dev/null +++ b/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/channels/chan_agent.c b/channels/chan_agent.c new file mode 100644 index 000000000..b12983dfc --- /dev/null +++ b/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/channels/chan_alsa.c b/channels/chan_alsa.c new file mode 100644 index 000000000..03db26b6a --- /dev/null +++ b/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/channels/chan_dahdi.c b/channels/chan_dahdi.c new file mode 100644 index 000000000..81498b372 --- /dev/null +++ b/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, ¶ms)) >= 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, ¶m)) + 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(§_conf, &conf, sizeof(sect_conf)); + + process_dahdi(§_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/channels/chan_features.c b/channels/chan_features.c new file mode 100644 index 000000000..043576623 --- /dev/null +++ b/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/channels/chan_gtalk.c b/channels/chan_gtalk.c new file mode 100644 index 000000000..abe0d63b6 --- /dev/null +++ b/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(>alk_list, name); + if (!gtalk && strchr(name, '@')) + gtalk = ASTOBJ_CONTAINER_FIND_FULL(>alk_list, name, user,,, strcasecmp); + + if (!gtalk) { + /* guest call */ + ASTOBJ_CONTAINER_TRAVERSE(>alk_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(>alklock); + tmp->next = client->p; + client->p = tmp; + ast_mutex_unlock(>alklock); + 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 = >alk_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(>alklock); + ast_cli(fd, FORMAT, "Channel", "Jabber ID", "Resource", "Read", "Write"); + ASTOBJ_CONTAINER_TRAVERSE(>alk_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(>alklock); + + 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(>alk_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(>alk_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(>alk_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(>alk_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(>alk_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(>alk_tech); + ast_rtp_proto_unregister(>alk_rtp); + + if (!ast_mutex_lock(>alklock)) { + /* Hangup all interfaces if they have an owner */ + ASTOBJ_CONTAINER_TRAVERSE(>alk_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(>alklock); + } else { + ast_log(LOG_WARNING, "Unable to lock the monitor\n"); + return -1; + } + ASTOBJ_CONTAINER_DESTROYALL(>alk_list, gtalk_member_destroy); + ASTOBJ_CONTAINER_DESTROY(>alk_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/channels/chan_h323.c b/channels/chan_h323.c new file mode 100644 index 000000000..1199ddda9 --- /dev/null +++ b/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/channels/chan_iax2.c b/channels/chan_iax2.c new file mode 100644 index 000000000..d03f47c6c --- /dev/null +++ b/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, ®seconds, 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(®exbuf, 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(®exbuf, 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(®exbuf); + + 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(®exbuf, 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(®exbuf, 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(®exbuf, 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(®exbuf); + + 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(®istrations); + AST_LIST_TRAVERSE(®istrations, 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(®istrations); + 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, ®->us, sizeof(oldus)); + oldmsgs = reg->messages; + if (inaddrcmp(®->addr, sin)) { + ast_log(LOG_WARNING, "Received unsolicited registry ack from '%s'\n", ast_inet_ntoa(sin->sin_addr)); + return -1; + } + memcpy(®->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, ®->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, ®->addr.sin_addr, ®->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(®istrations); + AST_LIST_INSERT_HEAD(®istrations, reg, entry); + AST_LIST_UNLOCK(®istrations); + + 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(®->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, ®->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(®istrations); + while ((reg = AST_LIST_REMOVE_HEAD(®istrations, 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(®istrations); + + 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(®istrations); + AST_LIST_TRAVERSE(®istrations, reg, entry) + iax2_do_register(reg); + AST_LIST_UNLOCK(®istrations); + /* 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(®istrations); + AST_LIST_TRAVERSE(®istrations, reg, entry) + iax2_do_register(reg); + AST_LIST_UNLOCK(®istrations); + + 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/channels/chan_local.c b/channels/chan_local.c new file mode 100644 index 000000000..b2e3c72b8 --- /dev/null +++ b/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/channels/chan_mgcp.c b/channels/chan_mgcp.c new file mode 100644 index 000000000..1d5114bee --- /dev/null +++ b/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/channels/chan_misdn.c b/channels/chan_misdn.c new file mode 100644 index 000000000..c5965a1c1 --- /dev/null +++ b/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/channels/chan_nbs.c b/channels/chan_nbs.c new file mode 100644 index 000000000..b231127ce --- /dev/null +++ b/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/channels/chan_oss.c b/channels/chan_oss.c new file mode 100644 index 000000000..ac62f2005 --- /dev/null +++ b/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/channels/chan_phone.c b/channels/chan_phone.c new file mode 100644 index 000000000..55bda8b72 --- /dev/null +++ b/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/channels/chan_sip.c b/channels/chan_sip.c new file mode 100644 index 000000000..004aaaabe --- /dev/null +++ b/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(®l, 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(®exbuf, 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(®exbuf, 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(®exbuf); + + 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(®exbuf, 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(®exbuf, 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(®exbuf); + + 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), ®l); + 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(®exbuf, 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(®exbuf, 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(®exbuf, 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(®l, 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, ¤t, 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(¤t.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, ®seconds, 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(®l, 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(®l, 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(®l, 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(®l); /* 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(®l, sip_registry_destroy); + ASTOBJ_CONTAINER_DESTROY(®l); + + 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/channels/chan_skinny.c b/channels/chan_skinny.c new file mode 100644 index 000000000..657163398 --- /dev/null +++ b/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/channels/chan_vpb.cc b/channels/chan_vpb.cc new file mode 100644 index 000000000..56467a6ce --- /dev/null +++ b/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/channels/gentone-ulaw.c b/channels/gentone-ulaw.c new file mode 100644 index 000000000..b290d76c7 --- /dev/null +++ b/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/channels/gentone.c b/channels/gentone.c new file mode 100644 index 000000000..29bd88e91 --- /dev/null +++ b/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/channels/h323/ChangeLog b/channels/h323/ChangeLog new file mode 100644 index 000000000..ddbf08193 --- /dev/null +++ b/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/channels/h323/INSTALL.openh323 b/channels/h323/INSTALL.openh323 new file mode 100644 index 000000000..f46c37905 --- /dev/null +++ b/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/channels/h323/Makefile.in b/channels/h323/Makefile.in new file mode 100644 index 000000000..083250f55 --- /dev/null +++ b/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/channels/h323/README b/channels/h323/README new file mode 100644 index 000000000..875bf3668 --- /dev/null +++ b/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/channels/h323/TODO b/channels/h323/TODO new file mode 100644 index 000000000..1e114ca3b --- /dev/null +++ b/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/channels/h323/ast_h323.cxx b/channels/h323/ast_h323.cxx new file mode 100644 index 000000000..32b674dec --- /dev/null +++ b/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/channels/h323/ast_h323.h b/channels/h323/ast_h323.h new file mode 100644 index 000000000..c4f24c529 --- /dev/null +++ b/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/channels/h323/caps_h323.cxx b/channels/h323/caps_h323.cxx new file mode 100644 index 000000000..a420825a3 --- /dev/null +++ b/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/channels/h323/caps_h323.h b/channels/h323/caps_h323.h new file mode 100644 index 000000000..be63e0230 --- /dev/null +++ b/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/channels/h323/chan_h323.h b/channels/h323/chan_h323.h new file mode 100644 index 000000000..0fd94561f --- /dev/null +++ b/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/channels/h323/cisco-h225.asn b/channels/h323/cisco-h225.asn new file mode 100644 index 000000000..1372e67d5 --- /dev/null +++ b/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/channels/h323/cisco-h225.cxx b/channels/h323/cisco-h225.cxx new file mode 100644 index 000000000..37adc4e87 --- /dev/null +++ b/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/channels/h323/cisco-h225.h b/channels/h323/cisco-h225.h new file mode 100644 index 000000000..7595b4b65 --- /dev/null +++ b/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/channels/h323/compat_h323.cxx b/channels/h323/compat_h323.cxx new file mode 100644 index 000000000..eec7361b2 --- /dev/null +++ b/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/channels/h323/compat_h323.h b/channels/h323/compat_h323.h new file mode 100644 index 000000000..63da8ac8c --- /dev/null +++ b/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/channels/h323/noexport.map b/channels/h323/noexport.map new file mode 100644 index 000000000..b51f84263 --- /dev/null +++ b/channels/h323/noexport.map @@ -0,0 +1,5 @@ +{ + global: + _Z11PAssertFuncPKc; + local: *; +};
\ No newline at end of file diff --git a/channels/iax2-parser.c b/channels/iax2-parser.c new file mode 100644 index 000000000..67bffa897 --- /dev/null +++ b/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/channels/iax2-parser.h b/channels/iax2-parser.h new file mode 100644 index 000000000..0f3e18c00 --- /dev/null +++ b/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/channels/iax2-provision.c b/channels/iax2-provision.c new file mode 100644 index 000000000..b6137a88b --- /dev/null +++ b/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/channels/iax2-provision.h b/channels/iax2-provision.h new file mode 100644 index 000000000..d95150253 --- /dev/null +++ b/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/channels/iax2.h b/channels/iax2.h new file mode 100644 index 000000000..960dec8bb --- /dev/null +++ b/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/channels/misdn/Makefile b/channels/misdn/Makefile new file mode 100644 index 000000000..e277636e6 --- /dev/null +++ b/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/channels/misdn/chan_misdn_config.h b/channels/misdn/chan_misdn_config.h new file mode 100644 index 000000000..f675704c0 --- /dev/null +++ b/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/channels/misdn/ie.c b/channels/misdn/ie.c new file mode 100644 index 000000000..2e7fae998 --- /dev/null +++ b/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/channels/misdn/isdn_lib.c b/channels/misdn/isdn_lib.c new file mode 100644 index 000000000..b21c794e7 --- /dev/null +++ b/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/channels/misdn/isdn_lib.h b/channels/misdn/isdn_lib.h new file mode 100644 index 000000000..451876888 --- /dev/null +++ b/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/channels/misdn/isdn_lib_intern.h b/channels/misdn/isdn_lib_intern.h new file mode 100644 index 000000000..93d879744 --- /dev/null +++ b/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/channels/misdn/isdn_msg_parser.c b/channels/misdn/isdn_msg_parser.c new file mode 100644 index 000000000..a587f8eae --- /dev/null +++ b/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/channels/misdn/portinfo.c b/channels/misdn/portinfo.c new file mode 100644 index 000000000..bcb9f0313 --- /dev/null +++ b/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/channels/misdn_config.c b/channels/misdn_config.c new file mode 100644 index 000000000..0924ad983 --- /dev/null +++ b/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/channels/ring10.h b/channels/ring10.h new file mode 100644 index 000000000..f45f8dbb9 --- /dev/null +++ b/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/channels/ring_tone.h b/channels/ring_tone.h new file mode 100644 index 000000000..559c42a7b --- /dev/null +++ b/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, + +}; |