💾 Archived View for gemini.rmf-dev.com › repo › Vaati › RouterMonitor › files › ceb78cbbdf3af3aa556c… captured on 2023-09-28 at 16:48:59. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-09-08)
-=-=-=-=-=-=-
0 #include <stdio.h>
1 #include <stdlib.h>
2 #include <stdarg.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <sys/types.h>
6 #include <sys/sysctl.h>
7 #include <sys/socket.h>
8 #include <ifaddrs.h>
9 #include <netdb.h>
10 #include <net/if.h>
11 #include <signal.h>
12 #include <time.h>
13 #include <sys/stat.h>
14 #include <sys/wait.h>
15 #include <pwd.h>
16 #include <errno.h>
17
18 // PF
19 #include <sys/socket.h>
20 #include <sys/ioctl.h>
21 #include <net/if.h>
22 #include <net/pfvar.h>
23 #include <fcntl.h>
24
25 #include "config.h"
26 #define GEMINI_PART 9
27 #define LEASES "/var/db/dhcpd.leases"
28
29 #include <err.h>
30 #include <syslog.h>
31
32 int fnv(const void* buf, size_t len, uint32_t hval) {
33 unsigned char *bp = (unsigned char *)buf;
34 unsigned char *be = bp + len;
35 while (bp < be) {
36 hval += (hval << 1) + (hval << 4) + (hval << 7) +
37 (hval << 8) + (hval << 24);
38 hval ^= (uint32_t)*bp++;
39 }
40 return hval;
41 }
42
43 void status(const int code, const char *file_mime)
44 {
45 printf("%i %s\r\n", code, file_mime);
46 fflush(stdout);
47 }
48
49 void errlogn(const char *format, ...) {
50 status(50, "FATAL ERROR");
51
52 char e[1024] = {'\0'};
53 va_list ap;
54
55 va_start(ap, format);
56 vsnprintf(e, sizeof(e), format, ap);
57 va_end(ap);
58
59 syslog(LOG_DAEMON, "%s", e);
60 exit(2);
61 }
62
63 void errlog(const char *format, ...)
64 {
65 char e[1024] = {'\0'};
66 va_list ap;
67
68 va_start(ap, format);
69 vsnprintf(e, sizeof(e), format, ap);
70 va_end(ap);
71
72 syslog(LOG_DAEMON, "%s", e);
73 exit(1);
74 }
75
76 const char* index_template =
77 "# "SYSTEM_NAME"\n\n"
78 "## System Info\n\n"
79 "* OS: %s %s\n"
80 "* CPU: %s (%d)\n"
81 "* Uptime: %s\n"
82 "* Memory: %s\n\n"
83 "## Network Info\n\n"
84 "%s"
85 #if DHCP
86 "## DHCP Leases\n\n"
87 "%s"
88 #endif
89 #if BLOCKLIST
90 "## IP Blacklist\n"
91 "%s\n"
92 #endif
93 ;
94
95 const char* if_template =
96 "%s"
97 "### %s\n"
98 "IP: %s\n"
99 "Bytes: %.2f GB\n"
100 "Packets: %lld\n\n";
101
102 #if DHCP
103 const char* dhcp_template =
104 "%s"
105 "### %s\n"
106 "IP: %s\n"
107 "Start: %s %s\n"
108 "End: %s %s\n\n";
109 #endif
110
111 #if BLOCKLIST
112 const char* block_template =
113 "%s"
114 "* %s\n";
115 #endif
116
117
118 int pid[2] = {-1, -1};
119
120 int dev_pf = -1;
121
122 void pfinit() {
123 dev_pf = open("/dev/pf", O_RDWR);
124 if (dev_pf < 0)
125 errlog("Failed open pf\n");
126 struct pf_status status;
127 if (ioctl(dev_pf, DIOCGETSTATUS, &status) == -1)
128 errlog("Failed to retrieve pf status\n");
129 if (!status.running)
130 errlog("PF is disabled\n");
131 }
132
133 struct pfr_addr* getpfaddrs(char* table, struct pfr_addr* addr, ssize_t* len) {
134 if (dev_pf == -1)
135 errlog("File description uninitialized\n");
136
137 struct pfioc_table io;
138 struct pfr_table tbl;
139
140 explicit_bzero(&io, sizeof(io));
141 explicit_bzero(&tbl, sizeof(tbl));
142 strlcpy(tbl.pfrt_name, table, sizeof(tbl.pfrt_name));
143 io.pfrio_table = tbl;
144 io.pfrio_buffer = addr;
145 io.pfrio_esize = sizeof(*addr);
146 io.pfrio_size = *len;
147 if (ioctl(dev_pf, DIOCRGETADDRS, &io))
148 errlog("Failed to fetch addresses from pf table\n");
149 if (io.pfrio_size > *len)
150 return NULL;
151 *len = io.pfrio_size;
152 return addr;
153 }
154
155 struct if_info {
156 unsigned long long bytes;
157 unsigned long long packets;
158 char name[64];
159 char host[NI_MAXHOST];
160 };
161
162 struct if_info ifinfo[MAX_INTERFACES];
163 struct if_info *getifinfo(char* name) {
164 for (int i=0; i<MAX_INTERFACES; i++) {
165 if (ifinfo[i].name[0] == '\0') {
166 strncpy(ifinfo[i].name, name, 64);
167 return &ifinfo[i];
168 }
169 if (strncmp(ifinfo[i].name, name, sizeof(ifinfo[i].name)) == 0)
170 return &ifinfo[i];
171 }
172 return NULL;
173 }
174
175 void fetchif(int fd) {
176 explicit_bzero(ifinfo, sizeof(ifinfo));
177 struct ifaddrs *ifap;
178 if (getifaddrs(&ifap)) {
179 syslog(LOG_DAEMON, "Failed to fetch network interfaces(%d)\n",
180 errno);
181 return;
182 }
183 for (struct ifaddrs* ifp = ifap; ifp; ifp=ifp->ifa_next) {
184 if (strlen(ifp->ifa_name)>1 && ifp->ifa_name[0] == 'l' &&
185 ifp->ifa_name[1] == 'o')
186 continue;
187 int family = ifp->ifa_addr->sa_family;
188 if (family == AF_LINK && ifp->ifa_data != NULL) {
189 struct if_data *stats = ifp->ifa_data;
190 if (stats->ifi_ipackets == 0 && stats->ifi_ibytes == 0)
191 continue;
192 struct if_info *info = getifinfo(ifp->ifa_name);
193 if (!info) {
194 syslog(LOG_DAEMON,
195 "Too many network interface (above %d)\n",
196 MAX_INTERFACES);
197 break;
198 }
199 info->bytes = stats->ifi_ibytes;
200 info->packets = stats->ifi_ipackets;
201 continue;
202 }
203 if (family != AF_INET && family != AF_INET6)
204 continue;
205 char hbuf[NI_MAXHOST];
206 if (getnameinfo(ifp->ifa_addr,
207 sizeof(*(ifp->ifa_addr)), hbuf, sizeof(hbuf),
208 NULL, 0, NI_NUMERICHOST) == 0) {
209 struct if_info *info = getifinfo(ifp->ifa_name);
210 if (!info) {
211 syslog(LOG_DAEMON,
212 "Too many network interface (above %d)\n",
213 MAX_INTERFACES);
214 break;
215 }
216 strncpy(info->host, hbuf, NI_MAXHOST);
217 }
218 }
219 freeifaddrs(ifap);
220 char ifbuf[4096];
221 ifbuf[0] = '\0';
222 for (int i=0; i<MAX_INTERFACES && ifinfo[i].name[0]; i++) {
223 snprintf(ifbuf, sizeof(ifbuf), if_template, ifbuf,
224 ifinfo[i].name, ifinfo[i].host,
225 (float)ifinfo[i].bytes/1000000000,
226 ifinfo[i].packets);
227 }
228 write(fd, ifbuf, strnlen(ifbuf, sizeof(ifbuf)));
229 }
230
231 int getuptime(char* out, size_t len) {
232 struct timespec boottime;
233 if (clock_gettime(CLOCK_BOOTTIME, &boottime) == -1)
234 return -1;
235 time_t uptime = boottime.tv_sec;
236 if (uptime > 59) {
237 uptime += 30;
238 int days = uptime / (3600*24);
239 uptime %= (3600*24);
240 int hrs = uptime / 3600;
241 uptime %= 3600;
242 int mins = uptime / 60;
243 memset(out, '\0', len);
244 if (days > 0)
245 snprintf(out, len, "%d day%s, ",
246 days, days > 1 ? "s" : "");
247 if (hrs > 0 && mins > 0)
248 snprintf(out, len, "%s%d hour%s, %02d min%s",
249 out, hrs, hrs > 1 ? "s" : "",
250 mins, mins > 1 ? "s" : "");
251 else {
252 if (hrs > 0)
253 snprintf(out, len, "%s%d hour%s",
254 out, hrs, hrs > 1 ? "s" : "");
255 if (mins > 0 || (days == 0 && hrs == 0))
256 snprintf(out, len, "%s%d min%s",
257 out, mins,
258 mins > 1 ? "s" : "");
259 }
260 } else {
261 snprintf(out, len, "%lld", uptime);
262 }
263 return 0;
264 }
265
266 #if DHCP
267 struct lease {
268 char ip[32];
269 char hostname[256];
270 char date[32];
271 char time[32];
272 char date_end[32];
273 char time_end[32];
274 };
275
276 int readleases(struct lease* leases, int len) {
277 FILE* f = fopen(LEASES, "r");
278 if (!f)
279 return -1;
280 char* line = NULL;
281 size_t length = 0;
282 ssize_t read;
283
284 int i = 0;
285 int j = 0;
286 int hostname_found = 0;
287 while ((read = getline(&line, &length, f) != -1)) {
288 if (i >= len) break;
289 if (strstr(line, "lease") && strchr(line, '{') &&
290 strstr(line, "lease") < strchr(line, '{')) {
291 char* ip = strchr(line, ' ')+1;
292 if (!ip)
293 goto failed;
294 char* ptr = strchr(ip, ' ');
295 if (!ptr)
296 goto failed;
297 ptr[0] = '\0';
298 int exist = 0;
299 for (int k=0; k<i; k++) {
300 if (strcmp(ip, leases[k].ip) == 0) {
301 exist = 1;
302 break;
303 }
304 }
305 if (!exist) {
306 strlcpy(leases[i].ip, ip,
307 sizeof(leases[i].ip));
308 j = 1;
309 }
310 } else if (strstr(line, "client-hostname")) {
311 char* hostname = strchr(line, '"')+1;
312 if (!hostname)
313 goto failed;
314 strchr(hostname, '"')[0] = '\0';
315 strlcpy(leases[i].hostname, hostname,
316 sizeof(leases[i].hostname));
317 j++;
318 hostname_found = 1;
319 } else if (strstr(line, "start") || strstr(line, "end")) {
320 int start = 1;
321 char* ptr = strstr(line, "start");
322 if (!ptr) {
323 ptr = strstr(line, "end");
324 start = 0;
325 }
326 int word = 0;
327 int inspace = 0;
328 char* date = NULL;
329 char* time = NULL;
330 for (; *ptr&&*ptr!='\n'; ptr++) {
331 if (*ptr == ' ' || *ptr == '\t')
332 inspace = 1;
333 else if (inspace) {
334 inspace = 0;
335 word++;
336 if (word==2) date = ptr;
337 if (word==3) {
338 time = ptr;
339 break;
340 }
341 }
342 }
343 if (word!=3) goto failed;
344 char* space = strchr(date, ' ');
345 if (!space) goto failed;
346 *space = 0;
347 space = strchr(time, ' ');
348 if (!space) goto failed;
349 *space = 0;
350 strlcpy(start?leases[i].date:leases[i].date_end,
351 date, sizeof(leases[i].date));
352 strlcpy(start?leases[i].time:leases[i].time_end,
353 time, sizeof(leases[i].time));
354 j++;
355 } else if (strchr(line, '}')) {
356 if (!hostname_found) {
357 strlcpy(leases[i].hostname, "Unknown",
358 sizeof(leases[i].hostname));
359 j++;
360 }
361 hostname_found = 0;
362 if (j>3) {
363 time_t t = time(NULL);
364 struct tm tm = *gmtime(&t);
365 int date[3];
366 int time[3];
367 int pos, last;
368 last = pos = 0;
369 char buf[32];
370 strlcpy(buf, leases[i].date_end, 32);
371 for (int i=0; i<32; i++) {
372 int end = 0;
373 if (buf[i] == '\0') end = 1;
374 if (buf[i] == '/' || end) {
375 buf[i] = '\0';
376 date[pos] = strtol(&buf[last],
377 NULL, 10);
378 if (end) break;
379 pos++;
380 last = i+1;
381 }
382 }
383 date[0] -= 1900;
384 date[1] -= 1;
385 time[1] -= 1;
386 strlcpy(buf, leases[i].time_end, 32);
387 pos = last = 0;
388 for (int i=0; i<32; i++) {
389 int end = 0;
390 if (buf[i] == '\0') end = 1;
391 if (buf[i] == ':' || end) {
392 buf[i] = '\0';
393 time[pos] = strtol(&buf[last],
394 NULL, 10);
395 if (end) break;
396 pos++;
397 last = i+1;
398 }
399 }
400 j = 0;
401 if (tm.tm_year>date[0]) continue;
402 if (tm.tm_year<date[0]) { i++; continue; }
403 if (tm.tm_mon>date[1]) continue;
404 if (tm.tm_mon<date[1]) { i++; continue; }
405 if (tm.tm_mday>date[2]) continue;
406 if (tm.tm_mday<date[2]) { i++; continue; }
407 if (tm.tm_hour>time[0]) continue;
408 if (tm.tm_hour<time[0]) { i++; continue; }
409 if (tm.tm_min>time[1]) continue;
410 if (tm.tm_min<time[1]) { i++; continue; }
411 if (tm.tm_sec>time[2]) continue;
412 if (tm.tm_sec<time[2]) { i++; continue; }
413 }
414 j = 0;
415 }
416 }
417 free(line);
418 return i;
419 failed:
420 free(line);
421 syslog(LOG_DAEMON, "Failed to parse leases file, %s\n", LEASES);
422 return -1;
423 }
424 #endif
425
426 #if BLOCKLIST
427 void ipv4str(unsigned int addr, char* buf, unsigned int len) {
428 uint8_t *b = (uint8_t*)&addr;
429 snprintf(buf, len, "%d.%d.%d.%d", b[0], b[1], b[2], b[3]);
430 }
431
432 void fetchaddrs(int fd) {
433 struct pfr_addr addrs[ADDRS];
434 ssize_t length = ADDRS;
435 struct pfr_addr* addrs_ptr = getpfaddrs(TABLE, addrs, &length);
436 char list[4096];
437 list[0] = '\0';
438
439 for (int i=0; addrs_ptr && i<length; i++) {
440 if (strnlen(list, 4096) > sizeof(list)-32) break;
441 char ip[32];
442 ipv4str(addrs_ptr[i].pfra_u._pfra_ip4addr.s_addr,
443 ip, sizeof(ip));
444 snprintf(list, sizeof(list), block_template, list, ip);
445 }
446 write(fd, list, strnlen(list, sizeof(list)));
447 }
448 #endif
449
450 int main(int argc, char* argv[]) {
451
452 if (argc > 1) {
453 printf("0x%x\n", fnv(argv[1], strlen(argv[1]), 1234));
454 return 0;
455 }
456
457 struct passwd *p;
458 if ((p = getpwnam(USER)) == NULL)
459 errlog("Failed to fetch uid\n");
460
461 // Open pf, require root
462 pfinit();
463
464 // Only needs to read leases file
465 if (unveil(LEASES, "r") || unveil(NULL, NULL))
466 errlog("Failed to unveil\n");
467
468 // Drop privileges
469 if (setgroups(0, NULL))
470 errlog("Failed to set process groups\n");
471 if (setgid(p->pw_gid))
472 errlog("Failed to set process gid\n");
473 if (setuid(p->pw_uid))
474 errlog("Failed to set process uid\n");
475
476 #if REQUEST
477 // Read request from stdin
478 char request[1024];
479 if (fgets(request, sizeof(request), stdin) == NULL) {
480 if (feof(stdin)) {
481 status(59, "request is too short and probably empty");
482 errlogn("request is too short and probably empty");
483 } else if (ferror(stdin)) {
484 status(59, "error while reading request");
485 errlogn("error while reading request: %s", request);
486 }
487 }
488
489 // Check if request is complete
490 if (request[strnlen(request, sizeof(request)) - 1] != '\n') {
491 status(59, "request is too long (1024 max)");
492 errlogn("request is too long (1024 max): %s", request);
493 }
494
495 // Remove \r character
496 char* pos = strchr(request, '\r');
497 if (pos != NULL)
498 *pos = '\0';
499
500 // Check if gemini:// is there
501 if (strncmp(request, "gemini://", GEMINI_PART) != 0)
502 errlogn("request %s doesn't match gemini://", request);
503
504 // Hide password in the log
505 pos = strchr(request, '?');
506 if (pos)
507 *pos = '\0';
508 syslog(LOG_DAEMON, "request: %s\n", request);
509 if (pos)
510 *pos = '?';
511
512 char* hostname = request+GEMINI_PART;
513 pos = strchr(hostname, '/');
514 if (pos != NULL) {
515 pos[0] = '\0';
516 pos++;
517 }
518
519 // Check if hostname is the right one
520 if (strcmp(hostname, HOSTNAME)) {
521 status(59, "invalid hostname");
522 errlogn("wrong hostname expecting %s, got %s",
523 HOSTNAME, hostname);
524 }
525
526 #if PASSWORD
527 if (!pos || *pos != '?') {
528 // Ask for password
529 status(11, "Password");
530 return 0;
531 } else {
532 // Check password
533 pos++;
534 uint32_t password = PASSWORD;
535 uint32_t hash = fnv((unsigned char*)pos, strlen(pos), 1234);
536
537 if (hash != password) {
538 status(59, "invalid password");
539 return 0;
540 }
541 }
542 #endif
543
544 // Write header
545 status(20, "text/gemini");
546 #endif
547
548 // Hardware info
549 int mib[2];
550 size_t len;
551 mib[0] = CTL_HW;
552 // CPU Name
553 mib[1] = HW_MODEL;
554 char cpuname[64];
555 len = sizeof(cpuname);
556 if (sysctl(mib, 2, cpuname, &len, NULL, 0) == -1)
557 errlog("sysctl failed\n");
558 // Physical memory
559 mib[1] = HW_PHYSMEM64;
560 size_t pmem;
561 memset(&pmem, 0, sizeof(pmem));
562 len = sizeof(pmem);
563 if (sysctl(mib, 2, &pmem, &len, NULL, 0) == -1)
564 errlog("sysctl failed\n");
565
566 #if BLOCKLIST
567 // Reading addresses
568 int pfpipe[2];
569 pipe(pfpipe);
570 pid[0] = fork();
571 if (pid[0] == 0) {
572 // No pledge allows to do that
573 fetchaddrs(pfpipe[1]);
574 return 0;
575 }
576 close(pfpipe[1]);
577 #endif
578
579 // Reading interfaces
580 int ifpipe[2];
581 pipe(ifpipe);
582 pid[1] = fork();
583 if (pid[1] == 0) {
584 if (pledge("stdio inet", ""))
585 errlog("Failed to pledge\n");
586 fetchif(ifpipe[1]);
587 return 0;
588 }
589 close(ifpipe[1]);
590
591 if (pledge("stdio wpath cpath rpath vminfo proc", ""))
592 errlog("Failed to pledge\n");
593
594 // Kernel
595 mib[0] = CTL_KERN;
596 // OS Version
597 mib[1] = KERN_OSRELEASE;
598 char version[64];
599 len = sizeof(version);
600 if (sysctl(mib, 2, version, &len, NULL, 0) == -1)
601 errlog("sysctl failed\n");
602 // OS Release
603 mib[1] = KERN_OSTYPE;
604 char osname[64];
605 len = sizeof(osname);
606 if (sysctl(mib, 2, osname, &len, NULL, 0) == -1)
607 errlog("sysctl failed\n");
608 // Hardware
609 mib[0] = CTL_HW;
610 // CPU count
611 mib[1] = HW_NCPU;
612 int ncpu;
613 len = sizeof(ncpu);
614 if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1)
615 errlog("sysctl failed\n");
616
617 // Prepare to fetch active memory
618 mib[0] = CTL_VM;
619 mib[1] = VM_UVMEXP;
620 struct uvmexp uvmexp;
621 len = sizeof(uvmexp);
622
623 // Fetch uptime
624 char uptime[32];
625 getuptime(uptime, sizeof(uptime));
626
627 // Fetch active memory
628 char membuf[64];
629 if (sysctl(mib, 2, &uvmexp, &len, NULL, 0) == -1)
630 errlog("sysctl failed\n");
631 snprintf(membuf, sizeof(membuf), "%u MiB/%lu MiB",
632 uvmexp.active*uvmexp.pagesize/1024/1024, pmem/1024/1024);
633
634 // Fetch network interfaces
635 char ifbuf[4096] = {0};
636 if (read(ifbuf[0], ifbuf, sizeof(ifbuf)) < 1) {
637 syslog(LOG_DAEMON, "Failed to read interfaces\n");
638 ifbuf[0] = '\0';
639 }
640
641 #if BLOCKLIST
642 // Fetch blocklist from pipe
643 char pfbuf[4096] = {0};
644 if (read(pfpipe[0], pfbuf, sizeof(pfbuf)) < 1) {
645 syslog(LOG_DAEMON, "Failed to read pf table %s\n", TABLE);
646 pfbuf[0] = '\0';
647 }
648 #endif
649
650 #if DHCP
651 // Fetch DHCPD
652 struct lease leases[256];
653 char leasesbuf[4096];
654 leasesbuf[0] = '\0';
655 int llen = readleases(leases, 256);
656 if (llen==-1)
657 snprintf(leasesbuf, sizeof(leasesbuf),
658 "Failed to parse leases file : %s\n\n", LEASES);
659 for (int i=0; i<llen; i++)
660 snprintf(leasesbuf, sizeof(leasesbuf), dhcp_template,
661 leasesbuf, leases[i].hostname, leases[i].ip,
662 leases[i].date, leases[i].time,
663 leases[i].date_end, leases[i].time_end);
664 #endif
665
666 printf(index_template, osname, version, cpuname, ncpu, uptime, membuf,
667 ifbuf
668 #if DHCP
669 ,leasesbuf
670 #endif
671 #if BLOCKLIST
672 ,pfbuf
673 #endif
674 );
675
676 wait(NULL);
677 #if BLOCKLIST
678 close(pfpipe[0]);
679 #endif
680 close(ifpipe[0]);
681 return 0;
682 }
683