web_server.c 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267
  1. #include "web_server.h"
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <errno.h>
  7. #include <pthread.h>
  8. #include <time.h>
  9. #include <sys/socket.h>
  10. #include <netinet/in.h>
  11. #include <arpa/inet.h>
  12. #include <ifaddrs.h>
  13. #include <net/if.h>
  14. #define BUF_SIZE 8192
  15. #define MAX_CAMS 8
  16. static const char *g_config_path;
  17. static const char *g_password;
  18. static int g_port;
  19. /* Optional NIC whitelist ([web] ifaces = eth0 eth1 in channels.conf).
  20. * Empty = show/configure every non-loopback interface. */
  21. static char g_iface_wl[8][IFNAMSIZ];
  22. static int g_iface_wn = 0;
  23. static int iface_allowed(const char *name);
  24. /* ---- MD5-like simple hash for session cookie (not crypto-secure, fine for LAN) ---- */
  25. static unsigned simple_hash(const char *s) {
  26. unsigned h = 0x811c9dc5;
  27. for (; *s; s++) {
  28. h ^= (unsigned char)*s;
  29. h *= 0x01000193;
  30. }
  31. return h;
  32. }
  33. /* ---- HTTP helpers ---- */
  34. static void send_str(int fd, const char *s) {
  35. size_t len = strlen(s);
  36. size_t sent = 0;
  37. while (sent < len) {
  38. ssize_t r = write(fd, s + sent, len - sent);
  39. if (r <= 0) return;
  40. sent += r;
  41. }
  42. }
  43. static void send_bytes(int fd, const char *buf, size_t len) {
  44. size_t sent = 0;
  45. while (sent < len) {
  46. ssize_t r = write(fd, buf + sent, len - sent);
  47. if (r <= 0) return;
  48. sent += r;
  49. }
  50. }
  51. static int recv_line(int fd, char *buf, int maxlen) {
  52. int n = 0;
  53. while (n < maxlen - 1) {
  54. char c;
  55. ssize_t r = read(fd, &c, 1);
  56. if (r <= 0) return -1;
  57. buf[n++] = c;
  58. if (c == '\n') break;
  59. }
  60. buf[n] = '\0';
  61. return n;
  62. }
  63. /* ---- URL decode ---- */
  64. static void url_decode(char *dst, size_t dstsz, const char *src) {
  65. size_t i = 0;
  66. while (*src && i < dstsz - 1) {
  67. if (*src == '%' && src[1] && src[2]) {
  68. char hex[3] = {src[1], src[2], 0};
  69. dst[i++] = (char)strtol(hex, NULL, 16);
  70. src += 3;
  71. } else if (*src == '+') {
  72. dst[i++] = ' ';
  73. src++;
  74. } else {
  75. dst[i++] = *src++;
  76. }
  77. }
  78. dst[i] = '\0';
  79. }
  80. /* ---- Extract value from POST body key=value&key2=value2 ---- */
  81. static int post_value(const char *body, const char *key, char *val, size_t valsz) {
  82. char pattern[64];
  83. snprintf(pattern, sizeof(pattern), "%s=", key);
  84. const char *p = strstr(body, pattern);
  85. if (!p) return -1;
  86. p += strlen(pattern);
  87. size_t i = 0;
  88. while (*p && *p != '&' && i < valsz - 1) val[i++] = *p++;
  89. val[i] = '\0';
  90. url_decode(val, valsz, val);
  91. return 0;
  92. }
  93. /* ---- Local network info (all interfaces / segments) ---- */
  94. struct net_if { char iface[IFNAMSIZ]; char ip[64]; char mask[64]; };
  95. static int collect_nets(struct net_if *out, int maxn) {
  96. struct ifaddrs *ifa0 = NULL;
  97. if (getifaddrs(&ifa0) != 0) return 0;
  98. int n = 0;
  99. for (struct ifaddrs *ifp = ifa0; ifp && n < maxn; ifp = ifp->ifa_next) {
  100. if (!ifp->ifa_addr || ifp->ifa_addr->sa_family != AF_INET) continue;
  101. if (strcmp(ifp->ifa_name, "lo") == 0) continue;
  102. if (!iface_allowed(ifp->ifa_name)) continue;
  103. struct sockaddr_in *a = (struct sockaddr_in *)ifp->ifa_addr;
  104. struct sockaddr_in *m = (struct sockaddr_in *)ifp->ifa_netmask;
  105. snprintf(out[n].iface, sizeof(out[n].iface), "%s", ifp->ifa_name);
  106. if (!inet_ntop(AF_INET, &a->sin_addr, out[n].ip, sizeof(out[n].ip))) continue;
  107. if (m) inet_ntop(AF_INET, &m->sin_addr, out[n].mask, sizeof(out[n].mask));
  108. else snprintf(out[n].mask, sizeof(out[n].mask), "255.255.255.0");
  109. n++;
  110. }
  111. freeifaddrs(ifa0);
  112. return n;
  113. }
  114. /* All physical interfaces (any family), loopback excluded. Used so the page
  115. * can also configure a NIC that currently has no IPv4 address at all. */
  116. static int collect_ifaces(char (*out)[IFNAMSIZ], int maxn) {
  117. struct ifaddrs *ifa0 = NULL;
  118. if (getifaddrs(&ifa0) != 0) return 0;
  119. int n = 0;
  120. for (struct ifaddrs *ifp = ifa0; ifp && n < maxn; ifp = ifp->ifa_next) {
  121. if (!ifp->ifa_name || strcmp(ifp->ifa_name, "lo") == 0) continue;
  122. if (!iface_allowed(ifp->ifa_name)) continue;
  123. int dup = 0;
  124. for (int i = 0; i < n; i++)
  125. if (strcmp(out[i], ifp->ifa_name) == 0) { dup = 1; break; }
  126. if (dup) continue;
  127. snprintf(out[n], IFNAMSIZ, "%s", ifp->ifa_name);
  128. n++;
  129. }
  130. freeifaddrs(ifa0);
  131. return n;
  132. }
  133. static int is_ipv4(const char *s) {
  134. struct in_addr a;
  135. return s && inet_pton(AF_INET, s, &a) == 1;
  136. }
  137. static int iface_exists(const char *name) {
  138. char tmp[16][IFNAMSIZ];
  139. int n = collect_ifaces(tmp, 16);
  140. for (int i = 0; i < n; i++)
  141. if (strcmp(tmp[i], name) == 0) return 1;
  142. return 0;
  143. }
  144. /* First IPv4 address currently on <iface> (the primary), if any. */
  145. static int primary_of(const char *iface, char *ip, size_t ipsz,
  146. char *mask, size_t masksz) {
  147. struct net_if nets[16];
  148. int n = collect_nets(nets, 16);
  149. for (int i = 0; i < n; i++) {
  150. if (strcmp(nets[i].iface, iface) == 0) {
  151. snprintf(ip, ipsz, "%s", nets[i].ip);
  152. snprintf(mask, masksz, "%s",
  153. (nets[i].mask[0] && strcmp(nets[i].mask, "0.0.0.0") != 0)
  154. ? nets[i].mask : "255.255.255.0");
  155. return 1;
  156. }
  157. }
  158. return 0;
  159. }
  160. /* Safe append that grows the heap buffer (avoids strcat overflow). */
  161. static char *buf_append(char *buf, const char *s) {
  162. size_t a = buf ? strlen(buf) : 0;
  163. size_t b = strlen(s);
  164. char *n = realloc(buf, a + b + 1);
  165. if (!n) return buf;
  166. memcpy(n + a, s, b + 1);
  167. return n;
  168. }
  169. /* Only [0-9A-Za-z./:] allowed: used to build the `ip` command safely. */
  170. static int is_safe_net_arg(const char *s) {
  171. if (!s || !s[0]) return 0;
  172. for (const char *p = s; *p; p++) {
  173. char c = *p;
  174. if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
  175. (c >= 'A' && c <= 'Z') || c == '.' || c == ':' || c == '/'))
  176. return 0;
  177. }
  178. return 1;
  179. }
  180. static int netmask_to_prefix(const char *mask) {
  181. struct in_addr a;
  182. if (!mask || inet_pton(AF_INET, mask, &a) != 1) return 24;
  183. unsigned v = ntohl(a.s_addr);
  184. int p = 0;
  185. for (int b = 31; b >= 0 && (v & (1u << b)); b--) p++;
  186. return p;
  187. }
  188. /* Some vendor rootfs images drive the NIC with NetworkManager (ifupdown is
  189. * installed but no longer manages the device), so /etc/network/interfaces is
  190. * ignored at boot. Detect that case and persist through nmcli instead. */
  191. static int nm_running(void) {
  192. FILE *p = popen("nmcli -t -g RUNNING general 2>/dev/null", "r");
  193. if (!p) return 0;
  194. char b[32] = "";
  195. size_t rd = fread(b, 1, sizeof(b) - 1, p);
  196. b[rd] = '\0';
  197. pclose(p);
  198. return strstr(b, "running") != NULL;
  199. }
  200. static int nm_managed(const char *iface) {
  201. char cmd[128], line[192];
  202. snprintf(cmd, sizeof(cmd),
  203. "nmcli -t -f GENERAL.STATE dev show %s 2>/dev/null", iface);
  204. FILE *p = popen(cmd, "r");
  205. if (!p) return 0;
  206. int managed = 0;
  207. while (fgets(line, sizeof(line), p)) {
  208. if (strstr(line, "unmanaged")) { managed = 0; break; }
  209. if (strstr(line, "GENERAL.STATE:")) managed = 1;
  210. }
  211. pclose(p);
  212. return managed;
  213. }
  214. static int nm_conn_name(const char *iface, char *out, size_t outsz) {
  215. char cmd[128], line[192];
  216. snprintf(cmd, sizeof(cmd),
  217. "nmcli -t -f GENERAL.CONNECTION dev show %s 2>/dev/null", iface);
  218. FILE *p = popen(cmd, "r");
  219. if (!p) return 0;
  220. int got = 0;
  221. while (fgets(line, sizeof(line), p)) {
  222. char *c = strchr(line, ':');
  223. if (!c) continue;
  224. *c = '\0';
  225. char *v = c + 1;
  226. v[strcspn(v, "\r\n")] = '\0';
  227. if (strcmp(line, "GENERAL.CONNECTION") == 0 && v[0]) {
  228. snprintf(out, outsz, "%s", v);
  229. got = 1;
  230. }
  231. }
  232. pclose(p);
  233. return got;
  234. }
  235. static int nm_safe_name(const char *s) {
  236. if (!s || !s[0] || strlen(s) >= 96) return 0;
  237. for (const char *p = s; *p; p++)
  238. if (strchr("'\"`\\$;&|<>()\n\r", *p)) return 0;
  239. return 1;
  240. }
  241. /* Persist an inet-static stanza for <iface> in /etc/network/interfaces.
  242. * Edits the matching block in place; appends one if absent. */
  243. static void strip_up_addr_lines(char *buf, const char *iface);
  244. static int persist_interfaces(const char *iface, const char *ip,
  245. const char *mask, const char *gw) {
  246. const char *path = "/etc/network/interfaces";
  247. long sz = 0;
  248. FILE *fp = fopen(path, "r");
  249. char *buf = NULL;
  250. if (fp) {
  251. fseek(fp, 0, SEEK_END);
  252. sz = ftell(fp);
  253. fseek(fp, 0, SEEK_SET);
  254. buf = malloc(sz + 8192);
  255. if (buf) { size_t rd = fread(buf, 1, sz, fp); buf[rd] = '\0'; }
  256. fclose(fp);
  257. }
  258. if (!buf) {
  259. buf = strdup("# generated by rtsp_multi_view web (no prior file)\n"
  260. "source /etc/network/interfaces.d/*\n\nauto lo\n"
  261. "iface lo inet loopback\n\n");
  262. }
  263. char pat[80];
  264. snprintf(pat, sizeof(pat), "iface %s inet", iface);
  265. char *sec = strstr(buf, pat);
  266. int nwritten = 0;
  267. if (sec) {
  268. /* walk the stanza lines until the next 'iface'/'auto'/'source' */
  269. char *line = sec + strlen(pat);
  270. char *end = strchr(line, '\n');
  271. while (line && *line && nwritten < 3) {
  272. char *next = end ? end + 1 : NULL;
  273. const char *p = line;
  274. while (*p == ' ' || *p == '\t') p++;
  275. if (strncmp(p, "iface ", 6) == 0 || strncmp(p, "auto ", 5) == 0 ||
  276. strncmp(p, "source", 6) == 0 || p[0] == '[') {
  277. break;
  278. }
  279. if (*p != '#' && *p != '\n' && *p != '\r' && *p) {
  280. char key[32], val[256] = {0};
  281. if (sscanf(p, "%31s %255[^\n\r]", key, val) >= 1) {
  282. char *repl = NULL;
  283. if (strcmp(key, "address") == 0) repl = (char *)ip;
  284. else if (strcmp(key, "netmask") == 0) repl = (char *)mask;
  285. else if (strcmp(key, "gateway") == 0) repl = (char *)gw;
  286. if (repl) {
  287. size_t oldlen = (end ? (size_t)(end - line) : strlen(line)) + (end ? 1 : 0);
  288. char newl[256];
  289. int nl = snprintf(newl, sizeof(newl), " %s %s", key, repl);
  290. newl[nl++] = '\n';
  291. size_t off = (size_t)(line - buf);
  292. size_t total = strlen(buf) + 1;
  293. memmove(line + nl, line + oldlen, total - off - oldlen);
  294. memcpy(line, newl, nl);
  295. nwritten++;
  296. line += nl;
  297. end = strchr(line, '\n');
  298. if (end) { next = end + 1; }
  299. else next = NULL;
  300. continue;
  301. }
  302. }
  303. }
  304. line = next;
  305. end = line ? strchr(line, '\n') : NULL;
  306. }
  307. /* append missing keys at the end of the stanza */
  308. char *stanza_end = line ? line : (buf + strlen(buf));
  309. if (line && (*line == '\n' || *line == 0)) { /* nothing special */ }
  310. if (nwritten < 3) {
  311. char add[384]; int ao = 0;
  312. if (nwritten == 0) { /* no address key yet */
  313. int need_line = (stanza_end > buf && stanza_end[-1] != '\n') ? 1 : 0;
  314. if (need_line) add[ao++] = '\n';
  315. ao += snprintf(add + ao, sizeof(add) - ao,
  316. " address %s\n netmask %s\n", ip, mask);
  317. if (gw && is_ipv4(gw))
  318. ao += snprintf(add + ao, sizeof(add) - ao, " gateway %s\n", gw);
  319. } else {
  320. if (!strstr(sec, "gateway") && gw && is_ipv4(gw))
  321. ao += snprintf(add + ao, sizeof(add) - ao, " gateway %s\n", gw);
  322. }
  323. size_t off = (size_t)(stanza_end - buf);
  324. size_t total = strlen(buf) + 1;
  325. memmove(stanza_end + ao, stanza_end, total - off);
  326. memcpy(stanza_end, add, ao);
  327. nwritten = 3;
  328. }
  329. } else {
  330. if (buf[strlen(buf) - 1] != '\n') buf = buf_append(buf, "\n");
  331. char seg[512];
  332. int so = snprintf(seg, sizeof(seg),
  333. "\nauto %s\niface %s inet static\n address %s\n netmask %s\n",
  334. iface, iface, ip, mask);
  335. if (gw && is_ipv4(gw))
  336. so += snprintf(seg + so, sizeof(seg) - so, " gateway %s\n", gw);
  337. buf = buf_append(buf, seg);
  338. }
  339. strip_up_addr_lines(buf, iface);
  340. char tmp[128];
  341. snprintf(tmp, sizeof(tmp), "%s.tmp", path);
  342. fp = fopen(tmp, "w");
  343. if (!fp) { free(buf); return -1; }
  344. fwrite(buf, 1, strlen(buf), fp);
  345. fclose(fp);
  346. rename(tmp, path);
  347. free(buf);
  348. return 0;
  349. }
  350. /* Remove any "up ip addr add ... dev <iface>" line from the whole file.
  351. * Used by replace-mode so extra addresses don't come back after reboot. */
  352. static void strip_up_addr_lines(char *buf, const char *iface) {
  353. char *line = buf;
  354. while (line && *line) {
  355. char *nl = strchr(line, '\n');
  356. char *end = nl ? nl + 1 : line + strlen(line);
  357. const char *p = line;
  358. while (*p == ' ' || *p == '\t' || *p == '\r') p++;
  359. if (strncmp(p, "up ip addr add", 14) == 0 && strstr(p, iface)) {
  360. size_t off = (size_t)(line - buf);
  361. size_t len = (size_t)(end - line);
  362. size_t total = strlen(buf) + 1;
  363. memmove(line, end, total - off - len);
  364. continue;
  365. }
  366. line = end;
  367. }
  368. }
  369. /* Append "up ip addr add <cidr> dev <iface>" inside the iface stanza
  370. * (before the next iface/auto/source block, or at EOF). Idempotent.
  371. * Grows the buffer itself (buf_append may have sized it exactly). */
  372. static void append_up_addr_line(char **pbuf, const char *cidr, const char *iface) {
  373. char *buf = *pbuf;
  374. if (strstr(buf, cidr)) return;
  375. char add[256];
  376. int n = snprintf(add, sizeof(add),
  377. " up ip addr add %s dev %s 2>/dev/null || true\n", cidr, iface);
  378. char pat[80];
  379. snprintf(pat, sizeof(pat), "iface %s inet", iface);
  380. char *sec = strstr(buf, pat);
  381. if (!sec) { /* no stanza: append at EOF */
  382. *pbuf = buf_append(buf, add);
  383. return;
  384. }
  385. /* scan from AFTER the "iface X inet ..." header until the next
  386. * iface/auto/source block boundary (or EOF) */
  387. char *ins = strchr(sec, '\n');
  388. ins = ins ? ins + 1 : (buf + strlen(buf));
  389. while (*ins) {
  390. const char *p = ins;
  391. while (*p == ' ' || *p == '\t') p++;
  392. if (strncmp(p, "iface ", 6) == 0 || strncmp(p, "auto ", 5) == 0 ||
  393. strncmp(p, "source", 6) == 0 || p[0] == '[') break;
  394. char *nl = strchr(ins, '\n');
  395. if (!nl) break;
  396. ins = nl + 1;
  397. }
  398. size_t off = (size_t)(ins - buf);
  399. size_t oldlen = strlen(buf);
  400. char *nbuf = realloc(buf, oldlen + (size_t)n + 64);
  401. if (!nbuf) return;
  402. *pbuf = nbuf;
  403. memmove(nbuf + off + n, nbuf + off, oldlen - off + 1);
  404. memcpy(nbuf + off, add, n);
  405. }
  406. /* Persist an ADDED address: keep the existing stanza untouched, just append
  407. * the extra "up ip addr add" line. If the iface has no stanza yet (NIC with
  408. * no IP), create one with the new address as primary. */
  409. static int persist_add_addr(const char *iface, const char *ip,
  410. const char *mask, const char *cidr) {
  411. const char *path = "/etc/network/interfaces";
  412. long sz = 0;
  413. FILE *fp = fopen(path, "r");
  414. char *buf = NULL;
  415. if (fp) {
  416. fseek(fp, 0, SEEK_END);
  417. sz = ftell(fp);
  418. fseek(fp, 0, SEEK_SET);
  419. buf = malloc(sz + 8192);
  420. if (buf) { size_t rd = fread(buf, 1, sz, fp); buf[rd] = '\0'; }
  421. fclose(fp);
  422. }
  423. if (!buf) {
  424. buf = strdup("# generated by rtsp_multi_view web (no prior file)\n"
  425. "source /etc/network/interfaces.d/*\n\nauto lo\n"
  426. "iface lo inet loopback\n\n");
  427. }
  428. char pat[80];
  429. snprintf(pat, sizeof(pat), "iface %s inet", iface);
  430. char *sec = strstr(buf, pat);
  431. if (!sec) {
  432. /* No stanza yet: use the interface's CURRENT primary address (if it
  433. * has one) so a reboot restores it; otherwise the added one is the
  434. * only address and becomes primary. */
  435. char prim_ip[64] = "", prim_mask[64] = "255.255.255.0";
  436. if (!primary_of(iface, prim_ip, sizeof(prim_ip), prim_mask, sizeof(prim_mask))) {
  437. snprintf(prim_ip, sizeof(prim_ip), "%s", ip);
  438. snprintf(prim_mask, sizeof(prim_mask), "%s", mask);
  439. }
  440. if (buf[strlen(buf) - 1] != '\n') buf = buf_append(buf, "\n");
  441. char seg[512];
  442. snprintf(seg, sizeof(seg),
  443. "\nauto %s\niface %s inet static\n address %s\n netmask %s\n",
  444. iface, iface, prim_ip, prim_mask);
  445. buf = buf_append(buf, seg);
  446. sec = strstr(buf, pat);
  447. }
  448. append_up_addr_line(&buf, cidr, iface);
  449. char tmp[128];
  450. snprintf(tmp, sizeof(tmp), "%s.tmp", path);
  451. fp = fopen(tmp, "w");
  452. if (!fp) { free(buf); return -1; }
  453. fwrite(buf, 1, strlen(buf), fp);
  454. fclose(fp);
  455. rename(tmp, path);
  456. free(buf);
  457. return 0;
  458. }
  459. /* ---- Read entire config file into buffer ---- */
  460. static char *read_config(long *out_size) {
  461. FILE *fp = fopen(g_config_path, "r");
  462. if (!fp) return NULL;
  463. fseek(fp, 0, SEEK_END);
  464. long sz = ftell(fp);
  465. fseek(fp, 0, SEEK_SET);
  466. char *buf = malloc(sz + 1);
  467. if (!buf) { fclose(fp); return NULL; }
  468. size_t rd = fread(buf, 1, sz, fp);
  469. buf[rd] = '\0';
  470. fclose(fp);
  471. *out_size = rd;
  472. return buf;
  473. }
  474. /* Find "[section]" only at the start of a line (skips comments that mention it). */
  475. static char *find_section(const char *buf, const char *section) {
  476. char pat[64];
  477. snprintf(pat, sizeof(pat), "[%s]", section);
  478. const char *p = buf;
  479. while ((p = strstr(p, pat)) != NULL) {
  480. /* must be at line start (or after leading spaces) */
  481. const char *ls = p;
  482. while (ls > buf && ls[-1] != '\n') ls--;
  483. const char *q = ls;
  484. while (*q == ' ' || *q == '\t') q++;
  485. if (q == p && (ls == buf || ls[-1] == '\n'))
  486. return (char *)p;
  487. p += strlen(pat);
  488. }
  489. return NULL;
  490. }
  491. /* Parse "[web] ifaces = eth0 eth1 ..." from the config file into the
  492. * whitelist. Missing/empty = all non-loopback interfaces allowed. */
  493. static void load_iface_whitelist(void) {
  494. g_iface_wn = 0;
  495. long sz;
  496. char *cfg = g_config_path ? read_config(&sz) : NULL;
  497. if (!cfg) return;
  498. char *sec = find_section(cfg, "web");
  499. if (sec) {
  500. const char *line = strchr(sec, '\n');
  501. if (line) line += 1; /* skip the "[web]" header itself */
  502. while (line && *line) {
  503. const char *nl = strchr(line, '\n');
  504. size_t len = nl ? (size_t)(nl - line) : strlen(line);
  505. if (len > 0) {
  506. char lb[128];
  507. size_t cp = len < sizeof(lb) - 1 ? len : sizeof(lb) - 1;
  508. memcpy(lb, line, cp);
  509. lb[cp] = '\0';
  510. char *p = lb;
  511. while (*p == ' ' || *p == '\t') p++;
  512. if (p[0] == '[') break; /* next section */
  513. char *eq = strchr(p, '=');
  514. if (eq) {
  515. char *k = p;
  516. *eq = '\0';
  517. char *ke = eq - 1;
  518. while (ke >= k && (*ke == ' ' || *ke == '\t')) *ke-- = '\0';
  519. if (strcmp(k, "ifaces") == 0) {
  520. char *v = eq + 1;
  521. char *tok = strtok(v, " ,\t\r\n");
  522. while (tok && g_iface_wn < 8) {
  523. snprintf(g_iface_wl[g_iface_wn], IFNAMSIZ, "%s", tok);
  524. g_iface_wn++;
  525. tok = strtok(NULL, " ,\t\r\n");
  526. }
  527. }
  528. }
  529. }
  530. if (!nl) break;
  531. line = nl + 1;
  532. }
  533. }
  534. free(cfg);
  535. }
  536. static int iface_allowed(const char *name) {
  537. if (g_iface_wn == 0) return 1;
  538. for (int i = 0; i < g_iface_wn; i++)
  539. if (strcmp(g_iface_wl[i], name) == 0) return 1;
  540. return 0;
  541. }
  542. /* ---- Write config file ---- */
  543. static int write_config(const char *buf, size_t len) {
  544. (void)len;
  545. FILE *fp = fopen(g_config_path, "w");
  546. if (!fp) return -1;
  547. fwrite(buf, 1, strlen(buf), fp);
  548. fclose(fp);
  549. return 0;
  550. }
  551. /* ---- Replace a key=value line within a [section] ---- */
  552. static void replace_key(char *buf, const char *section,
  553. const char *key, const char *newval) {
  554. char *sec = find_section(buf, section);
  555. if (!sec) return;
  556. char *line = sec;
  557. while (line && *line) {
  558. if (*line == '[' && line != sec) break;
  559. char *eol = strchr(line, '\n');
  560. size_t linelen = eol ? (size_t)(eol - line) : strlen(line);
  561. const char *p = line;
  562. while (*p == ' ' || *p == '\t') p++;
  563. if (*p == '#' || *p == '[' || *p == '\n' || *p == '\r' || !*p) {
  564. line = eol ? eol + 1 : NULL;
  565. continue;
  566. }
  567. size_t klen = strlen(key);
  568. if (strncmp(p, key, klen) == 0 &&
  569. (p[klen] == ' ' || p[klen] == '\t' || p[klen] == '=')) {
  570. size_t indent = (size_t)(p - line);
  571. char newline[512];
  572. int n = snprintf(newline, sizeof(newline), "%.*s%s = %s",
  573. (int)indent, line, key, newval);
  574. if (eol) newline[n++] = '\n';
  575. newline[n] = '\0';
  576. size_t newlen = (size_t)n;
  577. size_t oldlen = linelen + (eol ? 1 : 0);
  578. size_t off = (size_t)(line - buf);
  579. size_t total = strlen(buf) + 1;
  580. memmove(line + newlen, line + oldlen, total - off - oldlen);
  581. memcpy(line, newline, newlen);
  582. return;
  583. }
  584. line = eol ? eol + 1 : NULL;
  585. }
  586. /* Key not present: insert it right after the section header so new
  587. * options (e.g. debounce_ms) can be added via the web page too. */
  588. {
  589. char *nl = strchr(sec, '\n');
  590. if (!nl) return;
  591. size_t insert_off = (size_t)(nl - buf) + 1;
  592. char addline[512];
  593. int n = snprintf(addline, sizeof(addline), "%s = %s\n", key, newval);
  594. if (n < 0 || (size_t)n >= sizeof(addline)) return;
  595. size_t total = strlen(buf) + 1;
  596. memmove(buf + insert_off + n, buf + insert_off, total - insert_off);
  597. memcpy(buf + insert_off, addline, (size_t)n);
  598. }
  599. }
  600. /* ---- Replace a line in [camera_list] by camera ID ---- */
  601. static void replace_camera_line(char *buf, int cam_id, const char *new_ip) {
  602. char *sec = find_section(buf, "camera_list");
  603. if (!sec) return;
  604. char idpat[32];
  605. snprintf(idpat, sizeof(idpat), "%d ", cam_id);
  606. char *line = sec;
  607. while (line && *line) {
  608. if (*line == '[' && line != sec) break;
  609. char *eol = strchr(line, '\n');
  610. size_t linelen = eol ? (size_t)(eol - line) : strlen(line);
  611. const char *p = line;
  612. while (*p == ' ' || *p == '\t') p++;
  613. if (*p == '#' || *p == '\n' || *p == '\r' || !*p) {
  614. line = eol ? eol + 1 : NULL;
  615. continue;
  616. }
  617. if (strncmp(p, idpat, strlen(idpat)) == 0) {
  618. const char *fields = p + strlen(idpat);
  619. char user[64] = {0}, pass[64] = {0};
  620. int chan = 1, port = 554, httpport = 80;
  621. char dummy_ip[64];
  622. sscanf(fields, "%63s %63s %63s %d %d %d",
  623. dummy_ip, user, pass, &chan, &port, &httpport);
  624. char newline[512];
  625. int n = snprintf(newline, sizeof(newline), "%d %s %s %s %d %d %d",
  626. cam_id, new_ip, user, pass, chan, port, httpport);
  627. if (eol) newline[n++] = '\n';
  628. newline[n] = '\0';
  629. size_t newlen = (size_t)n;
  630. size_t oldlen = linelen + (eol ? 1 : 0);
  631. size_t off = (size_t)(line - buf);
  632. size_t total = strlen(buf) + 1;
  633. memmove(line + newlen, line + oldlen, total - off - oldlen);
  634. memcpy(line, newline, newlen);
  635. return;
  636. }
  637. line = eol ? eol + 1 : NULL;
  638. }
  639. }
  640. /* ---- HTML page ---- */
  641. static const char *HTML_HEAD =
  642. "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n"
  643. "<!DOCTYPE html><html><head><meta charset=\"utf-8\">"
  644. "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
  645. "<title>&#x591A;&#x8DEF;&#x89C6;&#x9891;&#x914D;&#x7F6E;</title>"
  646. "<style>"
  647. "body{font-family:system-ui,sans-serif;margin:0;padding:0;background:#1a1a2e;color:#eee}"
  648. ".box{max-width:700px;margin:40px auto;background:#16213e;border-radius:12px;padding:30px;box-shadow:0 4px 24px rgba(0,0,0,.5)}"
  649. "h1{text-align:center;color:#e94560;margin-bottom:8px;font-size:1.5em}"
  650. "h2{color:#0f3460;background:#e94560;padding:8px 12px;border-radius:6px;margin:24px 0 12px;font-size:1em}"
  651. "table{width:100%;border-collapse:collapse;margin-bottom:16px}"
  652. "td{padding:6px 8px;border-bottom:1px solid #0f3460}"
  653. "td:first-child{width:80px;color:#a0a0b0;font-size:.9em}"
  654. "input[type=text],input[type=password]{width:100%;padding:8px;border:1px solid #0f3460;"
  655. "border-radius:6px;background:#1a1a2e;color:#eee;font-size:.95em;box-sizing:border-box}"
  656. "input:focus{outline:none;border-color:#e94560}"
  657. ".btn{display:block;width:100%;padding:12px;border:none;border-radius:8px;"
  658. "background:#e94560;color:#fff;font-size:1.1em;cursor:pointer;margin-top:20px}"
  659. ".btn:hover{background:#c73550}"
  660. ".ok{background:#27ae60}.ok:hover{background:#219a52}"
  661. ".info{text-align:center;color:#a0a0b0;font-size:.85em;margin-top:16px}"
  662. "a{color:#e94560}"
  663. "</style></head><body>";
  664. /* ---- Login page ---- */
  665. static const char *LOGIN_PAGE =
  666. "<div class=\"box\">"
  667. "<h1>&#x591A;&#x8DEF;&#x89C6;&#x9891;&#x914D;&#x7F6E;</h1>"
  668. "<form method=POST action=\"/login\">"
  669. "<table><tr><td><label for=pwd>&#x5BC6;&#x7801;</label></td>"
  670. "<td><input type=password id=pwd name=pwd autofocus></td></tr></table>"
  671. "<button class=btn type=submit>&#x767B;&#x5F55;</button>"
  672. "</form></div>";
  673. /* ---- Config page ---- */
  674. static void send_config_page(int fd, const char *body, const char *query) {
  675. char *cfg = NULL;
  676. long cfglen = 0;
  677. /* Handle POST (save) */
  678. int saved = 0;
  679. if (body && body[0]) {
  680. cfg = read_config(&cfglen);
  681. if (cfg) {
  682. char val[256];
  683. /* Camera IPs: cam0..cam7 */
  684. for (int i = 0; i < MAX_CAMS; i++) {
  685. char key[32];
  686. snprintf(key, sizeof(key), "cam%d_ip", i);
  687. if (post_value(body, key, val, sizeof(val)) == 0 && val[0]) {
  688. replace_camera_line(cfg, i, val);
  689. }
  690. }
  691. /* PLC settings */
  692. if (post_value(body, "plc_ip", val, sizeof(val)) == 0 && val[0])
  693. replace_key(cfg, "plc", "ip", val);
  694. if (post_value(body, "plc_port", val, sizeof(val)) == 0 && val[0])
  695. replace_key(cfg, "plc", "port", val);
  696. if (post_value(body, "plc_db", val, sizeof(val)) == 0 && val[0])
  697. replace_key(cfg, "plc", "db", val);
  698. if (post_value(body, "plc_read_len", val, sizeof(val)) == 0 && val[0])
  699. replace_key(cfg, "plc", "read_len", val);
  700. if (post_value(body, "plc_tsap_local", val, sizeof(val)) == 0 && val[0])
  701. replace_key(cfg, "plc", "tsap_local", val);
  702. if (post_value(body, "plc_tsap_remote", val, sizeof(val)) == 0 && val[0])
  703. replace_key(cfg, "plc", "tsap_remote", val);
  704. if (post_value(body, "plc_debounce_ms", val, sizeof(val)) == 0 && val[0])
  705. replace_key(cfg, "plc", "debounce_ms", val);
  706. if (post_value(body, "plc_enable", val, sizeof(val)) == 0)
  707. replace_key(cfg, "plc", "enable", val);
  708. /* Write config */
  709. if (write_config(cfg, cfglen) == 0) saved = 1;
  710. free(cfg);
  711. cfg = NULL;
  712. }
  713. }
  714. /* Read current config for display */
  715. cfg = read_config(&cfglen);
  716. char cam_ips[MAX_CAMS][64];
  717. char cam_user[MAX_CAMS][64];
  718. char cam_pass[MAX_CAMS][64];
  719. int cam_port[MAX_CAMS];
  720. int cam_httpport[MAX_CAMS];
  721. int cam_chan[MAX_CAMS];
  722. int cam_count = 0;
  723. char plc_ip[64] = {0};
  724. int plc_port_val = 102;
  725. int plc_db = 1000;
  726. int plc_read_len = 10;
  727. int plc_enabled = 0;
  728. char plc_tsap_local[32] = {0};
  729. char plc_tsap_remote[32] = {0};
  730. int plc_debounce_ms = 300;
  731. if (cfg) {
  732. /* parse [camera_list] */
  733. char *sec = find_section(cfg, "camera_list");
  734. if (sec) {
  735. char *ns = strchr(sec + 1, '[');
  736. char *line = sec;
  737. while (line && (!ns || line < ns)) {
  738. char *eol = strchr(line, '\n');
  739. const char *p = line;
  740. while (*p == ' ' || *p == '\t') p++;
  741. if (*p != '#' && *p != '[' && *p != '\n' && *p != '\r' && *p) {
  742. int id;
  743. char ip[64] = {0}, user[64] = {0}, pass[64] = {0};
  744. int chan = 1, port = 554, httpport = 80;
  745. if (sscanf(p, "%d %63s %63s %63s %d %d %d",
  746. &id, ip, user, pass, &chan, &port, &httpport) >= 4) {
  747. if (id >= 0 && id < MAX_CAMS) {
  748. strncpy(cam_ips[id], ip, 63);
  749. strncpy(cam_user[id], user, 63);
  750. strncpy(cam_pass[id], pass, 63);
  751. cam_port[id] = port;
  752. cam_httpport[id] = httpport;
  753. cam_chan[id] = chan;
  754. if (id + 1 > cam_count) cam_count = id + 1;
  755. }
  756. }
  757. }
  758. line = eol ? eol + 1 : NULL;
  759. }
  760. }
  761. /* parse [plc] */
  762. sec = find_section(cfg, "plc");
  763. if (sec) {
  764. char *ns = strchr(sec + 1, '[');
  765. char *line = sec;
  766. while (line && (!ns || line < ns)) {
  767. char key[64], val[256] = {0};
  768. const char *p = line;
  769. while (*p == ' ' || *p == '\t') p++;
  770. if (*p != '#' && *p != '[' && *p != '\n' && *p != '\r' && *p &&
  771. sscanf(p, "%63s = %255[^\n\r]", key, val) >= 2) {
  772. /* trim leading spaces in val */
  773. const char *v = val;
  774. while (*v == ' ' || *v == '\t') v++;
  775. if (strcmp(key, "ip") == 0) strncpy(plc_ip, v, 63);
  776. else if (strcmp(key, "port") == 0) plc_port_val = atoi(v);
  777. else if (strcmp(key, "db") == 0) plc_db = atoi(v);
  778. else if (strcmp(key, "read_len") == 0) plc_read_len = atoi(v);
  779. else if (strcmp(key, "enable") == 0) plc_enabled = atoi(v);
  780. else if (strcmp(key, "tsap_local") == 0) strncpy(plc_tsap_local, v, 31);
  781. else if (strcmp(key, "tsap_remote") == 0) strncpy(plc_tsap_remote, v, 31);
  782. else if (strcmp(key, "debounce_ms") == 0) plc_debounce_ms = atoi(v);
  783. }
  784. char *eol = strchr(line, '\n');
  785. line = eol ? eol + 1 : NULL;
  786. }
  787. }
  788. free(cfg);
  789. }
  790. /* Build response */
  791. char resp[16384];
  792. int off = 0;
  793. off += snprintf(resp + off, sizeof(resp) - off, "%s", HTML_HEAD);
  794. off += snprintf(resp + off, sizeof(resp) - off,
  795. "<div class=\"box\">"
  796. "<h1>&#x591A;&#x8DEF;&#x89C6;&#x9891;&#x914D;&#x7F6E;</h1>"
  797. "<p style=\"text-align:center\"><a href=\"/net\">&#x672C;&#x673A;&#x7F51;&#x7EDC; IP &#x8BBE;&#x7F6E;</a></p>");
  798. if (saved) {
  799. off += snprintf(resp + off, sizeof(resp) - off,
  800. "<p style=\"background:#27ae60;padding:10px;border-radius:6px;text-align:center\">"
  801. "&#x914D;&#x7F6E;&#x5DF2;&#x4FDD;&#x5B58;&#xFF01;&#x670D;&#x52A1;&#x5C06;&#x5728; 2 &#x79D2;&#x540E;&#x91CD;&#x542F;...</p>");
  802. }
  803. off += snprintf(resp + off, sizeof(resp) - off,
  804. "<form method=POST action=\"/config\">"
  805. "<h2>&#x6444;&#x50CF;&#x673A;&#x8BBE;&#x7F6E;</h2>"
  806. "<table>"
  807. "<tr><td>&#x7F16;&#x53F7;</td><td>IP &#x5730;&#x5740;</td></tr>");
  808. for (int i = 0; i < cam_count && i < MAX_CAMS; i++) {
  809. char key_ip[32], key_port[32], key_http[32], key_user[32], key_pass[32], key_chan[32];
  810. snprintf(key_ip, sizeof(key_ip), "cam%d_ip", i);
  811. off += snprintf(resp + off, sizeof(resp) - off,
  812. "<tr><td>%d</td><td>"
  813. "<input type=text name=%s value=\"%s\" placeholder=\"IP &#x5730;&#x5740;\">"
  814. "</td></tr>",
  815. i, key_ip, cam_ips[i]);
  816. }
  817. off += snprintf(resp + off, sizeof(resp) - off,
  818. "</table>"
  819. "<h2>PLC &#x8BBE;&#x7F6E;</h2>"
  820. "<table>"
  821. "<tr><td>&#x542F;&#x7528;</td><td><input type=text name=plc_enable value=\"%d\" placeholder=\"1=&#x5F00; 0=&#x5173;\"></td></tr>"
  822. "<tr><td>IP &#x5730;&#x5740;</td><td><input type=text name=plc_ip value=\"%s\"></td></tr>"
  823. "<tr><td>&#x7AEF;&#x53E3;</td><td><input type=text name=plc_port value=\"%d\"></td></tr>"
  824. "<tr><td>&#x6570;&#x636E;&#x5757;</td><td><input type=text name=plc_db value=\"%d\"></td></tr>"
  825. "<tr><td>&#x8BFB;&#x53D6;&#x957F;&#x5EA6;</td><td><input type=text name=plc_read_len value=\"%d\"></td></tr>"
  826. "<tr><td>TSAP &#x672C;&#x5730;</td><td><input type=text name=plc_tsap_local value=\"%s\" placeholder=\"0x0100\"></td></tr>"
  827. "<tr><td>TSAP &#x8FDC;&#x7AEF;</td><td><input type=text name=plc_tsap_remote value=\"%s\" placeholder=\"0x0101\"></td></tr>"
  828. "<tr><td>&#x53BB;&#x6296; (ms)</td><td><input type=text name=plc_debounce_ms value=\"%d\" placeholder=\"300 (0=&#x5173;&#x95ED;)\"></td></tr>"
  829. "</table>"
  830. "<button class=btn type=submit>&#x4FDD;&#x5B58;&#x5E76;&#x91CD;&#x542F;</button>"
  831. "</form>"
  832. "<p class=info>&#x4FEE;&#x6539;&#x5185;&#x5BB9;&#x9700;&#x91CD;&#x542F;&#x670D;&#x52A1;&#x540E;&#x751F;&#x6548;&#x3002;</p>"
  833. "</div></body></html>",
  834. plc_enabled, plc_ip, plc_port_val, plc_db, plc_read_len,
  835. plc_tsap_local, plc_tsap_remote, plc_debounce_ms);
  836. send_bytes(fd, resp, off);
  837. }
  838. /* ---- Local network page: show all IPs, change device IP ---- */
  839. static void send_net_page(int fd, const char *body) {
  840. char resp[16384];
  841. int off = 0;
  842. off += snprintf(resp + off, sizeof(resp) - off, "%s", HTML_HEAD);
  843. struct net_if nets[16];
  844. int nn = collect_nets(nets, 16);
  845. const char *applied_if = NULL, *applied_ip = NULL;
  846. int applied = 0;
  847. char msg[256] = "";
  848. if (body && body[0]) {
  849. char iface[32] = {0}, ip[64] = {0}, mask[64] = {0}, gw[64] = {0}, mode[8] = {0};
  850. post_value(body, "iface", iface, sizeof(iface));
  851. post_value(body, "ip", ip, sizeof(ip));
  852. post_value(body, "mask", mask, sizeof(mask));
  853. post_value(body, "gw", gw, sizeof(gw));
  854. post_value(body, "mode", mode, sizeof(mode));
  855. int add_mode = (strcmp(mode, "add") == 0);
  856. if (!iface[0] || !iface_exists(iface)) {
  857. snprintf(msg, sizeof(msg), "\u9519\u8BEF: \u7F51\u5361 %s \u4E0D\u5B58\u5728\u6216\u672A\u9009\u62E9",
  858. iface[0] ? iface : "?");
  859. } else if (!is_ipv4(ip)) {
  860. snprintf(msg, sizeof(msg), "\u9519\u8BEF: IP \u5730\u5740 %s \u4E0D\u5408\u6CD5", ip);
  861. } else if (!mask[0] || !is_ipv4(mask)) {
  862. snprintf(msg, sizeof(msg), "\u9519\u8BEF: \u5B50\u7F51\u63A9\u7801 %s \u4E0D\u5408\u6CD5", mask[0] ? mask : "?");
  863. } else if (!add_mode && gw[0] && !is_ipv4(gw)) {
  864. snprintf(msg, sizeof(msg), "\u9519\u8BEF: \u7F51\u5173 %s \u4E0D\u5408\u6CD5", gw);
  865. } else if (!is_safe_net_arg(iface) || !is_safe_net_arg(ip) ||
  866. !is_safe_net_arg(mask) || (gw[0] && !is_safe_net_arg(gw))) {
  867. snprintf(msg, sizeof(msg), "\u9519\u8BEF: \u53C2\u6570\u5305\u542B\u4E0D\u5141\u8BB8\u5B57\u7B26");
  868. } else {
  869. int pfx = netmask_to_prefix(mask);
  870. char cmd[768];
  871. char conname[128] = "";
  872. int use_nm = nm_running() && nm_managed(iface) &&
  873. nm_conn_name(iface, conname, sizeof(conname)) &&
  874. nm_safe_name(conname);
  875. if (use_nm && add_mode) {
  876. /* keep every existing address, add the segment via NetworkManager */
  877. snprintf(cmd, sizeof(cmd),
  878. "nmcli con mod '%s' +ipv4.addresses %s/%d 2>/dev/null; "
  879. "nmcli dev reapply %s 2>/dev/null",
  880. conname, ip, pfx, iface);
  881. system(cmd);
  882. applied = 1;
  883. applied_if = iface;
  884. applied_ip = ip;
  885. nn = collect_nets(nets, 16);
  886. snprintf(msg, sizeof(msg),
  887. "\u5DF2\u6DFB\u52A0\u65B0\u7F51\u6BB5: %s + %s/%d\u3010\u4FDD\u7559\u73B0\u6709 "
  888. "IP\u3011\uFF0C\u5DF2\u901A\u8FC7 NetworkManager \u6301\u4E45\u5316 "
  889. "(\u8FDE\u63A5 \"%s\")\u3002",
  890. iface, ip, pfx, conname);
  891. } else if (use_nm) {
  892. if (gw[0] && is_ipv4(gw))
  893. snprintf(cmd, sizeof(cmd),
  894. "nmcli con mod '%s' ipv4.method manual ipv4.addresses %s/%d "
  895. "ipv4.gateway %s 2>/dev/null; nmcli con up '%s' 2>/dev/null",
  896. conname, ip, pfx, gw, conname);
  897. else
  898. snprintf(cmd, sizeof(cmd),
  899. "nmcli con mod '%s' ipv4.method manual ipv4.addresses %s/%d "
  900. "2>/dev/null; nmcli con up '%s' 2>/dev/null",
  901. conname, ip, pfx, conname);
  902. system(cmd);
  903. applied = 1;
  904. applied_if = iface;
  905. applied_ip = ip;
  906. nn = collect_nets(nets, 16);
  907. snprintf(msg, sizeof(msg),
  908. "\u5DF2\u66FF\u6362: %s \u2192 %s/%s (CIDR /%d); \u5DF2\u901A\u8FC7 "
  909. "NetworkManager \u6301\u4E45\u5316 (\u8FDE\u63A5 \"%s\")\u3002"
  910. "\u540E\u7EED\u8BBF\u95EE\u8BF7\u7528\u65B0 \u5730\u5740\u3002",
  911. iface, ip, mask, pfx, conname);
  912. } else if (add_mode) {
  913. /* keep every existing address, just add the new segment */
  914. snprintf(cmd, sizeof(cmd),
  915. "ip link set %s up 2>/dev/null; ip addr add %s/%d dev %s 2>/dev/null",
  916. iface, ip, pfx, iface);
  917. system(cmd);
  918. char cidr[96];
  919. snprintf(cidr, sizeof(cidr), "%s/%d", ip, pfx);
  920. persist_add_addr(iface, ip, mask, cidr);
  921. applied = 1;
  922. applied_if = iface;
  923. applied_ip = ip;
  924. nn = collect_nets(nets, 16);
  925. snprintf(msg, sizeof(msg),
  926. "\u5DF2\u6DFB\u52A0\u65B0\u7F51\u6BB5: %s + %s/%d\u3010\u4FDD\u7559\u73B0\u6709 "
  927. "IP\u3011\uFF0C\u5DF2\u5199\u5165 /etc/network/interfaces\u3002",
  928. iface, ip, pfx);
  929. } else {
  930. snprintf(cmd, sizeof(cmd),
  931. "ip addr flush dev %s 2>/dev/null; ip link set %s up 2>/dev/null; "
  932. "ip addr add %s/%d dev %s 2>/dev/null",
  933. iface, iface, ip, pfx, iface);
  934. system(cmd);
  935. if (gw[0] && is_ipv4(gw)) {
  936. snprintf(cmd, sizeof(cmd), "ip route replace default via %s 2>/dev/null", gw);
  937. system(cmd);
  938. }
  939. persist_interfaces(iface, ip, mask, gw[0] ? gw : "");
  940. applied = 1;
  941. applied_if = iface;
  942. applied_ip = ip;
  943. nn = collect_nets(nets, 16);
  944. snprintf(msg, sizeof(msg),
  945. "\u5DF2\u66FF\u6362: %s \u2192 %s/%s (CIDR /%d); \u5DF2\u5199\u5165 "
  946. "/etc/network/interfaces \u3002\u540E\u7EED\u8BBF\u95EE\u8BF7\u7528\u65B0 "
  947. "\u5730\u5740\u3002", iface, ip, mask, pfx);
  948. }
  949. }
  950. }
  951. off += snprintf(resp + off, sizeof(resp) - off,
  952. "<div class=\"box\">"
  953. "<h1>&#x591A;&#x8DEF;&#x89C6;&#x9891;&#x914D;&#x7F6E; - &#x672C;&#x673A;&#x7F51;&#x7EDC;</h1>"
  954. "<p style=\"text-align:center\"><a href=\"/config\">&larr; &#x8FD4;&#x56DE;&#x914D;&#x7F6E;&#x9875;&#x9762;</a></p>");
  955. if (applied) {
  956. off += snprintf(resp + off, sizeof(resp) - off,
  957. "<p style=\"background:#27ae60;padding:10px;border-radius:6px;text-align:center\">%s</p>",
  958. msg);
  959. } else if (msg[0]) {
  960. off += snprintf(resp + off, sizeof(resp) - off,
  961. "<p style=\"background:#c0392b;padding:10px;border-radius:6px;text-align:center\">%s</p>",
  962. msg);
  963. }
  964. off += snprintf(resp + off, sizeof(resp) - off,
  965. "<h2>&#x5F53;&#x524D;&#x7F51;&#x5361; IP (&#x591A;&#x7F51;&#x6BB5;&#x5168;&#x90E8;&#x5217;&#x51FA;)</h2>"
  966. "<table><tr><td>&#x7F51;&#x5361;</td><td>IP &#x5730;&#x5740;</td><td>&#x5B50;&#x7F51;&#x63A9;&#x7801;</td></tr>");
  967. for (int i = 0; i < nn; i++) {
  968. off += snprintf(resp + off, sizeof(resp) - off,
  969. "<tr><td>%s</td><td>%s</td><td>%s</td></tr>",
  970. nets[i].iface, nets[i].ip, nets[i].mask);
  971. }
  972. off += snprintf(resp + off, sizeof(resp) - off, "</table>");
  973. /* default values: first interface with an address, current ip/mask */
  974. char def_if[IFNAMSIZ] = "", def_ip[64] = "", def_mask[64] = "255.255.255.0";
  975. if (nn > 0) {
  976. snprintf(def_if, sizeof(def_if), "%s", nets[0].iface);
  977. snprintf(def_ip, sizeof(def_ip), "%s", nets[0].ip);
  978. if (nets[0].mask[0] && strcmp(nets[0].mask, "0.0.0.0") != 0)
  979. snprintf(def_mask, sizeof(def_mask), "%s", nets[0].mask);
  980. }
  981. /* all interfaces (incl. ones with no IPv4 yet) so a NIC can be seeded */
  982. char ifaces[16][IFNAMSIZ];
  983. int nif = collect_ifaces(ifaces, 16);
  984. if (nif > 0 && !def_if[0]) snprintf(def_if, sizeof(def_if), "%s", ifaces[0]);
  985. off += snprintf(resp + off, sizeof(resp) - off,
  986. "<h2>&#x8BBE;&#x7F6E;&#x672C;&#x673A; IP</h2>"
  987. "<form method=POST action=\"/net\">"
  988. "<table>"
  989. "<tr><td>&#x7F51;&#x5361;</td><td><select name=iface>");
  990. for (int i = 0; i < nif; i++) {
  991. const char *curip = "\u65E0 IP";
  992. char has[5] = "";
  993. for (int j = 0; j < nn; j++) {
  994. if (strcmp(nets[j].iface, ifaces[i]) == 0) { curip = nets[j].ip; break; }
  995. }
  996. snprintf(has, sizeof(has), "%s", strcmp(ifaces[i], def_if) == 0 ? " selected" : "");
  997. off += snprintf(resp + off, sizeof(resp) - off,
  998. "<option value=\"%s\"%s>%s (%s)</option>", ifaces[i], has, ifaces[i], curip);
  999. }
  1000. off += snprintf(resp + off, sizeof(resp) - off,
  1001. "</select></td></tr>"
  1002. "<tr><td>&#x65B0; IP</td><td><input type=text name=ip value=\"%s\"></td></tr>"
  1003. "<tr><td>&#x5B50;&#x7F51;&#x63A9;&#x7801;</td><td><input type=text name=mask value=\"%s\"></td></tr>"
  1004. "<tr><td>&#x7F51;&#x5173;</td><td><input type=text name=gw placeholder=\"&#x5982; 192.168.1.1\"></td></tr>"
  1005. "</table>"
  1006. "<button class=btn type=submit name=mode value=replace>&#x66FF;&#x6362; / &#x4FEE;&#x6539; IP (&#x53BB;&#x6389;&#x5176;&#x4ED6; IP)</button>"
  1007. "<button class=btn type=submit name=mode value=add style=\"background:#27ae60;margin-top:8px\">"
  1008. "&#x4FDD;&#x7559;&#x73B0;&#x6709; IP\uFF0C;&#x6DFB;&#x52A0;&#x65B0;&#x7F51;&#x6BB5; IP (&#x53EF;&#x591A;&#x7F51;&#x6BB5;)</button>"
  1009. "</form>"
  1010. "<p class=info>&#x8B66;&#x544A;: 1) [&#x66FF;&#x6362;] \u4F1A;&#x5148;&#x6E05;&#x9664;&#x8BE5;&#x7F51;&#x5361;&#x6240;&#x6709; IP \u518D;&#x6DFB;&#x52A0;&#xFF0C;&#x4FEE;&#x6539;&#x5F53;&#x524D;&#x8BBF;&#x95EE;&#x7684; IP \u4F1A;&#x65AD;&#x5F00;&#x8FDE;&#x63A5;&#x3002; "
  1011. "2) [&#x6DFB;&#x52A0;] \u4FDD;&#x7559;&#x73B0;&#x6709; IP\uFF0C;&#x53EA;&#x65B0;&#x589E;&#x4E00;&#x4E2A;&#x7F51;&#x6BB5; IP\uFF0C;&#x4E0D;&#x6539;&#x7F51;&#x5173;&#x3002; "
  1012. "3) &#x5DF2;&#x5199;&#x5165; /etc/network/interfaces \u6301;&#x4E45;&#x5316;&#xFF0C;&#x91CD;&#x542F;&#x540E;&#x4ECD;&#x751F;&#x6548;&#x3002;</p>"
  1013. "</div></body></html>", def_ip, def_mask);
  1014. send_bytes(fd, resp, off);
  1015. }
  1016. /* ---- HTTP request handler ---- */
  1017. static void handle_client(int cfd) {
  1018. /* Bound each connection so a slow/half-open client cannot stall the
  1019. * other clients (read timeout on the socket). */
  1020. struct timeval rcv_tv = { .tv_sec = 8, .tv_usec = 0 };
  1021. setsockopt(cfd, SOL_SOCKET, SO_RCVTIMEO, &rcv_tv, sizeof(rcv_tv));
  1022. char method[8], path[256], ver[16];
  1023. char line[1024];
  1024. if (recv_line(cfd, line, sizeof(line)) < 0) goto done;
  1025. if (sscanf(line, "%7s %255s %15s", method, path, ver) != 3) goto done;
  1026. /* Read headers (and detect Cookie) */
  1027. char cookie[256] = {0};
  1028. char content_len_hdr[32] = {0};
  1029. while (1) {
  1030. if (recv_line(cfd, line, sizeof(line)) < 0) break;
  1031. if (line[0] == '\n' || line[0] == '\r') break;
  1032. if (strncasecmp(line, "Cookie:", 7) == 0) {
  1033. char *p = line + 7;
  1034. while (*p == ' ') p++;
  1035. strncpy(cookie, p, sizeof(cookie) - 1);
  1036. cookie[strcspn(cookie, "\r\n")] = '\0';
  1037. }
  1038. if (strncasecmp(line, "Content-Length:", 15) == 0) {
  1039. char *p = line + 15;
  1040. while (*p == ' ') p++;
  1041. strncpy(content_len_hdr, p, sizeof(content_len_hdr) - 1);
  1042. content_len_hdr[strcspn(content_len_hdr, "\r\n")] = '\0';
  1043. }
  1044. }
  1045. /* Auth check */
  1046. char sesshash[64];
  1047. snprintf(sesshash, sizeof(sesshash), "sess=%u", simple_hash(g_password));
  1048. int authed = (strstr(cookie, sesshash) != NULL);
  1049. if (strcmp(method, "POST") == 0 && strcmp(path, "/login") == 0) {
  1050. /* Read POST body */
  1051. int clen = atoi(content_len_hdr);
  1052. char body[512] = {0};
  1053. if (clen > 0 && clen < (int)sizeof(body)) {
  1054. int got = 0;
  1055. while (got < clen) {
  1056. ssize_t r = read(cfd, body + got, clen - got);
  1057. if (r <= 0) break;
  1058. got += r;
  1059. }
  1060. }
  1061. char pwd[64] = {0};
  1062. post_value(body, "pwd", pwd, sizeof(pwd));
  1063. if (strcmp(pwd, g_password) == 0) {
  1064. char hdr[256];
  1065. snprintf(hdr, sizeof(hdr),
  1066. "HTTP/1.1 302 Found\r\nSet-Cookie: sess=%u; Path=/\r\n"
  1067. "Location: /config\r\nConnection: close\r\n\r\n",
  1068. simple_hash(g_password));
  1069. send_str(cfd, hdr);
  1070. } else {
  1071. send_str(cfd, "HTTP/1.1 302 Found\r\nLocation: /\r\nConnection: close\r\n\r\n");
  1072. }
  1073. goto done;
  1074. }
  1075. if (!authed) {
  1076. send_str(cfd, HTML_HEAD);
  1077. send_str(cfd, LOGIN_PAGE);
  1078. send_str(cfd, "</body></html>");
  1079. goto done;
  1080. }
  1081. /* Authed requests */
  1082. if (strcmp(path, "/") == 0 && strcmp(method, "GET") == 0) {
  1083. send_str(cfd, "HTTP/1.1 302 Found\r\nLocation: /config\r\nConnection: close\r\n\r\n");
  1084. } else if (strcmp(path, "/config") == 0 && strcmp(method, "GET") == 0) {
  1085. send_config_page(cfd, NULL, NULL);
  1086. } else if (strcmp(path, "/config") == 0 && strcmp(method, "POST") == 0) {
  1087. int clen = atoi(content_len_hdr);
  1088. char body[4096] = {0};
  1089. if (clen > 0 && clen < (int)sizeof(body)) {
  1090. int got = 0;
  1091. while (got < clen) {
  1092. ssize_t r = read(cfd, body + got, clen - got);
  1093. if (r <= 0) break;
  1094. got += r;
  1095. }
  1096. }
  1097. send_config_page(cfd, body, NULL);
  1098. /* Restart service after response is sent */
  1099. usleep(500000);
  1100. system("systemctl restart rtsp_multi_view &");
  1101. } else if (strcmp(path, "/net") == 0 && strcmp(method, "GET") == 0) {
  1102. send_net_page(cfd, NULL);
  1103. } else if (strcmp(path, "/net") == 0 && strcmp(method, "POST") == 0) {
  1104. int clen = atoi(content_len_hdr);
  1105. char body[1024] = {0};
  1106. if (clen > 0 && clen < (int)sizeof(body)) {
  1107. int got = 0;
  1108. while (got < clen) {
  1109. ssize_t r = read(cfd, body + got, clen - got);
  1110. if (r <= 0) break;
  1111. got += r;
  1112. }
  1113. }
  1114. send_net_page(cfd, body);
  1115. } else if (strcmp(path, "/restart") == 0) {
  1116. send_str(cfd, HTML_HEAD);
  1117. send_str(cfd, "<div class=\"box\"><h1>&#x6B63;&#x5728;&#x91CD;&#x542F;...</h1>"
  1118. "<p class=info>&#x670D;&#x52A1;&#x6B63;&#x5728;&#x91CD;&#x542F;&#xFF0C;&#x8BF7;&#x7A0D;&#x5019;&#x3002;</p></div></body></html>");
  1119. usleep(500000);
  1120. system("systemctl restart rtsp_multi_view &");
  1121. } else {
  1122. send_str(cfd, HTML_HEAD);
  1123. send_str(cfd, "<div class=\"box\"><h1>&#x9875;&#x9762;&#x4E0D;&#x5B58;&#x5728;</h1>"
  1124. "<p class=info>&#x8BF7;&#x68C0;&#x67E5;&#x8BBF;&#x95EE;&#x5730;&#x5740;&#x662F;&#x5426;&#x6B63;&#x786E;&#x3002;</p></div></body></html>");
  1125. }
  1126. done:
  1127. close(cfd);
  1128. }
  1129. /* ---- One thread per connection: a slow client cannot stall the others ---- */
  1130. static void *client_thread(void *arg) {
  1131. int cfd = (int)(long)arg;
  1132. handle_client(cfd);
  1133. return NULL;
  1134. }
  1135. /* ---- Server thread ---- */
  1136. static void *server_thread(void *arg) {
  1137. (void)arg;
  1138. int lfd = socket(AF_INET, SOCK_STREAM, 0);
  1139. if (lfd < 0) return NULL;
  1140. int one = 1;
  1141. setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
  1142. struct sockaddr_in sa = {0};
  1143. sa.sin_family = AF_INET;
  1144. sa.sin_addr.s_addr = INADDR_ANY;
  1145. sa.sin_port = htons(g_port);
  1146. if (bind(lfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
  1147. fprintf(stderr, "web: bind port %d failed: %s\n", g_port, strerror(errno));
  1148. close(lfd);
  1149. return NULL;
  1150. }
  1151. listen(lfd, 8);
  1152. fprintf(stderr, "web: listening on port %d\n", g_port);
  1153. while (1) {
  1154. struct sockaddr_in cli;
  1155. socklen_t clilen = sizeof(cli);
  1156. int cfd = accept(lfd, (struct sockaddr *)&cli, &clilen);
  1157. if (cfd < 0) continue;
  1158. pthread_t tid;
  1159. if (pthread_create(&tid, NULL, client_thread, (void *)(long)cfd) != 0)
  1160. close(cfd);
  1161. else
  1162. pthread_detach(tid);
  1163. }
  1164. close(lfd);
  1165. return NULL;
  1166. }
  1167. int web_server_start(const char *config_path, int port, const char *password) {
  1168. g_config_path = config_path;
  1169. g_port = port > 0 ? port : WEB_SERVER_PORT_DEFAULT;
  1170. g_password = password && password[0] ? password : WEB_SERVER_PASS_DEFAULT;
  1171. load_iface_whitelist();
  1172. pthread_t thr;
  1173. if (pthread_create(&thr, NULL, server_thread, NULL) != 0) return -1;
  1174. pthread_detach(thr);
  1175. return 0;
  1176. }