aboutsummaryrefslogtreecommitdiffstats
path: root/src/gsm
diff options
context:
space:
mode:
Diffstat (limited to 'src/gsm')
-rw-r--r--src/gsm/Makefile.am4
-rw-r--r--src/gsm/comp128v23.c6
-rw-r--r--src/gsm/gsm0411_utils.c3
-rw-r--r--src/gsm/gsm0480.c135
-rw-r--r--src/gsm/gsm0808.c21
-rw-r--r--src/gsm/gsm48.c20
-rw-r--r--src/gsm/ipa.c112
-rw-r--r--src/gsm/libosmogsm.map12
-rw-r--r--src/gsm/oap_client.c280
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, &params->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;
+}