diff options
Diffstat (limited to 'src/gsm')
-rw-r--r-- | src/gsm/Makefile.am | 4 | ||||
-rw-r--r-- | src/gsm/comp128v23.c | 6 | ||||
-rw-r--r-- | src/gsm/gsm0411_utils.c | 3 | ||||
-rw-r--r-- | src/gsm/gsm0480.c | 135 | ||||
-rw-r--r-- | src/gsm/gsm0808.c | 21 | ||||
-rw-r--r-- | src/gsm/gsm48.c | 20 | ||||
-rw-r--r-- | src/gsm/ipa.c | 112 | ||||
-rw-r--r-- | src/gsm/libosmogsm.map | 12 | ||||
-rw-r--r-- | src/gsm/oap_client.c | 280 |
9 files changed, 555 insertions, 38 deletions
diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am index 5387e3ab..29299a64 100644 --- a/src/gsm/Makefile.am +++ b/src/gsm/Makefile.am @@ -1,7 +1,7 @@ # This is _NOT_ the library release version, it's an API version. # Please read chapter "Library interface versions" of the libtool documentation # before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html -LIBVERSION=9:0:0 +LIBVERSION=10:0:0 AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS) AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN} @@ -30,7 +30,7 @@ libgsmint_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \ milenage/aes-internal.c milenage/aes-internal-enc.c \ milenage/milenage.c gan.c ipa.c gsm0341.c apn.c \ gsup.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \ - gsm23003.c mncc.c bts_features.c + gsm23003.c mncc.c bts_features.c oap_client.c libgsmint_la_LDFLAGS = -no-undefined libgsmint_la_LIBADD = $(top_builddir)/src/libosmocore.la diff --git a/src/gsm/comp128v23.c b/src/gsm/comp128v23.c index 68f4b2a3..550f6a49 100644 --- a/src/gsm/comp128v23.c +++ b/src/gsm/comp128v23.c @@ -1,8 +1,10 @@ /*! \file comp128v23.c * COMP128 version 2 and 3 implementation, common algorithm used for GSM Authentication (A3/A8). * - * This code is a C conversion of the original code from - * http://www.hackingprojects.net/ + * This code is a C conversion of the original code by Tamas Jos <info@skelsec.com> from: + * - original (out of service): http://www.hackingprojects.net/ + * - original (archive): https://web.archive.org/web/20130730113347/http://www.hackingprojects.net/ + * - new site: https://github.com/skelsec/COMP128 */ /* * (C) 2013 by Kévin Redon <kevredon@mail.tsaitgaist.info> diff --git a/src/gsm/gsm0411_utils.c b/src/gsm/gsm0411_utils.c index 53d37a43..ccefe546 100644 --- a/src/gsm/gsm0411_utils.c +++ b/src/gsm/gsm0411_utils.c @@ -35,7 +35,6 @@ #include <osmocom/core/logging.h> #include <osmocom/gsm/gsm48.h> -#include <osmocom/gsm/gsm0480.h> #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/protocol/gsm_03_40.h> #include <osmocom/gsm/protocol/gsm_04_11.h> @@ -354,7 +353,7 @@ int gsm411_push_cp_header(struct msgb *msg, uint8_t proto, uint8_t trans, uint8_t msg_type) { /* Outgoing proto_discr needs the highest bit set */ - gsm0480_l3hdr_push(msg, proto | (trans << 4), msg_type); + gsm48_push_l3hdr_tid(msg, proto, trans, msg_type); return 0; } diff --git a/src/gsm/gsm0480.c b/src/gsm/gsm0480.c index 165b309f..7756ecba 100644 --- a/src/gsm/gsm0480.c +++ b/src/gsm/gsm0480.c @@ -25,6 +25,7 @@ * */ +#include <osmocom/gsm/gsm48.h> #include <osmocom/gsm/gsm0480.h> #include <osmocom/gsm/gsm_utils.h> @@ -87,6 +88,15 @@ static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, uint8_t tag, return data; } +static inline unsigned char *msgb_push_NULL(struct msgb *msgb) +{ + uint8_t *data = msgb_push(msgb, 2); + + data[0] = ASN1_NULL_TYPE_TAG; + data[1] = 0; + return data; +} + /* wrap an invoke around it... the other way around * * 1.) Invoke Component tag @@ -122,7 +132,7 @@ struct msgb *gsm0480_create_unstructuredSS_Notify(int alertPattern, const char * uint8_t *seq_len_ptr, *ussd_len_ptr, *data; int len; - msg = msgb_alloc_headroom(1024, 128, "GSM 04.80"); + msg = gsm0480_msgb_alloc_name("TS 04.80 USSD Notify"); if (!msg) return NULL; @@ -168,7 +178,7 @@ struct msgb *gsm0480_create_notifySS(const char *text) if (len < 1 || len > 160) return NULL; - msg = msgb_alloc_headroom(1024, 128, "GSM 04.80"); + msg = gsm0480_msgb_alloc_name("TS 04.80 NotifySS"); if (!msg) return NULL; @@ -787,13 +797,22 @@ static int parse_ss_for_bs_req(const uint8_t *ss_req_data, return rc; } -struct msgb *gsm0480_create_ussd_resp(uint8_t invoke_id, uint8_t trans_id, const char *text) +struct msgb *gsm0480_msgb_alloc_name(const char *name) +{ + return msgb_alloc_headroom(1024, 128, name); +} + +/*! Generate a USSD ReturnResult component containing a string in default GSM alphabet. + * \param[in] invoke_id InvokeID of the request to which we respond + * \param[in] text USSD text in ASCII; to be encoded as GSM 7-but alphabet + */ +struct msgb *gsm0480_gen_ussd_resp_7bit(uint8_t invoke_id, const char *text) { struct msgb *msg; uint8_t *ptr8; int response_len; - msg = msgb_alloc_headroom(1024, 128, "GSM 04.80"); + msg = gsm0480_msgb_alloc_name("TS 04.80 USSD Resp"); if (!msg) return NULL; @@ -824,25 +843,95 @@ struct msgb *gsm0480_create_ussd_resp(uint8_t invoke_id, uint8_t trans_id, const /* Wrap this up as a Return Result component */ msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT); + return msg; +} + +/*! Legacy helper: Generate USSD response including FACILITY IE + L3 header. + * + * This function is just like \ref gsm0480_gen_ussd_resp_7bit, but it generates + * not only the FACILITY value, but the full L3 message including message header + * and FACILITY IE Tag+Length. + */ +struct msgb *gsm0480_create_ussd_resp(uint8_t invoke_id, uint8_t trans_id, const char *text) +{ + struct msgb *msg; + + msg = gsm0480_gen_ussd_resp_7bit(invoke_id, text); + if (!msg) + return NULL; + /* Wrap the component in a Facility message */ msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); /* And finally pre-pend the L3 header */ - gsm0480_l3hdr_push(msg, - GSM48_PDISC_NC_SS | trans_id - | (1<<7) /* TI direction = 1 */, - GSM0480_MTYPE_RELEASE_COMPLETE); + gsm48_push_l3hdr_tid(msg, GSM48_PDISC_NC_SS, + /* FIXME: TI direction is always 1 ?!? */ + trans_id | (1 << 7), + GSM0480_MTYPE_RELEASE_COMPLETE); + + return msg; +} + +/*! Generate a ReturnError component (see section 3.6.1) and given error code (see section 3.6.6). + * \param[in] invoke_id InvokeID of the request + * \param[in] error_code Error code (section 4.5) + * \return message buffer containing the Reject component + * + * Note: if InvokeID is not available, e.g. when message parsing failed, any incorrect vlue + * can be passed (0x00 > x > 0xff), so the universal NULL-tag (see table 3.6) will be used instead. + */ +struct msgb *gsm0480_gen_return_error(uint8_t invoke_id, uint8_t error_code) +{ + struct msgb *msg; + + msg = gsm0480_msgb_alloc_name("TS 04.80 ReturnError"); + if (!msg) + return NULL; + + /* First insert the problem code */ + msgb_push_TLV1(msg, GSM_0480_ERROR_CODE_TAG, error_code); + + /* Before it, insert the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, invoke_id); + + /* Wrap this up as a Reject component */ + msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_ERROR); + + /* FIXME: Wrap in Facility + L3? */ return msg; } -struct gsm48_hdr *gsm0480_l3hdr_push(struct msgb *msg, uint8_t proto_discr, - uint8_t msg_type) +/*! Generate a Reject component (see section 3.6.1) and given error code (see section 3.6.7). + * \param[in] invoke_id InvokeID of the request + * \param[in] problem_tag Problem code tag (table 3.13) + * \param[in] problem_code Problem code (table 3.14-3.17) + * \return message buffer containing the Reject component + * + * Note: if InvokeID is not available, e.g. when message parsing failed, any incorrect vlue + * can be passed (0x00 > x > 0xff), so the universal NULL-tag (see table 3.6) will be used instead. + */ +struct msgb *gsm0480_gen_reject(int invoke_id, uint8_t problem_tag, uint8_t problem_code) { - struct gsm48_hdr *gh; - gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); - gh->proto_discr = proto_discr; - gh->msg_type = msg_type; - return gh; + struct msgb *msg; + + msg = gsm0480_msgb_alloc_name("TS 04.80 Reject"); + if (!msg) + return NULL; + + /* First insert the problem code */ + msgb_push_TLV1(msg, problem_tag, problem_code); + + /* If the Invoke ID is not available, Universal NULL (table 3.9) with length=0 shall be used */ + if (invoke_id < 0 || invoke_id > 255) + msgb_push_NULL(msg); + else + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, invoke_id); + + /* Wrap this up as a Reject component */ + msgb_wrap_with_TL(msg, GSM0480_CTYPE_REJECT); + + /* FIXME: Wrap in Facility + L3? */ + return msg; } struct msgb *gsm0480_create_ussd_notify(int level, const char *text) @@ -856,7 +945,11 @@ struct msgb *gsm0480_create_ussd_notify(int level, const char *text) gsm0480_wrap_invoke(msg, GSM0480_OP_CODE_USS_NOTIFY, 0); gsm0480_wrap_facility(msg); - gsm0480_l3hdr_push(msg, GSM48_PDISC_NC_SS, GSM0480_MTYPE_REGISTER); + /* And finally pre-pend the L3 header */ + gsm48_push_l3hdr(msg, GSM48_PDISC_NC_SS, + /* FIXME: no transactionID?!? */ + GSM0480_MTYPE_REGISTER); + return msg; } @@ -864,12 +957,14 @@ struct msgb *gsm0480_create_ussd_release_complete(void) { struct msgb *msg; - msg = msgb_alloc_headroom(1024, 128, "GSM 04.80 USSD REL COMPL"); + msg = gsm0480_msgb_alloc_name("TS 04.80 USSD REL COMPL"); if (!msg) return NULL; - /* FIXME: should this set trans_id and TI direction flag? */ - gsm0480_l3hdr_push(msg, GSM48_PDISC_NC_SS, - GSM0480_MTYPE_RELEASE_COMPLETE); + /* And finally pre-pend the L3 header */ + gsm48_push_l3hdr(msg, GSM48_PDISC_NC_SS, + /* FIXME: no transactionID?!? */ + GSM0480_MTYPE_RELEASE_COMPLETE); + return msg; } diff --git a/src/gsm/gsm0808.c b/src/gsm/gsm0808.c index a90aa227..e3b10d0c 100644 --- a/src/gsm/gsm0808.c +++ b/src/gsm/gsm0808.c @@ -588,7 +588,7 @@ struct msgb *gsm0808_create_clear_rqst(uint8_t cause) /*! Create BSSMAP PAGING message * \param[in] imsi Mandatory paged IMSI in string representation * \param[in] tmsi Optional paged TMSI - * \param[in] cil Cell Identity List (where to page) + * \param[in] cil Mandatory Cell Identity List (where to page) * \param[in] chan_needed Channel Type needed * \returns callee-allocated msgb with BSSMAP PAGING message */ struct msgb *gsm0808_create_paging2(const char *imsi, const uint32_t *tmsi, @@ -615,7 +615,7 @@ struct msgb *gsm0808_create_paging2(const char *imsi, const uint32_t *tmsi, /* Message Type 3.2.2.1 */ msgb_v_put(msg, BSS_MAP_MSG_PAGING); - /* IMSI 3.2.2.6 */ + /* mandatory IMSI 3.2.2.6 */ mid_len = gsm48_generate_mid_from_imsi(mid_buf, imsi); msgb_tlv_put(msg, GSM0808_IE_IMSI, mid_len - 2, mid_buf + 2); @@ -626,9 +626,8 @@ struct msgb *gsm0808_create_paging2(const char *imsi, const uint32_t *tmsi, (uint8_t *) & tmsi_sw); } - /* Cell Identifier List 3.2.2.27 */ - if (cil) - gsm0808_enc_cell_id_list2(msg, cil); + /* mandatory Cell Identifier List 3.2.2.27 */ + gsm0808_enc_cell_id_list2(msg, cil); /* Channel Needed 3.2.2.36 */ if (chan_needed) { @@ -763,6 +762,9 @@ struct msgb *gsm0808_create_handover_request_ack(const uint8_t *l3_info, uint8_t if (chosen_speech_version != 0) msgb_tv_put(msg, GSM0808_IE_SPEECH_VERSION, chosen_speech_version); + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + return msg; } @@ -780,6 +782,9 @@ struct msgb *gsm0808_create_handover_detect() /* Message Type, 3.2.2.1 */ msgb_v_put(msg, BSS_MAP_MSG_HANDOVER_DETECT); + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + return msg; } @@ -816,6 +821,9 @@ struct msgb *gsm0808_create_handover_complete(const struct gsm0808_handover_comp if (params->lcls_bss_status_present) msgb_tv_put(msg, GSM0808_IE_LCLS_BSS_STATUS, params->lcls_bss_status); + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + return msg; } @@ -843,6 +851,9 @@ struct msgb *gsm0808_create_handover_failure(const struct gsm0808_handover_failu if (params->codec_list_bss_supported.len) gsm0808_enc_speech_codec_list(msg, ¶ms->codec_list_bss_supported); + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + return msg; } diff --git a/src/gsm/gsm48.c b/src/gsm/gsm48.c index b4892dea..136b9375 100644 --- a/src/gsm/gsm48.c +++ b/src/gsm/gsm48.c @@ -1024,4 +1024,24 @@ const struct value_string gsm48_reject_value_names[] = { { 0, NULL } }; +/*! Wrap a given \ref msg with \ref gsm48_hdr structure + * \param[out] msg A message to be wrapped + * \param[in] pdisc GSM TS 04.07 protocol discriminator 1/2, + * sub-pdisc, trans_id or skip_ind 1/2, + * see section 11.2.3.1 for details + * \param[in] msg_type GSM TS 04.08 message type + * @return pointer to pushed header within \ref msg + */ +struct gsm48_hdr *gsm48_push_l3hdr(struct msgb *msg, + uint8_t pdisc, uint8_t msg_type) +{ + struct gsm48_hdr *gh; + + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + gh->proto_discr = pdisc; + gh->msg_type = msg_type; + + return gh; +} + /*! @} */ diff --git a/src/gsm/ipa.c b/src/gsm/ipa.c index 0c7aaad6..d423c262 100644 --- a/src/gsm/ipa.c +++ b/src/gsm/ipa.c @@ -137,6 +137,83 @@ int ipa_ccm_idtag_parse_off(struct tlv_parsed *dec, unsigned char *buf, int len, return 0; } +/*! Parse the payload part of an IPA CCM ID GET, return \ref tlv_parsed format. + * The odd payload format of those messages is structured as follows: + * * 8bit length value (length of payload *and tag*) + * * 8bit tag value + * * optional, variable-length payload + * \param[out] dec Caller-provided/allocated output structure for parsed payload + * \param[in] buf Buffer containing the payload (excluding 1 byte msg_type) of the message + * \param[in] len Length of \a buf in octets + * \returns 0 on success; negative on error */ +int ipa_ccm_id_get_parse(struct tlv_parsed *dec, const uint8_t *buf, unsigned int len) +{ + uint8_t t_len; + uint8_t t_tag; + const uint8_t *cur = buf; + + memset(dec, 0, sizeof(*dec)); + + while (len >= 2) { + len -= 2; + t_len = *cur++; + t_tag = *cur++; + + if (t_len > len + 1) { + LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d > %d\n", t_len, len + 1); + return -EINVAL; + } + + DEBUGPC(DLMI, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur); + + dec->lv[t_tag].len = t_len-1; + dec->lv[t_tag].val = cur; + + cur += t_len-1; + len -= t_len-1; + } + return 0; +} + +/*! Parse the payload part of an IPA CCM ID RESP, return \ref tlv_parsed format. + * The odd payload format of those messages is structured as follows: + * * 16bit length value (length of payload *and tag*) + * * 8bit tag value + * * optional, variable-length payload + * \param[out] dec Caller-provided/allocated output structure for parsed payload + * \param[in] buf Buffer containing the payload (excluding 1 byte msg_type) of the message + * \param[in] len Length of \a buf in octets + * \returns 0 on success; negative on error */ +int ipa_ccm_id_resp_parse(struct tlv_parsed *dec, const uint8_t *buf, unsigned int len) +{ + uint8_t t_len; + uint8_t t_tag; + const uint8_t *cur = buf; + + memset(dec, 0, sizeof(*dec)); + + while (len >= 3) { + len -= 3; + t_len = *cur++ << 8; + t_len += *cur++; + t_tag = *cur++; + + if (t_len > len + 1) { + LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d > %d\n", t_len, len + 1); + return -EINVAL; + } + + DEBUGPC(DLMI, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur); + + dec->lv[t_tag].len = t_len-1; + dec->lv[t_tag].val = cur; + + cur += t_len-1; + len -= t_len-1; + } + return 0; +} + int ipa_parse_unitid(const char *str, struct ipaccess_unit *unit_data) { unsigned long ul; @@ -251,23 +328,23 @@ struct msgb *ipa_ccm_make_id_resp(const struct ipaccess_unit *dev, break; case IPAC_IDTAG_LOCATION1: if (dev->location1) - strncpy(str, dev->location1, IPA_STRING_MAX); + osmo_strlcpy(str, dev->location1, sizeof(str)); break; case IPAC_IDTAG_LOCATION2: if (dev->location2) - strncpy(str, dev->location2, IPA_STRING_MAX); + osmo_strlcpy(str, dev->location2, sizeof(str)); break; case IPAC_IDTAG_EQUIPVERS: if (dev->equipvers) - strncpy(str, dev->equipvers, IPA_STRING_MAX); + osmo_strlcpy(str, dev->equipvers, sizeof(str)); break; case IPAC_IDTAG_SWVERSION: if (dev->swversion) - strncpy(str, dev->swversion, IPA_STRING_MAX); + osmo_strlcpy(str, dev->swversion, sizeof(str)); break; case IPAC_IDTAG_UNITNAME: if (dev->unit_name) { - snprintf(str, sizeof(str), dev->unit_name, IPA_STRING_MAX); + snprintf(str, sizeof(str), "%s", dev->unit_name); } else { snprintf(str, sizeof(str), "%02x-%02x-%02x-%02x-%02x-%02x", @@ -278,7 +355,7 @@ struct msgb *ipa_ccm_make_id_resp(const struct ipaccess_unit *dev, break; case IPAC_IDTAG_SERNR: if (dev->serno) - strncpy(str, dev->serno, IPA_STRING_MAX); + osmo_strlcpy(str, dev->serno, sizeof(str)); break; default: LOGP(DLINP, LOGL_NOTICE, @@ -286,7 +363,6 @@ struct msgb *ipa_ccm_make_id_resp(const struct ipaccess_unit *dev, msgb_free(msg); return NULL; } - str[IPA_STRING_MAX-1] = '\0'; LOGP(DLINP, LOGL_INFO, " tag %d: %s\n", ies_req[i], str); tag = msgb_put(msg, 3 + strlen(str) + 1); @@ -452,6 +528,9 @@ void ipa_prepend_header(struct msgb *msg, int proto) #ifdef HAVE_SYS_SOCKET_H #include <sys/socket.h> +/*! Read one ipa message from socket fd without caching not fully received + * messages. See \ref ipa_msg_recv_buffered for further information. + */ int ipa_msg_recv(int fd, struct msgb **rmsg) { int rc = ipa_msg_recv_buffered(fd, rmsg, NULL); @@ -462,6 +541,25 @@ int ipa_msg_recv(int fd, struct msgb **rmsg) return rc; } +/*! Read one ipa message from socket fd or store part if still not fully received. + * \param[in] fd The fd for the socket to read from. + * \param[out] rmsg internally allocated msgb containing a fully received ipa message. + * \param[inout] tmp_msg internally allocated msgb caching data for not yet fully received message. + * + * As ipa can run on top of stream based protocols such as TCP, there's the + * possibility that such lower layers split ipa messages in several low level + * packets. If a low layer packet is received containing several ipa frames, + * this function will pull from the socket and return only the first one + * available in the stream. As the socket will remain with data, it will + * trigger again during next select() and then this function will fetch the + * next ipa message, and so on. + * + * \returns -EAGAIN and allocated tmp_msg if message was not yet fully + * received. Other negative values indicate an error and cached msgb will be + * freed. 0 if socket is found dead. Positive value indicating l2 msgb len and + * rmsg pointing to internally allocated msgb containing the ipa frame on + * scucess. + */ int ipa_msg_recv_buffered(int fd, struct msgb **rmsg, struct msgb **tmp_msg) { struct msgb *msg = tmp_msg ? *tmp_msg : NULL; diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map index 3b403c2b..1da398c1 100644 --- a/src/gsm/libosmogsm.map +++ b/src/gsm/libosmogsm.map @@ -97,6 +97,10 @@ gsm0480_wrap_facility; gsm0480_wrap_invoke; gsm0480_comp_type_names; gsm0480_op_code_names; +gsm0480_msgb_alloc_name; +gsm0480_gen_ussd_resp_7bit; +gsm0480_gen_return_error; +gsm0480_gen_reject; gsm0502_calc_paging_group; @@ -237,6 +241,7 @@ gsm411_rp_state_names; gsm414_msgt_names; +gsm48_push_l3hdr; gsm48_att_tlvdef; gsm48_cc_msg_name; gsm48_rr_msg_name; @@ -458,6 +463,8 @@ ipa_ccm_tlv_to_unitdata; ipa_ccm_idtag_name; ipa_ccm_idtag_parse; ipa_ccm_idtag_parse_off; +ipa_ccm_id_get_parse; +ipa_ccm_id_resp_parse; ipa_ccm_make_id_resp; ipa_ccm_make_id_resp_from_req; ipa_msg_alloc; @@ -489,5 +496,10 @@ osmo_mncc_stringify; osmo_mncc_names; _osmo_mncc_log; +osmo_oap_client_encoded; +osmo_oap_client_handle; +osmo_oap_client_init; +osmo_oap_client_register; + local: *; }; diff --git a/src/gsm/oap_client.c b/src/gsm/oap_client.c new file mode 100644 index 00000000..ea406341 --- /dev/null +++ b/src/gsm/oap_client.c @@ -0,0 +1,280 @@ +/* Osmocom Authentication Protocol API */ + +/* (C) 2015 by Sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <string.h> +#include <errno.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> +#include <osmocom/crypt/auth.h> +#include <osmocom/gsm/oap.h> + +#include <osmocom/gsm/oap_client.h> + +int osmo_oap_client_init(struct osmo_oap_client_config *config, + struct osmo_oap_client_state *state) +{ + OSMO_ASSERT(state->state == OSMO_OAP_UNINITIALIZED); + + if (!config) + goto disable; + + if (config->client_id == 0) + goto disable; + + if (config->secret_k_present == 0) { + LOGP(DLOAP, LOGL_NOTICE, "OAP: client ID set, but secret K missing.\n"); + goto disable; + } + + if (config->secret_opc_present == 0) { + LOGP(DLOAP, LOGL_NOTICE, "OAP: client ID set, but secret OPC missing.\n"); + goto disable; + } + + state->client_id = config->client_id; + memcpy(state->secret_k, config->secret_k, sizeof(state->secret_k)); + memcpy(state->secret_opc, config->secret_opc, sizeof(state->secret_opc)); + state->state = OSMO_OAP_INITIALIZED; + return 0; + +disable: + state->state = OSMO_OAP_DISABLED; + return 0; +} + +/* From the given state and received RAND and AUTN octets, validate the + * server's authenticity and formulate the matching milenage reply octets in + * *tx_xres. The state is not modified. + * On success, and if tx_res is not NULL, exactly 8 octets will be written to + * *tx_res. If not NULL, tx_res must point at allocated memory of at least 8 + * octets. The caller will want to send XRES back to the server in a challenge + * response message and update the state. + * Return 0 on success; -1 if OAP is disabled; -2 if rx_random and rx_autn fail + * the authentication check; -3 for any other errors. */ +static int oap_evaluate_challenge(const struct osmo_oap_client_state *state, + const uint8_t *rx_random, + const uint8_t *rx_autn, + uint8_t *tx_xres) +{ + struct osmo_auth_vector vec; + + struct osmo_sub_auth_data auth = { + .type = OSMO_AUTH_TYPE_UMTS, + .algo = OSMO_AUTH_ALG_MILENAGE, + }; + + osmo_static_assert(sizeof(((struct osmo_sub_auth_data*)0)->u.umts.k) + == sizeof(state->secret_k), _secret_k_size_match); + osmo_static_assert(sizeof(((struct osmo_sub_auth_data*)0)->u.umts.opc) + == sizeof(state->secret_opc), _secret_opc_size_match); + + switch (state->state) { + case OSMO_OAP_UNINITIALIZED: + case OSMO_OAP_DISABLED: + return -1; + default: + break; + } + + memcpy(auth.u.umts.k, state->secret_k, sizeof(auth.u.umts.k)); + memcpy(auth.u.umts.opc, state->secret_opc, sizeof(auth.u.umts.opc)); + memset(auth.u.umts.amf, '\0', sizeof(auth.u.umts.amf)); + auth.u.umts.sqn = 41; /* TODO use incrementing sequence nr */ + + memset(&vec, 0, sizeof(vec)); + osmo_auth_gen_vec(&vec, &auth, rx_random); + + if (vec.res_len != 8) { + LOGP(DLOAP, LOGL_ERROR, "OAP: Expected XRES to be 8 octets, got %d\n", + vec.res_len); + return -3; + } + + if (osmo_constant_time_cmp(vec.autn, rx_autn, sizeof(vec.autn)) != 0) { + LOGP(DLOAP, LOGL_ERROR, "OAP: AUTN mismatch!\n"); + LOGP(DLOAP, LOGL_INFO, "OAP: AUTN from server: %s\n", + osmo_hexdump_nospc(rx_autn, sizeof(vec.autn))); + LOGP(DLOAP, LOGL_INFO, "OAP: AUTN expected: %s\n", + osmo_hexdump_nospc(vec.autn, sizeof(vec.autn))); + return -2; + } + + if (tx_xres != NULL) + memcpy(tx_xres, vec.res, 8); + return 0; +} + +struct msgb *osmo_oap_client_encoded(const struct osmo_oap_message *oap_msg) +{ + struct msgb *msg = msgb_alloc_headroom(1000, 64, __func__); + OSMO_ASSERT(msg); + osmo_oap_encode(msg, oap_msg); + return msg; +} + +/* Create a new msgb containing an OAP registration message. + * On error, return NULL. */ +static struct msgb* oap_msg_register(uint16_t client_id) +{ + struct osmo_oap_message oap_msg = {0}; + + if (client_id < 1) { + LOGP(DLOAP, LOGL_ERROR, "OAP: Invalid client ID: %d\n", client_id); + return NULL; + } + + oap_msg.message_type = OAP_MSGT_REGISTER_REQUEST; + oap_msg.client_id = client_id; + return osmo_oap_client_encoded(&oap_msg); +} + +int osmo_oap_client_register(struct osmo_oap_client_state *state, struct msgb **msg_tx) +{ + *msg_tx = oap_msg_register(state->client_id); + if (!(*msg_tx)) + return -1; + + state->state = OSMO_OAP_REQUESTED_CHALLENGE; + return 0; +} + +/* Create a new msgb containing an OAP challenge response message. + * xres must point at 8 octets to return as challenge response. + * On error, return NULL. */ +static struct msgb* oap_msg_challenge_response(uint8_t *xres) +{ + struct osmo_oap_message oap_reply = {0}; + + oap_reply.message_type = OAP_MSGT_CHALLENGE_RESULT; + memcpy(oap_reply.xres, xres, sizeof(oap_reply.xres)); + oap_reply.xres_present = 1; + return osmo_oap_client_encoded(&oap_reply); +} + +static int handle_challenge(struct osmo_oap_client_state *state, + struct osmo_oap_message *oap_rx, + struct msgb **msg_tx) +{ + int rc; + uint8_t xres[8]; + + if (!(oap_rx->rand_present && oap_rx->autn_present)) { + LOGP(DLOAP, LOGL_ERROR, + "OAP challenge incomplete (rand_present: %d, autn_present: %d)\n", + oap_rx->rand_present, oap_rx->autn_present); + rc = -2; + goto failure; + } + + rc = oap_evaluate_challenge(state, + oap_rx->rand, + oap_rx->autn, + xres); + if (rc < 0) + goto failure; + + *msg_tx = oap_msg_challenge_response(xres); + if ((*msg_tx) == NULL) { + rc = -1; + goto failure; + } + + state->state = OSMO_OAP_SENT_CHALLENGE_RESULT; + return 0; + +failure: + OSMO_ASSERT(rc < 0); + state->state = OSMO_OAP_INITIALIZED; + return rc; +} + +int osmo_oap_client_handle(struct osmo_oap_client_state *state, + const struct msgb *msg_rx, struct msgb **msg_tx) +{ + uint8_t *data = msgb_l2(msg_rx); + size_t data_len = msgb_l2len(msg_rx); + struct osmo_oap_message oap_msg = {0}; + int rc = 0; + + *msg_tx = NULL; + + OSMO_ASSERT(data); + + rc = osmo_oap_decode(&oap_msg, data, data_len); + if (rc < 0) { + LOGP(DLOAP, LOGL_ERROR, + "Decoding OAP message failed with error '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, -rc), -rc); + return -10; + } + + switch (state->state) { + case OSMO_OAP_UNINITIALIZED: + LOGP(DLOAP, LOGL_ERROR, + "Received OAP message %d, but the OAP client is" + " not initialized\n", oap_msg.message_type); + return -ENOTCONN; + case OSMO_OAP_DISABLED: + LOGP(DLOAP, LOGL_ERROR, + "Received OAP message %d, but the OAP client is" + " disabled\n", oap_msg.message_type); + return -ENOTCONN; + default: + break; + } + + switch (oap_msg.message_type) { + case OAP_MSGT_CHALLENGE_REQUEST: + return handle_challenge(state, &oap_msg, msg_tx); + + case OAP_MSGT_REGISTER_RESULT: + /* successfully registered */ + state->state = OSMO_OAP_REGISTERED; + break; + + case OAP_MSGT_REGISTER_ERROR: + LOGP(DLOAP, LOGL_ERROR, + "OAP registration failed\n"); + state->state = OSMO_OAP_INITIALIZED; + if (state->registration_failures < 3) { + state->registration_failures++; + return osmo_oap_client_register(state, msg_tx); + } + return -11; + + case OAP_MSGT_REGISTER_REQUEST: + case OAP_MSGT_CHALLENGE_RESULT: + LOGP(DLOAP, LOGL_ERROR, + "Received invalid OAP message type for OAP client side: %d\n", + (int)oap_msg.message_type); + return -12; + + default: + LOGP(DLOAP, LOGL_ERROR, + "Unknown OAP message type: %d\n", + (int)oap_msg.message_type); + return -13; + } + + return 0; +} |