Skip to content
Snippets Groups Projects
dongle.c 14.96 KiB
#include <getopt.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <limits.h>
#include <netdb.h>
#include <ifaddrs.h>

#include "dongle.h"

static void uloop_add_get_devices(struct uloop_timeout *t);
static int get_devices_from_path(char *input_path);
static int get_devices(void);
static int poll_devices(struct uloop_timeout *t);
static int tag_missing_devices(void);
static int add_device(struct device *new_dev);
static int delete_all_devices(void);
static int delete_device_by_name(char *name);
static int delete_device(struct device *dev);
static void free_device(struct device *dev);
static void free_usb(struct USB *usb);
static struct device *search_list(char *name);
static char *get_usb_stat(char *path, char *file);
static char *get_device_name(char *dir_name);
static char *get_device_ip(char *device_name);
static int list_to_blob(struct blob_buf *bb);
static int print_list(struct ubus_context *ctx, struct ubus_object *obj,
					  struct ubus_request_data *req, const char *method,
					  struct blob_attr *msg);
static int clear(struct ubus_context *ctx, struct ubus_object *obj,
				 struct ubus_request_data *req, const char *method,
				 struct blob_attr *msg);
static int test(struct ubus_context *ctx, struct ubus_object *obj,
				struct ubus_request_data *req, const char *method,
				struct blob_attr *msg);
static int remove_device(struct ubus_context *ctx, struct ubus_object *obj,
						 struct ubus_request_data *req, const char *method,
						 struct blob_attr *msg);
static void init_ubus(void);
static int publish_object(struct ubus_context *ctx);

static struct ubus_context *global_ctx;
struct device *global_dev;
int debug;


static struct uloop_timeout timeout = {
	.cb = poll_devices
};

static struct option long_options[] = {
	{"debug", required_argument, NULL, 'd'},
	{0,	0,	0,	0}
};

static const struct blobmsg_policy dev_policy[__DEV_MAX] = {
	[DEV] = {.name = "dev", .type = BLOBMSG_TYPE_STRING},
};

enum {
	USB_PATH,
	__USB_MAX
};
static const struct blobmsg_policy usb_policy[__USB_MAX] = {
	[USB_PATH] = {.name = "path", .type = BLOBMSG_TYPE_STRING},
};

LIST_HEAD(devices);
LIST_HEAD(stack);
LIST_HEAD(visited);

static int parse_args(int argc, char **argv)
{
	char ch;

	while ((ch = getopt_long(argc, argv, ":d:", long_options, NULL)) != -1) {
		switch (ch) {
		case 'd':
			debug = atoi(optarg);
			if (debug > 1 || debug < 0) {
				printf("%s: option '-%c' is invalid.\n", argv[0], optopt);
				goto fail;
			}
			goto done;
			break;
		case ':':
			fprintf(stderr, "%s: option '-%c' requires an argument\n", argv[0], optopt);
			goto fail;
			break;
		case '?':
			fprintf(stderr, "%s: option '-%c' is invalid: ignored\n", argv[0], optopt);
			goto fail;
			break;
		default:
			goto fail;
			break;
		}
	}

done:
	return 0;
fail:
	return -1;
}

static int poll_devices(struct uloop_timeout *t)
{
	get_devices();
	tag_missing_devices();

	uloop_add_get_devices(t);

	return 0;
}

static int tag_missing_devices(void)
{
	struct device *dev, *tmp;

	list_for_each_entry_safe(dev, tmp, &devices, list) {
		if (dev->present) {
			dev->missing = 0;
			dev->present = false;
			continue;
		}

		dev->missing++;
		if (dev->missing == 1) {
			/* TEMPORARY FIX -- TODO: HOW TO CHECK IF IT CONTAINS ELEMENT */
			list_del(&dev->list);
			delete_device(dev);
		}
	}

	return 0;
}

static int update_device(struct device *dev)
{
	struct device *prev_dev;
	int rv;

	prev_dev = search_list(dev->usb.if_name);
	if (!prev_dev)
		goto not_present;

	if (prev_dev->ip)
		free(prev_dev->ip);

	if (dev->ip)
		prev_dev->ip = strdup(dev->ip);
	else
		prev_dev->ip = NULL;

	if (!prev_dev->ubus_obj && prev_dev->ip) {
		prev_dev->ubus_obj = dongle_create_dynamic_object(prev_dev);
		if (!prev_dev->ubus_obj)
			goto fail;

		rv = publish_ubus_object(global_ctx, prev_dev->ubus_obj);
		if (rv)
			goto fail;

		global_dev = prev_dev;

	} else if (prev_dev->ubus_obj && !prev_dev->ip) {
		unpublish_ubus_object(global_ctx, prev_dev->ubus_obj);
		dongle_destroy_dynamic_object(&(prev_dev->ubus_obj));
	}
	prev_dev->present = true;

	return 0;
not_present:
	return -1;
fail:
	return -2;
}

