/*
 * 钥鉴 / 科御 V3 网络验证 · Linux C 测试示例
 *
 * 协议（与官方 api.keyunet.cn 实测一致）：
 *   请求签名  sign = MD5(apiRoute + mode + authCode + password + deviceId + timeStamp + signKey)
 *   请求密文  data = Base64( AES-128-ECB-PKCS7( mode|authCode|password|deviceId|timeStamp ) )
 *             加密密钥 = 签名密钥 UTF-8 前 16 字节（不足补 0）
 *   POST      /v3/License/verify/{appId}
 *             Content-Type: application/x-www-form-urlencoded;charset=utf-8
 *             body: data=<urlencode(b64)>&sign=<md5>&encoding=utf-8
 *   响应      除 sign 外每个字段单独 AES+Base64；sign 为明文 MD5
 *             成功：MD5(code + endDate + token + remark + timeStamp + signKey)
 *             失败：MD5(code + msg [+ link] + signKey)
 *
 * 编译：
 *   gcc -O2 -Wall -o keyunet_verify keyunet_verify.c -ldl
 *   （HTTPS 运行时加载 libssl，无需安装 libssl-dev）
 *
 * 运行：
 *   ./keyunet_verify -t
 *   ./keyunet_verify -H api.keyunet.cn -P 80 -A 67612919 -K gngkcyuBkc56xMmM -C MYo1VJWrGdWm -D MY-LINUX-001
 *   ./keyunet_verify -H keyu.grok.me -s -e sign -A 10000 -K KeyuNetSignKey01 -C KY-YEAR-DEMO01 -D MY-LINUX-001
 *
 * 科御 AES 项目必须 POST data+sign+encoding（signKey 只本地算 MD5/AES，不进请求体）。
 */

#include <arpa/inet.h>
#include <dlfcn.h>
#include <netdb.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>

static char g_host[128] = "api.keyunet.cn";
static int g_port = 80;
static int g_tls = 0;
static int g_aes = 1; /* 科御 AES 项目：请求 body 为 data=&sign=&encoding= */
static char g_app[32] = "67612919";
static char g_key[128] = "gngkcyuBkc56xMmM";
static char g_auth[128] = "MYo1VJWrGdWm";
static char g_pass[128] = "";
static char g_mode[8] = "0";
static char g_dev[128] = "LINUXTEST0001";

/* ======================== MD5 ======================== */
typedef struct {
    uint32_t s[4];
    uint64_t n;
    uint8_t buf[64];
} md5_t;

static uint32_t rol(uint32_t x, int r) { return (x << r) | (x >> (32 - r)); }

static void md5_init(md5_t *c) {
    c->s[0] = 0x67452301u;
    c->s[1] = 0xefcdab89u;
    c->s[2] = 0x98badcfeu;
    c->s[3] = 0x10325476u;
    c->n = 0;
}

