#include "conf.h"
#include "rhost.h"
#include "libiptc.h"
#include "libclamav.h"
#include "clamscan.h"

#include "ccronexpr.h"

// 存储公网IP
char *public_ip;

struct MemoryStruct
{
    char *memory;
    size_t size;
};

static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
    size_t realsize = size * nmemb;
    struct MemoryStruct *mem = (struct MemoryStruct *)userp;

    // 注意这里根据每次被调用获得的数据重新动态分配缓存区的大小
    char *ptr = realloc(mem->memory, mem->size + realsize + 1);
    if (ptr == NULL)
    {
        /* 内存不足！ */
        printf("not enough memory (realloc returned NULL)\n");
        return 0;
    }

    mem->memory = ptr;
    memcpy(&(mem->memory[mem->size]), contents, realsize);
    mem->size += realsize;
    mem->memory[mem->size] = 0;

    return realsize;
}

// 获取公网IP
static char *GET_PUBLIC_IP(char *URL)
{
    CURL *curl_handle;
    CURLcode res;

    struct MemoryStruct chunk;

    chunk.memory = malloc(1);   /* 将根据上述再分配的需要增长 */
    chunk.size = 0;             /* 此时没有数据 */

    curl_global_init(CURL_GLOBAL_ALL);

    /* 初始化curl会话 */
    curl_handle = curl_easy_init();

    /* 指定要获取的URL */
    curl_easy_setopt(curl_handle, CURLOPT_URL, URL);
    /* 将所有数据发送到此函数  */
    //对于同一次阻塞的curl_easy_perform而言，在写完获取的数据之前，会多次调用 WriteMemoryCallback
    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
    /* 将"chunk"结构传递给回调函数 */
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
    curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
    //对于同一次阻塞的curl_easy_perform而言，在写完获取的数据之前，会多次调用 WriteMemoryCallback
    res = curl_easy_perform(curl_handle);

    if (res != CURLE_OK)
    {
        fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
    }
    else
    {
        //printf("%lu bytes retrieved\n", (unsigned long)chunk.size);
        //printf("%s", chunk.memory);
        ;
    }

    curl_easy_cleanup(curl_handle);
    curl_global_cleanup();

    return chunk.memory;
}

// 检测系统
int check_system()
{
    if (0 == access("/etc/debian_version", F_OK))
    {
        return DEBISN_SYSTEM;
    } else if (0 == access("/etc/centos-release", F_OK))
    {
        return CENTOS_SYSTEM;
    }

    return UNKNOWN_SYSTEM;
}

// 钉钉告警
int dingding_warning(char *illegal_ip, char *public_ip, conf * conf)
{
    FILE *fp;
    char temp[64];
    char jsonObj[BUFFER];

    memset(jsonObj, 0, BUFFER);
    memset(temp, 0, 64);
    strcpy(temp, public_ip);
    temp[strlen(public_ip) - 1] = '\0';

    if ((fp = fopen("libcurl.log", "wt+")) == NULL)
    {
        return 1;
    }

    CURL *curl;
    CURLcode res;

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();
    if (curl == NULL)
    {
        return 1;
    }
#define JSIN "{  \
            \"msgtype\": \"text\",  \
            \"text\": { \
                \"content\": \"Alert @%s 服务器地址:%s，封禁非法入侵主机:%s\" \
            },  \
            \"at\": {   \
                \"atMobiles\": [\"%s\"], \
                \"isAtAll\": false  \
            }   \
        }"

    sprintf(jsonObj, JSIN, conf->PHONE, temp, illegal_ip, conf->PHONE);
    printf("%s\n", jsonObj);

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Accept: application/json");
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = curl_slist_append(headers, "charset: utf-8");

    curl_easy_setopt(curl, CURLOPT_URL, conf->DING_WEBHOOK);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
    curl_easy_setopt(curl, CURLOPT_POST, 1);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonObj);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl/0.1");
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);

    res = curl_easy_perform(curl);

    curl_easy_cleanup(curl);
    curl_global_cleanup();
    fclose(fp);

    return res;
}