static int add_device(struct device *dev)
{
	int rv;

	rv = update_device(dev);
	if (rv == 0)
		goto already_present;

	dev->present = true;

	if (list_empty(&devices))
		INIT_LIST_HEAD(&devices);
	list_add_tail(&dev->list, &devices);

	if (dev->ip) {
		dev->ubus_obj = dongle_create_dynamic_object(dev);
		if (!dev->ubus_obj)
			goto fail;

		rv = publish_ubus_object(global_ctx, dev->ubus_obj);
		if (rv)
			goto fail_publish;

		global_dev = dev;
	}

	return 0;
already_present:
	return -1;
fail_publish:
	dongle_destroy_dynamic_object(&(dev->ubus_obj));
fail:
	return -2;
}

static int delete_all_devices(void)
{
	struct device *dev, *tmp;

	list_for_each_entry_safe(dev, tmp, &devices, list) {
		/* TEMPORARY FIX -- TODO: HOW TO CHECK IF IT CONTAINS ELEMENT */
		list_del(&dev->list);
		delete_device(dev);
	}

	return 0;
}

static int delete_device_by_name(char *name)
{
	struct device *dev, *tmp;

	list_for_each_entry_safe(dev, tmp, &devices, list) {
		if (strncmp(dev->usb.if_name, name, 128) != 0)
			continue;
		/* TEMPORARY FIX -- TODO: HOW TO CHECK IF IT CONTAINS ELEMENT */
		list_del(&dev->list);
		delete_device(dev);
		return 0;
	}

	return -1;
}

static int delete_device(struct device *dev)
{
	if (dev->ubus_obj) {
		unpublish_ubus_object(global_ctx, dev->ubus_obj);
		dongle_destroy_dynamic_object(&(dev->ubus_obj));
	}

	/*if (dev->list) // how to check if list contains this element?
		list_del(&dev->list);*/
	free_device(dev);
	free(dev);
	return 0;
}

static void free_device(struct device *dev)
{
	if (!dev)
		return;

	free(dev->ip);
	free_usb(&(dev->usb));
	//free(&(dev->usb));
}

static void free_usb(struct USB *usb)
{
	if (!usb)
		return;

	free(usb->product);
	free(usb->product_id);
	free(usb->vendor_id);
	free(usb->if_name);
}

static struct device *search_list(char *name)
{
	struct device *dev;

	list_for_each_entry(dev, &devices, list) {
		if (strncmp(name, dev->usb.if_name, 128) == 0)
			return dev;
	}
	return NULL;
}

static char *get_usb_stat(char *path, char *file)
{
	char stat_path[PATH_MAX], contents[1024];
	FILE *f;

	snprintf(stat_path, PATH_MAX, "%s%s", path, file);
	f = fopen(stat_path, "r");
	if (!f) {
		perror("fopen");
		goto fail_fopen;
	}

	fgets(contents, 1024, f);
	remove_newline(contents);

	fclose(f);
	return strdup(contents);
fail_fopen:
	return NULL;
}

static char *get_device_name(char *dir_name)
{
	char path[PATH_MAX], *name = NULL;
	struct directory *dr, *sub_dr;
	struct dirent *de;
	struct stat st;
	DIR *dir;

	dr = (struct directory *)malloc(sizeof(*dr));
	if (!dr) {
		perror("malloc");
		goto fail_dr;
	}

	dr->path = strdup(dir_name);
	if (!dr->path)
		goto fail_path;

	push(dr, &stack);

	while (!list_empty(&stack) && !name) {
		dr = pop(&stack);

		dir = opendir(dr->path);
		if (!dir) {
			free(dr->path);
			free(dr);
			continue;
		}
		push(dr, &visited);

		while ((de = readdir(dir)) != NULL) {
			if (de->d_name[0] == '.')
				continue;

			if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
				perror("fstatat");
				continue;
			}

			if (!S_ISDIR(st.st_mode))
				continue;

			if (!strstr(dr->path, "net")) {
				snprintf(path, PATH_MAX, "%s%s/", dr->path, de->d_name);

				if (search(path, &visited))
					continue;

				sub_dr = (struct directory *)malloc(sizeof(*sub_dr));
				if (!sub_dr) {
					perror("malloc");
					continue;
				}

				sub_dr->path = strdup(path);
				if (!sub_dr->path) {
					free(sub_dr);
					continue;
				}

				push(sub_dr, &stack);
				continue;
			}

			if (!strstr(de->d_name, "eth") && !strstr(de->d_name, "usb"))
				break;

			name = strdup(de->d_name);
			// how to manage failure on this strdup
			break;
		}
		closedir(dir);
	}

	clear_list(&visited);
	clear_list(&stack);
	return name;
