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