Skip to content

Instantly share code, notes, and snippets.

@legale
Created February 20, 2023 17:49
Show Gist options
  • Save legale/526aecb6cbbc096ca64ec22708e3481a to your computer and use it in GitHub Desktop.
Save legale/526aecb6cbbc096ca64ec22708e3481a to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
/* event_struct.h must be included first cause of LIST_HEAD macro used by libevent2 */
#include <event2/event.h>
#include <syslog.h>
#include <event2/event_struct.h>
#include <json-c/json.h>
#include <iwinfo.h> /* iwinfo library (package/libiwinfo */
#include "sysinfo.h"
#include "config.h"
#include "list.h" //double linked list header lib from linux kernel
static void parse_line(json_object *root_cur, char *line);
static void parse_cfg_json_object(json_object *jso, char *buf, size_t buf_len, char **p, size_t depth, bool is_delete);
static void parse_cfg_json_array(json_object *a, char *buf, size_t buf_len, char **p, size_t depth, bool is_delete);
static void parse_cfg_json_string(json_object *s, char *buf, size_t buf_len, char **p, bool is_delete);
static void parse_cfg_json_boolean(json_object *b, char *buf, size_t buf_len, char **p, bool is_delete);
static void parse_cfg_json_int(json_object *i, char *buf, size_t buf_len, char **p, bool is_delete);
static void parse_cfg_json_double(json_object *d, char *buf, size_t buf_len, char **p, bool is_delete);
static void parse_cfg_json(json_object *val, char *buf, size_t buf_len, char **p, size_t depth, bool is_delete);
/**
* @brief check if file exists
*
* @param fname
* @return true
* @return false
*/
bool is_file_exists(const char *fname){
FILE *file;
if ((file = fopen(fname, "r")))
{
fclose(file);
return true;
}
return false;
}
/**
* Remove trailing white space characters from string
*/
size_t trim_str(char * str){
int index, i;
/* Set default index */
index = -1;
/* Find last index of non-white space character */
i = 0;
while(str[i] != '\0')
{
/* somehow char 127 'del' may appear */
if(str[i] != ' ' && str[i] != '\t' && str[i] != '\n')
{
index = i;
}
i++;
}
/* Mark next character to last non-white space character as NULL */
str[index + 1] = '\0';
return index;
}
static void parse_cfg_json_object(json_object *jso, char *buf, size_t buf_len, char **p, size_t depth, bool is_delete) {
lh_table *table = json_object_get_object(jso);
for (struct lh_entry *entry = table->head; entry; entry = entry->next) {
char subbuf[4096];
char *subp = subbuf;
size_t subbuf_len = buf_len;
memcpy(subbuf, buf, buf_len);
subp += buf_len;
char *key = (char *) entry->k;
if (memcmp(key, "section_type", 12)) {
const char *pattern = depth == 0 ? "%s" : ".%s";
subbuf_len += snprintf(subp, 4096, pattern, key);
}
parse_cfg_json((json_object *) entry->v, subbuf, subbuf_len, p, depth + 1, is_delete);
}
}
/**
* @brief parse json entry type array
*
* @param a
* @param buf
* @param buf_len
* @param p
* @param depth
* @param is_delete
*/
static void parse_cfg_json_array(json_object *a, char *buf, size_t buf_len, char **p, size_t depth, bool is_delete) {
array_list *list = json_object_get_array(a);
size_t len = list->length;
for (size_t i = 0; i < len; i++) {
char subbuf[4096];
char *subp = subbuf;
size_t subbuf_len = buf_len;
memcpy(subbuf, buf, buf_len);
subp += buf_len;
subbuf_len += snprintf(subp, 512, "[%zu]", i);
json_object *o = (json_object *) list->array[i];
parse_cfg_json(o, subbuf, subbuf_len, p, depth + 1, is_delete);
}
}
/**
* @brief parse json entry type string
*
* @param s
* @param buf
* @param buf_len
* @param p
* @param is_delete
*/
static void parse_cfg_json_string(json_object *s, char *buf, size_t buf_len, char **p, bool is_delete) {
memcpy(*p, buf, buf_len);
*p += buf_len;
if (!is_delete) {
const char *string = json_object_get_string(s);
*p += snprintf(*p, 512, "=\"%s\"\n", string);
} else {
**p = '\n';
*p += 1;
}
}
/**
* @brief parse json entry type boolean
*
* @param b
* @param buf
* @param buf_len
* @param p
* @param is_delete
* @return void*
*/
static void parse_cfg_json_boolean(json_object *b, char *buf, size_t buf_len, char **p, bool is_delete) {
memcpy(*p, buf, buf_len);
*p += buf_len;
if (!is_delete) {
int i = json_object_get_boolean(b);
*p += snprintf(*p, 512, "=%d\n", i);
} else {
**p = '\n';
*p += 1;
}
}
/**
* @brief parse json entry type integer
*
* @param i
* @param buf
* @param buf_len
* @param p
* @param is_delete
* @return char*
*/
static void parse_cfg_json_int(json_object *i, char *buf, size_t buf_len, char **p, bool is_delete) {
memcpy(*p, buf, buf_len);
*p += buf_len;
if (!is_delete) {
int ii = json_object_get_int(i);
*p += snprintf(*p, 512, "=%d\n", ii);
} else {
**p = '\n';
*p += 1;
}
}
/**
* @brief parse json entry type double
*
* @param d
* @param buf
* @param buf_len
* @param p
* @param is_delete
* @return void*
*/
static void parse_cfg_json_double(json_object *d, char *buf, size_t buf_len, char **p, bool is_delete) {
memcpy(*p, buf, buf_len);
*p += buf_len;
if (!is_delete) {
double dd = json_object_get_double(d);
*p += snprintf(*p, 512, "=%f\n", dd);
} else {
**p = '\n';
*p += 1;
}
}
/**
* @brief dispatcher fucntion to parse json object
*
* @param val
* @param buf
* @param buf_len
* @param p
* @param depth
* @param is_delete
*/
static void parse_cfg_json(json_object *val, char *buf, size_t buf_len, char **p, size_t depth, bool is_delete) {
json_type type = json_object_get_type(val);
switch (type) {
case json_type_object:
parse_cfg_json_object(val, buf, buf_len, p, depth, is_delete);
break;
case json_type_array:
parse_cfg_json_array(val, buf, buf_len, p, depth, is_delete);
break;
case json_type_string:
parse_cfg_json_string(val, buf, buf_len, p, is_delete);
break;
case json_type_boolean:
parse_cfg_json_boolean(val, buf, buf_len, p, is_delete);
break;
case json_type_int:
parse_cfg_json_int(val, buf, buf_len, p, is_delete);
break;
case json_type_double:
parse_cfg_json_double(val, buf, buf_len, p, is_delete);
break;
case json_type_null:
default:
break;
}
}
/*
lh_table *table = json_object_get_object(root);
for (struct lh_entry *entry = table->head; entry; entry = entry->next){
char *key = (char *)entry->k;
char *val = json_object_get_string((json_object *)entry->v);
syslogwda(LOG_DEBUG,"key: %s val: %s\n", key, val);
}
*/
bool config_set_or_delete(json_object *root, bool is_delete) {
syslogwda(LOG_INFO,"%s() %s:%d\n", __func__, __FILE__, __LINE__);
char *uci_command = is_delete ? "uci delete" : "uci set";
/* parse json */
char buf[16384]; /* commands buffer */
char *bp = buf;
char **p = &bp;
if (json_object_get_type(root) != json_type_object) {
syslogwda(LOG_ALERT,"error: data is not an object\n");
return false;
}
parse_cfg_json(root, NULL, 0, p, 0, is_delete);
syslogwda(LOG_DEBUG,"commands:\n%s\n", buf);
/* exec */
FILE *fp;
char command[256];
char outbuf[256];
char *line = strtok(buf, "\n");
do {
snprintf(command, 256, "%s %s", uci_command, line);
syslogwda(LOG_WARNING,"cmd exec: %s\n", command);
fp = popen(command, "r");
if (fp == NULL) {
syslogwda(LOG_ALERT,"error: Unable to open process %s\n", command);
return false;
}
/* print cmd outbuf */
while (fgets(outbuf, 256, fp) != NULL) {
syslogwda(LOG_DEBUG,"%s\n", outbuf);
}
/* status code */
int ret = WEXITSTATUS(pclose(fp));
if (ret != 0) syslogwda(LOG_WARNING,"status code: %d %s\n", ret, strerror(ret));
} while ((line = strtok(NULL, "\n")));
/* commit and reload */
snprintf(buf, 512, "uci commit\nreload_config\n");
line = strtok(buf, "\n");
do {
snprintf(command, 256, "%s", line);
syslogwda(LOG_WARNING,"cmd exec: %s\n", command);
fp = popen(command, "r");
if (fp == NULL) {
syslogwda(LOG_ALERT,"error: %s Unable to open process %s\n", strerror(errno), command);
return false;
}
/* print cmd outbuf */
while (fgets(outbuf, 256, fp) != NULL) {
syslogwda(LOG_DEBUG,"%s\n", outbuf);
}
/* status code */
int ret = WEXITSTATUS(pclose(fp));
if(ret != 0) syslogwda(LOG_DEBUG,"status code: %d %s\n", ret, strerror(ret));
} while ((line = strtok(NULL, "\n")));
return true;
}
size_t read_file(const char *filepath, char *buf, size_t buf_len){
syslogwda(LOG_INFO,"%s() %s:%d\n", __func__, __FILE__, __LINE__);
FILE *fp = fopen(filepath,"r");
if(fp == NULL){
syslogwda(LOG_ALERT,"error: Unable to open file %s\n", filepath);
return 0;
}
size_t read = fread(buf, 1, buf_len, fp);
buf[read] = '\0'; //set terminator
fclose(fp);
return read;
}
/**
* @brief exec given command with shell
*
* @param command
* @param buf
* @param buf_len
* @return int
*/
int exec(const char *command, char *buf, size_t buf_len){
syslogwda(LOG_INFO,"%s() %s:%d\n", __func__, __FILE__, __LINE__);
FILE *fp;
syslogwda(LOG_NOTICE,"exec: %s\n", command);
fp = popen(command, "r");
if (fp == NULL) {
syslogwda(LOG_ERR,"error: %s Unable to open process %s\n", strerror(errno), command);
return errno;
}
if(buf == NULL || buf_len == 0) {
buf_len = 8192;
char tmp_buf[buf_len];
buf = (char *)&tmp_buf;
}
size_t p = fread(buf, 1, buf_len, fp); //fread will return number of 1 byte entries readed
/* check bounds */
if(p > buf_len){
syslogwda(LOG_ERR, "INFO: exec stdout > buf_len %zu\n", buf_len);
buf[buf_len - 1] = '\0';
} else {
buf[p] = '\0'; //set terminator
}
/* status code */
int ret = WEXITSTATUS(pclose(fp));
if(ret != 0) syslogwda(LOG_NOTICE, "status code: %d %s\n", ret, strerror(ret));
return ret;
}
/**
* @brief exec given command with format like printf, fprintf etc
*
* @param buf
* @param buflen
* @param fmt
* @param ...
* @return int
*/
int execf(char *buf, size_t buflen, const char *fmt, ...){
size_t cmdlen = 2048;
char cmd[cmdlen];
va_list arg;
va_start (arg, fmt);
cmdlen -= vsnprintf(cmd, cmdlen, fmt, arg);
va_end (arg);
return exec(cmd, buf, buflen);
}
int exec2(char *outbuf, size_t *outbufsize, char *errbuf, size_t *errbufsize, char *argv[]) {
int fdout[2]; /* pipe fd array for the stdout */
int fderr[2]; /* pipe fd array for the stderr */
pid_t pid; /* process id */
int status; /* place to store child process status */
if (pipe(fdout)==-1) /* first pipe for the stdout */
perror("pipe out"); /* exit with error if something wrong */
if (pipe(fderr)==-1) /* second pipe for the stderr */
perror("pipe err");
/*
* On success, the PID of the child process is returned in the
* parent, and 0 is returned in the child. On failure, -1 is
* returned in the parent, no child process is created, and errno is
* set to indicate the error.
*/
if ((pid = fork()) == -1) /* fork current process and store pid */
perror("fork error");
if(pid == 0) { /* if pid == 0 than this is a child process */
dup2 (fdout[1], STDOUT_FILENO); /* send stdout to the pipe fdout */
dup2 (fderr[1], STDERR_FILENO); /* send stderr to the pipe fderr */
/* close both sides of the pipe in the child */
close(fdout[0]);
close(fdout[1]);
/* close both sides of the pipe in the child */
close(fderr[0]);
close(fderr[1]);
/* execvp is called in the child ps
* argv[0] is a ptr to the first arg
* &argv[0] is a ptr to a ptr to the first arg
*/
execvp(argv[0], &argv[0]);
/* this part is never reached if execvp success */
return errno;
} else { /* parent */
while(wait(&status) > 0){} /* wait for the child processes to finish */
/* close write ends of the pipes */
close(fdout[1]);
close(fderr[1]);
/* if child process finished with errorprint error
* and return error code from parent process
*/
if (WIFEXITED(status) && WEXITSTATUS(status)){
printf("%s\n", strerror(WEXITSTATUS(status)));
return WEXITSTATUS(status);
}
*outbufsize = read(fdout[0], outbuf, *outbufsize);
outbuf[*outbufsize] = '\0';
//printf("OUT: \"%.*s\"\n", (int)*outbufsize, outbuf);
*errbufsize = read(fderr[0], errbuf, *errbufsize);
errbuf[*errbufsize] = '\0';
//printf("ERR: \"%.*s\"\n", (int)*errbufsize, errbuf);
/* close read ends of the pipes */
close(fdout[0]);
close(fderr[0]);
}
return 0;
}
json_object *config_get_raw() {
syslogwda(LOG_INFO,"%s() %s:%d\n", __func__, __FILE__, __LINE__);
size_t bufsize = 32768;
char *buf = malloc(bufsize);
exec("uci show", buf, bufsize);
json_object *json_array = json_object_new_array();
char *tok = strtok(buf, "\n");
size_t idx = 0;
do {
syslogwda(LOG_INFO,"uci cmd: %s\n", tok);
json_object *json_string = json_object_new_string(tok);
json_object_array_put_idx(json_array, idx, json_string);
idx++;
} while ((tok = strtok(NULL, "\n")));
free(buf);
return json_array;
}
void config_wireless_iface(bool remove){
syslogwda(LOG_INFO,"%s() %s:%d\n", __func__, __FILE__, __LINE__);
char buf[16384];
exec("uci show wireless | grep '=wifi-iface'", buf, 16384);
char *tok = strtok(buf, "\n");
if(tok == NULL) return;
size_t idx = 0;
do {
char *last = strchr(tok,'=');
if(last == NULL){
syslogwda(LOG_INFO,"%zu found: '%s' wifi-iface not found! skip.\n", idx, tok);
continue;
}
size_t len = last - tok;
char wifi_iface[len + 1]; /* + 1 for terminator byte '\0' */
memcpy(wifi_iface, tok, len);
wifi_iface[len] = '\0'; /* terminate string */
syslogwda(LOG_INFO,"%zu found '%s' len: %zu wifi-iface: '%s'\n", idx, tok, len, wifi_iface);
if(remove) {
char command[256];
snprintf(command, 256, "uci delete %s", wifi_iface);
exec(command, NULL, 0);
}
idx++;
} while ((tok = strtok(NULL, "\n")));
return;
}
void config_network_clear(){
syslogwda(LOG_INFO,"%s() %s:%d\n", __func__, __FILE__, __LINE__);
char buf[16384];
exec("uci show network | grep -E '=[device|interface]' | grep -Ev 'dev\\d{1,2}=|loopback'", buf, 16384);
char *tok = strtok(buf, "\n");
if(tok == NULL) return;
size_t idx = 0;
do {
char *last = strchr(tok,'=');
if(last == NULL){
syslogwda(LOG_DEBUG,"%zu found: '%s' network interface not found! skip.\n", idx, tok);
continue;
}
size_t len = last - tok;
char net_iface[len + 1]; /* + 1 for terminator byte '\0' */
memcpy(net_iface, tok, len);
net_iface[len] = '\0'; /* terminate string */
syslogwda(LOG_DEBUG,"%zu found '%s' len: %zu network iface: '%s'\n", idx, tok, len, net_iface);
char command[256];
snprintf(command, 256, "uci delete %s", net_iface);
exec(command, NULL, 0);
idx++;
} while ((tok = strtok(NULL, "\n")));
return;
}
json_object *config_get() {
syslogwda(LOG_INFO,"%s() %s:%d\n", __func__, __FILE__, __LINE__);
FILE *fp;
char string[1024]; /* stdout string len */
fp = popen("uci show", "r");
if (fp == NULL) return false;
/* json init */
json_object *root = json_object_new_object();
while (fgets(string, 1024, fp) != NULL) {
parse_line(root, string);
}
pclose(fp);
return root;
}
static void parse_line(json_object *root_cur, char *line) {
char *start = NULL, *end = NULL, *pos = NULL, *val = NULL, *tok = NULL, *tok_prev = NULL, *tok_last = NULL;
json_object *root_parent = NULL, *child = NULL;
bool is_section_type; //this flag for special parse uci section_type e.g.: wireless.2g=wifi-iface
is_section_type = false;
/* value delimiter */
char delim = '=';
pos = strchr(line, delim);
if (pos == NULL) {
syslogwda(LOG_DEBUG,"warning: delimiter '%c' not found in the line: %s\n", delim, line);
return;
}
*pos = '\0'; /* set terminator inplace delimiter */
val = pos + 1;
/* remove trailing \n */
size_t len = strlen(val);
end = val + len - 1; // last char pos
if (*end == '\n') *end = '\0';
end--; // replace \n with line terminator
/* remove quotes if exists */
if (*val == '"' || *val == '\'') {
val++; //shift forward to remove opening quote
if (*end == '"' || *end == '\'') *end = '\0';
end--; // shift backward to remove closing quote
} else{
is_section_type = true;
}
/*last token detection */
tok_last = strrchr(line, '.');
if (tok_last) {
tok_last += 1;
} else {
tok_last = line;
};
/* split line by dots */
tok = strtok(line, ".");
do {
size_t idx = 0;
bool is_array = false;
json_object *container;
//syslogwda(LOG_DEBUG,"\ttok: %s \n", tok);
/* if array is present in token */
if ((start = strchr(tok, '['))) {
*start = '\0'; /* set terminator char */
//syslogwda(LOG_DEBUG,"\ttok clean: %s\n", tok);
if ((end = strchr(start + 1, ']'))) {
is_array = true;
*end = '\0'; /* set terminator char */
idx = atoi(start + 1); /* array index */
//syslogwda(LOG_DEBUG,"\t\tarray idx: %d\n", idx);
}
}
/* if last token */
if (tok == tok_last) {
child = json_object_new_string(val);
/* 2 scenarios */
if (!is_array) {
if (json_object_get_type(root_cur) != json_type_object){
json_object_object_del(root_parent, tok_prev);
root_cur = json_object_new_object();
json_object_object_add(root_parent, tok_prev, root_cur);
}
//special parse section_type
if(is_section_type){
json_object *jso = json_object_new_object();
json_object_object_add(root_cur, tok, jso);
root_cur = jso;
json_object_object_add(root_cur, "section_type", child);
}else {
json_object_object_add(root_cur, tok, child);
}
} else if (is_array) {
//special parse section_type
if(is_section_type){
json_object *jso = json_object_new_object();
json_object_object_add(jso, "section_type", child);
child = jso;
}
json_object_object_get_ex(root_cur, tok, &container);
//remove if not an array
if(json_object_get_type(container) != json_type_array){
json_object_object_del(root_cur, tok); //delete container
container = json_object_new_array();
json_object_object_add(root_cur, tok, container);
}
json_object_array_put_idx(container, idx, child);
}
continue;
}
/* get tok container */
json_object_object_get_ex(root_cur, tok, &container);
//save for next cycles root_cur and tok
root_parent = root_cur;
tok_prev = tok;
/* 4 scenarios */
if (!is_array && container) {
root_cur = container;
} else if (!is_array && !container) {
child = json_object_new_object();
json_object_object_add(root_cur, tok, child);
root_cur = child;
} else if (is_array && container) {
if(json_object_get_type(container) != json_type_array){
json_object_object_del(root_cur, tok); //delete container
container = json_object_new_array();
json_object_object_add(root_cur, tok, container);
child = NULL;
} else {
child = json_object_array_get_idx(container, idx);
}
if (!child) {
child = json_object_new_object();
json_object_array_put_idx(container, idx, child);
}
root_cur = child;
} else if (is_array && !container) {
child = json_object_new_object();
container = json_object_new_array();
json_object_array_put_idx(container, idx, child);
json_object_object_add(root_cur, tok, container);
root_cur = child;
}
} while ((tok = strtok(NULL, ".")));
}
int wireless_del_station(const char *dev, const char *mac) {
syslogwda(LOG_INFO,"%s() %s:%d\n", __func__, __FILE__, __LINE__);
syslogwda(LOG_WARNING,"delete sta dev: %s mac: %s\n", dev, mac);
//if dev and mac passed
if(dev && mac){
return ubus_hostpad_del_station(dev, mac);
}
//if dev passed and mac is not
size_t entry_size = sizeof(struct iwinfo_assoclist_entry);
const struct iwinfo_ops *iw;
int len = 0;
char buf[IWINFO_BUFSIZE];
if(dev && !mac) {
iw = iwinfo_backend(dev);
iw->assoclist(dev, buf, &len);
for (int i = 0; i < len; i += entry_size) {
struct iwinfo_assoclist_entry *e;
e = (struct iwinfo_assoclist_entry *) &buf[i];
int ret = ubus_hostpad_del_station(dev, format_mac((uint8_t *)e->mac));
if(ret != 0) return ret;
}
return 0;
}
//if dev is not passed
/* get wlan devs */
size_t devs_size = 50;
net_dev netdev[devs_size];
net_devs devs = {.cnt = -1, .dev = (net_dev *)&netdev};
get_wifi_dev(&devs, devs_size);
for (int i = 0; i < devs.cnt; i++) {
char *ifname = devs.dev[i].name;
/* get assoc. users list */
iw = iwinfo_backend(ifname);
iw->assoclist(ifname, buf, &len);
for (int i = 0; i < len; i += entry_size) {
struct iwinfo_assoclist_entry *e;
e = (struct iwinfo_assoclist_entry *) &buf[i];
int ret = ubus_hostpad_del_station(ifname, format_mac((uint8_t *)e->mac));
if(ret != 0) return ret;
}
}
return 0;
}
int ubus_hostpad_del_station(const char *dev, const char *mac) {
syslogwda(LOG_INFO,"%s() %s:%d\n", __func__, __FILE__, __LINE__);
syslogwda(LOG_WARNING,"delete sta dev: %s mac: %s\n", dev, mac);
if(!dev || !mac) return -1;
char command[256];
snprintf(command, 256, "ubus call hostapd.%s "
"del_client "
"\"{'addr':'%s', 'reason':5, 'deauth':true, 'ban_time':0}\"",
dev, mac);
FILE *fp = popen(command, "r");
/* status code */
int ret = WEXITSTATUS(pclose(fp));
if(ret != 0) syslogwda(LOG_WARNING,"status code: %d %s\n", ret, strerror(ret));
return ret;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment