sgsn: Delete PDP contexts properly

Currently the PDP contexts are hard freed (via sgsn_pdp_ctx_free)
at some places in gprs_gmm.c on the reception of a Detach Req and on
re-use of an IMSI that is already associated with an MM context. This
can lead to segfaults when there is a pending request or a data
indication at libgtp.

This patch add a new function sgsn_pdp_ctx_terminate that de-associates
the PTP context from the MM context, deactivates SNDCP, sets pdp->mm
to NULL and then calls sgsn_delete_pdp_ctx. sgsn_libgtp is updated to
check for pdp->mm being non-NULL before dereferencing it. The
sgsn_pdp_ctx_terminate function will be called for each PDP context of
an MM context before this context is going to be deleted via
sgsn_mm_ctx_free. To ensure, that the ctx->llme (which is accessed
during the deactivation of SNDCP) remains valid, the call to
gprs_llgmm_assign is moved after the call to sgsn_mm_ctx_free. The
handling of re-used IMSIs is changed to mimic the processing of a
Detach Req.

Addresses:
<0002> gprs_gmm.c:654 MM(/f6b31ab0) Deleting old MM Context for same
    IMSI p_tmsi_old=0xc6f19134
<000f> gprs_sgsn.c:259 PDP freeing PDP context that still has a
    libgtp handle attached to it, this shouldn't happen!
[...]
SEGFAULT

Ticket: OW#1311
Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gprs_gmm.c b/openbsc/src/gprs/gprs_gmm.c
index a2d61cf..9df0cc6 100644
--- a/openbsc/src/gprs/gprs_gmm.c
+++ b/openbsc/src/gprs/gprs_gmm.c
@@ -265,6 +265,28 @@
 	msgb_nsei(msg) = mm->nsei;
 }
 
+static void mm_ctx_cleanup_free(struct sgsn_mm_ctx *ctx, const char *log_text)
+{
+	struct gprs_llc_llme *llme = ctx->llme;
+	uint32_t tlli = ctx->tlli;
+	struct sgsn_pdp_ctx *pdp, *pdp2;
+
+	/* Mark MM state as deregistered */
+	ctx->mm_state = GMM_DEREGISTERED;
+
+	llist_for_each_entry_safe(pdp, pdp2, &ctx->pdp_list, list) {
+		LOGMMCTXP(LOGL_NOTICE, ctx, "Dropping PDP context for NSAPI=%u "
+		     "due to %s\n", pdp->nsapi, log_text);
+		sgsn_pdp_ctx_terminate(pdp);
+	}
+
+	sgsn_mm_ctx_free(ctx);
+	ctx = NULL;
+
+	/* TLLI unassignment, must be called after sgsn_mm_ctx_free */
+	gprs_llgmm_assign(llme, tlli, 0xffffffff, GPRS_ALGO_GEA0, NULL);
+}
+
 /* Chapter 9.4.18 */
 static int _tx_status(struct msgb *msg, uint8_t cause,
 		      struct sgsn_mm_ctx *mmctx, int sm)
@@ -603,15 +625,16 @@
 			struct sgsn_mm_ctx *ictx;
 			ictx = sgsn_mm_ctx_by_imsi(mi_string);
 			if (ictx) {
+				/* Handle it like in gsm48_rx_gmm_det_req,
+				 * except that no messages are sent to the BSS */
+
 				LOGMMCTXP(LOGL_NOTICE, ctx, "Deleting old MM Context for same IMSI "
 				       "p_tmsi_old=0x%08x\n",
 					ictx->p_tmsi);
-				gprs_llgmm_assign(ictx->llme, ictx->tlli,
-						  0xffffffff, GPRS_ALGO_GEA0, NULL);
-				/* FIXME: this is a hard free, we don't
-				 * clean-up any PDP contexts on the
-				 * libgtp side */
-				sgsn_mm_ctx_free(ictx);
+
+				ictx->mm_state = GMM_DEREGISTERED;
+
+				mm_ctx_cleanup_free(ictx, "GPRS IMSI re-use");
 			}
 		}
 		strncpy(ctx->imsi, mi_string, sizeof(ctx->imsi));
@@ -776,7 +799,6 @@
 static int gsm48_rx_gmm_det_req(struct sgsn_mm_ctx *ctx, struct msgb *msg)
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
-	struct sgsn_pdp_ctx *pdp, *pdp2;
 	uint8_t detach_type, power_off;
 	int rc;
 
@@ -789,26 +811,10 @@
 		msgb_tlli(msg), get_value_string(gprs_det_t_mo_strs, detach_type),
 		power_off ? "Power-off" : "");
 
-	/* Mark MM state as deregistered */
-	ctx->mm_state = GMM_DEREGISTERED;
-
-	/* delete all existing PDP contexts for this MS */
-	llist_for_each_entry_safe(pdp, pdp2, &ctx->pdp_list, list) {
-		LOGMMCTXP(LOGL_NOTICE, ctx, "Dropping PDP context for NSAPI=%u "
-		     "due to GPRS DETACH REQUEST\n", pdp->nsapi);
-		sgsn_delete_pdp_ctx(pdp);
-		/* FIXME: the callback wants to transmit a DEACT PDP CTX ACK,
-		 * which is quite stupid for a MS that has just detached.. */
-	}
-
 	/* force_stby = 0 */
 	rc = gsm48_tx_gmm_det_ack(ctx, 0);
 
-	/* TLLI unassignment */
-	gprs_llgmm_assign(ctx->llme, ctx->tlli, 0xffffffff,
-			  GPRS_ALGO_GEA0, NULL);
-
-	sgsn_mm_ctx_free(ctx);
+	mm_ctx_cleanup_free(ctx, "GPRS DETACH REQUEST");
 
 	return rc;
 }