Files
MeowOS-For-R3P2/patch_v2/meowpanel/src/mp_auth.c
T

427 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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