mod_netconf: Every request is processed by new thread. Added session locking.
diff --git a/src/mod_netconf.c b/src/mod_netconf.c
index ca62efa..ceff507 100644
--- a/src/mod_netconf.c
+++ b/src/mod_netconf.c
@@ -48,6 +48,8 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/un.h>
+#include <pthread.h>
+#include <ctype.h>
 
 #include <unixd.h>
 #include <httpd.h>
@@ -112,6 +114,20 @@
 	char* sockname;
 } mod_netconf_cfg;
 
+struct pass_to_thread {
+	int client; /**< opened socket */
+	apr_pool_t * pool; /**< ?? */
+	server_rec * server; /**< ?? */
+	apr_hash_t * netconf_sessions_list; /**< ?? */
+};
+
+struct session_with_mutex {
+	struct nc_session * session; /**< netconf session */
+	pthread_mutex_t lock; /**< mutex protecting the session from multiple access */
+};
+
+pthread_rwlock_t session_lock; /**< mutex protecting netconf_session_list from multiple access errors */
+
 volatile int isterminated = 0;
 
 static char* password;
@@ -214,11 +230,17 @@
 static char* netconf_connect(server_rec* server, apr_pool_t* pool, apr_hash_t* conns, const char* host, const char* port, const char* user, const char* pass)
 {
 	struct nc_session* session;
+	struct session_with_mutex * locked_session;
 	char *session_key;
+	struct nc_cpblts * cpblts = nc_session_get_cpblts_default ();
+
+	nc_cpblts_add(cpblts, "urn:cesnet:tmc:netopeer:1.0");
 
 	/* connect to the requested NETCONF server */
 	password = (char*)pass;
-	session = nc_session_connect(host, (unsigned short) atoi (port), user, NULL);
+	session = nc_session_connect(host, (unsigned short) atoi (port), user, cpblts);
+
+	nc_cpblts_free (cpblts);
 
 	/* if connected successful, add session to the list */
 	if (session != NULL) {
@@ -227,7 +249,28 @@
 				(host==NULL) ? "localhost" : host,
 				(port==NULL) ? "830" : port,
 				nc_session_get_id(session));
-		apr_hash_set(conns, apr_pstrdup(pool, session_key), APR_HASH_KEY_STRING, (void *) session);
+
+		if ((locked_session = malloc (sizeof (struct session_with_mutex))) == NULL || pthread_mutex_init (&locked_session->lock, NULL) != 0) {
+			nc_session_free(session);
+			free (locked_session);
+			ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Creating structure session_with_mutex failed %d (%s)", errno, strerror(errno));
+			return NULL;
+		}
+		locked_session->session = session;
+		pthread_mutex_init (&locked_session->lock, NULL);
+		/* get exclusive access to sessions_list (conns) */
+		if (pthread_rwlock_wrlock (&session_lock) != 0) {
+			nc_session_free(session);
+			free (locked_session);
+			ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
+			return NULL;
+		}
+		apr_hash_set(conns, apr_pstrdup(pool, session_key), APR_HASH_KEY_STRING, (void *) locked_session);
+		/* end of critical section */
+		if (pthread_rwlock_unlock (&session_lock) != 0) {
+			ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
+			return NULL;
+		}
 		ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, server, "NETCONF session established");
 		return (session_key);
 	} else {
@@ -240,9 +283,20 @@
 static int netconf_close(server_rec* server, apr_hash_t* conns, const char* session_key)
 {
 	struct nc_session *ns = NULL;
+	struct session_with_mutex * locked_session;
 
 	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Key in hash to get: %s", session_key);
-	ns = (struct nc_session *)apr_hash_get(conns, session_key, APR_HASH_KEY_STRING);
+	/* get exclusive (write) access to sessions_list (conns) */
+	if (pthread_rwlock_wrlock (&session_lock) != 0) {
+		ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
+		return EXIT_FAILURE;
+	}
+	locked_session = (struct session_with_mutex *)apr_hash_get(conns, session_key, APR_HASH_KEY_STRING);
+	if (locked_session != NULL) {
+		pthread_mutex_destroy(&locked_session->lock);
+		ns = locked_session->session;
+		free (locked_session);
+	}
 	if (ns != NULL) {
 		nc_session_close (ns, "NETCONF session closed by client");
 		nc_session_free (ns);
@@ -250,6 +304,11 @@
 
 		/* remove session from the active sessions list */
 		apr_hash_set(conns, session_key, APR_HASH_KEY_STRING, NULL);
+		/* end of critical section */
+		if (pthread_rwlock_unlock (&session_lock) != 0) {
+			ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
+			return EXIT_FAILURE;
+		}
 		ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, server, "NETCONF session closed");
 
 		return (EXIT_SUCCESS);
@@ -262,6 +321,7 @@
 static int netconf_op(server_rec* server, apr_hash_t* conns, const char* session_key, nc_rpc* rpc)
 {
 	struct nc_session *session = NULL;
+	struct session_with_mutex * locked_session;
 	nc_reply* reply;
 	int retval = EXIT_SUCCESS;
 
@@ -271,21 +331,58 @@
 		return (EXIT_FAILURE);
 	}
 
+	/* get non-exclusive (read) access to sessions_list (conns) */
+	if (pthread_rwlock_rdlock (&session_lock) != 0) {
+		ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
+		return EXIT_FAILURE;
+	}
 	/* get session where send the RPC */
-	session = (struct nc_session *)apr_hash_get(conns, session_key, APR_HASH_KEY_STRING);
+	locked_session = (struct session_with_mutex *)apr_hash_get(conns, session_key, APR_HASH_KEY_STRING);
+	if (locked_session != NULL) {
+		session = locked_session->session;
+	}
 	if (session != NULL) {
+		/* get exclusive access to session */
+		if (pthread_mutex_lock(&locked_session->lock) != 0) {
+			/* unlock before returning error */
+			if (pthread_rwlock_unlock (&session_lock) != 0) {
+				ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
+				return EXIT_FAILURE;
+			}
+			return EXIT_FAILURE;
+		}
 		/* send the request and get the reply */
 		nc_session_send_rpc (session, rpc);
 		if (nc_session_recv_reply (session, &reply) == 0) {
 			if (nc_session_get_status(session) != NC_SESSION_STATUS_WORKING) {
 				ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "mod_netconf: receiving rpc-reply failed");
+				/* first release exclusive lock for this session */
+				pthread_mutex_unlock(&locked_session->lock);
+				/* release read lock, netconf_close will get exclusive access and close this session */
+				if (pthread_rwlock_unlock (&session_lock) != 0) {
+					ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
+					return EXIT_FAILURE;
+				}
 				netconf_close(server, conns, session_key);
 				return (EXIT_FAILURE);
 			}
-
+			/* first release exclusive lock for this session */
+			pthread_mutex_unlock(&locked_session->lock);
+			/* unlock before returning error */
+			if (pthread_rwlock_unlock (&session_lock) != 0) {
+				ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
+				return EXIT_FAILURE;
+			}
 			/* there is error handled by callback */
 			return (EXIT_FAILURE);
 		}
+		/* first release exclusive lock for this session */
+		pthread_mutex_unlock(&locked_session->lock);
+		/* end of critical section */
+		if (pthread_rwlock_unlock (&session_lock) != 0) {
+			ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
+			return EXIT_FAILURE;
+		}
 
 		switch (nc_reply_get_type (reply)) {
 		case NC_REPLY_OK:
@@ -307,6 +404,7 @@
 static char* netconf_opdata(server_rec* server, apr_hash_t* conns, const char* session_key, nc_rpc* rpc)
 {
 	struct nc_session *session = NULL;
+	struct session_with_mutex * locked_session;
 	nc_reply* reply;
 	char* data;
 
@@ -316,21 +414,58 @@
 		return (NULL);
 	}
 
+	/* get non-exclusive (read) access to sessions_list (conns) */
+	if (pthread_rwlock_rdlock (&session_lock) != 0) {
+		ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
+		return NULL;
+	}
 	/* get session where send the RPC */
-	session = (struct nc_session *)apr_hash_get(conns, session_key, APR_HASH_KEY_STRING);
+	locked_session = (struct session_with_mutex *)apr_hash_get(conns, session_key, APR_HASH_KEY_STRING);
+	if (locked_session != NULL) {
+		session = locked_session->session;
+	}
 	if (session != NULL) {
+		/* get exclusive access to session */
+		if (pthread_mutex_lock(&locked_session->lock) != 0) {
+			/* unlock before returning error */
+			if (pthread_rwlock_unlock (&session_lock) != 0) {
+				ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while locking rwlock: %d (%s)", errno, strerror(errno));
+				return NULL;
+			}
+			return NULL;
+		}
 		/* send the request and get the reply */
 		nc_session_send_rpc (session, rpc);
 		if (nc_session_recv_reply (session, &reply) == 0) {
 			if (nc_session_get_status(session) != NC_SESSION_STATUS_WORKING) {
 				ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "mod_netconf: receiving rpc-reply failed");
+				/* first release exclusive lock for this session */
+				pthread_mutex_unlock(&locked_session->lock);
+				/* release read lock, netconf_close will get exclusive access and close this session */
+				if (pthread_rwlock_unlock (&session_lock) != 0) {
+					ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
+					return NULL;
+				}
 				netconf_close(server, conns, session_key);
 				return (NULL);
 			}
-
+			/* first release exclusive lock for this session */
+			pthread_mutex_unlock(&locked_session->lock);
+			/* unlock before returning error */
+			if (pthread_rwlock_unlock (&session_lock) != 0) {
+				ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
+				return NULL;
+			}
 			/* there is error handled by callback */
 			return (NULL);
 		}
+		/* first release exclusive lock for this session */
+		pthread_mutex_unlock(&locked_session->lock);
+		/* end of critical section */
+		if (pthread_rwlock_unlock (&session_lock) != 0) {
+			ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "Error while unlocking rwlock: %d (%s)", errno, strerror(errno));
+			return NULL;
+		}
 
 		switch (nc_reply_get_type (reply)) {
 		case NC_REPLY_DATA:
@@ -566,6 +701,494 @@
 	}
 }
 
+void * thread_routine (void * arg)
+{
+	void * retval = NULL;
+
+	ssize_t buffer_len;
+	struct pollfd fds;
+	int status, buffer_size, ret;
+	json_object *request, *reply, *json_obj;
+	int operation;
+	int i, chunk_len;
+	char* session_key, *data;
+	const char *host, *port, *user, *pass;
+	const char *msgtext, *cpbltstr;
+	const char *target, *source, *filter, *config, *defop, *erropt, *sid;
+	struct nc_session *session = NULL;
+	struct nc_cpblts* cpblts;
+	NC_DATASTORE ds_type_s, ds_type_t;
+	NC_EDIT_DEFOP_TYPE defop_type = 0;
+	NC_EDIT_ERROPT_TYPE erropt_type = 0;
+
+	apr_pool_t * pool = ((struct pass_to_thread*)arg)->pool;
+	apr_hash_t *netconf_sessions_list = ((struct pass_to_thread*)arg)->netconf_sessions_list;
+	server_rec * server = ((struct pass_to_thread*)arg)->server;
+	int client = ((struct pass_to_thread*)arg)->client;
+
+	char * buffer, chunk_len_str[12], *chunked_msg;
+	char c;
+
+	while (!isterminated) {
+		fds.fd = client;
+		fds.events = POLLIN;
+		fds.revents = 0;
+
+		status = poll(&fds, 1, 1000);
+
+		if (status == 0 || (status == -1 && (errno == EAGAIN || (errno == EINTR && isterminated == 0)))) {
+			/* poll was interrupted - check if the isterminated is set and if not, try poll again */
+			//ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "poll interrupted");
+			continue;
+		} else if (status < 0) {
+			/* 0:  poll time outed
+			 *     close socket and ignore this request from the client, it can try it again
+			 * -1: poll failed
+			 *     something wrong happend, close this socket and wait for another request
+			 */
+			//ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "poll failed, status %d(%d: %s)", status, errno, strerror(errno));
+			close(client);
+			break;
+		}
+		/* status > 0 */
+
+		/* check the status of the socket */
+
+		/* if nothing to read and POLLHUP (EOF) or POLLERR set */
+		if ((fds.revents & POLLHUP) || (fds.revents & POLLERR)) {
+			/* close client's socket (it's probably already closed by client */
+			//ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "socket error (%d)", fds.revents);
+			close(client);
+			break;
+		}
+
+		/* read json in chunked framing */
+		buffer_size = 0;
+		buffer_len = 0;
+		buffer = NULL;
+		while (1) {
+			fds.fd = client;
+			fds.events = POLLIN;
+			fds.revents = 0;
+
+			status = poll(&fds, 1, 1000);
+
+			if (status == 0 || (status == -1 && (errno == EAGAIN || (errno == EINTR && isterminated == 0)))) {
+				/* poll was interrupted - check if the isterminated is set and if not, try poll again */
+				//ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "poll interrupted");
+				continue;
+			} else if (status < 0) {
+				/* 0:  poll time outed
+				 *     close socket and ignore this request from the client, it can try it again
+				 * -1: poll failed
+				 *     something wrong happend, close this socket and wait for another request
+				 */
+				//ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "poll failed, status %d(%d: %s)", status, errno, strerror(errno));
+				close(client);
+				break;
+			}
+			/* status > 0 */
+
+		if (buffer != NULL) {
+			request = json_tokener_parse(buffer);
+			operation = json_object_get_int(json_object_object_get(request, "type"));
+
+			session_key = (char*) json_object_get_string(json_object_object_get(request, "session"));
+			/* DO NOT FREE session_key HERE, IT IS PART OF REQUEST */
+			if (operation != MSG_CONNECT && session_key == NULL) {
+				reply =  json_object_new_object();
+				json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+				json_object_object_add(reply, "error-message", json_object_new_string("Missing session specification."));
+				msgtext = json_object_to_json_string(reply);
+				send(client, msgtext, strlen(msgtext) + 1, 0);
+				json_object_put(reply);
+				/* there is some stupid client, so close the connection to give a chance to some other client */
+				close(client);
+				break;
+			}
+
+			/* get parameters */
+			ds_type_t = -1;
+			if ((target = json_object_get_string(json_object_object_get(request, "target"))) != NULL) {
+				if (strcmp(target, "running") == 0) {
+					ds_type_t = NC_DATASTORE_RUNNING;
+				} else if (strcmp(target, "startup") == 0) {
+					ds_type_t = NC_DATASTORE_STARTUP;
+				} else if (strcmp(target, "candidate") == 0) {
+					ds_type_t = NC_DATASTORE_CANDIDATE;
+				}
+			}
+			ds_type_s = -1;
+			if ((source = json_object_get_string(json_object_object_get(request, "source"))) != NULL) {
+				if (strcmp(source, "running") == 0) {
+					ds_type_s = NC_DATASTORE_RUNNING;
+				} else if (strcmp(source, "startup") == 0) {
+					ds_type_s = NC_DATASTORE_STARTUP;
+				} else if (strcmp(source, "candidate") == 0) {
+					ds_type_s = NC_DATASTORE_CANDIDATE;
+				}
+			}
+
+			/* null global JSON error-reply */
+			err_reply = NULL;
+
+			/* prepare reply envelope */
+			reply =  json_object_new_object();
+
+			/* process required operation */
+			switch (operation) {
+			case MSG_CONNECT:
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: Connect");
+
+				host = json_object_get_string(json_object_object_get(request, "host"));
+				port = json_object_get_string(json_object_object_get(request, "port"));
+				user = json_object_get_string(json_object_object_get(request, "user"));
+				pass = json_object_get_string(json_object_object_get(request, "pass"));
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "host: %s, port: %s, user: %s", host, port, user);
+				if ((host == NULL) || (user == NULL)) {
+					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Cannot connect - insufficient input.");
+					session_key = NULL;
+				} else {
+					session_key = netconf_connect(server, pool, netconf_sessions_list, host, port, user, pass);
+					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "hash: %s", session_key);
+				}
+
+				reply =  json_object_new_object();
+				if (session_key == NULL) {
+					/* negative reply */
+					if (err_reply == NULL) {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+						json_object_object_add(reply, "error-message", json_object_new_string("Connecting NETCONF server failed."));
+					} else {
+						/* use filled err_reply from libnetconf's callback */
+						json_object_put(reply);
+						reply = err_reply;
+					}
+				} else {
+					/* positive reply */
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
+					json_object_object_add(reply, "session", json_object_new_string(session_key));
+
+					free(session_key);
+				}
+
+				break;
+			case MSG_GET:
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: get (session %s)", session_key);
+
+				filter = json_object_get_string(json_object_object_get(request, "filter"));
+
+				//ap_log_error (APLOG_MARK, APLOG_ERR, 0, server, "get filter: %p", filter);
+
+				if ((data = netconf_get(server, netconf_sessions_list, session_key, NULL)) == NULL) {
+					if (err_reply == NULL) {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+						json_object_object_add(reply, "error-message", json_object_new_string("get failed."));
+					} else {
+						/* use filled err_reply from libnetconf's callback */
+						json_object_put(reply);
+						reply = err_reply;
+					}
+				} else {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_DATA));
+					json_object_object_add(reply, "data", json_object_new_string(data));
+				}
+				break;
+			case MSG_GETCONFIG:
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: get-config (session %s)", session_key);
+
+				filter = json_object_get_string(json_object_object_get(request, "filter"));
+
+				if (ds_type_s == -1) {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+					json_object_object_add(reply, "error-message", json_object_new_string("Invalid source repository type requested."));
+					break;
+				}
+
+				if ((data = netconf_getconfig(server, netconf_sessions_list, session_key, ds_type_s, filter)) == NULL) {
+					if (err_reply == NULL) {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+						json_object_object_add(reply, "error-message", json_object_new_string("get-config failed."));
+					} else {
+						/* use filled err_reply from libnetconf's callback */
+						json_object_put(reply);
+						reply = err_reply;
+					}
+				} else {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_DATA));
+					json_object_object_add(reply, "data", json_object_new_string(data));
+				}
+				break;
+			case MSG_EDITCONFIG:
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: edit-config (session %s)", session_key);
+
+				defop = json_object_get_string(json_object_object_get(request, "default-operation"));
+				if (defop != NULL) {
+					if (strcmp(defop, "merge") == 0) {
+						defop_type = NC_EDIT_DEFOP_MERGE;
+					} else if (strcmp(defop, "replace") == 0) {
+						defop_type = NC_EDIT_DEFOP_REPLACE;
+					} else if (strcmp(defop, "none") == 0) {
+						defop_type = NC_EDIT_DEFOP_NONE;
+					} else {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+						json_object_object_add(reply, "error-message", json_object_new_string("Invalid default-operation parameter."));
+						break;
+					}
+				} else {
+					defop_type = 0;
+				}
+
+				erropt = json_object_get_string(json_object_object_get(request, "error-option"));
+				if (erropt != NULL) {
+					if (strcmp(erropt, "continue-on-error") == 0) {
+						erropt_type = NC_EDIT_ERROPT_CONT;
+					} else if (strcmp(erropt, "stop-on-error") == 0) {
+						erropt_type = NC_EDIT_ERROPT_STOP;
+					} else if (strcmp(erropt, "rollback-on-error") == 0) {
+						erropt_type = NC_EDIT_ERROPT_ROLLBACK;
+					} else {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+						json_object_object_add(reply, "error-message", json_object_new_string("Invalid error-option parameter."));
+						break;
+					}
+				} else {
+					erropt_type = 0;
+				}
+
+				if (ds_type_t == -1) {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+					json_object_object_add(reply, "error-message", json_object_new_string("Invalid target repository type requested."));
+					break;
+				}
+
+				config = json_object_get_string(json_object_object_get(request, "config"));
+				if (config == NULL) {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+					json_object_object_add(reply, "error-message", json_object_new_string("Invalid config data parameter."));
+					break;
+				}
+
+				if (netconf_editconfig(server, netconf_sessions_list, session_key, ds_type_t, defop_type, erropt_type, config) != EXIT_SUCCESS) {
+					if (err_reply == NULL) {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+						json_object_object_add(reply, "error-message", json_object_new_string("edit-config failed."));
+					} else {
+						/* use filled err_reply from libnetconf's callback */
+						json_object_put(reply);
+						reply = err_reply;
+					}
+				} else {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
+				}
+				break;
+			case MSG_COPYCONFIG:
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: copy-config (session %s)", session_key);
+				config = NULL;
+
+				if (source == NULL) {
+					/* no explicit source specified -> use config data */
+					ds_type_s = NC_DATASTORE_NONE;
+					config = json_object_get_string(json_object_object_get(request, "config"));
+				} else if (ds_type_s == -1) {
+					/* source datastore specified, but it is invalid */
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+					json_object_object_add(reply, "error-message", json_object_new_string("Invalid source repository type requested."));
+					break;
+				}
+
+				if (ds_type_t == -1) {
+					/* invalid target datastore specified */
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+					json_object_object_add(reply, "error-message", json_object_new_string("Invalid target repository type requested."));
+					break;
+				}
+
+				if (source == NULL && config == NULL) {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+					json_object_object_add(reply, "error-message", json_object_new_string("invalid input parameters - one of source and config is required."));
+				} else {
+					if (netconf_copyconfig(server, netconf_sessions_list, session_key, ds_type_s, ds_type_t, config) != EXIT_SUCCESS) {
+						if (err_reply == NULL) {
+							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+							json_object_object_add(reply, "error-message", json_object_new_string("copy-config failed."));
+						} else {
+							/* use filled err_reply from libnetconf's callback */
+							json_object_put(reply);
+							reply = err_reply;
+						}
+					} else {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
+					}
+				}
+				break;
+			case MSG_DELETECONFIG:
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: delete-config (session %s)", session_key);
+				/* no break - unifying code */
+			case MSG_LOCK:
+				if (operation == MSG_LOCK) {
+					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: lock (session %s)", session_key);
+				}
+				/* no break - unifying code */
+			case MSG_UNLOCK:
+				if (operation == MSG_UNLOCK) {
+					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: unlock (session %s)", session_key);
+				}
+
+				if (ds_type_t == -1) {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+					json_object_object_add(reply, "error-message", json_object_new_string("Invalid target repository type requested."));
+					break;
+				}
+
+				switch(operation) {
+				case MSG_DELETECONFIG:
+					status = netconf_deleteconfig(server, netconf_sessions_list, session_key, ds_type_t);
+					break;
+				case MSG_LOCK:
+					status = netconf_lock(server, netconf_sessions_list, session_key, ds_type_t);
+					break;
+				case MSG_UNLOCK:
+					status = netconf_unlock(server, netconf_sessions_list, session_key, ds_type_t);
+					break;
+				default:
+					status = -1;
+					break;
+				}
+
+				if (status != EXIT_SUCCESS) {
+					if (err_reply == NULL) {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+						json_object_object_add(reply, "error-message", json_object_new_string("operation failed."));
+					} else {
+						/* use filled err_reply from libnetconf's callback */
+						json_object_put(reply);
+						reply = err_reply;
+					}
+				} else {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
+				}
+				break;
+			case MSG_KILL:
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: kill-session, session %s", session_key);
+
+				sid = json_object_get_string(json_object_object_get(request, "session-id"));
+
+				if (sid == NULL) {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+					json_object_object_add(reply, "error-message", json_object_new_string("Missing session-id parameter."));
+					break;
+				}
+
+				if (netconf_killsession(server, netconf_sessions_list, session_key, sid) != EXIT_SUCCESS) {
+					if (err_reply == NULL) {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+						json_object_object_add(reply, "error-message", json_object_new_string("kill-session failed."));
+					} else {
+						/* use filled err_reply from libnetconf's callback */
+						json_object_put(reply);
+						reply = err_reply;
+					}
+				} else {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
+				}
+				break;
+			case MSG_DISCONNECT:
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: Disconnect session %s", session_key);
+
+				if (netconf_close(server, netconf_sessions_list, session_key) != EXIT_SUCCESS) {
+					if (err_reply == NULL) {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+						json_object_object_add(reply, "error-message", json_object_new_string("Invalid session identifier."));
+					} else {
+						/* use filled err_reply from libnetconf's callback */
+						json_object_put(reply);
+						reply = err_reply;
+					}
+				} else {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
+				}
+				break;
+			case MSG_INFO:
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: get info about session %s", session_key);
+
+				session = (struct nc_session *)apr_hash_get(netconf_sessions_list, session_key, APR_HASH_KEY_STRING);
+				if (session != NULL) {
+					json_object_object_add(reply, "sid", json_object_new_string(nc_session_get_id(session)));
+					json_object_object_add(reply, "version", json_object_new_string((nc_session_get_version(session) == 0)?"1.0":"1.1"));
+					json_object_object_add(reply, "host", json_object_new_string(nc_session_get_host(session)));
+					json_object_object_add(reply, "port", json_object_new_string(nc_session_get_port(session)));
+					json_object_object_add(reply, "user", json_object_new_string(nc_session_get_user(session)));
+					cpblts = nc_session_get_cpblts (session);
+					if (cpblts != NULL) {
+						json_obj = json_object_new_array();
+						nc_cpblts_iter_start (cpblts);
+						while ((cpbltstr = nc_cpblts_iter_next (cpblts)) != NULL) {
+							json_object_array_add(json_obj, json_object_new_string(cpbltstr));
+						}
+						json_object_object_add(reply, "capabilities", json_obj);
+					}
+				} else {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+					json_object_object_add(reply, "error-message", json_object_new_string("Invalid session identifier."));
+				}
+
+				break;
+			case MSG_GENERIC:
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: generic request for session %s", session_key);
+
+				config = json_object_get_string(json_object_object_get(request, "content"));
+
+				if (config == NULL) {
+					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+					json_object_object_add(reply, "error-message", json_object_new_string("Missing content parameter."));
+					break;
+				}
+
+				if (netconf_generic(server, netconf_sessions_list, session_key, config, &data) != EXIT_SUCCESS) {
+					if (err_reply == NULL) {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+						json_object_object_add(reply, "error-message", json_object_new_string("kill-session failed."));
+					} else {
+						/* use filled err_reply from libnetconf's callback */
+						json_object_put(reply);
+						reply = err_reply;
+					}
+				} else {
+					if (data == NULL) {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
+					} else {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_DATA));
+						json_object_object_add(reply, "data", json_object_new_string(data));
+					}
+				}
+				break;
+			default:
+				ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Unknown mod_netconf operation requested (%d)", operation);
+				reply =  json_object_new_object();
+				json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
+				json_object_object_add(reply, "error-message", json_object_new_string("Operation not supported."));
+				break;
+			}
+			json_object_put(request);
+
+				/* send reply to caller */
+				if (reply != NULL) {
+					msgtext = json_object_to_json_string(reply);
+					send(client, msgtext, strlen(msgtext) + 1, 0);
+					json_object_put(reply);
+				} else {
+					break;
+				}
+			}
+		}
+	}
+
+	free (arg);
+
+	return retval;
+}
+
 /*
  * This is actually implementation of NETCONF client
  * - requests are received from UNIX socket in the predefined format
@@ -577,25 +1200,18 @@
 static void forked_proc(apr_pool_t * pool, server_rec * server)
 {
 	struct sockaddr_un local, remote;
-	int lsock, client;
-	socklen_t len, len2;
-	struct pollfd fds;
-	int status;
+	int lsock, client, ret, i, pthread_count = 0;
+	socklen_t len;
 	mod_netconf_cfg *cfg;
-	json_object *request, *reply, *json_obj;
-	int operation;
-	char* session_key, *data;
-	const char *msgtext, *cpbltstr;
-	const char *host, *port, *user, *pass;
-	const char *target, *source, *filter, *config, *defop, *erropt, *sid;
-	struct nc_session *session = NULL;
-	struct nc_cpblts* cpblts;
-	NC_DATASTORE ds_type_s, ds_type_t;
-	NC_EDIT_DEFOP_TYPE defop_type = 0;
-	NC_EDIT_ERROPT_TYPE erropt_type = 0;
-
 	apr_hash_t *netconf_sessions_list;
-	char buffer[BUFFER_SIZE];
+	struct pass_to_thread * arg;
+	pthread_t * ptids = calloc (1,sizeof(pthread_t));
+	struct timespec maxtime;
+	pthread_rwlockattr_t lock_attrs;
+
+	/* wait at most 5 secons for every thread to terminate */
+	maxtime.tv_sec = 5;
+	maxtime.tv_nsec = 0;
 
 	/* change uid and gid of process for security reasons */
 	unixd_setup_child();
