#include "web_server.h" #include #include #include #include #include #include #include #include #include #include #include #include #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 (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 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 " 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 dev " 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" "" "" "多路视频配置" ""; /* ---- Login page ---- */ static const char *LOGIN_PAGE = "
" "

多路视频配置

" "
" "" "
" "" "
"; /* ---- 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, "
" "

多路视频配置

" "

本机网络 IP 设置

"); if (saved) { off += snprintf(resp + off, sizeof(resp) - off, "

" "配置已保存!服务将在 2 秒后重启...

"); } off += snprintf(resp + off, sizeof(resp) - off, "
" "

摄像机设置

" "" ""); 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, "", i, key_ip, cam_ips[i]); } off += snprintf(resp + off, sizeof(resp) - off, "
编号IP 地址
%d" "" "
" "

PLC 设置

" "" "" "" "" "" "" "" "" "" "
启用
IP 地址
端口
数据块
读取长度
TSAP 本地
TSAP 远端
去抖 (ms)
" "" "
" "

修改内容需重启服务后生效。

" "
", 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, "
" "

多路视频配置 - 本机网络

" "

← 返回配置页面

"); if (applied) { off += snprintf(resp + off, sizeof(resp) - off, "

%s

", msg); } else if (msg[0]) { off += snprintf(resp + off, sizeof(resp) - off, "

%s

", msg); } off += snprintf(resp + off, sizeof(resp) - off, "

当前网卡 IP (多网段全部列出)

" ""); for (int i = 0; i < nn; i++) { off += snprintf(resp + off, sizeof(resp) - off, "", nets[i].iface, nets[i].ip, nets[i].mask); } off += snprintf(resp + off, sizeof(resp) - off, "
网卡IP 地址子网掩码
%s%s%s
"); /* 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, "

设置本机 IP

" "
" "" "" "" "" "" "
网卡
新 IP
子网掩码
网关
" "" "" "
" "

警告: 1) [替换] \u4F1A;先清除该网卡所有 IP \u518D;添加,修改当前访问的 IP \u4F1A;断开连接。 " "2) [添加] \u4FDD;留现有 IP\uFF0C;只新增一个网段 IP\uFF0C;不改网关。 " "3) 已写入 /etc/network/interfaces \u6301;久化,重启后仍生效。

" "
", 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, ""); 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, "

正在重启...

" "

服务正在重启,请稍候。

"); usleep(500000); system("systemctl restart rtsp_multi_view &"); } else { send_str(cfd, HTML_HEAD); send_str(cfd, "

页面不存在

" "

请检查访问地址是否正确。

"); } 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; }