mod_netconf: copy-config support

copy-config operation is now supported by mod_netconf.
diff --git a/src/mod_netconf.c b/src/mod_netconf.c
index 23c3251..ffc0500 100644
--- a/src/mod_netconf.c
+++ b/src/mod_netconf.c
@@ -308,7 +308,7 @@
 	struct nc_session *session = NULL;
 	nc_rpc* rpc;
 	nc_reply* reply;
-	char* data;
+	char* data = NULL;
 	struct nc_filter *f = NULL;
 
 	session = (struct nc_session *)apr_hash_get(conns, session_key, APR_HASH_KEY_STRING);
@@ -350,7 +350,8 @@
 			break;
 		default:
 			ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "mod_netconf: unexpected rpc-reply");
-			return (NULL);
+			data = NULL; /* error return code */
+			break;
 		}
 		nc_reply_free(reply);
 
@@ -361,6 +362,63 @@
 	}
 }
 
+static int netconf_copyconfig(server_rec* server, apr_hash_t* conns, char* session_key, NC_DATASTORE source, NC_DATASTORE target)
+{
+	struct nc_session *session = NULL;
+	nc_rpc* rpc;
+	nc_reply* reply;
+	int retval = EXIT_SUCCESS;
+
+	session = (struct nc_session *)apr_hash_get(conns, session_key, APR_HASH_KEY_STRING);
+	if (session != NULL) {
+		/* create requests */
+		rpc = nc_rpc_copyconfig(source, target, NULL);
+		if (rpc == NULL) {
+			ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "mod_netconf: creating rpc request failed");
+			return (EXIT_FAILURE);
+		}
+ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "1");
+
+		/* send the request and get the reply */
+		nc_session_send_rpc (session, rpc);
+ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "2");
+		if (nc_session_recv_reply (session, &reply) == 0) {
+ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "3");
+			nc_rpc_free (rpc);
+ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "4");
+			if (nc_session_get_status(session) != NC_SESSION_STATUS_WORKING) {
+ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "5");
+				ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "mod_netconf: receiving rpc-reply failed");
+ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "6");
+				netconf_close(server, conns, session_key);
+ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "7");
+				return (EXIT_FAILURE);
+			}
+
+			/* there is error handled by callback */
+			return (EXIT_FAILURE);
+		}
+ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "8");
+		nc_rpc_free (rpc);
+ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "9");
+
+		switch (nc_reply_get_type (reply)) {
+		case NC_REPLY_OK:
+			retval = EXIT_SUCCESS;
+			break;
+		default:
+			ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "mod_netconf: unexpected rpc-reply");
+			retval = EXIT_FAILURE;
+			break;
+		}
+		nc_reply_free(reply);
+		return (retval);
+	} else {
+		ap_log_error(APLOG_MARK, APLOG_ERR, 0, server, "Unknown session to process.");
+		return (EXIT_FAILURE);
+	}
+}
+
 server_rec* clb_print_server;
 int clb_print(const char* msg)
 {
@@ -506,6 +564,20 @@
 				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;
+				}
+
 				switch (operation) {
 				case MSG_CONNECT:
 					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: Connect");
@@ -532,9 +604,7 @@
 					free(session_key);
 					break;
 				case MSG_GET:
-					session_key = (char*)json_object_get_string(json_object_object_get(request, "session"));
 					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: get-config (session %s)", session_key);
-					/* DO NOT FREE session_key HERE, IT IS PART OF REQUEST */
 
 					filter = json_object_get_string(json_object_object_get(request, "filter"));
 
@@ -542,22 +612,19 @@
 
 					if ((data = netconf_get(server, netconf_sessions_list, session_key, filter)) == 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."));
-						break;
+						json_object_object_add(reply, "error-message", json_object_new_string("get failed."));
+					} else {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_DATA));
+						json_object_object_add(reply, "data", json_object_new_string(data));
 					}
-
-					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:
-					session_key = (char*)json_object_get_string(json_object_object_get(request, "session"));
 					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: get-config (session %s)", session_key);
-					/* DO NOT FREE session_key HERE, IT IS PART OF REQUEST */
 
 					source = json_object_get_string(json_object_object_get(request, "source"));
 					filter = json_object_get_string(json_object_object_get(request, "filter"));
 
-					reply =  json_object_new_object();
+					reply = json_object_new_object();
 
 					if (strcmp(source, "running") == 0) {
 						ds_type1 = NC_DATASTORE_RUNNING;
@@ -567,23 +634,58 @@
 						ds_type1 = NC_DATASTORE_CANDIDATE;
 					} 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 target repository type requested."));
