From 070baa9655e321a040a5899d1de043a4ec0433c3 Mon Sep 17 00:00:00 2001 From: Patrick McHardy Date: Fri, 29 Jan 2010 19:08:21 +0100 Subject: [PATCH] chan_dect: add authentication, ciphering and key allocation Signed-off-by: Patrick McHardy --- channels/chan_dect.c | 933 ++++++++++++++++++++++++++++++++++++--- configs/dect.conf.sample | 6 + 2 files changed, 875 insertions(+), 64 deletions(-) diff --git a/channels/chan_dect.c b/channels/chan_dect.c index 7ffb2cff2..1870f1ba9 100644 --- a/channels/chan_dect.c +++ b/channels/chan_dect.c @@ -9,6 +9,11 @@ */ #include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision") + +#include + #include "asterisk/lock.h" #include "asterisk/channel.h" #include "asterisk/config.h" @@ -17,7 +22,9 @@ #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 @@ -25,8 +32,6 @@ #define CONFIG_FILE "dect.conf" -ASTERISK_FILE_VERSION(__FILE__, "$Revision") - static const struct ast_channel_tech dect_tech; static struct dect_handle *dh; static struct sched_context *sched; @@ -39,41 +44,93 @@ static const struct ast_jb_conf dect_default_jbconf = { }; struct { - struct ast_jb_conf jbconf; - char default_context[AST_MAX_CONTEXT]; + 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")]; } dect_cfg; static AST_LIST_HEAD_STATIC(dect_pt_list, dect_pt); struct dect_pt { - AST_LIST_ENTRY(dect_pt) list; - char name[64]; - struct dect_ipui ipui; + AST_LIST_ENTRY(dect_pt) list; + char name[64]; + char fullname[64]; + 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); + 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); ); - unsigned int display_lines; - unsigned int display_columns; + 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; }; struct dect_pvt { - struct dect_pt *pt; - struct dect_call *call; - struct ast_channel *chan; + struct dect_pt *pt; + struct dect_call *call; + struct ast_channel *chan; - AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(cid_num); - AST_STRING_FIELD(cid_name); - AST_STRING_FIELD(display); + struct dect_mm_endpoint *mme; + struct dect_ie_collection *iec; + + /* authentication */ + enum dect_auth_state { + DECT_AUTH_NONE, + DECT_AUTH_PT, + DECT_AUTH_KEY_ALLOCATION, + } auth_state; + uint64_t rand; + uint64_t rs; + uint8_t uak[DECT_AUTH_KEY_LEN]; + void (*auth_cfm)(struct dect_pvt *, bool, + struct dect_ie_collection *); + + /* ciphering */ + bool ciphered; + void (*cipher_cfm)(struct dect_pvt *, bool, + struct dect_ie_collection *); + + AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(cid_num); + AST_STRING_FIELD(cid_name); + AST_STRING_FIELD(display); ); - int timer_id; + int timer_id; }; +#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_pvt *pvt, + void (*auth_cfm)(struct dect_pvt *, bool, + struct dect_ie_collection *), + struct dect_ie_collection *iec); + +static void dect_cipher(struct dect_pvt *pvt, + void (*auth_cfm)(struct dect_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) { @@ -148,21 +205,30 @@ static struct dect_pt *dect_pt_get_by_ipui(const struct dect_ipui *ipui) return 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 struct dect_pt *dect_init_portable(const char *name, const struct ast_variable *v) { struct dect_pt *pt; - pt = ast_malloc(sizeof(*pt)); + pt = ast_calloc(1, sizeof(*pt)); if (pt == NULL) return NULL; - memset(pt, 0, sizeof(*pt)); 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.default_context); + ast_string_field_set(pt, context, dect_cfg.context); ast_string_field_set(pt, regexten, ""); - ast_string_field_set(pt, language, ""); + 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"); @@ -192,13 +258,40 @@ static struct dect_pt *dect_init_portable(const char *name, 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.default_context)) + if (ast_strlen_zero(dect_cfg.regcontext)) return; ast_copy_string(exten, S_OR(pt->regexten, pt->name), sizeof(exten)); @@ -213,12 +306,12 @@ static void dect_register_extension(const struct dect_pt *pt, bool onoff) continue; } } else - context = dect_cfg.default_context; + context = dect_cfg.regcontext; if (onoff) { if (!ast_exists_extension(NULL, context, ext, 1, NULL)) { - ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop", - ast_strdup(pt->name), ast_free, "DECT"); + 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)) { @@ -446,6 +539,20 @@ static int dect_fixup(struct ast_channel *old, struct ast_channel *new) 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 const struct ast_channel_tech dect_tech = { .type = "DECT", .description = "Digital Enhanced Cordless Telecommunications (DECT)", @@ -458,34 +565,92 @@ static const struct ast_channel_tech dect_tech = { .write = dect_write, .read = dect_read, .fixup = dect_fixup, + .send_text = dect_send_text, }; -static void dect_mncc_setup_ind(struct dect_handle *dh, struct dect_call *call, - struct dect_mncc_setup_param *param) +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_pvt *pvt, 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_mncc_release_param release; - struct dect_ie_release_reason release_reason; struct dect_ie_delimiter_request delimiter_request; + struct dect_ie_codec_list codec_list; struct dect_ie_signal signal; - struct dect_pvt *pvt = dect_call_priv(call); - struct dect_pt *pt; + struct dect_call *call = pvt->call; + struct dect_pt *pt = pvt->pt; enum ast_channel_state state; const char *exten; - pt = dect_pt_get_by_ipui(dect_call_portable_identity(call)); - if (pt == NULL) { - ast_log(LOG_NOTICE, "Incoming call from unknown PT\n"); - - memset(&release, 0, sizeof(release)); - release.release_reason = dect_ie_init(&release_reason); - release_reason.reason = DECT_RELEASE_UNKNOWN_IDENTITY; - dect_mncc_reject_req(dh, call, &release); - return; - } - - ast_log(LOG_NOTICE, "Incoming call from %s\n", pt->name); + 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 @@ -513,6 +678,7 @@ static void dect_mncc_setup_ind(struct dect_handle *dh, struct dect_call *call, 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); @@ -524,26 +690,44 @@ static void dect_mncc_setup_ind(struct dect_handle *dh, struct dect_call *call, dect_mncc_setup_ack_req(dh, call, &setup_ack); } - pvt->pt = pt; - pvt->call = call; 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_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); + } + + ast_log(LOG_NOTICE, "Incoming call from %s\n", pt->name); + + pvt->pt = pt; + pvt->call = call; + + dect_mncc_setup_auth_cfm(pvt, true, ¶m->common); + //dect_cipher(pvt, 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) { - ast_log(LOG_NOTICE, "MNCC_SETUP_ACK-ind\n"); } static void dect_mncc_reject_ind(struct dect_handle *dh, struct dect_call *call, struct dect_mncc_release_param *param) { - ast_log(LOG_NOTICE, "MNCC_REJECT-ind\n"); } #if 0 @@ -573,8 +757,6 @@ static void dect_mncc_alert_ind(struct dect_handle *dh, struct dect_call *call, char pattern[16]; const char *c; - ast_log(LOG_NOTICE, "MNCC_ALERT-ind\n"); - ast_copy_string(pattern, pvt->pt->ring_pattern, sizeof(pattern)); c = pbx_builtin_getvar_helper(pvt->chan, "RING_PATTERN"); if (c != NULL) @@ -614,7 +796,6 @@ static void dect_mncc_connect_ind(struct dect_handle *dh, struct dect_call *call { struct dect_pvt *pvt = dect_call_priv(call); - ast_log(LOG_NOTICE, "MNCC_CONNECT-ind\n"); ast_queue_control(pvt->chan, AST_CONTROL_ANSWER); dect_mncc_connect_res(dh, call, param); } @@ -624,7 +805,6 @@ static void dect_mncc_release_ind(struct dect_handle *dh, struct dect_call *call { struct dect_pvt *pvt = dect_call_priv(call); - ast_log(LOG_NOTICE, "MNCC_RELEASE-ind\n"); ast_setstate(pvt->chan, AST_STATE_DOWN); pvt->chan->hangupcause = dect_release_reason_to_ast(param->release_reason->reason); @@ -636,7 +816,6 @@ static void dect_mncc_release_ind(struct dect_handle *dh, struct dect_call *call static void dect_mncc_release_cfm(struct dect_handle *dh, struct dect_call *call, struct dect_mncc_release_param *param) { - ast_log(LOG_NOTICE, "MNCC_RELEASE-cfm\n"); } static void dect_dl_u_data_ind(struct dect_handle *dh, struct dect_call *call, @@ -710,23 +889,496 @@ static const struct dect_cc_ops dect_cc_ops = { .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_pvt *pvt = dect_mm_priv(mme); + typeof(pvt->auth_cfm) auth_cfm = pvt->auth_cfm; + struct dect_ie_collection *iec = pvt->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[4]; + 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 (pvt->auth_state == DECT_AUTH_KEY_ALLOCATION) { + /* Reset state before invoking completion handler since it may invoke + * a new authentication procedure. + */ + pvt->auth_state = DECT_AUTH_NONE; + pvt->auth_cfm = NULL; + + dect_auth_a11(k, pvt->rs, ks); + dect_auth_a12(ks, pvt->rand, dck, &res1); + + if (res1 == param->res->value) { + ast_log(LOG_NOTICE, "PT authentication succeeded\n"); + + rs.value = pvt->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(pvt->uak, ks, sizeof(pvt->uak)); + auth_cfm(pvt, true, iec); + } else { + ast_log(LOG_NOTICE, "PT authentication failed\n"); + dect_mm_authenticate_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED); + auth_cfm(pvt, false, iec); + } + __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_pvt *pvt = dect_mm_priv(mme); + struct dect_pt *pt = pvt->pt; + typeof(pvt->auth_cfm) auth_cfm = pvt->auth_cfm; + struct dect_ie_collection *iec = pvt->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. + */ + pvt->auth_state = DECT_AUTH_NONE; + pvt->auth_cfm = NULL; + + if (!accept) + goto reject; + + dect_auth_b1(pt->uak, sizeof(pt->uak), k); + + dect_auth_a11(k, pvt->rs, ks); + dect_auth_a12(ks, pvt->rand, dck, &res1); + + if (res1 == param->res->value) { + ast_log(LOG_NOTICE, "PT authentication succeeded\n"); + + /* Store DCK */ + memcpy(pt->dck, dck, sizeof(pt->dck)); + auth_cfm(pvt, true, iec); + } else { +reject: + ast_log(LOG_NOTICE, "PT authentication failed\n"); + auth_cfm(pvt, false, iec); + } + + __dect_ie_collection_put(dh, iec); +} + +static void dect_authenticate(struct dect_pvt *pvt, + void (*auth_cfm)(struct dect_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, + }; + + pvt->auth_state = DECT_AUTH_PT; + pvt->auth_cfm = auth_cfm; + pvt->iec = __dect_ie_collection_hold(iec); + + pvt->rand = dect_random(); + pvt->rs = dect_random(); + + auth_type.auth_id = DECT_AUTH_DSAA; + auth_type.auth_key_type = DECT_KEY_USER_AUTHENTICATION_KEY; + 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 = pvt->rand; + rs.value = pvt->rs; + + dect_mm_authenticate_req(dh, pvt->mme, &req); +} + +static void dect_mm_key_allocate(struct dect_pvt *pvt, + void (*auth_cfm)(struct dect_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, + }; + + pvt->auth_state = DECT_AUTH_KEY_ALLOCATION; + pvt->auth_cfm = auth_cfm; + pvt->iec = __dect_ie_collection_hold(iec); + + pvt->rand = dect_random(); + pvt->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 = pvt->rand; + rs.value = pvt->rs; + + dect_mm_key_allocate_req(dh, pvt->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_pvt *pvt = dect_mm_priv(mme); + typeof(pvt->cipher_cfm) cipher_cfm = pvt->cipher_cfm; + struct dect_ie_collection *iec = pvt->iec; + + pvt->cipher_cfm = NULL; + cipher_cfm(pvt, accept, iec); + __dect_ie_collection_put(dh, iec); +} + +static void dect_cipher_auth_cfm(struct dect_pvt *pvt, 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 (!success) + return dect_mm_cipher_cfm(dh, pvt->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, pvt->mme, &req, pvt->pt->dck); +} + +/* Authenticate the PT, thereby establishing a new DCK, then switch to ciphering. */ +static void dect_cipher(struct dect_pvt *pvt, + void (*cipher_cfm)(struct dect_pvt *, bool, + struct dect_ie_collection *), + struct dect_ie_collection *iec) +{ + if (pvt->ciphered) + return cipher_cfm(pvt, true, iec); + + pvt->cipher_cfm = cipher_cfm; + pvt->iec = __dect_ie_collection_hold(iec); + + dect_authenticate(pvt, dect_cipher_auth_cfm, pvt->iec); +} + +/* + * Access rights procedures + */ + +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_pvt *pvt, bool success, + struct dect_ie_collection *iec) +{ + struct dect_pt *pt; + struct dect_mm_endpoint *mme = pvt->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, NULL); + if (pt == NULL) { + dect_mm_access_rights_reject(mme, DECT_REJECT_INSUFFICIENT_MEMORY); + return; + } + + ast_string_field_build(pt, cid_num, "%u", exten); + ast_string_field_build(pt, regexten, "%u", exten); + + memcpy(&pt->ipui, ¶m->portable_identity->ipui, sizeof(pt->ipui)); + dect_init_tpui(&pt->tpui, exten); + } + + if (dect_mm_access_rights_res(dh, mme, true, &reply) < 0) + return; + + memcpy(pt->uak, pvt->uak, sizeof(pt->uak)); + dect_ie_update(pt->terminal_capability, param->terminal_capability); + dect_ie_update(pt->codec_list, param->codec_list); +} + +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_pvt *pvt = dect_mm_priv(mme); + + 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); + } + + pvt->mme = mme; + + dect_mm_key_allocate(pvt, 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_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_pvt *pvt, bool success, + struct dect_ie_collection *iec) +{ + struct dect_pt *pt = pvt->pt; + struct dect_mm_endpoint *mme = pvt->mme; + + if (!success) { + dect_mm_access_rights_terminate_reject(mme, DECT_REJECT_AUTHENTICATION_FAILED); + return; + } + + 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_pvt *pvt = 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); + + pvt->pt = pt; + pvt->mme = mme; + + dect_cipher(pvt, 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_pvt *pvt = dect_mm_priv(mme); + struct dect_pt *pt = pvt->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_pvt *pvt; + + mme = dect_mm_endpoint_alloc(dh, &pt->ipui); + if (mme == NULL) + return; + pvt = dect_mm_priv(mme); + pvt->mme = mme; + pvt->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 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_pvt *pvt, bool success, + struct dect_ie_collection *iec) +{ + struct dect_pt *pt = pvt->pt; + struct dect_mm_endpoint *mme = pvt->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, + }; + + 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; + duration.time = DECT_TIME_LIMIT_DEFINED_TIME_LIMIT_1; + duration.duration = 1; + + if (dect_mm_locate_res(dh, mme, true, &reply) < 0) + return; + + dect_ie_update(pt->terminal_capability, param->terminal_capability); + dect_ie_update(pt->codec_list, param->codec_list); + + dect_register_extension(pt, true); +} + static void dect_mm_locate_ind(struct dect_handle *dh, struct dect_mm_endpoint *mme, struct dect_mm_locate_param *param) { - ast_log(LOG_NOTICE, "MM_LOCATE-ind\n"); + struct dect_pvt *pvt = 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); + + pvt->pt = pt; + pvt->mme = mme; + + dect_cipher(pvt, 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) { - ast_log(LOG_NOTICE, "MN_IDENTITY_ASSIGN-cfm\n"); } static const struct dect_mm_ops dect_mm_ops = { - .mm_locate_ind = dect_mm_locate_ind, - .mm_identity_assign_cfm = dect_mm_identity_assign_cfm, + .priv_size = sizeof(struct dect_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, }; static int dect_show_debug(const char *fmt, va_list ap) @@ -763,8 +1415,21 @@ static int dect_load_config(void) continue; if (!strcasecmp(v->name, "context")) { - ast_copy_string(dect_cfg.default_context, v->value, - sizeof(dect_cfg.default_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)); } } @@ -785,6 +1450,23 @@ static int dect_load_config(void) * 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) { @@ -812,8 +1494,112 @@ static char *dect_cli_debug(struct ast_cli_entry *e, int cmd, 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\n"); + AST_LIST_TRAVERSE(&dect_pt_list, pt, list) + ast_cli(a->fd, "%-16s%s\n", pt->name, pt->regexten); + + 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, "Extension: %s\n", pt->regexten); + 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")) { + ast_cli(a->fd, "ARI enabled\n"); + } else if (!strcasecmp(arg, "disable")) { + 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 struct ast_cli_entry dect_cli_cmds[] = { - AST_CLI_DEFINE(dect_cli_debug, "Enable/Disable DECT debugging"), + 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"), }; /* @@ -933,6 +1719,13 @@ static int dect_io_thread_start(void) 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_load_module(void) { sched = sched_context_create(); @@ -971,10 +1764,16 @@ static int dect_load_module(void) } return AST_MODULE_LOAD_SUCCESS; + err6: + dect_close_handle(dh); err5: + dect_io_thread_stop(); err4: + ast_cli_unregister_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds)); + ast_channel_unregister(&dect_tech); err3: + io_context_destroy(io); err2: sched_context_destroy(sched); err1: @@ -983,6 +1782,12 @@ err1: static int dect_unload_module(void) { + dect_close_handle(dh); + dect_io_thread_stop(); + 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); return 0; } diff --git a/configs/dect.conf.sample b/configs/dect.conf.sample index 778df689b..d7fc4c627 100644 --- a/configs/dect.conf.sample +++ b/configs/dect.conf.sample @@ -1,5 +1,11 @@ [general] context = default +language = de + +; Registration context for dynamic registrations +regcontext = dect_register +regexten_base = 600 +pin = 1234 ;------------------------------ JITTER BUFFER CONFIGURATION -------------------------- ; jbenable = yes ; Enables the use of a jitterbuffer on the receiving side of an