// 邮件告警
int mail_warning(char *illegal_ip, char *public_ip, conf * conf)
{
    FILE *fp = NULL;
    char buff[BUFFER];
    char text[BUFFER];
    char temp[64];

    memset(buff, 0, BUFFER);
    memset(text, 0, BUFFER);
    memset(temp, 0, 64);

    strcpy(temp, public_ip);
    temp[strlen(public_ip) - 1] = '\0';
    sprintf(text, "echo \"主机:%s, 禁止%s访问\" | mail -s \"System ban IP\" %s", temp, illegal_ip, conf->RECV_MAIL);

    if (NULL == (fp = popen(text, "r")))
    {
        perror("popen text");
    }

    while (fgets(buff, BUFFER, fp) != NULL)
    {
        buff[strlen(buff) - 1] = '\0';
    }

    if (NULL != fp)
        pclose(fp);

    return 0;
}

// 第三方邮箱告警
int QQ_mail_warning(char *illegal_ip, char *public_ip, conf * conf)
{
    char string[BUFFER + (sizeof(QQMAIL)) + 1];
    char text[BUFFER];
    char temp[32];

    memset(string, 0, BUFFER + (sizeof(QQMAIL)) + 1);
    memset(text, 0, BUFFER);
    memset(temp, 0, 32);

    strcpy(temp, public_ip);
    temp[strlen(public_ip) - 1] = '\0';

    sprintf(text, "主机:%s, 禁止%s访问", temp, illegal_ip);
    sprintf(string, QQMAIL, conf->RECV_MAIL, text);

    return system(string);
}

// IP段白名单对比
int whitelist(char *client_ip, char (*whitelist_ip)[WHITELIST_IP_NUM])
{
    int i;

    for (i = 1; i < WHITELIST_IP_NUM - 1; i++)
    {
        if (strcmp(whitelist_ip[i], "\0") == 0)         // 如果字符串为空就跳出循环
        {
            break;
        }
        if ((strncmp(client_ip, whitelist_ip[i], strlen(whitelist_ip[i]))) == 0)    // 对比client_ip长度,
        {
            return 1;
        }
    }

    return 0;
}

// 地域段白名单对比
int isregion(char *str, char (*region_list)[WHITELIST_IP_NUM])
{
    int i;
    char *p;

    for (i = 1; i < WHITELIST_IP_NUM - 1; i++)
    {
        if (strcmp(region_list[i], "\0") == 0)   // 如果字符串为空就跳出循环
        {
            break;
        }
        // 在str中查找region_list[i]
        p = strstr(str, region_list[i]);
        if (p != NULL)
        {
            return 1;
        }
    }

    return 0;
}

// 去除空格
char *remove_space(const char *str)
{
    unsigned int i = 0, j = 0;
    unsigned int uLen = strlen(str);
    char *strRet;

    if (0 == uLen)
    {
        return '\0';
    }

    strRet = (char *)malloc(uLen + 1);
    memset(strRet, 0, uLen + 1);

    for (i = 0; i < uLen + 1; i++)
    {
        if (str[i] != ' ')
        {
            strRet[j++] = str[i];
        }
    }
    strRet[j] = '\0';

    return strRet;
}