+						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_type1, filter)) == 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 {
+						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_COPYCONFIG:
+					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: copy-config (session %s)", session_key);
+
+					source = json_object_get_string(json_object_object_get(request, "source"));
+					target = json_object_get_string(json_object_object_get(request, "target"));
+
+					reply = json_object_new_object();
+
+					if (strcmp(source, "running") == 0) {
+						ds_type1 = NC_DATASTORE_RUNNING;
+					} else if (strcmp(source, "startup") == 0) {
+						ds_type1 = NC_DATASTORE_STARTUP;
+					} else if (strcmp(source, "candidate") == 0) {
+						ds_type1 = NC_DATASTORE_CANDIDATE;
+					} 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 source repository type requested."));
+						break;
+					}
+					if (strcmp(target, "running") == 0) {
+						ds_type2 = NC_DATASTORE_RUNNING;
+					} else if (strcmp(target, "startup") == 0) {
+						ds_type2 = NC_DATASTORE_STARTUP;
+					} else if (strcmp(target, "candidate") == 0) {
+						ds_type2 = NC_DATASTORE_CANDIDATE;
+					} 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 target repository type requested."));
 						break;
 					}
 
-					json_object_object_add(reply, "type", json_object_new_int(REPLY_DATA));
-					json_object_object_add(reply, "data", json_object_new_string(data));
+					if (netconf_copyconfig(server, netconf_sessions_list, session_key, ds_type1, ds_type2) != EXIT_SUCCESS) {
+						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 {
+						json_object_object_add(reply, "type", json_object_new_int(REPLY_OK));
+					}
 					break;
 				case MSG_DISCONNECT:
-					session_key = (char*)json_object_get_string(json_object_object_get(request, "session"));
 					ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "Request: Disconnect session %s", session_key);
-					/* DO NOT FREE session_key HERE, IT IS PART OF REQUEST */
 
 					reply =  json_object_new_object();
 					if (netconf_close(server, netconf_sessions_list, session_key) != EXIT_SUCCESS) {
@@ -610,13 +712,6 @@
 				} else {
 					break;
 				}
-
-#if 0
-				case MSG_DATA:
-					/* netconf data */
-					handle_netconf_request(server, client, netconf_sessions_list, &buffer[1]);
-					break;
-#endif
 			}
 		}
 	}
diff --git a/src/test-client.c b/src/test-client.c
index f9e099e..d0d887a 100644
--- a/src/test-client.c
+++ b/src/test-client.c
@@ -88,7 +88,7 @@
 
 int main (int argc, char* argv[])
 {
-	json_object* msg = NULL, *reply;
+	json_object* msg = NULL, *reply = NULL;
 	const char* msg_text;
 	int sock;
 	struct sockaddr_un addr;
@@ -148,6 +148,20 @@
 		line[(strlen(line)-1)] = 0;
 		json_object_object_add(msg, "session", json_object_new_string(line));
 	} else if (strcmp(argv[1], "copy-config") == 0) {
+		msg = json_object_new_object();
+		json_object_object_add(msg, "type", json_object_new_int(MSG_COPYCONFIG));
+		printf("Session: ");
+		getline (&line, &len, stdin);
+		line[(strlen(line)-1)] = 0;
+		json_object_object_add(msg, "session", json_object_new_string(line));
+		printf("Source (running|startup|candidate): ");
+		getline (&line, &len, stdin);
+		line[(strlen(line)-1)] = 0;
+		json_object_object_add(msg, "source", json_object_new_string(line));
+		printf("Target (running|startup|candidate): ");
+		getline (&line, &len, stdin);
+		line[(strlen(line)-1)] = 0;
+		json_object_object_add(msg, "target", json_object_new_string(line));
 	} else if (strcmp(argv[1], "delete-config") == 0) {
 	} else if (strcmp(argv[1], "edit-config") == 0) {
 	} else if (strcmp(argv[1], "get") == 0) {
@@ -193,7 +207,10 @@
 	if (msg != NULL) {
 		msg_text = json_object_to_json_string(msg);
 
-		printf("Sending: %s\n", msg_text);
+		if (json_object_object_get(msg, "pass") == NULL) {
+			/* print message only if it does not contain password */
+			printf("Sending: %s\n", msg_text);
+		}
 		send(sock, msg_text, strlen(msg_text) + 1, 0);
 
 		json_object_put(msg);
@@ -203,23 +220,29 @@
 	}
 
 	len = recv(sock, buffer, BUFFER_SIZE, 0);
-	reply = json_tokener_parse(buffer);
-	printf("Received:\n");
-	json_object_object_foreach(reply, key, value) {
-		printf("Key: %s, Value: ", key);
-		switch(json_object_get_type(value)) {
-		case json_type_string:
-			printf("%s\n", json_object_get_string(value));
-			break;
-		case json_type_int:
-			printf("%d\n", json_object_get_int(value));
-			break;
-		default:
-			printf("\n");
-			break;
-		}
+	if (len > 0) {
+		reply = json_tokener_parse(buffer);
 	}
-	json_object_put(reply);
+	printf("Received:\n");
+	if (reply == NULL) {
+		printf("(null)\n");
+	} else {
+		json_object_object_foreach(reply, key, value) {
+			printf("Key: %s, Value: ", key);
+			switch (json_object_get_type(value)) {
+			case json_type_string:
+				printf("%s\n", json_object_get_string(value));
+				break;
+			case json_type_int:
+				printf("%d\n", json_object_get_int(value));
+				break;
+			default:
+				printf("\n");
+				break;
+			}
+		}
+		json_object_put(reply);
+	}
 	close(sock);
 	free(line);