/*
** iplog_icmp.c - iplog ICMP traffic logger.
** Copyright (C) 1999 behe <eric@ojnk.net>, Ryan McCabe <odin@numb.org>
** Copyright (C) 2000-2001 Ryan McCabe <odin@numb.org>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License, version 2,
** as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
**
** $Id: iplog_icmp.c,v 1.23 2001/01/01 16:02:14 odin Exp $
*/

#include <config.h>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pthread.h>
#include <time.h>

#include <iplog.h>
#include <iplog_config.h>
#include <iplog_options.h>
#include <iplog_icmp.h>
#include <iplog_scan.h>

extern struct filter_data *filters[3];

static void
print_router_advertisement(const struct icmp *, const u_char *, const u_char *);
static u_char *print_data(const struct icmp *icmp, u_char *buf, size_t len);

/*
** Returns true if host resolution is enabled for ICMP, false if it isn't.
*/

bool icmp_res(void) {
	return (opt_enabled(ICMP_RES));
}

/*
** ICMP packet handler.  Returns 0 on success, -1 on failure.
*/

int icmp_parser(const struct ip *ip) {
	struct icmp *icmp = (struct icmp *) ((char *) ip + __IP_HDR_LENGTH(ip));
	u_char src_host[MAX_HSTLEN], dst_host[MAX_HSTLEN], data_buf[MAX_HSTLEN];
	u_long len;

	if (icmp_filter(FIL_ICMP, ip, icmp->icmp_type))
		return (0);

	len = ntohs(ip->ip_len) - __IP_HDR_LENGTH(ip);

	if (opt_enabled(SMURF) && icmp->icmp_type == ICMP_ECHO_REPLY &&
		check_scan(ip, SCAN_SMURF, len, -1, -1) != 0)
	{
		return (0);
	}

	if (opt_enabled(PING_FLOOD) && icmp->icmp_type == ICMP_ECHO &&
		check_scan(ip, SCAN_PING, len, -1, -1) != 0)
	{
		return (0);
	}

	if (opt_enabled(SCANS_ONLY))
		return (0);

	host_lookup(&ip->ip_src, icmp_res(), src_host, sizeof(src_host));

	if (opt_enabled(LOG_DEST))
		host_lookup(&ip->ip_dst, icmp_res(), dst_host, sizeof(dst_host));

	switch (icmp->icmp_type) {
	case ICMP_TIME_EXCEEDED:
		print_data(icmp, data_buf, sizeof(data_buf));

		if (opt_enabled(LOG_DEST)) {
			mysyslog("ICMP: %s %s to %s (%s)", src_host,
				icmp_codes[ICMP_TIME_EXCEEDED], dst_host, data_buf);
		} else {
			mysyslog("ICMP: %s %s (%s)", src_host,
				icmp_codes[ICMP_TIME_EXCEEDED], data_buf);
		}
		break;

	case ICMP_ECHO_REPLY:
	case ICMP_ECHO:
		if (opt_enabled(LOG_DEST)) {
			mysyslog("ICMP: %s from %s to %s (%lu bytes)",
				icmp_codes[icmp->icmp_type], src_host, dst_host, len);
		} else {
			mysyslog("ICMP: %s from %s (%lu bytes)",
				icmp_codes[icmp->icmp_type], src_host, len);
		}
		break;

	case ICMP_DEST_UNREACHABLE:
		if (icmp->icmp_code >= UNREACHABLE_MAX) {
			if (opt_enabled(LOG_DEST)) {
				mysyslog("ICMP: %s (%d) from %s to %s",
					icmp_unreach[UNREACHABLE_MAX], icmp->icmp_code,
					src_host, dst_host);
			} else {
				mysyslog("ICMP: %s (%d) from %s",
					icmp_unreach[UNREACHABLE_MAX], icmp->icmp_code, src_host);
			}
		} else {
			u_char host[2 * MAX_HSTLEN + 10];

			print_data(icmp, data_buf, sizeof(data_buf));

			if (icmp->icmp_ip.ip_dst.s_addr != ip->ip_src.s_addr) {
				u_char dest[MAX_HSTLEN];

				host_lookup(&icmp->icmp_ip.ip_dst, icmp_res(),
							dest, sizeof(dest));

				snprintf(host, sizeof(host), "(from %s) %s", src_host, dest);
			} else
				snprintf(host, sizeof(host), "%s", src_host);

			if (opt_enabled(LOG_DEST)) {
				mysyslog("ICMP: %s: %s to %s (%s)",
					host, icmp_unreach[ICMP_DEST_UNREACHABLE],
					dst_host, data_buf);
			} else {
				mysyslog("ICMP: %s: %s to (%s)",
					host, icmp_unreach[ICMP_DEST_UNREACHABLE],
					data_buf);
			}
		}
		break;

	case ICMP_REDIRECT:
		if (icmp->icmp_code > REDIRECT_MAX) {
			if (opt_enabled(LOG_DEST)) {
				mysyslog("ICMP: undefined redirect code %d to %s from %s",
					icmp->icmp_code, dst_host, src_host);
			} else {
				mysyslog("ICMP: undefined redirect code %d from %s",
					icmp->icmp_code, src_host);
			}
		} else {
			u_char route[MAX_HSTLEN];

			host_lookup(&icmp->icmp_gwaddr, icmp_res(), route, sizeof(route));
			host_lookup(&icmp->icmp_ip.ip_dst, icmp_res(),
						data_buf, sizeof(data_buf));
			mysyslog(icmp_redir[icmp->icmp_code], route, src_host, data_buf);
		}
		break;

	case ICMP_TIMESTAMP_REPLY:
	{
		struct tm tsr_tm;
		u_long tsr_time;

		tsr_time = ntohl(icmp->icmp_ttime) / 1000;
		localtime_r((time_t *) &tsr_time, &tsr_tm);

		if (opt_enabled(LOG_DEST)) {
			mysyslog("ICMP: timestamp request to %s from %s (%.2d:%.02d:%.02d)",
				dst_host, src_host, tsr_tm.tm_hour,
				tsr_tm.tm_min, tsr_tm.tm_sec);
		} else {
			mysyslog("ICMP: timestamp request from %s (%.2d:%.02d:%.02d)",
				src_host, tsr_tm.tm_hour, tsr_tm.tm_min, tsr_tm.tm_sec);
		}

		break;
	}

	case ICMP_ADDRESS_REPLY:
	{
		struct in_addr iar_in;

		iar_in.s_addr = htonl(icmp->icmp_mask);
		host_lookup(&iar_in, icmp_res(), data_buf, sizeof(data_buf));

		if (opt_enabled(LOG_DEST)) {
			mysyslog("ICMP: %s to %s from %s (%s)",
				icmp_codes[ICMP_ADDRESS_REPLY], dst_host, src_host, data_buf);
		} else {
			mysyslog("ICMP: %s from %s (%s)",
				icmp_codes[ICMP_ADDRESS_REPLY], src_host, data_buf);
		}

		break;
	}

	case ICMP_PARAMETER_PROBLEM:
		if (opt_enabled(LOG_DEST)) {
			mysyslog("ICMP: %s to %s from %s (ptr %d)",
				icmp_codes[ICMP_PARAMETER_PROBLEM],
				dst_host, src_host, icmp->icmp_pptr);
		} else {
			mysyslog("ICMP: %s from %s (ptr %d)",
				icmp_codes[ICMP_PARAMETER_PROBLEM],
				src_host, icmp->icmp_pptr);
		}
		break;

	case ICMP_ROUTER_ADVERT:
		print_router_advertisement(icmp, src_host, dst_host);
		break;

	default:
		if (icmp->icmp_type > 18 || icmp_codes[icmp->icmp_type] == NULL) {
			if (opt_enabled(LOG_DEST)) {
				mysyslog("ICMP: undefined ICMP type %d to %s from %s",
					icmp->icmp_type, dst_host, src_host);
			} else {
				mysyslog("ICMP: undefined ICMP type %d from %s",
					icmp->icmp_type, src_host);
			}
		} else if (opt_enabled(LOG_DEST)) {
			mysyslog("ICMP: %s to %s from %s", icmp_codes[icmp->icmp_type],
				dst_host, src_host);
		} else {
			mysyslog("ICMP: %s from %s",
				icmp_codes[icmp->icmp_type], src_host);
		}
		break;
	}

	return (0);
}

