/* mp_auth.c — 访问级 PIN 鉴权(panel.pin)+ 内联 SHA-256 + 内存会话。 */ #include "mp.h" time_t g_auth_ttl = AUTH_TOKEN_TTL; #define PIN_MAX_FAILS 5 /* 连续错 PIN 锁定阈值 */ #define PIN_LOCK_SEC 300 /* 锁定 5 分钟 */ static const uint32_t mp_sha256_k[64] = { 0x428a2f98u,0x71374491u,0xb5c0fbcfu,0xe9b5dba5u,0x3956c25bu,0x59f111f1u,0x923f82a4u,0xab1c5ed5u, 0xd807aa98u,0x12835b01u,0x243185beu,0x550c7dc3u,0x72be5d74u,0x80deb1feu,0x9bdc06a7u,0xc19bf174u, 0xe49b69c1u,0xefbe4786u,0x0fc19dc6u,0x240ca1ccu,0x2de92c6fu,0x4a7484aau,0x5cb0a9dcu,0x76f988dau, 0x983e5152u,0xa831c66du,0xb00327c8u,0xbf597fc7u,0xc6e00bf3u,0xd5a79147u,0x06ca6351u,0x14292967u, 0x27b70a85u,0x2e1b2138u,0x4d2c6dfcu,0x53380d13u,0x650a7354u,0x766a0abbu,0x81c2c92eu,0x92722c85u, 0xa2bfe8a1u,0xa81a664bu,0xc24b8b70u,0xc76c51a3u,0xd192e819u,0xd6990624u,0xf40e3585u,0x106aa070u, 0x19a4c116u,0x1e376c08u,0x2748774cu,0x34b0bcb5u,0x391c0cb3u,0x4ed8aa4au,0x5b9cca4fu,0x682e6ff3u, 0x748f82eeu,0x78a5636fu,0x84c87814u,0x8cc70208u,0x90befffau,0xa4506cebu,0xbef9a3f7u,0xc67178f2u }; static int g_pin_fails = 0; static time_t g_pin_locked_until = 0; static auth_sess_t g_sess[AUTH_MAX_SESS]; static int g_sess_n = 0; #define MP_ROR(x,n) (((x) >> (n)) | ((x) << (32 - (n)))) #define MP_CH(x,y,z) (((x) & (y)) ^ (~(x) & (z))) #define MP_MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) #define MP_BSIG0(x) (MP_ROR(x,2) ^ MP_ROR(x,13) ^ MP_ROR(x,22)) #define MP_BSIG1(x) (MP_ROR(x,6) ^ MP_ROR(x,11) ^ MP_ROR(x,25)) #define MP_SSIG0(x) (MP_ROR(x,7) ^ MP_ROR(x,18) ^ ((x) >> 3)) #define MP_SSIG1(x) (MP_ROR(x,17) ^ MP_ROR(x,19) ^ ((x) >> 10)) void mp_sha256_init(mp_sha256_ctx *c) { c->state[0] = 0x6a09e667u; c->state[1] = 0xbb67ae85u; c->state[2] = 0x3c6ef372u; c->state[3] = 0xa54ff53au; c->state[4] = 0x510e527fu; c->state[5] = 0x9b05688cu; c->state[6] = 0x1f83d9abu; c->state[7] = 0x5be0cd19u; c->count = 0; c->buflen = 0; } void mp_sha256_block(mp_sha256_ctx *c, const uint8_t *p) { uint32_t w[64]; int i; for (i = 0; i < 16; i++) w[i] = ((uint32_t)p[i*4] << 24) | ((uint32_t)p[i*4+1] << 16) | ((uint32_t)p[i*4+2] << 8) | (uint32_t)p[i*4+3]; for (i = 16; i < 64; i++) w[i] = MP_SSIG1(w[i-2]) + w[i-7] + MP_SSIG0(w[i-15]) + w[i-16]; uint32_t a = c->state[0], b = c->state[1], cc = c->state[2], d = c->state[3]; uint32_t e = c->state[4], f = c->state[5], g = c->state[6], h = c->state[7]; for (i = 0; i < 64; i++) { uint32_t t1 = h + MP_BSIG1(e) + MP_CH(e,f,g) + mp_sha256_k[i] + w[i]; uint32_t t2 = MP_BSIG0(a) + MP_MAJ(a,b,cc); h = g; g = f; f = e; e = d + t1; d = cc; cc = b; b = a; a = t1 + t2; } c->state[0] += a; c->state[1] += b; c->state[2] += cc; c->state[3] += d; c->state[4] += e; c->state[5] += f; c->state[6] += g; c->state[7] += h; } void mp_sha256_update(mp_sha256_ctx *c, const uint8_t *data, size_t len) { c->count += (uint64_t)len; if (c->buflen) { size_t need = 64 - c->buflen; size_t take = len < need ? len : need; memcpy(c->buf + c->buflen, data, take); c->buflen += take; data += take; len -= take; if (c->buflen == 64) { mp_sha256_block(c, c->buf); c->buflen = 0; } } while (len >= 64) { mp_sha256_block(c, data); data += 64; len -= 64; } if (len) { memcpy(c->buf, data, len); c->buflen = len; } } void mp_sha256_final(mp_sha256_ctx *c, uint8_t out[32]) { uint64_t bitlen = c->count * 8; uint8_t *b = c->buf; size_t bl = c->buflen; int i; b[bl++] = 0x80; if (bl > 56) { while (bl < 64) b[bl++] = 0; mp_sha256_block(c, b); bl = 0; } while (bl < 56) b[bl++] = 0; for (i = 0; i < 8; i++) b[bl++] = (uint8_t)(bitlen >> (56 - i * 8)); mp_sha256_block(c, b); for (i = 0; i < 8; i++) { out[i*4] = (uint8_t)(c->state[i] >> 24); out[i*4+1] = (uint8_t)(c->state[i] >> 16); out[i*4+2] = (uint8_t)(c->state[i] >> 8); out[i*4+3] = (uint8_t)(c->state[i]); } } int auth_read_random(unsigned char *buf, size_t n) { int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) return -1; size_t got = 0; while (got < n) { ssize_t rd = read(fd, buf + got, n - got); if (rd <= 0) { close(fd); return -1; } got += (size_t)rd; } close(fd); return 0; } void auth_hex(const unsigned char *raw, size_t n, char *out) { static const char hex[] = "0123456789abcdef"; for (size_t i = 0; i < n; i++) { out[i*2] = hex[raw[i] >> 4]; out[i*2+1] = hex[raw[i] & 0xf]; } out[n*2] = 0; } int auth_token_gen(char *out, int outcap) /* 16B → 32 hex(128bit) */ { unsigned char raw[AUTH_TOKEN_BYTES]; if (outcap < AUTH_TOKEN_BYTES * 2 + 1) return -1; if (auth_read_random(raw, sizeof raw) < 0) return -1; auth_hex(raw, sizeof raw, out); return 0; } int auth_make_salt(char *out, int outcap) /* 16B → 32 hex 盐 */ { unsigned char raw[16]; if (outcap < 33) return -1; if (auth_read_random(raw, sizeof raw) < 0) return -1; auth_hex(raw, sizeof raw, out); return 0; } int auth_pass_hash(const char *salt, const char *password, char *out, int outcap) { mp_sha256_ctx c; uint8_t digest[32]; if (outcap < AUTH_HASH_LEN + 1) return -1; mp_sha256_init(&c); mp_sha256_update(&c, (const uint8_t *)salt, strlen(salt)); mp_sha256_update(&c, (const uint8_t *)password, strlen(password)); mp_sha256_final(&c, digest); auth_hex(digest, sizeof digest, out); return 0; } int auth_make_hash(const char *password, char *out, int outcap) /* salt:hex */ { char salt[33], h[AUTH_HASH_LEN + 1]; if (auth_make_salt(salt, sizeof salt) < 0) return -1; if (auth_pass_hash(salt, password, h, sizeof h) < 0) return -1; snprintf(out, outcap, "%s:%s", salt, h); return 0; } int hash_matches(const char *stored, const char *password) { if (!stored || !stored[0] || !password) return 0; char tmp[160]; snprintf(tmp, sizeof tmp, "%s", stored); char *colon = strchr(tmp, ':'); if (!colon) return 0; *colon = 0; char want[AUTH_HASH_LEN + 1]; if (auth_pass_hash(tmp, password, want, sizeof want) != 0) return 0; if (strlen(colon + 1) != AUTH_HASH_LEN) return 0; return memcmp(want, colon + 1, AUTH_HASH_LEN) == 0; } int pin_set(void) /* panel.pin 是否已设(访问屏障开启) */ { char *v = settings_get_value("panel.pin"); int s = (v && v[0]) ? 1 : 0; free(v); return s; } int auth_pass_set(void) /* 旧密码是否已设(兼容迁移用) */ { char *v = settings_get_value("panel.auth_pass"); int s = (v && v[0]) ? 1 : 0; free(v); return s; } int auth_verify(const char *password) /* 旧密码校验(迁移期;见 login) */ { char *stored = settings_get_value("panel.auth_pass"); int ok = hash_matches(stored, password); free(stored); return ok; } int pin_verify(const char *pin) /* PIN 校验 */ { char *stored = settings_get_value("panel.pin"); int ok = hash_matches(stored, pin); free(stored); return ok; } int pin_validate(const char *pin) { if (!pin) return 0; size_t l = strlen(pin); if (l != 4) return 0; for (size_t i = 0; i < l; i++) if (pin[i] < '0' || pin[i] > '9') return 0; return 1; } long long pin_lock_remaining(void) /* 剩余锁定秒;0=未锁 */ { time_t now = time(NULL); if (g_pin_locked_until <= now) return 0; return (long long)(g_pin_locked_until - now); } void pin_lock_reset(void) { g_pin_fails = 0; g_pin_locked_until = 0; } void pin_record_fail(void) { if (++g_pin_fails >= PIN_MAX_FAILS) { g_pin_locked_until = time(NULL) + PIN_LOCK_SEC; g_pin_fails = 0; } } int sess_find(const char *token) { for (int i = 0; i < g_sess_n; i++) if (!strcmp(g_sess[i].token, token)) return i; return -1; } int sess_valid(const char *token) { int i = sess_find(token); if (i < 0) return 0; if (g_sess[i].expires > 0 && time(NULL) >= g_sess[i].expires) return 0; return 1; } void sess_add(const char *token, time_t expires) { int i = sess_find(token); if (i >= 0) { g_sess[i].expires = expires; return; } if (g_sess_n >= AUTH_MAX_SESS) { /* 满:淘汰最早过期的一条 */ int e = 0; for (int k = 1; k < g_sess_n; k++) if (g_sess[k].expires < g_sess[e].expires) e = k; if (e < g_sess_n - 1) memmove(&g_sess[e], &g_sess[e + 1], (size_t)(g_sess_n - 1 - e) * sizeof(auth_sess_t)); g_sess_n--; } snprintf(g_sess[g_sess_n].token, sizeof g_sess[g_sess_n].token, "%s", token); g_sess[g_sess_n].expires = expires; g_sess_n++; } void sess_remove(const char *token) { int i = sess_find(token); if (i < 0) return; if (i < g_sess_n - 1) memmove(&g_sess[i], &g_sess[i + 1], (size_t)(g_sess_n - 1 - i) * sizeof(auth_sess_t)); g_sess_n--; } void auth_cp(char *dst, int dcap, const char *src) /* 有界拷贝(防 -Wformat-truncation) */ { size_t l = strlen(src); if (l >= (size_t)dcap) l = (size_t)dcap - 1; memcpy(dst, src, l); dst[l] = 0; } int extract_token(const req_t *r, char *out, int outcap) { out[0] = 0; if (r->x_meow_token[0]) { auth_cp(out, outcap, r->x_meow_token); return out[0] ? 1 : 0; } if (!strncasecmp(r->authorization, "Bearer ", 7)) { auth_cp(out, outcap, r->authorization + 7); return out[0] ? 1 : 0; } if (r->cookie[0]) { const char *p = strstr(r->cookie, "meow_token="); if (p) { p += 11; const char *e = p; while (*e && *e != ';' && *e != ' ' && *e != '\t') e++; size_t l = (size_t)(e - p); if (l >= (size_t)outcap) l = (size_t)outcap - 1; memcpy(out, p, l); out[l] = 0; return out[0] ? 1 : 0; } } return 0; } int require_auth(int sock, const req_t *r) { const char *p = r->path; if (!strcmp(p, "/api/auth/login") || !strcmp(p, "/api/auth/logout") || !strcmp(p, "/api/auth/status") || !strcmp(p, "/api/auth/setup")) return 1; char t[65]; if (extract_token(r, t, sizeof t) && t[0] && sess_valid(t)) return 1; return send_err(sock, 401, "UNAUTHORIZED", "未认证或会话已失效"); } int api_auth_status(int sock, const req_t *r) { int ps = pin_set(); int lg = auth_pass_set(); int sess = 0; char t[65]; if (extract_token(r, t, sizeof t) && t[0]) sess = sess_valid(t); long long rem = pin_lock_remaining(); sbuf_t s = {0}; sA(&s, "{\"auth_enabled\":true,\"session\":%s,\"pin_set\":%s,\"legacy\":%s,\"locked\":%s", sess ? "true" : "false", ps ? "true" : "false", lg ? "true" : "false", rem > 0 ? "true" : "false"); if (rem > 0) sA(&s, ",\"lock_remaining_ms\":%lld", rem * 1000LL); sA(&s, "}"); int rc = send_json(sock, 200, s.b ? s.b : "{}"); sbuf_free(&s); return rc; } int api_auth_login(int sock, const req_t *r) { if (!r->body || !r->body[0]) return send_err(sock, 400, "BAD_REQUEST", "缺少 PIN"); jv_t *root = jv_parse(r->body, (int)r->body_len); if (!root) return send_err(sock, 400, "BAD_REQUEST", "body 非合法 JSON"); jv_t *jpin = jv_obj_get(root, "pin"); jv_t *jpw = jv_obj_get(root, "password"); char pin[8], pw[256]; if (jpin && jpin->type == J_STR && jpin->str) snprintf(pin, sizeof pin, "%s", jpin->str); else pin[0] = 0; if (jpw && jpw->type == J_STR && jpw->str) snprintf(pw, sizeof pw, "%s", jpw->str); else pw[0] = 0; jv_free(root); if (pin_lock_remaining() > 0) { /* 已在锁定期 */ sbuf_t e = {0}; sA(&e, "{\"ok\":false,\"error\":{\"code\":\"LOCKED\",\"message\":\"已锁定,请稍后重试\"},\"lock_remaining_ms\":%lld}", pin_lock_remaining() * 1000LL); int rc = send_json(sock, 429, e.b ? e.b : ""); sbuf_free(&e); return rc; } int use_legacy = 0; const char *cred = NULL; if (pin_set()) { cred = pin; if (!cred || !cred[0]) return send_err(sock, 400, "BAD_REQUEST", "缺少 pin 字段"); } else if (auth_pass_set()) { use_legacy = 1; cred = pw; if (!cred || !cred[0]) return send_err(sock, 400, "BAD_REQUEST", "缺少 password 字段"); } else return send_err(sock, 400, "BAD_REQUEST", "请先设置面板 PIN(/api/auth/setup)"); int ok = use_legacy ? auth_verify(cred) : pin_verify(cred); if (!ok) { pin_record_fail(); if (pin_lock_remaining() > 0) { /* 本错已触发锁定 */ sbuf_t e = {0}; sA(&e, "{\"ok\":false,\"error\":{\"code\":\"LOCKED\",\"message\":\"连续错误次数过多,已锁定 5 分钟\"},\"lock_remaining_ms\":%lld}", pin_lock_remaining() * 1000LL); int rc = send_json(sock, 429, e.b ? e.b : ""); sbuf_free(&e); return rc; } return send_err(sock, 401, "UNAUTHORIZED", "PIN 错误"); } pin_lock_reset(); char token[65]; if (auth_token_gen(token, sizeof token) < 0) return send_err(sock, 500, "INTERNAL", "生成会话失败"); time_t exp = time(NULL) + g_auth_ttl; /* 固定 3 天(MEOW_AUTH_TTL 可覆盖测试用),不滑动续期 */ sess_add(token, exp); sbuf_t s = {0}; sA(&s, "{\"token\":"); sA_str(&s, token); sA(&s, ",\"expires_at\":%lld,\"auth_enabled\":true}", (long long)exp); int rc = send_json(sock, 200, s.b ? s.b : "{}"); sbuf_free(&s); return rc; } int api_auth_setup(int sock, const req_t *r) { if (pin_set()) return send_err(sock, 409, "CONFLICT", "面板 PIN 已设置,请直接登录"); if (!r->body || !r->body[0]) return send_err(sock, 400, "BAD_REQUEST", "缺少 PIN"); jv_t *root = jv_parse(r->body, (int)r->body_len); if (!root) return send_err(sock, 400, "BAD_REQUEST", "body 非合法 JSON"); jv_t *jpin = jv_obj_get(root, "pin"); char pin[8]; if (jpin && jpin->type == J_STR && jpin->str) snprintf(pin, sizeof pin, "%s", jpin->str); else pin[0] = 0; jv_free(root); if (!pin_validate(pin)) return send_err(sock, 422, "UNSUPPORTED", "PIN 必须为 4 位数字"); char h[200]; if (auth_make_hash(pin, h, sizeof h) < 0) return send_err(sock, 500, "INTERNAL", "生成 PIN 哈希失败"); if (settings_write_one("panel.pin", h) < 0) return send_err(sock, 500, "INTERNAL", "写设置失败"); pin_lock_reset(); return send_json(sock, 200, "{\"pin_set\":true}"); } int api_auth_logout(int sock, const req_t *r) { char t[65]; if (extract_token(r, t, sizeof t) && t[0]) sess_remove(t); return send_json(sock, 200, "{\"logged_out\":true}"); } #undef MP_ROR #undef MP_CH #undef MP_MAJ #undef MP_BSIG0 #undef MP_BSIG1 #undef MP_SSIG0 #undef MP_SSIG1