static void md5_block(md5_t *c, const uint8_t *p) {
    static const uint32_t K[64] = {
        0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
        0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
        0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
        0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
        0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
        0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
        0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
        0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
    static const int S[64] = {7,  12, 17, 22, 7,  12, 17, 22, 7,  12, 17, 22, 7,  12, 17, 22, 5,  9,  14, 20, 5,  9,
                              14, 20, 5,  9,  14, 20, 5,  9,  14, 20, 4,  11, 16, 23, 4,  11, 16, 23, 4,  11, 16, 23,
                              4,  11, 16, 23, 6,  10, 15, 21, 6,  10, 15, 21, 6,  10, 15, 21, 6,  10, 15, 21};
    uint32_t w[16], a = c->s[0], b = c->s[1], cc = c->s[2], d = c->s[3], f, t;
    int i, g;
    for (i = 0; i < 16; i++)
        w[i] = (uint32_t)p[4 * i] | ((uint32_t)p[4 * i + 1] << 8) | ((uint32_t)p[4 * i + 2] << 16) |
               ((uint32_t)p[4 * i + 3] << 24);
    for (i = 0; i < 64; i++) {
        if (i < 16) {
            f = (b & cc) | (~b & d);
            g = i;
        } else if (i < 32) {
            f = (d & b) | (~d & cc);
            g = (5 * i + 1) % 16;
        } else if (i < 48) {
            f = b ^ cc ^ d;
            g = (3 * i + 5) % 16;
        } else {
            f = cc ^ (b | ~d);
            g = (7 * i) % 16;
        }
        t = d;
        d = cc;
        cc = b;
        b += rol(a + f + K[i] + w[g], S[i]);
        a = t;
    }
    c->s[0] += a;
    c->s[1] += b;
    c->s[2] += cc;
    c->s[3] += d;
}

static void md5_upd(md5_t *c, const void *data, size_t len) {
    const uint8_t *p = (const uint8_t *)data;
    size_t i, idx = (size_t)(c->n % 64);
    c->n += len;
    for (i = 0; i < len; i++) {
        c->buf[idx++] = p[i];
        if (idx == 64) {
            md5_block(c, c->buf);
            idx = 0;
        }
    }
}

static void md5_hex(md5_t *c, char out[33]) {
    uint8_t pad[64], digest[16];
    uint64_t bits = c->n * 8;
    size_t idx = (size_t)(c->n % 64), i;
    memset(pad, 0, sizeof(pad));
    pad[0] = 0x80;
    if (idx < 56)
        md5_upd(c, pad, 56 - idx);
    else
        md5_upd(c, pad, 64 - idx + 56);
    for (i = 0; i < 8; i++) pad[i] = (uint8_t)(bits >> (8 * i));
    md5_upd(c, pad, 8);
    for (i = 0; i < 4; i++) {
        digest[4 * i] = (uint8_t)c->s[i];
        digest[4 * i + 1] = (uint8_t)(c->s[i] >> 8);
        digest[4 * i + 2] = (uint8_t)(c->s[i] >> 16);
        digest[4 * i + 3] = (uint8_t)(c->s[i] >> 24);
    }
    for (i = 0; i < 16; i++) sprintf(out + i * 2, "%02x", digest[i]);
}

static void md5_str(const char *s, char out[33]) {
    md5_t c;
    md5_init(&c);
    md5_upd(&c, s, strlen(s));
    md5_hex(&c, out);
}

/* ======================== AES-128-ECB PKCS7 ======================== */
static const uint8_t SBOX[256] = {
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9,
    0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f,
    0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07,
    0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3,
    0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58,
    0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3,
    0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f,
    0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
    0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac,
    0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a,
    0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70,
    0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11,
    0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42,
    0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16};
static const uint8_t RSBOX[256] = {
    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39,
    0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2,
    0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76,
    0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc,
    0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d,
    0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c,
    0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f,
    0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85,
    0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62,
    0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd,
    0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60,
    0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d,
    0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6,
    0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d};
static const uint8_t RCON[11] = {0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};

static uint8_t xtime(uint8_t x) { return (uint8_t)((x << 1) ^ ((x >> 7) * 0x1b)); }

static uint8_t gf_mul(uint8_t v, uint8_t n) {
    uint8_t r = 0;
    while (n) {
        if (n & 1) r ^= v;
        v = xtime(v);
        n >>= 1;
    }
    return r;
}

static void key_expand(const uint8_t key[16], uint8_t rk[176]) {
    int i;
    memcpy(rk, key, 16);
    for (i = 4; i < 44; i++) {
        uint8_t t[4];
        memcpy(t, rk + (i - 1) * 4, 4);
        if (i % 4 == 0) {
            uint8_t k = t[0];
            t[0] = (uint8_t)(SBOX[t[1]] ^ RCON[i / 4]);
            t[1] = SBOX[t[2]];
            t[2] = SBOX[t[3]];
            t[3] = SBOX[k];
        }
        rk[i * 4 + 0] = (uint8_t)(rk[(i - 4) * 4 + 0] ^ t[0]);
        rk[i * 4 + 1] = (uint8_t)(rk[(i - 4) * 4 + 1] ^ t[1]);
        rk[i * 4 + 2] = (uint8_t)(rk[(i - 4) * 4 + 2] ^ t[2]);
        rk[i * 4 + 3] = (uint8_t)(rk[(i - 4) * 4 + 3] ^ t[3]);
    }
}

static void add_rk(uint8_t s[16], const uint8_t *rk) {
    int i;
    for (i = 0; i < 16; i++) s[i] ^= rk[i];
}

static void sub_bytes(uint8_t s[16], int inv) {
    int i;
    for (i = 0; i < 16; i++) s[i] = inv ? RSBOX[s[i]] : SBOX[s[i]];
}

static void shift_rows(uint8_t s[16], int inv) {
    uint8_t t[16];
    memcpy(t, s, 16);
    if (!inv) {
        s[1] = t[5];
        s[5] = t[9];
        s[9] = t[13];
        s[13] = t[1];
        s[2] = t[10];
        s[6] = t[14];
        s[10] = t[2];
        s[14] = t[6];
        s[3] = t[15];
        s[7] = t[3];
        s[11] = t[7];
        s[15] = t[11];
    } else {
        s[1] = t[13];
        s[5] = t[1];
        s[9] = t[5];
        s[13] = t[9];
        s[2] = t[10];
        s[6] = t[14];
        s[10] = t[2];
        s[14] = t[6];
        s[3] = t[7];
        s[7] = t[11];
        s[11] = t[15];
        s[15] = t[3];
    }
}

static void mix_columns(uint8_t s[16], int inv) {
    int i;
    for (i = 0; i < 4; i++) {
        uint8_t *c = s + i * 4;
        uint8_t a = c[0], b = c[1], d = c[2], e = c[3];
        if (!inv) {
            c[0] = (uint8_t)(xtime(a) ^ xtime(b) ^ b ^ d ^ e);
            c[1] = (uint8_t)(a ^ xtime(b) ^ xtime(d) ^ d ^ e);
            c[2] = (uint8_t)(a ^ b ^ xtime(d) ^ xtime(e) ^ e);
            c[3] = (uint8_t)(xtime(a) ^ a ^ b ^ d ^ xtime(e));
        } else {
            c[0] = (uint8_t)(gf_mul(a, 0x0e) ^ gf_mul(b, 0x0b) ^ gf_mul(d, 0x0d) ^ gf_mul(e, 0x09));
            c[1] = (uint8_t)(gf_mul(a, 0x09) ^ gf_mul(b, 0x0e) ^ gf_mul(d, 0x0b) ^ gf_mul(e, 0x0d));
            c[2] = (uint8_t)(gf_mul(a, 0x0d) ^ gf_mul(b, 0x09) ^ gf_mul(d, 0x0e) ^ gf_mul(e, 0x0b));
            c[3] = (uint8_t)(gf_mul(a, 0x0b) ^ gf_mul(b, 0x0d) ^ gf_mul(d, 0x09) ^ gf_mul(e, 0x0e));
        }
    }
}

static void aes_block(uint8_t s[16], const uint8_t rk[176], int inv) {
    int r;
    if (!inv) {
        add_rk(s, rk);
        for (r = 1; r < 10; r++) {
            sub_bytes(s, 0);
            shift_rows(s, 0);
            mix_columns(s, 0);
            add_rk(s, rk + r * 16);
        }
        sub_bytes(s, 0);
        shift_rows(s, 0);
        add_rk(s, rk + 160);
    } else {
        add_rk(s, rk + 160);
        for (r = 9; r >= 1; r--) {
            shift_rows(s, 1);
            sub_bytes(s, 1);
            add_rk(s, rk + r * 16);
            mix_columns(s, 1);
        }
        shift_rows(s, 1);
        sub_bytes(s, 1);
        add_rk(s, rk);
    }
}

static void aes_key16(const char *secret, uint8_t out[16]) {
    size_t n = strlen(secret);
    memset(out, 0, 16);
    if (n > 16) n = 16;
    memcpy(out, secret, n);
}

static int aes_ecb(const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen, const char *secret, int enc) {
    uint8_t key[16], rk[176];
    size_t i, n;
    aes_key16(secret, key);
    key_expand(key, rk);
    if (enc) {
        uint8_t pad = (uint8_t)(16 - (inlen % 16));
        n = inlen + pad;
        memcpy(out, in, inlen);
        memset(out + inlen, pad, pad);
        for (i = 0; i < n; i += 16) aes_block(out + i, rk, 0);
        *outlen = n;
        return 0;
    }
    if (inlen == 0 || inlen % 16) return -1;
    memcpy(out, in, inlen);
    for (i = 0; i < inlen; i += 16) aes_block(out + i, rk, 1);
    {
        uint8_t pad = out[inlen - 1];
        if (pad < 1 || pad > 16) return -1;
        for (i = 0; i < pad; i++)
            if (out[inlen - 1 - i] != pad) return -1;
        *outlen = inlen - pad;
        out[*outlen] = 0;
    }
    return 0;
}

/* ======================== Base64 / URL ======================== */
static const char B64TAB[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static char *b64enc(const uint8_t *in, size_t n) {
    size_t i, j = 0;
    char *s = (char *)malloc(4 * ((n + 2) / 3) + 1);
    if (!s) return NULL;
    for (i = 0; i < n; i += 3) {
        int v = in[i] << 16;
        if (i + 1 < n) v |= in[i + 1] << 8;
        if (i + 2 < n) v |= in[i + 2];
        s[j++] = B64TAB[(v >> 18) & 63];
        s[j++] = B64TAB[(v >> 12) & 63];
        s[j++] = (i + 1 < n) ? B64TAB[(v >> 6) & 63] : '=';
        s[j++] = (i + 2 < n) ? B64TAB[v & 63] : '=';
    }
    s[j] = 0;
    return s;
}

static int b64val(int c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
    if (c >= '0' && c <= '9') return c - '0' + 52;
    if (c == '+') return 62;
    if (c == '/') return 63;
    return -1;
}

static uint8_t *b64dec(const char *s, size_t *outlen) {
    size_t n = strlen(s), i, j = 0, k = 0;
    uint8_t buf[4], *o = (uint8_t *)malloc(n + 4);
    if (!o) return NULL;
    for (i = 0; i < n; i++) {
        int v;
        if (s[i] == '=' || s[i] == '\n' || s[i] == '\r') continue;
        v = b64val((unsigned char)s[i]);
        if (v < 0) continue;
        buf[k++] = (uint8_t)v;
        if (k == 4) {
            o[j++] = (uint8_t)((buf[0] << 2) | (buf[1] >> 4));
            o[j++] = (uint8_t)((buf[1] << 4) | (buf[2] >> 2));
            o[j++] = (uint8_t)((buf[2] << 6) | buf[3]);
            k = 0;
        }
    }
    if (k >= 2) {
        o[j++] = (uint8_t)((buf[0] << 2) | (buf[1] >> 4));
        if (k >= 3) o[j++] = (uint8_t)((buf[1] << 4) | (buf[2] >> 2));
    }
    o[j] = 0;
    *outlen = j;
    return o;
}

static char *urlenc(const char *s) {
    size_t n = strlen(s), i, j = 0;
    char *o = (char *)malloc(n * 3 + 1);
    if (!o) return NULL;
    for (i = 0; i < n; i++) {
        unsigned char c = (unsigned char)s[i];
        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' ||
            c == '.' || c == '~') {
            o[j++] = (char)c;
        } else {
            sprintf(o + j, "%%%02X", c);
            j += 3;
        }
    }
    o[j] = 0;
    return o;
}

static char *aes_b64(const char *plain) {
    uint8_t tmp[4096];
    size_t n = 0;
    if (strlen(plain) > 4000) return NULL;
    if (aes_ecb((const uint8_t *)plain, strlen(plain), tmp, &n, g_key, 1) != 0) return NULL;
    return b64enc(tmp, n);
}

static int aes_unb64(const char *b64, char *out, size_t cap) {
    size_t rawn = 0, n = 0;
    uint8_t *raw, tmp[4096];
    raw = b64dec(b64, &rawn);
    if (!raw) return -1;
    if (rawn > 4000 || aes_ecb(raw, rawn, tmp, &n, g_key, 0) != 0) {
        free(raw);
        return -1;
    }
    free(raw);
    if (n >= cap) n = cap - 1;
    memcpy(out, tmp, n);
    out[n] = 0;
    return 0;
}

/* ======================== HTTP / HTTPS POST ======================== */
#define SSL_CTRL_SET_TLSEXT_HOSTNAME 55

static void *h_ssl;
static void *(*p_ctx_new)(const void *);
static const void *(*p_method)(void);
static void *(*p_ssl_new)(void *);
static int (*p_set_fd)(void *, int);
static int (*p_ssl_connect)(void *);
static int (*p_ssl_write)(void *, const void *, int);
static int (*p_ssl_read)(void *, void *, int);
static int (*p_ssl_shutdown)(void *);
static void (*p_ssl_free)(void *);
static void (*p_ctx_free)(void *);
static long (*p_ssl_ctrl)(void *, int, long, void *);
static int (*p_ssl_init)(uint64_t, const void *);

static int ssl_load(void) {
    if (h_ssl) return 0;
    dlopen("libcrypto.so.3", RTLD_NOW | RTLD_GLOBAL);
    dlopen("libcrypto.so.1.1", RTLD_NOW | RTLD_GLOBAL);
    h_ssl = dlopen("libssl.so.3", RTLD_NOW | RTLD_GLOBAL);
    if (!h_ssl) h_ssl = dlopen("libssl.so.1.1", RTLD_NOW | RTLD_GLOBAL);
    if (!h_ssl) h_ssl = dlopen("libssl.so", RTLD_NOW | RTLD_GLOBAL);
    if (!h_ssl) {
        fprintf(stderr, "找不到 libssl（HTTPS 需要）。Debian/Ubuntu: apt install libssl3\n%s\n", dlerror());
        return -1;
    }
    p_ctx_new = (void *(*)(const void *))dlsym(h_ssl, "SSL_CTX_new");
    p_method = (const void *(*)(void))dlsym(h_ssl, "TLS_client_method");
    p_ssl_new = (void *(*)(void *))dlsym(h_ssl, "SSL_new");
    p_set_fd = (int (*)(void *, int))dlsym(h_ssl, "SSL_set_fd");
    p_ssl_connect = (int (*)(void *))dlsym(h_ssl, "SSL_connect");
    p_ssl_write = (int (*)(void *, const void *, int))dlsym(h_ssl, "SSL_write");
    p_ssl_read = (int (*)(void *, void *, int))dlsym(h_ssl, "SSL_read");
    p_ssl_shutdown = (int (*)(void *))dlsym(h_ssl, "SSL_shutdown");
    p_ssl_free = (void (*)(void *))dlsym(h_ssl, "SSL_free");
    p_ctx_free = (void (*)(void *))dlsym(h_ssl, "SSL_CTX_free");
    p_ssl_ctrl = (long (*)(void *, int, long, void *))dlsym(h_ssl, "SSL_ctrl");
    p_ssl_init = (int (*)(uint64_t, const void *))dlsym(h_ssl, "OPENSSL_init_ssl");
    if (!p_ctx_new || !p_method || !p_ssl_new || !p_set_fd || !p_ssl_connect || !p_ssl_write || !p_ssl_read ||
        !p_ssl_free || !p_ctx_free) {
        fprintf(stderr, "libssl 符号不完整\n");
        return -1;
    }
    if (p_ssl_init) p_ssl_init(0, NULL);
    return 0;
}

static int tcp_connect(void) {
    struct addrinfo hints, *ai = NULL, *rp;
    char port[16];
    int fd = -1;
    snprintf(port, sizeof(port), "%d", g_port);
    memset(&hints, 0, sizeof(hints));
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_family = AF_UNSPEC;
    if (getaddrinfo(g_host, port, &hints, &ai) != 0) {
        perror("getaddrinfo");
        return -1;
    }
    for (rp = ai; rp; rp = rp->ai_next) {
        fd = (int)socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (fd < 0) continue;
        if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) break;
        close(fd);
        fd = -1;
    }
    freeaddrinfo(ai);
    if (fd < 0) perror("connect");
    return fd;
}

