/********************************************************************\
 * This program is free software; you can redistribute it and/or    *
 * modify it under the terms of the GNU General Public License as   *
 * published by the Free Software Foundation; either version 2 of   *
 * the License, or (at your option) any later version.              *
 *                                                                  *
 * This program is distributed in the hope that it will be useful,  *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 * GNU General Public License for more details.                     *
 *                                                                  *
 * You should have received a copy of the GNU General Public License*
 * along with this program; if not, contact:                        *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
 * Boston, MA  02111-1307,  USA       gnu@gnu.org                   *
 *                                                                  *
\********************************************************************/

/* $Id$ */
/** @file wdctl.c
    @brief Monitoring and control of wifidog, client part
    @author Copyright (C) 2004 Alexandre Carmel-Veilleux <acv@acv.ca>
	@author Copyright (C) 2016 Dengfeng Liu <liudf0716@gmail.com>
*/

#define _GNU_SOURCE

#include "common.h"
#include "wdctl.h"
#include "util.h"

#define WDCTL_TIMEOUT   1000*2
#define WDCTL_MSG_LENG  1024*8

static char *sk_name = NULL;
char *progname = NULL;

static void usage(void);
static void parse_commandline(int, char **);
static void send_request(int, const char *);
static void read_response(int);
static void wdctl_cmd_process(int , char **, int);
static void wdctl_command_action(const char *, const char *);
static int connect_to_server(const char *);


static struct wdctl_client_command {
    const char *command; // command name
    const char *cmd_args; // comand args demo
    const char *cmd_description; // help
} wdctl_clt_cmd [] = {
    {"status", NULL, "get apfree wifidog status"},
#ifdef AW_FW3
    {"clear_trusted_pdomains", NULL, "clear trusted pan-domain"},
    {"show_trusted_pdomains", NULL, "show trusted pan-domain"},
#endif
    {"clear_trusted_iplist", NULL, "clear trusted iplist"},
    {"clear_trusted_domains", NULL, "clear trusted domain and it's ip"},
    {"show_trusted_domains", NULL, "show trusted domains and its ip"},
    {"show_trusted_mac", NULL, "show trusted mac list"},
    {"clear_trusted_mac", NULL, "clear trusted mac list"},
#ifdef AW_FW3
    {"add_trusted_pdomains", "pan-domain1,pan-domain2...", "add one or more trusted pan-domain like qq.com..."},
    {"del_trusted_pdomains", "pan-domain1,pan-domain2...", "del one or more trusted pan-domain list like qq.com..."},
#endif
    {"add_trusted_domains", "domain1,domain2...", "add trusted domain list like www.qq.com..."},
    {"del_trusted_domains", "domain1,domain2...", "del trusted domain list like www.qq.com...."},
    {"add_trusted_iplist", "ip1,ip2...", "add one or more trusted ip list like ip1,ip2..."},
    {"del_trusted_iplist", "ip1,ip2...", "del one or more trsuted ip list like ip1,ip2..."},
    {"add_trusted_mac", "mac1,mac2...", "add one or more trusted mac list like mac1,mac2..."},
    {"del_trusted_mac", "mac1,mac2...", "del one or more trusted mac list like mac1,mac2..."},
    {"reparse_trusted_domains", NULL, "reparse trusted domain's ip and add new parsed ip"},
    {"add_online_client", "{\"ip\":\"ipaddress\", \"mac\":\"devMac\", \"name\":\"devName\"}", "add roam client to connected list "},
	{"add_auth_client", "{\"ip\":\"ipaddress\", \"mac\":\"devMac\", \"name\":\"devName\"}", "add test client to connected list "},
    {"user_cfg_save", NULL, "save all rule to config file"},
    {"reset", "ip|mac", "logout connected client by its ip or mac"},
    {"stop", NULL, "stop apfree wifidog"},
    {"demo", NULL, "give some demonstration of method"},
};

/** 
 * @internal
 * @brief Print usage
 *
 * Prints usage, called when wdctl is run with -h or with an unknown option
 */
static void
usage(void)
{
    fprintf(stdout, "Usage: %s [options] command [arguments]\n", progname);
    fprintf(stdout, "\n");
    fprintf(stdout, "options:\n");
    fprintf(stdout, "  -s <path>         Path to the socket\n");
    fprintf(stdout, "  -h                Print usage\n");
    fprintf(stdout, "\n");
    fprintf(stdout, "commands arg\t description:\n");
    for (int i = 0; i < ARRAYLEN(wdctl_clt_cmd); i++) {
        fprintf(stdout, " %s %s\t %s \n", wdctl_clt_cmd[i].command, 
            wdctl_clt_cmd[i].cmd_args?wdctl_clt_cmd[i].cmd_args:"", 
            wdctl_clt_cmd[i].cmd_description);
    }
}

