1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2020 Tobias Kortkamp <tobik@FreeBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include "config.h"
30
31 #if HAVE_CAPSICUM
32 # include <sys/capsicum.h>
33 #endif
34 #include <sys/stat.h>
35 #include <ctype.h>
36 #if HAVE_ERR
37 # include <err.h>
38 #endif
39 #include <errno.h>
40 #include <limits.h>
41 #include <fcntl.h>
42 #include <stdbool.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <time.h>
47 #include <unistd.h>
48
49 #include <libias/array.h>
50 #include <libias/diff.h>
51 #include <libias/flow.h>
52 #include <libias/io.h>
53 #include <libias/mem.h>
54 #include <libias/mempool.h>
55 #include <libias/mempool/file.h>
56 #include <libias/set.h>
57 #include <libias/str.h>
58
59 #include "capsicum_helpers.h"
60 #include "portscan/log.h"
61
62 struct PortscanLogDir {
63 int fd;
64 char *path;
65 char *commit;
66 };
67
68 struct PortscanLog {
69 struct Mempool *pool;
70 struct Array *entries;
71 };
72
73 struct PortscanLogEntry {
74 enum PortscanLogEntryType type;
75 size_t index;
76 char *origin;
77 char *value;
78 };
79
80 // Prototypes
81 static void portscan_log_sort(struct PortscanLog *);
82 static char *log_entry_tostring(const struct PortscanLogEntry *, struct Mempool *);
83 static struct PortscanLogEntry *log_entry_parse(struct Mempool *, const char *);
84 static int log_entry_compare(const void *, const void *, void *);
85 static int log_update_latest(struct PortscanLogDir *, const char *);
86 static char *log_filename(const char *, struct Mempool *);
87 static char *log_commit(int, struct Mempool *);
88
89 // Constants
90 static const char *PORTSCAN_LOG_DATE_FORMAT = "portscan-%Y%m%d%H%M%S";
91 static const char *PORTSCAN_LOG_INIT = "/dev/null";
92
93 struct PortscanLog *
portscan_log_new(struct Mempool * extpool)94 portscan_log_new(struct Mempool *extpool)
95 {
96 struct Mempool *pool = mempool_new();
97 struct PortscanLog *log = mempool_alloc(pool, sizeof(struct PortscanLog));
98 log->pool = pool;
99 log->entries = mempool_array(pool);
100 return mempool_add(extpool, log, portscan_log_free);
101 }
102
103 void
portscan_log_free(struct PortscanLog * log)104 portscan_log_free(struct PortscanLog *log)
105 {
106 if (log == NULL) {
107 return;
108 }
109 mempool_free(log->pool);
110 }
111
112 void
portscan_log_sort(struct PortscanLog * log)113 portscan_log_sort(struct PortscanLog *log)
114 {
115 array_sort(log->entries, log_entry_compare, NULL);
116 }
117
118 size_t
portscan_log_len(struct PortscanLog * log)119 portscan_log_len(struct PortscanLog *log)
120 {
121 return array_len(log->entries);
122 }
123
124 char *
log_entry_tostring(const struct PortscanLogEntry * entry,struct Mempool * pool)125 log_entry_tostring(const struct PortscanLogEntry *entry, struct Mempool *pool)
126 {
127 switch (entry->type) {
128 case PORTSCAN_LOG_ENTRY_UNKNOWN_VAR:
129 return str_printf(pool, "%-7c %-40s %s\n", 'V', entry->origin, entry->value);
130 case PORTSCAN_LOG_ENTRY_UNKNOWN_TARGET:
131 return str_printf(pool, "%-7c %-40s %s\n", 'T', entry->origin, entry->value);
132 case PORTSCAN_LOG_ENTRY_DUPLICATE_VAR:
133 return str_printf(pool, "%-7s %-40s %s\n", "Vc", entry->origin, entry->value);
134 case PORTSCAN_LOG_ENTRY_OPTION_DEFAULT_DESCRIPTION:
135 return str_printf(pool, "%-7s %-40s %s\n", "OD", entry->origin, entry->value);
136 case PORTSCAN_LOG_ENTRY_OPTION_GROUP:
137 return str_printf(pool, "%-7s %-40s %s\n", "OG", entry->origin, entry->value);
138 case PORTSCAN_LOG_ENTRY_OPTION:
139 return str_printf(pool, "%-7c %-40s %s\n", 'O', entry->origin, entry->value);
140 case PORTSCAN_LOG_ENTRY_CATEGORY_NONEXISTENT_PORT:
141 return str_printf(pool, "%-7s %-40s %s\n", "Ce", entry->origin, entry->value);
142 case PORTSCAN_LOG_ENTRY_CATEGORY_UNHOOKED_PORT:
143 return str_printf(pool, "%-7s %-40s %s\n", "Cu", entry->origin, entry->value);
144 case PORTSCAN_LOG_ENTRY_CATEGORY_UNSORTED:
145 return str_printf(pool, "%-7c %-40s %s\n", 'C', entry->origin, entry->value);
146 case PORTSCAN_LOG_ENTRY_ERROR:
147 return str_printf(pool, "%-7c %-40s %s\n", 'E', entry->origin, entry->value);
148 case PORTSCAN_LOG_ENTRY_VARIABLE_VALUE:
149 return str_printf(pool, "%-7s %-40s %s\n", "Vv", entry->origin, entry->value);
150 case PORTSCAN_LOG_ENTRY_COMMENT:
151 return str_printf(pool, "%-7c %-40s %s\n", '#', entry->origin, entry->value);
152 }
153
154 panic("unhandled portscan log entry type: %d", entry->type);
155 }
156
157 void
portscan_log_add_entries(struct PortscanLog * log,enum PortscanLogEntryType type,const char * origin,struct Set * values)158 portscan_log_add_entries(struct PortscanLog *log, enum PortscanLogEntryType type, const char *origin, struct Set *values)
159 {
160 if (values == NULL) {
161 return;
162 }
163
164 SET_FOREACH (values, const char *, value) {
165 portscan_log_add_entry(log, type, origin, value);
166 }
167 }
168
169 void
portscan_log_add_entry(struct PortscanLog * log,enum PortscanLogEntryType type,const char * origin,const char * value)170 portscan_log_add_entry(struct PortscanLog *log, enum PortscanLogEntryType type, const char *origin, const char *value)
171 {
172 struct PortscanLogEntry *entry = mempool_alloc(log->pool, sizeof(struct PortscanLogEntry));
173 entry->type = type;
174 entry->index = array_len(log->entries);
175 entry->origin = str_dup(log->pool, origin);
176 entry->value = str_dup(log->pool, value);
177 array_append(log->entries, entry);
178 }
179
180 struct PortscanLogEntry *
log_entry_parse(struct Mempool * pool,const char * s)181 log_entry_parse(struct Mempool *pool, const char *s)
182 {
183 enum PortscanLogEntryType type = PORTSCAN_LOG_ENTRY_UNKNOWN_VAR;
184 if (str_startswith(s, "V ")) {
185 type = PORTSCAN_LOG_ENTRY_UNKNOWN_VAR;
186 s++;
187 } else if (str_startswith(s, "T ")) {
188 type = PORTSCAN_LOG_ENTRY_UNKNOWN_TARGET;
189 s++;
190 } else if (str_startswith(s, "Vc ")) {
191 type = PORTSCAN_LOG_ENTRY_DUPLICATE_VAR;
192 s += 2;
193 } else if (str_startswith(s, "OD ")) {
194 type = PORTSCAN_LOG_ENTRY_OPTION_DEFAULT_DESCRIPTION;
195 s += 2;
196 } else if (str_startswith(s, "OG ")) {
197 type = PORTSCAN_LOG_ENTRY_OPTION_GROUP;
198 s += 2;
199 } else if (str_startswith(s, "O ")) {
200 type = PORTSCAN_LOG_ENTRY_OPTION;
201 s++;
202 } else if (str_startswith(s, "Ce ")) {
203 type = PORTSCAN_LOG_ENTRY_CATEGORY_NONEXISTENT_PORT;
204 s += 2;
205 } else if (str_startswith(s, "Cu ")) {
206 type = PORTSCAN_LOG_ENTRY_CATEGORY_UNHOOKED_PORT;
207 s += 2;
208 } else if (str_startswith(s, "C ")) {
209 type = PORTSCAN_LOG_ENTRY_CATEGORY_UNSORTED;
210 s++;
211 } else if (str_startswith(s, "E ")) {
212 type = PORTSCAN_LOG_ENTRY_ERROR;
213 s++;
214 } else if (str_startswith(s, "Vv ")) {
215 type = PORTSCAN_LOG_ENTRY_VARIABLE_VALUE;
216 s += 2;
217 } else if (str_startswith(s, "# ")) {
218 type = PORTSCAN_LOG_ENTRY_COMMENT;
219 s++;
220 } else {
221 fprintf(stderr, "unable to parse log entry: %s\n", s);
222 return NULL;
223 }
224
225 while (*s != 0 && isspace(*s)) {
226 s++;
227 }
228 const char *origin_start = s;
229 while (*s != 0 && !isspace(*s)) {
230 s++;
231 }
232 const char *value = s;
233 while (*value != 0 && isspace(*value)) {
234 value++;
235 }
236 size_t value_len = strlen(value);
237 if (value_len > 0 && value[value_len - 1] == '\n') {
238 value_len--;
239 }
240
241 if ((s - origin_start) == 0 || value_len == 0) {
242 fprintf(stderr, "unable to parse log entry: %s\n", s);
243 return NULL;
244 }
245
246 struct PortscanLogEntry *e = mempool_alloc(pool, sizeof(struct PortscanLogEntry));
247 e->type = type;
248 e->origin = str_ndup(pool, origin_start, s - origin_start);
249 e->value = str_ndup(pool, value, value_len);
250 return e;
251 }
252
253 int
log_entry_compare(const void * ap,const void * bp,void * userdata)254 log_entry_compare(const void *ap, const void *bp, void *userdata)
255 {
256 const struct PortscanLogEntry *a = *(const struct PortscanLogEntry **)ap;
257 const struct PortscanLogEntry *b = *(const struct PortscanLogEntry **)bp;
258
259 int retval = strcmp(a->origin, b->origin);
260 if (retval == 0) {
261 if (a->type > b->type) {
262 retval = 1;
263 } else if (a->type < b->type) {
264 retval = -1;
265 } else {
266 retval = strcmp(a->value, b->value);
267 }
268 }
269
270 return retval;
271 }
272
273 int
portscan_log_compare(struct PortscanLog * prev,struct PortscanLog * log)274 portscan_log_compare(struct PortscanLog *prev, struct PortscanLog *log)
275 {
276 SCOPE_MEMPOOL(pool);
277
278 portscan_log_sort(prev);
279 portscan_log_sort(log);
280
281 struct diff *p = array_diff(prev->entries, log->entries, pool, log_entry_compare, NULL);
282 if (p == NULL) {
283 errx(1, "array_diff failed");
284 }
285 int equal = 1;
286 for (size_t i = 0; i < p->sessz; i++) {
287 if (p->ses[i].type != DIFF_COMMON) {
288 equal = 0;
289 break;
290 }
291 }
292
293 return equal;
294 }
295
296 int
portscan_log_serialize_to_file(struct PortscanLog * log,FILE * out)297 portscan_log_serialize_to_file(struct PortscanLog *log, FILE *out)
298 {
299 SCOPE_MEMPOOL(pool);
300
301 portscan_log_sort(log);
302
303 ARRAY_FOREACH(log->entries, struct PortscanLogEntry *, entry) {
304 char *line = log_entry_tostring(entry, pool);
305 if (write(fileno(out), line, strlen(line)) == -1) {
306 return 0;
307 }
308 }
309
310 return 1;
311 }
312
313 int
log_update_latest(struct PortscanLogDir * logdir,const char * log_path)314 log_update_latest(struct PortscanLogDir *logdir, const char *log_path)
315 {
316 SCOPE_MEMPOOL(pool);
317
318 char *prev = NULL;
319 if (!symlink_update(logdir->fd, log_path, PORTSCAN_LOG_LATEST, pool, &prev)) {
320 return 0;
321 }
322 if (prev != NULL && !symlink_update(logdir->fd, prev, PORTSCAN_LOG_PREVIOUS, pool, NULL)) {
323 return 0;
324 }
325 return 1;
326 }
327
328 char *
log_filename(const char * commit,struct Mempool * pool)329 log_filename(const char *commit, struct Mempool *pool)
330 {
331 time_t date = time(NULL);
332 if (date == -1) {
333 return NULL;
334 }
335 struct tm *tm = gmtime(&date);
336
337 char buf[PATH_MAX];
338 if (strftime(buf, sizeof(buf), PORTSCAN_LOG_DATE_FORMAT, tm) == 0) {
339 return NULL;
340 }
341
342 return str_printf(pool, "%s-%s.log", buf, commit);
343 }
344
345 int
portscan_log_serialize_to_dir(struct PortscanLog * log,struct PortscanLogDir * logdir)346 portscan_log_serialize_to_dir(struct PortscanLog *log, struct PortscanLogDir *logdir)
347 {
348 SCOPE_MEMPOOL(pool);
349
350 char *log_path = log_filename(logdir->commit, pool);
351 FILE *out = mempool_fopenat(pool, logdir->fd, log_path, "w", 0644);
352 if (out == NULL) {
353 return 0;
354 }
355 if (!portscan_log_serialize_to_file(log, out) ||
356 !log_update_latest(logdir, log_path)) {
357 return 0;
358 }
359
360 return 1;
361 }
362
363 char *
log_commit(int portsdir,struct Mempool * pool)364 log_commit(int portsdir, struct Mempool *pool)
365 {
366 if (fchdir(portsdir) == -1) {
367 err(1, "fchdir");
368 }
369
370 FILE *fp = popen("git rev-parse HEAD 2>/dev/null", "r");
371 if (fp == NULL) {
372 err(1, "popen");
373 }
374
375 char *revision = NULL;
376
377 LINE_FOREACH(fp, line) {
378 revision = str_printf(pool, "%s", line);
379 break;
380 }
381 pclose(fp);
382
383 if (revision == NULL) {
384 revision = str_dup(pool, "unknown");
385 }
386 return revision;
387 }
388
389 struct PortscanLogDir *
portscan_log_dir_open(struct Mempool * extpool,const char * logdir_path,int portsdir)390 portscan_log_dir_open(struct Mempool *extpool, const char *logdir_path, int portsdir)
391 {
392 SCOPE_MEMPOOL(pool);
393
394 int created_dir = 0;
395 int logdir;
396 while ((logdir = open(logdir_path, O_DIRECTORY)) == -1) {
397 if (errno == ENOENT) {
398 if (mkdir(logdir_path, 0777) == -1) {
399 return NULL;
400 }
401 created_dir = 1;
402 } else {
403 return NULL;
404 }
405 }
406 if (created_dir) {
407 if (symlinkat(PORTSCAN_LOG_INIT, logdir, PORTSCAN_LOG_PREVIOUS) == -1) {
408 goto error;
409 }
410 if (symlinkat(PORTSCAN_LOG_INIT, logdir, PORTSCAN_LOG_LATEST) == -1) {
411 goto error;
412 }
413 } else {
414 char *prev = symlink_read(logdir, PORTSCAN_LOG_PREVIOUS, pool);
415 if (prev == NULL &&
416 symlinkat(PORTSCAN_LOG_INIT, logdir, PORTSCAN_LOG_PREVIOUS) == -1) {
417 goto error;
418 }
419
420 char *latest = symlink_read(logdir, PORTSCAN_LOG_LATEST, pool);
421 if (latest == NULL &&
422 symlinkat(PORTSCAN_LOG_INIT, logdir, PORTSCAN_LOG_LATEST) == -1) {
423 goto error;
424 }
425 }
426
427 #if HAVE_CAPSICUM
428 if (caph_limit_stream(logdir, CAPH_CREATE | CAPH_FTRUNCATE | CAPH_READ | CAPH_SYMLINK) < 0) {
429 err(1, "caph_limit_stream");
430 }
431 #endif
432 struct PortscanLogDir *dir = xmalloc(sizeof(struct PortscanLogDir));
433 dir->fd = logdir;
434 dir->path = str_dup(NULL, logdir_path);
435 dir->commit = str_dup(NULL, log_commit(portsdir, pool));
436
437 return mempool_add(extpool, dir, portscan_log_dir_close);
438
439 error:
440 close(logdir);
441 return NULL;
442 }
443
444 void
portscan_log_dir_close(struct PortscanLogDir * dir)445 portscan_log_dir_close(struct PortscanLogDir *dir)
446 {
447 if (dir == NULL) {
448 return;
449 }
450 close(dir->fd);
451 free(dir->path);
452 free(dir->commit);
453 free(dir);
454 }
455
456 struct PortscanLog *
portscan_log_read_all(struct Mempool * extpool,struct PortscanLogDir * logdir,const char * log_path)457 portscan_log_read_all(struct Mempool *extpool, struct PortscanLogDir *logdir, const char *log_path)
458 {
459 SCOPE_MEMPOOL(pool);
460
461 struct PortscanLog *log = portscan_log_new(extpool);
462
463 char *buf = symlink_read(logdir->fd, log_path, pool);
464 if (buf == NULL) {
465 if (errno == ENOENT) {
466 return log;
467 } else if (errno != EINVAL) {
468 err(1, "symlink_read: %s", log_path);
469 }
470 } else if (strcmp(buf, PORTSCAN_LOG_INIT) == 0) {
471 return log;
472 }
473
474 FILE *fp = mempool_fopenat(pool, logdir->fd, log_path, "r", 0);
475 if (fp == NULL) {
476 if (errno == ENOENT) {
477 return log;
478 }
479 err(1, "openat: %s", log_path);
480 }
481
482 LINE_FOREACH(fp, line) {
483 struct PortscanLogEntry *entry = log_entry_parse(log->pool, line);
484 if (entry != NULL) {
485 array_append(log->entries, entry);
486 }
487 }
488
489 portscan_log_sort(log);
490
491 return log;
492 }
493
494