static int http_status(const char *resp) {
    const char *p = strstr(resp, "HTTP/");
    if (!p) return 0;
    p = strchr(p, ' ');
    return p ? atoi(p + 1) : 0;
}

static int http_post(const char *path, const char *body, char *resp, size_t cap) {
    char hdr[768], req[8192];
    int fd, n, total = 0, hn;
    fd = tcp_connect();
    if (fd < 0) return -1;
    hn = snprintf(hdr, sizeof(hdr),
                  "POST %s HTTP/1.0\r\n"
                  "Host: %s\r\n"
                  "Content-Type: application/x-www-form-urlencoded;charset=utf-8\r\n"
                  "Content-Length: %zu\r\n"
                  "Connection: close\r\n\r\n",
                  path, g_host, strlen(body));
    if ((size_t)hn + strlen(body) >= sizeof(req)) {
        fprintf(stderr, "请求过大\n");
        close(fd);
        return -1;
    }
    memcpy(req, hdr, (size_t)hn);
    memcpy(req + hn, body, strlen(body) + 1);
    n = hn + (int)strlen(body);

    if (g_tls) {
        void *ctx, *ssl;
        int off = 0;
        if (ssl_load() != 0) {
            close(fd);
            return -1;
        }
        ctx = p_ctx_new(p_method());
        ssl = p_ssl_new(ctx);
        p_set_fd(ssl, fd);
        if (p_ssl_ctrl) p_ssl_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, 0, g_host);
        if (p_ssl_connect(ssl) != 1) {
            fprintf(stderr, "TLS 握手失败（检查 -s / 443 与域名证书）\n");
            p_ssl_free(ssl);
            p_ctx_free(ctx);
            close(fd);
            return -1;
        }
        while (off < n) {
            int w = p_ssl_write(ssl, req + off, n - off);
            if (w <= 0) break;
            off += w;
        }
        while (total + 1 < (int)cap) {
            int r = p_ssl_read(ssl, resp + total, (int)cap - 1 - total);
            if (r <= 0) break;
            total += r;
        }
        if (p_ssl_shutdown) p_ssl_shutdown(ssl);
        p_ssl_free(ssl);
        p_ctx_free(ctx);
        close(fd);
    } else {
        if (send(fd, req, (size_t)n, 0) < 0) {
            perror("send");
            close(fd);
            return -1;
        }
        while (total + 1 < (int)cap) {
            int r = (int)recv(fd, resp + total, cap - 1 - (size_t)total, 0);
            if (r <= 0) break;
            total += r;
        }
        close(fd);
    }
    resp[total] = 0;

    {
        int st = http_status(resp);
        if (!g_tls && (st == 301 || st == 302 || st == 307 || st == 308) && strstr(resp, "https://")) {
            printf("HTTP %d，站点强制 HTTPS，自动改走 443 TLS …\n", st);
            g_tls = 1;
            g_port = 443;
            return http_post(path, body, resp, cap);
        }
    }
    return total;
}