static void
list_all_method()
{
#define COMMAND_EQUAL(CMD) !strcmp(cmd,CMD)
    const char *cmd = NULL;
    for (int i = 0; i < ARRAYLEN(wdctl_clt_cmd); i++) {
        cmd = wdctl_clt_cmd[i].command;
        if (COMMAND_EQUAL("list"))
            continue;
        else if(COMMAND_EQUAL("add_online_client"))
            fprintf(stdout, "%s %s {\"ip\":\"192.168.1.211\", \"mac\":\"aa:bb:cc:dd:ee:ff\", \"name\":\"apfree\"}\n", progname, cmd);
        else if (COMMAND_EQUAL("add_trusted_domains"))
            fprintf(stdout, "%s %s captive.apple.com,www.baidu.com,www.qq.com,www.alibaba.com,aaa,bbb\n", progname, cmd);
        else if (COMMAND_EQUAL("add_trusted_pdomains"))
            fprintf(stdout, "%s %s apple.com,baidu.com,qq.com,aa,bb\n", progname, cmd);
        else if (COMMAND_EQUAL("add_trusted_mac"))
            fprintf(stdout, "%s %s aa:bb:cc:11:22:33,11:22:33:aa:bb:cc:dd,22.22.22:aa:aa:aa\n", progname, cmd);
        else if (COMMAND_EQUAL("add_trusted_iplist"))
            fprintf(stdout, "%s %s 192.168.1.2,192.168.1.3,192.168.1.4\n", progname, cmd);
        else
            fprintf(stdout, "%s %s \n", progname, cmd);
    }
#undef COMMAND_EQUAL
}

static void
wdctl_cmd_process(int argc, char **argv, int optind)
{
    if ((argc - optind) <= 0) {
        goto ERR;
    }

    for (int i = 0; i < ARRAYLEN(wdctl_clt_cmd); i++) {
        if (!strcmp(wdctl_clt_cmd[i].command, "demo")) {
            list_all_method();
            return;
        }

        if (!strcmp(wdctl_clt_cmd[i].command, *(argv+optind))) {
            if ((argc - (optind + 1)) > 0 && wdctl_clt_cmd[i].cmd_args) {
                wdctl_command_action(wdctl_clt_cmd[i].command, *(argv + optind + 1));
            } else if ((argc - (optind + 1)) == 0 && !wdctl_clt_cmd[i].cmd_args)
                wdctl_command_action(wdctl_clt_cmd[i].command, NULL);
            else
                goto ERR;

            return;
        }
    }
ERR:
    fprintf(stderr, "wdctlx: Error: Invalid command \"%s\"\n", *(argv + optind));
    usage();
    exit(EXIT_FAILURE);
}

/** @internal
 *
 * Uses getopt() to parse the command line and set configuration values
 */
void
parse_commandline(int argc, char **argv)
{
    extern int optind;
    int c;

    progname = argv[0];

    while (-1 != (c = getopt(argc, argv, "s:h"))) {
        switch (c) {
        case 'h':
            usage();
            exit(1);
            break;

        case 's':
            if (optarg) {
                sk_name = strdup(optarg);
            }
            break;

        default:
            usage();
            exit(1);
            break;
        }
    }

    if (!sk_name) sk_name = strdup(DEFAULT_SOCK);

    wdctl_cmd_process(argc, argv, optind);

}

static int
connect_to_server(const char *sock_name)
{
    struct sockaddr_un sa_un;
    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock < 0) {
        fprintf(stdout, "wdctl: could not get socket (Error: %s)\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    memset(&sa_un, 0, sizeof(sa_un));
    sa_un.sun_family = AF_UNIX;
    strncpy(sa_un.sun_path, sock_name, (sizeof(sa_un.sun_path) - 1));

    if (wd_connect(sock, (struct sockaddr *)&sa_un, strlen(sa_un.sun_path) + sizeof(sa_un.sun_family), 2)) {
        fprintf(stdout, "wdctl: wifidog probably not started (Error: %s)\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    return sock;
}

static void 
send_request(int sock, const char *request)
{
    struct pollfd fds;	
    fds.fd      = sock;
    fds.events  = POLLOUT;

    if (poll(&fds, 1, WDCTL_TIMEOUT) > 0 && fds.revents == POLLOUT) {
        write(sock, request, strlen(request));
    } 
}

/**
 * @brief execute command sending from wdctl_thread after process wdctlx's command
 * usually it execute 'dnsmasq restart', this feature depends openwrt system 
 *      
 */ 
static void
execute_post_cmd(char *raw_cmd)
{
    size_t nlen = strlen(raw_cmd);
    if (nlen < 3) goto ERR;

    char *cmd = NULL;
    if (raw_cmd[0] == '[' && raw_cmd[nlen-1] == ']') {
        raw_cmd[nlen-1] = '\0';
        cmd = raw_cmd + 1;
        system(cmd);
        fprintf(stdout, "execut shell [%s] success", cmd);
        return;
    }

ERR:
    fprintf(stdout, "[%s] is illegal post command", raw_cmd);
}

/**
 * @brief read reponse from wdctl_thread
 * 
 */ 
static void
read_response(int sock)
{
    char buf[WDCTL_MSG_LENG+1] = {0};
    struct pollfd fds;	
    fds.fd      = sock;
    fds.events  = POLLIN;

    if (poll(&fds, 1, WDCTL_TIMEOUT) > 0 && fds.revents == POLLIN) {
        if (read(sock, buf, WDCTL_MSG_LENG) > 0) {
            if (!strncmp(buf, "CMD", 3)) {
                execute_post_cmd(buf+3);
            } else
                fprintf(stdout, "%s\n", buf);
        }
    } 
    close(sock);
}

static void
wdctl_command_action(const char *cmd, const char *param)
{
    char *request = NULL;	
    int sock = connect_to_server(sk_name);
	
	if(param)	
		asprintf(&request, "%s %s", cmd, param);
	else
		asprintf(&request, "%s", cmd);

    send_request(sock, request);
    free(request);

    read_response(sock);
}

int
main(int argc, char **argv)
{
    parse_commandline(argc, argv);
}
