changeset 415:540ed390c04f

Added sess_destroy function
author Sebastien Decugis <sdecugis@nict.go.jp>
date Tue, 16 Jun 2009 13:37:46 +0900
parents c7ce13cb7d23
children 5c5d72fd7f2a
files extensions/radius_gw/rg_api.h extensions/radius_gw/rgw_extensions.c extensions/radius_gw/rgw_servers.c extensions/radius_gw/rgw_work.c extensions/radius_gw/sub_acct.c extensions/radius_gw/sub_auth.c extensions/radius_gw/sub_debug.c extensions/radius_gw/sub_echo_drop.c include/waaad/session-api.h waaad/session.c
diffstat 10 files changed, 119 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/extensions/radius_gw/rg_api.h	Mon Jun 15 15:00:10 2009 +0900
+++ b/extensions/radius_gw/rg_api.h	Tue Jun 16 13:37:46 2009 +0900
@@ -49,19 +49,15 @@
 	void	(*rga_conf_free_cb) (struct rga_conf_state * cs); 	/* Free an object returned by rga_conf_parse_cb */
 	
 	/* handle an incoming RADIUS message */
-	int	(*rga_rad_req_cb) ( struct rga_conf_state * cs, sess_id_t ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, msg_t ** diam_fw, void * cli );
+	int	(*rga_rad_req_cb) ( struct rga_conf_state * cs, sess_id_t * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, msg_t ** diam_fw, void * cli );
 	/* ret 0: continue; 
-	   ret -1: stop processing this message and destroy the session (or fallback if supported)
-	   ret -2: stop processing this message and keep the session (or fallback if supported)
-	   ret -3: reply the content of rad_ans to the RADIUS client immediatly and destroy the session
-	   ret -4: reply the content of rad_ans to the RADIUS client immediatly and keep the session
+	   ret -1: stop processing this message
+	   ret -2: reply the content of rad_ans (created with rg_msg_create_ans) to the RADIUS client immediatly
 	   ret >0: critical error (errno), log and exit.
-			   
-	   for cases 3 and 4, the answer must be created with rg_msg_create_ans.
 	 */
 	
 	/* handle the corresponding Diameter answer */
-	int	(*rga_diam_ans_cb) ( struct rga_conf_state * cs, sess_id_t ** session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli );
+	int	(*rga_diam_ans_cb) ( struct rga_conf_state * cs, sess_id_t * session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli );
 	/* ret 0: continue; ret >0: error; ret: -1 ... (tbd) */
 };
 
--- a/extensions/radius_gw/rgw_extensions.c	Mon Jun 15 15:00:10 2009 +0900
+++ b/extensions/radius_gw/rgw_extensions.c	Tue Jun 16 13:37:46 2009 +0900
@@ -364,11 +364,11 @@
 		
 		if (ext->api.rga_rad_req_cb) {
 			TRACE_DEBUG(ANNOYING, "Calling next extension: %s", ext->extname);
-			ret = (*ext->api.rga_rad_req_cb)(ext->cs, session, &(*rad)->radius, &rad_ans, diam_msg, (void *)cli);
+			ret = (*ext->api.rga_rad_req_cb)(ext->cs, *session, &(*rad)->radius, &rad_ans, diam_msg, (void *)cli);
 			if (ret)
 				break;
 		} else {
-			TRACE_DEBUG(ANNOYING, "Skipping next extension: %s (NULL callback)", ext->extname);
+			TRACE_DEBUG(ANNOYING, "Skipping extension '%s' (NULL callback)", ext->extname);
 		}					
 	}
 	
@@ -382,14 +382,13 @@
 		*diam_msg = NULL;
 	}
 	