fail_path:
	free(dr);
fail_dr:
	return NULL;
}

static int get_devices_from_path(char *input_path)
{
	char *path, *name;
	struct dirent *de;
	DIR *dir;
	struct stat st;
	struct device *dev;
	int rv, path_len;

	dir = opendir(input_path);
	if (!dir) {
		perror("opendir");
		goto fail_dr;
	}

	path_len = strlen(input_path) + strlen("/product") + 1;
	path = malloc(path_len);
	if (!path) {
		perror("malloc");
		goto fail_path;
	}

	while ((de = readdir(dir)) != NULL) {
		if (de->d_name[0] == '.')
			continue;
		if (strstr(de->d_name, ":") || strstr(de->d_name, "usb"))
			continue;
		snprintf(path, path_len, "%s/product", input_path);

		memset(&st, 0, sizeof(st));
		rv = stat(path, &st);
		if (rv < 0) {
			//perror("stat");
			continue;
		}
		snprintf(path, path_len, "%s/", input_path);
		name = get_device_name(path);
		if (!name)
			continue;

		dev = (struct device *)calloc(1, sizeof(*dev));
		if (!dev) {
			perror("calloc");
			/*goto fail_dev;*/
			free(name);
			continue;
		}

		dev->usb.product = get_usb_stat(path, "product");
		dev->usb.product_id = get_usb_stat(path, "idProduct");
		dev->usb.vendor_id = get_usb_stat(path, "idVendor");
		dev->usb.if_name = name;
		dev->ip = get_device_ip(dev->usb.if_name);

		rv = add_device(dev);
		if (rv < 0)
			delete_device(dev);

		break;
	}
	closedir(dir);
	return 0;
fail_path:
	closedir(dir);
fail_dr:
	return -1;
}

static int get_devices(void)
{
	char devices_path[PATH_MAX], mount_path[PATH_MAX];
	struct dirent *de;
	DIR *dir;
	int rv;

	strncpy(devices_path, "/sys/bus/usb/devices/", PATH_MAX);
	dir = opendir(devices_path);
	if (!dir) {
		perror("opendir");
		goto fail_dr;
	}

	while ((de = readdir(dir)) != NULL) {
		if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
			continue;
		if (strstr(de->d_name, ":") || strstr(de->d_name, "usb"))
			continue;

		// found the usb folder
		snprintf(mount_path, PATH_MAX, "%s%s/", devices_path, de->d_name);
		rv = get_devices_from_path(mount_path);
		if (rv < 0)
			break;
	}

	closedir(dir);
	return 0;
fail_dr:
	return -1;
}

static char *get_device_ip(char *device_name)
{
	char *iface, *destination, *gateway, *flags, *route, *ipv4_addr = NULL;
	int host_flag;
	struct in_addr addr;
	FILE *fp;

	fp = fopen("/proc/net/route", "r");
	if (!fp) {
		perror("fopen");
		goto fail_fopen;
	}

	route = (char *)calloc(1, 1024);
	if (!route) {
		perror("calloc");
		goto fail_route;
	}

	while ((fgets(route, 1024, fp)) != NULL) {
		remove_newline(route);

		iface = strtok(route, "\t");
		destination = strtok(NULL, "\t");
		gateway = strtok(NULL, "\t");
		flags = strtok(NULL, "\t");

		if (strncmp(iface, device_name, 10) != 0)
			continue;

		host_flag = atoi(flags) & 4;
		if (!host_flag)
			continue;

		ipv4_addr = (char *)calloc(1, IPV4_MAX);
		if (!ipv4_addr)
			break;

		addr.s_addr = strtoul(destination, NULL, IPV4_MAX);
		inet_ntop(AF_INET, &(addr.s_addr), ipv4_addr, IPV4_MAX);
	}

	free(route);
fail_route:
	fclose(fp);
fail_fopen:
	return ipv4_addr;
}

static int list_to_blob(struct blob_buf *bb)
{
	struct device *net_dev;
	void *arr, *table;
	int rv;

	memset(bb, 0, sizeof(*bb));
	rv = blob_buf_init(bb, 0);
	if (rv != 0)
		goto fail;

	arr = blobmsg_open_array(bb, "network_devices");
	if (!arr)
		goto fail_arr;

	list_for_each_entry(net_dev, &devices, list) {
		table = blobmsg_open_table(bb, "");
		if (!table)
			goto fail_table;

		rv = blobmsg_add_string(bb, "product", net_dev->usb.product);
		if (rv < 0)
			goto fail_string;

		rv = blobmsg_add_string(bb, "interface_name", net_dev->usb.if_name);
		if (rv < 0)
			goto fail_string;

		rv = blobmsg_add_string(bb, "idproduct", net_dev->usb.product_id);
		if (rv < 0)
			goto fail_string;

		rv = blobmsg_add_string(bb, "idvendor", net_dev->usb.vendor_id);
		if (rv < 0)
			goto fail_string;

		if (net_dev->ip) {
			rv = blobmsg_add_string(bb, "ip", net_dev->ip);
			if (rv < 0)
				goto fail_string;
		}
		blobmsg_close_table(bb, table);
	}
	blobmsg_close_array(bb, arr);

	return 0;
fail_string:
fail_table:
fail_arr:
fail:
	return UBUS_STATUS_UNKNOWN_ERROR;
}

