/* * Asterisk DECT Channel driver * * Copyright (c) 2009-2011 Patrick McHardy * * 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" ASTERISK_FILE_VERSION(__FILE__, "$Revision") #include #include #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" #include "asterisk/astdb.h" #include "asterisk/causes.h" #include "asterisk/callerid.h" #include "asterisk/abstract_jb.h" #include #include #include #include #include #define CONFIG_FILE "dect.conf" #define DECT_LOCATE_TIMEOUT_SLACK 10 /* seconds */ static struct ast_channel_tech dect_tech; static struct dect_handle *dh; static struct ast_sched_context *sched; static struct io_context *io; static int access_rights_timer; static const struct ast_jb_conf dect_default_jbconf = { .max_size = -1, .resync_threshold = -1, .impl = "", }; 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, .ehlc2 = DECT_EHLC2_LIST_ACCESS_FEATURES, }; struct { char cluster[DECTNAMSIZ]; 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")]; unsigned int locate_duration; } dect_cfg; static AST_LIST_HEAD_STATIC(dect_pt_list, dect_pt); struct dect_pt { AST_LIST_ENTRY(dect_pt) list; char name[64]; char fullname[64]; char ipei[DECT_IPEI_STRING_LEN + 1]; 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); ); 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; int locate_timer; }; #define dect_pt_log(pt, fmt, args...) \ ast_log(LOG_NOTICE, "PT '%s': " fmt, (pt)->name, ## args) struct dect_pvt { struct dect_pt *pt; struct dect_mm_endpoint *mme; struct dect_call *call; struct ast_channel *chan; int timer_id; }; struct dect_mm_pvt { struct dect_pt *pt; struct dect_mm_endpoint *mme; struct dect_call *call; struct dect_ie_collection *iec; /* authentication */ enum dect_auth_types { DECT_AUTH_NONE, DECT_AUTH_PT, DECT_AUTH_USER, DECT_AUTH_KEY_ALLOCATION, } auth_type; uint64_t rand; uint64_t rs; uint8_t uak[DECT_AUTH_KEY_LEN]; void (*auth_cfm)(struct dect_mm_pvt *, bool, struct dect_ie_collection *); /* ciphering */ bool ciphered; void (*cipher_cfm)(struct dect_mm_pvt *, bool, struct dect_ie_collection *); /* user authentication */ uint8_t upi[DECT_UPI_LEN]; sem_t userauth_semaphore; bool userauth_status; }; #define div_round_up(x, y) (((x) + ((y) - 1)) / (y)) #define dect_ie_update(pos, ie) \ do { \ if (ie == NULL) \ break; \ dect_ie_put(dh, pos); \ pos = dect_ie_hold(ie); \ } while (0) static void dect_authenticate(struct dect_mm_pvt *pvt, enum dect_auth_types type, void (*auth_cfm)(struct dect_mm_pvt *, bool, struct dect_ie_collection *), struct dect_ie_collection *iec); static void dect_cipher(struct dect_mm_pvt *pvt, void (*auth_cfm)(struct dect_mm_pvt *, bool, struct dect_ie_collection *), struct dect_ie_collection *iec); static uint64_t dect_random(void) { return (uint64_t)ast_random() << 32 | ast_random(); } 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; } } 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; } } 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; } 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"); pt->locate_timer = -1; 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) { 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; } 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 */ 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) { ast_db_deltree("dect", pt->ipei); } static int dect_db_put(const struct dect_pt *pt, const char *key, const char *value) { char ptkey[256]; snprintf(ptkey, sizeof(ptkey), "%s/%s", pt->ipei, key); 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]; snprintf(nkey, sizeof(nkey), key, index); 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; if (strncmp(key, "codec_list/", strlen("codec_list/"))) return -1; 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; } if (sscanf(key, "%u/", &i) != 1) return -1; key = strchr(key, '/') + 1; if (!strcmp(key, "codec")) cl->entry[i].codec = dect_parse_num(val); else if (!strcmp(key, "service")) cl->entry[i].service = dect_parse_num(val); else if (!strcmp(key, "slot")) cl->entry[i].slot = dect_parse_num(val); else if (!strcmp(key, "cplane")) cl->entry[i].cplane = dect_parse_num(val); else return -1; if (i + 1 > cl->num) cl->num = i + 1; return 0; } static void dect_db_store_codec_list(const struct dect_pt *pt) { const struct dect_ie_codec_list *cl = pt->codec_list; unsigned int i; for (i = 0; i < cl->num; i++) { 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); } } static int dect_db_parse_termcap(struct dect_pt *pt, const char *key, const char *val) { struct dect_ie_terminal_capability *tc; if (strncmp(key, "capabilities/", strlen("capabilities/"))) return -1; key += strlen("capabilities/"); tc = pt->terminal_capability; if (tc == NULL) { pt->terminal_capability = (void *)dect_ie_alloc(dh, sizeof(*tc)); tc = pt->terminal_capability; } if (!strcmp(key, "tone_capabilities")) tc->tone = dect_parse_num(val); else if (!strcmp(key, "echo_parameters")) tc->echo = dect_parse_num(val); else if (!strcmp(key, "noise_rejection")) tc->noise_rejection = dect_parse_num(val); else if (!strcmp(key, "volume_ctrl")) tc->volume_ctrl = dect_parse_num(val); else if (!strcmp(key, "slot_capabilities")) tc->slot = dect_parse_num(val); else if (!strcmp(key, "display_capabilities")) tc->display = dect_parse_num(val); else if (!strcmp(key, "display_memory")) tc->display_memory = dect_parse_num(val); else if (!strcmp(key, "display_lines")) tc->display_lines = dect_parse_num(val); else if (!strcmp(key, "display_columns")) tc->display_columns = dect_parse_num(val); else if (!strcmp(key, "display_control")) tc->display_control = dect_parse_num(val); else if (!strcmp(key, "display_charsets")) tc->display_charsets = dect_parse_num(val); else if (!strcmp(key, "scrolling")) tc->scrolling = dect_parse_num(val); else if (!strcmp(key, "profile_indicator")) tc->profile_indicator = dect_parse_num(val); else return -1; return 0; } static void dect_db_store_termcap(const struct dect_pt *pt) { const struct dect_ie_terminal_capability *tc = pt->terminal_capability; 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) { if (!strcmp(key, "name")) { ast_copy_string(pt->name, val, sizeof(pt->name)); snprintf(pt->fullname, sizeof(pt->fullname), "DECT/%s", pt->name); } else if (!strcmp(key, "regexten")) dect_pt_set_extension(pt, strtoul(val, NULL, 0)); 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) { dect_db_put(pt, "name", pt->name); 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)); } 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; ast_format_set(&chan->readformat, AST_FORMAT_G726_AAL2, 0); ast_format_set(&chan->writeformat, AST_FORMAT_G726_AAL2, 0); ast_format_cap_add(chan->nativeformats, &chan->readformat); 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; dect_pt_log(pt, "connecting call\n"); 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; struct dect_pt *pt = pvt->pt; struct dect_mncc_connect_param connect = {}; dect_pt_log(pt, "answer call state %u\n", chan->_state); 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; } static void dect_call_auth_cfm(struct dect_mm_pvt *mmp, bool success, struct dect_ie_collection *iec) { struct dect_call *call = mmp->call; struct dect_pvt *pvt = dect_call_priv(call); struct ast_channel *chan = pvt->chan; 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; struct dect_mncc_setup_param param; int presentation; if (!success) return; basic_service.class = DECT_CALL_CLASS_NORMAL; basic_service.service = DECT_SERVICE_BASIC_SPEECH_DEFAULT; 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; memset(¶m, 0, sizeof(param)); 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; dect_mncc_setup_req(dh, pvt->call, &pvt->pt->ipui, ¶m); } 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); ast_setstate(chan, AST_STATE_DIALING); chan->hangupcause = AST_CAUSE_NORMAL_CLEARING; return 0; } static struct ast_channel *dect_request_call(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, void *data, int *cause) { struct dect_call *call; struct dect_pvt *pvt; struct dect_mm_endpoint *mme; struct dect_mm_pvt *mmp; 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); *cause = AST_CAUSE_UNALLOCATED; return NULL; } dect_pt_log(pt, "outgoing call\n"); call = dect_call_alloc(dh); if (call == NULL) { *cause = AST_CAUSE_SWITCH_CONGESTION; return NULL; } 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); pvt->pt = pt; pvt->mme = mme; pvt->call = call; if (dect_init_call(pvt, AST_STATE_RESERVED, NULL) < 0) { *cause = AST_CAUSE_SWITCH_CONGESTION; return NULL; } 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) { dect_pt_log(pvt->pt, "hangup\n"); 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); ast_module_unref(ast_module_info->self); 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; struct dect_pt *pt = pvt->pt; 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: dect_pt_log(pt, "call is ringing\n"); 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; dect_pt_log(pt, "indicate condition %d\n", condition); break; default: dect_pt_log(pt, "indicate unknown condition %d\n", condition); 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; mb.len = f->datalen; dect_dl_u_data_req(dh, pvt->call, &mb); return 0; } static struct ast_frame *dect_read(struct ast_channel *chan) { ast_log(LOG_NOTICE, "audio underrun?\n"); return &ast_null_frame; } static int dect_fixup(struct ast_channel *old, struct ast_channel *new) { ast_log(LOG_NOTICE, "fixup\n"); return 0; } 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; } static struct ast_channel_tech dect_tech = { .type = "DECT", .description = "Digital Enhanced Cordless Telecommunications (DECT)", .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, .send_text = dect_send_text, }; static void dect_mncc_reject(struct dect_call *call, enum dect_release_reasons reason) { 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; } static void dect_mncc_setup_auth_cfm(struct dect_mm_pvt *mmp, bool success, struct dect_ie_collection *iec) { struct dect_mncc_setup_param *param = (void *)iec; struct dect_mncc_setup_ack_param setup_ack; struct dect_mncc_call_proc_param call_proc; struct dect_ie_delimiter_request delimiter_request; struct dect_ie_codec_list codec_list; struct dect_ie_signal signal; struct dect_pt *pt = mmp->pt; struct dect_call *call = mmp->call; struct dect_pvt *pvt = dect_call_priv(call); enum ast_channel_state state; const char *exten; if (!success) return dect_mncc_reject(call, DECT_RELEASE_AUTHENTICATION_FAILED); /* 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)); call_proc.codec_list = dect_select_codec(pt, &codec_list); 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); } 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); struct dect_mm_endpoint *mme; struct dect_mm_pvt *mmp; 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); } dect_pt_log(pt, "incoming call\n"); 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; pvt->pt = pt; pvt->call = call; dect_cipher(mmp, dect_mncc_setup_auth_cfm, ¶m->common); } 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, enum dect_causes cause, struct dect_mncc_release_param *param) { struct dect_pvt *pvt = dect_call_priv(call); int ast_cause; ast_setstate(pvt->chan, AST_STATE_DOWN); if (cause == DECT_CAUSE_PEER_MESSAGE && param->release_reason != NULL) ast_cause = dect_release_reason_to_ast(param->release_reason->reason); else ast_cause = AST_CAUSE_FAILURE; pvt->chan->hangupcause = ast_cause; ast_queue_hangup(pvt->chan); } 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; struct dect_mncc_info_param info = { .signal = &signal, }; 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); signal.code = DECT_SIGNAL_ALERTING_BASE | (atoi(pattern) & 0xf); dect_mncc_info_req(dh, call, &info); } 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, enum dect_causes cause, 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, }; /* * 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) { 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; 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]; uint8_t ac[DECT_AUTH_CODE_LEN]; 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); if (mmp->auth_type == DECT_AUTH_KEY_ALLOCATION) { /* Reset state before invoking completion handler since it may invoke * a new authentication procedure. */ mmp->auth_type = DECT_AUTH_NONE; mmp->auth_cfm = NULL; mmp->iec = NULL; dect_auth_a11(k, mmp->rs, ks); dect_auth_a12(ks, mmp->rand, dck, &res1); if (res1 == param->res->value) { ast_log(LOG_NOTICE, "PT authentication succeeded\n"); rs.value = mmp->rs; 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 */ memcpy(mmp->uak, ks, sizeof(mmp->uak)); auth_cfm(mmp, true, iec); } else { ast_log(LOG_NOTICE, "PT authentication failed\n"); dect_mm_authenticate_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED); auth_cfm(mmp, false, iec); } if (iec != NULL) __dect_ie_collection_put(dh, iec); } 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) { struct dect_mm_pvt *mmp = dect_mm_priv(mme); struct dect_pt *pt = mmp->pt; enum dect_auth_types auth_type = mmp->auth_type; typeof(mmp->auth_cfm) auth_cfm = mmp->auth_cfm; struct dect_ie_collection *iec = mmp->iec; 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. */ mmp->auth_type = DECT_AUTH_NONE; mmp->auth_cfm = NULL; mmp->iec = NULL; if (!accept) goto reject; 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); dect_auth_a11(k, mmp->rs, ks); dect_auth_a12(ks, mmp->rand, dck, &res1); if (res1 == param->res->value) { dect_pt_log(pt, "authentication succeeded\n"); /* Store DCK */ memcpy(pt->dck, dck, sizeof(pt->dck)); auth_cfm(mmp, true, iec); } else { reject: dect_pt_log(pt, "authentication failed\n"); auth_cfm(mmp, false, iec); } if (iec != NULL) __dect_ie_collection_put(dh, iec); } static void dect_authenticate(struct dect_mm_pvt *mmp, enum dect_auth_types type, void (*auth_cfm)(struct dect_mm_pvt *, bool, 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, }; mmp->auth_type = type; mmp->auth_cfm = auth_cfm; if (iec != NULL) mmp->iec = __dect_ie_collection_hold(iec); mmp->rand = dect_random(); mmp->rs = dect_random(); auth_type.auth_id = DECT_AUTH_DSAA; 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; auth_type.auth_key_num = 0 | DECT_AUTH_KEY_IPUI_PARK; auth_type.cipher_key_num = 0; auth_type.flags = DECT_AUTH_FLAG_UPC; rand.value = mmp->rand; rs.value = mmp->rs; dect_mm_authenticate_req(dh, mmp->mme, &req); } static void dect_mm_key_allocate(struct dect_mm_pvt *mmp, void (*auth_cfm)(struct dect_mm_pvt *, bool, 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, }; mmp->auth_type = DECT_AUTH_KEY_ALLOCATION; mmp->auth_cfm = auth_cfm; mmp->iec = __dect_ie_collection_hold(iec); mmp->rand = dect_random(); mmp->rs = dect_random(); 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; rand.value = mmp->rand; rs.value = mmp->rs; dect_mm_key_allocate_req(dh, mmp->mme, &req); } 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) { 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; mmp->ciphered = accept; mmp->cipher_cfm = NULL; mmp->iec = NULL; cipher_cfm(mmp, accept, iec); if (iec != NULL) __dect_ie_collection_put(dh, iec); } static void dect_cipher_auth_cfm(struct dect_mm_pvt *mmp, bool success, struct dect_ie_collection *iec) { struct dect_ie_cipher_info cipher_info; struct dect_mm_cipher_param req = { .cipher_info = &cipher_info, }; if (iec != NULL) mmp->iec = __dect_ie_collection_hold(iec); if (!success) return dect_mm_cipher_cfm(dh, mmp->mme, success, NULL); 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; dect_mm_cipher_req(dh, mmp->mme, &req, mmp->pt->dck); } /* Authenticate the PT, thereby establishing a new DCK, then switch to ciphering. */ static void dect_cipher(struct dect_mm_pvt *mmp, void (*cipher_cfm)(struct dect_mm_pvt *, bool, struct dect_ie_collection *), struct dect_ie_collection *iec) { if (mmp->ciphered) return cipher_cfm(mmp, true, iec); mmp->cipher_cfm = cipher_cfm; if (iec != NULL) mmp->iec = __dect_ie_collection_hold(iec); dect_authenticate(mmp, DECT_AUTH_PT, dect_cipher_auth_cfm, mmp->iec); } /* * Access rights procedures */ 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; } 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); } static void dect_mm_access_rights_auth_cfm(struct dect_mm_pvt *mmp, bool success, struct dect_ie_collection *iec) { struct dect_pt *pt; struct dect_mm_endpoint *mme = mmp->mme; 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); pt = dect_init_portable(name); if (pt == NULL) { dect_mm_access_rights_reject(mme, DECT_REJECT_INSUFFICIENT_MEMORY); return; } dect_pt_set_ipui(pt, ¶m->portable_identity->ipui); dect_pt_set_extension(pt, exten); } dect_mm_access_rights_res(dh, mme, true, &reply); dect_access_rights_requests_disable(); memcpy(pt->uak, mmp->uak, sizeof(pt->uak)); 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); } } 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; struct dect_mm_pvt *mmp = dect_mm_priv(mme); if (!(dect_fpc.hlc & DECT_HLC_ACCESS_RIGHTS_REQUESTS)) return dect_mm_access_rights_reject(mme, DECT_REJECT_INCOMPATIBLE_SERVICE); 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); } mmp->mme = mme; dect_mm_key_allocate(mmp, dect_mm_access_rights_auth_cfm, ¶m->common); } /* * 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); AST_SCHED_DEL(sched, pt->locate_timer); 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); } static void dect_mm_access_rights_terminate_auth_cfm(struct dect_mm_pvt *mmp, bool success, struct dect_ie_collection *iec) { struct dect_pt *pt = mmp->pt; struct dect_mm_endpoint *mme = mmp->mme; if (!success) { dect_mm_access_rights_terminate_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED); return; } dect_db_remove(pt); 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) { struct dect_mm_pvt *mmp = dect_mm_priv(mme); 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); mmp->pt = pt; mmp->mme = mme; dect_cipher(mmp, dect_mm_access_rights_terminate_auth_cfm, ¶m->common); } 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) { struct dect_mm_pvt *mmp = dect_mm_priv(mme); struct dect_pt *pt = mmp->pt; dect_db_remove(pt); 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; struct dect_mm_pvt *mmp; mme = dect_mm_endpoint_alloc(dh, &pt->ipui); if (mme == NULL) return; mmp = dect_mm_priv(mme); mmp->mme = mme; mmp->pt = pt; portable_identity.type = DECT_PORTABLE_ID_TYPE_IPUI; portable_identity.ipui = pt->ipui; dect_mm_access_rights_terminate_req(dh, mme, ¶m); } /* * Location procedures */ 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; } 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); } static void dect_mm_locate_auth_cfm(struct dect_mm_pvt *mmp, bool success, struct dect_ie_collection *iec) { struct dect_pt *pt = mmp->pt; struct dect_mm_endpoint *mme = mmp->mme; 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, }; unsigned int limit, timeout; 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; 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; dect_mm_locate_res(dh, mme, true, &reply); 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); } timeout += DECT_LOCATE_TIMEOUT_SLACK; dect_pt_log(pt, "location registration: timeout: %us\n", timeout); dect_register_extension(pt, true); pt->locate_timer = ast_sched_replace(pt->locate_timer, sched, timeout * 1000, dect_locate_timer, pt); } static void dect_mm_locate_ind(struct dect_handle *dh, struct dect_mm_endpoint *mme, struct dect_mm_locate_param *param) { struct dect_mm_pvt *mmp = dect_mm_priv(mme); 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); mmp->pt = pt; mmp->mme = mme; dect_cipher(mmp, dect_mm_locate_auth_cfm, ¶m->common); } /* * Identity assignment procedures */ 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 = { .priv_size = sizeof(struct dect_mm_pvt), .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, }; /* * 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, }; /* * User authentication App */ static void dect_userauth_auth_cfm(struct dect_mm_pvt *mmp, bool success, struct dect_ie_collection *iec) { mmp->userauth_status = success; sem_post(&mmp->userauth_semaphore); } static int dect_userauth_exec(struct ast_channel *chan, const char *data) { struct dect_pvt *pvt = chan->tech_pvt; struct dect_pt *pt = pvt->pt; struct dect_mm_endpoint *mme; struct dect_mm_pvt *mmp; if (data == NULL) { ast_log(LOG_WARNING, "DectUserAuth requires an argument (PIN)"); return -1; } if (strlen(data) > 8) { ast_log(LOG_WARNING, "User personal identity may not exceed 8 digits"); return -1; } mme = dect_mm_endpoint_get(dh, &pt->ipui); if (mme == NULL) return -1; mmp = dect_mm_priv(mme); mmp->pt = pt; mmp->mme = mme; mmp->call = pvt->call; sem_init(&mmp->userauth_semaphore, 0, 0); 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"); return 0; } static void dect_show_debug(enum dect_debug_subsys subsys, const char *fmt, va_list ap) { ast_verbose_ap(fmt, ap); } /* * Asterisk CLI commands */ 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; } 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; } 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; } ast_cli(a->fd, "Name Extension Registered\n"); AST_LIST_TRAVERSE(&dect_pt_list, pt, list) ast_cli(a->fd, "%-16s%-16s%-16s\n", pt->name, pt->regexten, pt->locate_timer == -1 ? "No" : "Yes"); 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 \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, ""); dect_codecs_merge(codbuf, sizeof(codbuf), pt); ast_cli(a->fd, "IPEI: %s\n", pt->ipei); ast_cli(a->fd, "Extension: %s\n", pt->regexten); ast_cli(a->fd, "Registered: %s\n", pt->locate_timer == -1 ? "No" : "Yes"); 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")) { if (dect_access_rights_requests_enable() == 0) ast_cli(a->fd, "ARI enabled\n"); } else if (!strcasecmp(arg, "disable")) { if (dect_access_rights_requests_disable() == 0) ast_cli(a->fd, "ARI disabled\n"); } 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; } 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; } static struct ast_cli_entry dect_cli_cmds[] = { 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"), AST_CLI_DEFINE(dect_cli_db_reload, "Reload database"), }; /* * 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; dect_fd_process(dh, dfd, events); return 1; } static int dect_register_fd(const struct dect_handle *dh, struct dect_fd *dfd, uint32_t events) { struct dect_fd_priv *priv = dect_fd_priv(dfd); short io_events = 0; if (events & DECT_FD_READ) io_events |= AST_IO_IN; if (events & DECT_FD_WRITE) io_events |= AST_IO_OUT; priv->id = ast_io_add(io, dect_fd_num(dfd), dect_io_callback, io_events, dfd);; return priv->id ? 0 : -1; } static void dect_unregister_fd(const struct dect_handle *dh, struct dect_fd *dfd) { struct dect_fd_priv *priv = dect_fd_priv(dfd); 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;; dect_timer_run(dh, timer); return 0; } static void dect_start_timer(const struct dect_handle *dh, struct dect_timer *timer, const struct timeval *tv) { struct dect_timer_priv *priv = dect_timer_priv(timer); 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) { struct dect_timer_priv *priv = dect_timer_priv(timer); 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, .ss_ops = &dect_ss_ops, }; 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); if (timeout < 0 || timeout > 1000) 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; } static void dect_io_thread_stop(void) { pthread_cancel(io_thread); pthread_kill(io_thread, SIGURG); pthread_join(io_thread, NULL); } static int dect_db_read(void) { struct ast_db_entry *dbt, *dbe; struct dect_ipui ipui; struct dect_pt *pt; const char *key, *val; char *tmp, ipei[64]; memset(&ipui, 0, sizeof(ipui)); ipui.put = DECT_IPUI_N; dbt = ast_db_gettree("dect", NULL); for (dbe = dbt; dbe != NULL; dbe = dbe->next) { key = dbe->key + strlen("/dect/"); strncpy(ipei, key, sizeof(ipei)); tmp = strchr(ipei, '/'); if (tmp == NULL) continue; *tmp = '\0'; key = tmp + 1; val = dbe->data; if (!dect_parse_ipei_string(&ipui.pun.n.ipei, ipei)) continue; pt = dect_pt_get_by_ipui(&ipui); if (pt == NULL) { pt = dect_init_portable(NULL); if (pt == NULL) return -1; dect_pt_set_ipui(pt, &ipui); } 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; } 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; } static int dect_load_module(void) { struct ast_format tmpfmt; const char *cluster; sched = ast_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; } dect_tech.capabilities = ast_format_cap_alloc(); if (dect_tech.capabilities == NULL) goto err3; ast_format_cap_add(dect_tech.capabilities, ast_format_set(&tmpfmt, AST_FORMAT_G726_AAL2, 0)); if (ast_channel_register(&dect_tech)) { ast_log(LOG_ERROR, "Unable to register 'DECT' channel\n"); goto err4; } ast_cli_register_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds)); ast_register_application_xml("DectUserAuth", dect_userauth_exec); dect_load_config(); if (dect_io_thread_start() < 0) goto err5; cluster = strlen(dect_cfg.cluster) ? dect_cfg.cluster : NULL; dh = dect_open_handle(&dect_ops, cluster); if (dh == NULL) { ast_log(LOG_ERROR, "Unable to initialize DECT handle\n"); goto err6; } if (dect_llme_rfp_preload_req(dh, &dect_fpc) < 0) { ast_log(LOG_ERROR, "Unable to set FP capabilities\n"); goto err7; } if (dect_db_read() < 0) { ast_log(LOG_ERROR, "Unable to read database\n"); goto err7; } return AST_MODULE_LOAD_SUCCESS; err7: dect_close_handle(dh); err6: dect_io_thread_stop(); err5: ast_cli_unregister_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds)); ast_channel_unregister(&dect_tech); err4: ast_format_cap_destroy(dect_tech.capabilities); err3: io_context_destroy(io); err2: ast_sched_context_destroy(sched); err1: return AST_MODULE_LOAD_FAILURE; } static int dect_unload_module(void) { dect_close_handle(dh); dect_io_thread_stop(); ast_unregister_application("DectUserAuth"); ast_cli_unregister_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds)); ast_channel_unregister(&dect_tech); ast_format_cap_destroy(dect_tech.capabilities); io_context_destroy(io); ast_sched_context_destroy(sched); return 0; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "DECT", .load = dect_load_module, .unload = dect_unload_module, );