-	/* Destroy the session unless instructed to keep it */
-	if (*session && (ret != -2) && (ret != -4)) {
-		CHECK_FCT_DO( sess_unlink(*session), );
-		*session = NULL;
+	/* Destroy the session, there won't be a reply message to retrieve the data */
+	if (*session) {
+		CHECK_FCT_DO( sess_destroy(session), );
 	}
 	
 	/* Send the radius message back if required */
-	if (((ret == -3) || (ret == -4)) && rad_ans && rad) {
+	if ((ret == -2) && rad_ans && rad) {
 		CHECK_FCT_DO( rgw_client_finish_send(&rad_ans, *rad, cli), /* It failed, it can't be helped... */);
 	}
 	
--- a/extensions/radius_gw/rgw_servers.c	Mon Jun 15 15:00:10 2009 +0900
+++ b/extensions/radius_gw/rgw_servers.c	Tue Jun 16 13:37:46 2009 +0900
@@ -254,14 +254,14 @@
 	}
 	
 	if (!ret) {
-		TRACE_DEBUG(INFO, "Trying to send a message with a disabled server: %s / %s", 
+		TRACE_DEBUG(INFO, "Trying to send a message from a disabled server: %s / %s", 
 				(type == RGW_EXT_TYPE_AUTH) ? "Auth" : "Acct",
 				(to->sa_family == AF_INET)  ? "IPv4" : "IPv6");
 		return EINVAL;
 	}
 	
 	/* Prepare the destination info */
-	memset(&sto, 0, sizeof(to));
+	memset(&sto, 0, sizeof(sto));
 	if (to->sa_family == AF_INET) {
 		memcpy(&sto, to, sizeof(struct sockaddr_in));
 		((struct sockaddr_in *)&sto)->sin_port = to_port;
@@ -277,7 +277,7 @@
 		char portstr[8];
 
 		if (ret = getnameinfo((struct sockaddr *)&sto, sto_len, &ipstr[0], INET6_ADDRSTRLEN, &portstr[0], sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV)) {
-			TRACE_DEBUG(FULL, "Sending %d bytes to unknown source: %s", buflen, gai_strerror(ret));
+			TRACE_DEBUG(FULL, "Sending %d bytes to unknown destination: %s", buflen, gai_strerror(ret));
 		} else {
 			TRACE_DEBUG(FULL, "Sending %d bytes to [%s]:%s", buflen, ipstr, portstr);
 		}
--- a/extensions/radius_gw/rgw_work.c	Mon Jun 15 15:00:10 2009 +0900
+++ b/extensions/radius_gw/rgw_work.c	Tue Jun 16 13:37:46 2009 +0900
@@ -67,9 +67,31 @@
 	struct pending_answer * pa = (struct pending_answer *)paback;
 	
 	TRACE_ENTRY("%p %p", pa, ans);
+	CHECK_PARAMS_DO( pa && ans, return );
 	
 	TRACE_DEBUG(INFO, "Handling Diameter answer: Not implemented yet...");
 	
+	/* Create an empty RADIUS answer message */
+	
+	
+	/* Pass the Diameter answer to the same extensions as the request */
+	
+	
+	/* Now try and send the RADIUS answer */
+	
+
+out:
+	/* Destroy remaining session data (stateless gateway) */
+	CHECK_FCT_DO( sess_destroy(&pa->sess),  );
+	
+	/* Clear the Diameter message */
+	CHECK_FCT_DO( msg_free(ans, 1),  );
+	
+	/* Clear the answer data */
+	free(pa);
+	
+	/* Finished */
+	return;
 }
 
 /* Worker thread, processing incoming RADIUS messages (after parsing) */
@@ -150,7 +172,8 @@
 		
 		session = NULL;
 		diam_msg = NULL;
-		/* Create the session and empty message with some common AVPs */
+		
+		/* Create the session and an empty message with some common AVPs */
 		CHECK_FCT_DO( rgw_msg_create_base(msg, cli, &session, &diam_msg),
 			{
 				/* An error occurred, discard message */
@@ -203,7 +226,7 @@
 		if (pb) {
 			/* Something went wrong during the conversion */
 			if (session) {
-				CHECK_FCT_DO( sess_unlink(&session), );
+				CHECK_FCT_DO( sess_destroy(&session), );
 			}
 			
 			if (diam_msg) {
@@ -230,8 +253,7 @@
 				/* If an error occurs, log and destroy the data */
 				log_error("An error occurred while sending Diameter message, please turn Debug on for detail.\n");
 				if (session) {
-					CHECK_FCT_DO( sess_unlink(session), );
-					session = NULL;
+					CHECK_FCT_DO( sess_destroy(&session), );
 				}
 
 				if (diam_msg) {
--- a/extensions/radius_gw/sub_acct.c	Mon Jun 15 15:00:10 2009 +0900
+++ b/extensions/radius_gw/sub_acct.c	Tue Jun 16 13:37:46 2009 +0900
@@ -51,7 +51,6 @@
 struct rga_conf_state {
 	char * conffile;
 	void * dl_handle;
-	int (*rgw_client_session_stop)(void * cli, sess_id_t * sess, int32_t reason);
 };
 
 static struct rga_conf_state * acct_conf_parse(char * conffile)
@@ -72,7 +71,6 @@
 	}
 	
 	CHECK_FCT_DO( rg_pointers_init(&cs->dl_handle), return NULL );
-	rg_pointers_resolve(cs->rgw_client_session_stop, cs->dl_handle, "rgw_client_session_stop", NULL);
 	
 	return cs;
 }
@@ -86,7 +84,7 @@
 	return;
 }
 
-static int acct_rad_req(struct rga_conf_state * cs, sess_id_t ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, msg_t ** diam_fw, void * cli )
+static int acct_rad_req(struct rga_conf_state * cs, sess_id_t * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, msg_t ** diam_fw, void * cli )
 {
 	int idx;
 	int got_id = 0;
@@ -113,19 +111,11 @@
 		return EINVAL;
 	}
 	
-	/* Handle the Accounting-On case: nothing to do, just reply OK, since Diameter does not support this */
-	if (status_type == RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON) {
+	/* Handle the Accounting-On/Off case: nothing to do, just reply OK, since Diameter does not support this */
+	if ((status_type == RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON) || (status_type == RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_OFF)) {
 		TRACE_DEBUG(FULL, "Received Accounting-On Acct-Status-Type attribute, replying directly.");
 		CHECK_MALLOC( *rad_ans = radius_msg_new(RADIUS_CODE_ACCOUNTING_RESPONSE, rad_req->hdr->identifier) );
-		return -3;
-	}
-	
-	/* Handle Accounting-Off case: terminate all registered sessions with this RADIUS client */
-	if (status_type == RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_OFF) {
-		TRACE_DEBUG(FULL, "Received Accounting-Off Acct-Status-Type attribute, replying directly and terminating all sessions.");
-		CHECK_MALLOC( *rad_ans = radius_msg_new(RADIUS_CODE_ACCOUNTING_RESPONSE, rad_req->hdr->identifier) );
-		CHECK_FCT( (*cs->rgw_client_session_stop)(cli, NULL, 4 /* DIAMETER_ADMINISTRATIVE */) );
-		return -3;
+		return -2;
 	}
 	
 			/*
@@ -155,7 +145,7 @@
 	return ENOTSUP;
 }
 
-static int acct_diam_ans(struct rga_conf_state * cs, sess_id_t ** session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli )
+static int acct_diam_ans(struct rga_conf_state * cs, sess_id_t * session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli )
 {
 	TRACE_ENTRY("%p %p %p %p %p", cs, session, diam_ans, rad_fw, cli);
 	CHECK_PARAMS(cs);
--- a/extensions/radius_gw/sub_auth.c	Mon Jun 15 15:00:10 2009 +0900
+++ b/extensions/radius_gw/sub_auth.c	Tue Jun 16 13:37:46 2009 +0900
@@ -91,7 +91,7 @@
 	return;
 }
 
-static int auth_rad_req(struct rga_conf_state * cs, sess_id_t ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, msg_t ** diam_fw, void * cli )
+static int auth_rad_req(struct rga_conf_state * cs, sess_id_t * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, msg_t ** diam_fw, void * cli )
 {
 	int idx;
 	int got_id = 0;
@@ -819,7 +819,7 @@
 	return 0;
 }
 
-static int auth_diam_ans(struct rga_conf_state * cs, sess_id_t ** session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli )
+static int auth_diam_ans(struct rga_conf_state * cs, sess_id_t * session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli )
 {
 	TRACE_ENTRY("%p %p %p %p %p", cs, session, diam_ans, rad_fw, cli);
 	CHECK_PARAMS(cs);
--- a/extensions/radius_gw/sub_debug.c	Mon Jun 15 15:00:10 2009 +0900
+++ b/extensions/radius_gw/sub_debug.c	Tue Jun 16 13:37:46 2009 +0900
@@ -110,11 +110,11 @@
 	radius_msg_dump(msg);
 }
 
-static int debug_rad_req(struct rga_conf_state * cs, sess_id_t ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, msg_t ** diam_fw, void * cli )
+static int debug_rad_req(struct rga_conf_state * cs, sess_id_t * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, msg_t ** diam_fw, void * cli )
 {
 	TRACE_ENTRY("%p %p %p %p %p %p", cs, session, rad_req, rad_ans, diam_fw, cli);
 	
-	log_debug("sub_debug ( c:'%s' ) state dump start for incoming RADIUS message (session: %p->%p)\n", cs->conffile, session, session?*session:NULL);
+	log_debug("sub_debug ( c:'%s' ) state dump start for incoming RADIUS message (session: %p)\n", cs->conffile, session);
 	
 	if (!rad_req) {
 		log_debug(" RADIUS request: NULL pointer\n");
@@ -142,11 +142,11 @@
 	return 0;
 }
 
-static int debug_diam_ans(struct rga_conf_state * cs, sess_id_t ** session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli )
+static int debug_diam_ans(struct rga_conf_state * cs, sess_id_t * session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli )
 {
 	TRACE_ENTRY("%p %p %p %p %p", cs, session, diam_ans, rad_fw, cli);
 	CHECK_PARAMS(cs);
-	log_debug("sub_debug ( c:'%s' ) state dump start for incoming Diameter answer (session: %p->%p)\n", cs->conffile, session, session?*session:NULL);
+	log_debug("sub_debug ( c:'%s' ) state dump start for incoming Diameter answer (session: %p)\n", cs->conffile, session);
 	
 	if (!diam_ans || ! *diam_ans) {
 		log_debug(" Diameter message: NULL pointer\n");
--- a/extensions/radius_gw/sub_echo_drop.c	Mon Jun 15 15:00:10 2009 +0900
+++ b/extensions/radius_gw/sub_echo_drop.c	Tue Jun 16 13:37:46 2009 +0900
@@ -127,9 +127,8 @@
 }
 
 /* Handle attributes from a RADIUS request as specified in the configuration */
-static int sed_rad_req(struct rga_conf_state * cs, sess_id_t ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, msg_t ** diam_fw, void * cli )
+static int sed_rad_req(struct rga_conf_state * cs, sess_id_t * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, msg_t ** diam_fw, void * cli )
 {
-	size_t *nattr_pos;
 	size_t nattr_used = 0;
 	int idx;
 	
@@ -141,8 +140,6 @@
 	
 	rg_list_init(&echo_list);
 	
-	CHECK_MALLOC( nattr_pos = malloc(rad_req->attr_size * sizeof(size_t)) );
-	
 	/* For each attribute in the original message */
 	for (idx = 0; idx < rad_req->attr_used; idx++) {
 		int action = 0;
@@ -219,36 +216,31 @@
 				}
 				break;
 			
-			case 0: /* Attribute was not specified in the configuration */
-			default: /* unknown action value */
+			default: /* Attribute was not specified in the configuration */
 				/* We just keep the attribute in the RADIUS message */
-				nattr_pos[nattr_used++] = rad_req->attr_pos[idx];
+				rad_req->attr_pos[nattr_used++] = rad_req->attr_pos[idx];
 		}
 	}
+	rad_req->attr_used = nattr_used;
 	
 	/* Save the echoed values in the session, if any */
 	if (!rg_list_is_empty(&echo_list)) {
-		CHECK_PARAMS(session && *session);
+		CHECK_PARAMS(session);
 		CHECK_MALLOC( li = malloc(sizeof(struct rg_list)) );
 		memcpy(li, &echo_list, sizeof(struct rg_list));
 		
 		/* Check that we have no previous data stored for this session, for debug */
-		ASSERT( sess_data_dereg(*session, cs->sess_hdl, NULL) == ENOENT );
+		ASSERT( sess_data_dereg(session, cs->sess_hdl, NULL) == ENOENT );
 		
 		/* Save the list in the session */
-		CHECK_FCT( sess_data_reg(*session, cs->sess_hdl, li, ssi_cleanup) );
+		CHECK_FCT( sess_data_reg(session, cs->sess_hdl, li, ssi_cleanup) );
 	}
 	
-	/* Finally update the radius message to remove all handled attributes */
-	free(rad_req->attr_pos);
-	rad_req->attr_pos = nattr_pos;
-	rad_req->attr_used = nattr_used;
-	
 	return 0;
 }
 
-/* Process an answer: add back the ECHO attributes, if any */
-static int sed_diam_ans(struct rga_conf_state * cs, sess_id_t ** session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli )
+/* Process an answer: add the ECHO attributes back, if any */
+static int sed_diam_ans(struct rga_conf_state * cs, sess_id_t * session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli )
 {
 	int ret;
 	struct rg_list * list = NULL;
@@ -257,13 +249,13 @@
 	CHECK_PARAMS(cs);
 	
 	/* If there is no session associated, just give up */
-	if (! session || ! *session) {
+	if (! session ) {
 		TRACE_DEBUG(FULL, "No session associated with the message, nothing to do here...");
 		return 0;
 	}
 	
-	/* No try and retrieve any data from the session */
-	ret = sess_data_dereg( *session, cs->sess_hdl, (void *)&list );
+	/* Now try and retrieve any data from the session */
+	ret = sess_data_dereg( session, cs->sess_hdl, (void *)&list );
 	if (ret == ENOENT) {
 		TRACE_DEBUG(FULL, "No data saved in the session, no attribute to add back");
 		return 0;
--- a/include/waaad/session-api.h	Mon Jun 15 15:00:10 2009 +0900
+++ b/include/waaad/session-api.h	Tue Jun 16 13:37:46 2009 +0900
@@ -212,6 +212,21 @@
 int sess_unlink ( sess_id_t ** session );
 
 /*
+ * FUNCTION:	sess_destroy
+ *
+ * PARAMETERS:
+ *  session	: Pointer to a session object.
+ *
+ * DESCRIPTION: 
+ *   Destroys a session an all associated data, if any.
+ *
+ * RETURN VALUE:
+ *  0      	: The session no longer exists.
+ *  EINVAL 	: A parameter is invalid.
+ */
+int sess_destroy ( sess_id_t ** session );
+
+/*
  * FUNCTION:	sess_regext
  *
  * PARAMETERS:
@@ -308,7 +323,7 @@
 #endif /* ! IN_EXTENSION */
 
 /* The version of this API, to check binary compatibility -- increment each time a change is made in api_sess_t */
-#define WAAAD_API_SESSION_VER	1
+#define WAAAD_API_SESSION_VER	2
 
 /* Now define the type of the structure that contains the callback to pass to extensions */
 typedef struct {
@@ -322,6 +337,7 @@
 	int (*sess_getsid)     ( sess_id_t * session, char ** sid );
 	int (*sess_link)       ( sess_id_t * session );
 	int (*sess_unlink)     ( sess_id_t ** session );
+	int (*sess_destroy)    ( sess_id_t ** session );
 	int (*sess_regext)     ( sess_reg_t ** client );
 	int (*sess_deregext)   ( sess_reg_t * client );
 	int (*sess_data_reg)   ( sess_id_t * session, sess_reg_t * client, void * data, void (*cleanup)(void *) ); 
@@ -344,6 +360,7 @@
 #define sess_getsid	g_api_session->sess_getsid
 #define sess_link	g_api_session->sess_link
 #define sess_unlink	g_api_session->sess_unlink
+#define sess_destroy	g_api_session->sess_destroy
 #define sess_regext	g_api_session->sess_regext
 #define sess_deregext	g_api_session->sess_deregext
 #define sess_data_reg	g_api_session->sess_data_reg
@@ -354,7 +371,7 @@
 #else /* IN_EXTENSION */
 
 /* From the daemon, we must initialize the API object, in extension.c */
-# define MY_WAAAD_API_SESSION_VER 1
+# define MY_WAAAD_API_SESSION_VER 2
 # if MY_WAAAD_API_SESSION_VER != WAAAD_API_SESSION_VER
 #  error "You must update API_INIT_SESSION also"
 # endif
@@ -368,6 +385,7 @@
 	(api_session).sess_getsid     = sess_getsid;		\
 	(api_session).sess_link       = sess_link;		\
 	(api_session).sess_unlink     = sess_unlink;		\
+	(api_session).sess_destroy    = sess_destroy;		\
 	(api_session).sess_regext     = sess_regext;		\
 	(api_session).sess_deregext   = sess_deregext;		\
 	(api_session).sess_data_reg   = sess_data_reg;		\
--- a/waaad/session.c	Mon Jun 15 15:00:10 2009 +0900
+++ b/waaad/session.c	Tue Jun 16 13:37:46 2009 +0900
@@ -57,6 +57,7 @@
 /* The following two integers are used to generate values which are eternaly unique */
 static uint32_t   	g_sid_h;	/* initialized to the current time at module initialization */
 static uint32_t   	g_sid_l;	/* incremented each time a session id is created. */
+
 static pthread_mutex_t 	g_sid_lock = PTHREAD_MUTEX_INITIALIZER;
 
 /************ sess_id_t ***********/
@@ -536,7 +537,7 @@
 	TRACE_ENTRY( "%p", session );
 	
 	/* Check the parameter */
-	CHECK_PARAMS(   VALIDATE_SI(*session) && (session != NULL)  );
+	CHECK_PARAMS(  session && VALIDATE_SI(*session)  );
 	
 	/* Copy the hash value */
 	hash = _SI(*session)->hash;
@@ -743,6 +744,41 @@
 	return noent ? ENOENT : 0;
 }
 
+/* Destroy a session and any registered data */
+int sess_destroy( sess_id_t ** session )
+{
+	int ret = 0;
+	uti_list_t *li, *item = NULL;
+	
+	TRACE_ENTRY( "%p", session );
+	
+	/* Check the parameter */
+	CHECK_PARAMS(  session && VALIDATE_SI(*session)  );
+	
+	/* Lock the global list of handlers */
+	CHECK_POSIX( pthread_mutex_lock(&_str_lock) );
+	
+	for (li = _srt_sentinel.next; li != &_srt_sentinel; li = li->next) {
+		/* Lock the sublist */
+		CHECK_POSIX(	pthread_mutex_lock( &_SR(li)->lock )	);
+		
+		/* Search for data registered for this session in this handler */
+		ret = find_sd(_SI(*session), &_SR(li)->sentinel, &item);
+		
+		/* If found, destroy it */
+		if (ret == 0) {
+			CHECK_FCT( _destroy_sr_data(_SD(item), NULL) )
+		}
+		
+		CHECK_POSIX(	pthread_mutex_unlock( &_SR(li)->lock )	);
+	}
+	
+	CHECK_POSIX( pthread_mutex_unlock(&_str_lock) );
+	
+	/* Now unlink the session. It will be freed unless someone had called "sess_link" explicitely. */
+	return sess_unlink(session);
+}
+
 /* End of the module */
 int sess_fini ( void )
 {
"Welcome to our mercurial repository"