// 封禁非法IP
int rule(conf * conf)
{
    char whitelist_ip[WHITELIST_IP_NUM][WHITELIST_IP_NUM] = { { 0 }, { 0 } };
    char region_list[WHITELIST_IP_NUM][WHITELIST_IP_NUM] = { { 0 }, { 0 } };

    char p[2], splice_command[LONG_BUFFER], command[LONG_BUFFER], *temp, buffer[BUFFER], awk[BUFFER];
    FILE *fp, *fc;
    time_t timep;
    struct tm *tp;


    fp = NULL;
    fc = NULL;
    timep = time(NULL);
    tp = localtime(&timep);
    memset(splice_command, 0, LONG_BUFFER);
    memset(command, 0, LONG_BUFFER);
    memset(buffer, 0, BUFFER);
    memset(awk, 0, BUFFER);


    if (DEBISN_SYSTEM == check_system()) // Debian 系统规则
    {
        if (tp->tm_mday >= 10)
        {
            if ((fp = popen(GE_10, "r")) == NULL)
            {
                perror("GE_10");
                return 1;
            }
        }
        else
        {
            if ((fp = popen(LE_10, "r")) == NULL)
            {
                perror("LE_10");
                return 1;
            }
        }
    }
    else if (CENTOS_SYSTEM == check_system()) // Centos 7系统规则
    {
        if (tp->tm_mday >= 10)
        {
            if ((fp = popen(CENTOS_GE_10, "r")) == NULL)
            {
                perror("CENTOS_GE_10");
                return 1;
            }
        }
        else
        {
            if ((fp = popen(CENTOS_LE_10, "r")) == NULL)
            {
                perror("CENTOS_LE_10");
                return 1;
            }
        }

    }
    else
    {
        
        return UNKNOWN_SYSTEM;
    }


    while (fgets(buffer, BUFFER, fp) != NULL)
    {
        temp = strstr(buffer, "rhost");
        sscanf(temp, "rhost=%s", temp);
        if (atoi(strncpy(p, temp, 1)) > 0)
        {
            strcat(splice_command, temp);
            strcat(splice_command, "\n");
        }
    }
    
    printf("%s", splice_command);           // 打印所有非法IP

    sprintf(awk, AWK, conf->REFUSE_NUMBER); // 拼接命令
    strcpy(command, "echo \"");
    strcat(command, splice_command);
    strcat(command, "\"");
    strcat(command, awk);

    if ((fc = popen(command, "r")) == NULL) // 执行命令
    {
        perror("popen command");
        return 1;
    }

    while (fgets(buffer, BUFFER, fc) != NULL) // 执行命令后, 为空时就不会
    {
        buffer[strlen(buffer) - 1] = '\0'; // 去除回车

        split_string(conf->IPV4_WHITE_LIST, " ", whitelist_ip);
        split_string(conf->REGION_LIST, " ", region_list);


        if (conf->IPV4_RESTRICTION == 1)    // 是否启用白名单
        {
            if (whitelist(buffer, whitelist_ip) == 1)
            {
                printf("白名单IPV4:%s\n", buffer);
                continue;
            }
        }

        if (0 != show_all_rule(buffer))     // libiptc库判断否存在规则
        {
            char *location = NULL;
            char *location_json = NULL;
            char iplocation[BUFFER];
            char URL[BUFFER + 70];
            char temp[BUFFER];
            char *p;
            char *p1;
            
            memset(URL, 0, BUFFER + 70);
            //sprintf(URL, "http://opendata.baidu.com/api.php?query=%s&co=&resource_id=6006&oe=utf8", buffer);
            sprintf(URL, conf->REGION_URL, buffer);
            //printf("%s\n", URL);

            location_json = GET_PUBLIC_IP(URL);
            if (NULL == location_json)
            {
                printf("获取IP位置错误!\n");
                goto BLOCKED;
            }
            else
            {
                p = strstr(location_json, "\"location\"");
                p1 = strstr(p, "\",");

                memset(temp, 0, BUFFER);
                memcpy(temp, p + 12, p1 - p - 12);
                location = remove_space(temp);
            }

            memset(iplocation, 0, BUFFER);
            strcpy(iplocation, buffer);
            strcat(iplocation, "(");
            strcat(iplocation, location);
            strcat(iplocation, ")");
            
            if (location)
                free(location);
            if (location_json)
                free(location_json);
            
            // 地域白名单
            if (conf->REGION == 1)
            {
                if (isregion(iplocation, region_list) == 1)
                {
                    printf("地域白名单: %s\n", iplocation);
                    continue;
                }

            }

            if (conf->IS_DING_WEBHOOK == 1) // 钉钉告警
            {
                dingding_warning(iplocation, public_ip, conf);
                sleep(3);
            }

            if (conf->IS_MAIL == 1)         // 邮件告警
            {
                mail_warning(iplocation, public_ip, conf);
                sleep(3);
            }

            if (conf->IS_QQMAIL == 1)       // 邮件告警
            {
                QQ_mail_warning(iplocation, public_ip, conf);
                sleep(3);
            }


BLOCKED:
            // 是否封禁攻击IP
            if (conf->IS_BLOCKED == 1)
            {
                // libiptc 库插入规则 iptables -t filter -A INPUT -p tcp -m tcp -s xxxx -j DROP
                unsigned int srcIp;
                inet_pton(AF_INET, buffer, &srcIp);
                iptc_add_rule("filter", "INPUT", IPPROTO_TCP, NULL, NULL, srcIp, 0, NULL, NULL, "DROP", NULL, 1);
            }

        }

    }

    if (fp != NULL)
        pclose(fp);

    if (fc != NULL)
        pclose(fc);

    return 0;
}