static char *http_body(char *resp) {
    char *p = strstr(resp, "\r\n\r\n");
    if (p) return p + 4;
    p = strstr(resp, "\n\n");
    return p ? p + 2 : resp;
}

/* ======================== JSON ======================== */
static int json_get(const char *js, const char *key, char *out, size_t cap) {
    char pat[80];
    const char *p;
    size_t n = 0;
    snprintf(pat, sizeof(pat), "\"%s\"", key);
    p = strstr(js, pat);
    if (!p) return -1;
    p = strchr(p + strlen(pat), ':');
    if (!p) return -1;
    p++;
    while (*p == ' ' || *p == '\t') p++;
    if (*p == '"') {
        p++;
        while (*p && n + 1 < cap) {
            if (*p == '\\' && p[1]) {
                p++;
                out[n++] = *p++;
            } else if (*p == '"')
                break;
            else
                out[n++] = *p++;
        }
        out[n] = 0;
        return 0;
    }
    while (*p && *p != ',' && *p != '}' && *p != ' ' && n + 1 < cap) out[n++] = *p++;
    out[n] = 0;
    return 0;
}

static int looks_cipher(const char *s) {
    size_t n;
    if (!s) return 0;
    n = strlen(s);
    if (n < 16) return 0;
    if (strpbrk(s, "+/=")) return 1;
    return n >= 24;
}

