| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267 |
- #include "web_server.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <errno.h>
- #include <pthread.h>
- #include <time.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <ifaddrs.h>
- #include <net/if.h>
- #define BUF_SIZE 8192
- #define MAX_CAMS 8
- static const char *g_config_path;
- static const char *g_password;
- static int g_port;
- /* Optional NIC whitelist ([web] ifaces = eth0 eth1 in channels.conf).
- * Empty = show/configure every non-loopback interface. */
- static char g_iface_wl[8][IFNAMSIZ];
- static int g_iface_wn = 0;
- static int iface_allowed(const char *name);
- /* ---- MD5-like simple hash for session cookie (not crypto-secure, fine for LAN) ---- */
- static unsigned simple_hash(const char *s) {
- unsigned h = 0x811c9dc5;
- for (; *s; s++) {
- h ^= (unsigned char)*s;
- h *= 0x01000193;
- }
- return h;
- }
- /* ---- HTTP helpers ---- */
- static void send_str(int fd, const char *s) {
- size_t len = strlen(s);
- size_t sent = 0;
- while (sent < len) {
- ssize_t r = write(fd, s + sent, len - sent);
- if (r <= 0) return;
- sent += r;
- }
- }
- static void send_bytes(int fd, const char *buf, size_t len) {
- size_t sent = 0;
- while (sent < len) {
- ssize_t r = write(fd, buf + sent, len - sent);
- if (r <= 0) return;
- sent += r;
- }
- }
- static int recv_line(int fd, char *buf, int maxlen) {
- int n = 0;
- while (n < maxlen - 1) {
- char c;
- ssize_t r = read(fd, &c, 1);
- if (r <= 0) return -1;
- buf[n++] = c;
- if (c == '\n') break;
- }
- buf[n] = '\0';
- return n;
- }
- /* ---- URL decode ---- */
- static void url_decode(char *dst, size_t dstsz, const char *src) {
- size_t i = 0;
- while (*src && i < dstsz - 1) {
- if (*src == '%' && src[1] && src[2]) {
- char hex[3] = {src[1], src[2], 0};
- dst[i++] = (char)strtol(hex, NULL, 16);
- src += 3;
- } else if (*src == '+') {
- dst[i++] = ' ';
- src++;
- } else {
- dst[i++] = *src++;
- }
- }
- dst[i] = '\0';
- }
- /* ---- Extract value from POST body key=value&key2=value2 ---- */
- static int post_value(const char *body, const char *key, char *val, size_t valsz) {
- char pattern[64];
- snprintf(pattern, sizeof(pattern), "%s=", key);
- const char *p = strstr(body, pattern);
- if (!p) return -1;
- p += strlen(pattern);
- size_t i = 0;
- while (*p && *p != '&' && i < valsz - 1) val[i++] = *p++;
- val[i] = '\0';
- url_decode(val, valsz, val);
- return 0;
- }
- /* ---- Local network info (all interfaces / segments) ---- */
- struct net_if { char iface[IFNAMSIZ]; char ip[64]; char mask[64]; };
- static int collect_nets(struct net_if *out, int maxn) {
- struct ifaddrs *ifa0 = NULL;
- if (getifaddrs(&ifa0) != 0) return 0;
- int n = 0;
- for (struct ifaddrs *ifp = ifa0; ifp && n < maxn; ifp = ifp->ifa_next) {
- if (!ifp->ifa_addr || ifp->ifa_addr->sa_family != AF_INET) continue;
- if (strcmp(ifp->ifa_name, "lo") == 0) continue;
- if (!iface_allowed(ifp->ifa_name)) continue;
- struct sockaddr_in *a = (struct sockaddr_in *)ifp->ifa_addr;
- struct sockaddr_in *m = (struct sockaddr_in *)ifp->ifa_netmask;
- snprintf(out[n].iface, sizeof(out[n].iface), "%s", ifp->ifa_name);
- if (!inet_ntop(AF_INET, &a->sin_addr, out[n].ip, sizeof(out[n].ip))) continue;
- if (m) inet_ntop(AF_INET, &m->sin_addr, out[n].mask, sizeof(out[n].mask));
- else snprintf(out[n].mask, sizeof(out[n].mask), "255.255.255.0");
- n++;
- }
- freeifaddrs(ifa0);
- return n;
- }
- /* All physical interfaces (any family), loopback excluded. Used so the page
- * can also configure a NIC that currently has no IPv4 address at all. */
- static int collect_ifaces(char (*out)[IFNAMSIZ], int maxn) {
- struct ifaddrs *ifa0 = NULL;
- if (getifaddrs(&ifa0) != 0) return 0;
- int n = 0;
- for (struct ifaddrs *ifp = ifa0; ifp && n < maxn; ifp = ifp->ifa_next) {
- if (!ifp->ifa_name || strcmp(ifp->ifa_name, "lo") == 0) continue;
- if (!iface_allowed(ifp->ifa_name)) continue;
- int dup = 0;
- for (int i = 0; i < n; i++)
- if (strcmp(out[i], ifp->ifa_name) == 0) { dup = 1; break; }
- if (dup) continue;
- snprintf(out[n], IFNAMSIZ, "%s", ifp->ifa_name);
- n++;
- }
- freeifaddrs(ifa0);
- return n;
- }
- static int is_ipv4(const char *s) {
- struct in_addr a;
- return s && inet_pton(AF_INET, s, &a) == 1;
- }
- static int iface_exists(const char *name) {
- char tmp[16][IFNAMSIZ];
- int n = collect_ifaces(tmp, 16);
- for (int i = 0; i < n; i++)
- if (strcmp(tmp[i], name) == 0) return 1;
- return 0;
- }
- /* First IPv4 address currently on <iface> (the primary), if any. */
- static int primary_of(const char *iface, char *ip, size_t ipsz,
- char *mask, size_t masksz) {
- struct net_if nets[16];
- int n = collect_nets(nets, 16);
- for (int i = 0; i < n; i++) {
- if (strcmp(nets[i].iface, iface) == 0) {
- snprintf(ip, ipsz, "%s", nets[i].ip);
- snprintf(mask, masksz, "%s",
- (nets[i].mask[0] && strcmp(nets[i].mask, "0.0.0.0") != 0)
- ? nets[i].mask : "255.255.255.0");
- return 1;
- }
- }
- return 0;
- }
- /* Safe append that grows the heap buffer (avoids strcat overflow). */
- static char *buf_append(char *buf, const char *s) {
- size_t a = buf ? strlen(buf) : 0;
- size_t b = strlen(s);
- char *n = realloc(buf, a + b + 1);
- if (!n) return buf;
- memcpy(n + a, s, b + 1);
- return n;
- }
- /* Only [0-9A-Za-z./:] allowed: used to build the `ip` command safely. */
- static int is_safe_net_arg(const char *s) {
- if (!s || !s[0]) return 0;
- for (const char *p = s; *p; p++) {
- char c = *p;
- if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
- (c >= 'A' && c <= 'Z') || c == '.' || c == ':' || c == '/'))
- return 0;
- }
- return 1;
- }
- static int netmask_to_prefix(const char *mask) {
- struct in_addr a;
- if (!mask || inet_pton(AF_INET, mask, &a) != 1) return 24;
- unsigned v = ntohl(a.s_addr);
- int p = 0;
- for (int b = 31; b >= 0 && (v & (1u << b)); b--) p++;
- return p;
- }
- /* Some vendor rootfs images drive the NIC with NetworkManager (ifupdown is
- * installed but no longer manages the device), so /etc/network/interfaces is
- * ignored at boot. Detect that case and persist through nmcli instead. */
- static int nm_running(void) {
- FILE *p = popen("nmcli -t -g RUNNING general 2>/dev/null", "r");
- if (!p) return 0;
- char b[32] = "";
- size_t rd = fread(b, 1, sizeof(b) - 1, p);
- b[rd] = '\0';
- pclose(p);
- return strstr(b, "running") != NULL;
- }
- static int nm_managed(const char *iface) {
- char cmd[128], line[192];
- snprintf(cmd, sizeof(cmd),
- "nmcli -t -f GENERAL.STATE dev show %s 2>/dev/null", iface);
- FILE *p = popen(cmd, "r");
- if (!p) return 0;
- int managed = 0;
- while (fgets(line, sizeof(line), p)) {
- if (strstr(line, "unmanaged")) { managed = 0; break; }
- if (strstr(line, "GENERAL.STATE:")) managed = 1;
- }
- pclose(p);
- return managed;
- }
- static int nm_conn_name(const char *iface, char *out, size_t outsz) {
- char cmd[128], line[192];
- snprintf(cmd, sizeof(cmd),
- "nmcli -t -f GENERAL.CONNECTION dev show %s 2>/dev/null", iface);
- FILE *p = popen(cmd, "r");
- if (!p) return 0;
- int got = 0;
- while (fgets(line, sizeof(line), p)) {
- char *c = strchr(line, ':');
- if (!c) continue;
- *c = '\0';
- char *v = c + 1;
- v[strcspn(v, "\r\n")] = '\0';
- if (strcmp(line, "GENERAL.CONNECTION") == 0 && v[0]) {
- snprintf(out, outsz, "%s", v);
- got = 1;
- }
- }
- pclose(p);
- return got;
- }
- static int nm_safe_name(const char *s) {
- if (!s || !s[0] || strlen(s) >= 96) return 0;
- for (const char *p = s; *p; p++)
- if (strchr("'\"`\\$;&|<>()\n\r", *p)) return 0;
- return 1;
- }
- /* Persist an inet-static stanza for <iface> in /etc/network/interfaces.
- * Edits the matching block in place; appends one if absent. */
- static void strip_up_addr_lines(char *buf, const char *iface);
- static int persist_interfaces(const char *iface, const char *ip,
- const char *mask, const char *gw) {
- const char *path = "/etc/network/interfaces";
- long sz = 0;
- FILE *fp = fopen(path, "r");
- char *buf = NULL;
- if (fp) {
- fseek(fp, 0, SEEK_END);
- sz = ftell(fp);
- fseek(fp, 0, SEEK_SET);
- buf = malloc(sz + 8192);
- if (buf) { size_t rd = fread(buf, 1, sz, fp); buf[rd] = '\0'; }
- fclose(fp);
- }
- if (!buf) {
- buf = strdup("# generated by rtsp_multi_view web (no prior file)\n"
- "source /etc/network/interfaces.d/*\n\nauto lo\n"
- "iface lo inet loopback\n\n");
- }
- char pat[80];
- snprintf(pat, sizeof(pat), "iface %s inet", iface);
- char *sec = strstr(buf, pat);
- int nwritten = 0;
- if (sec) {
- /* walk the stanza lines until the next 'iface'/'auto'/'source' */
- char *line = sec + strlen(pat);
- char *end = strchr(line, '\n');
- while (line && *line && nwritten < 3) {
- char *next = end ? end + 1 : NULL;
- const char *p = line;
- while (*p == ' ' || *p == '\t') p++;
- if (strncmp(p, "iface ", 6) == 0 || strncmp(p, "auto ", 5) == 0 ||
- strncmp(p, "source", 6) == 0 || p[0] == '[') {
- break;
- }
- if (*p != '#' && *p != '\n' && *p != '\r' && *p) {
- char key[32], val[256] = {0};
- if (sscanf(p, "%31s %255[^\n\r]", key, val) >= 1) {
- char *repl = NULL;
- if (strcmp(key, "address") == 0) repl = (char *)ip;
- else if (strcmp(key, "netmask") == 0) repl = (char *)mask;
- else if (strcmp(key, "gateway") == 0) repl = (char *)gw;
- if (repl) {
- size_t oldlen = (end ? (size_t)(end - line) : strlen(line)) + (end ? 1 : 0);
- char newl[256];
- int nl = snprintf(newl, sizeof(newl), " %s %s", key, repl);
- newl[nl++] = '\n';
- size_t off = (size_t)(line - buf);
- size_t total = strlen(buf) + 1;
- memmove(line + nl, line + oldlen, total - off - oldlen);
- memcpy(line, newl, nl);
- nwritten++;
- line += nl;
- end = strchr(line, '\n');
- if (end) { next = end + 1; }
- else next = NULL;
- continue;
- }
- }
- }
- line = next;
- end = line ? strchr(line, '\n') : NULL;
- }
- /* append missing keys at the end of the stanza */
- char *stanza_end = line ? line : (buf + strlen(buf));
- if (line && (*line == '\n' || *line == 0)) { /* nothing special */ }
- if (nwritten < 3) {
- char add[384]; int ao = 0;
- if (nwritten == 0) { /* no address key yet */
- int need_line = (stanza_end > buf && stanza_end[-1] != '\n') ? 1 : 0;
- if (need_line) add[ao++] = '\n';
- ao += snprintf(add + ao, sizeof(add) - ao,
- " address %s\n netmask %s\n", ip, mask);
- if (gw && is_ipv4(gw))
- ao += snprintf(add + ao, sizeof(add) - ao, " gateway %s\n", gw);
- } else {
- if (!strstr(sec, "gateway") && gw && is_ipv4(gw))
- ao += snprintf(add + ao, sizeof(add) - ao, " gateway %s\n", gw);
- }
- size_t off = (size_t)(stanza_end - buf);
- size_t total = strlen(buf) + 1;
- memmove(stanza_end + ao, stanza_end, total - off);
- memcpy(stanza_end, add, ao);
- nwritten = 3;
- }
- } else {
- if (buf[strlen(buf) - 1] != '\n') buf = buf_append(buf, "\n");
- char seg[512];
- int so = snprintf(seg, sizeof(seg),
- "\nauto %s\niface %s inet static\n address %s\n netmask %s\n",
- iface, iface, ip, mask);
- if (gw && is_ipv4(gw))
- so += snprintf(seg + so, sizeof(seg) - so, " gateway %s\n", gw);
- buf = buf_append(buf, seg);
- }
- strip_up_addr_lines(buf, iface);
- char tmp[128];
- snprintf(tmp, sizeof(tmp), "%s.tmp", path);
- fp = fopen(tmp, "w");
- if (!fp) { free(buf); return -1; }
- fwrite(buf, 1, strlen(buf), fp);
- fclose(fp);
- rename(tmp, path);
- free(buf);
- return 0;
- }
- /* Remove any "up ip addr add ... dev <iface>" line from the whole file.
- * Used by replace-mode so extra addresses don't come back after reboot. */
- static void strip_up_addr_lines(char *buf, const char *iface) {
- char *line = buf;
- while (line && *line) {
- char *nl = strchr(line, '\n');
- char *end = nl ? nl + 1 : line + strlen(line);
- const char *p = line;
- while (*p == ' ' || *p == '\t' || *p == '\r') p++;
- if (strncmp(p, "up ip addr add", 14) == 0 && strstr(p, iface)) {
- size_t off = (size_t)(line - buf);
- size_t len = (size_t)(end - line);
- size_t total = strlen(buf) + 1;
- memmove(line, end, total - off - len);
- continue;
- }
- line = end;
- }
- }
- /* Append "up ip addr add <cidr> dev <iface>" inside the iface stanza
- * (before the next iface/auto/source block, or at EOF). Idempotent.
- * Grows the buffer itself (buf_append may have sized it exactly). */
- static void append_up_addr_line(char **pbuf, const char *cidr, const char *iface) {
- char *buf = *pbuf;
- if (strstr(buf, cidr)) return;
- char add[256];
- int n = snprintf(add, sizeof(add),
- " up ip addr add %s dev %s 2>/dev/null || true\n", cidr, iface);
- char pat[80];
- snprintf(pat, sizeof(pat), "iface %s inet", iface);
- char *sec = strstr(buf, pat);
- if (!sec) { /* no stanza: append at EOF */
- *pbuf = buf_append(buf, add);
- return;
- }
- /* scan from AFTER the "iface X inet ..." header until the next
- * iface/auto/source block boundary (or EOF) */
- char *ins = strchr(sec, '\n');
- ins = ins ? ins + 1 : (buf + strlen(buf));
- while (*ins) {
- const char *p = ins;
- while (*p == ' ' || *p == '\t') p++;
- if (strncmp(p, "iface ", 6) == 0 || strncmp(p, "auto ", 5) == 0 ||
- strncmp(p, "source", 6) == 0 || p[0] == '[') break;
- char *nl = strchr(ins, '\n');
- if (!nl) break;
- ins = nl + 1;
- }
- size_t off = (size_t)(ins - buf);
- size_t oldlen = strlen(buf);
- char *nbuf = realloc(buf, oldlen + (size_t)n + 64);
- if (!nbuf) return;
- *pbuf = nbuf;
- memmove(nbuf + off + n, nbuf + off, oldlen - off + 1);
- memcpy(nbuf + off, add, n);
- }
- /* Persist an ADDED address: keep the existing stanza untouched, just append
- * the extra "up ip addr add" line. If the iface has no stanza yet (NIC with
- * no IP), create one with the new address as primary. */
- static int persist_add_addr(const char *iface, const char *ip,
- const char *mask, const char *cidr) {
- const char *path = "/etc/network/interfaces";
- long sz = 0;
- FILE *fp = fopen(path, "r");
- char *buf = NULL;
- if (fp) {
- fseek(fp, 0, SEEK_END);
- sz = ftell(fp);
- fseek(fp, 0, SEEK_SET);
- buf = malloc(sz + 8192);
- if (buf) { size_t rd = fread(buf, 1, sz, fp); buf[rd] = '\0'; }
- fclose(fp);
- }
- if (!buf) {
- buf = strdup("# generated by rtsp_multi_view web (no prior file)\n"
- "source /etc/network/interfaces.d/*\n\nauto lo\n"
- "iface lo inet loopback\n\n");
- }
- char pat[80];
- snprintf(pat, sizeof(pat), "iface %s inet", iface);
- char *sec = strstr(buf, pat);
- if (!sec) {
- /* No stanza yet: use the interface's CURRENT primary address (if it
- * has one) so a reboot restores it; otherwise the added one is the
- * only address and becomes primary. */
- char prim_ip[64] = "", prim_mask[64] = "255.255.255.0";
- if (!primary_of(iface, prim_ip, sizeof(prim_ip), prim_mask, sizeof(prim_mask))) {
- snprintf(prim_ip, sizeof(prim_ip), "%s", ip);
- snprintf(prim_mask, sizeof(prim_mask), "%s", mask);
- }
- if (buf[strlen(buf) - 1] != '\n') buf = buf_append(buf, "\n");
- char seg[512];
- snprintf(seg, sizeof(seg),
- "\nauto %s\niface %s inet static\n address %s\n netmask %s\n",
- iface, iface, prim_ip, prim_mask);
- buf = buf_append(buf, seg);
- sec = strstr(buf, pat);
- }
- append_up_addr_line(&buf, cidr, iface);
- char tmp[128];
- snprintf(tmp, sizeof(tmp), "%s.tmp", path);
- fp = fopen(tmp, "w");
- if (!fp) { free(buf); return -1; }
- fwrite(buf, 1, strlen(buf), fp);
- fclose(fp);
- rename(tmp, path);
- free(buf);
- return 0;
- }
- /* ---- Read entire config file into buffer ---- */
- static char *read_config(long *out_size) {
- FILE *fp = fopen(g_config_path, "r");
- if (!fp) return NULL;
- fseek(fp, 0, SEEK_END);
- long sz = ftell(fp);
- fseek(fp, 0, SEEK_SET);
- char *buf = malloc(sz + 1);
- if (!buf) { fclose(fp); return NULL; }
- size_t rd = fread(buf, 1, sz, fp);
- buf[rd] = '\0';
- fclose(fp);
- *out_size = rd;
- return buf;
- }
- /* Find "[section]" only at the start of a line (skips comments that mention it). */
- static char *find_section(const char *buf, const char *section) {
- char pat[64];
- snprintf(pat, sizeof(pat), "[%s]", section);
- const char *p = buf;
- while ((p = strstr(p, pat)) != NULL) {
- /* must be at line start (or after leading spaces) */
- const char *ls = p;
- while (ls > buf && ls[-1] != '\n') ls--;
- const char *q = ls;
- while (*q == ' ' || *q == '\t') q++;
- if (q == p && (ls == buf || ls[-1] == '\n'))
- return (char *)p;
- p += strlen(pat);
- }
- return NULL;
- }
- /* Parse "[web] ifaces = eth0 eth1 ..." from the config file into the
- * whitelist. Missing/empty = all non-loopback interfaces allowed. */
- static void load_iface_whitelist(void) {
- g_iface_wn = 0;
- long sz;
- char *cfg = g_config_path ? read_config(&sz) : NULL;
- if (!cfg) return;
- char *sec = find_section(cfg, "web");
- if (sec) {
- const char *line = strchr(sec, '\n');
- if (line) line += 1; /* skip the "[web]" header itself */
- while (line && *line) {
- const char *nl = strchr(line, '\n');
- size_t len = nl ? (size_t)(nl - line) : strlen(line);
- if (len > 0) {
- char lb[128];
- size_t cp = len < sizeof(lb) - 1 ? len : sizeof(lb) - 1;
- memcpy(lb, line, cp);
- lb[cp] = '\0';
- char *p = lb;
- while (*p == ' ' || *p == '\t') p++;
- if (p[0] == '[') break; /* next section */
- char *eq = strchr(p, '=');
- if (eq) {
- char *k = p;
- *eq = '\0';
- char *ke = eq - 1;
- while (ke >= k && (*ke == ' ' || *ke == '\t')) *ke-- = '\0';
- if (strcmp(k, "ifaces") == 0) {
- char *v = eq + 1;
- char *tok = strtok(v, " ,\t\r\n");
- while (tok && g_iface_wn < 8) {
- snprintf(g_iface_wl[g_iface_wn], IFNAMSIZ, "%s", tok);
- g_iface_wn++;
- tok = strtok(NULL, " ,\t\r\n");
- }
- }
- }
- }
- if (!nl) break;
- line = nl + 1;
- }
- }
- free(cfg);
- }
- static int iface_allowed(const char *name) {
- if (g_iface_wn == 0) return 1;
- for (int i = 0; i < g_iface_wn; i++)
- if (strcmp(g_iface_wl[i], name) == 0) return 1;
- return 0;
- }
- /* ---- Write config file ---- */
- static int write_config(const char *buf, size_t len) {
- (void)len;
- FILE *fp = fopen(g_config_path, "w");
- if (!fp) return -1;
- fwrite(buf, 1, strlen(buf), fp);
- fclose(fp);
- return 0;
- }
- /* ---- Replace a key=value line within a [section] ---- */
- static void replace_key(char *buf, const char *section,
- const char *key, const char *newval) {
- char *sec = find_section(buf, section);
- if (!sec) return;
- char *line = sec;
- while (line && *line) {
- if (*line == '[' && line != sec) break;
- char *eol = strchr(line, '\n');
- size_t linelen = eol ? (size_t)(eol - line) : strlen(line);
- const char *p = line;
- while (*p == ' ' || *p == '\t') p++;
- if (*p == '#' || *p == '[' || *p == '\n' || *p == '\r' || !*p) {
- line = eol ? eol + 1 : NULL;
- continue;
- }
- size_t klen = strlen(key);
- if (strncmp(p, key, klen) == 0 &&
- (p[klen] == ' ' || p[klen] == '\t' || p[klen] == '=')) {
- size_t indent = (size_t)(p - line);
- char newline[512];
- int n = snprintf(newline, sizeof(newline), "%.*s%s = %s",
- (int)indent, line, key, newval);
- if (eol) newline[n++] = '\n';
- newline[n] = '\0';
- size_t newlen = (size_t)n;
- size_t oldlen = linelen + (eol ? 1 : 0);
- size_t off = (size_t)(line - buf);
- size_t total = strlen(buf) + 1;
- memmove(line + newlen, line + oldlen, total - off - oldlen);
- memcpy(line, newline, newlen);
- return;
- }
- line = eol ? eol + 1 : NULL;
- }
- /* Key not present: insert it right after the section header so new
- * options (e.g. debounce_ms) can be added via the web page too. */
- {
- char *nl = strchr(sec, '\n');
- if (!nl) return;
- size_t insert_off = (size_t)(nl - buf) + 1;
- char addline[512];
- int n = snprintf(addline, sizeof(addline), "%s = %s\n", key, newval);
- if (n < 0 || (size_t)n >= sizeof(addline)) return;
- size_t total = strlen(buf) + 1;
- memmove(buf + insert_off + n, buf + insert_off, total - insert_off);
- memcpy(buf + insert_off, addline, (size_t)n);
- }
- }
- /* ---- Replace a line in [camera_list] by camera ID ---- */
- static void replace_camera_line(char *buf, int cam_id, const char *new_ip) {
- char *sec = find_section(buf, "camera_list");
- if (!sec) return;
- char idpat[32];
- snprintf(idpat, sizeof(idpat), "%d ", cam_id);
- char *line = sec;
- while (line && *line) {
- if (*line == '[' && line != sec) break;
- char *eol = strchr(line, '\n');
- size_t linelen = eol ? (size_t)(eol - line) : strlen(line);
- const char *p = line;
- while (*p == ' ' || *p == '\t') p++;
- if (*p == '#' || *p == '\n' || *p == '\r' || !*p) {
- line = eol ? eol + 1 : NULL;
- continue;
- }
- if (strncmp(p, idpat, strlen(idpat)) == 0) {
- const char *fields = p + strlen(idpat);
- char user[64] = {0}, pass[64] = {0};
- int chan = 1, port = 554, httpport = 80;
- char dummy_ip[64];
- sscanf(fields, "%63s %63s %63s %d %d %d",
- dummy_ip, user, pass, &chan, &port, &httpport);
- char newline[512];
- int n = snprintf(newline, sizeof(newline), "%d %s %s %s %d %d %d",
- cam_id, new_ip, user, pass, chan, port, httpport);
- if (eol) newline[n++] = '\n';
- newline[n] = '\0';
- size_t newlen = (size_t)n;
- size_t oldlen = linelen + (eol ? 1 : 0);
- size_t off = (size_t)(line - buf);
- size_t total = strlen(buf) + 1;
- memmove(line + newlen, line + oldlen, total - off - oldlen);
- memcpy(line, newline, newlen);
- return;
- }
- line = eol ? eol + 1 : NULL;
- }
- }
- /* ---- HTML page ---- */
- static const char *HTML_HEAD =
- "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n"
- "<!DOCTYPE html><html><head><meta charset=\"utf-8\">"
- "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
- "<title>多路视频配置</title>"
- "<style>"
- "body{font-family:system-ui,sans-serif;margin:0;padding:0;background:#1a1a2e;color:#eee}"
- ".box{max-width:700px;margin:40px auto;background:#16213e;border-radius:12px;padding:30px;box-shadow:0 4px 24px rgba(0,0,0,.5)}"
- "h1{text-align:center;color:#e94560;margin-bottom:8px;font-size:1.5em}"
- "h2{color:#0f3460;background:#e94560;padding:8px 12px;border-radius:6px;margin:24px 0 12px;font-size:1em}"
- "table{width:100%;border-collapse:collapse;margin-bottom:16px}"
- "td{padding:6px 8px;border-bottom:1px solid #0f3460}"
- "td:first-child{width:80px;color:#a0a0b0;font-size:.9em}"
- "input[type=text],input[type=password]{width:100%;padding:8px;border:1px solid #0f3460;"
- "border-radius:6px;background:#1a1a2e;color:#eee;font-size:.95em;box-sizing:border-box}"
- "input:focus{outline:none;border-color:#e94560}"
- ".btn{display:block;width:100%;padding:12px;border:none;border-radius:8px;"
- "background:#e94560;color:#fff;font-size:1.1em;cursor:pointer;margin-top:20px}"
- ".btn:hover{background:#c73550}"
- ".ok{background:#27ae60}.ok:hover{background:#219a52}"
- ".info{text-align:center;color:#a0a0b0;font-size:.85em;margin-top:16px}"
- "a{color:#e94560}"
- "</style></head><body>";
- /* ---- Login page ---- */
- static const char *LOGIN_PAGE =
- "<div class=\"box\">"
- "<h1>多路视频配置</h1>"
- "<form method=POST action=\"/login\">"
- "<table><tr><td><label for=pwd>密码</label></td>"
- "<td><input type=password id=pwd name=pwd autofocus></td></tr></table>"
- "<button class=btn type=submit>登录</button>"
- "</form></div>";
- /* ---- Config page ---- */
- static void send_config_page(int fd, const char *body, const char *query) {
- char *cfg = NULL;
- long cfglen = 0;
- /* Handle POST (save) */
- int saved = 0;
- if (body && body[0]) {
- cfg = read_config(&cfglen);
- if (cfg) {
- char val[256];
- /* Camera IPs: cam0..cam7 */
- for (int i = 0; i < MAX_CAMS; i++) {
- char key[32];
- snprintf(key, sizeof(key), "cam%d_ip", i);
- if (post_value(body, key, val, sizeof(val)) == 0 && val[0]) {
- replace_camera_line(cfg, i, val);
- }
- }
- /* PLC settings */
- if (post_value(body, "plc_ip", val, sizeof(val)) == 0 && val[0])
- replace_key(cfg, "plc", "ip", val);
- if (post_value(body, "plc_port", val, sizeof(val)) == 0 && val[0])
- replace_key(cfg, "plc", "port", val);
- if (post_value(body, "plc_db", val, sizeof(val)) == 0 && val[0])
- replace_key(cfg, "plc", "db", val);
- if (post_value(body, "plc_read_len", val, sizeof(val)) == 0 && val[0])
- replace_key(cfg, "plc", "read_len", val);
- if (post_value(body, "plc_tsap_local", val, sizeof(val)) == 0 && val[0])
- replace_key(cfg, "plc", "tsap_local", val);
- if (post_value(body, "plc_tsap_remote", val, sizeof(val)) == 0 && val[0])
- replace_key(cfg, "plc", "tsap_remote", val);
- if (post_value(body, "plc_debounce_ms", val, sizeof(val)) == 0 && val[0])
- replace_key(cfg, "plc", "debounce_ms", val);
- if (post_value(body, "plc_enable", val, sizeof(val)) == 0)
- replace_key(cfg, "plc", "enable", val);
- /* Write config */
- if (write_config(cfg, cfglen) == 0) saved = 1;
- free(cfg);
- cfg = NULL;
- }
- }
- /* Read current config for display */
- cfg = read_config(&cfglen);
- char cam_ips[MAX_CAMS][64];
- char cam_user[MAX_CAMS][64];
- char cam_pass[MAX_CAMS][64];
- int cam_port[MAX_CAMS];
- int cam_httpport[MAX_CAMS];
- int cam_chan[MAX_CAMS];
- int cam_count = 0;
- char plc_ip[64] = {0};
- int plc_port_val = 102;
- int plc_db = 1000;
- int plc_read_len = 10;
- int plc_enabled = 0;
- char plc_tsap_local[32] = {0};
- char plc_tsap_remote[32] = {0};
- int plc_debounce_ms = 300;
- if (cfg) {
- /* parse [camera_list] */
- char *sec = find_section(cfg, "camera_list");
- if (sec) {
- char *ns = strchr(sec + 1, '[');
- char *line = sec;
- while (line && (!ns || line < ns)) {
- char *eol = strchr(line, '\n');
- const char *p = line;
- while (*p == ' ' || *p == '\t') p++;
- if (*p != '#' && *p != '[' && *p != '\n' && *p != '\r' && *p) {
- int id;
- char ip[64] = {0}, user[64] = {0}, pass[64] = {0};
- int chan = 1, port = 554, httpport = 80;
- if (sscanf(p, "%d %63s %63s %63s %d %d %d",
- &id, ip, user, pass, &chan, &port, &httpport) >= 4) {
- if (id >= 0 && id < MAX_CAMS) {
- strncpy(cam_ips[id], ip, 63);
- strncpy(cam_user[id], user, 63);
- strncpy(cam_pass[id], pass, 63);
- cam_port[id] = port;
- cam_httpport[id] = httpport;
- cam_chan[id] = chan;
- if (id + 1 > cam_count) cam_count = id + 1;
- }
- }
- }
- line = eol ? eol + 1 : NULL;
- }
- }
- /* parse [plc] */
- sec = find_section(cfg, "plc");
- if (sec) {
- char *ns = strchr(sec + 1, '[');
- char *line = sec;
- while (line && (!ns || line < ns)) {
- char key[64], val[256] = {0};
- const char *p = line;
- while (*p == ' ' || *p == '\t') p++;
- if (*p != '#' && *p != '[' && *p != '\n' && *p != '\r' && *p &&
- sscanf(p, "%63s = %255[^\n\r]", key, val) >= 2) {
- /* trim leading spaces in val */
- const char *v = val;
- while (*v == ' ' || *v == '\t') v++;
- if (strcmp(key, "ip") == 0) strncpy(plc_ip, v, 63);
- else if (strcmp(key, "port") == 0) plc_port_val = atoi(v);
- else if (strcmp(key, "db") == 0) plc_db = atoi(v);
- else if (strcmp(key, "read_len") == 0) plc_read_len = atoi(v);
- else if (strcmp(key, "enable") == 0) plc_enabled = atoi(v);
- else if (strcmp(key, "tsap_local") == 0) strncpy(plc_tsap_local, v, 31);
- else if (strcmp(key, "tsap_remote") == 0) strncpy(plc_tsap_remote, v, 31);
- else if (strcmp(key, "debounce_ms") == 0) plc_debounce_ms = atoi(v);
- }
- char *eol = strchr(line, '\n');
- line = eol ? eol + 1 : NULL;
- }
- }
- free(cfg);
- }
- /* Build response */
- char resp[16384];
- int off = 0;
- off += snprintf(resp + off, sizeof(resp) - off, "%s", HTML_HEAD);
- off += snprintf(resp + off, sizeof(resp) - off,
- "<div class=\"box\">"
- "<h1>多路视频配置</h1>"
- "<p style=\"text-align:center\"><a href=\"/net\">本机网络 IP 设置</a></p>");
- if (saved) {
- off += snprintf(resp + off, sizeof(resp) - off,
- "<p style=\"background:#27ae60;padding:10px;border-radius:6px;text-align:center\">"
- "配置已保存!服务将在 2 秒后重启...</p>");
- }
- off += snprintf(resp + off, sizeof(resp) - off,
- "<form method=POST action=\"/config\">"
- "<h2>摄像机设置</h2>"
- "<table>"
- "<tr><td>编号</td><td>IP 地址</td></tr>");
- for (int i = 0; i < cam_count && i < MAX_CAMS; i++) {
- char key_ip[32], key_port[32], key_http[32], key_user[32], key_pass[32], key_chan[32];
- snprintf(key_ip, sizeof(key_ip), "cam%d_ip", i);
- off += snprintf(resp + off, sizeof(resp) - off,
- "<tr><td>%d</td><td>"
- "<input type=text name=%s value=\"%s\" placeholder=\"IP 地址\">"
- "</td></tr>",
- i, key_ip, cam_ips[i]);
- }
- off += snprintf(resp + off, sizeof(resp) - off,
- "</table>"
- "<h2>PLC 设置</h2>"
- "<table>"
- "<tr><td>启用</td><td><input type=text name=plc_enable value=\"%d\" placeholder=\"1=开 0=关\"></td></tr>"
- "<tr><td>IP 地址</td><td><input type=text name=plc_ip value=\"%s\"></td></tr>"
- "<tr><td>端口</td><td><input type=text name=plc_port value=\"%d\"></td></tr>"
- "<tr><td>数据块</td><td><input type=text name=plc_db value=\"%d\"></td></tr>"
- "<tr><td>读取长度</td><td><input type=text name=plc_read_len value=\"%d\"></td></tr>"
- "<tr><td>TSAP 本地</td><td><input type=text name=plc_tsap_local value=\"%s\" placeholder=\"0x0100\"></td></tr>"
- "<tr><td>TSAP 远端</td><td><input type=text name=plc_tsap_remote value=\"%s\" placeholder=\"0x0101\"></td></tr>"
- "<tr><td>去抖 (ms)</td><td><input type=text name=plc_debounce_ms value=\"%d\" placeholder=\"300 (0=关闭)\"></td></tr>"
- "</table>"
- "<button class=btn type=submit>保存并重启</button>"
- "</form>"
- "<p class=info>修改内容需重启服务后生效。</p>"
- "</div></body></html>",
- plc_enabled, plc_ip, plc_port_val, plc_db, plc_read_len,
- plc_tsap_local, plc_tsap_remote, plc_debounce_ms);
- send_bytes(fd, resp, off);
- }
- /* ---- Local network page: show all IPs, change device IP ---- */
- static void send_net_page(int fd, const char *body) {
- char resp[16384];
- int off = 0;
- off += snprintf(resp + off, sizeof(resp) - off, "%s", HTML_HEAD);
- struct net_if nets[16];
- int nn = collect_nets(nets, 16);
- const char *applied_if = NULL, *applied_ip = NULL;
- int applied = 0;
- char msg[256] = "";
- if (body && body[0]) {
- char iface[32] = {0}, ip[64] = {0}, mask[64] = {0}, gw[64] = {0}, mode[8] = {0};
- post_value(body, "iface", iface, sizeof(iface));
- post_value(body, "ip", ip, sizeof(ip));
- post_value(body, "mask", mask, sizeof(mask));
- post_value(body, "gw", gw, sizeof(gw));
- post_value(body, "mode", mode, sizeof(mode));
- int add_mode = (strcmp(mode, "add") == 0);
- if (!iface[0] || !iface_exists(iface)) {
- snprintf(msg, sizeof(msg), "\u9519\u8BEF: \u7F51\u5361 %s \u4E0D\u5B58\u5728\u6216\u672A\u9009\u62E9",
- iface[0] ? iface : "?");
- } else if (!is_ipv4(ip)) {
- snprintf(msg, sizeof(msg), "\u9519\u8BEF: IP \u5730\u5740 %s \u4E0D\u5408\u6CD5", ip);
- } else if (!mask[0] || !is_ipv4(mask)) {
- snprintf(msg, sizeof(msg), "\u9519\u8BEF: \u5B50\u7F51\u63A9\u7801 %s \u4E0D\u5408\u6CD5", mask[0] ? mask : "?");
- } else if (!add_mode && gw[0] && !is_ipv4(gw)) {
- snprintf(msg, sizeof(msg), "\u9519\u8BEF: \u7F51\u5173 %s \u4E0D\u5408\u6CD5", gw);
- } else if (!is_safe_net_arg(iface) || !is_safe_net_arg(ip) ||
- !is_safe_net_arg(mask) || (gw[0] && !is_safe_net_arg(gw))) {
- snprintf(msg, sizeof(msg), "\u9519\u8BEF: \u53C2\u6570\u5305\u542B\u4E0D\u5141\u8BB8\u5B57\u7B26");
- } else {
- int pfx = netmask_to_prefix(mask);
- char cmd[768];
- char conname[128] = "";
- int use_nm = nm_running() && nm_managed(iface) &&
- nm_conn_name(iface, conname, sizeof(conname)) &&
- nm_safe_name(conname);
- if (use_nm && add_mode) {
- /* keep every existing address, add the segment via NetworkManager */
- snprintf(cmd, sizeof(cmd),
- "nmcli con mod '%s' +ipv4.addresses %s/%d 2>/dev/null; "
- "nmcli dev reapply %s 2>/dev/null",
- conname, ip, pfx, iface);
- system(cmd);
- applied = 1;
- applied_if = iface;
- applied_ip = ip;
- nn = collect_nets(nets, 16);
- snprintf(msg, sizeof(msg),
- "\u5DF2\u6DFB\u52A0\u65B0\u7F51\u6BB5: %s + %s/%d\u3010\u4FDD\u7559\u73B0\u6709 "
- "IP\u3011\uFF0C\u5DF2\u901A\u8FC7 NetworkManager \u6301\u4E45\u5316 "
- "(\u8FDE\u63A5 \"%s\")\u3002",
- iface, ip, pfx, conname);
- } else if (use_nm) {
- if (gw[0] && is_ipv4(gw))
- snprintf(cmd, sizeof(cmd),
- "nmcli con mod '%s' ipv4.method manual ipv4.addresses %s/%d "
- "ipv4.gateway %s 2>/dev/null; nmcli con up '%s' 2>/dev/null",
- conname, ip, pfx, gw, conname);
- else
- snprintf(cmd, sizeof(cmd),
- "nmcli con mod '%s' ipv4.method manual ipv4.addresses %s/%d "
- "2>/dev/null; nmcli con up '%s' 2>/dev/null",
- conname, ip, pfx, conname);
- system(cmd);
- applied = 1;
- applied_if = iface;
- applied_ip = ip;
- nn = collect_nets(nets, 16);
- snprintf(msg, sizeof(msg),
- "\u5DF2\u66FF\u6362: %s \u2192 %s/%s (CIDR /%d); \u5DF2\u901A\u8FC7 "
- "NetworkManager \u6301\u4E45\u5316 (\u8FDE\u63A5 \"%s\")\u3002"
- "\u540E\u7EED\u8BBF\u95EE\u8BF7\u7528\u65B0 \u5730\u5740\u3002",
- iface, ip, mask, pfx, conname);
- } else if (add_mode) {
- /* keep every existing address, just add the new segment */
- snprintf(cmd, sizeof(cmd),
- "ip link set %s up 2>/dev/null; ip addr add %s/%d dev %s 2>/dev/null",
- iface, ip, pfx, iface);
- system(cmd);
- char cidr[96];
- snprintf(cidr, sizeof(cidr), "%s/%d", ip, pfx);
- persist_add_addr(iface, ip, mask, cidr);
- applied = 1;
- applied_if = iface;
- applied_ip = ip;
- nn = collect_nets(nets, 16);
- snprintf(msg, sizeof(msg),
- "\u5DF2\u6DFB\u52A0\u65B0\u7F51\u6BB5: %s + %s/%d\u3010\u4FDD\u7559\u73B0\u6709 "
- "IP\u3011\uFF0C\u5DF2\u5199\u5165 /etc/network/interfaces\u3002",
- iface, ip, pfx);
- } else {
- snprintf(cmd, sizeof(cmd),
- "ip addr flush dev %s 2>/dev/null; ip link set %s up 2>/dev/null; "
- "ip addr add %s/%d dev %s 2>/dev/null",
- iface, iface, ip, pfx, iface);
- system(cmd);
- if (gw[0] && is_ipv4(gw)) {
- snprintf(cmd, sizeof(cmd), "ip route replace default via %s 2>/dev/null", gw);
- system(cmd);
- }
- persist_interfaces(iface, ip, mask, gw[0] ? gw : "");
- applied = 1;
- applied_if = iface;
- applied_ip = ip;
- nn = collect_nets(nets, 16);
- snprintf(msg, sizeof(msg),
- "\u5DF2\u66FF\u6362: %s \u2192 %s/%s (CIDR /%d); \u5DF2\u5199\u5165 "
- "/etc/network/interfaces \u3002\u540E\u7EED\u8BBF\u95EE\u8BF7\u7528\u65B0 "
- "\u5730\u5740\u3002", iface, ip, mask, pfx);
- }
- }
- }
- off += snprintf(resp + off, sizeof(resp) - off,
- "<div class=\"box\">"
- "<h1>多路视频配置 - 本机网络</h1>"
- "<p style=\"text-align:center\"><a href=\"/config\">← 返回配置页面</a></p>");
- if (applied) {
- off += snprintf(resp + off, sizeof(resp) - off,
- "<p style=\"background:#27ae60;padding:10px;border-radius:6px;text-align:center\">%s</p>",
- msg);
- } else if (msg[0]) {
- off += snprintf(resp + off, sizeof(resp) - off,
- "<p style=\"background:#c0392b;padding:10px;border-radius:6px;text-align:center\">%s</p>",
- msg);
- }
- off += snprintf(resp + off, sizeof(resp) - off,
- "<h2>当前网卡 IP (多网段全部列出)</h2>"
- "<table><tr><td>网卡</td><td>IP 地址</td><td>子网掩码</td></tr>");
- for (int i = 0; i < nn; i++) {
- off += snprintf(resp + off, sizeof(resp) - off,
- "<tr><td>%s</td><td>%s</td><td>%s</td></tr>",
- nets[i].iface, nets[i].ip, nets[i].mask);
- }
- off += snprintf(resp + off, sizeof(resp) - off, "</table>");
- /* default values: first interface with an address, current ip/mask */
- char def_if[IFNAMSIZ] = "", def_ip[64] = "", def_mask[64] = "255.255.255.0";
- if (nn > 0) {
- snprintf(def_if, sizeof(def_if), "%s", nets[0].iface);
- snprintf(def_ip, sizeof(def_ip), "%s", nets[0].ip);
- if (nets[0].mask[0] && strcmp(nets[0].mask, "0.0.0.0") != 0)
- snprintf(def_mask, sizeof(def_mask), "%s", nets[0].mask);
- }
- /* all interfaces (incl. ones with no IPv4 yet) so a NIC can be seeded */
- char ifaces[16][IFNAMSIZ];
- int nif = collect_ifaces(ifaces, 16);
- if (nif > 0 && !def_if[0]) snprintf(def_if, sizeof(def_if), "%s", ifaces[0]);
- off += snprintf(resp + off, sizeof(resp) - off,
- "<h2>设置本机 IP</h2>"
- "<form method=POST action=\"/net\">"
- "<table>"
- "<tr><td>网卡</td><td><select name=iface>");
- for (int i = 0; i < nif; i++) {
- const char *curip = "\u65E0 IP";
- char has[5] = "";
- for (int j = 0; j < nn; j++) {
- if (strcmp(nets[j].iface, ifaces[i]) == 0) { curip = nets[j].ip; break; }
- }
- snprintf(has, sizeof(has), "%s", strcmp(ifaces[i], def_if) == 0 ? " selected" : "");
- off += snprintf(resp + off, sizeof(resp) - off,
- "<option value=\"%s\"%s>%s (%s)</option>", ifaces[i], has, ifaces[i], curip);
- }
- off += snprintf(resp + off, sizeof(resp) - off,
- "</select></td></tr>"
- "<tr><td>新 IP</td><td><input type=text name=ip value=\"%s\"></td></tr>"
- "<tr><td>子网掩码</td><td><input type=text name=mask value=\"%s\"></td></tr>"
- "<tr><td>网关</td><td><input type=text name=gw placeholder=\"如 192.168.1.1\"></td></tr>"
- "</table>"
- "<button class=btn type=submit name=mode value=replace>替换 / 修改 IP (去掉其他 IP)</button>"
- "<button class=btn type=submit name=mode value=add style=\"background:#27ae60;margin-top:8px\">"
- "保留现有 IP\uFF0C;添加新网段 IP (可多网段)</button>"
- "</form>"
- "<p class=info>警告: 1) [替换] \u4F1A;先清除该网卡所有 IP \u518D;添加,修改当前访问的 IP \u4F1A;断开连接。 "
- "2) [添加] \u4FDD;留现有 IP\uFF0C;只新增一个网段 IP\uFF0C;不改网关。 "
- "3) 已写入 /etc/network/interfaces \u6301;久化,重启后仍生效。</p>"
- "</div></body></html>", def_ip, def_mask);
- send_bytes(fd, resp, off);
- }
- /* ---- HTTP request handler ---- */
- static void handle_client(int cfd) {
- /* Bound each connection so a slow/half-open client cannot stall the
- * other clients (read timeout on the socket). */
- struct timeval rcv_tv = { .tv_sec = 8, .tv_usec = 0 };
- setsockopt(cfd, SOL_SOCKET, SO_RCVTIMEO, &rcv_tv, sizeof(rcv_tv));
- char method[8], path[256], ver[16];
- char line[1024];
- if (recv_line(cfd, line, sizeof(line)) < 0) goto done;
- if (sscanf(line, "%7s %255s %15s", method, path, ver) != 3) goto done;
- /* Read headers (and detect Cookie) */
- char cookie[256] = {0};
- char content_len_hdr[32] = {0};
- while (1) {
- if (recv_line(cfd, line, sizeof(line)) < 0) break;
- if (line[0] == '\n' || line[0] == '\r') break;
- if (strncasecmp(line, "Cookie:", 7) == 0) {
- char *p = line + 7;
- while (*p == ' ') p++;
- strncpy(cookie, p, sizeof(cookie) - 1);
- cookie[strcspn(cookie, "\r\n")] = '\0';
- }
- if (strncasecmp(line, "Content-Length:", 15) == 0) {
- char *p = line + 15;
- while (*p == ' ') p++;
- strncpy(content_len_hdr, p, sizeof(content_len_hdr) - 1);
- content_len_hdr[strcspn(content_len_hdr, "\r\n")] = '\0';
- }
- }
- /* Auth check */
- char sesshash[64];
- snprintf(sesshash, sizeof(sesshash), "sess=%u", simple_hash(g_password));
- int authed = (strstr(cookie, sesshash) != NULL);
- if (strcmp(method, "POST") == 0 && strcmp(path, "/login") == 0) {
- /* Read POST body */
- int clen = atoi(content_len_hdr);
- char body[512] = {0};
- if (clen > 0 && clen < (int)sizeof(body)) {
- int got = 0;
- while (got < clen) {
- ssize_t r = read(cfd, body + got, clen - got);
- if (r <= 0) break;
- got += r;
- }
- }
- char pwd[64] = {0};
- post_value(body, "pwd", pwd, sizeof(pwd));
- if (strcmp(pwd, g_password) == 0) {
- char hdr[256];
- snprintf(hdr, sizeof(hdr),
- "HTTP/1.1 302 Found\r\nSet-Cookie: sess=%u; Path=/\r\n"
- "Location: /config\r\nConnection: close\r\n\r\n",
- simple_hash(g_password));
- send_str(cfd, hdr);
- } else {
- send_str(cfd, "HTTP/1.1 302 Found\r\nLocation: /\r\nConnection: close\r\n\r\n");
- }
- goto done;
- }
- if (!authed) {
- send_str(cfd, HTML_HEAD);
- send_str(cfd, LOGIN_PAGE);
- send_str(cfd, "</body></html>");
- goto done;
- }
- /* Authed requests */
- if (strcmp(path, "/") == 0 && strcmp(method, "GET") == 0) {
- send_str(cfd, "HTTP/1.1 302 Found\r\nLocation: /config\r\nConnection: close\r\n\r\n");
- } else if (strcmp(path, "/config") == 0 && strcmp(method, "GET") == 0) {
- send_config_page(cfd, NULL, NULL);
- } else if (strcmp(path, "/config") == 0 && strcmp(method, "POST") == 0) {
- int clen = atoi(content_len_hdr);
- char body[4096] = {0};
- if (clen > 0 && clen < (int)sizeof(body)) {
- int got = 0;
- while (got < clen) {
- ssize_t r = read(cfd, body + got, clen - got);
- if (r <= 0) break;
- got += r;
- }
- }
- send_config_page(cfd, body, NULL);
- /* Restart service after response is sent */
- usleep(500000);
- system("systemctl restart rtsp_multi_view &");
- } else if (strcmp(path, "/net") == 0 && strcmp(method, "GET") == 0) {
- send_net_page(cfd, NULL);
- } else if (strcmp(path, "/net") == 0 && strcmp(method, "POST") == 0) {
- int clen = atoi(content_len_hdr);
- char body[1024] = {0};
- if (clen > 0 && clen < (int)sizeof(body)) {
- int got = 0;
- while (got < clen) {
- ssize_t r = read(cfd, body + got, clen - got);
- if (r <= 0) break;
- got += r;
- }
- }
- send_net_page(cfd, body);
- } else if (strcmp(path, "/restart") == 0) {
- send_str(cfd, HTML_HEAD);
- send_str(cfd, "<div class=\"box\"><h1>正在重启...</h1>"
- "<p class=info>服务正在重启,请稍候。</p></div></body></html>");
- usleep(500000);
- system("systemctl restart rtsp_multi_view &");
- } else {
- send_str(cfd, HTML_HEAD);
- send_str(cfd, "<div class=\"box\"><h1>页面不存在</h1>"
- "<p class=info>请检查访问地址是否正确。</p></div></body></html>");
- }
- done:
- close(cfd);
- }
- /* ---- One thread per connection: a slow client cannot stall the others ---- */
- static void *client_thread(void *arg) {
- int cfd = (int)(long)arg;
- handle_client(cfd);
- return NULL;
- }
- /* ---- Server thread ---- */
- static void *server_thread(void *arg) {
- (void)arg;
- int lfd = socket(AF_INET, SOCK_STREAM, 0);
- if (lfd < 0) return NULL;
- int one = 1;
- setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
- struct sockaddr_in sa = {0};
- sa.sin_family = AF_INET;
- sa.sin_addr.s_addr = INADDR_ANY;
- sa.sin_port = htons(g_port);
- if (bind(lfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
- fprintf(stderr, "web: bind port %d failed: %s\n", g_port, strerror(errno));
- close(lfd);
- return NULL;
- }
- listen(lfd, 8);
- fprintf(stderr, "web: listening on port %d\n", g_port);
- while (1) {
- struct sockaddr_in cli;
- socklen_t clilen = sizeof(cli);
- int cfd = accept(lfd, (struct sockaddr *)&cli, &clilen);
- if (cfd < 0) continue;
- pthread_t tid;
- if (pthread_create(&tid, NULL, client_thread, (void *)(long)cfd) != 0)
- close(cfd);
- else
- pthread_detach(tid);
- }
- close(lfd);
- return NULL;
- }
- int web_server_start(const char *config_path, int port, const char *password) {
- g_config_path = config_path;
- g_port = port > 0 ? port : WEB_SERVER_PORT_DEFAULT;
- g_password = password && password[0] ? password : WEB_SERVER_PASS_DEFAULT;
- load_iface_whitelist();
- pthread_t thr;
- if (pthread_create(&thr, NULL, server_thread, NULL) != 0) return -1;
- pthread_detach(thr);
- return 0;
- }
|