/*
** Logs data encapsulated in ICMP packets.
*/

static u_char *print_data(const struct icmp *icmp, u_char *buf, size_t len) {
	struct tcphdr *data = (struct tcphdr *) ((struct icmp *) icmp + 1);

	if (icmp->icmp_ip.ip_p == IPPROTO_ICMP) {
		struct { u_char type, code; } *tmp = (typeof(tmp)) data;

		if (tmp->type == ICMP_DEST_UNREACHABLE || tmp->type == ICMP_REDIRECT) {
			snprintf(buf, len, "ICMP: %s code %u",
				icmp_types[min(tmp->type, ICMP_UNDEFINED)], tmp->code);
		} else {
			snprintf(buf, len, "ICMP: %s",
				icmp_types[min(tmp->type, ICMP_UNDEFINED)]);
		}
	} else {
		u_char pbuf[MAX_PRTLEN];

		snprintf(buf, len, "%s: dest port %u, source port %u",
			proto_lookup(icmp->icmp_ip.ip_p, pbuf, sizeof(pbuf)),
			ntohs(data->th_dport),
			ntohs(data->th_sport));
	}

	return (buf);
}

/*
** Logs ICMP router advertisement messages.
*/

static void print_router_advertisement(	const struct icmp *icmp,
										const u_char *src_host,
										const u_char *dst_host)
{
	struct id_rdiscovery *routers;
	u_int imin = 12, lifetime = ntohs(icmp->icmp_lifetime);
	u_char msg[MAX_IPLEN * 12 + 1], duration[32], lbuf[MAX_HSTLEN], i;

	routers = (struct id_rdiscovery *) &icmp->icmp_dun.id_rdiscovery;
	msg[0] = '\0';

	if (icmp->icmp_num_addr <= imin)
		imin = icmp->icmp_num_addr;

	for (i = 0 ; i < imin ; i++) {
		if (i > 0)
			xstrncat(msg, " -> ", sizeof(msg));

		host_lookup(&(routers++)->router_addr, false, lbuf, sizeof(lbuf));
		xstrncat(msg, lbuf, sizeof(msg));
	}

	if (lifetime < 60)
		snprintf(duration, sizeof(duration), "%u sec", lifetime);
	else {
		snprintf(duration, sizeof(duration), "%u min, %u sec",
				(lifetime / 60), (lifetime % 60));
	}

	if (opt_enabled(LOG_DEST)) {
		mysyslog("ICMP: %s to %s from %s: %s (lifetime %s)",
			icmp_codes[ICMP_ROUTER_ADVERT], dst_host, src_host, msg, duration);
	} else {
		mysyslog("ICMP: %s from %s: %s (lifetime %s)",
			icmp_codes[ICMP_ROUTER_ADVERT], src_host, msg, duration);
	}
}

/* vim:ts=4:sw=8:tw=0 */


syntax highlighted by Code2HTML, v. 0.9.1