static int collect_keys(const char *js, char keys[][32], int max) {
    int nk = 0;
    const char *p = js;
    while (*p && nk < max) {
        if (*p != '"') {
            p++;
            continue;
        }
        {
            char k[32];
            int i = 0;
            p++;
            while (*p && *p != '"' && i < 31) k[i++] = *p++;
            k[i] = 0;
            if (*p == '"') p++;
            while (*p == ' ') p++;
            if (*p != ':') continue;
            p++;
            if (strcmp(k, "sign") != 0) {
                snprintf(keys[nk], 32, "%s", k);
                nk++;
            }
            while (*p == ' ') p++;
            if (*p == '"') {
                p++;
                while (*p && *p != '"') {
                    if (*p == '\\' && p[1]) p++;
                    p++;
                }
                if (*p == '"') p++;
            } else {
                while (*p && *p != ',' && *p != '}') p++;
            }
        }
    }
    return nk;
}

/* ======================== 业务 ======================== */
static void call_api(const char *api_route, const char *plain_join, const char *sign_src, int do_heart);

static void maybe_heartbeat(const char *token) {
    char route[128], join[512], src[1024];
    long ts;
    if (!token || !token[0]) return;
    ts = (long)time(NULL);
    snprintf(route, sizeof(route), "License/heartBeat/%s", g_app);
    snprintf(join, sizeof(join), "%s|%s|%ld", token, g_dev, ts);
    snprintf(src, sizeof(src), "%s%s%s%ld%s", route, token, g_dev, ts, g_key);
    call_api(route, join, src, 0);
}

