1 /*
2     output logging/reporting
3 
4     This is the file that formats the output files -- that is to say,
5     where we report everything we find.
6 
7     PLUGINS
8 
9     The various types of output (XML, binary, Redis, etc.) are written vaguely
10     as "plugins", which means as a structure with function pointers. In the
11     future, it should be possible to write plugins as DDLs/shared-objects
12     and load them at runtime, but right now, they are just hard coded.
13 
14     ROTATE
15 
16     Files can be "rotated". This is done by prefixing the file with the
17     date/time when the file was created.
18 
19     A key feature of this design is to prevent files being lost during
20     rotation. Therefore, the files are renamed while they are still open.
21     If the rename function fails, then the file is left in-place and still
22     open for writing, with continued appending to the file.
23 
24     Thus, you could start the program logging to "--rotate-dir ../foobar"
25     and then notice the error messages saying that rotating isn't working,
26     then go create the "foobar" directory, at which point rotating will now
27     work -- it's just that the first rotated file will contain several
28     periods of data.
29 */
30 
31 /* Needed for Linux to make offsets 64 bits */
32 #define _FILE_OFFSET_BITS 64
33 
34 #include "output.h"
35 #include "masscan.h"
36 #include "masscan-status.h"
37 #include "string_s.h"
38 #include "logger.h"
39 #include "proto-banner1.h"
40 #include "masscan-app.h"
41 #include "main-globals.h"
42 #include "pixie-file.h"
43 #include "pixie-sockets.h"
44 #include "util-malloc.h"
45 
46 #include <limits.h>
47 #include <ctype.h>
48 #include <string.h>
49 
50 
51 /*****************************************************************************
52  *****************************************************************************/
ftell_x(FILE * fp)53 static int64_t ftell_x(FILE *fp)
54 {
55 #if defined(WIN32) && defined(__GNUC__)
56     return ftello64(fp);
57 #elif defined(WIN32) && defined(_MSC_VER)
58     return _ftelli64(fp);
59 #else
60     return ftello(fp);
61 #endif
62 }
63 
64 /*****************************************************************************
65  * The 'status' variable contains both the open/closed info as well as the
66  * protocol info. This splits it back out into two values.
67  *****************************************************************************/
68 const char *
name_from_ip_proto(unsigned ip_proto)69 name_from_ip_proto(unsigned ip_proto)
70 {
71     switch (ip_proto) {
72         case 0: return "arp";
73         case 1: return "icmp";
74         case 6: return "tcp";
75         case 17: return "udp";
76         case 132: return "sctp";
77         default: return "err";
78     }
79 }
80 
81 
82 /*****************************************************************************
83  * The actual 'status' variable is narrowly defined depending on the
84  * underlying protocol. This function creates a gross "open" v. "closed"
85  * string based on the narrow variable.
86  *****************************************************************************/
87 const char *
status_string(enum PortStatus status)88 status_string(enum PortStatus status)
89 {
90     switch (status) {
91         case PortStatus_Open: return "open";
92         case PortStatus_Closed: return "closed";
93         case PortStatus_Arp: return "up";
94         default: return "unknown";
95     }
96 }
97 
98 
99 /*****************************************************************************
100  * Convert TCP flags into an nmap-style "reason" string
101  *****************************************************************************/
102 const char *
reason_string(int x,char * buffer,size_t sizeof_buffer)103 reason_string(int x, char *buffer, size_t sizeof_buffer)
104 {
105     sprintf_s(buffer, sizeof_buffer, "%s%s%s%s%s%s%s%s",
106         (x&0x01)?"fin-":"",
107         (x&0x02)?"syn-":"",
108         (x&0x04)?"rst-":"",
109         (x&0x08)?"psh-":"",
110         (x&0x10)?"ack-":"",
111         (x&0x20)?"urg-":"",
112         (x&0x40)?"ece-":"",
113         (x&0x80)?"cwr-":""
114         );
115     if (buffer[0] == '\0')
116         return "none";
117     else
118         buffer[strlen(buffer)-1] = '\0';
119     return buffer;
120 }
121 
122 
123 /*****************************************************************************
124  * Remove bad characters from the banner, especially new lines and HTML
125  * control codes.
126  *****************************************************************************/
127 const char *
normalize_string(const unsigned char * px,size_t length,char * buf,size_t buf_len)128 normalize_string(const unsigned char *px, size_t length,
129                  char *buf, size_t buf_len)
130 {
131     size_t i=0;
132     size_t offset = 0;
133 
134 
135     for (i=0; i<length; i++) {
136         unsigned char c = px[i];
137 
138         if (isprint(c) && c != '<' && c != '>' && c != '&' && c != '\\' && c != '\"' && c != '\'') {
139             if (offset + 2 < buf_len)
140                 buf[offset++] = px[i];
141         } else {
142             if (offset + 5 < buf_len) {
143                 buf[offset++] = '\\';
144                 buf[offset++] = 'x';
145                 buf[offset++] = "0123456789abcdef"[px[i]>>4];
146                 buf[offset++] = "0123456789abcdef"[px[i]&0xF];
147             }
148         }
149     }
150 
151     buf[offset] = '\0';
152 
153     return buf;
154 }
155 
156 
157 /*****************************************************************************
158  * PORTABILITY: WINDOWS
159  *
160  * Windows POSIX functions open the file without the "share-delete" flag,
161  * meaning they can't be renamed while open. Therefore, we need to
162  * construct our own open flag.
163  *****************************************************************************/
164 static FILE *
open_rotate(struct Output * out,const char * filename)165 open_rotate(struct Output *out, const char *filename)
166 {
167     FILE *fp = 0;
168     unsigned is_append = out->is_append;
169     int x;
170 
171     /*
172      * KLUDGE: do something special for redis
173      */
174     if (out->format == Output_Redis) {
175         ptrdiff_t fd = out->redis.fd;
176         if (fd < 1) {
177             struct sockaddr_in sin = {0};
178             fd = (ptrdiff_t)socket(AF_INET, SOCK_STREAM, 0);
179             if (fd == -1) {
180                 LOG(0, "redis: socket() failed to create socket\n");
181                 exit(1);
182             }
183             sin.sin_addr.s_addr = htonl(out->redis.ip.ipv4); /* TODO: ipv6 */
184             sin.sin_port = htons((unsigned short)out->redis.port);
185             sin.sin_family = AF_INET;
186             x = connect((SOCKET)fd, (struct sockaddr*)&sin, sizeof(sin));
187             if (x != 0) {
188                 LOG(0, "redis: connect() failed\n");
189                 perror("connect");
190             }
191             out->redis.fd = fd;
192         }
193         out->funcs->open(out, (FILE*)fd);
194 
195         return (FILE*)fd;
196     }
197 
198     /* Do something special for the "-" filename */
199     if (filename[0] == '-' && filename[1] == '\0')
200         fp = stdout;
201 
202     /* open a "shareable" file. On Windows, by default files can't be renamed
203      * while they are open, so we need a special function that takes care
204      * of this. */
205     if (fp == 0) {
206         x = pixie_fopen_shareable(&fp, filename, is_append);
207         if (x != 0 || fp == NULL) {
208             fprintf(stderr, "out: could not open file for %s\n",
209                     is_append?"appending":"writing");
210             perror(filename);
211             is_tx_done = 1;
212             return NULL;
213         }
214     }
215 
216     /*
217      * Mark the file as newly opened. That way, before writing any data
218      * to it, we'll first have to write headers
219      */
220     out->is_virgin_file = 1;
221 
222     return fp;
223 }
224 
225 
226 /*****************************************************************************
227  * Write the remaining data the file and close it. This function is
228  * called "rotate", but it doesn't actually rotate, this name just reflects
229  * how it's used in the rotate process.
230  *****************************************************************************/
231 static void
close_rotate(struct Output * out,FILE * fp)232 close_rotate(struct Output *out, FILE *fp)
233 {
234     if (out == NULL)
235         return;
236     if (fp == NULL)
237         return;
238 
239     /*
240      * Write the format-specific trailers, like </xml>
241      */
242     if (!out->is_virgin_file)
243         out->funcs->close(out, fp);
244 
245     memset(&out->counts, 0, sizeof(out->counts));
246 
247     /* Redis Kludge*/
248     if (out->format == Output_Redis)
249         return;
250 
251     fflush(fp);
252     fclose(fp);
253 }
254 
255 
256 /*****************************************************************************
257  * Returns the time when the next rotate should occur. Rotations are
258  * aligned to the period, which means that if you rotate hourly, it's done
259  * on the hour every hour, like at 9:00:00 o'clock exactly. In other words,
260  * a period of "hourly" doesn't really mean "every 60 minutes", but
261  * on the hour". Since the program will be launched midway in a period,
262  * that means the first rotation will happen in less than a full period.
263  *****************************************************************************/
264 static time_t
next_rotate_time(time_t last_rotate,unsigned period,unsigned offset)265 next_rotate_time(time_t last_rotate, unsigned period, unsigned offset)
266 {
267     time_t next;
268 
269     next = last_rotate - (last_rotate % period) + period + offset;
270 
271     return next;
272 }
273 
274 
275 #if 0
276 /*****************************************************************************
277  *****************************************************************************/
278 static int
279 ends_with(const char *filename, const char *extension)
280 {
281     if (filename == NULL || extension == NULL)
282         return 0;
283     if (strlen(filename) + 1 < strlen(extension))
284         return 0;
285     if (memcmp(filename + strlen(filename) - strlen(extension),
286                 extension, strlen(extension)) != 0)
287         return 0;
288     if (filename[strlen(filename) - strlen(extension) - 1] != '.')
289         return 0;
290 
291     return 1;
292 }
293 #endif
294 
295 /*****************************************************************************
296  * strdup(): compilers don't like strdup(), so I just write my own here. I
297  * should probably find a better solution.
298  *****************************************************************************/
299 static char *
duplicate_string(const char * str)300 duplicate_string(const char *str)
301 {
302     size_t length;
303     char *result;
304 
305     /* Find the length of the string. We allow NULL strings, in which case
306      * the length is zero */
307     if (str == NULL)
308         length = 0;
309     else
310         length = strlen(str);
311 
312     /* Allocate memory for the string */
313     result = MALLOC(length + 1);
314 
315 
316     /* Copy the string */
317     if (str)
318         memcpy(result, str, length+1);
319     result[length] = '\0';
320 
321     return result;
322 }
323 
324 /*****************************************************************************
325  * Adds the index variable to just before the file extension. For example,
326  * if the original filename is "foo.bar", and the index is 1, then the
327  * new filename becomes "foo.01.bar". By putting the index before the
328  * extension, it preserves the file type. By prepending a zero on the index,
329  * it allows up to 100 files while still being able to easily sort the files.
330  *****************************************************************************/
331 static char *
indexed_filename(const char * filename,unsigned index)332 indexed_filename(const char *filename, unsigned index)
333 {
334     size_t len = strlen(filename);
335     size_t ext;
336     char *new_filename;
337     size_t new_length = strlen(filename) + 32;
338 
339     /* find the extension */
340     ext = len;
341     while (ext) {
342         ext--;
343         if (filename[ext] == '.')
344             break;
345         if (filename[ext] == '/' || filename[ext] == '\\') {
346             /* no dot found, so ext is end of file */
347             ext = len;
348             break;
349         }
350     }
351     if (ext == 0 && len > 0 && filename[0] != '.')
352         ext = len;
353 
354     /* allocate memory */
355     new_filename = MALLOC(new_length);
356 
357 
358     /* format the new name */
359     sprintf_s(new_filename, new_length, "%.*s.%02u%s",
360               (unsigned)ext, filename,
361               index,
362               filename+ext);
363 
364     return new_filename;
365 
366 }
367 
368 /*****************************************************************************
369  * Create an "output" structure. If we are writing a file, we create the
370  * file now, so that any errors creating the file are caught immediately,
371  * rather than later in the scan when it might fail.
372  *****************************************************************************/
373 struct Output *
output_create(const struct Masscan * masscan,unsigned thread_index)374 output_create(const struct Masscan *masscan, unsigned thread_index)
375 {
376     struct Output *out;
377     unsigned i;
378 
379     /* allocate/initialize memory */
380     out = CALLOC(1, sizeof(*out));
381     out->masscan = masscan;
382     out->when_scan_started = time(0);
383     out->is_virgin_file = 1;
384 
385     /*
386      * Copy the configuration information from the 'masscan' structure.
387      */
388     out->rotate.period = masscan->output.rotate.timeout;
389     out->rotate.offset = masscan->output.rotate.offset;
390     out->rotate.filesize = masscan->output.rotate.filesize;
391     out->redis.port = masscan->redis.port;
392     out->redis.ip = masscan->redis.ip;
393     out->is_banner = masscan->is_banners;
394     out->is_gmt = masscan->is_gmt;
395     out->is_interactive = masscan->output.is_interactive;
396     out->is_show_open = masscan->output.is_show_open;
397     out->is_show_closed = masscan->output.is_show_closed;
398     out->is_show_host = masscan->output.is_show_host;
399     out->is_append = masscan->output.is_append;
400     out->xml.stylesheet = duplicate_string(masscan->output.stylesheet);
401     out->rotate.directory = duplicate_string(masscan->output.rotate.directory);
402     if (masscan->nic_count <= 1)
403         out->filename = duplicate_string(masscan->output.filename);
404     else
405         out->filename = indexed_filename(masscan->output.filename, thread_index);
406 
407     for (i=0; i<8; i++) {
408         out->src[i] = masscan->nic[i].src;
409     }
410 
411     /*
412      * Link the appropriate output module.
413      * TODO: support multiple output modules
414      */
415     out->format = masscan->output.format;
416     switch (out->format) {
417     case Output_List:
418         out->funcs = &text_output;
419         break;
420     case Output_Unicornscan:
421         out->funcs = &unicornscan_output;
422         break;
423     case Output_XML:
424         out->funcs = &xml_output;
425         break;
426     case Output_JSON:
427         out->funcs = &json_output;
428         break;
429     case Output_NDJSON:
430         out->funcs = &ndjson_output;
431         break;
432     case Output_Certs:
433         out->funcs = &certs_output;
434         break;
435     case Output_Binary:
436         out->funcs = &binary_output;
437         break;
438     case Output_Grepable:
439         out->funcs = &grepable_output;
440         break;
441     case Output_Redis:
442         out->funcs = &redis_output;
443         break;
444     case Output_Hostonly:
445         out->funcs = &hostonly_output;
446         break;
447     case Output_None:
448         out->funcs = &null_output;
449         break;
450     default:
451         out->funcs = &null_output;
452         break;
453     }
454 
455     /*
456      * Open the desired output file. We do this now at the start of the scan
457      * so that we can immediately notify the user of an error, rather than
458      * waiting midway through a long scan and have it fail.
459      */
460     if (masscan->output.filename[0] && out->funcs != &null_output) {
461         FILE *fp;
462 
463         fp = open_rotate(out, masscan->output.filename);
464         if (fp == NULL) {
465             perror(masscan->output.filename);
466             exit(1);
467         }
468 
469         out->fp = fp;
470         out->rotate.last = time(0);
471     }
472 
473     /*
474      * Set the time of the next rotation. If we aren't rotating files, then
475      * this time will be set at "infinity" in the future.
476      * TODO: this code isn't Y2036 compliant.
477      */
478     if (masscan->output.rotate.timeout == 0) {
479         /* TODO: how does one find the max time_t value??*/
480         out->rotate.next = (time_t)LONG_MAX;
481     } else {
482         if (out->rotate.offset > 1) {
483             out->rotate.next = next_rotate_time(
484                                     out->rotate.last-out->rotate.period,
485                                     out->rotate.period, out->rotate.offset);
486         } else {
487             out->rotate.next = next_rotate_time(
488                                     out->rotate.last,
489                                     out->rotate.period, out->rotate.offset);
490         }
491     }
492 
493 
494 
495     return out;
496 }
497 
498 
499 /*****************************************************************************
500  * Rotate the file, moving it from the local directory to a remote directory
501  * and changing the name to include the timestamp. This is done while the file
502  * is still open: we move the file and rename it first, then close it.
503  *****************************************************************************/
504 static FILE *
output_do_rotate(struct Output * out,int is_closing)505 output_do_rotate(struct Output *out, int is_closing)
506 {
507     const char *dir;
508     const char *filename;
509     char *new_filename;
510     size_t new_filename_size;
511     struct tm tm;
512     int err;
513 
514     /* Don't do anything if there is no file */
515     if (out == NULL || out->fp == NULL)
516         return NULL;
517 
518     dir = out->rotate.directory;
519     filename = out->filename;
520 
521     /* Make sure that all output has been flushed to the file */
522     fflush(out->fp);
523 
524     /* Remove directory prefix from filename, we just want the root filename
525      * to start with */
526     while (strchr(filename, '/')) {
527         filename = strchr(filename, '/');
528         if (*filename == '/')
529             filename++;
530     }
531 
532     while (strchr(filename, '\\')) {
533         filename = strchr(filename, '\\');
534         if (*filename == '\\')
535             filename++;
536     }
537 
538     /* Allocate memory for the new filename */
539     new_filename_size =     strlen(dir)
540                             + strlen("/")
541                             + strlen(filename)
542                             + strlen("1308201101-")
543                             + strlen(filename)
544                             + 1  /* - */
545                             + 1; /* nul */
546     new_filename = MALLOC(new_filename_size);
547 
548     /* Get the proper timestamp for the file */
549     if (out->is_gmt) {
550         err = gmtime_s(&tm, &out->rotate.last);
551     } else {
552         err = localtime_s(&tm, &out->rotate.last);
553     }
554     if (err != 0) {
555         free(new_filename);
556         perror("gmtime(): file rotation ended");
557         return out->fp;
558     }
559 
560 
561     /* Look for a name that doesn't collide with an exist name. If the desired
562      * file already exists, then increment the filename. This should never
563      * happen. */
564     err = 0;
565 again:
566     if (out->rotate.filesize) {
567         size_t x_off=0, x_len=0;
568         if (strrchr(filename, '.')) {
569             x_off = strrchr(filename, '.') - filename;
570             x_len = strlen(filename + x_off);
571         } else {
572             x_off = strlen(filename);
573             x_len = 0;
574         }
575         sprintf_s(new_filename, new_filename_size,
576                       "%s/%.*s-%05u%.*s",
577                 dir,
578                 (unsigned)x_off, filename,
579                 out->rotate.filecount++,
580                 (unsigned)x_len, filename + x_off
581                 );
582     } else {
583         sprintf_s(new_filename, new_filename_size,
584                   "%s/%02u%02u%02u-%02u%02u%02u" "-%s",
585             dir,
586             tm.tm_year % 100,
587             tm.tm_mon+1,
588             tm.tm_mday,
589             tm.tm_hour,
590             tm.tm_min,
591             tm.tm_sec,
592             filename);
593         if (access(new_filename, 0) == 0) {
594             tm.tm_sec++;
595             if (err++ == 0)
596                 goto again;
597         }
598     }
599     filename = out->filename;
600 
601     /*
602      * Move the file
603      */
604     err = rename(filename, new_filename);
605     if (err) {
606         LOG(0, "rename(\"%s\", \"%s\"): failed\n", filename, new_filename);
607         perror("rename()");
608         free(new_filename);
609         return out->fp;
610     }
611 
612     /*
613      * Set the next rotate time, which is the current time plus the period
614      * length
615      */
616     out->rotate.bytes_written = 0;
617 
618     if (out->rotate.period) {
619         out->rotate.next = next_rotate_time(time(0),
620                                         out->rotate.period, out->rotate.offset);
621     }
622 
623     LOG(1, "rotated: %s\n", new_filename);
624     free(new_filename);
625 
626     /*
627      * Now create a new file
628      */
629     if (is_closing)
630         out->fp = NULL; /* program shutting down, so don't create new file */
631     else {
632         FILE *fp;
633 
634         fp = open_rotate(out, filename);
635         if (fp == NULL) {
636             LOG(0, "rotate: %s: failed: %s\n", filename, strerror_x(errno));
637         } else {
638             close_rotate(out, out->fp);
639             out->fp = fp;
640             out->rotate.last = time(0);
641             LOG(1, "rotate: started new file: %s\n", filename);
642         }
643     }
644     return out->fp;
645 }
646 
647 /***************************************************************************
648  ***************************************************************************/
649 static int
is_rotate_time(const struct Output * out,time_t now,FILE * fp)650 is_rotate_time(const struct Output *out, time_t now, FILE *fp)
651 {
652     if (out->is_virgin_file)
653         return 0;
654     if (now >= out->rotate.next)
655         return 1;
656     if (out->rotate.filesize != 0 &&
657         ftell_x(fp) >= (int64_t)out->rotate.filesize)
658         return 1;
659     return 0;
660 }
661 
662 /***************************************************************************
663  * Return the vendor/OUI string matchng the first three bytes of a
664  * MAC address.
665  * TODO: this should be read in from a file
666  ***************************************************************************/
667 static const char *
oui_from_mac(const unsigned char mac[6])668 oui_from_mac(const unsigned char mac[6])
669 {
670     unsigned oui = mac[0]<<16 | mac[1]<<8 | mac[2];
671     switch (oui) {
672     case 0x0001c0: return "Compulab";
673     case 0x000732: return "Aaeon";
674     case 0x000c29: return "VMware";
675     case 0x001075: return "Seagate";
676     case 0x001132: return "Synology";
677     case 0x022618: return "Asus";
678     case 0x0022b0: return "D-Link";
679     case 0x00236c: return "Apple";
680     case 0x0016CB: return "Apple";
681     case 0x001e06: return "Odroid";
682     case 0x001ff3: return "Apple";
683     case 0x002590: return "Supermicro";
684     case 0x08cc68: return "Cisco";
685     case 0x0C9D92: return "Asus";
686     case 0x244CE3: return "Amazon";
687     case 0x2c27d7: return "HP";
688     case 0x3497f6: return "Asus";
689     case 0x38f73d: return "Amazon";
690     case 0x404a03: return "Zyxel";
691     case 0x4C9EFF: return "Zyxel";
692     case 0x5855CA: return "Apple";
693     case 0x60a44c: return "Asus";
694     case 0x6c72e7: return "Apple";
695     case 0x9003b7: return "Parrot";
696     case 0x94dbc9: return "Azurewave";
697     case 0xacbc32: return "Apple";
698     case 0xb827eb: return "Raspberry Pi";
699     case 0xc05627: return "Belkin";
700     case 0xc0c1c0: return "Cisco-Linksys";
701     case 0xDCA4CA: return "Apple";
702     case 0xe4956e: return "[random]";
703     default: return "";
704     }
705 }
706 
707 /***************************************************************************
708  * Report simply "open" or "closed", with little additional information.
709  * This is called directly from the receive thread when responses come
710  * back.
711  ***************************************************************************/
712 void
output_report_status(struct Output * out,time_t timestamp,int status,ipaddress ip,unsigned ip_proto,unsigned port,unsigned reason,unsigned ttl,const unsigned char mac[6])713 output_report_status(struct Output *out, time_t timestamp, int status,
714         ipaddress ip, unsigned ip_proto, unsigned port, unsigned reason, unsigned ttl,
715         const unsigned char mac[6])
716 {
717     FILE *fp = out->fp;
718     time_t now = time(0);
719     ipaddress_formatted_t fmt = ipaddress_fmt(ip);
720 
721     global_now = now;
722 
723     /* if "--open"/"--open-only" parameter specified on command-line, then
724      * don't report the status of closed-ports */
725     if (!out->is_show_closed && status == PortStatus_Closed)
726         return;
727     if (!out->is_show_open && status == PortStatus_Open)
728         return;
729 
730     /* If in "--interactive" mode, then print the banner to the command
731      * line screen */
732     if (out->is_interactive || out->format == 0 || out->format == Output_Interactive) {
733         unsigned count;
734 
735         switch (ip_proto) {
736         case 0: /* ARP */
737             count = fprintf(stdout, "Discovered %s port %u/%s on %s (%02x:%02x:%02x:%02x:%02x:%02x) %s",
738                         status_string(status),
739                         port,
740                         name_from_ip_proto(ip_proto),
741                         fmt.string,
742                         mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
743                         oui_from_mac(mac)
744                         );
745             break;
746         default:
747             count = fprintf(stdout, "Discovered %s port %u/%s on %s",
748                         status_string(status),
749                         port,
750                         name_from_ip_proto(ip_proto),
751                         fmt.string
752                         );
753         }
754 
755         /* Because this line may overwrite the "%done" status line, print
756          * some spaces afterward to completely cover up the line */
757         if (count < 80)
758             fprintf(stdout, "%.*s", (int)(79-count),
759                     "                                          "
760                     "                                          ");
761 
762         fprintf(stdout, "\n");
763         fflush(stdout);
764 
765     }
766 
767 
768     if (fp == NULL)
769         return;
770 
771     /* Rotate, if we've pass the time limit. Rotating the log files happens
772      * inline while writing output, whenever there's output to write to the
773      * file, rather than in a separate thread right at the time interval.
774      * Thus, if results are coming in slowly, the rotation won't happen
775      * on precise boundaries */
776     if (is_rotate_time(out, now, fp)) {
777         fp = output_do_rotate(out, 0);
778         if (fp == NULL)
779             return;
780     }
781 
782 
783     /* Keep some statistics so that the user can monitor how much stuff is
784      * being found. */
785     switch (status) {
786         case PortStatus_Open:
787             switch (ip_proto) {
788             case 1:
789                 out->counts.icmp.echo++;
790                 break;
791             case 6:
792                 out->counts.tcp.open++;
793                 break;
794             case 17:
795                 out->counts.udp.open++;
796                 break;
797             case 132:
798                 out->counts.sctp.open++;
799                 break;
800             default:
801                 out->counts.oproto.open++;
802                 break;
803             }
804             if (!out->is_show_open)
805                 return;
806             break;
807         case PortStatus_Closed:
808             switch (ip_proto) {
809             case 6:
810                 out->counts.tcp.closed++;
811                 break;
812             case 17:
813                 out->counts.udp.closed++;
814                 break;
815             case 132:
816                 out->counts.sctp.closed++;
817                 break;
818             }
819             if (!out->is_show_closed)
820                 return;
821             break;
822         case PortStatus_Arp:
823             out->counts.arp.open++;
824             break;
825         default:
826             LOG(0, "unknown status type: %u\n", status);
827             return;
828     }
829 
830     /*
831      * If this is a newly opened file, then write file headers
832      */
833     if (out->is_virgin_file) {
834         out->funcs->open(out, fp);
835         out->is_virgin_file = 0;
836     }
837 
838     /*
839      * Now do the actual output, whether it be XML, binary, JSON, ndjson, Redis,
840      * and so on.
841      */
842     out->funcs->status(out, fp, timestamp, status, ip, ip_proto, port, reason, ttl);
843 }
844 
845 
846 /***************************************************************************
847  ***************************************************************************/
848 void
output_report_banner(struct Output * out,time_t now,ipaddress ip,unsigned ip_proto,unsigned port,unsigned proto,unsigned ttl,const unsigned char * px,unsigned length)849 output_report_banner(struct Output *out, time_t now,
850                 ipaddress ip, unsigned ip_proto, unsigned port,
851                 unsigned proto,
852                 unsigned ttl,
853                 const unsigned char *px, unsigned length)
854 {
855     FILE *fp = out->fp;
856     ipaddress_formatted_t fmt = ipaddress_fmt(ip);
857 
858     /* If we aren't doing banners, then don't do anything. That's because
859      * when doing UDP scans, we'll still get banner information from
860      * decoding the response packets, even if the user isn't interested */
861     if (!out->is_banner)
862         return;
863 
864     /* If in "--interactive" mode, then print the banner to the command
865      * line screen */
866     if (out->is_interactive || out->format == 0 || out->format == Output_Interactive) {
867         unsigned count;
868         char banner_buffer[4096];
869 
870         count = fprintf(stdout, "Banner on port %u/%s on %s: [%s] %s",
871             port,
872             name_from_ip_proto(ip_proto),
873             fmt.string,
874             masscan_app_to_string(proto),
875             normalize_string(px, length, banner_buffer, sizeof(banner_buffer))
876             );
877 
878         /* Because this line may overwrite the "%done" status line, print
879          * some spaces afterward to completely cover up the line */
880         if (count < 80)
881             fprintf(stdout, "%.*s", (int)(79-count),
882                     "                                          "
883                     "                                          ");
884 
885         fprintf(stdout, "\n");
886     }
887 
888     /* If not outputing to a file, then don't do anything */
889     if (fp == NULL)
890         return;
891 
892     /* Rotate, if we've pass the time limit. Rotating the log files happens
893      * inline while writing output, whenever there's output to write to the
894      * file, rather than in a separate thread right at the time interval.
895      * Thus, if results are coming in slowly, the rotation won't happen
896      * on precise boundaries */
897     if (is_rotate_time(out, now, fp)) {
898         fp = output_do_rotate(out, 0);
899         if (fp == NULL)
900             return;
901     }
902 
903     /*
904      * If this is a newly opened file, then write file headers
905      */
906     if (out->is_virgin_file) {
907         out->funcs->open(out, fp);
908         out->is_virgin_file = 0;
909     }
910 
911     /*
912      * Now do the actual output, whether it be XML, binary, JSON, ndjson, Redis,
913      * and so on.
914      */
915     out->funcs->banner(out, fp, now, ip, ip_proto, port, proto, ttl, px, length);
916 
917 }
918 
919 
920 /***************************************************************************
921  * Called on exit of the program to close/free everything
922  ***************************************************************************/
923 void
output_destroy(struct Output * out)924 output_destroy(struct Output *out)
925 {
926     if (out == NULL)
927         return;
928 
929     /* If rotating files, then do one last rotate of this file to the
930      * destination directory */
931     if (out->rotate.period || out->rotate.filesize) {
932         LOG(1, "doing finale rotate\n");
933         output_do_rotate(out, 1);
934     }
935 
936     /* If not rotating files, then simply close this file. Remember
937      * that some files will write closing information before closing
938      * the file */
939     if (out->fp)
940         close_rotate(out, out->fp);
941 
942 
943 
944     free(out->xml.stylesheet);
945     free(out->rotate.directory);
946     free(out->filename);
947 
948     free(out);
949 }
950 
951 
952 /*****************************************************************************
953  * Regression tests for this unit.
954  *****************************************************************************/
955 int
output_selftest(void)956 output_selftest(void)
957 {
958     char *f;
959 
960     f = indexed_filename("foo.bar", 1);
961     if (strcmp(f, "foo.01.bar") != 0) {
962         fprintf(stderr, "output: failed selftest\n");
963         return 1;
964     }
965     free(f);
966 
967     f = indexed_filename("foo.b/ar", 2);
968     if (strcmp(f, "foo.b/ar.02") != 0) {
969         fprintf(stderr, "output: failed selftest\n");
970         return 1;
971     }
972     free(f);
973 
974     f = indexed_filename(".foobar", 3);
975     if (strcmp(f, ".03.foobar") != 0) {
976         fprintf(stderr, "output: failed selftest\n");
977         return 1;
978     }
979     free(f);
980 
981     return 0;
982 }
983 
984