static void sig_child(int signo)
{
    pid_t pid;
    int stat;
    // 处理僵尸进程
    while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) ;

    return;
}

static int get_executable_path(char *processdir, char *processname, int len)
{
    char *filename;
    
    if (readlink("/proc/self/exe", processdir, len) <= 0)
        return -1;
    filename = strrchr(processdir, '/');
    if (filename == NULL)
        return -1;
    ++filename;
    strcpy(processname, filename);
    *filename = '\0';
    return (int)(filename - processdir);
}

// 处理参数
int process_argv(int argc, char *argv[], char **argvs)
{
    argvs[0] = argv[0];
    int i;
    for (i = 0; i <= argc - 1; i++)
    {
        if (i == 1)
        {
            for (int j = i; j <= argc - 2; j++)
            {
                argvs[j] = argv[j + 1];
            }
        }
    }

    return 0;
}

int _crontab(struct tm **calnext, char *string)
{
    const char *err = NULL;
    time_t cur;
    time_t datenext;
    
    time(&cur);
    cron_expr parsed;
    cron_parse_expr(string, &parsed, &err);
    datenext = cron_next(&parsed, cur);
    *calnext = localtime(&datenext);
    assert(*calnext);
    
    return 0;
}

int main(int argc, char *argv[], char **env)
{
    if (CENTOS_SYSTEM == check_system()) {
        char **head_argvs;
        int head_argc = 0;
        char *argvs[ARGS_NUM] = { NULL };
        
        argvs[0] = argv[0];
        argvs[1] = "--user=root";
        argvs[2] = "--quiet";
        argvs[3] = "--no-warnings";
        head_argvs = &(argvs[0]);
        head_argc = 2;
        
        /*
        for(int i=0; i<head_argc; i++)
        {
            printf("%s  %d\n", head_argvs[i], i);
        }
        */
        
        // freshclam配置文件
        if (access("/etc/clamav/freshclam.conf", F_OK) == -1) {
            system("mkdir -p /etc/clamav/");
            system("cp freshclam.conf /etc/clamav/");
        }
        
        _freshclam(head_argc, head_argvs);
    }
    
    signal(SIGCHLD, sig_child); // 创建捕捉子进程退出信号

    int pid;
    
    // 读取配置
    char path[BUFFER] = { 0 };
    char executable_filename[BUFFER] = { 0 };
    (void)get_executable_path(path, executable_filename, sizeof(path));
    strcat(executable_filename, ".conf");
    strcat(path, executable_filename);
    conf *conf = (struct CONF *)malloc(sizeof(struct CONF));
    read_conf(path, conf);
    //ptintf_conf(conf);
    
    
    // 处理clamav参数
    char **head_argvs;
    int head_argc = 0;
    char *argvs[ARGS_NUM] = { NULL };
    char args[ARGS_NUM][WHITELIST_IP_NUM] = { { 0 }, { 0 } };
    //printf("%d\n", argc);
    if (argc > 3)                   // 手动输入参数(如果手动输入参数个数大于3个, 则使用用户输入的参数)
    {
        process_argv(argc, argv, &(argvs[0]));
        head_argvs = &(argvs[0]);   // head_argvs指向argvs[0]
        head_argc = argc - 1;       // 改变argc数

    }
    else                            // 读取配置文件参数
    {
        argvs[0] = argv[0];
        split_string(conf->CLAMAV_ARG, " ", args);
        for (int i=1; i<ARGS_NUM; i++)
        {
            if (args[i][0] == '\0')
            {
                continue;
            }
            else
            {
                argvs[i] = strdup(args[i]);
                head_argc++;
            }
        }
        
        head_argvs = &(argvs[0]);
        head_argc += 1;
    }
/*
    // 打印clamav参数
    for(int i=0; i<head_argc; i++)
    {
        printf("%s  %d\n", head_argvs[i], i);
    }
*/
    
    now_next_time *t = (now_next_time *)malloc(sizeof(struct now_next_time));
    memset(t, 0, sizeof(struct now_next_time));

    // 获取公网IP
    public_ip = GET_PUBLIC_IP(conf->PUBLIC_IP);
    //printf("%s", public_ip);


    if (0 == strcmp(conf->DAEMON, "on"))
    {
        goto goto_daemon;
    }

    if (argv[1] != NULL && 0 == strcmp(argv[1], "-d"))
    {
goto_daemon:
/*
        if (daemon(1, 1))       // 守护进程
        {
            perror("daemon");
            return -1;
        }
*/

        // 守护进程
        if ((pid = fork()) < 0) {
            return 0;
        } else if (0 != pid) {
            for(int i=1; i<head_argc; i++)
            {
                if (head_argvs[i])
                    free(head_argvs[i]);
            }
            free(t);
            free_conf(conf);
            free(conf);
            free(public_ip);
            exit(0);
        }

        if (setsid() < 0) {
            return 0;
        }

        signal(SIGHUP, SIG_IGN);
        if ((pid = fork()) < 0) {
            return 0;
        } else if (0 != pid) {
            for(int i=1; i<head_argc; i++)
            {
                if (head_argvs[i])
                    free(head_argvs[i]);
            }
            free(t);
            free_conf(conf);
            free(conf);
            free(public_ip);
            exit(0);
        }


        while (1)
        {
            // Cron          
            struct tm *calnext; //取得Cron规则时间
            calnext = (struct tm *)malloc(sizeof(struct tm));
            memset(calnext, 0, sizeof(struct tm));
            _crontab(&calnext, conf->CLAMAV_TIME);
            t->next_year = 1900 + calnext->tm_year;
            t->next_mon = 1 + calnext->tm_mon;
            t->next_day = calnext->tm_mday;
            t->next_hour = calnext->tm_hour;
            t->next_min = calnext->tm_min;
            t->next_sec = calnext->tm_sec;

            
            // 取得现在时间
            time_t timep;
            struct tm *p;
            timep = time(NULL);
            p = localtime(&timep);
            t->now_year = 1900 + p->tm_year;
            t->now_mon = 1 + p->tm_mon;
            t->now_day = p->tm_mday;
            t->now_hour = p->tm_hour;
            t->now_min = p->tm_min;
            t->now_sec = p->tm_sec;
            
            
            // Clamav call
            if (1 == conf->CLAMAV)
            {
                if ( t->now_year == t->next_year && t->now_mon == t->next_mon && t->now_day == t->next_day && t->now_hour == t->next_hour && t->now_min == t->next_min )
                {
                    //printf("%d%d%d %d:%d:%d\n", t->now_year, t->now_mon, t->now_day, t->now_hour, t->now_min, t->now_sec);
                    //printf("%d%d%d %d:%d:%d\n", t->next_year, t->next_mon, t->next_day, t->next_hour, t->next_min, t->next_sec);
                    
                    pid_t pid;
                    pid = fork();
                    if (pid < 0) {
                        printf("fork error.\n");
                        return -1;
                    }
                    else if (pid == 0)              // child process
                    {          

                        int r = 0;
                        r = _clamscan(head_argc, head_argvs);
                        _exit(r);
                    }
                    else
                    {
                        int status = 0;
                        wait(&status);              // wait the end of child process
                        if (WIFEXITED(status))
                        {
                            ;
                            //printf("child process return %d\n", WEXITSTATUS(status));
                        }
                        
                        sleep(60);                  // 跳过这一分钟
                    }
                }
            }
            

            rule(conf);
            sleep(conf->TIME);
        }
    }
    else
    {
        rule(conf);
    }

    free(t);
    free_conf(conf);
    free(conf);
    free(public_ip);
    
    for(int i=1; i<head_argc; i++)
    {
        //printf("%s  %d\n", head_argvs[i], i);
        if (head_argvs[i])
            free(head_argvs[i]);
        
    }
    
    
    return 0;
}