static void call_api(const char *api_route, const char *plain_join, const char *sign_src, int do_heart) {
    char sign[33], path[256], body[4096], resp[16384];
    char *data = NULL, *edata = NULL, *js;
    char token[128] = {0};
    md5_str(sign_src, sign);
    snprintf(path, sizeof(path), "/v3/%s", api_route);
    if (g_aes) {
        data = aes_b64(plain_join);
        if (!data) {
            fprintf(stderr, "AES 加密失败\n");
            return;
        }
        edata = urlenc(data);
        snprintf(body, sizeof(body), "data=%s&sign=%s&encoding=utf-8", edata, sign);
    } else {
        char *eauth = urlenc(g_auth), *edev = urlenc(g_dev), *epass = urlenc(g_pass);
        const char *ts = strrchr(plain_join, '|');
        ts = ts ? ts + 1 : "0";
        if (strstr(api_route, "heartBeat")) {
            char tok[160] = {0};
            const char *bar = strchr(plain_join, '|');
            size_t n = bar ? (size_t)(bar - plain_join) : 0;
            if (n >= sizeof(tok)) n = sizeof(tok) - 1;
            memcpy(tok, plain_join, n);
            tok[n] = 0;
            snprintf(body, sizeof(body), "token=%s&deviceId=%s&timeStamp=%s&sign=%s&encoding=utf-8", tok, edev, ts,
                     sign);
        } else {
            snprintf(body, sizeof(body),
                     "mode=%s&authCode=%s&password=%s&deviceId=%s&timeStamp=%s&sign=%s&encoding=utf-8", g_mode, eauth,
                     epass, edev, ts, sign);
        }
        free(eauth);
        free(edev);
        free(epass);
    }
    printf("\n==== POST %s ====\n", path);
    printf("mode   = %s   （signKey 只本地算 MD5/AES，请求体是 data+sign+encoding）\n", g_aes ? "AES-128-ECB" : "仅签名");
    printf("sign   = %s\n", sign);
    printf("plain  = %s\n", plain_join);
    if (data) printf("data   = %s\n", data);
    else printf("body   = %s\n", body);
    if (http_post(path, body, resp, sizeof(resp)) < 0) {
        free(data);
        free(edata);
        return;
    }
    js = http_body(resp);
    printf("raw    = %s\n", js);
    if (strstr(js, "加解密错误") || strstr(js, "签名错误")) {
        printf("提示: -K 必须等于项目设置里的签名密钥（当前 -K %s）。密钥不进请求体。\n", g_key);
    }
    {
        char keys[16][32];
        char concat[2048];
        char got_sign[80];
        char code_plain[32] = {0};
        int nk, i;
        concat[0] = 0;
        got_sign[0] = 0;
        json_get(js, "sign", got_sign, sizeof(got_sign));
        nk = collect_keys(js, keys, 16);
        printf("---- 解密 ----\n");
        for (i = 0; i < nk; i++) {
            char val[1024], plain[1024];
            val[0] = 0;
            if (json_get(js, keys[i], val, sizeof(val)) != 0) continue;
            if (looks_cipher(val) && aes_unb64(val, plain, sizeof(plain)) == 0) {
                printf("  %-10s = %s\n", keys[i], plain);
                strncat(concat, plain, sizeof(concat) - strlen(concat) - 1);
                if (strcmp(keys[i], "token") == 0) snprintf(token, sizeof(token), "%s", plain);
                if (strcmp(keys[i], "code") == 0) snprintf(code_plain, sizeof(code_plain), "%s", plain);
            } else {
                printf("  %-10s = %s\n", keys[i], val);
                strncat(concat, val, sizeof(concat) - strlen(concat) - 1);
                if (strcmp(keys[i], "token") == 0) snprintf(token, sizeof(token), "%s", val);
                if (strcmp(keys[i], "code") == 0) snprintf(code_plain, sizeof(code_plain), "%s", val);
            }
        }
        if (!g_aes && strcmp(code_plain, "203") == 0) {
            printf("提示: 科御 AES 项目要求发送 data 密文，自动改走 -e aes …\n");
            g_aes = 1;
            free(data);
            free(edata);
            call_api(api_route, plain_join, sign_src, do_heart);
            return;
        }
        if (got_sign[0]) {
            char expect[33], raw[4096];
            snprintf(raw, sizeof(raw), "%s%s", concat, g_key);
            md5_str(raw, expect);
            printf("sign     = %s  %s\n", got_sign, strcmp(expect, got_sign) == 0 ? "[校验通过]" : "[校验失败]");
            if (strcmp(expect, got_sign) != 0) printf("expect   = %s\nconcat   = %s\n", expect, concat);
        }
        if (do_heart && token[0]) maybe_heartbeat(token);
    }
    free(data);
    free(edata);
}

