sgsn: Add a subscriber based authentication phase

This implements the MAP way of subscriber validation when the MS
tries to perform an Attach Request:

  1. perform authentication (optionally invoke the sendAuthInfo
     procedure), starts the Auth & Ciph procedure
  2. perform update location
  3. insert subscriber data
  4. finish the update location
  5. Attach Accept / Attach Reject

The authentication triplets are used and eventually updated if all of
them have been used.

This is currently accessible via the VTY interface by the following
commands:

  - update-subscriber imsi IMSI update-auth-info
  - update-subscriber imsi IMSI update-location-result (ok|ERR-CAUSE)

Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gprs_subscriber.c b/openbsc/src/gprs/gprs_subscriber.c
index 1675331..1f21029 100644
--- a/openbsc/src/gprs/gprs_subscriber.c
+++ b/openbsc/src/gprs/gprs_subscriber.c
@@ -92,9 +92,18 @@
 	gprs_subscr_delete(subscr);
 }
 
-int gprs_subscr_query(struct gsm_subscriber *subscr)
+int gprs_subscr_query_auth_info(struct gsm_subscriber *subscr)
 {
-	/* TODO: Implement remote query to MSC, ... */
+	/* TODO: Implement remote query to HLR, ... */
+
+	LOGMMCTXP(LOGL_INFO, subscr->sgsn_data->mm,
+		  "subscriber auth info is not available (remote query NYI)\n");
+	return -ENOTSUP;
+}
+
+int gprs_subscr_location_update(struct gsm_subscriber *subscr)
+{
+	/* TODO: Implement remote query to HLR, ... */
 
 	LOGMMCTXP(LOGL_INFO, subscr->sgsn_data->mm,
 		  "subscriber data is not available (remote query NYI)\n");
@@ -105,56 +114,80 @@
 {
 	LOGMMCTXP(LOGL_DEBUG, subscr->sgsn_data->mm, "Updating subscriber data\n");
 
-	subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_PENDING;
+	subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING;
 	subscr->flags &= ~GSM_SUBSCRIBER_FIRST_CONTACT;
 
 	sgsn_update_subscriber_data(subscr->sgsn_data->mm, subscr);
 }
 
-int gprs_subscr_request_update(struct sgsn_mm_ctx *mmctx)
+void gprs_subscr_update_auth_info(struct gsm_subscriber *subscr)
+{
+	LOGMMCTXP(LOGL_DEBUG, subscr->sgsn_data->mm,
+		  "Updating subscriber authentication info\n");
+
+	subscr->flags &= ~GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING;
+	subscr->flags &= ~GSM_SUBSCRIBER_FIRST_CONTACT;
+
+	sgsn_update_subscriber_data(subscr->sgsn_data->mm, subscr);
+}
+
+struct gsm_subscriber *gprs_subscr_get_or_create_by_mmctx(struct sgsn_mm_ctx *mmctx)
 {
 	struct gsm_subscriber *subscr = NULL;
-	int need_update = 0;
-	int rc;
 
-	LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber data update\n");
+	if (mmctx->subscr)
+		return subscr_get(mmctx->subscr);
 
-	if (mmctx->subscr) {
-		subscr = subscr_get(mmctx->subscr);
-	} else if (mmctx->imsi[0]) {
+	if (mmctx->imsi[0])
 		subscr = gprs_subscr_get_by_imsi(mmctx->imsi);
-		need_update = 1;
-	}
 
 	if (!subscr) {
 		subscr = gprs_subscr_get_or_create(mmctx->imsi);
 		subscr->flags |= GSM_SUBSCRIBER_FIRST_CONTACT;
-		need_update = 1;
 	}
 
 	if (strcpy(subscr->equipment.imei, mmctx->imei) != 0) {
 		strncpy(subscr->equipment.imei, mmctx->imei, GSM_IMEI_LENGTH-1);
 		subscr->equipment.imei[GSM_IMEI_LENGTH-1] = 0;
-		need_update = 1;
 	}
 
-	if (subscr->lac != mmctx->ra.lac) {
+	if (subscr->lac != mmctx->ra.lac)
 		subscr->lac = mmctx->ra.lac;
-		need_update = 1;
-	}
 
-	if (need_update) {
-		subscr->flags |= GPRS_SUBSCRIBER_UPDATE_PENDING;
-		if (!mmctx->subscr) {
-			subscr->sgsn_data->mm = mmctx;
-			mmctx->subscr = subscr_get(subscr);
-		}
+	subscr->sgsn_data->mm = mmctx;
+	mmctx->subscr = subscr_get(subscr);
 
-		rc = gprs_subscr_query(subscr);
-		subscr_put(subscr);
-		return rc;
-	}
-	gprs_subscr_update(subscr);
+	return subscr;
+}
+
+int gprs_subscr_request_update_location(struct sgsn_mm_ctx *mmctx)
+{
+	struct gsm_subscriber *subscr = NULL;
+	int rc;
+
+	LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber data update\n");
+
+	subscr = gprs_subscr_get_or_create_by_mmctx(mmctx);
+
+	subscr->flags |= GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING;
+
+	rc = gprs_subscr_location_update(subscr);
 	subscr_put(subscr);
-	return 0;
+	return rc;
+}
+
+int gprs_subscr_request_auth_info(struct sgsn_mm_ctx *mmctx)
+{
+	struct gsm_subscriber *subscr = NULL;
+	int rc;
+
+	LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting subscriber authentication info\n");
+
+	subscr = gprs_subscr_get_or_create_by_mmctx(mmctx);
+
+	subscr->flags |= GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING;
+
+	rc = gprs_subscr_query_auth_info(subscr);
+	subscr_put(subscr);
+	return rc;
 }
diff --git a/openbsc/src/gprs/sgsn_auth.c b/openbsc/src/gprs/sgsn_auth.c
index b065c06..3755a51 100644
--- a/openbsc/src/gprs/sgsn_auth.c
+++ b/openbsc/src/gprs/sgsn_auth.c
@@ -23,7 +23,7 @@
 #include <openbsc/gprs_sgsn.h>
 #include <openbsc/gprs_gmm.h>
 #include <openbsc/gsm_subscriber.h>
-
+#include <openbsc/gsm_04_08_gprs.h>
 #include <openbsc/debug.h>
 
 const struct value_string auth_state_names[] = {
@@ -107,11 +107,12 @@
 		if (!mmctx->subscr)
 			return mmctx->auth_state;
 
-		if (mmctx->subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING)
+		if (mmctx->subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING_MASK)
 			return mmctx->auth_state;
 
 		if (mmctx->subscr->sgsn_data->authenticate &&
-		    !mmctx->is_authenticated)
+		    (!mmctx->is_authenticated ||
+		     mmctx->subscr->sgsn_data->auth_triplets_updated))
 			return SGSN_AUTH_AUTHENTICATE;
 
 		if (mmctx->subscr->authorized)
@@ -141,20 +142,60 @@
 	return SGSN_AUTH_REJECTED;
 }
 