@@ -649,12 +1265,23 @@
 	/* disable publickey authentication */
 	nc_ssh_pref(NC_SSH_AUTH_PUBLIC_KEYS, -1);
 
+	/* create mutex protecting session list */
+	pthread_rwlockattr_init(&lock_attrs);
+	/* rwlock is shared only with threads in this process */
+	pthread_rwlockattr_setpshared(&lock_attrs, PTHREAD_PROCESS_PRIVATE);
+	/* create rw lock */
+	if (pthread_rwlock_init(&session_lock, &lock_attrs) != 0) {
+		ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Initialization of mutex failed: %d (%s)", errno, strerror(errno));
+		close (lsock);
+		return;
+	}
+
 	while (isterminated == 0) {
 		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "waiting for another client's request");
 
 		/* open incoming connection if any */
-		len2 = sizeof(remote);
-		client = accept(lsock, (struct sockaddr *) &remote, &len2);
+		len = sizeof(remote);
+		client = accept(lsock, (struct sockaddr *) &remote, &len);
 		if (client == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
 			apr_sleep(SLEEP_TIME);
 			continue;
@@ -664,442 +1291,52 @@
 			ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Accepting mod_netconf client connection failed (%s)", strerror(errno));
 			continue;
 		}
-		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "client's socket accepted.");
 
 		/* set client's socket as non-blocking */
 		//fcntl(client, F_SETFL, fcntl(client, F_GETFL, 0) | O_NONBLOCK);
 