static int print_list(struct ubus_context *ctx, struct ubus_object *obj,
			   struct ubus_request_data *req, const char *method,
			   struct blob_attr *msg)
{
	struct blob_buf bb;
	int rv;

	rv = list_to_blob(&bb);
	if (rv != 0)
		goto fail;

	ubus_send_reply(ctx, req, bb.head);

fail:
	blob_buf_free(&bb);
	return rv;
}

static int clear(struct ubus_context *ctx, struct ubus_object *obj,
				 struct ubus_request_data *req, const char *method,
				 struct blob_attr *msg)
{
	printf("%d\n", delete_all_devices());
	return 0;
}

static int test(struct ubus_context *ctx, struct ubus_object *obj,
				struct ubus_request_data *req, const char *method,
				struct blob_attr *msg)
{
	printf("%s\n", get_device_ip("usb0"));
	return 0;
}

static int alert(struct ubus_context *ctx, struct ubus_object *obj,
				 struct ubus_request_data *req, const char *method,
				 struct blob_attr *msg)
{
	struct blob_attr *tb[__USB_MAX];
	char *usb_path;

	blobmsg_parse(usb_policy, __USB_MAX, tb, blob_data(msg), blob_len(msg));

	if (!tb[USB_PATH])
		goto fail;
	usb_path = (char *)blobmsg_data(tb[USB_PATH]);

	get_devices_from_path(usb_path);
	return 0;
fail:
	return UBUS_STATUS_INVALID_ARGUMENT;
}

static int remove_device(struct ubus_context *ctx, struct ubus_object *obj,
						 struct ubus_request_data *req, const char *method,
						 struct blob_attr *msg)
{
	struct blob_attr *tb[__DEV_MAX];
	char *dev;

	blobmsg_parse(dev_policy, __DEV_MAX, tb, blob_data(msg), blob_len(msg));

	if (!tb[DEV])
		goto fail;
	dev = (char *)blobmsg_data(tb[DEV]);

	printf("%d\n", delete_device_by_name(dev));

	return 0;
fail:
	return UBUS_STATUS_INVALID_ARGUMENT;
}

static int update(struct ubus_context *ctx, struct ubus_object *obj,
				  struct ubus_request_data *req, const char *method,
				  struct blob_attr *msg)
{
	get_devices();
	tag_missing_devices();

	return 0;
}

static struct ubus_method dongle_object_methods[] = {
	UBUS_METHOD_NOARG("test", test),
	UBUS_METHOD_NOARG("list", print_list),
	UBUS_METHOD_NOARG("clear", clear),
	UBUS_METHOD("remove_device", remove_device, dev_policy),
	UBUS_METHOD("alert", alert, usb_policy),
	UBUS_METHOD_NOARG("update", update)};

static struct ubus_object_type dongle_object_type = UBUS_OBJECT_TYPE("dongle", dongle_object_methods);

static struct ubus_object dongle_object = {
	.name = "dongle",
	.type = &dongle_object_type,
	.methods = dongle_object_methods,
	.n_methods = ARRAY_SIZE(dongle_object_methods),
};

static void uloop_add_get_devices(struct uloop_timeout *t)
{
	uloop_timeout_set(t, 60000);
	uloop_timeout_add(t);
}

static void init_ubus(void)
{
	global_ctx = ubus_connect(NULL);
	if (!global_ctx) {
		perror("ubus");
		exit(1);
	}
	ubus_add_uloop(global_ctx);
}

static int publish_object(struct ubus_context *ctx)
{
	int rv;

	rv = ubus_add_object(ctx, &dongle_object);
	if (rv) {
		debug_print("failed to add dongle.pin to ubus!\n");
		return -1;
	}

	return 0;
}

int main(int argc, char **argv)
{
	int rv;

	rv = parse_args(argc, argv);
	if (rv < 0)
		goto fail;

	uloop_init();
	init_ubus();
	uloop_timeout_set(&timeout, 0);
	uloop_timeout_add(&timeout);

	rv = publish_object(global_ctx);
	if (rv < 0)
		goto fail;

	uloop_run();

	return 0;
fail:
	return -1;
}