static int self_test(void) {
    const char *plain = "0|MYo1VJWrGdWm||SNTEST00000001|1787577527";
    const char *want = "Z6InylZv2B29yhi1OjTVV+5W6G27q2o5BhTy+OAN3I7xQ6G/LF4bKswpr7ejENVv";
    char *got, back[256], hex[33];
    int ok = 1;
    strncpy(g_key, "gngkcyuBkc56xMmM", sizeof(g_key) - 1);
    got = aes_b64(plain);
    printf("AES 官方向量: %s\n", got && strcmp(got, want) == 0 ? "通过" : "失败");
    if (!got || strcmp(got, want) != 0) {
        printf("  got  %s\n  want %s\n", got ? got : "(null)", want);
        ok = 0;
    }
    if (!got || aes_unb64(got, back, sizeof(back)) != 0 || strcmp(back, plain) != 0) {
        printf("AES 往返失败\n");
        ok = 0;
    } else
        printf("AES 往返: 通过\n");
    md5_str("abc", hex);
    printf("MD5(\"abc\") = %s  %s\n", hex, strcmp(hex, "900150983cd24fb0d6963f7d28e17f72") == 0 ? "通过" : "失败");
    if (strcmp(hex, "900150983cd24fb0d6963f7d28e17f72") != 0) ok = 0;
    free(got);
    return ok ? 0 : 1;
}