-		while (1) {
-			fds.fd = client;
-			fds.events = POLLIN;
-			fds.revents = 0;
+		arg = malloc (sizeof(struct pass_to_thread));
+		arg->client = client;
+		arg->pool = pool;
+		arg->server = server;
+		arg->netconf_sessions_list = netconf_sessions_list;
 
-			status = poll(&fds, 1, 1000);
+		/* start new thread. It will serve this particular request and then terminate */
+		if ((ret = pthread_create (&ptids[pthread_count], NULL, thread_routine, (void*)arg)) != 0) {
+			ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Creating POSIX thread failed: %d\n", ret);
+		} else {
+			ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Thread %lu created", ptids[pthread_count]);
+			pthread_count++;
+			ptids = realloc (ptids, sizeof(pthread_t)*(pthread_count+1));
+			ptids[pthread_count] = 0;
+		}
 
-			if (status == 0 || (status == -1 && (errno == EAGAIN || (errno == EINTR && isterminated == 0)))) {
-				/* poll was interrupted - check if the isterminated is set and if not, try poll again */
-				//ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "poll interrupted");
-				continue;
-			} else if (status < 0) {
-				/* 0:  poll time outed
-				 *     close socket and ignore this request from the client, it can try it again
-				 * -1: poll failed
-				 *     something wrong happend, close this socket and wait for another request
-				 */
-				//ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "poll failed, status %d(%d: %s)", status, errno, strerror(errno));
-				close(client);
-				break;
-			}
-			/* status > 0 */
-
-			/* check the status of the socket */
-
-			/* if nothing to read and POLLHUP (EOF) or POLLERR set */
-			if ((fds.revents & POLLHUP) || (fds.revents & POLLERR)) {
-				/* close client's socket (it's probably already closed by client */
-				//ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "socket error (%d)", fds.revents);
-				close(client);
-				break;
-			}
-
-			if ((len2 = recv(client, buffer, BUFFER_SIZE, 0)) <= 0) {
-				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "receiving failed %d (%s)", errno, strerror(errno));
-				continue;
-			} else {
-				request = json_tokener_parse(buffer);
-				operation = json_object_get_int(json_object_object_get(request, "type"));
-
-				session_key = (char*) json_object_get_string(json_object_object_get(request, "session"));
-				/* DO NOT FREE session_key HERE, IT IS PART OF REQUEST */
-				if (operation != MSG_CONNECT && session_key == NULL) {
-					reply =  json_object_new_object();
-					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-					json_object_object_add(reply, "error-message", json_object_new_string("Missing session specification."));
-					msgtext = json_object_to_json_string(reply);
-					send(client, msgtext, strlen(msgtext) + 1, 0);
-					json_object_put(reply);
-					/* there is some stupid client, so close the connection to give a chance to some other client */
-					close(client);
-					break;
-				}
-
-				/* get parameters */
-				ds_type_t = -1;
-				if ((target = json_object_get_string(json_object_object_get(request, "target"))) != NULL) {
-					if (strcmp(target, "running") == 0) {
-						ds_type_t = NC_DATASTORE_RUNNING;
-					} else if (strcmp(target, "startup") == 0) {
-						ds_type_t = NC_DATASTORE_STARTUP;
-					} else if (strcmp(target, "candidate") == 0) {
-						ds_type_t = NC_DATASTORE_CANDIDATE;
-					}
-				}
-				ds_type_s = -1;
-				if ((source = json_object_get_string(json_object_object_get(request, "source"))) != NULL) {
-					if (strcmp(source, "running") == 0) {
-						ds_type_s = NC_DATASTORE_RUNNING;
-					} else if (strcmp(source, "startup") == 0) {
-						ds_type_s = NC_DATASTORE_STARTUP;
-					} else if (strcmp(source, "candidate") == 0) {
-						ds_type_s = NC_DATASTORE_CANDIDATE;
-					}
-				}
-
-				/* null global JSON error-reply */
-				err_reply = NULL;
-
-				/* prepare reply envelope */
-				reply =  json_object_new_object();
-
-				/* process required operation */
-				switch (operation) {
-				case MSG_CONNECT:
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: Connect");
-
-					host = json_object_get_string(json_object_object_get(request, "host"));
-					port = json_object_get_string(json_object_object_get(request, "port"));
-					user = json_object_get_string(json_object_object_get(request, "user"));
-					pass = json_object_get_string(json_object_object_get(request, "pass"));
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "host: %s, port: %s, user: %s", host, port, user);
-					if ((host == NULL) || (user == NULL)) {
-						ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Cannot connect - insufficient input.");
-						session_key = NULL;
-					} else {
-						session_key = netconf_connect(server, pool, netconf_sessions_list, host, port, user, pass);
-						ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "hash: %s", session_key);
-					}
-
-					reply =  json_object_new_object();
-					if (session_key == NULL) {
-						/* negative reply */
-						if (err_reply == NULL) {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-							json_object_object_add(reply, "error-message", json_object_new_string("Connecting NETCONF server failed."));
-						} else {
-							/* use filled err_reply from libnetconf's callback */
-							json_object_put(reply);
-							reply = err_reply;
-						}
-					} else {
-						/* positive reply */
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
-						json_object_object_add(reply, "session", json_object_new_string(session_key));
-
-						free(session_key);
-					}
-
-					break;
-				case MSG_GET:
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: get-config (session %s)", session_key);
-
-					filter = json_object_get_string(json_object_object_get(request, "filter"));
-
-					if ((data = netconf_get(server, netconf_sessions_list, session_key, filter)) == NULL) {
-						if (err_reply == NULL) {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-							json_object_object_add(reply, "error-message", json_object_new_string("get failed."));
-						} else {
-							/* use filled err_reply from libnetconf's callback */
-							json_object_put(reply);
-							reply = err_reply;
-						}
-					} else {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_DATA));
-						json_object_object_add(reply, "data", json_object_new_string(data));
-					}
-					break;
-				case MSG_GETCONFIG:
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: get-config (session %s)", session_key);
-
-					filter = json_object_get_string(json_object_object_get(request, "filter"));
-
-					if (ds_type_s == -1) {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-						json_object_object_add(reply, "error-message", json_object_new_string("Invalid source repository type requested."));
-						break;
-					}
-
-					if ((data = netconf_getconfig(server, netconf_sessions_list, session_key, ds_type_s, filter)) == NULL) {
-						if (err_reply == NULL) {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-							json_object_object_add(reply, "error-message", json_object_new_string("get-config failed."));
-						} else {
-							/* use filled err_reply from libnetconf's callback */
-							json_object_put(reply);
-							reply = err_reply;
-						}
-					} else {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_DATA));
-						json_object_object_add(reply, "data", json_object_new_string(data));
-					}
-					break;
-				case MSG_EDITCONFIG:
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: edit-config (session %s)", session_key);
-
-					defop = json_object_get_string(json_object_object_get(request, "default-operation"));
-					if (defop != NULL) {
-						if (strcmp(defop, "merge") == 0) {
-							defop_type = NC_EDIT_DEFOP_MERGE;
-						} else if (strcmp(defop, "replace") == 0) {
-							defop_type = NC_EDIT_DEFOP_REPLACE;
-						} else if (strcmp(defop, "none") == 0) {
-							defop_type = NC_EDIT_DEFOP_NONE;
-						} else {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-							json_object_object_add(reply, "error-message", json_object_new_string("Invalid default-operation parameter."));
-							break;
-						}
-					} else {
-						defop_type = 0;
-					}
-
-					erropt = json_object_get_string(json_object_object_get(request, "error-option"));
-					if (erropt != NULL) {
-						if (strcmp(erropt, "continue-on-error") == 0) {
-							erropt_type = NC_EDIT_ERROPT_CONT;
-						} else if (strcmp(erropt, "stop-on-error") == 0) {
-							erropt_type = NC_EDIT_ERROPT_STOP;
-						} else if (strcmp(erropt, "rollback-on-error") == 0) {
-							erropt_type = NC_EDIT_ERROPT_ROLLBACK;
-						} else {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-							json_object_object_add(reply, "error-message", json_object_new_string("Invalid error-option parameter."));
-							break;
-						}
-					} else {
-						erropt_type = 0;
-					}
-
-					if (ds_type_t == -1) {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-						json_object_object_add(reply, "error-message", json_object_new_string("Invalid target repository type requested."));
-						break;
-					}
-
-					config = json_object_get_string(json_object_object_get(request, "config"));
-					if (config == NULL) {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-						json_object_object_add(reply, "error-message", json_object_new_string("Invalid config data parameter."));
-						break;
-					}
-
-					if (netconf_editconfig(server, netconf_sessions_list, session_key, ds_type_t, defop_type, erropt_type, config) != EXIT_SUCCESS) {
-						if (err_reply == NULL) {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-							json_object_object_add(reply, "error-message", json_object_new_string("edit-config failed."));
-						} else {
-							/* use filled err_reply from libnetconf's callback */
-							json_object_put(reply);
-							reply = err_reply;
-						}
-					} else {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
-					}
-					break;
-				case MSG_COPYCONFIG:
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: copy-config (session %s)", session_key);
-					config = NULL;
-
-					if (source == NULL) {
-						/* no explicit source specified -> use config data */
-						ds_type_s = NC_DATASTORE_NONE;
-						config = json_object_get_string(json_object_object_get(request, "config"));
-					} else if (ds_type_s == -1) {
-						/* source datastore specified, but it is invalid */
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-						json_object_object_add(reply, "error-message", json_object_new_string("Invalid source repository type requested."));
-						break;
-					}
-
-					if (ds_type_t == -1) {
-						/* invalid target datastore specified */
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-						json_object_object_add(reply, "error-message", json_object_new_string("Invalid target repository type requested."));
-						break;
-					}
-
-					if (source == NULL && config == NULL) {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-						json_object_object_add(reply, "error-message", json_object_new_string("invalid input parameters - one of source and config is required."));
-					} else {
-						if (netconf_copyconfig(server, netconf_sessions_list, session_key, ds_type_s, ds_type_t, config) != EXIT_SUCCESS) {
-							if (err_reply == NULL) {
-								json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-								json_object_object_add(reply, "error-message", json_object_new_string("copy-config failed."));
-							} else {
-								/* use filled err_reply from libnetconf's callback */
-								json_object_put(reply);
-								reply = err_reply;
-							}
-						} else {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
-						}
-					}
-					break;
-				case MSG_DELETECONFIG:
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: delete-config (session %s)", session_key);
-					/* no break - unifying code */
-				case MSG_LOCK:
-					if (operation == MSG_LOCK) {
-						ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: lock (session %s)", session_key);
-					}
-					/* no break - unifying code */
-				case MSG_UNLOCK:
-					if (operation == MSG_UNLOCK) {
-						ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: unlock (session %s)", session_key);
-					}
-
-					if (ds_type_t == -1) {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-						json_object_object_add(reply, "error-message", json_object_new_string("Invalid target repository type requested."));
-						break;
-					}
-
-					switch(operation) {
-					case MSG_DELETECONFIG:
-						status = netconf_deleteconfig(server, netconf_sessions_list, session_key, ds_type_t);
-						break;
-					case MSG_LOCK:
-						status = netconf_lock(server, netconf_sessions_list, session_key, ds_type_t);
-						break;
-					case MSG_UNLOCK:
-						status = netconf_unlock(server, netconf_sessions_list, session_key, ds_type_t);
-						break;
-					default:
-						status = -1;
-						break;
-					}
-
-					if (status != EXIT_SUCCESS) {
-						if (err_reply == NULL) {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-							json_object_object_add(reply, "error-message", json_object_new_string("operation failed."));
-						} else {
-							/* use filled err_reply from libnetconf's callback */
-							json_object_put(reply);
-							reply = err_reply;
-						}
-					} else {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
-					}
-					break;
-				case MSG_KILL:
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: kill-session, session %s", session_key);
-
-					sid = json_object_get_string(json_object_object_get(request, "session-id"));
-
-					if (sid == NULL) {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-						json_object_object_add(reply, "error-message", json_object_new_string("Missing session-id parameter."));
-						break;
-					}
-
-					if (netconf_killsession(server, netconf_sessions_list, session_key, sid) != EXIT_SUCCESS) {
-						if (err_reply == NULL) {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-							json_object_object_add(reply, "error-message", json_object_new_string("kill-session failed."));
-						} else {
-							/* use filled err_reply from libnetconf's callback */
-							json_object_put(reply);
-							reply = err_reply;
-						}
-					} else {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
-					}
-					break;
-				case MSG_DISCONNECT:
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: Disconnect session %s", session_key);
-
-					if (netconf_close(server, netconf_sessions_list, session_key) != EXIT_SUCCESS) {
-						if (err_reply == NULL) {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-							json_object_object_add(reply, "error-message", json_object_new_string("Invalid session identifier."));
-						} else {
-							/* use filled err_reply from libnetconf's callback */
-							json_object_put(reply);
-							reply = err_reply;
-						}
-					} else {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
-					}
-					break;
-				case MSG_INFO:
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: get info about session %s", session_key);
-
-					session = (struct nc_session *)apr_hash_get(netconf_sessions_list, session_key, APR_HASH_KEY_STRING);
-					if (session != NULL) {
-						json_object_object_add(reply, "sid", json_object_new_string(nc_session_get_id(session)));
-						json_object_object_add(reply, "version", json_object_new_string((nc_session_get_version(session) == 0)?"1.0":"1.1"));
-						json_object_object_add(reply, "host", json_object_new_string(nc_session_get_host(session)));
-						json_object_object_add(reply, "port", json_object_new_string(nc_session_get_port(session)));
-						json_object_object_add(reply, "user", json_object_new_string(nc_session_get_user(session)));
-						cpblts = nc_session_get_cpblts (session);
-						if (cpblts != NULL) {
-							json_obj = json_object_new_array();
-							nc_cpblts_iter_start (cpblts);
-							while ((cpbltstr = nc_cpblts_iter_next (cpblts)) != NULL) {
-								json_object_array_add(json_obj, json_object_new_string(cpbltstr));
-							}
-							json_object_object_add(reply, "capabilities", json_obj);
-						}
-					} else {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-						json_object_object_add(reply, "error-message", json_object_new_string("Invalid session identifier."));
-					}
-
-					break;
-				case MSG_GENERIC:
-					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: generic request for session %s", session_key);
-
-					config = json_object_get_string(json_object_object_get(request, "content"));
-
-					if (config == NULL) {
-						json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-						json_object_object_add(reply, "error-message", json_object_new_string("Missing content parameter."));
-						break;
-					}
-
-					if (netconf_generic(server, netconf_sessions_list, session_key, config, &data) != EXIT_SUCCESS) {
-						if (err_reply == NULL) {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-							json_object_object_add(reply, "error-message", json_object_new_string("kill-session failed."));
-						} else {
-							/* use filled err_reply from libnetconf's callback */
-							json_object_put(reply);
-							reply = err_reply;
-						}
-					} else {
-						if (data == NULL) {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
-						} else {
-							json_object_object_add(reply, "type", json_object_new_int(REPLY_DATA));
-							json_object_object_add(reply, "data", json_object_new_string(data));
-						}
-					}
-					break;
-				default:
-					ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Unknown mod_netconf operation requested (%d)", operation);
-					reply =  json_object_new_object();
-					json_object_object_add(reply, "type", json_object_new_int(REPLY_ERROR));
-					json_object_object_add(reply, "error-message", json_object_new_string("Operation not supported."));
-					break;
-				}
-				json_object_put(request);
-
-				/* send reply to caller */
-				if (reply != NULL) {
-					msgtext = json_object_to_json_string(reply);
-					send(client, msgtext, strlen(msgtext) + 1, 0);
-					json_object_put(reply);
-				} else {
-					break;
+		/* check if some thread already terminated, free some resources by joining it */
+		for (i=0; i<pthread_count; i++) {
+			if (pthread_tryjoin_np (ptids[i], (void**)&arg) == 0) {
+				ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Thread %lu joined with retval %p", ptids[i], arg);
+				pthread_count--;
+				if (pthread_count > 0) {
+					/* place last Thread ID on the place of joined one */
+					ptids[i] = ptids[pthread_count];
 				}
 			}
 		}
+		ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Running %d threads", pthread_count);
 	}
 
+	/* join all threads */
+	for (i=0; i<pthread_count; i++) {
+		pthread_timedjoin_np (ptids[i], (void**)&arg, &maxtime);
+	}
+	free (ptids);
+
 	close(lsock);
 
+	/* destroy rwlock */
+	pthread_rwlock_destroy(&session_lock);
+	pthread_rwlockattr_destroy(&lock_attrs);
+
 	ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Exiting from the mod_netconf daemon");
 
 	exit(APR_SUCCESS);