+/*
+ * This function is directly called by e.g. the GMM layer. It returns either
+ * after calling sgsn_auth_update directly or after triggering an asynchronous
+ * procedure which will call sgsn_auth_update later on.
+ */
 int sgsn_auth_request(struct sgsn_mm_ctx *mmctx)
 {
+	struct gsm_subscriber *subscr;
+	struct gsm_auth_tuple *at;
+	int need_update_location;
+	int rc;
+
 	LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting authorization\n");
 
-	if (sgsn->cfg.auth_policy == SGSN_AUTH_POLICY_REMOTE && !mmctx->subscr) {
-		if (gprs_subscr_request_update(mmctx) >= 0) {
+	if (sgsn->cfg.auth_policy != SGSN_AUTH_POLICY_REMOTE) {
+		sgsn_auth_update(mmctx);
+		return 0;
+	}
+
+	need_update_location =
+		mmctx->subscr == NULL ||
+		mmctx->pending_req == GSM48_MT_GMM_ATTACH_REQ;
+
+	/* This has the side effect of registering the subscr with the mmctx */
+	subscr = gprs_subscr_get_or_create_by_mmctx(mmctx);
+	subscr_put(subscr);
+
+	OSMO_ASSERT(mmctx->subscr != NULL);
+
+	if (mmctx->subscr->sgsn_data->authenticate && !mmctx->is_authenticated) {
+		/* Find next tuple */
+		at = sgsn_auth_get_tuple(mmctx, mmctx->auth_triplet.key_seq);
+
+		if (!at) {
+			/* No valid tuple found, request fresh ones */
+			mmctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL;
 			LOGMMCTXP(LOGL_INFO, mmctx,
-				  "Missing information, requesting subscriber data\n");
-			return 0;
+				  "Requesting authentication tuples\n");
+			rc = gprs_subscr_request_auth_info(mmctx);
+			if (rc >= 0)
+				return 0;
+
+			return rc;
 		}
+
+		mmctx->auth_triplet = *at;
+	} else if (need_update_location) {
+		LOGMMCTXP(LOGL_INFO, mmctx,
+			  "Missing information, requesting subscriber data\n");
+		if (gprs_subscr_request_update_location(mmctx) >= 0)
+			return 0;
 	}
 
 	sgsn_auth_update(mmctx);
-
 	return 0;
 }
 
@@ -162,6 +203,7 @@
 {
 	enum sgsn_auth_state auth_state;
 	struct gsm_subscriber *subscr = mmctx->subscr;
+	struct gsm_auth_tuple *at;
 
 	auth_state = sgsn_auth_state(mmctx);
 
@@ -170,13 +212,27 @@
 		  get_value_string(sgsn_auth_state_names, auth_state));
 
 	if (auth_state == SGSN_AUTH_UNKNOWN && subscr &&
-	    !(subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING)) {
-		/* Reject requests if gprs_subscr_request_update fails */
+	    !(subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING_MASK)) {
+		/* Reject requests if gprs_subscr_request_update_location fails */
 		LOGMMCTXP(LOGL_ERROR, mmctx,
 			  "Missing information, authorization not possible\n");
 		auth_state = SGSN_AUTH_REJECTED;
 	}
 
+	if (auth_state == SGSN_AUTH_AUTHENTICATE &&
+	    mmctx->auth_triplet.key_seq == GSM_KEY_SEQ_INVAL) {
+		/* The current tuple is not valid, but we are possibly called
+		 * because new auth tuples have been received */
+		at = sgsn_auth_get_tuple(mmctx, mmctx->auth_triplet.key_seq);
+		if (!at) {
+			LOGMMCTXP(LOGL_ERROR, mmctx,
+				  "Missing auth tuples, authorization not possible\n");
+			auth_state = SGSN_AUTH_REJECTED;
+		} else {
+			mmctx->auth_triplet = *at;
+		}
+	}
+
 	if (mmctx->auth_state == auth_state)
 		return;
 
@@ -188,6 +244,9 @@
 
 	switch (auth_state) {
 	case SGSN_AUTH_AUTHENTICATE:
+		if (subscr)
+			subscr->sgsn_data->auth_triplets_updated = 0;
+
 		gsm0408_gprs_authenticate(mmctx);
 		break;
 	case SGSN_AUTH_ACCEPTED:
diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c
index 99c5985..9b925a8 100644
--- a/openbsc/src/gprs/sgsn_vty.c
+++ b/openbsc/src/gprs/sgsn_vty.c
@@ -428,13 +428,15 @@
 	}
 
 	if (subscr->flags)
-		vty_out(vty, "    Flags: %s%s%s%s",
+		vty_out(vty, "    Flags: %s%s%s%s%s",
 			subscr->flags & GSM_SUBSCRIBER_FIRST_CONTACT ?
 			"FIRST_CONTACT " : "",
 			subscr->flags & GPRS_SUBSCRIBER_CANCELLED ?
 			"CANCELLED " : "",
-			subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING ?
-			"UPDATE_PENDING " : "",
+			subscr->flags & GPRS_SUBSCRIBER_UPDATE_LOCATION_PENDING ?
+			"UPDATE_LOCATION_PENDING " : "",
+			subscr->flags & GPRS_SUBSCRIBER_UPDATE_AUTH_INFO_PENDING ?
+			"AUTH_INFO_PENDING " : "",
 			VTY_NEWLINE);
 
 	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
@@ -587,6 +589,68 @@
 	return CMD_SUCCESS;
 }
 
+#define UL_ERR_STR "system-failure|data-missing|unexpected-data-value|" \
+		   "unknown-subscriber|roaming-not-allowed"
+
+#define UL_ERR_HELP \
+		"Force error code SystemFailure\n" \
+		"Force error code DataMissing\n" \
+		"Force error code UnexpectedDataValue\n" \
+		"Force error code UnknownSubscriber\n" \
+		"Force error code RoamingNotAllowed\n"
+
+DEFUN(update_subscr_update_location_result, update_subscr_update_location_result_cmd,
+	UPDATE_SUBSCR_STR "update-location-result (ok|" UL_ERR_STR ")",
+	UPDATE_SUBSCR_HELP
+	"Complete the update location procedure\n"
+	"The update location request succeeded\n"
+	UL_ERR_HELP)
+{
+	const char *imsi = argv[0];
+	const char *ret_code_str = argv[1];
+
+	struct gsm_subscriber *subscr;
+
+	subscr = gprs_subscr_get_by_imsi(imsi);
+	if (!subscr) {
+		vty_out(vty, "%% unable to get subscriber record for %s\n", imsi);
+		return CMD_WARNING;
+	}
+	if (strcmp(ret_code_str, "ok") == 0)
+		subscr->authorized = 1;
+	else
+		subscr->authorized = 0;
+
+	gprs_subscr_update(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(update_subscr_update_auth_info, update_subscr_update_auth_info_cmd,
+	UPDATE_SUBSCR_STR "update-auth-info",
+	UPDATE_SUBSCR_HELP
+	"Complete the send authentication info procedure\n")
+{
+	const char *imsi = argv[0];
+
+	struct gsm_subscriber *subscr;
+
+	subscr = gprs_subscr_get_by_imsi(imsi);
+	if (!subscr) {
+		vty_out(vty, "%% unable to get subscriber record for %s\n", imsi);
+		return CMD_WARNING;
+	}
+
+	gprs_subscr_update_auth_info(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+
 int sgsn_vty_init(void)
 {
 	install_element_ve(&show_sgsn_cmd);
@@ -600,6 +664,8 @@
 	install_element(ENABLE_NODE, &update_subscr_insert_auth_triplet_cmd);
 	install_element(ENABLE_NODE, &update_subscr_cancel_cmd);
 	install_element(ENABLE_NODE, &update_subscr_commit_cmd);
+	install_element(ENABLE_NODE, &update_subscr_update_location_result_cmd);
+	install_element(ENABLE_NODE, &update_subscr_update_auth_info_cmd);
 
 	install_element(CONFIG_NODE, &cfg_sgsn_cmd);
 	install_node(&sgsn_node, config_write_sgsn);