static void usage(const char *a) {
    fprintf(stderr,
            "用法: %s [选项]\n"
            "  -H host      服务器 (默认 %s)\n"
            "  -P port      端口   (默认 %d；-P 80 走 HTTP，-P 443/-s 走 HTTPS)\n"
            "  -s           强制 HTTPS:443（grok.me 需要；api.keyunet.cn 用 80）\n"
            "  -e mode      aes=科御 AES data 打包（默认） / sign=仅签名明文\n"
            "  -A appId     项目ID (默认 %s)\n"
            "  -K key       签名密钥（只本地算 sign/AES，不发送）\n"
            "  -C authCode  卡密\n"
            "  -w password  账号模式密码，卡密模式留空\n"
            "  -m mode      0 卡密 / 1 计次 / 2 账号\n"
            "  -D deviceId  设备ID，5-64 位字母数字（不能有连字符）\n"
            "  -t           只跑 MD5/AES 自检，不联网\n",
            a, g_host, g_port, g_app);
}

int main(int argc, char **argv) {
    int opt;
    char route[128], join[512], src[1024];
    long ts;
    while ((opt = getopt(argc, argv, "H:P:A:K:C:w:m:D:e:sth")) != -1) {
        switch (opt) {
        case 'H':
            strncpy(g_host, optarg, sizeof(g_host) - 1);
            break;
        case 'P':
            g_port = atoi(optarg);
            if (g_port == 80) g_tls = 0;
            if (g_port == 443) g_tls = 1;
            break;
        case 's':
            g_tls = 1;
            if (g_port == 80) g_port = 443;
            break;
        case 'e':
            g_aes = !(strcmp(optarg, "sign") == 0 || strcmp(optarg, "SIGN") == 0);
            break;
        case 'A':
            strncpy(g_app, optarg, sizeof(g_app) - 1);
            break;
        case 'K':
            strncpy(g_key, optarg, sizeof(g_key) - 1);
            break;
        case 'C':
            strncpy(g_auth, optarg, sizeof(g_auth) - 1);
            break;
        case 'w':
            strncpy(g_pass, optarg, sizeof(g_pass) - 1);
            break;
        case 'm':
            strncpy(g_mode, optarg, sizeof(g_mode) - 1);
            break;
        case 'D':
            strncpy(g_dev, optarg, sizeof(g_dev) - 1);
            break;
        case 't':
            return self_test();
        default:
            usage(argv[0]);
            return 1;
        }
    }
    if (g_port == 443) g_tls = 1;
    ts = (long)time(NULL);
    snprintf(route, sizeof(route), "License/verify/%s", g_app);
    snprintf(join, sizeof(join), "%s|%s|%s|%s|%ld", g_mode, g_auth, g_pass, g_dev, ts);
    snprintf(src, sizeof(src), "%s%s%s%s%s%ld%s", route, g_mode, g_auth, g_pass, g_dev, ts, g_key);
    printf("host=%s:%d %s appId=%s key=%s device=%s\n", g_host, g_port, g_tls ? "HTTPS" : "HTTP", g_app, g_key, g_dev);
    call_api(route, join, src, 1);
    return 0;
}
