2009-12-16 04:05:47 +00:00
|
|
|
/*
|
|
|
|
* Asterisk DECT Channel driver
|
|
|
|
*
|
|
|
|
* Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "asterisk.h"
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
ASTERISK_FILE_VERSION(__FILE__, "$Revision")
|
|
|
|
|
|
|
|
#include <signal.h>
|
2010-10-12 05:34:59 +00:00
|
|
|
#include <semaphore.h>
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
#include "asterisk/lock.h"
|
|
|
|
#include "asterisk/channel.h"
|
|
|
|
#include "asterisk/config.h"
|
|
|
|
#include "asterisk/module.h"
|
|
|
|
#include "asterisk/pbx.h"
|
|
|
|
#include "asterisk/sched.h"
|
|
|
|
#include "asterisk/io.h"
|
|
|
|
#include "asterisk/cli.h"
|
2010-01-29 18:08:21 +00:00
|
|
|
#include "asterisk/astdb.h"
|
2009-12-16 04:05:47 +00:00
|
|
|
#include "asterisk/causes.h"
|
2010-01-29 18:08:21 +00:00
|
|
|
#include "asterisk/callerid.h"
|
2009-12-16 04:05:47 +00:00
|
|
|
#include "asterisk/abstract_jb.h"
|
|
|
|
|
2010-07-25 14:54:27 +00:00
|
|
|
#include <linux/dect.h>
|
2010-08-16 18:51:29 +00:00
|
|
|
#include <linux/dect_netlink.h>
|
2009-12-16 04:05:47 +00:00
|
|
|
#include <dect/libdect.h>
|
|
|
|
#include <dect/terminal.h>
|
2010-07-24 20:16:24 +00:00
|
|
|
#include <dect/debug.h>
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
#define CONFIG_FILE "dect.conf"
|
|
|
|
|
2010-10-11 15:29:03 +00:00
|
|
|
#define DECT_LOCATE_TIMEOUT_SLACK 10 /* seconds */
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
static const struct ast_channel_tech dect_tech;
|
|
|
|
static struct dect_handle *dh;
|
|
|
|
static struct sched_context *sched;
|
|
|
|
static struct io_context *io;
|
2010-08-16 18:51:29 +00:00
|
|
|
static int access_rights_timer;
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
static const struct ast_jb_conf dect_default_jbconf = {
|
|
|
|
.max_size = -1,
|
|
|
|
.resync_threshold = -1,
|
|
|
|
.impl = "",
|
|
|
|
};
|
|
|
|
|
2010-08-16 18:51:29 +00:00
|
|
|
static struct dect_fp_capabilities dect_fpc = {
|
|
|
|
.hlc = DECT_HLC_ADPCM_G721_VOICE |
|
|
|
|
DECT_HLC_GAP_PAP_BASIC_SPEECH |
|
|
|
|
DECT_HLC_CISS_SERVICE |
|
|
|
|
DECT_HLC_CLMS_SERVICE |
|
|
|
|
DECT_HLC_LOCATION_REGISTRATION |
|
|
|
|
DECT_HLC_STANDARD_AUTHENTICATION |
|
|
|
|
DECT_HLC_STANDARD_CIPHERING,
|
2010-09-03 06:50:55 +00:00
|
|
|
.ehlc2 = DECT_EHLC2_LIST_ACCESS_FEATURES,
|
2010-08-16 18:51:29 +00:00
|
|
|
};
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
struct {
|
2010-07-25 14:54:27 +00:00
|
|
|
char cluster[DECTNAMSIZ];
|
2010-01-29 18:08:21 +00:00
|
|
|
struct ast_jb_conf jbconf;
|
|
|
|
char context[AST_MAX_CONTEXT];
|
|
|
|
char language[MAX_LANGUAGE];
|
|
|
|
char regcontext[AST_MAX_CONTEXT];
|
|
|
|
unsigned int regexten_base;
|
|
|
|
char pin[sizeof("00000000")];
|
2010-10-11 15:29:03 +00:00
|
|
|
unsigned int locate_duration;
|
2009-12-16 04:05:47 +00:00
|
|
|
} dect_cfg;
|
|
|
|
|
|
|
|
static AST_LIST_HEAD_STATIC(dect_pt_list, dect_pt);
|
|
|
|
|
|
|
|
struct dect_pt {
|
2010-01-29 18:08:21 +00:00
|
|
|
AST_LIST_ENTRY(dect_pt) list;
|
|
|
|
char name[64];
|
|
|
|
char fullname[64];
|
2010-10-08 20:20:47 +00:00
|
|
|
char ipei[DECT_IPEI_STRING_LEN + 1];
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ipui ipui;
|
|
|
|
struct dect_tpui tpui;
|
|
|
|
|
|
|
|
AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(regexten);
|
|
|
|
AST_STRING_FIELD(context);
|
|
|
|
AST_STRING_FIELD(language);
|
|
|
|
AST_STRING_FIELD(cid_num);
|
|
|
|
AST_STRING_FIELD(cid_name);
|
|
|
|
AST_STRING_FIELD(ring_pattern);
|
2009-12-16 04:05:47 +00:00
|
|
|
);
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
uint8_t uak[DECT_AUTH_KEY_LEN];
|
|
|
|
uint8_t dck[DECT_CIPHER_KEY_LEN];
|
|
|
|
|
|
|
|
struct dect_ie_terminal_capability *terminal_capability;
|
|
|
|
struct dect_ie_codec_list *codec_list;
|
2010-10-11 15:29:03 +00:00
|
|
|
|
|
|
|
int locate_timer;
|
2009-12-16 04:05:47 +00:00
|
|
|
};
|
|
|
|
|
2010-10-11 15:29:03 +00:00
|
|
|
#define dect_pt_log(pt, fmt, args...) \
|
|
|
|
ast_log(LOG_NOTICE, "PT '%s': " fmt, (pt)->name, ## args)
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
struct dect_pvt {
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_pt *pt;
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_endpoint *mme;
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_call *call;
|
|
|
|
struct ast_channel *chan;
|
2010-10-09 02:47:08 +00:00
|
|
|
int timer_id;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct dect_mm_pvt {
|
|
|
|
struct dect_pt *pt;
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_mm_endpoint *mme;
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_call *call;
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *iec;
|
|
|
|
|
|
|
|
/* authentication */
|
2010-10-12 05:34:59 +00:00
|
|
|
enum dect_auth_types {
|
2010-01-29 18:08:21 +00:00
|
|
|
DECT_AUTH_NONE,
|
|
|
|
DECT_AUTH_PT,
|
2010-10-12 05:34:59 +00:00
|
|
|
DECT_AUTH_USER,
|
2010-01-29 18:08:21 +00:00
|
|
|
DECT_AUTH_KEY_ALLOCATION,
|
2010-10-12 05:34:59 +00:00
|
|
|
} auth_type;
|
2010-01-29 18:08:21 +00:00
|
|
|
uint64_t rand;
|
|
|
|
uint64_t rs;
|
|
|
|
uint8_t uak[DECT_AUTH_KEY_LEN];
|
2010-10-09 02:47:08 +00:00
|
|
|
void (*auth_cfm)(struct dect_mm_pvt *, bool,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *);
|
|
|
|
|
|
|
|
/* ciphering */
|
|
|
|
bool ciphered;
|
2010-10-09 02:47:08 +00:00
|
|
|
void (*cipher_cfm)(struct dect_mm_pvt *, bool,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *);
|
2010-10-12 05:34:59 +00:00
|
|
|
|
|
|
|
/* user authentication */
|
2010-10-14 21:07:35 +00:00
|
|
|
uint8_t upi[DECT_UPI_LEN];
|
2010-10-12 05:34:59 +00:00
|
|
|
sem_t userauth_semaphore;
|
|
|
|
bool userauth_status;
|
2009-12-16 04:05:47 +00:00
|
|
|
};
|
|
|
|
|
2010-10-11 15:29:03 +00:00
|
|
|
#define div_round_up(x, y) (((x) + ((y) - 1)) / (y))
|
2010-01-29 18:08:21 +00:00
|
|
|
#define dect_ie_update(pos, ie) \
|
|
|
|
do { \
|
|
|
|
if (ie == NULL) \
|
|
|
|
break; \
|
|
|
|
dect_ie_put(dh, pos); \
|
|
|
|
pos = dect_ie_hold(ie); \
|
|
|
|
} while (0)
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
static void dect_authenticate(struct dect_mm_pvt *pvt, enum dect_auth_types type,
|
2010-10-09 02:47:08 +00:00
|
|
|
void (*auth_cfm)(struct dect_mm_pvt *, bool,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *),
|
|
|
|
struct dect_ie_collection *iec);
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
static void dect_cipher(struct dect_mm_pvt *pvt,
|
|
|
|
void (*auth_cfm)(struct dect_mm_pvt *, bool,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *),
|
|
|
|
struct dect_ie_collection *iec);
|
|
|
|
|
|
|
|
static uint64_t dect_random(void)
|
|
|
|
{
|
|
|
|
return (uint64_t)ast_random() << 32 | ast_random();
|
|
|
|
}
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
static int dect_release_reason_to_ast(enum dect_release_reasons reason)
|
|
|
|
{
|
|
|
|
switch (reason) {
|
|
|
|
case DECT_RELEASE_NORMAL:
|
|
|
|
return AST_CAUSE_NORMAL_CLEARING;
|
|
|
|
case DECT_RELEASE_UNEXPECTED_MESSAGE:
|
|
|
|
case DECT_RELEASE_UNKNOWN_TRANSACTION_IDENTIFIER:
|
|
|
|
return AST_CAUSE_INVALID_MSG_UNSPECIFIED;
|
|
|
|
case DECT_RELEASE_MANDATORY_IE_MISSING:
|
|
|
|
return AST_CAUSE_MANDATORY_IE_MISSING;
|
|
|
|
case DECT_RELEASE_INVALID_IE_CONTENTS:
|
|
|
|
return AST_CAUSE_INVALID_IE_CONTENTS;
|
|
|
|
case DECT_RELEASE_INCOMPATIBLE_SERVICE:
|
|
|
|
case DECT_RELEASE_SERVICE_NOT_IMPLEMENTED:
|
|
|
|
case DECT_RELEASE_NEGOTIATION_NOT_SUPPORTED:
|
|
|
|
case DECT_RELEASE_INVALID_IDENTITY:
|
|
|
|
case DECT_RELEASE_AUTHENTICATION_FAILED:
|
|
|
|
case DECT_RELEASE_UNKNOWN_IDENTITY:
|
|
|
|
case DECT_RELEASE_NEGOTIATION_FAILED:
|
|
|
|
case DECT_RELEASE_TIMER_EXPIRY:
|
|
|
|
case DECT_RELEASE_PARTIAL_RELEASE:
|
|
|
|
case DECT_RELEASE_UNKNOWN:
|
|
|
|
return AST_CAUSE_NORMAL;
|
|
|
|
/* user values */
|
|
|
|
case DECT_RELEASE_USER_DETACHED:
|
|
|
|
case DECT_RELEASE_USER_NOT_IN_RANGE:
|
|
|
|
return AST_CAUSE_SUBSCRIBER_ABSENT;
|
|
|
|
case DECT_RELEASE_USER_UNKNOWN:
|
|
|
|
return AST_CAUSE_NO_ROUTE_DESTINATION;
|
|
|
|
case DECT_RELEASE_USER_ALREADY_ACTIVE:
|
|
|
|
case DECT_RELEASE_USER_BUSY:
|
|
|
|
return AST_CAUSE_USER_BUSY;
|
|
|
|
case DECT_RELEASE_USER_REJECTION:
|
|
|
|
return AST_CAUSE_CALL_REJECTED;
|
|
|
|
case DECT_RELEASE_USER_CALL_MODIFY:
|
|
|
|
/* external handover values */
|
|
|
|
case DECT_RELEASE_EXTERNAL_HANDOVER_NOT_SUPPORTED:
|
|
|
|
case DECT_RELEASE_NETWORK_PARAMETERS_MISSING:
|
|
|
|
case DECT_RELEASE_EXTERNAL_HANDOVER_RELEASE:
|
|
|
|
return AST_CAUSE_NORMAL;
|
|
|
|
/* temporary overload values */
|
|
|
|
case DECT_RELEASE_OVERLOAD:
|
|
|
|
case DECT_RELEASE_INSUFFICIENT_RESOURCES:
|
|
|
|
case DECT_RELEASE_INSUFFICIENT_BEARERS_AVAILABLE:
|
|
|
|
return AST_CAUSE_NORMAL_CIRCUIT_CONGESTION;
|
|
|
|
case DECT_RELEASE_IWU_CONGESTION:
|
|
|
|
return AST_CAUSE_SWITCH_CONGESTION;
|
|
|
|
default:
|
|
|
|
return AST_CAUSE_NORMAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-08-06 14:19:19 +00:00
|
|
|
static enum dect_presentation_indicators dect_ast_presentation_to_dect(int presentation)
|
|
|
|
{
|
|
|
|
presentation &= AST_PRES_NUMBER_TYPE;
|
|
|
|
|
|
|
|
switch (presentation) {
|
|
|
|
default:
|
|
|
|
ast_log(LOG_WARNING, "Unknown presentation coding %x\n", presentation);
|
|
|
|
/* fall through */
|
|
|
|
case AST_PRES_ALLOWED:
|
|
|
|
return DECT_PRESENTATION_ALLOWED;
|
|
|
|
case AST_PRES_RESTRICTED:
|
|
|
|
return DECT_PRESENTATION_RESTRICTED;
|
|
|
|
case AST_PRES_UNAVAILABLE:
|
|
|
|
return DECT_PRESENTATION_NOT_AVAILABLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum dect_screening_indicators dect_ast_screening_to_dect(int screening)
|
|
|
|
{
|
|
|
|
screening &= AST_PRES_RESTRICTION;
|
|
|
|
|
|
|
|
switch (screening) {
|
|
|
|
default:
|
|
|
|
ast_log(LOG_WARNING, "Unknown screening coding %x\n", screening);
|
|
|
|
/* fall through */
|
|
|
|
case AST_PRES_USER_NUMBER_UNSCREENED:
|
|
|
|
return DECT_SCREENING_USER_PROVIDED_NOT_SCREENED;
|
|
|
|
case AST_PRES_USER_NUMBER_PASSED_SCREEN:
|
|
|
|
return DECT_SCREENING_USER_PROVIDED_VERIFIED_PASSED;
|
|
|
|
case AST_PRES_USER_NUMBER_FAILED_SCREEN:
|
|
|
|
return DECT_SCREENING_USER_PROVIDED_VERIFIED_FAILED;
|
|
|
|
case AST_PRES_NETWORK_NUMBER:
|
|
|
|
return DECT_SCREENING_NETWORK_PROVIDED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
static struct dect_pt *dect_pt_get_by_name(const char *name)
|
|
|
|
{
|
|
|
|
struct dect_pt *pt;
|
|
|
|
|
|
|
|
AST_LIST_TRAVERSE(&dect_pt_list, pt, list) {
|
|
|
|
if (!strcasecmp(pt->name, name))
|
|
|
|
return pt;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct dect_pt *dect_pt_get_by_ipui(const struct dect_ipui *ipui)
|
|
|
|
{
|
|
|
|
struct dect_pt *pt;
|
|
|
|
|
|
|
|
AST_LIST_TRAVERSE(&dect_pt_list, pt, list) {
|
|
|
|
if (!dect_ipui_cmp(&pt->ipui, ipui))
|
|
|
|
return pt;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2010-10-08 20:20:47 +00:00
|
|
|
static struct dect_pt *dect_init_portable(const char *name)
|
|
|
|
{
|
|
|
|
struct dect_pt *pt;
|
|
|
|
|
|
|
|
pt = ast_calloc(1, sizeof(*pt));
|
|
|
|
if (pt == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (name != NULL) {
|
|
|
|
ast_copy_string(pt->name, name, sizeof(pt->name));
|
|
|
|
snprintf(pt->fullname, sizeof(pt->fullname), "DECT/%s", pt->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_string_field_init(pt, 512);
|
|
|
|
ast_string_field_set(pt, context, dect_cfg.context);
|
|
|
|
ast_string_field_set(pt, regexten, "");
|
|
|
|
ast_string_field_set(pt, language, dect_cfg.language);
|
|
|
|
ast_string_field_set(pt, cid_num, "");
|
|
|
|
ast_string_field_set(pt, cid_name, "");
|
|
|
|
ast_string_field_set(pt, ring_pattern, "0");
|
2010-10-11 15:29:03 +00:00
|
|
|
pt->locate_timer = -1;
|
2010-10-08 20:20:47 +00:00
|
|
|
|
|
|
|
AST_LIST_INSERT_TAIL(&dect_pt_list, pt, list);
|
|
|
|
return pt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_alloc_extension(unsigned int *exten)
|
|
|
|
{
|
|
|
|
const struct dect_pt *pt;
|
|
|
|
uint32_t bitmap[1024];
|
|
|
|
unsigned int pext;
|
|
|
|
|
|
|
|
memset(bitmap, 0, sizeof(bitmap));
|
|
|
|
AST_LIST_TRAVERSE(&dect_pt_list, pt, list) {
|
|
|
|
pext = strtoul(pt->regexten, NULL, 0);
|
|
|
|
if (pext < dect_cfg.regexten_base ||
|
|
|
|
pext >= 8 * sizeof(bitmap))
|
|
|
|
continue;
|
|
|
|
pext -= dect_cfg.regexten_base;
|
|
|
|
|
|
|
|
bitmap[pext / 32] |= 1 << (pext % 32);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (pext = 0; pext < 8 * sizeof(bitmap); pext++) {
|
|
|
|
if (!(bitmap[pext / 32] & (1 << (pext % 32)))) {
|
|
|
|
*exten = dect_cfg.regexten_base + pext;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_register_extension(const struct dect_pt *pt, bool onoff)
|
|
|
|
{
|
|
|
|
struct pbx_find_info q = { .stacklen = 0 };
|
|
|
|
char *ext, *extenp, *context;
|
|
|
|
char exten[256];
|
|
|
|
|
|
|
|
if (ast_strlen_zero(dect_cfg.regcontext))
|
|
|
|
return;
|
|
|
|
|
|
|
|
ast_copy_string(exten, S_OR(pt->regexten, pt->name), sizeof(exten));
|
|
|
|
extenp = exten;
|
|
|
|
while ((ext = strsep(&extenp, "&")) != NULL) {
|
|
|
|
context = strchr(ext, '@');
|
|
|
|
if (context != NULL) {
|
|
|
|
*context++ = '\0';
|
|
|
|
if (!ast_context_find(context)) {
|
|
|
|
ast_log(LOG_WARNING, "Context %s for PT %s does not exist",
|
|
|
|
context, pt->name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
context = dect_cfg.regcontext;
|
|
|
|
|
|
|
|
if (onoff) {
|
|
|
|
if (!ast_exists_extension(NULL, context, ext, 1, NULL)) {
|
|
|
|
ast_add_extension(context, 1, ext, 1, NULL, NULL, "Dial",
|
|
|
|
(char *)pt->fullname, NULL, "DECT");
|
|
|
|
}
|
|
|
|
} else if (pbx_find_extension(NULL, NULL, &q, context, ext, 1,
|
|
|
|
NULL, "", E_MATCH)) {
|
|
|
|
ast_context_remove_extension(context, ext, 1, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_init_tpui(struct dect_tpui *tpui, unsigned int exten)
|
|
|
|
{
|
2010-10-11 16:54:28 +00:00
|
|
|
tpui->ia.digits[0] = exten >= 10000 ? exten / 10000 % 10 : 0xb;
|
|
|
|
tpui->ia.digits[1] = exten >= 1000 ? exten / 1000 % 10 : 0xb;
|
|
|
|
tpui->ia.digits[2] = exten >= 100 ? exten / 100 % 10 : 0xb;
|
|
|
|
tpui->ia.digits[3] = exten >= 10 ? exten / 10 % 10 : 0xb;
|
|
|
|
tpui->ia.digits[4] = exten >= 1 ? exten / 1 % 10 : 0xb;
|
2010-10-08 20:20:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_pt_set_extension(struct dect_pt *pt, unsigned int exten)
|
|
|
|
{
|
|
|
|
ast_string_field_build(pt, cid_num, "%u", exten);
|
|
|
|
ast_string_field_build(pt, regexten, "%u", exten);
|
|
|
|
dect_init_tpui(&pt->tpui, exten);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_pt_set_ipui(struct dect_pt *pt, const struct dect_ipui *ipui)
|
|
|
|
{
|
|
|
|
pt->ipui = *ipui;
|
|
|
|
dect_format_ipei_string(&ipui->pun.n.ipei, pt->ipei);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Database functions
|
|
|
|
*/
|
2010-10-08 13:37:02 +00:00
|
|
|
static uint64_t dect_parse_num(const char *str)
|
|
|
|
{
|
|
|
|
uint64_t num;
|
|
|
|
|
|
|
|
if (sscanf(str, "%" PRIx64, &num) != 1)
|
|
|
|
ast_log(LOG_WARNING, "Could not parse numeric value '%s'\n", str);
|
|
|
|
|
|
|
|
return num;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_parse_base64(const char *str, uint8_t *dst, unsigned int size)
|
|
|
|
{
|
|
|
|
ast_base64decode(dst, str, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_db_remove(const struct dect_pt *pt)
|
|
|
|
{
|
2010-10-08 20:20:47 +00:00
|
|
|
ast_db_deltree("dect", pt->ipei);
|
2010-10-08 13:37:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_db_put(const struct dect_pt *pt, const char *key, const char *value)
|
|
|
|
{
|
|
|
|
char ptkey[256];
|
|
|
|
|
2010-10-08 20:20:47 +00:00
|
|
|
snprintf(ptkey, sizeof(ptkey), "%s/%s", pt->ipei, key);
|
2010-10-08 13:37:02 +00:00
|
|
|
return ast_db_put("dect", ptkey, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_db_put_numbered(const struct dect_pt *pt, const char *key,
|
|
|
|
unsigned int index, const char *value)
|
|
|
|
{
|
|
|
|
char nkey[256];
|
|
|
|
|
2010-10-09 05:59:09 +00:00
|
|
|
snprintf(nkey, sizeof(nkey), key, index);
|
2010-10-08 13:37:02 +00:00
|
|
|
return dect_db_put(pt, nkey, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_db_put_base64(const struct dect_pt *pt, const char *key,
|
|
|
|
const void *data, unsigned int len)
|
|
|
|
{
|
|
|
|
char value[4 * len + 4];
|
|
|
|
|
|
|
|
ast_base64encode(value, data, len, sizeof(value));
|
|
|
|
return dect_db_put(pt, key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_db_put_num(const struct dect_pt *pt, const char *key,
|
|
|
|
uint64_t num)
|
|
|
|
{
|
|
|
|
char value[sizeof("ffffffffffffffff")];
|
|
|
|
|
|
|
|
snprintf(value, sizeof(value), "%" PRIx64, num);
|
|
|
|
return dect_db_put(pt, key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_db_put_numbered_num(const struct dect_pt *pt, const char *key,
|
|
|
|
unsigned int index, uint64_t num)
|
|
|
|
{
|
|
|
|
char value[sizeof("ffffffffffffffff")];
|
|
|
|
|
|
|
|
snprintf(value, sizeof(value), "%" PRIx64, num);
|
|
|
|
return dect_db_put_numbered(pt, key, index, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_db_parse_codec_list(struct dect_pt *pt, const char *key,
|
|
|
|
const char *val)
|
|
|
|
{
|
|
|
|
struct dect_ie_codec_list *cl;
|
|
|
|
unsigned int i;
|
|
|
|
|
2010-10-15 04:04:21 +00:00
|
|
|
if (strncmp(key, "codec_list/", strlen("codec_list/")))
|
|
|
|
return -1;
|
2010-10-08 13:37:02 +00:00
|
|
|
key += strlen("codec_list/");
|
|
|
|
|
|
|
|
cl = pt->codec_list;
|
|
|
|
if (cl == NULL) {
|
|
|
|
pt->codec_list = (void *)dect_ie_alloc(dh, sizeof(*cl));
|
|
|
|
cl = pt->codec_list;
|
|
|
|
}
|
|
|
|
|
2010-10-09 05:59:09 +00:00
|
|
|
if (sscanf(key, "%u/", &i) != 1)
|
2010-10-08 20:20:47 +00:00
|
|
|
return -1;
|
2010-10-09 05:59:09 +00:00
|
|
|
key = strchr(key, '/') + 1;
|
2010-10-08 13:37:02 +00:00
|
|
|
|
2010-10-09 05:59:09 +00:00
|
|
|
if (!strcmp(key, "codec"))
|
2010-10-08 13:37:02 +00:00
|
|
|
cl->entry[i].codec = dect_parse_num(val);
|
2010-10-09 05:59:09 +00:00
|
|
|
else if (!strcmp(key, "service"))
|
2010-10-08 13:37:02 +00:00
|
|
|
cl->entry[i].service = dect_parse_num(val);
|
2010-10-09 05:59:09 +00:00
|
|
|
else if (!strcmp(key, "slot"))
|
2010-10-08 13:37:02 +00:00
|
|
|
cl->entry[i].slot = dect_parse_num(val);
|
2010-10-09 05:59:09 +00:00
|
|
|
else if (!strcmp(key, "cplane"))
|
2010-10-08 13:37:02 +00:00
|
|
|
cl->entry[i].cplane = dect_parse_num(val);
|
|
|
|
else
|
|
|
|
return -1;
|
2010-10-09 02:51:56 +00:00
|
|
|
|
|
|
|
if (i + 1 > cl->num)
|
|
|
|
cl->num = i + 1;
|
2010-10-08 13:37:02 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_db_store_codec_list(const struct dect_pt *pt)
|
|
|
|
{
|
2010-10-08 20:20:47 +00:00
|
|
|
const struct dect_ie_codec_list *cl = pt->codec_list;
|
2010-10-08 13:37:02 +00:00
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < cl->num; i++) {
|
2010-10-09 05:59:09 +00:00
|
|
|
dect_db_put_numbered_num(pt, "codec_list/%u/codec", i, cl->entry[i].codec);
|
|
|
|
dect_db_put_numbered_num(pt, "codec_list/%u/service", i, cl->entry[i].service);
|
|
|
|
dect_db_put_numbered_num(pt, "codec_list/%u/slot", i, cl->entry[i].slot);
|
|
|
|
dect_db_put_numbered_num(pt, "codec_list/%u/cplane", i, cl->entry[i].cplane);
|
2010-10-08 13:37:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_db_parse_termcap(struct dect_pt *pt, const char *key,
|
|
|
|
const char *val)
|
|
|
|
{
|
2010-10-08 20:20:47 +00:00
|
|
|
struct dect_ie_terminal_capability *tc;
|
2010-10-08 13:37:02 +00:00
|
|
|
|
|
|
|
if (strncmp(key, "capabilities/", strlen("capabilities/")))
|
|
|
|
return -1;
|
|
|
|
key += strlen("capabilities/");
|
|
|
|
|
2010-10-08 20:20:47 +00:00
|
|
|
tc = pt->terminal_capability;
|
|
|
|
if (tc == NULL) {
|
|
|
|
pt->terminal_capability = (void *)dect_ie_alloc(dh, sizeof(*tc));
|
|
|
|
tc = pt->terminal_capability;
|
2010-10-08 13:37:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(key, "tone_capabilities"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->tone = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "echo_parameters"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->echo = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "noise_rejection"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->noise_rejection = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "volume_ctrl"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->volume_ctrl = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "slot_capabilities"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->slot = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "display_capabilities"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->display = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "display_memory"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->display_memory = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "display_lines"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->display_lines = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "display_columns"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->display_columns = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "display_control"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->display_control = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "display_charsets"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->display_charsets = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "scrolling"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->scrolling = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "profile_indicator"))
|
2010-10-08 20:20:47 +00:00
|
|
|
tc->profile_indicator = dect_parse_num(val);
|
2010-10-08 13:37:02 +00:00
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_db_store_termcap(const struct dect_pt *pt)
|
|
|
|
{
|
2010-10-08 20:20:47 +00:00
|
|
|
const struct dect_ie_terminal_capability *tc = pt->terminal_capability;
|
2010-10-08 13:37:02 +00:00
|
|
|
|
|
|
|
dect_db_put_num(pt, "capabilities/tone_capabilities", tc->tone);
|
|
|
|
dect_db_put_num(pt, "capabilities/echo_parameters", tc->echo);
|
|
|
|
dect_db_put_num(pt, "capabilities/noise_rejection", tc->noise_rejection);
|
|
|
|
dect_db_put_num(pt, "capabilities/volume_ctrl", tc->volume_ctrl);
|
|
|
|
dect_db_put_num(pt, "capabilities/slot_capabilities", tc->slot);
|
|
|
|
dect_db_put_num(pt, "capabilities/display_capabilities", tc->display);
|
|
|
|
dect_db_put_num(pt, "capabilities/display_memory", tc->display_memory);
|
|
|
|
dect_db_put_num(pt, "capabilities/display_lines", tc->display_lines);
|
|
|
|
dect_db_put_num(pt, "capabilities/display_columns", tc->display_columns);
|
|
|
|
dect_db_put_num(pt, "capabilities/display_control", tc->display_control);
|
|
|
|
dect_db_put_num(pt, "capabilities/display_charsets", tc->display_charsets);
|
|
|
|
dect_db_put_num(pt, "capabilities/scrolling", tc->scrolling);
|
|
|
|
dect_db_put_num(pt, "capabilities/profile_indicator", tc->profile_indicator);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_db_parse_global(struct dect_pt *pt, const char *key,
|
|
|
|
const char *val)
|
|
|
|
{
|
2010-10-08 20:20:47 +00:00
|
|
|
if (!strcmp(key, "name")) {
|
|
|
|
ast_copy_string(pt->name, val, sizeof(pt->name));
|
|
|
|
snprintf(pt->fullname, sizeof(pt->fullname), "DECT/%s", pt->name);
|
2010-10-08 13:37:02 +00:00
|
|
|
} else if (!strcmp(key, "regexten"))
|
2010-10-08 20:20:47 +00:00
|
|
|
dect_pt_set_extension(pt, strtoul(val, NULL, 0));
|
2010-10-08 13:37:02 +00:00
|
|
|
else if (!strcmp(key, "context"))
|
|
|
|
ast_string_field_set(pt, context, val);
|
|
|
|
else if (!strcmp(key, "language"))
|
|
|
|
ast_string_field_set(pt, language, val);
|
|
|
|
else if (!strcmp(key, "cid_num"))
|
|
|
|
ast_string_field_set(pt, cid_num, val);
|
|
|
|
else if (!strcmp(key, "cid_name"))
|
|
|
|
ast_string_field_set(pt, cid_name, val);
|
|
|
|
else if (!strcmp(key, "ring_pattern"))
|
|
|
|
ast_string_field_set(pt, ring_pattern, val);
|
|
|
|
else if (!strcmp(key, "uak"))
|
|
|
|
dect_parse_base64(val, pt->uak, sizeof(pt->uak));
|
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_db_store_portable(const struct dect_pt *pt)
|
|
|
|
{
|
2010-10-08 20:20:47 +00:00
|
|
|
dect_db_put(pt, "name", pt->name);
|
2010-10-08 13:37:02 +00:00
|
|
|
dect_db_put(pt, "regexten", pt->regexten);
|
|
|
|
dect_db_put(pt, "context", pt->context);
|
|
|
|
dect_db_put(pt, "language", pt->language);
|
|
|
|
dect_db_put(pt, "cid_num", pt->cid_num);
|
|
|
|
dect_db_put(pt, "cid_name", pt->cid_name);
|
|
|
|
dect_db_put(pt, "ring_pattern", pt->ring_pattern);
|
|
|
|
dect_db_put_base64(pt, "uak", pt->uak, sizeof(pt->uak));
|
|
|
|
}
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
static int dect_init_call(struct dect_pvt *pvt, enum ast_channel_state state,
|
|
|
|
const char *exten)
|
|
|
|
{
|
|
|
|
struct dect_pt *pt = pvt->pt;
|
|
|
|
struct ast_channel *chan;
|
|
|
|
|
|
|
|
chan = ast_channel_alloc(1, state, pt->cid_num, pt->cid_name, "",
|
|
|
|
exten, pt->context, 0, 0,
|
|
|
|
"DECT/%s-%08lx", pt->name, (long)pvt);
|
|
|
|
if (chan == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
chan->tech = &dect_tech;
|
|
|
|
chan->nativeformats = AST_FORMAT_G726_AAL2;
|
|
|
|
chan->readformat = AST_FORMAT_G726_AAL2;
|
|
|
|
chan->rawreadformat = AST_FORMAT_G726_AAL2;
|
|
|
|
chan->writeformat = AST_FORMAT_G726_AAL2;
|
|
|
|
chan->rawwriteformat = AST_FORMAT_G726_AAL2;
|
|
|
|
chan->tech_pvt = pvt;
|
|
|
|
|
|
|
|
if (exten != NULL)
|
|
|
|
ast_copy_string(chan->exten, exten, sizeof(chan->exten));
|
|
|
|
if (!ast_strlen_zero(pt->language))
|
|
|
|
ast_string_field_set(chan, language, pt->language);
|
|
|
|
|
|
|
|
ast_jb_configure(chan, &dect_cfg.jbconf);
|
|
|
|
|
|
|
|
ast_module_ref(ast_module_info->self);
|
|
|
|
pvt->chan = chan;
|
|
|
|
|
|
|
|
if (state != AST_STATE_RESERVED && state != AST_STATE_DIALING) {
|
|
|
|
if (ast_pbx_start(chan)) {
|
|
|
|
ast_log(LOG_WARNING, "Unable to start PBX\n");
|
|
|
|
ast_hangup(chan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_try_to_connect_call(struct dect_pvt *pvt)
|
|
|
|
{
|
|
|
|
struct ast_channel *chan = pvt->chan;
|
|
|
|
struct dect_pt *pt = pvt->pt;
|
|
|
|
|
|
|
|
if (!ast_exists_extension(NULL, pt->context, chan->exten, 1, NULL))
|
|
|
|
return -1;
|
2010-10-11 15:29:03 +00:00
|
|
|
dect_pt_log(pt, "connecting call\n");
|
2009-12-16 04:05:47 +00:00
|
|
|
if (ast_pbx_start(chan)) {
|
|
|
|
ast_log(LOG_WARNING, "Unable to start PBX\n");
|
|
|
|
ast_hangup(chan);
|
|
|
|
} else
|
|
|
|
ast_setstate(chan, AST_STATE_RING);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_answer(struct ast_channel *chan)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = chan->tech_pvt;
|
2010-10-11 15:29:03 +00:00
|
|
|
struct dect_pt *pt = pvt->pt;
|
2009-12-16 04:05:47 +00:00
|
|
|
struct dect_mncc_connect_param connect = {};
|
|
|
|
|
2010-10-11 15:29:03 +00:00
|
|
|
dect_pt_log(pt, "answer call state %u\n", chan->_state);
|
2009-12-16 04:05:47 +00:00
|
|
|
if (chan->_state == AST_STATE_UP || chan->_state == AST_STATE_RINGING)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
dect_mncc_connect_req(dh, pvt->call, &connect);
|
|
|
|
ast_setstate(chan, AST_STATE_UP);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
static void dect_call_auth_cfm(struct dect_mm_pvt *mmp, bool success,
|
|
|
|
struct dect_ie_collection *iec)
|
2009-12-16 04:05:47 +00:00
|
|
|
{
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_call *call = mmp->call;
|
|
|
|
struct dect_pvt *pvt = dect_call_priv(call);
|
|
|
|
struct ast_channel *chan = pvt->chan;
|
2010-08-06 14:19:19 +00:00
|
|
|
struct dect_ie_basic_service basic_service;
|
|
|
|
struct dect_ie_calling_party_number calling_party_number;
|
|
|
|
struct dect_ie_calling_party_name calling_party_name;
|
2009-12-16 04:05:47 +00:00
|
|
|
struct dect_mncc_setup_param param;
|
2010-08-06 14:19:19 +00:00
|
|
|
int presentation;
|
2009-12-16 04:05:47 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
if (!success)
|
|
|
|
return;
|
|
|
|
|
2010-08-06 14:19:19 +00:00
|
|
|
basic_service.class = DECT_CALL_CLASS_NORMAL;
|
|
|
|
basic_service.service = DECT_SERVICE_BASIC_SPEECH_DEFAULT;
|
|
|
|
|
2010-10-11 17:23:36 +00:00
|
|
|
presentation = ast_party_id_presentation(&chan->connected.id);
|
|
|
|
if (chan->connected.id.number.valid) {
|
|
|
|
calling_party_number.type = DECT_NUMBER_TYPE_UNKNOWN;
|
|
|
|
calling_party_number.npi = DECT_NPI_UNKNOWN;
|
|
|
|
calling_party_number.presentation = dect_ast_presentation_to_dect(presentation);
|
|
|
|
calling_party_number.screening = dect_ast_screening_to_dect(presentation);
|
|
|
|
calling_party_number.len = strlen(chan->connected.id.number.str);
|
|
|
|
memcpy(calling_party_number.address, chan->connected.id.number.str,
|
|
|
|
calling_party_number.len);
|
|
|
|
} else
|
|
|
|
calling_party_number.len = 0;
|
|
|
|
|
|
|
|
if (chan->connected.id.name.valid) {
|
|
|
|
calling_party_name.presentation = dect_ast_presentation_to_dect(presentation);
|
|
|
|
calling_party_name.screening = dect_ast_screening_to_dect(presentation);
|
|
|
|
calling_party_name.alphabet = DECT_ALPHABET_STANDARD;
|
|
|
|
calling_party_name.len = strlen(chan->connected.id.name.str);
|
|
|
|
memcpy(calling_party_name.name, chan->connected.id.name.str,
|
|
|
|
calling_party_name.len);
|
|
|
|
} else
|
|
|
|
calling_party_name.len = 0;
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
memset(¶m, 0, sizeof(param));
|
2010-08-06 14:19:19 +00:00
|
|
|
param.basic_service = &basic_service;
|
|
|
|
if (calling_party_number.len)
|
|
|
|
param.calling_party_number = &calling_party_number;
|
|
|
|
if (calling_party_name.len)
|
|
|
|
param.calling_party_name = &calling_party_name;
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
dect_mncc_setup_req(dh, pvt->call, &pvt->pt->ipui, ¶m);
|
2010-10-09 02:47:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_call(struct ast_channel *chan, char *dest, int timeout)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = chan->tech_pvt;
|
|
|
|
struct dect_mm_pvt *mmp = dect_mm_priv(pvt->mme);
|
|
|
|
|
|
|
|
dect_cipher(mmp, dect_call_auth_cfm, NULL);
|
2009-12-16 04:05:47 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
ast_setstate(chan, AST_STATE_DIALING);
|
2009-12-16 04:05:47 +00:00
|
|
|
chan->hangupcause = AST_CAUSE_NORMAL_CLEARING;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ast_channel *dect_request_call(const char *type, format_t format,
|
|
|
|
const struct ast_channel *requestor,
|
|
|
|
void *data, int *cause)
|
|
|
|
{
|
|
|
|
struct dect_call *call;
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_pvt *pvt;
|
|
|
|
struct dect_mm_endpoint *mme;
|
|
|
|
struct dect_mm_pvt *mmp;
|
2009-12-16 04:05:47 +00:00
|
|
|
struct dect_pt *pt;
|
|
|
|
const char *name = data;
|
|
|
|
|
|
|
|
pt = dect_pt_get_by_name(name);
|
|
|
|
if (pt == NULL) {
|
|
|
|
ast_log(LOG_NOTICE, "Call to unknown PT '%s' requested\n", name);
|
2010-04-01 06:15:01 +00:00
|
|
|
*cause = AST_CAUSE_UNALLOCATED;
|
2009-12-16 04:05:47 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
2010-10-11 15:29:03 +00:00
|
|
|
dect_pt_log(pt, "outgoing call\n");
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
call = dect_call_alloc(dh);
|
2010-04-01 06:15:01 +00:00
|
|
|
if (call == NULL) {
|
|
|
|
*cause = AST_CAUSE_SWITCH_CONGESTION;
|
2009-12-16 04:05:47 +00:00
|
|
|
return NULL;
|
2010-04-01 06:15:01 +00:00
|
|
|
}
|
2009-12-16 04:05:47 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
mme = dect_mm_endpoint_get(dh, &pt->ipui);
|
|
|
|
if (mme == NULL) {
|
|
|
|
*cause = AST_CAUSE_SWITCH_CONGESTION;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
mmp = dect_mm_priv(mme);
|
|
|
|
mmp->pt = pt;
|
|
|
|
mmp->mme = mme;
|
|
|
|
mmp->call = call;
|
|
|
|
|
|
|
|
pvt = dect_call_priv(call);
|
2009-12-16 04:05:47 +00:00
|
|
|
pvt->pt = pt;
|
2010-10-09 02:47:08 +00:00
|
|
|
pvt->mme = mme;
|
2009-12-16 04:05:47 +00:00
|
|
|
pvt->call = call;
|
2010-04-01 06:15:01 +00:00
|
|
|
if (dect_init_call(pvt, AST_STATE_RESERVED, NULL) < 0) {
|
|
|
|
*cause = AST_CAUSE_SWITCH_CONGESTION;
|
2009-12-16 04:05:47 +00:00
|
|
|
return NULL;
|
2010-04-01 06:15:01 +00:00
|
|
|
}
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
return pvt->chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_hangup(struct ast_channel *chan)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = chan->tech_pvt;
|
|
|
|
struct dect_ie_release_reason release_reason;
|
|
|
|
struct dect_mncc_release_param param;
|
|
|
|
|
|
|
|
if (chan->_state != AST_STATE_DOWN) {
|
2010-10-11 15:29:03 +00:00
|
|
|
dect_pt_log(pvt->pt, "hangup\n");
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
dect_ie_init(&release_reason);
|
|
|
|
release_reason.reason = DECT_RELEASE_NORMAL;
|
|
|
|
|
|
|
|
memset(¶m, 0, sizeof(param));
|
|
|
|
param.release_reason = &release_reason;
|
|
|
|
dect_mncc_release_req(dh, pvt->call, ¶m);
|
|
|
|
}
|
|
|
|
|
|
|
|
chan->tech_pvt = NULL;
|
|
|
|
ast_setstate(chan, AST_STATE_DOWN);
|
2010-09-10 13:03:01 +00:00
|
|
|
ast_module_unref(ast_module_info->self);
|
2009-12-16 04:05:47 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_indicate(struct ast_channel *chan, int condition,
|
|
|
|
const void *data, size_t datalen)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = chan->tech_pvt;
|
2010-10-11 15:29:03 +00:00
|
|
|
struct dect_pt *pt = pvt->pt;
|
2009-12-16 04:05:47 +00:00
|
|
|
struct dect_mncc_alert_param alert;
|
|
|
|
struct dect_ie_progress_indicator progress;
|
|
|
|
struct dect_ie_signal signal;
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
switch (condition) {
|
|
|
|
case AST_CONTROL_RINGING:
|
2010-10-11 15:29:03 +00:00
|
|
|
dect_pt_log(pt, "call is ringing\n");
|
2009-12-16 04:05:47 +00:00
|
|
|
memset(&alert, 0, sizeof(alert));
|
|
|
|
if (0) {
|
|
|
|
alert.signal = dect_signal_init(&signal, DECT_SIGNAL_RING_BACK_TONE_ON);
|
|
|
|
} else {
|
|
|
|
dect_ie_init(&progress);
|
|
|
|
progress.location = DECT_LOCATION_PRIVATE_NETWORK_SERVING_LOCAL_USER;
|
|
|
|
progress.progress = DECT_PROGRESS_INBAND_INFORMATION_NOW_AVAILABLE;
|
|
|
|
dect_ie_list_add(&progress, &alert.progress_indicator);
|
|
|
|
}
|
|
|
|
dect_mncc_alert_req(dh, pvt->call, &alert);
|
|
|
|
ast_setstate(chan, AST_STATE_RINGING);
|
|
|
|
res = -1;
|
|
|
|
break;
|
|
|
|
case AST_CONTROL_BUSY:
|
|
|
|
case AST_CONTROL_CONGESTION:
|
|
|
|
case AST_CONTROL_PROCEEDING:
|
|
|
|
case AST_CONTROL_PROGRESS:
|
|
|
|
case AST_CONTROL_HOLD:
|
|
|
|
case AST_CONTROL_UNHOLD:
|
|
|
|
case AST_CONTROL_VIDUPDATE:
|
|
|
|
case AST_CONTROL_T38_PARAMETERS:
|
|
|
|
case AST_CONTROL_SRCUPDATE:
|
|
|
|
case -1:
|
|
|
|
res = -1;
|
2010-10-11 15:29:03 +00:00
|
|
|
dect_pt_log(pt, "indicate condition %d\n", condition);
|
2009-12-16 04:05:47 +00:00
|
|
|
break;
|
|
|
|
default:
|
2010-10-11 15:29:03 +00:00
|
|
|
dect_pt_log(pt, "indicate unknown condition %d\n", condition);
|
2009-12-16 04:05:47 +00:00
|
|
|
res = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_write(struct ast_channel *chan, struct ast_frame *f)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = chan->tech_pvt;
|
|
|
|
struct dect_msg_buf mb;
|
|
|
|
|
|
|
|
mb.data = f->data.ptr;
|
2010-09-09 21:15:41 +00:00
|
|
|
mb.len = f->datalen;
|
|
|
|
|
|
|
|
dect_dl_u_data_req(dh, pvt->call, &mb);
|
2009-12-16 04:05:47 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ast_frame *dect_read(struct ast_channel *chan)
|
|
|
|
{
|
|
|
|
ast_log(LOG_NOTICE, "read\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_fixup(struct ast_channel *old, struct ast_channel *new)
|
|
|
|
{
|
|
|
|
ast_log(LOG_NOTICE, "fixup\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
static int dect_send_text(struct ast_channel *chan, const char *text)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = chan->tech_pvt;
|
|
|
|
struct dect_ie_display display;
|
|
|
|
struct dect_mncc_info_param info = { .display = &display };
|
|
|
|
|
|
|
|
dect_display_init(&display);
|
|
|
|
dect_display_append_char(&display, DECT_C_CLEAR_DISPLAY);
|
|
|
|
dect_display_append(&display, text, strlen(text));
|
|
|
|
|
|
|
|
dect_mncc_info_req(dh, pvt->call, &info);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
static const struct ast_channel_tech dect_tech = {
|
|
|
|
.type = "DECT",
|
|
|
|
.description = "Digital Enhanced Cordless Telecommunications (DECT)",
|
|
|
|
.capabilities = AST_FORMAT_G726_AAL2,
|
|
|
|
.answer = dect_answer,
|
|
|
|
.requester = dect_request_call,
|
|
|
|
.call = dect_call,
|
|
|
|
.hangup = dect_hangup,
|
|
|
|
.indicate = dect_indicate,
|
|
|
|
.write = dect_write,
|
|
|
|
.read = dect_read,
|
|
|
|
.fixup = dect_fixup,
|
2010-01-29 18:08:21 +00:00
|
|
|
.send_text = dect_send_text,
|
2009-12-16 04:05:47 +00:00
|
|
|
};
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
static void dect_mncc_reject(struct dect_call *call, enum dect_release_reasons reason)
|
2009-12-16 04:05:47 +00:00
|
|
|
{
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_release_reason release_reason = { .reason = reason };
|
|
|
|
struct dect_mncc_release_param release = { .release_reason = &release_reason };
|
|
|
|
|
|
|
|
dect_mncc_reject_req(dh, call, &release);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct {
|
|
|
|
const char *name;
|
|
|
|
unsigned int priority;
|
|
|
|
} dect_codecs[] = {
|
|
|
|
[DECT_CODEC_USER_SPECIFIC_32KBIT] = { "user specific 32kbit", 0 },
|
|
|
|
[DECT_CODEC_G726_32KBIT] = { "G.726", 1 },
|
|
|
|
[DECT_CODEC_G722_64KBIT] = { "G.722", 2 },
|
|
|
|
[DECT_CODEC_G711_ALAW_64KBIT] = { "G.711 alaw", 0 },
|
|
|
|
[DECT_CODEC_G711_ULAW_64KBIT] = { "G.711 ulaw", 0 },
|
|
|
|
[DECT_CODEC_G729_1_32KBIT] = { "G.729.1", 0 },
|
|
|
|
[DECT_CODEC_MPEG4_ER_AAC_LD_32KBIT] = { "MPEG-4 ER AAC-LD 32kbit", 0 },
|
|
|
|
[DECT_CODEC_MPEG4_ER_AAC_LD_64KBIT] = { "MPEG-4 ER AAC-LD 64kbit", 0 },
|
|
|
|
[DECT_CODEC_USER_SPECIFIC_64KBIT] = { "user specific 64kbit", 0 },
|
|
|
|
};
|
|
|
|
|
|
|
|
static char *dect_codecs_merge(char *buf, size_t size, const struct dect_pt *pt)
|
|
|
|
{
|
|
|
|
struct dect_ie_codec_list *codec_list = pt->codec_list;
|
|
|
|
const char *sep = "";
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
if (codec_list == NULL) {
|
|
|
|
strcat(buf, dect_codecs[DECT_CODEC_G726_32KBIT].name);
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < codec_list->num; i++) {
|
|
|
|
strcat(buf, sep);
|
|
|
|
strcat(buf, dect_codecs[codec_list->entry[i].codec].name);
|
|
|
|
sep = ", ";
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct dect_ie_codec_list *dect_select_codec(const struct dect_pt *pt,
|
|
|
|
struct dect_ie_codec_list *codec_list)
|
|
|
|
{
|
|
|
|
unsigned int i, prio, best = 0, n = 0;
|
|
|
|
|
|
|
|
if (pt->codec_list == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (i = 0; i < pt->codec_list->num; i++) {
|
|
|
|
prio = dect_codecs[pt->codec_list->entry[i].codec].priority;
|
|
|
|
if (prio > best) {
|
|
|
|
best = prio;
|
|
|
|
n = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
codec_list->negotiation = DECT_NEGOTIATION_CODEC;
|
|
|
|
codec_list->num = 1;
|
|
|
|
codec_list->entry[0] = pt->codec_list->entry[n];
|
|
|
|
|
|
|
|
return codec_list;
|
|
|
|
}
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
static void dect_mncc_setup_auth_cfm(struct dect_mm_pvt *mmp, bool success,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *iec)
|
|
|
|
{
|
|
|
|
struct dect_mncc_setup_param *param = (void *)iec;
|
2009-12-16 04:05:47 +00:00
|
|
|
struct dect_mncc_setup_ack_param setup_ack;
|
|
|
|
struct dect_mncc_call_proc_param call_proc;
|
|
|
|
struct dect_ie_delimiter_request delimiter_request;
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_codec_list codec_list;
|
2009-12-16 04:05:47 +00:00
|
|
|
struct dect_ie_signal signal;
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_pt *pt = mmp->pt;
|
|
|
|
struct dect_call *call = mmp->call;
|
|
|
|
struct dect_pvt *pvt = dect_call_priv(call);
|
2009-12-16 04:05:47 +00:00
|
|
|
enum ast_channel_state state;
|
|
|
|
const char *exten;
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
if (!success)
|
|
|
|
return dect_mncc_reject(call, DECT_RELEASE_AUTHENTICATION_FAILED);
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
/* If the phone supplies the entire number en-bloc, enter call
|
|
|
|
* proceeding state. A number is considered complete when contained
|
|
|
|
* in a called party number IE, or a multi-keypad IE and the extension
|
|
|
|
* exists. When no number is supplied and an 's' extension exists in
|
|
|
|
* the context, the call is also moved to call proceeding state and
|
|
|
|
* further decisions are left to the PBX.
|
|
|
|
*
|
|
|
|
* Otherwise enter overlap sending and wait for further information.
|
|
|
|
*/
|
|
|
|
state = AST_STATE_RING;
|
|
|
|
exten = NULL;
|
|
|
|
|
|
|
|
if (param->called_party_number != NULL)
|
|
|
|
; //ast_copy_string(exten, param->called_party_number.info, sizeof(exten));
|
|
|
|
else if (param->keypad != NULL && param->keypad->info[0] < 0x80)
|
|
|
|
exten = (char *)param->keypad->info;
|
|
|
|
else {
|
|
|
|
exten = "s";
|
|
|
|
if (!ast_exists_extension(NULL, pt->context, exten, 1, NULL)) {
|
|
|
|
state = AST_STATE_DIALING;
|
|
|
|
exten = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state == AST_STATE_RING) {
|
|
|
|
memset(&call_proc, 0, sizeof(call_proc));
|
2010-01-29 18:08:21 +00:00
|
|
|
call_proc.codec_list = dect_select_codec(pt, &codec_list);
|
2009-12-16 04:05:47 +00:00
|
|
|
dect_mncc_call_proc_req(dh, call, &call_proc);
|
|
|
|
} else {
|
|
|
|
dect_ie_init(&delimiter_request);
|
|
|
|
dect_signal_init(&signal, DECT_SIGNAL_DIAL_TONE_ON);
|
|
|
|
|
|
|
|
memset(&setup_ack, 0, sizeof(setup_ack));
|
|
|
|
setup_ack.delimiter_request = &delimiter_request;
|
|
|
|
setup_ack.signal = &signal;
|
|
|
|
dect_mncc_setup_ack_req(dh, call, &setup_ack);
|
|
|
|
}
|
|
|
|
|
|
|
|
dect_init_call(pvt, state, exten);
|
|
|
|
|
|
|
|
/* WTF? Asterisk eats (actually leaks) the first queued frame when
|
|
|
|
* answering a channel. Give it something to eat :| */
|
|
|
|
if (state == AST_STATE_DIALING)
|
|
|
|
ast_queue_control(pvt->chan, -1);
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mncc_setup_ind(struct dect_handle *dh, struct dect_call *call,
|
|
|
|
struct dect_mncc_setup_param *param)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = dect_call_priv(call);
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_endpoint *mme;
|
|
|
|
struct dect_mm_pvt *mmp;
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_pt *pt;
|
|
|
|
|
|
|
|
pt = dect_pt_get_by_ipui(dect_call_portable_identity(call));
|
|
|
|
if (pt == NULL) {
|
|
|
|
ast_log(LOG_NOTICE, "Incoming call from unknown PT\n");
|
|
|
|
return dect_mncc_reject(call, DECT_RELEASE_UNKNOWN_IDENTITY);
|
|
|
|
}
|
2010-10-11 15:29:03 +00:00
|
|
|
dect_pt_log(pt, "incoming call\n");
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
mme = dect_mm_endpoint_get(dh, &pt->ipui);
|
|
|
|
if (mme == NULL)
|
|
|
|
return dect_mncc_reject(call, DECT_RELEASE_INSUFFICIENT_RESOURCES);
|
|
|
|
|
|
|
|
mmp = dect_mm_priv(mme);
|
|
|
|
mmp->pt = pt;
|
|
|
|
mmp->mme = mme;
|
|
|
|
mmp->call = call;
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
pvt->pt = pt;
|
|
|
|
pvt->call = call;
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
dect_cipher(mmp, dect_mncc_setup_auth_cfm, ¶m->common);
|
2009-12-16 04:05:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mncc_setup_ack_ind(struct dect_handle *dh, struct dect_call *call,
|
|
|
|
struct dect_mncc_setup_ack_param *param)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mncc_reject_ind(struct dect_handle *dh, struct dect_call *call,
|
2010-09-16 04:18:19 +00:00
|
|
|
enum dect_causes cause,
|
2009-12-16 04:05:47 +00:00
|
|
|
struct dect_mncc_release_param *param)
|
|
|
|
{
|
2010-09-03 06:50:55 +00:00
|
|
|
struct dect_pvt *pvt = dect_call_priv(call);
|
2010-09-16 04:18:19 +00:00
|
|
|
int ast_cause;
|
2010-09-03 06:50:55 +00:00
|
|
|
|
|
|
|
ast_setstate(pvt->chan, AST_STATE_DOWN);
|
2010-09-16 04:18:19 +00:00
|
|
|
if (cause == DECT_CAUSE_PEER_MESSAGE &&
|
|
|
|
param->release_reason != NULL)
|
|
|
|
ast_cause = dect_release_reason_to_ast(param->release_reason->reason);
|
2010-09-03 06:50:55 +00:00
|
|
|
else
|
2010-09-16 04:18:19 +00:00
|
|
|
ast_cause = AST_CAUSE_FAILURE;
|
2010-09-03 06:50:55 +00:00
|
|
|
|
2010-09-16 04:18:19 +00:00
|
|
|
pvt->chan->hangupcause = ast_cause;
|
2010-09-03 06:50:55 +00:00
|
|
|
ast_queue_hangup(pvt->chan);
|
2009-12-16 04:05:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mncc_alert_ind(struct dect_handle *dh, struct dect_call *call,
|
|
|
|
struct dect_mncc_alert_param *param)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = dect_call_priv(call);
|
|
|
|
struct dect_ie_signal signal;
|
2010-10-11 17:23:36 +00:00
|
|
|
struct dect_mncc_info_param info = { .signal = &signal, };
|
2009-12-16 04:05:47 +00:00
|
|
|
char pattern[16];
|
|
|
|
const char *c;
|
|
|
|
|
|
|
|
ast_copy_string(pattern, pvt->pt->ring_pattern, sizeof(pattern));
|
|
|
|
c = pbx_builtin_getvar_helper(pvt->chan, "RING_PATTERN");
|
|
|
|
if (c != NULL)
|
|
|
|
ast_copy_string(pattern, c, sizeof(pattern));
|
|
|
|
|
|
|
|
if (strcasecmp(pattern, "silent")) {
|
|
|
|
dect_ie_init(&signal);
|
2010-01-08 16:45:09 +00:00
|
|
|
signal.code = DECT_SIGNAL_ALERTING_BASE | (atoi(pattern) & 0xf);
|
2009-12-16 04:05:47 +00:00
|
|
|
|
2010-10-11 17:29:33 +00:00
|
|
|
dect_mncc_info_req(dh, call, &info);
|
|
|
|
}
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
ast_queue_control(pvt->chan, AST_CONTROL_RINGING);
|
|
|
|
ast_setstate(pvt->chan, AST_STATE_RINGING);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mncc_connect_ind(struct dect_handle *dh, struct dect_call *call,
|
|
|
|
struct dect_mncc_connect_param *param)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = dect_call_priv(call);
|
|
|
|
|
|
|
|
ast_queue_control(pvt->chan, AST_CONTROL_ANSWER);
|
|
|
|
dect_mncc_connect_res(dh, call, param);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mncc_release_ind(struct dect_handle *dh, struct dect_call *call,
|
|
|
|
struct dect_mncc_release_param *param)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = dect_call_priv(call);
|
|
|
|
|
|
|
|
ast_setstate(pvt->chan, AST_STATE_DOWN);
|
|
|
|
pvt->chan->hangupcause =
|
|
|
|
dect_release_reason_to_ast(param->release_reason->reason);
|
|
|
|
ast_queue_hangup(pvt->chan);
|
|
|
|
|
|
|
|
dect_mncc_release_res(dh, call, param);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mncc_release_cfm(struct dect_handle *dh, struct dect_call *call,
|
2010-09-16 04:18:19 +00:00
|
|
|
enum dect_causes cause,
|
2009-12-16 04:05:47 +00:00
|
|
|
struct dect_mncc_release_param *param)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_dl_u_data_ind(struct dect_handle *dh, struct dect_call *call,
|
|
|
|
struct dect_msg_buf *mb)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = dect_call_priv(call);
|
|
|
|
struct ast_frame f;
|
|
|
|
|
|
|
|
if (pvt->chan->_state == AST_STATE_DOWN)
|
|
|
|
return;
|
|
|
|
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
|
|
f.frametype = AST_FRAME_VOICE;
|
|
|
|
f.subclass.integer = AST_FORMAT_G726_AAL2;
|
|
|
|
f.samples = mb->len * 2;
|
|
|
|
f.datalen = mb->len;
|
|
|
|
f.data.ptr = mb->data;
|
|
|
|
f.offset = AST_FRIENDLY_OFFSET;
|
|
|
|
|
|
|
|
ast_queue_frame(pvt->chan, &f);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_keypad_info(struct dect_pvt *pvt,
|
|
|
|
const struct dect_ie_keypad *keypad)
|
|
|
|
{
|
|
|
|
struct ast_frame f = { .frametype = AST_FRAME_DTMF, };
|
|
|
|
struct ast_channel *chan = pvt->chan;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
if (chan->_state == AST_STATE_DIALING) {
|
|
|
|
strncat(chan->exten, (char *)keypad->info, keypad->len);
|
|
|
|
dect_try_to_connect_call(pvt);
|
|
|
|
} else {
|
|
|
|
for (i = 0; i < keypad->len; i++) {
|
|
|
|
ast_verbose("keypad: '%c' (%x)\n", keypad->info[i], keypad->info[i]);
|
|
|
|
f.subclass.integer = keypad->info[i];
|
|
|
|
ast_queue_frame(chan, &f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mncc_info_ind(struct dect_handle *dh, struct dect_call *call,
|
|
|
|
struct dect_mncc_info_param *param)
|
|
|
|
{
|
|
|
|
struct dect_pvt *pvt = dect_call_priv(call);
|
|
|
|
|
|
|
|
if (param->keypad != NULL)
|
|
|
|
dect_keypad_info(pvt, param->keypad);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dect_cc_ops dect_cc_ops = {
|
|
|
|
.priv_size = sizeof(struct dect_pvt),
|
|
|
|
.mncc_setup_ind = dect_mncc_setup_ind,
|
|
|
|
.mncc_setup_ack_ind = dect_mncc_setup_ack_ind,
|
|
|
|
.mncc_reject_ind = dect_mncc_reject_ind,
|
|
|
|
.mncc_alert_ind = dect_mncc_alert_ind,
|
|
|
|
.mncc_connect_ind = dect_mncc_connect_ind,
|
|
|
|
.mncc_release_ind = dect_mncc_release_ind,
|
|
|
|
.mncc_release_cfm = dect_mncc_release_cfm,
|
|
|
|
.mncc_facility_ind = NULL,
|
|
|
|
.mncc_info_ind = dect_mncc_info_ind,
|
|
|
|
.mncc_modify_ind = NULL,
|
|
|
|
.mncc_modify_cfm = NULL,
|
|
|
|
.mncc_hold_ind = NULL,
|
|
|
|
.mncc_hold_cfm = NULL,
|
|
|
|
.mncc_retrieve_ind = NULL,
|
|
|
|
.mncc_retrieve_cfm = NULL,
|
|
|
|
.mncc_iwu_info_ind = NULL,
|
|
|
|
.dl_u_data_ind = dect_dl_u_data_ind,
|
|
|
|
};
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
/*
|
|
|
|
* Authentication / Key Allocation / Ciphering
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void dect_mm_authenticate_reject(struct dect_mm_endpoint *mme,
|
|
|
|
enum dect_reject_reasons reason)
|
|
|
|
{
|
|
|
|
struct dect_ie_reject_reason reject_reason = { .reason = reason };
|
|
|
|
struct dect_mm_authenticate_param reply = { .reject_reason = &reject_reason };
|
|
|
|
|
|
|
|
dect_mm_authenticate_res(dh, mme, false, &reply);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mm_authenticate_ind(struct dect_handle *dh,
|
|
|
|
struct dect_mm_endpoint *mme,
|
|
|
|
struct dect_mm_authenticate_param *param)
|
|
|
|
{
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_pvt *mmp = dect_mm_priv(mme);
|
|
|
|
typeof(mmp->auth_cfm) auth_cfm = mmp->auth_cfm;
|
|
|
|
struct dect_ie_collection *iec = mmp->iec;
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_auth_value rs;
|
|
|
|
struct dect_ie_auth_res res;
|
|
|
|
struct dect_mm_authenticate_param reply = {
|
|
|
|
.res = &res,
|
|
|
|
.rs = &rs,
|
|
|
|
};
|
|
|
|
uint8_t k[DECT_AUTH_KEY_LEN], ks[DECT_AUTH_KEY_LEN];
|
|
|
|
uint8_t dck[DECT_CIPHER_KEY_LEN];
|
2010-10-14 21:07:35 +00:00
|
|
|
uint8_t ac[DECT_AUTH_CODE_LEN];
|
2010-01-29 18:08:21 +00:00
|
|
|
uint32_t res1;
|
|
|
|
|
|
|
|
if (param->auth_type->auth_id != DECT_AUTH_DSAA) {
|
|
|
|
dect_mm_authenticate_reject(mme, DECT_REJECT_AUTHENTICATION_ALGORITHM_NOT_SUPPORTED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dect_pin_to_ac(dect_cfg.pin, ac, sizeof(ac));
|
|
|
|
dect_auth_b1(ac, sizeof(ac), k);
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
if (mmp->auth_type == DECT_AUTH_KEY_ALLOCATION) {
|
2010-01-29 18:08:21 +00:00
|
|
|
/* Reset state before invoking completion handler since it may invoke
|
|
|
|
* a new authentication procedure.
|
|
|
|
*/
|
2010-10-12 05:34:59 +00:00
|
|
|
mmp->auth_type = DECT_AUTH_NONE;
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->auth_cfm = NULL;
|
2010-10-12 05:34:59 +00:00
|
|
|
mmp->iec = NULL;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
dect_auth_a11(k, mmp->rs, ks);
|
|
|
|
dect_auth_a12(ks, mmp->rand, dck, &res1);
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
if (res1 == param->res->value) {
|
|
|
|
ast_log(LOG_NOTICE, "PT authentication succeeded\n");
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
rs.value = mmp->rs;
|
2010-01-29 18:08:21 +00:00
|
|
|
dect_auth_a21(k, rs.value, ks);
|
|
|
|
dect_auth_a22(ks, param->rand->value, &res.value);
|
|
|
|
|
|
|
|
dect_mm_authenticate_res(dh, mme, true, &reply);
|
|
|
|
|
|
|
|
/* Store KS' as UAK */
|
2010-10-09 02:47:08 +00:00
|
|
|
memcpy(mmp->uak, ks, sizeof(mmp->uak));
|
|
|
|
auth_cfm(mmp, true, iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
} else {
|
|
|
|
ast_log(LOG_NOTICE, "PT authentication failed\n");
|
|
|
|
dect_mm_authenticate_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED);
|
2010-10-09 02:47:08 +00:00
|
|
|
auth_cfm(mmp, false, iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
2010-10-09 02:47:08 +00:00
|
|
|
|
|
|
|
if (iec != NULL)
|
|
|
|
__dect_ie_collection_put(dh, iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
} else {
|
|
|
|
ast_log(LOG_NOTICE, "FT authentication\n");
|
|
|
|
|
|
|
|
rs.value = dect_random();
|
|
|
|
dect_auth_a21(k, rs.value, ks);
|
|
|
|
dect_auth_a22(ks, param->rand->value, &res.value);
|
|
|
|
|
|
|
|
dect_mm_authenticate_res(dh, mme, true, &reply);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mm_authenticate_cfm(struct dect_handle *dh,
|
|
|
|
struct dect_mm_endpoint *mme, bool accept,
|
|
|
|
struct dect_mm_authenticate_param *param)
|
|
|
|
{
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_pvt *mmp = dect_mm_priv(mme);
|
|
|
|
struct dect_pt *pt = mmp->pt;
|
2010-10-12 05:34:59 +00:00
|
|
|
enum dect_auth_types auth_type = mmp->auth_type;
|
2010-10-09 02:47:08 +00:00
|
|
|
typeof(mmp->auth_cfm) auth_cfm = mmp->auth_cfm;
|
|
|
|
struct dect_ie_collection *iec = mmp->iec;
|
2010-01-29 18:08:21 +00:00
|
|
|
uint8_t k[DECT_AUTH_KEY_LEN], ks[DECT_AUTH_KEY_LEN];
|
|
|
|
uint8_t dck[DECT_CIPHER_KEY_LEN];
|
|
|
|
uint32_t res1;
|
|
|
|
|
|
|
|
/* Reset state before invoking completion handler since it may invoke
|
|
|
|
* a new authentication procedure.
|
|
|
|
*/
|
2010-10-12 05:34:59 +00:00
|
|
|
mmp->auth_type = DECT_AUTH_NONE;
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->auth_cfm = NULL;
|
2010-10-12 05:34:59 +00:00
|
|
|
mmp->iec = NULL;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
if (!accept)
|
|
|
|
goto reject;
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
if (auth_type == DECT_AUTH_PT)
|
|
|
|
dect_auth_b1(pt->uak, sizeof(pt->uak), k);
|
|
|
|
else
|
|
|
|
dect_auth_b2(pt->uak, sizeof(pt->uak),
|
|
|
|
mmp->upi, sizeof(mmp->upi), k);
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
dect_auth_a11(k, mmp->rs, ks);
|
|
|
|
dect_auth_a12(ks, mmp->rand, dck, &res1);
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
if (res1 == param->res->value) {
|
2010-10-11 15:29:03 +00:00
|
|
|
dect_pt_log(pt, "authentication succeeded\n");
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
/* Store DCK */
|
|
|
|
memcpy(pt->dck, dck, sizeof(pt->dck));
|
2010-10-09 02:47:08 +00:00
|
|
|
auth_cfm(mmp, true, iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
} else {
|
|
|
|
reject:
|
2010-10-11 15:29:03 +00:00
|
|
|
dect_pt_log(pt, "authentication failed\n");
|
2010-10-09 02:47:08 +00:00
|
|
|
auth_cfm(mmp, false, iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
if (iec != NULL)
|
|
|
|
__dect_ie_collection_put(dh, iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
static void dect_authenticate(struct dect_mm_pvt *mmp, enum dect_auth_types type,
|
2010-10-09 02:47:08 +00:00
|
|
|
void (*auth_cfm)(struct dect_mm_pvt *, bool,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *),
|
|
|
|
struct dect_ie_collection *iec)
|
|
|
|
{
|
|
|
|
struct dect_ie_auth_type auth_type;
|
|
|
|
struct dect_ie_auth_value rand, rs;
|
|
|
|
struct dect_mm_authenticate_param req = {
|
|
|
|
.auth_type = &auth_type,
|
|
|
|
.rand = &rand,
|
|
|
|
.rs = &rs,
|
|
|
|
};
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
mmp->auth_type = type;
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->auth_cfm = auth_cfm;
|
2010-10-12 05:34:59 +00:00
|
|
|
if (iec != NULL)
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->iec = __dect_ie_collection_hold(iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->rand = dect_random();
|
|
|
|
mmp->rs = dect_random();
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
auth_type.auth_id = DECT_AUTH_DSAA;
|
2010-10-12 05:34:59 +00:00
|
|
|
if (type == DECT_AUTH_PT)
|
|
|
|
auth_type.auth_key_type = DECT_KEY_USER_AUTHENTICATION_KEY;
|
|
|
|
else
|
|
|
|
auth_type.auth_key_type = DECT_KEY_USER_PERSONAL_IDENTITY;
|
2010-01-29 18:08:21 +00:00
|
|
|
auth_type.auth_key_num = 0 | DECT_AUTH_KEY_IPUI_PARK;
|
|
|
|
auth_type.cipher_key_num = 0;
|
|
|
|
auth_type.flags = DECT_AUTH_FLAG_UPC;
|
2010-10-09 02:47:08 +00:00
|
|
|
rand.value = mmp->rand;
|
|
|
|
rs.value = mmp->rs;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
dect_mm_authenticate_req(dh, mmp->mme, &req);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
static void dect_mm_key_allocate(struct dect_mm_pvt *mmp,
|
|
|
|
void (*auth_cfm)(struct dect_mm_pvt *, bool,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *iec),
|
|
|
|
struct dect_ie_collection *iec)
|
|
|
|
{
|
|
|
|
struct dect_ie_allocation_type allocation_type;
|
|
|
|
struct dect_ie_auth_value rand, rs;
|
|
|
|
struct dect_mm_key_allocate_param req = {
|
|
|
|
.allocation_type = &allocation_type,
|
|
|
|
.rand = &rand,
|
|
|
|
.rs = &rs,
|
|
|
|
};
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
mmp->auth_type = DECT_AUTH_KEY_ALLOCATION;
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->auth_cfm = auth_cfm;
|
|
|
|
mmp->iec = __dect_ie_collection_hold(iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->rand = dect_random();
|
|
|
|
mmp->rs = dect_random();
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
allocation_type.auth_id = DECT_AUTH_DSAA;
|
|
|
|
allocation_type.auth_key_num = 0 | DECT_AUTH_KEY_IPUI_PARK;
|
|
|
|
allocation_type.auth_code_num = 0 | DECT_AUTH_KEY_IPUI_PARK;
|
2010-10-09 02:47:08 +00:00
|
|
|
rand.value = mmp->rand;
|
|
|
|
rs.value = mmp->rs;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
dect_mm_key_allocate_req(dh, mmp->mme, &req);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mm_cipher_ind(struct dect_handle *dh,
|
|
|
|
struct dect_mm_endpoint *mme,
|
|
|
|
struct dect_mm_cipher_param *param)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mm_cipher_cfm(struct dect_handle *dh,
|
|
|
|
struct dect_mm_endpoint *mme, bool accept,
|
|
|
|
struct dect_mm_cipher_param *param)
|
|
|
|
{
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_pvt *mmp = dect_mm_priv(mme);
|
|
|
|
typeof(mmp->cipher_cfm) cipher_cfm = mmp->cipher_cfm;
|
|
|
|
struct dect_ie_collection *iec = mmp->iec;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->ciphered = accept;
|
|
|
|
mmp->cipher_cfm = NULL;
|
2010-10-12 05:34:59 +00:00
|
|
|
mmp->iec = NULL;
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
cipher_cfm(mmp, accept, iec);
|
2010-10-12 05:34:59 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
if (iec != NULL)
|
|
|
|
__dect_ie_collection_put(dh, iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
static void dect_cipher_auth_cfm(struct dect_mm_pvt *mmp, bool success,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *iec)
|
|
|
|
{
|
|
|
|
struct dect_ie_cipher_info cipher_info;
|
|
|
|
struct dect_mm_cipher_param req = {
|
|
|
|
.cipher_info = &cipher_info,
|
|
|
|
};
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
if (iec != NULL)
|
|
|
|
mmp->iec = __dect_ie_collection_hold(iec);
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
if (!success)
|
2010-10-09 02:47:08 +00:00
|
|
|
return dect_mm_cipher_cfm(dh, mmp->mme, success, NULL);
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
cipher_info.enable = true;
|
|
|
|
cipher_info.cipher_alg_id = DECT_CIPHER_STANDARD_1;
|
|
|
|
cipher_info.cipher_key_type = DECT_CIPHER_DERIVED_KEY;
|
|
|
|
cipher_info.cipher_key_num = 0;
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
dect_mm_cipher_req(dh, mmp->mme, &req, mmp->pt->dck);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Authenticate the PT, thereby establishing a new DCK, then switch to ciphering. */
|
2010-10-09 02:47:08 +00:00
|
|
|
static void dect_cipher(struct dect_mm_pvt *mmp,
|
|
|
|
void (*cipher_cfm)(struct dect_mm_pvt *, bool,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *),
|
|
|
|
struct dect_ie_collection *iec)
|
|
|
|
{
|
2010-10-09 02:47:08 +00:00
|
|
|
if (mmp->ciphered)
|
|
|
|
return cipher_cfm(mmp, true, iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->cipher_cfm = cipher_cfm;
|
|
|
|
if (iec != NULL)
|
|
|
|
mmp->iec = __dect_ie_collection_hold(iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
dect_authenticate(mmp, DECT_AUTH_PT, dect_cipher_auth_cfm, mmp->iec);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Access rights procedures
|
|
|
|
*/
|
|
|
|
|
2010-08-16 18:51:29 +00:00
|
|
|
static int dect_access_rights_timer(const void *data)
|
|
|
|
{
|
|
|
|
ast_log(LOG_NOTICE, "disabling access rights requests\n");
|
|
|
|
dect_fpc.hlc &= ~DECT_HLC_ACCESS_RIGHTS_REQUESTS;
|
|
|
|
dect_fpc.ehlc2 &= ~DECT_EHLC2_EASY_PAIRING;
|
|
|
|
dect_llme_rfp_preload_req(dh, &dect_fpc);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_access_rights_requests_enable(void)
|
|
|
|
{
|
|
|
|
int id;
|
|
|
|
|
|
|
|
if (dect_fpc.hlc & DECT_HLC_ACCESS_RIGHTS_REQUESTS)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
id = ast_sched_add(sched, 120 * 1000, dect_access_rights_timer, NULL);
|
|
|
|
|
|
|
|
dect_fpc.hlc |= DECT_HLC_ACCESS_RIGHTS_REQUESTS;
|
|
|
|
dect_fpc.ehlc2 |= DECT_EHLC2_EASY_PAIRING;
|
|
|
|
if (dect_llme_rfp_preload_req(dh, &dect_fpc) < 0)
|
|
|
|
goto err1;
|
|
|
|
|
|
|
|
access_rights_timer = id;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err1:
|
|
|
|
AST_SCHED_DEL(sched, id);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_access_rights_requests_disable(void)
|
|
|
|
{
|
|
|
|
if (!(dect_fpc.hlc & DECT_HLC_ACCESS_RIGHTS_REQUESTS))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
dect_fpc.hlc &= ~DECT_HLC_ACCESS_RIGHTS_REQUESTS;
|
|
|
|
dect_fpc.ehlc2 &= ~DECT_EHLC2_EASY_PAIRING;
|
|
|
|
if (dect_llme_rfp_preload_req(dh, &dect_fpc) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
AST_SCHED_DEL(sched, access_rights_timer);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
static void dect_mm_access_rights_reject(struct dect_mm_endpoint *mme,
|
|
|
|
enum dect_reject_reasons reason)
|
|
|
|
{
|
|
|
|
struct dect_ie_reject_reason reject_reason = { .reason = reason };
|
|
|
|
struct dect_mm_access_rights_param reply = { .reject_reason = &reject_reason };
|
|
|
|
|
|
|
|
dect_mm_access_rights_res(dh, mme, false, &reply);
|
|
|
|
}
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
static void dect_mm_access_rights_auth_cfm(struct dect_mm_pvt *mmp, bool success,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *iec)
|
|
|
|
{
|
|
|
|
struct dect_pt *pt;
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_endpoint *mme = mmp->mme;
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_mm_access_rights_param *param = (void *)iec, reply = {
|
|
|
|
.portable_identity = param->portable_identity,
|
|
|
|
.auth_type = param->auth_type,
|
|
|
|
.cipher_info = param->cipher_info,
|
|
|
|
.codec_list = param->codec_list,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
dect_mm_access_rights_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pt = dect_pt_get_by_ipui(¶m->portable_identity->ipui);
|
|
|
|
if (pt == NULL) {
|
|
|
|
unsigned int exten;
|
|
|
|
char name[64];
|
|
|
|
|
|
|
|
if (!dect_alloc_extension(&exten)) {
|
|
|
|
dect_mm_access_rights_reject(mme, DECT_REJECT_INSUFFICIENT_MEMORY);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(name, sizeof(name), "DECT-PT-%u", exten);
|
2010-10-08 20:20:47 +00:00
|
|
|
pt = dect_init_portable(name);
|
2010-01-29 18:08:21 +00:00
|
|
|
if (pt == NULL) {
|
|
|
|
dect_mm_access_rights_reject(mme, DECT_REJECT_INSUFFICIENT_MEMORY);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-10-08 20:20:47 +00:00
|
|
|
dect_pt_set_ipui(pt, ¶m->portable_identity->ipui);
|
|
|
|
dect_pt_set_extension(pt, exten);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (dect_mm_access_rights_res(dh, mme, true, &reply) < 0)
|
|
|
|
return;
|
2010-08-16 18:51:29 +00:00
|
|
|
dect_access_rights_requests_disable();
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
memcpy(pt->uak, mmp->uak, sizeof(pt->uak));
|
2010-10-08 13:37:02 +00:00
|
|
|
dect_db_store_portable(pt);
|
|
|
|
|
|
|
|
if (param->terminal_capability) {
|
|
|
|
dect_ie_update(pt->terminal_capability, param->terminal_capability);
|
|
|
|
dect_db_store_termcap(pt);
|
|
|
|
}
|
|
|
|
if (param->codec_list) {
|
|
|
|
dect_ie_update(pt->codec_list, param->codec_list);
|
|
|
|
dect_db_store_codec_list(pt);
|
|
|
|
}
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mm_access_rights_ind(struct dect_handle *dh,
|
|
|
|
struct dect_mm_endpoint *mme,
|
|
|
|
struct dect_mm_access_rights_param *param)
|
|
|
|
{
|
|
|
|
struct dect_ie_auth_type *auth_type = param->auth_type;
|
|
|
|
struct dect_ie_cipher_info *cipher_info = param->cipher_info;
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_pvt *mmp = dect_mm_priv(mme);
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-08-16 18:51:29 +00:00
|
|
|
if (!(dect_fpc.hlc & DECT_HLC_ACCESS_RIGHTS_REQUESTS))
|
|
|
|
return dect_mm_access_rights_reject(mme, DECT_REJECT_INCOMPATIBLE_SERVICE);
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
if (auth_type) {
|
|
|
|
if (auth_type->auth_id != DECT_AUTH_DSAA)
|
|
|
|
return dect_mm_access_rights_reject(mme, DECT_REJECT_AUTHENTICATION_ALGORITHM_NOT_SUPPORTED);
|
|
|
|
if (auth_type->auth_key_type != DECT_KEY_AUTHENTICATION_CODE)
|
|
|
|
return dect_mm_access_rights_reject(mme, DECT_REJECT_AUTHENTICATION_KEY_NOT_SUPPORTED);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cipher_info) {
|
|
|
|
if (cipher_info->cipher_alg_id != DECT_CIPHER_STANDARD_1)
|
|
|
|
return dect_mm_access_rights_reject(mme, DECT_REJECT_CIPHER_ALGORITHM_NOT_SUPPORTED);
|
|
|
|
}
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->mme = mme;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
dect_mm_key_allocate(mmp, dect_mm_access_rights_auth_cfm, ¶m->common);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Access rights termination
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void dect_destroy_portable(struct dect_pt *pt)
|
|
|
|
{
|
|
|
|
dect_register_extension(pt, false);
|
|
|
|
|
|
|
|
AST_LIST_REMOVE(&dect_pt_list, pt, list);
|
2010-10-11 15:29:03 +00:00
|
|
|
AST_SCHED_DEL(sched, pt->locate_timer);
|
2010-01-29 18:08:21 +00:00
|
|
|
ast_free(pt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mm_access_rights_terminate_reject(struct dect_mm_endpoint *mme,
|
|
|
|
enum dect_reject_reasons reason)
|
|
|
|
{
|
|
|
|
struct dect_ie_reject_reason reject_reason = { .reason = reason };
|
|
|
|
struct dect_mm_access_rights_terminate_param reply = { .reject_reason = &reject_reason };
|
|
|
|
|
|
|
|
dect_mm_access_rights_terminate_res(dh, mme, false, &reply);
|
|
|
|
}
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
static void dect_mm_access_rights_terminate_auth_cfm(struct dect_mm_pvt *mmp, bool success,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *iec)
|
|
|
|
{
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_pt *pt = mmp->pt;
|
|
|
|
struct dect_mm_endpoint *mme = mmp->mme;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
dect_mm_access_rights_terminate_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-10-08 13:37:02 +00:00
|
|
|
dect_db_remove(pt);
|
2010-01-29 18:08:21 +00:00
|
|
|
dect_destroy_portable(pt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mm_access_rights_terminate_ind(struct dect_handle *dh,
|
|
|
|
struct dect_mm_endpoint *mme,
|
|
|
|
struct dect_mm_access_rights_terminate_param *param)
|
|
|
|
{
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_pvt *mmp = dect_mm_priv(mme);
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_pt *pt;
|
|
|
|
|
|
|
|
pt = dect_pt_get_by_ipui(¶m->portable_identity->ipui);
|
|
|
|
if (pt == NULL)
|
|
|
|
return dect_mm_access_rights_terminate_reject(mme, DECT_REJECT_IPUI_UNKNOWN);
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->pt = pt;
|
|
|
|
mmp->mme = mme;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
dect_cipher(mmp, dect_mm_access_rights_terminate_auth_cfm, ¶m->common);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mm_access_rights_terminate_cfm(struct dect_handle *dh,
|
|
|
|
struct dect_mm_endpoint *mme, bool accept,
|
|
|
|
struct dect_mm_access_rights_terminate_param *param)
|
|
|
|
{
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_pvt *mmp = dect_mm_priv(mme);
|
|
|
|
struct dect_pt *pt = mmp->pt;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-08 13:37:02 +00:00
|
|
|
dect_db_remove(pt);
|
2010-01-29 18:08:21 +00:00
|
|
|
dect_destroy_portable(pt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_access_rights_terminate(struct dect_pt *pt)
|
|
|
|
{
|
|
|
|
struct dect_ie_portable_identity portable_identity;
|
|
|
|
struct dect_mm_access_rights_terminate_param param = {
|
|
|
|
.portable_identity = &portable_identity,
|
|
|
|
};
|
|
|
|
struct dect_mm_endpoint *mme;
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_pvt *mmp;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
mme = dect_mm_endpoint_alloc(dh, &pt->ipui);
|
|
|
|
if (mme == NULL)
|
|
|
|
return;
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp = dect_mm_priv(mme);
|
|
|
|
mmp->mme = mme;
|
|
|
|
mmp->pt = pt;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
portable_identity.type = DECT_PORTABLE_ID_TYPE_IPUI;
|
|
|
|
portable_identity.ipui = pt->ipui;
|
|
|
|
|
|
|
|
dect_mm_access_rights_terminate_req(dh, mme, ¶m);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Location procedures
|
|
|
|
*/
|
|
|
|
|
2010-10-11 15:29:03 +00:00
|
|
|
static int dect_locate_timer(const void *data)
|
|
|
|
{
|
|
|
|
struct dect_pt *pt = (struct dect_pt *)data;;
|
|
|
|
|
|
|
|
dect_pt_log(pt, "location registation timeout\n");
|
|
|
|
dect_register_extension(pt, false);
|
|
|
|
pt->locate_timer = -1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
static void dect_mm_locate_reject(struct dect_mm_endpoint *mme,
|
|
|
|
enum dect_reject_reasons reason)
|
|
|
|
{
|
|
|
|
struct dect_ie_reject_reason reject_reason = { .reason = reason };
|
|
|
|
struct dect_mm_locate_param reply = { .reject_reason = &reject_reason };
|
|
|
|
|
|
|
|
dect_mm_locate_res(dh, mme, false, &reply);
|
|
|
|
}
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
static void dect_mm_locate_auth_cfm(struct dect_mm_pvt *mmp, bool success,
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_collection *iec)
|
|
|
|
{
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_pt *pt = mmp->pt;
|
|
|
|
struct dect_mm_endpoint *mme = mmp->mme;
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_ie_portable_identity portable_identity;
|
|
|
|
struct dect_ie_duration duration;
|
|
|
|
struct dect_mm_locate_param *param = (void *)iec, reply = {
|
|
|
|
.portable_identity = &portable_identity,
|
|
|
|
.location_area = param->location_area,
|
|
|
|
.codec_list = param->codec_list,
|
|
|
|
.duration = &duration,
|
|
|
|
};
|
2010-10-11 15:29:03 +00:00
|
|
|
unsigned int limit, timeout;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
dect_mm_locate_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
portable_identity.type = DECT_PORTABLE_ID_TYPE_TPUI;
|
|
|
|
portable_identity.tpui = pt->tpui;
|
|
|
|
|
|
|
|
duration.lock = DECT_LOCK_TEMPORARY_USER_LIMIT_1;
|
2010-10-11 15:29:03 +00:00
|
|
|
if (dect_cfg.locate_duration * DECT_FRAMES_PER_SECOND <=
|
|
|
|
255 * DECT_TIME_LIMIT_UNITS_1) {
|
|
|
|
limit = div_round_up(dect_cfg.locate_duration *
|
|
|
|
DECT_FRAMES_PER_SECOND,
|
|
|
|
DECT_TIME_LIMIT_UNITS_1);
|
|
|
|
timeout = div_round_up(limit * DECT_TIME_LIMIT_UNITS_1,
|
|
|
|
DECT_FRAMES_PER_SECOND);
|
|
|
|
duration.time = DECT_TIME_LIMIT_DEFINED_TIME_LIMIT_1;
|
|
|
|
} else {
|
|
|
|
limit = div_round_up(dect_cfg.locate_duration *
|
|
|
|
DECT_FRAMES_PER_SECOND,
|
|
|
|
DECT_TIME_LIMIT_UNITS_2);
|
|
|
|
timeout = div_round_up(limit * DECT_TIME_LIMIT_UNITS_2,
|
|
|
|
DECT_FRAMES_PER_SECOND);
|
|
|
|
duration.time = DECT_TIME_LIMIT_DEFINED_TIME_LIMIT_2;
|
|
|
|
}
|
|
|
|
duration.duration = limit;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
if (dect_mm_locate_res(dh, mme, true, &reply) < 0)
|
|
|
|
return;
|
|
|
|
|
2010-10-08 13:37:02 +00:00
|
|
|
if (param->terminal_capability) {
|
|
|
|
dect_ie_update(pt->terminal_capability, param->terminal_capability);
|
|
|
|
dect_db_store_termcap(pt);
|
|
|
|
}
|
|
|
|
if (param->codec_list) {
|
|
|
|
dect_ie_update(pt->codec_list, param->codec_list);
|
|
|
|
dect_db_store_codec_list(pt);
|
|
|
|
}
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-11 15:29:03 +00:00
|
|
|
timeout += DECT_LOCATE_TIMEOUT_SLACK;
|
|
|
|
dect_pt_log(pt, "location registration: timeout: %us\n", timeout);
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
dect_register_extension(pt, true);
|
2010-10-11 15:29:03 +00:00
|
|
|
pt->locate_timer = ast_sched_replace(pt->locate_timer, sched,
|
|
|
|
timeout * 1000,
|
|
|
|
dect_locate_timer, pt);
|
2010-01-29 18:08:21 +00:00
|
|
|
}
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
static void dect_mm_locate_ind(struct dect_handle *dh,
|
|
|
|
struct dect_mm_endpoint *mme,
|
|
|
|
struct dect_mm_locate_param *param)
|
|
|
|
{
|
2010-10-09 02:47:08 +00:00
|
|
|
struct dect_mm_pvt *mmp = dect_mm_priv(mme);
|
2010-01-29 18:08:21 +00:00
|
|
|
struct dect_pt *pt;
|
|
|
|
|
|
|
|
pt = dect_pt_get_by_ipui(¶m->portable_identity->ipui);
|
|
|
|
if (pt == NULL)
|
|
|
|
return dect_mm_locate_reject(mme, DECT_REJECT_IPUI_UNKNOWN);
|
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
mmp->pt = pt;
|
|
|
|
mmp->mme = mme;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2010-10-09 02:47:08 +00:00
|
|
|
dect_cipher(mmp, dect_mm_locate_auth_cfm, ¶m->common);
|
2009-12-16 04:05:47 +00:00
|
|
|
}
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
/*
|
|
|
|
* Identity assignment procedures
|
|
|
|
*/
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
static void dect_mm_identity_assign_cfm(struct dect_handle *dh,
|
|
|
|
struct dect_mm_endpoint *mme, bool accept,
|
|
|
|
struct dect_mm_identity_assign_param *param)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dect_mm_ops dect_mm_ops = {
|
2010-10-09 02:47:08 +00:00
|
|
|
.priv_size = sizeof(struct dect_mm_pvt),
|
2010-01-29 18:08:21 +00:00
|
|
|
.mm_authenticate_ind = dect_mm_authenticate_ind,
|
|
|
|
.mm_authenticate_cfm = dect_mm_authenticate_cfm,
|
|
|
|
.mm_cipher_ind = dect_mm_cipher_ind,
|
|
|
|
.mm_cipher_cfm = dect_mm_cipher_cfm,
|
|
|
|
.mm_access_rights_ind = dect_mm_access_rights_ind,
|
|
|
|
.mm_access_rights_terminate_ind = dect_mm_access_rights_terminate_ind,
|
|
|
|
.mm_access_rights_terminate_cfm = dect_mm_access_rights_terminate_cfm,
|
|
|
|
.mm_locate_ind = dect_mm_locate_ind,
|
|
|
|
.mm_identity_assign_cfm = dect_mm_identity_assign_cfm,
|
2009-12-16 04:05:47 +00:00
|
|
|
};
|
|
|
|
|
2010-10-09 05:22:14 +00:00
|
|
|
/*
|
|
|
|
* Supplementary Services
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void dect_mnss_setup_ind(struct dect_handle *dh, struct dect_ss_endpoint *sse,
|
|
|
|
struct dect_mnss_param *param)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_mnss_release_ind(struct dect_handle *dh, struct dect_ss_endpoint *sse,
|
|
|
|
struct dect_mnss_param *param)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dect_ss_ops dect_ss_ops = {
|
|
|
|
.mnss_setup_ind = dect_mnss_setup_ind,
|
|
|
|
.mnss_release_ind = dect_mnss_release_ind,
|
|
|
|
};
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
/*
|
|
|
|
* User authentication App
|
|
|
|
*/
|
|
|
|
static void dect_userauth_auth_cfm(struct dect_mm_pvt *mmp, bool success,
|
|
|
|
struct dect_ie_collection *iec)
|
2009-12-16 04:05:47 +00:00
|
|
|
{
|
2010-10-12 05:34:59 +00:00
|
|
|
mmp->userauth_status = success;
|
|
|
|
sem_post(&mmp->userauth_semaphore);
|
2009-12-16 04:05:47 +00:00
|
|
|
}
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
static int dect_userauth_exec(struct ast_channel *chan, const char *data)
|
2009-12-16 04:05:47 +00:00
|
|
|
{
|
2010-10-12 05:34:59 +00:00
|
|
|
struct dect_pvt *pvt = chan->tech_pvt;
|
|
|
|
struct dect_pt *pt = pvt->pt;
|
|
|
|
struct dect_mm_endpoint *mme;
|
|
|
|
struct dect_mm_pvt *mmp;
|
2009-12-16 04:05:47 +00:00
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
if (data == NULL) {
|
|
|
|
ast_log(LOG_WARNING, "DectUserAuth requires an argument (PIN)");
|
|
|
|
return -1;
|
2009-12-16 04:05:47 +00:00
|
|
|
}
|
2010-10-12 05:34:59 +00:00
|
|
|
if (strlen(data) > 8) {
|
|
|
|
ast_log(LOG_WARNING, "User personal identity may not exceed 8 digits");
|
|
|
|
return -1;
|
2009-12-16 04:05:47 +00:00
|
|
|
}
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
mme = dect_mm_endpoint_get(dh, &pt->ipui);
|
|
|
|
if (mme == NULL)
|
|
|
|
return -1;
|
2009-12-16 04:05:47 +00:00
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
mmp = dect_mm_priv(mme);
|
|
|
|
mmp->pt = pt;
|
|
|
|
mmp->mme = mme;
|
|
|
|
mmp->call = pvt->call;
|
|
|
|
sem_init(&mmp->userauth_semaphore, 0, 0);
|
2009-12-16 04:05:47 +00:00
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
dect_pin_to_ac(data, mmp->upi, sizeof(mmp->upi));
|
|
|
|
dect_authenticate(mmp, DECT_AUTH_USER, dect_userauth_auth_cfm, NULL);
|
|
|
|
sem_wait(&mmp->userauth_semaphore);
|
|
|
|
|
|
|
|
dect_pt_log(pt, "DectUserAuth: status: %d\n", mmp->userauth_status);
|
|
|
|
pbx_builtin_setvar_helper(chan, "USERAUTHSTATUS",
|
|
|
|
mmp->userauth_status ? "SUCCESS" : "FAILURE");
|
2009-12-16 04:05:47 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
static void dect_show_debug(enum dect_debug_subsys subsys,
|
|
|
|
const char *fmt, va_list ap)
|
|
|
|
{
|
|
|
|
ast_verbose_ap(fmt, ap);
|
|
|
|
}
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
/*
|
|
|
|
* Asterisk CLI commands
|
|
|
|
*/
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
static char *dect_complete_pt(const char *word, int state)
|
|
|
|
{
|
|
|
|
size_t wordlen = strlen(word);
|
|
|
|
struct dect_pt *pt;
|
|
|
|
int which = 0;
|
|
|
|
char *res;
|
|
|
|
|
|
|
|
AST_LIST_TRAVERSE(&dect_pt_list, pt, list) {
|
|
|
|
if (!strncasecmp(word, pt->name, wordlen) && ++which > state) {
|
|
|
|
res = ast_strdup(pt->name);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
static char *dect_cli_debug(struct ast_cli_entry *e, int cmd,
|
|
|
|
struct ast_cli_args *a)
|
|
|
|
{
|
|
|
|
const char *arg;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case CLI_INIT:
|
|
|
|
e->command = "dect set debug {on|off}";
|
|
|
|
e->usage = "Usage: dect set debug {on|off}\n";
|
|
|
|
return NULL;
|
|
|
|
case CLI_GENERATE:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
arg = a->argv[e->args - 1];
|
|
|
|
if (!strcasecmp(arg, "on")) {
|
|
|
|
dect_set_debug_hook(dect_show_debug);
|
|
|
|
ast_cli(a->fd, "DECT debugging enabled\n");
|
|
|
|
} else if (!strcasecmp(arg, "off")) {
|
|
|
|
dect_set_debug_hook(NULL);
|
|
|
|
ast_cli(a->fd, "DECT debugging disabled\n");
|
|
|
|
} else
|
|
|
|
return CLI_SHOWUSAGE;
|
|
|
|
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
static char *dect_cli_show_portables(struct ast_cli_entry *e, int cmd,
|
|
|
|
struct ast_cli_args *a)
|
|
|
|
{
|
|
|
|
struct dect_pt *pt;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case CLI_INIT:
|
|
|
|
e->command = "dect show portables";
|
|
|
|
e->usage = "Usage: dect show portables\n";
|
|
|
|
return NULL;
|
|
|
|
case CLI_GENERATE:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2010-10-11 15:29:03 +00:00
|
|
|
ast_cli(a->fd, "Name Extension Registered\n");
|
2010-01-29 18:08:21 +00:00
|
|
|
AST_LIST_TRAVERSE(&dect_pt_list, pt, list)
|
2010-10-11 15:29:03 +00:00
|
|
|
ast_cli(a->fd, "%-16s%-16s%-16s\n",
|
|
|
|
pt->name, pt->regexten, pt->locate_timer == -1 ? "No" : "Yes");
|
2010-01-29 18:08:21 +00:00
|
|
|
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *dect_cli_show_portable(struct ast_cli_entry *e, int cmd,
|
|
|
|
struct ast_cli_args *a)
|
|
|
|
{
|
|
|
|
struct dect_pt *pt;
|
|
|
|
char cidbuf[256], codbuf[256];
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case CLI_INIT:
|
|
|
|
e->command = "dect show portable";
|
|
|
|
e->usage = "Usage: dect show portable <name>\n";
|
|
|
|
return NULL;
|
|
|
|
case CLI_GENERATE:
|
|
|
|
if (a->pos == 3)
|
|
|
|
return dect_complete_pt(a->word, a->n);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a->argc < 4)
|
|
|
|
return CLI_SHOWUSAGE;
|
|
|
|
|
|
|
|
pt = dect_pt_get_by_name(a->argv[3]);
|
|
|
|
if (pt == NULL) {
|
|
|
|
ast_cli(a->fd, "PT '%s' not found\n", a->argv[3]);
|
|
|
|
return CLI_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_callerid_merge(cidbuf, sizeof(cidbuf), pt->cid_name, pt->cid_num,
|
|
|
|
"<unspecified>");
|
|
|
|
dect_codecs_merge(codbuf, sizeof(codbuf), pt);
|
|
|
|
|
2010-10-08 20:20:47 +00:00
|
|
|
ast_cli(a->fd, "IPEI: %s\n", pt->ipei);
|
2010-01-29 18:08:21 +00:00
|
|
|
ast_cli(a->fd, "Extension: %s\n", pt->regexten);
|
2010-10-11 15:29:03 +00:00
|
|
|
ast_cli(a->fd, "Registered: %s\n", pt->locate_timer == -1 ? "No" : "Yes");
|
2010-01-29 18:08:21 +00:00
|
|
|
ast_cli(a->fd, "Context: %s\n", pt->context);
|
|
|
|
ast_cli(a->fd, "Language: %s\n", pt->language);
|
|
|
|
ast_cli(a->fd, "CallerId: %s\n", cidbuf);
|
|
|
|
ast_cli(a->fd, "Codecs: %s\n", codbuf);
|
|
|
|
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *dect_cli_ari(struct ast_cli_entry *e, int cmd,
|
|
|
|
struct ast_cli_args *a)
|
|
|
|
{
|
|
|
|
struct dect_pt *pt;
|
|
|
|
const char *arg;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case CLI_INIT:
|
|
|
|
e->command = "dect access-rights {enable|disable|terminate}";
|
|
|
|
e->usage = "Usage: dect access-rights {enable|disable|terminate}\n";
|
|
|
|
return NULL;
|
|
|
|
case CLI_GENERATE:
|
|
|
|
if (a->pos == 3 && !strcasecmp(a->argv[2], "terminate"))
|
|
|
|
return dect_complete_pt(a->word, a->n);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a->argc < 3)
|
|
|
|
return CLI_SHOWUSAGE;
|
|
|
|
|
|
|
|
arg = a->argv[2];
|
|
|
|
if (!strcasecmp(arg, "enable")) {
|
2010-08-16 18:51:29 +00:00
|
|
|
if (dect_access_rights_requests_enable() == 0)
|
|
|
|
ast_cli(a->fd, "ARI enabled\n");
|
2010-01-29 18:08:21 +00:00
|
|
|
} else if (!strcasecmp(arg, "disable")) {
|
2010-08-16 18:51:29 +00:00
|
|
|
if (dect_access_rights_requests_disable() == 0)
|
|
|
|
ast_cli(a->fd, "ARI disabled\n");
|
2010-01-29 18:08:21 +00:00
|
|
|
} else if (!strcasecmp(arg, "terminate")) {
|
|
|
|
if (a->argc < 4)
|
|
|
|
return CLI_SHOWUSAGE;
|
|
|
|
ast_cli(a->fd, "ARI terminate\n");
|
|
|
|
pt = dect_pt_get_by_name(a->argv[3]);
|
|
|
|
if (pt == NULL) {
|
|
|
|
ast_cli(a->fd, "PT '%s' not found\n", a->argv[3]);
|
|
|
|
return CLI_FAILURE;
|
|
|
|
}
|
|
|
|
dect_access_rights_terminate(pt);
|
|
|
|
} else
|
|
|
|
return CLI_SHOWUSAGE;
|
|
|
|
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2010-10-08 13:37:02 +00:00
|
|
|
static int dect_db_read(void);
|
|
|
|
static char *dect_cli_db_reload(struct ast_cli_entry *e, int cmd,
|
|
|
|
struct ast_cli_args *a)
|
|
|
|
{
|
|
|
|
switch (cmd) {
|
|
|
|
case CLI_INIT:
|
|
|
|
e->command = "dect reload database";
|
|
|
|
e->usage = "Usage: dect reload database\n";
|
|
|
|
return NULL;
|
|
|
|
case CLI_GENERATE:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a->argc < 3)
|
|
|
|
return CLI_SHOWUSAGE;
|
|
|
|
|
|
|
|
dect_db_read();
|
|
|
|
return CLI_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
static struct ast_cli_entry dect_cli_cmds[] = {
|
2010-01-29 18:08:21 +00:00
|
|
|
AST_CLI_DEFINE(dect_cli_debug, "Enable/Disable DECT debugging"),
|
|
|
|
AST_CLI_DEFINE(dect_cli_ari, "Access rights modification"),
|
|
|
|
AST_CLI_DEFINE(dect_cli_show_portables, "Show list of portables"),
|
|
|
|
AST_CLI_DEFINE(dect_cli_show_portable, "Show portable information"),
|
2010-10-08 13:37:02 +00:00
|
|
|
AST_CLI_DEFINE(dect_cli_db_reload, "Reload database"),
|
2009-12-16 04:05:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* libdect event ops
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct dect_fd_priv {
|
|
|
|
int *id;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int dect_io_callback(int *id, int fd, short io_events, void *data)
|
|
|
|
{
|
|
|
|
struct dect_fd *dfd = data;
|
|
|
|
uint32_t events = 0;
|
|
|
|
|
|
|
|
if (io_events & (AST_IO_IN | AST_IO_ERR | AST_IO_HUP))
|
|
|
|
events |= DECT_FD_READ;
|
|
|
|
if (io_events & AST_IO_OUT)
|
|
|
|
events |= DECT_FD_WRITE;
|
2010-08-02 16:29:48 +00:00
|
|
|
dect_fd_process(dh, dfd, events);
|
2009-12-16 04:05:47 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_register_fd(const struct dect_handle *dh, struct dect_fd *dfd,
|
|
|
|
uint32_t events)
|
|
|
|
{
|
2010-07-24 20:16:24 +00:00
|
|
|
struct dect_fd_priv *priv = dect_fd_priv(dfd);
|
2009-12-16 04:05:47 +00:00
|
|
|
short io_events = 0;
|
|
|
|
|
|
|
|
if (events & DECT_FD_READ)
|
|
|
|
io_events |= AST_IO_IN;
|
|
|
|
if (events & DECT_FD_WRITE)
|
|
|
|
io_events |= AST_IO_OUT;
|
|
|
|
|
2010-07-24 20:16:24 +00:00
|
|
|
priv->id = ast_io_add(io, dect_fd_num(dfd), dect_io_callback,
|
|
|
|
io_events, dfd);;
|
2009-12-16 04:05:47 +00:00
|
|
|
return priv->id ? 0 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_unregister_fd(const struct dect_handle *dh, struct dect_fd *dfd)
|
|
|
|
{
|
2010-07-24 20:16:24 +00:00
|
|
|
struct dect_fd_priv *priv = dect_fd_priv(dfd);
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
ast_io_remove(io, priv->id);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dect_timer_priv {
|
|
|
|
int id;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int dect_timer_callback(const void *data)
|
|
|
|
{
|
|
|
|
struct dect_timer *timer = (struct dect_timer *)data;;
|
|
|
|
|
2010-08-02 16:29:48 +00:00
|
|
|
dect_timer_run(dh, timer);
|
2009-12-16 04:05:47 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_start_timer(const struct dect_handle *dh,
|
|
|
|
struct dect_timer *timer,
|
|
|
|
const struct timeval *tv)
|
|
|
|
{
|
2010-03-31 05:56:59 +00:00
|
|
|
struct dect_timer_priv *priv = dect_timer_priv(timer);
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
priv->id = ast_sched_add(sched, tv->tv_sec * 1000 + tv->tv_usec / 1000,
|
|
|
|
dect_timer_callback, timer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dect_stop_timer(const struct dect_handle *dh,
|
|
|
|
struct dect_timer *timer)
|
|
|
|
{
|
2010-03-31 05:56:59 +00:00
|
|
|
struct dect_timer_priv *priv = dect_timer_priv(timer);
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
AST_SCHED_DEL(sched, priv->id);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dect_event_ops dect_event_ops = {
|
|
|
|
.fd_priv_size = sizeof(struct dect_fd_priv),
|
|
|
|
.register_fd = dect_register_fd,
|
|
|
|
.unregister_fd = dect_unregister_fd,
|
|
|
|
.timer_priv_size = sizeof(struct dect_timer_priv),
|
|
|
|
.start_timer = dect_start_timer,
|
|
|
|
.stop_timer = dect_stop_timer,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct dect_ops dect_ops = {
|
|
|
|
.event_ops = &dect_event_ops,
|
|
|
|
.cc_ops = &dect_cc_ops,
|
|
|
|
.mm_ops = &dect_mm_ops,
|
2010-10-09 05:22:14 +00:00
|
|
|
.ss_ops = &dect_ss_ops,
|
2009-12-16 04:05:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static pthread_t io_thread = AST_PTHREADT_NULL;
|
|
|
|
|
|
|
|
static void *dect_io_thread(void *ignore)
|
|
|
|
{
|
|
|
|
int timeout;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
pthread_testcancel();
|
|
|
|
timeout = ast_sched_wait(sched);
|
2010-09-14 00:32:33 +00:00
|
|
|
if (timeout < 0 || timeout > 1000)
|
2009-12-16 04:05:47 +00:00
|
|
|
timeout = 1000;
|
|
|
|
ast_io_wait(io, timeout);
|
|
|
|
ast_sched_runq(sched);
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_io_thread_start(void)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = ast_pthread_create_background(&io_thread, NULL, dect_io_thread, NULL);
|
|
|
|
if (err < 0) {
|
|
|
|
ast_log(LOG_ERROR, "Unable to start IO thread\n");
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-01-29 18:08:21 +00:00
|
|
|
static void dect_io_thread_stop(void)
|
|
|
|
{
|
|
|
|
pthread_cancel(io_thread);
|
|
|
|
pthread_kill(io_thread, SIGURG);
|
|
|
|
pthread_join(io_thread, NULL);
|
|
|
|
}
|
|
|
|
|
2010-10-08 13:37:02 +00:00
|
|
|
static int dect_db_read(void)
|
|
|
|
{
|
|
|
|
struct ast_db_entry *dbt, *dbe;
|
2010-10-08 20:20:47 +00:00
|
|
|
struct dect_ipui ipui;
|
2010-10-08 13:37:02 +00:00
|
|
|
struct dect_pt *pt;
|
|
|
|
const char *key, *val;
|
2010-10-08 20:20:47 +00:00
|
|
|
char *tmp, ipei[64];
|
|
|
|
|
|
|
|
memset(&ipui, 0, sizeof(ipui));
|
|
|
|
ipui.put = DECT_IPUI_N;
|
2010-10-08 13:37:02 +00:00
|
|
|
|
|
|
|
dbt = ast_db_gettree("dect", NULL);
|
|
|
|
for (dbe = dbt; dbe != NULL; dbe = dbe->next) {
|
|
|
|
key = dbe->key + strlen("/dect/");
|
2010-10-08 20:20:47 +00:00
|
|
|
strncpy(ipei, key, sizeof(ipei));
|
|
|
|
tmp = strchr(ipei, '/');
|
2010-10-08 13:37:02 +00:00
|
|
|
|
|
|
|
if (tmp == NULL)
|
|
|
|
continue;
|
|
|
|
*tmp = '\0';
|
|
|
|
key = tmp + 1;
|
|
|
|
val = dbe->data;
|
|
|
|
|
2010-10-08 20:20:47 +00:00
|
|
|
if (!dect_parse_ipei_string(&ipui.pun.n.ipei, ipei))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
pt = dect_pt_get_by_ipui(&ipui);
|
2010-10-08 13:37:02 +00:00
|
|
|
if (pt == NULL) {
|
2010-10-08 20:20:47 +00:00
|
|
|
pt = dect_init_portable(NULL);
|
2010-10-08 13:37:02 +00:00
|
|
|
if (pt == NULL)
|
|
|
|
return -1;
|
2010-10-08 20:20:47 +00:00
|
|
|
dect_pt_set_ipui(pt, &ipui);
|
2010-10-08 13:37:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (dect_db_parse_global(pt, key, val) != 0 &&
|
|
|
|
dect_db_parse_termcap(pt, key, val) != 0 &&
|
|
|
|
dect_db_parse_codec_list(pt, key, val) != 0) {
|
|
|
|
ast_log(LOG_NOTICE, "Unknown database key '%s'\n", key);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-10-12 05:34:59 +00:00
|
|
|
static int dect_load_config(void)
|
|
|
|
{
|
|
|
|
struct ast_flags cfg_flags = {};
|
|
|
|
struct ast_config *cfg;
|
|
|
|
struct ast_variable *v;
|
|
|
|
|
|
|
|
memcpy(&dect_cfg.jbconf, &dect_default_jbconf, sizeof(struct ast_jb_conf));
|
|
|
|
|
|
|
|
cfg = ast_config_load(CONFIG_FILE, cfg_flags);
|
|
|
|
if (cfg == CONFIG_STATUS_FILEINVALID) {
|
|
|
|
ast_log(LOG_ERROR, "Config file %s is in an invalid format.\n",
|
|
|
|
CONFIG_FILE);
|
|
|
|
return AST_MODULE_LOAD_DECLINE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cfg == NULL) {
|
|
|
|
ast_log(LOG_ERROR, "Unable to load config %s\n", CONFIG_FILE);
|
|
|
|
return AST_MODULE_LOAD_DECLINE;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (v = ast_variable_browse(cfg, "general"); v != NULL; v = v->next) {
|
|
|
|
/* jitter buffer configuration */
|
|
|
|
if (!ast_jb_read_conf(&dect_cfg.jbconf, v->name, v->value))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!strcasecmp(v->name, "cluster")) {
|
|
|
|
ast_copy_string(dect_cfg.cluster, v->value,
|
|
|
|
sizeof(dect_cfg.cluster));
|
|
|
|
} else if (!strcasecmp(v->name, "context")) {
|
|
|
|
ast_copy_string(dect_cfg.context, v->value,
|
|
|
|
sizeof(dect_cfg.context));
|
|
|
|
} else if (!strcasecmp(v->name, "language")) {
|
|
|
|
ast_copy_string(dect_cfg.language, v->value,
|
|
|
|
sizeof(dect_cfg.language));
|
|
|
|
} else if (!strcasecmp(v->name, "regcontext")) {
|
|
|
|
ast_copy_string(dect_cfg.regcontext, v->value,
|
|
|
|
sizeof(dect_cfg.regcontext));
|
|
|
|
/* Create context if it doesn't exist already */
|
|
|
|
ast_context_find_or_create(NULL, NULL, dect_cfg.regcontext, "DECT");
|
|
|
|
} else if (!strcasecmp(v->name, "regexten_base")) {
|
|
|
|
dect_cfg.regexten_base = strtoul(v->value, NULL, 0);
|
|
|
|
} else if (!strcasecmp(v->name, "pin")) {
|
|
|
|
ast_copy_string(dect_cfg.pin, v->value,
|
|
|
|
sizeof(dect_cfg.pin));
|
|
|
|
} else if (!strcasecmp(v->name, "locate_duration")) {
|
|
|
|
dect_cfg.locate_duration = strtoul(v->value, NULL, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
static int dect_load_module(void)
|
|
|
|
{
|
2010-07-25 14:54:27 +00:00
|
|
|
const char *cluster;
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
sched = sched_context_create();
|
|
|
|
if (sched == NULL) {
|
|
|
|
ast_log(LOG_ERROR, "Unable to create scheduler context\n");
|
|
|
|
goto err1;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = io_context_create();
|
|
|
|
if (io == NULL) {
|
|
|
|
ast_log(LOG_ERROR, "Unable to create IO context\n");
|
|
|
|
goto err2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ast_channel_register(&dect_tech)) {
|
|
|
|
ast_log(LOG_ERROR, "Unable to register 'DECT' channel\n");
|
|
|
|
goto err3;
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_cli_register_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds));
|
2010-10-12 05:34:59 +00:00
|
|
|
ast_register_application_xml("DectUserAuth", dect_userauth_exec);
|
2009-12-16 04:05:47 +00:00
|
|
|
|
|
|
|
dect_load_config();
|
|
|
|
|
|
|
|
if (dect_io_thread_start() < 0)
|
|
|
|
goto err4;
|
|
|
|
|
2010-07-25 14:54:27 +00:00
|
|
|
cluster = strlen(dect_cfg.cluster) ? dect_cfg.cluster : NULL;
|
2010-09-03 07:06:54 +00:00
|
|
|
dh = dect_open_handle(&dect_ops, cluster);
|
|
|
|
if (dh == NULL) {
|
2009-12-16 04:05:47 +00:00
|
|
|
ast_log(LOG_ERROR, "Unable to initialize DECT handle\n");
|
2010-09-03 07:06:54 +00:00
|
|
|
goto err5;
|
2009-12-16 04:05:47 +00:00
|
|
|
}
|
2010-08-16 18:51:29 +00:00
|
|
|
if (dect_llme_rfp_preload_req(dh, &dect_fpc) < 0) {
|
|
|
|
ast_log(LOG_ERROR, "Unable to set FP capabilities\n");
|
|
|
|
goto err6;
|
|
|
|
}
|
2009-12-16 04:05:47 +00:00
|
|
|
|
2010-10-08 13:37:02 +00:00
|
|
|
if (dect_db_read() < 0) {
|
|
|
|
ast_log(LOG_ERROR, "Unable to read database\n");
|
|
|
|
goto err6;
|
|
|
|
}
|
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
return AST_MODULE_LOAD_SUCCESS;
|
2010-01-29 18:08:21 +00:00
|
|
|
|
2009-12-16 04:05:47 +00:00
|
|
|
err6:
|
2010-01-29 18:08:21 +00:00
|
|
|
dect_close_handle(dh);
|
2009-12-16 04:05:47 +00:00
|
|
|
err5:
|
2010-01-29 18:08:21 +00:00
|
|
|
dect_io_thread_stop();
|
2009-12-16 04:05:47 +00:00
|
|
|
err4:
|
2010-01-29 18:08:21 +00:00
|
|
|
ast_cli_unregister_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds));
|
|
|
|
ast_channel_unregister(&dect_tech);
|
2009-12-16 04:05:47 +00:00
|
|
|
err3:
|
2010-01-29 18:08:21 +00:00
|
|
|
io_context_destroy(io);
|
2009-12-16 04:05:47 +00:00
|
|
|
err2:
|
|
|
|
sched_context_destroy(sched);
|
|
|
|
err1:
|
|
|
|
return AST_MODULE_LOAD_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dect_unload_module(void)
|
|
|
|
{
|
2010-01-29 18:08:21 +00:00
|
|
|
dect_close_handle(dh);
|
|
|
|
dect_io_thread_stop();
|
2010-10-12 05:34:59 +00:00
|
|
|
ast_unregister_application("DectUserAuth");
|
2010-01-29 18:08:21 +00:00
|
|
|
ast_cli_unregister_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds));
|
|
|
|
ast_channel_unregister(&dect_tech);
|
|
|
|
io_context_destroy(io);
|
|
|
|
sched_context_destroy(sched);
|
2009-12-16 04:05:47 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "DECT",
|
|
|
|
.load = dect_load_module,
|
|
|
|
.unload = dect_unload_module,
|
|
|
|
);
|