1 /*
2  * Copyright (c) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 2000, 2009, 2010, 2012, 2013, 2016, 2019
3  * The Regents of the University of California. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University nor the names of its contributors
13  *       may be used to endorse or promote products derived from this software
14  *       without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND 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 REGENTS 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 #ifndef lint
29 static const char rcsid[] =
30     "@(#) $Id: report.c 1532 2021-12-15 22:11:07Z leres $ (LBL)";
31 #endif
32 
33 /*
34  * report - arpwatch report generating routines
35  */
36 
37 #include <sys/param.h>
38 #include <sys/types.h>				/* concession to AIX */
39 #include <sys/socket.h>
40 #include <sys/time.h>
41 #include <sys/wait.h>
42 
43 #if __STDC__
44 struct mbuf;
45 struct rtentry;
46 #endif
47 #include <net/if.h>
48 
49 #include <netinet/in.h>
50 
51 #include <arpa/inet.h>
52 
53 #include <ctype.h>
54 #include <errno.h>
55 #include <fcntl.h>
56 #include <paths.h>
57 #include <signal.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <syslog.h>
62 #ifdef TIME_WITH_SYS_TIME
63 #include <time.h>
64 #endif
65 #include <unistd.h>
66 
67 #include "gnuc.h"
68 #ifdef HAVE_OS_PROTO_H
69 #include "os-proto.h"
70 #endif
71 
72 #include "arpwatch.h"
73 #include "dns.h"
74 #include "ec.h"
75 #include "report.h"
76 #include "setsignal.h"
77 #include "util.h"
78 
79 #define PLURAL(n) ((n) == 1 || (n) == -1 ? "" : "s")
80 
81 static int cdepth;	/* number of outstanding children */
82 
83 static char *fmtdate(time_t);
84 static char *fmtdelta(time_t);
85 RETSIGTYPE reaper(int);
86 static int32_t gmt2local(void);
87 
88 static char *
fmtdelta(time_t t)89 fmtdelta(time_t t)
90 {
91 	char *cp;
92 	int minus;
93 	static char buf[132];
94 
95 	minus = 0;
96 	if (t < 0) {
97 		t = -t;
98 		++minus;
99 	}
100 	if (t < 60) {
101 		cp = "second";
102 	} else if (t < 60 * 60) {
103 		t /= 60;
104 		cp = "minute";
105 	} else if (t < 24 * 60 * 60) {
106 		t /= (60 * 60);
107 		cp = "hour";
108 	} else {
109 		t /= (24 * 60 * 60);
110 		cp = "day";
111 	}
112 	if (minus)
113 		t = -t;
114 	(void)snprintf(buf, sizeof(buf), "%u %s%s",
115 	    (u_int32_t)t, cp, PLURAL(t));
116 	return(buf);
117 }
118 
119 static char *dow[7] = {
120 	"Sunday",
121 	"Monday",
122 	"Tuesday",
123 	"Wednesday",
124 	"Thursday",
125 	"Friday",
126 	"Saturday"
127 };
128 
129 static char *moy[12] = {
130 	"January",
131 	"February",
132 	"March",
133 	"April",
134 	"May",
135 	"June",
136 	"July",
137 	"August",
138 	"September",
139 	"October",
140 	"November",
141 	"December"
142 };
143 
144 #define DOW(d) ((d) < 0 || (d) >= 7 ? "?" : dow[d])
145 #define MOY(m) ((m) < 0 || (m) >= 12 ? "?" : moy[(m)])
146 
147 static char *
fmtdate(time_t t)148 fmtdate(time_t t)
149 {
150 	struct tm *tm;
151 	int32_t mw;
152 	char ch;
153 	static int init = 0;
154 	static char zone[32], buf[132];
155 
156 	if (t == 0)
157 		return("<no date>");
158 
159 	if (!init) {
160 		mw = gmt2local() / 60;
161 		if (mw < 0) {
162 			ch = '-';
163 			mw = -mw;
164 		} else {
165 			ch = '+';
166 		}
167 		(void)snprintf(zone, sizeof(zone), "%c%02d%02d",
168 		    ch, mw / 60, mw % 60);
169 		++init;
170 	}
171 
172 	tm = localtime(&t);
173 	(void)snprintf(buf, sizeof(buf), "%s, %s %d, %d %d:%02d:%02d %s",
174 	    DOW(tm->tm_wday),
175 	    MOY(tm->tm_mon),
176 	    tm->tm_mday,
177 	    tm->tm_year + 1900,
178 	    tm->tm_hour,
179 	    tm->tm_min,
180 	    tm->tm_sec,
181 	    zone);
182 	return(buf);
183 }
184 
185 /*
186  * Returns the difference between gmt and local time in seconds.
187  * Use gmtime() and localtime() to keep things simple.
188  */
189 static int32_t
gmt2local(void)190 gmt2local(void)
191 {
192 	int dt, dir;
193 	struct tm *gmt, *loc;
194 	time_t t;
195 	struct tm sgmt;
196 
197 	t = time(NULL);
198 	gmt = &sgmt;
199 	*gmt = *gmtime(&t);
200 	loc = localtime(&t);
201 	dt = (loc->tm_hour - gmt->tm_hour) * 60 * 60 +
202 	    (loc->tm_min - gmt->tm_min) * 60;
203 
204 	/*
205 	 * If the year or julian day is different, we span 00:00 GMT
206 	 * and must add or subtract a day. Check the year first to
207 	 * avoid problems when the julian day wraps.
208 	 */
209 	dir = loc->tm_year - gmt->tm_year;
210 	if (dir == 0)
211 		dir = loc->tm_yday - gmt->tm_yday;
212 	dt += dir * 24 * 60 * 60;
213 
214 	return (dt);
215 }
216 
217 RETSIGTYPE
reaper(int signo)218 reaper(int signo)
219 {
220 	pid_t pid;
221 	DECLWAITSTATUS status;
222 
223 	for (;;) {
224 		pid = waitpid((pid_t)0, &status, WNOHANG);
225 		if ((int)pid < 0) {
226 			/* ptrace foo */
227 			if (errno == EINTR)
228 				continue;
229 			/* ECHILD means no one left */
230 			if (errno != ECHILD)
231 				lg(LOG_ERR, "reaper: %s", strerror(errno));
232 			break;
233 		}
234 		/* Already got everyone who was done */
235 		if (pid == 0)
236 			break;
237 		--cdepth;
238 		if (WEXITSTATUS(status))
239 			lg(LOG_DEBUG, "reaper: pid %d, exit status %d",
240 			    pid, WEXITSTATUS(status));
241 	}
242 	return RETSIGVAL;
243 }
244 
245 void
report(const char * title,u_int32_t a,const u_char * e1,const u_char * e2,const time_t * t1p,const time_t * t2p)246 report(const char *title, u_int32_t a, const u_char *e1, const u_char *e2,
247     const time_t *t1p, const time_t *t2p)
248 {
249 	char *cp, *hn;
250 	int fd, pid;
251 	FILE *f;
252 	char tempfile[64], cpu[64], os[64];
253 	char *fmt = "%20s: %s\n";
254 	char *sendmail = PATH_SENDMAIL;
255 	char *unknown = "<unknown>";
256 	char buf[132];
257 	static int init = 0;
258 
259 	/* No report until we're initialized */
260 	if (initializing)
261 		return;
262 
263 	/* No mail for 0.0.0.0 if -z */
264 	if (zeroflag && a == 0) {
265 		dosyslog(LOG_NOTICE, title, a, e1, e2);
266 		return;
267 	}
268 
269 	if (debug) {
270 		if (debug > 1) {
271 			dosyslog(LOG_NOTICE, title, a, e1, e2);
272 			return;
273 		}
274 		f = stdout;
275 		(void)putc('\n', f);
276 	} else {
277 		/* Setup child reaper if we haven't already */
278 		if (!init) {
279 			(void)setsignal(SIGCHLD, reaper);
280 			++init;
281 		}
282 		while (cdepth >= 3) {
283 			lg(LOG_ERR, "report: pausing (cdepth %d)", cdepth);
284 			pause();
285 		}
286 
287 		/* Syslog this event too */
288 		dosyslog(LOG_NOTICE, title, a, e1, e2);
289 
290 		/* Update child depth */
291 		++cdepth;
292 
293 		/* Fork off child to send mail */
294 		pid = fork();
295 		if (pid) {
296 			/* Parent */
297 			if (pid < 0)
298 				lg(LOG_ERR, "report: fork() 1: %s",
299 				    strerror(errno));
300 			return;
301 		}
302 
303 		/* Child */
304 		closelog();
305 		(void)strncpy(tempfile, "/tmp/arpwatch.XXXXXX",
306 		    sizeof(tempfile));
307 		tempfile[sizeof(tempfile) - 1] = '\0';
308 		if ((fd = mkstemp(tempfile)) < 0) {
309 			lg(LOG_ERR, "mkstemp(%s) %s",
310 			    tempfile, strerror(errno));
311 			exit(1);
312 		}
313 		if ((f = fdopen(fd, "w+")) == NULL) {
314 			lg(LOG_ERR, "child fdopen(%s): %s",
315 			    tempfile, strerror(errno));
316 			exit(1);
317 		}
318 		/* Cheap delete-on-close */
319 		if (unlink(tempfile) < 0)
320 			lg(LOG_ERR, "unlink(%s): %s",
321 			    tempfile, strerror(errno));
322 	}
323 
324 	(void)fprintf(f, "From: %s\n", watchee);
325 	(void)fprintf(f, "To: %s\n", watcher);
326 	hn = gethname(a);
327 	if (hn != NULL && !isdigit(*hn))
328 		(void)fprintf(f, "Subject: %s (%s)\n", title, hn);
329 	else {
330 		(void)fprintf(f, "Subject: %s\n", title);
331 		hn = unknown;
332 	}
333 	(void)putc('\n', f);
334 	if (hn != NULL)
335 		(void)fprintf(f, fmt, "hostname", hn);
336 	(void)fprintf(f, fmt, "ip address", intoa(a));
337 	(void)fprintf(f, fmt, "ethernet address", e2str(e1));
338 	if ((cp = ec_find(e1)) == NULL)
339 		cp = unknown;
340 	(void)fprintf(f, fmt, "ethernet vendor", cp);
341 	if (hn != unknown && gethinfo(hn, cpu, sizeof(cpu), os, sizeof(os))) {
342 		(void)snprintf(buf, sizeof(buf), "%s %s", cpu, os);
343 		(void)fprintf(f, fmt, "dns cpu & os", buf);
344 	}
345 	if (e2) {
346 		(void)fprintf(f, fmt, "old ethernet address", e2str(e2));
347 		if ((cp = ec_find(e2)) == NULL)
348 			cp = unknown;
349 		(void)fprintf(f, fmt, "old ethernet vendor", cp);
350 	}
351 	if (t1p)
352 		(void)fprintf(f, fmt, "timestamp", fmtdate(*t1p));
353 	if (t2p)
354 		(void)fprintf(f, fmt, "previous timestamp", fmtdate(*t2p));
355 	if (t1p && t2p && *t1p && *t2p)
356 		(void)fprintf(f, fmt, "delta", fmtdelta(*t1p - *t2p));
357 
358 	if (debug) {
359 		fflush(f);
360 		return;
361 	}
362 
363 	(void)rewind(f);
364 	if (dup2(fileno(f), fileno(stdin)) < 0) {
365 		lg(LOG_ERR, "dup2: %s", strerror(errno));
366 		exit(1);
367 	}
368 	/* XXX Need to freopen()? */
369 
370 	/*
371 	 * Open /dev/null as stdout and stderr so that sendmail 8.12.1 (and
372 	 * above ?) won't complain about missing file descriptors.
373 	 */
374 	fd = open(_PATH_DEVNULL, O_RDWR);
375 	if (fd < 0) {
376 		lg(LOG_ERR, "Cannot open %s: %s",
377 		    _PATH_DEVNULL, strerror(errno));
378 		exit(1);
379 	}
380 	if (dup2(fd, STDOUT_FILENO) < 0) {
381 		lg(LOG_ERR, "Cannot dup2 %s to stdout: %s",
382 		    _PATH_DEVNULL, strerror(errno));
383 		exit(1);
384 	}
385 	if (dup2(fd, STDERR_FILENO) < 0) {
386 		lg(LOG_ERR, "Cannot dup2 %s to stderr: %s",
387 		    _PATH_DEVNULL, strerror(errno));
388 		exit(1);
389 	}
390 	close(fd);
391 
392 	/* Always Deliver interactively (pause when child depth gets large) */
393 	execl(sendmail, "sendmail", "-odi", watcher, NULL);
394 	lg(LOG_ERR, "execl: %s: %s", sendmail, strerror(errno));
395 	exit(1);
396 }
397