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