POSA_LEAKSMS/firmware/common/jsonbody.c
유창욱 90f121e14c chore: import codebase with security hardening
SHT30 온습도 모니터링 시스템 전체 소스(서버 PHP, STM32 펌웨어, SQL, 테스트).
전체 코드리뷰에서 도출된 보안 하드닝 10건 반영:
- 요청 서명 HMAC-SHA256 전환(펌웨어 sig.c/서버 config.php/호스트 패리티 동시)
- 재전송 방어 + 기본 API_KEY fail-closed + 디바이스 문자열 정제(api/sensor_data.php)
- 오프라인 SMS 중복 발송 경합 제거(cron_heartbeat.php, 원자적 선점)
- CSV 수식 주입 방지(monthly_report.php), 감사로그 회전 락(retention_cleanup.php)
- 브루트포스 카운터 원자화(login.php), 예시 TOTP 비밀키 무효화, 마이그레이션 멱등화

_backup/(하드코딩 실 비밀값 포함)·config.local.php·런타임 상태는 .gitignore 제외.
2026-06-20 09:37:40 +09:00

149 lines
4.8 KiB
C

/* =============================================================================
* jsonbody.c - 요청 본문 생성 (경계 검사 포함)
* ===========================================================================*/
#include "jsonbody.h"
#include <string.h>
/* ── 경계 검사 어펜더 ───────────────────────────────────────────────────── */
typedef struct {
char *p;
size_t cap; /* NUL 자리 포함 전체 버퍼 크기 */
size_t len; /* 현재 길이(NUL 제외) */
int ok; /* 0 이면 오버플로 발생 */
} jb_buf;
static void jb_init(jb_buf *b, char *out, size_t cap)
{
b->p = out;
b->cap = cap;
b->len = 0;
b->ok = (cap > 0);
if (b->ok) b->p[0] = '\0';
}
static void jb_putc(jb_buf *b, char c)
{
if (!b->ok) return;
if (b->len + 1 >= b->cap) { b->ok = 0; return; } /* +1: NUL 자리 보존 */
b->p[b->len++] = c;
b->p[b->len] = '\0';
}
static void jb_raw(jb_buf *b, const char *s)
{
while (*s) jb_putc(b, *s++);
}
/* JSON 문자열 값(따옴표 없이) 이스케이프하여 추가.
* ASCII 제어문자(<0x20)는 \u00XX, " 와 \ 는 백슬래시 이스케이프,
* 그 외(멀티바이트 UTF-8 포함)는 그대로. '/' 는 이스케이프하지 않는다. */
static void jb_estr(jb_buf *b, const char *s)
{
static const char hexd[] = "0123456789abcdef";
const unsigned char *u = (const unsigned char *)s;
while (*u) {
unsigned char c = *u++;
if (c == '"') { jb_putc(b, '\\'); jb_putc(b, '"'); }
else if (c == '\\') { jb_putc(b, '\\'); jb_putc(b, '\\'); }
else if (c < 0x20) {
jb_putc(b, '\\'); jb_putc(b, 'u');
jb_putc(b, '0'); jb_putc(b, '0');
jb_putc(b, hexd[(c >> 4) & 0x0F]);
jb_putc(b, hexd[c & 0x0F]);
} else {
jb_putc(b, (char)c);
}
}
}
/* 부호 없는 정수 추가 */
static void jb_u32(jb_buf *b, uint32_t v)
{
char tmp[10];
int n = 0;
if (v == 0) { jb_putc(b, '0'); return; }
while (v > 0 && n < (int)sizeof(tmp)) { tmp[n++] = (char)('0' + (v % 10)); v /= 10; }
while (n > 0) jb_putc(b, tmp[--n]);
}
/* 부호 있는 정수 추가 */
static void jb_i32(jb_buf *b, int v)
{
if (v < 0) { jb_putc(b, '-'); jb_u32(b, (uint32_t)(-(long)v)); }
else jb_u32(b, (uint32_t)v);
}
/* 소수 2자리 고정 포맷 (locale·newlib float-printf 비의존, 결정적 반올림).
* 예: 24.0 -> "24.00", 48.5 -> "48.50", -3.245 -> "-3.25"
* Python 레퍼런스(reference.py)와 바이트 동일해야 한다. */
static void jb_fixed2(jb_buf *b, double v)
{
int neg = 0;
if (v < 0) { neg = 1; v = -v; }
/* 반올림하여 1/100 단위 정수로 (round half up) */
long long scaled = (long long)(v * 100.0 + 0.5);
long long ip = scaled / 100;
int fp = (int)(scaled % 100);
if (neg && scaled != 0) jb_putc(b, '-');
/* 정수부 */
if (ip == 0) {
jb_putc(b, '0');
} else {
char tmp[20];
int n = 0;
while (ip > 0 && n < (int)sizeof(tmp)) { tmp[n++] = (char)('0' + (ip % 10)); ip /= 10; }
while (n > 0) jb_putc(b, tmp[--n]);
}
/* 소수부 (항상 2자리) */
jb_putc(b, '.');
jb_putc(b, (char)('0' + (fp / 10)));
jb_putc(b, (char)('0' + (fp % 10)));
}
/* 키 헬퍼: ,"key": */
static void jb_key(jb_buf *b, int first, const char *key)
{
if (!first) jb_putc(b, ',');
jb_putc(b, '"');
jb_raw(b, key);
jb_putc(b, '"');
jb_putc(b, ':');
}
static void jb_str_field(jb_buf *b, int first, const char *key, const char *val)
{
jb_key(b, first, key);
jb_putc(b, '"');
jb_estr(b, val);
jb_putc(b, '"');
}
int jb_sht30_event(char *out, size_t cap,
const char *device_id, const char *device_location,
int sensor_id, const char *sensor_name,
const char *event_type, uint32_t timestamp,
double temperature_c, double humidity_percent,
const char *metric_status, const char *app_version)
{
jb_buf b;
jb_init(&b, out, cap);
jb_putc(&b, '{');
jb_str_field(&b, 1, "device_id", device_id);
jb_str_field(&b, 0, "device_location", device_location);
jb_key(&b, 0, "sensor_id"); jb_i32(&b, sensor_id);
jb_str_field(&b, 0, "sensor_name", sensor_name);
jb_str_field(&b, 0, "event_type", event_type);
jb_key(&b, 0, "timestamp"); jb_u32(&b, timestamp);
jb_str_field(&b, 0, "metric_type", "sht30");
jb_key(&b, 0, "temperature_c"); jb_fixed2(&b, temperature_c);
jb_key(&b, 0, "humidity_percent"); jb_fixed2(&b, humidity_percent);
jb_str_field(&b, 0, "metric_status", metric_status);
jb_str_field(&b, 0, "app_version", app_version);
jb_putc(&b, '}');
return b.ok ? (int)b.len : -1;
}