diff --git a/zte-mf823/Makefile b/zte-mf823/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3ce6719db2432ae40ecdb3fdc9a939bfc910866b
--- /dev/null
+++ b/zte-mf823/Makefile
@@ -0,0 +1,36 @@
+CC		= gcc
+CFLAGS		= -g -Wall
+LIBS		= -ljson-c -lubox -lubus -luci -lcurl
+
+PAOBJS		= parse_args.o
+PASRCS		= parse_args.c
+HSRCS		= parse_args.h
+
+all: libmobile dongle_apn dongle_pin dongle_network
+
+libmobile: ${MOBJS}
+	${CC} -c ${MSRCS} -o ${MOBJS}
+
+DAOBJS		= dongle_apn.o
+DASRCS		= dongle_apn.c
+dongle_apn: ${DAOBJS}
+	 ${CC} ${CFLAGS} ${DAOBJS} ${MOBJS} -o dongle_apn  ${LIBS}
+
+DPOBJS		= dongle_pin.o
+DPSRCS		= dongle_pin.c
+dongle_pin: ${DPOBJS}
+	 ${CC} ${CFLAGS} ${DPOBJS} ${MOBJS} -o dongle_pin  ${LIBS}
+
+DNOBJS		= dongle_network.o
+DNSRCS		= dongle_network.c
+dongle_network: ${DNOBJS}
+	 ${CC} ${CFLAGS} ${DNOBJS} ${MOBJS} -o dongle_network  ${LIBS}
+clean:
+	rm -f dongle_apn dongle_pin dongle_network *.o
+
+codingstyle:
+	checkpatch.pl --no-tree -f *.c --terse
+
+cppcheck:
+	cppcheck --enable=all *.c
+
diff --git a/zte-mf823/common.h b/zte-mf823/common.h
new file mode 100644
index 0000000000000000000000000000000000000000..cad2ff1dd5f4d85ad8ad0012a33c755e365bcbc7
--- /dev/null
+++ b/zte-mf823/common.h
@@ -0,0 +1,10 @@
+#ifndef COMMON_H
+#define COMMON_H
+#define PIPE_PATH "/tmp/appipe"
+#define DEBUG(message, ...)						\
+	do											\
+	{											\
+		fprintf(stdout, "\n(DEBUG)\t");			\
+		fprintf(stdout, message, ##__VA_ARGS__);\
+	} while (0)
+#endif
diff --git a/zte-mf823/dongle_apn.c b/zte-mf823/dongle_apn.c
new file mode 100644
index 0000000000000000000000000000000000000000..4bf679be5c686bec7f65da3f5c2f5e2a32d7c7f1
--- /dev/null
+++ b/zte-mf823/dongle_apn.c
@@ -0,0 +1,209 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <time.h>
+#include <limits.h>
+
+#include <libubox/list.h>
+#include <libubus.h>
+
+#include "libmobile.h"
+
+struct ubus_context *ctx;
+
+enum {
+	APN,
+	__APN_MAX,
+};
+
+const struct blobmsg_policy apn_policy[__APN_MAX] = {
+	[APN] = {.name = "apn", .type = BLOBMSG_TYPE_STRING},
+};
+
+int list_apn_profiles(struct ubus_context *ctx, struct ubus_object *obj,
+			   struct ubus_request_data *req, const char *method,
+			   struct blob_attr *msg)
+{
+	struct json_object *apn_profiles = get_apn_profiles_json();
+	struct blob_buf bb;
+
+	memset(&bb, 0, sizeof(struct blob_buf));
+	blob_buf_init(&bb, 0);
+	json_object_object_foreach(apn_profiles, key, val) {
+		char *apn_profile = json_object_get_string(val);
+
+		if (strlen(apn_profile) > 0) {
+			char *apn_name = strtok(apn_profile, "($)");
+
+			blobmsg_add_string(&bb, key, apn_name);
+		} else
+			break;
+	}
+	ubus_send_reply(ctx, req, bb.head);
+
+	json_object_put(apn_profiles);
+	blob_buf_free(&bb);
+	return 0;
+}
+
+int delete_apn_profile(struct ubus_context *ctx, struct ubus_object *obj,
+			   struct ubus_request_data *req, const char *method,
+			   struct blob_attr *msg)
+{
+	struct json_object *apn_profiles = get_apn_profiles_json();
+	struct blob_attr *tb[__APN_MAX];
+	char name[1024]; //what is max available name length in dongle?
+	int idx;
+
+	blobmsg_parse(apn_policy, __APN_MAX, tb, blob_data(msg), blob_len(msg));
+	name[0] = '\0';
+	strncpy(name, (char *)blobmsg_data(tb[APN]), 1023);
+	printf("name to remove: %s\n", name);
+
+	idx = get_apn_profile_idx(apn_profiles, name);
+
+	if (idx >= 0) {
+		char response[1024];
+		int rv;
+
+		rv = _delete_apn(idx, response);
+		if (rv < 0)
+			return -1;
+
+		struct json_object *parsed_response = json_tokener_parse(response);
+		struct blob_buf bb;
+
+		memset(&bb, 0, sizeof(struct blob_buf));
+		blob_buf_init(&bb, 0);
+		bb = json_to_blob(parsed_response, bb);
+		ubus_send_reply(ctx, req, bb.head);
+		blob_buf_free(&bb);
+		json_object_put(parsed_response);
+		json_object_put(apn_profiles);
+	}
+
+	return 0;
+}
+
+int set_apn_profile(struct ubus_context *ctx, struct ubus_object *obj,
+					   struct ubus_request_data *req, const char *method,
+					   struct blob_attr *msg)
+{
+	struct json_object *apn_profiles = get_apn_profiles_json();
+	struct blob_attr *tb[__APN_MAX];
+	char apn[1024]; //STR_MAX or something from limits.h
+	int idx;
+
+	blobmsg_parse(apn_policy, __APN_MAX, tb, blob_data(msg), blob_len(msg));
+
+	apn[0] = '\0';
+	strncpy(apn, (char *)blobmsg_data(tb[APN]), 1023);
+	printf("apn %s\n", apn);
+
+	idx = get_apn_profile_idx(apn_profiles, apn);
+	if (idx < 0)
+		goto fail;
+
+	char *response = _set_apn_profile(idx);
+
+	struct json_object *parsed_response = json_tokener_parse(response);
+	struct blob_buf bb;
+
+	memset(&bb, 0, sizeof(struct blob_buf));
+	blob_buf_init(&bb, 0);
+	bb = json_to_blob(parsed_response, bb);
+	ubus_send_reply(ctx, req, bb.head);
+	blob_buf_free(&bb);
+	json_object_put(parsed_response);
+fail:
+	free(response);
+	json_object_put(apn_profiles);
+	return 0;
+}
+
+int create_apn_profile(struct ubus_context *ctx, struct ubus_object *obj,
+				struct ubus_request_data *req, const char *method,
+				struct blob_attr *msg)
+{
+	struct blob_attr *tb[__APN_MAX];
+	char apn[1024]; //STR_MAX or something from limits.h
+
+	blobmsg_parse(apn_policy, __APN_MAX, tb, blob_data(msg), blob_len(msg));
+
+	apn[0] = '\0';
+	strncpy(apn, (char *)blobmsg_data(tb[APN]), 1023);
+	char *response = _create_apn_profile(apn);
+	struct json_object *parsed_response = json_tokener_parse(response);
+	struct blob_buf bb;
+
+	memset(&bb, 0, sizeof(struct blob_buf));
+	blob_buf_init(&bb, 0);
+	bb = json_to_blob(parsed_response, bb);
+	ubus_send_reply(ctx, req, bb.head);
+	blob_buf_free(&bb);
+	json_object_put(parsed_response);
+	free(response);
+
+	return 0;
+}
+
+int show_current_apn(struct ubus_context *ctx, struct ubus_object *obj,
+				 struct ubus_request_data *req, const char *method,
+				 struct blob_attr *msg)
+{
+	char *wan_apn = get_current_wan_apn();
+	struct json_object *parsed_response = json_tokener_parse(wan_apn);
+	struct blob_buf bb;
+
+	memset(&bb, 0, sizeof(struct blob_buf));
+	blob_buf_init(&bb, 0);
+	bb = json_to_blob(parsed_response, bb);
+	ubus_send_reply(ctx, req, bb.head);
+	blob_buf_free(&bb);
+	json_object_put(parsed_response);
+	free(wan_apn);
+	return 0;
+}
+
+struct ubus_method dongle_object_methods[] = {
+	UBUS_METHOD_NOARG("list_apn_profiles", list_apn_profiles),
+	UBUS_METHOD("create_apn_profile", create_apn_profile, apn_policy),
+	UBUS_METHOD("delete_apn_profile", delete_apn_profile, apn_policy),
+	UBUS_METHOD("set_apn_profile", set_apn_profile, apn_policy),
+	UBUS_METHOD_NOARG("show_current_apn", show_current_apn),
+};
+
+struct ubus_object_type dongle_object_type = UBUS_OBJECT_TYPE("dongle", dongle_object_methods);
+
+struct ubus_object dongle_object = {
+	.name = "dongle.apn",
+	.type = &dongle_object_type,
+	.methods = dongle_object_methods,
+	.n_methods = ARRAY_SIZE(dongle_object_methods),
+};
+
+void init_ubus(void)
+{
+	ctx = ubus_connect(NULL);
+	if (!ctx) {
+		perror("ubus");
+		exit(1);
+	}
+	ubus_add_uloop(ctx);
+}
+
+int main(int argc, char **argv)
+{
+	uloop_init();
+	init_ubus();
+	ubus_add_object(ctx, &dongle_object);
+	uloop_run();
+
+	return 0;
+}
diff --git a/zte-mf823/dongle_network.c b/zte-mf823/dongle_network.c
new file mode 100644
index 0000000000000000000000000000000000000000..81cb5af1b6b4f63151cb26186822177a8485aa58
--- /dev/null
+++ b/zte-mf823/dongle_network.c
@@ -0,0 +1,71 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <time.h>
+#include <limits.h>
+
+#include <libubox/list.h>
+#include <libubus.h>
+
+#include "libmobile.h"
+
+struct ubus_context *ctx;
+
+int signal_strength(struct ubus_context *ctx, struct ubus_object *obj,
+			struct ubus_request_data *req, const char *method,
+			struct blob_attr *msg)
+{
+	struct blob_buf bb;
+
+	char * response = get_csv("rssi");
+	struct json_object *parsed_response = json_tokener_parse(response);
+print:
+	memset(&bb, 0, sizeof(struct blob_buf));
+	blob_buf_init(&bb, 0);
+	bb = json_to_blob(parsed_response, bb);
+	ubus_send_reply(ctx, req, bb.head);
+	blob_buf_free(&bb);
+	json_object_put(parsed_response);
+fail:
+	return 0;
+}
+
+struct ubus_method dongle_object_methods[] = {
+	UBUS_METHOD_NOARG("signal_strength", signal_strength),
+};
+
+struct ubus_object_type dongle_object_type = UBUS_OBJECT_TYPE("dongle", dongle_object_methods);
+
+struct ubus_object dongle_object = {
+	.name = "dongle.network",
+	.type = &dongle_object_type,
+	.methods = dongle_object_methods,
+	.n_methods = ARRAY_SIZE(dongle_object_methods),
+};
+
+void init_ubus(void)
+{
+	ctx = ubus_connect(NULL);
+	if (!ctx) {
+		perror("ubus");
+		exit(1);
+	}
+	ubus_add_uloop(ctx);
+}
+
+int main(int argc, char **argv)
+{
+	uloop_init();
+	init_ubus();
+	ubus_add_object(ctx, &dongle_object);
+	uloop_run();
+
+	return 0;
+}
diff --git a/zte-mf823/dongle_pin.c b/zte-mf823/dongle_pin.c
new file mode 100644
index 0000000000000000000000000000000000000000..1f0ffdbb458e4873bec2cd779248ace5c4b8b4d0
--- /dev/null
+++ b/zte-mf823/dongle_pin.c
@@ -0,0 +1,293 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <time.h>
+#include <limits.h>
+
+#include <libubox/list.h>
+#include <libubus.h>
+
+#include "libmobile.h"
+
+struct ubus_context *ctx;
+
+enum {
+	NEW_PIN,
+	CURRENT_PIN,
+	__SET_PIN_MAX,
+};
+
+enum {
+	PIN,
+	__PIN_MAX,
+};
+
+const struct blobmsg_policy set_pin_policy[__SET_PIN_MAX] = {
+	[NEW_PIN] = {.name = "new_pin", .type = BLOBMSG_TYPE_STRING},
+	[CURRENT_PIN] = {.name = "current_pin", .type = BLOBMSG_TYPE_STRING},
+};
+
+const struct blobmsg_policy pin_policy[__PIN_MAX] = {
+	[PIN] = {.name = "pin", .type = BLOBMSG_TYPE_STRING},
+};
+
+int print_response(struct json_object *parsed_response, ubus_context *ctx, ubus_request_data *req)
+{
+	struct blob_buf bb;
+
+	memset(&bb, 0, sizeof(struct blob_buf));
+	blob_buf_init(&bb, 0);
+	bb = json_to_blob(parsed_response, bb);
+	ubus_send_reply(ctx, req, bb.head);
+	blob_buf_free(&bb);
+	json_object_put(parsed_response);
+	return 0;
+}
+
+int isdigits(const char *pin)
+{
+	while (*pin){
+		if (isdigit(*pin++) == 0)
+			return false;
+	}
+	return true;
+}
+
+int validate_pin_format(char *pin)
+{
+	if (!isdigits(pin)) {
+		printf("Please enter digits only!\n");
+		goto fail;
+	} else if (strlen(pin) > 8 && strlen(pin) < 4 ) {
+		printf("Please enter between 4 to 8 digits!\n");
+		goto fail;
+	} else if (atoi(pin) == 0) {
+		printf("0000 is not a valid pin! Lowest available is 0001\n");
+		goto fail;
+	}
+	return 0;
+fail:
+	return -1;
+}
+
+int set_pin(struct ubus_context *ctx, struct ubus_object *obj,
+				 struct ubus_request_data *req, const char *method,
+				 struct blob_attr *msg)
+{
+	struct blob_buf bb;
+	struct blob_attr *tb[__SET_PIN_MAX];
+	char new_pin[10] = {0}, current_pin[10] = {0};
+
+	blobmsg_parse(set_pin_policy, __SET_PIN_MAX, tb, blob_data(msg), blob_len(msg));
+
+	if (!tb[NEW_PIN] && !tb[CURRENT_PIN]) {
+		printf("Please enter both a new pin and old pin!");
+		goto fail;
+	}
+
+	strncpy(new_pin, (char *)blobmsg_data(tb[NEW_PIN]), 9);
+	strncpy(current_pin, (char *)blobmsg_data(tb[CURRENT_PIN]), 9);
+	validate_pin_format(new_pin);
+	validate_pin_format(current_pin);
+
+	printf("new: %s, old: %s\n", new_pin, current_pin);
+	char *response = get_csv("pin_status");
+	struct json_object *parsed_response = json_tokener_parse(response);
+	struct json_object *rv;
+
+	json_object_object_get_ex(parsed_response, "pin_status", &rv);
+	if (!json_object_get_int(rv)) {
+		response = _enable_pin(current_pin);
+		json_object_put(parsed_response);
+		parsed_response = json_tokener_parse(response);
+		json_object_object_get_ex(parsed_response, "result", &rv);
+		if (strncmp(json_object_get_string(rv), "failure", strlen("failure")) == 0) {
+			printf("Incorrect pin!");
+			goto print;
+		}
+	}
+
+	response = _set_pin(current_pin, new_pin);
+	json_object_put(parsed_response);
+	parsed_response = json_tokener_parse(response);
+	json_object_object_get_ex(parsed_response, "result", &rv);
+	if (strncmp(json_object_get_string(rv), "failure", strlen("failure")) == 0) {
+		printf("Incorrect pin!");
+		goto print;
+	}
+
+print:
+	print_response(parsed_response, ctx, req);
+fail:
+	return 0;
+}
+
+int disable_pin(struct ubus_context *ctx, struct ubus_object *obj,
+			struct ubus_request_data *req, const char *method,
+			struct blob_attr *msg)
+{
+	struct blob_attr *tb[__PIN_MAX];
+	char pin[10] = {0};
+
+	blobmsg_parse(pin_policy, __PIN_MAX, tb, blob_data(msg), blob_len(msg));
+	if (!tb[PIN]) {
+		printf("Please enter both a new pin and old pin!");
+		goto fail;
+	}
+	strncpy(pin, (char *)blobmsg_data(tb[PIN]), 9);
+	validate_pin_format(pin);
+
+	char *response = get_csv("pin_status");
+	struct json_object *parsed_response = json_tokener_parse(response);
+	struct json_object *rv;
+
+	json_object_object_get_ex(parsed_response, "pin_status", &rv);
+	if (json_object_get_int(rv)) {
+		response = _disable_pin(pin);
+		json_object_put(parsed_response);
+		parsed_response = json_tokener_parse(response);
+		json_object_object_get_ex(parsed_response, "result", &rv);
+		if (strncmp(json_object_get_string(rv), "failure", strlen("failure")) == 0) {
+			printf("Incorrect pin!");
+			goto print;
+		}
+	} else {
+		printf("already disabled!\n");
+	}
+
+print:
+	print_response(parsed_response, ctx, req);
+fail:
+	return 0;
+}
+
+int enable_pin(struct ubus_context *ctx, struct ubus_object *obj,
+			struct ubus_request_data *req, const char *method,
+			struct blob_attr *msg)
+{
+	struct blob_buf bb;
+	struct blob_attr *tb[__PIN_MAX];
+	char pin[10] = {0};
+
+	blobmsg_parse(pin_policy, __PIN_MAX, tb, blob_data(msg), blob_len(msg));
+	if (!tb[PIN]) {
+		printf("Please enter both a new pin and old pin!");
+		goto fail;
+	}
+
+	strncpy(pin, (char *)blobmsg_data(tb[PIN]), 9);
+	validate_pin_format(pin);
+
+	char *response = get_csv("pin_status");
+	struct json_object *parsed_response = json_tokener_parse(response);
+	struct json_object *rv;
+
+	json_object_object_get_ex(parsed_response, "pin_status", &rv);
+	if (!json_object_get_int(rv)) {
+		response = _enable_pin(pin);
+		json_object_put(parsed_response);
+		parsed_response = json_tokener_parse(response);
+		json_object_object_get_ex(parsed_response, "result", &rv);
+		if (strncmp(json_object_get_string(rv), "failure", strlen("failure")) == 0) {
+			printf("Incorrect pin!");
+			goto print;
+		}
+	} else {
+		printf("already enabled!\n");
+	}
+
+print:
+	print_response(parsed_response, ctx, req);
+fail:
+	return 0;
+}
+
+int verify_pin(struct ubus_context *ctx, struct ubus_object *obj,
+			   struct ubus_request_data *req, const char *method,
+			   struct blob_attr *msg)
+{
+	struct blob_buf bb;
+	struct blob_attr *tb[__PIN_MAX];
+	char pin[10] = {0};
+
+	blobmsg_parse(pin_policy, __PIN_MAX, tb, blob_data(msg), blob_len(msg));
+	if (!tb[PIN]) {
+		printf("Please enter both a new pin and old pin!");
+		goto fail;
+	}
+	strncpy(pin, (char *)blobmsg_data(tb[PIN]), 9);
+	validate_pin_format(pin);
+
+	char *response = get_csv("pin_status");
+	struct json_object *parsed_response = json_tokener_parse(response);
+	struct json_object *rv;
+
+	json_object_object_get_ex(parsed_response, "pin_status", &rv);
+	if (json_object_get_string(rv)) {
+		response = _enable_pin(pin);
+	} else {
+		response = _disable_pin(pin);
+	}
+
+	json_object_put(parsed_response);
+	parsed_response = json_tokener_parse(response);
+
+	print_response(parsed_response, ctx, req);
+fail:
+	return 0;
+}
+
+int remaining_tries(struct ubus_context *ctx, struct ubus_object *obj,
+			   struct ubus_request_data *req, const char *method,
+			   struct blob_attr *msg)
+{
+	struct blob_buf bb;
+	char *response = get_csv("pinnumber");
+	struct json_object *parsed_response = json_tokener_parse(response);
+	print_response(parsed_response, ctx, req);
+	return 0;
+}
+
+struct ubus_method dongle_object_methods[] = {
+	UBUS_METHOD("set_pin", set_pin, set_pin_policy),
+	UBUS_METHOD("disable_pin", disable_pin, pin_policy),
+	UBUS_METHOD("enable_pin", enable_pin, pin_policy),
+	UBUS_METHOD("verify_pin", verify_pin, pin_policy),
+	UBUS_METHOD_NOARG("remaining_tries", remaining_tries),
+};
+
+struct ubus_object_type dongle_object_type = UBUS_OBJECT_TYPE("dongle", dongle_object_methods);
+
+struct ubus_object dongle_object = {
+	.name = "dongle.pin",
+	.type = &dongle_object_type,
+	.methods = dongle_object_methods,
+	.n_methods = ARRAY_SIZE(dongle_object_methods),
+};
+
+void init_ubus(void)
+{
+	ctx = ubus_connect(NULL);
+	if (!ctx) {
+		perror("ubus");
+		exit(1);
+	}
+	ubus_add_uloop(ctx);
+}
+
+int main(int argc, char **argv)
+{
+	uloop_init();
+	init_ubus();
+	ubus_add_object(ctx, &dongle_object);
+	uloop_run();
+
+	return 0;
+}
diff --git a/zte-mf823/libmobile.c b/zte-mf823/libmobile.c
new file mode 100644
index 0000000000000000000000000000000000000000..cb97ac49035cf5d4f95785c2144c4c95d93a48c9
--- /dev/null
+++ b/zte-mf823/libmobile.c
@@ -0,0 +1,391 @@
+#include "libmobile.h"
+
+struct string {
+	char *ptr;
+	size_t len;
+};
+
+void curl_cleaner(CURLcode *curl)
+{
+	curl_easy_cleanup(curl);
+	curl_global_cleanup();
+}
+
+size_t write_func(void *buffer, size_t size, size_t nmemb, void *userp)
+{
+	struct string *str = (struct string *)userp;
+	size_t new_len = str->len + (size * nmemb);
+
+	str->ptr = realloc(str->ptr, new_len + 1);
+	if (str->ptr == NULL) {
+		printf("not enough ptr (realloc returned NULL)\n");
+		return 0;
+	}
+	memcpy(str->ptr + str->len, buffer, size * nmemb);
+	str->ptr[new_len] = '\0';
+	str->len = new_len;
+
+	return size * nmemb;
+}
+
+struct json_object *get_apn_profiles_json(void)
+{
+	CURL *curl;
+	CURLcode res;
+	struct string str;
+
+	str.ptr = calloc(1, 1);
+	str.len = 0;
+	curl = curl_easy_init();
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.0.1/goform/goform_get_cmd_process?isTest=false&cmd=APN_config0,APN_config1,APN_config2,APN_config3,APN_config4,APN_config5,APN_config6,APN_config7,APN_config8,APN_config9,APN_config10,APN_config11,APN_config12,APN_config13,APN_config14,APN_config15,APN_config16,APN_config17,APN_config18,APN_config19&multi_data=1");
+		curl_easy_setopt(curl, CURLOPT_REFERER, "http://192.168.0.1/index.html");
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
+		res = curl_easy_perform(curl);
+		if (res) {
+			printf("errored when performing curl, %s\n", curl_easy_strerror(res));
+			goto fail;
+		}
+	}
+	struct json_object *apn_profiles = json_tokener_parse(str.ptr);
+
+fail:
+	curl_cleaner(curl);
+	return apn_profiles;
+}
+
+struct blob_buf json_to_blob(struct json_object *response, struct blob_buf bb)
+{
+	void *arr, *obj;
+	int i;
+
+	json_object_object_foreach(response, key, val) {
+		int val_type = json_object_get_type(val);
+
+		switch (val_type) {
+		case json_type_int:
+			blobmsg_add_u32(&bb, key, json_object_get_int(val));
+			break;
+		case json_type_double:
+			blobmsg_add_double(&bb, key, json_object_get_double(val));
+			break;
+		case json_type_string:
+			blobmsg_add_string(&bb, key, json_object_get_string(val));
+			break;
+		case json_type_array:
+			arr = blobmsg_open_array(&bb, key);
+			for (i = 0; i < json_object_array_length(val); i++)
+				bb = json_to_blob(json_object_array_get_idx(val, i), bb);
+			blobmsg_close_array(&bb, arr);
+			break;
+		case json_type_object:
+			obj = blobmsg_open_table(&bb, key);
+			bb = json_to_blob(val, bb);
+			blobmsg_close_array(&bb, obj);
+			break;
+		}
+	}
+
+	return bb;
+}
+
+int get_apn_profiles_len(void)
+{
+	struct json_object *apn_profiles = get_apn_profiles_json();
+	int len = 0;
+
+	json_object_object_foreach(apn_profiles, key, val) {
+		if (strlen(json_object_get_string(val)) > 0)
+			len++;
+		else
+			return len;
+	}
+
+	json_object_put(apn_profiles);
+	return -1;
+}
+
+int _delete_apn(int idx, char *response)
+{
+	CURL *curl;
+	CURLcode res;
+	char post_query[1024];
+	struct string str;
+
+	str.ptr = calloc(1, 1);
+	str.len = 0;
+
+	printf("index we are attempting to remove: %d\n", idx);
+	post_query[0] = '\0';
+	strncpy(post_query, "isTest=false&apn_action=delete&apn_mode=manual&index=", 1023);
+	sprintf(post_query + strlen(post_query), "%d", idx);
+	strncat(post_query + strlen(post_query), "&goformId=APN_PROC_EX", 1023);
+
+	printf("post_query: %s\n", post_query);
+
+	curl = curl_easy_init();
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.0.1/goform/goform_set_cmd_process");
+		curl_easy_setopt(curl, CURLOPT_REFERER, "http://192.168.0.1/index.html");
+		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_query);
+
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
+		res = curl_easy_perform(curl);
+		if (res) {
+			printf("errored when performing curl, %s\n", curl_easy_strerror(res));
+			goto fail;
+		}
+	}
+	printf("%s\n", str.ptr);
+	strncpy(response, str.ptr, 1023);
+	return 0;
+fail:
+	return -1;
+}
+
+int get_apn_profile_idx(struct json_object *apn_profiles, char *name)
+{
+	int idx = 0;
+
+	json_object_object_foreach(apn_profiles, key, val) {
+		char *apn_profile = json_object_get_string(val);
+		char *apn_name = strtok(apn_profile, "($)");
+
+		if (strncmp(apn_name, name, 1024) == 0)
+			return idx;
+		idx++;
+	}
+	return -1;
+}
+
+char *get_current_wan_apn(void)
+{
+	CURL *curl;
+	CURLcode res;
+	struct string str;
+
+	str.ptr = calloc(1, 1);
+	str.len = 0;
+	curl = curl_easy_init();
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.0.1/goform/goform_get_cmd_process?isTest=false&cmd=wan_apn&multi_data=1");
+		curl_easy_setopt(curl, CURLOPT_REFERER, "http://192.168.0.1/index.html");
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
+		res = curl_easy_perform(curl);
+		if (res) {
+			printf("errored when performing curl, %s\n", curl_easy_strerror(res));
+			goto fail;
+		}
+	}
+	return str.ptr;
+fail:
+	return NULL;
+}
+
+char *_create_apn_profile(char *apn)
+{
+	CURL *curl;
+	CURLcode res;
+	char post_query[1024];
+	struct string str;
+
+	str.ptr = calloc(1, 1);
+	str.len = 0;
+
+	post_query[0] = '\0';
+	strncpy(post_query, "isTest=false&goformId=APN_PROC_EX&apn_action=save&apn_mode=manual&profile_name=", 1023);
+	strncat(post_query + strlen(post_query), apn, 1023);
+	strncat(post_query + strlen(post_query), "&wan_dial=*99%23&apn_select=manual&pdp_type=IP&pdp_select=auto&pdp_addr=&index=", 1023);
+	sprintf(post_query + strlen(post_query), "%d", get_apn_profiles_len());
+	strncat(post_query + strlen(post_query), "&wan_apn=", 1023);
+	strncat(post_query + strlen(post_query), apn, 1023);
+	strncat(post_query + strlen(post_query), "&ppp_auth_mode=none&ppp_username=&ppp_passwd=&dns_mode=auto&prefer_dns_manual=&standby_dns_manual=", 1023);
+	printf("post_query: %s\n", post_query);
+
+	curl = curl_easy_init();
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.0.1/goform/goform_set_cmd_process");
+		curl_easy_setopt(curl, CURLOPT_REFERER, "http://192.168.0.1/index.html");
+		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_query);
+
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
+		res = curl_easy_perform(curl);
+		if (res) {
+			printf("errored when performing curl, %s\n", curl_easy_strerror(res));
+			goto fail;
+		}
+	}
+	return str.ptr;
+fail:
+	return NULL;
+}
+
+char *_set_apn_profile(int idx)
+{
+	CURL *curl;
+	CURLcode res;
+	char post_query[1024];
+	struct string str;
+
+	str.ptr = calloc(1, 1);
+	str.len = 0;
+	post_query[0] = '\0';
+	strncpy(post_query, "isTest=false&goformId=APN_PROC_EX&apn_mode=manual&apn_action=set_default&set_default_flag=1&pdp_type=IP&index=", 1023);
+	sprintf(post_query + strlen(post_query), "%d", idx);
+	printf("post_query: %s\n", post_query);
+
+	curl = curl_easy_init();
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.0.1/goform/goform_set_cmd_process");
+		curl_easy_setopt(curl, CURLOPT_REFERER, "http://192.168.0.1/index.html");
+		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_query);
+
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
+		res = curl_easy_perform(curl);
+		if (res) {
+			printf("errored when performing curl, %s\n", curl_easy_strerror(res));
+			goto fail;
+		}
+	}
+	return str.ptr;
+fail:
+	return NULL;
+}
+
+char *get_csv(char *csv)
+{
+	CURL *curl;
+	CURLcode res;
+	struct string str;
+	char get_query[1024];
+
+	str.ptr = calloc(1, 1);
+	str.len = 0;
+	curl = curl_easy_init();
+
+	strncpy(get_query, "http://192.168.0.1/goform/goform_get_cmd_process?isTest=false&cmd=", 1023);
+	strncat(get_query + strlen(get_query), csv, 1023);
+	strncat(get_query, "&multi_data=1", 1023);
+
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, get_query);
+		curl_easy_setopt(curl, CURLOPT_REFERER, "http://192.168.0.1/index.html");
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
+		res = curl_easy_perform(curl);
+		if (res) {
+			printf("errored when performing curl, %s\n", curl_easy_strerror(res));
+			goto fail;
+		}
+	}
+	return str.ptr;
+fail:
+	return NULL;
+}
+
+char *_enable_pin(char *pin)
+{
+	CURL *curl;
+	CURLcode res;
+	struct string str;
+	char post_query[1024];
+
+	str.ptr = calloc(1, 1);
+	str.len = 0;
+	curl = curl_easy_init();
+
+	strncpy(post_query, "goformId=ENABLE_PIN&OldPinNumber=", 1023);
+	strncat(post_query + strlen(post_query), pin, 1023);
+	strncat(post_query + strlen(post_query), "&pin_save_flag=0&isTest=false", 1023);
+
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.0.1/goform/goform_set_cmd_process");
+		curl_easy_setopt(curl, CURLOPT_REFERER, "http://192.168.0.1/index.html");
+		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_query);
+
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
+		res = curl_easy_perform(curl);
+		if (res)
+		{
+			printf("errored when performing curl, %s\n", curl_easy_strerror(res));
+			goto fail;
+		}
+	}
+	return str.ptr;
+fail:
+	return NULL;
+}
+
+char *_set_pin(char *current_pin, char *new_pin)
+{
+	CURL *curl;
+	CURLcode res;
+	struct string str;
+	char post_query[1024];
+
+	str.ptr = calloc(1, 1);
+	str.len = 0;
+	curl = curl_easy_init();
+
+	strncpy(post_query, "goformId=ENABLE_PIN&OldPinNumber=", 1023);
+	strncat(post_query + strlen(post_query), current_pin, 1023);
+	strncat(post_query + strlen(post_query), "&NewPinNumber=", 1023);
+	strncat(post_query + strlen(post_query), new_pin, 1023);
+	strncat(post_query + strlen(post_query), "&pin_save_flag=0&isTest=false", 1023);
+
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.0.1/goform/goform_set_cmd_process");
+		curl_easy_setopt(curl, CURLOPT_REFERER, "http://192.168.0.1/index.html");
+		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_query);
+
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
+		res = curl_easy_perform(curl);
+		if (res) {
+			printf("errored when performing curl, %s\n", curl_easy_strerror(res));
+			goto fail;
+		}
+	}
+	return str.ptr;
+fail:
+	return NULL;
+}
+
+char *_disable_pin(char *pin)
+{
+	CURL *curl;
+	CURLcode res;
+	struct string str;
+	char post_query[1024] = {0};
+
+	str.ptr = calloc(1, 1);
+	str.len = 0;
+	curl = curl_easy_init();
+
+	strncpy(post_query, "goformId=DISABLE_PIN&OldPinNumber=", 1023);
+	strncat(post_query + strlen(post_query), pin, 1023);
+	strncat(post_query + strlen(post_query), "&pin_save_flag=0&isTest=false", 1023);
+
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.0.1/goform/goform_set_cmd_process");
+		curl_easy_setopt(curl, CURLOPT_REFERER, "http://192.168.0.1/index.html");
+		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_query);
+
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
+		res = curl_easy_perform(curl);
+		if (res) {
+			printf("errored when performing curl, %s\n", curl_easy_strerror(res));
+			goto fail;
+		}
+	}
+	return str.ptr;
+fail:
+	return NULL;
+}