/*
** iplog_input.c - IP input handling.
** Copyright (C) 1999-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_input.c,v 1.19 2001/01/01 16:02:14 odin Exp $
*/

#include <config.h>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/socket.h>
#include <pthread.h>

#include <iplog.h>
#include <iplog_options.h>
#include <iplog_input.h>

static struct ip *iplog_reassemble(const struct frag_data *fl);
static void deallocate_frags(struct frag_data *fl, u_long hash);
static void frag_cleanup(void *data);

static struct frag_list *frags;
static size_t frag_size;

/*
** Packet parser.
*/

void parse_packet(struct ip *ip) {
	u_char buf[MAX_HSTLEN];
	u_int h_len = __IP_HDR_LENGTH(ip);
	u_int len = ntohs(ip->ip_len);
	struct ip *temp_ip = NULL;

	/*
	** I think it's safe to assume most any TCP/IP implementation
	** will drop packets that fail either of the following two checks..
	*/

	if (h_len < sizeof(struct ip) || len < h_len) {
		if (opt_enabled(VERBOSE)) {
			if (opt_enabled(LOG_DEST)) {
				u_char buf2[MAX_HSTLEN];

				mysyslog("Short IP packet to %s from %s",
					host_lookup(&ip->ip_dst, any_res(), buf2, sizeof(buf2)),
					host_lookup(&ip->ip_src, any_res(),	buf, sizeof(buf)));
			} else {
				mysyslog("Short IP packet from %s",
					host_lookup(&ip->ip_src, any_res(),	buf, sizeof(buf)));
			}
		}

		return;
	}

	if (in_cksum((u_short *) ip, h_len) != 0) {
		if (opt_enabled(VERBOSE)) {
			if (opt_enabled(LOG_DEST)) {
				u_char buf2[MAX_HSTLEN];

				mysyslog("IP packet with invalid checksum to %s from %s",
					host_lookup(&ip->ip_dst, any_res(), buf2, sizeof(buf2)),
					host_lookup(&ip->ip_src, any_res(),	buf, sizeof(buf)));
			} else {
				mysyslog("IP packet with invalid checksum from %s",
					host_lookup(&ip->ip_src, any_res(),	buf, sizeof(buf)));
			}
		}

		return;
	}

	if (ntohs(ip->ip_off) & ~IP_DF) {
		bool done = false, found = false;
		u_int offset = ntohs(ip->ip_off);
		struct ip_fragment *tf, *temp;
		struct frag_data *cur;
		u_long hash;

		hash = FRAGHASH(ip->ip_src.s_addr, ip->ip_dst.s_addr,
						ip->ip_id, ip->ip_p);

		pthread_mutex_lock(&frags[hash].lock);
		for (cur = frags[hash].head ; cur != NULL ; cur = cur->next) {
			if (cur->id == ip->ip_id &&
				cur->saddr == ip->ip_src.s_addr &&
				cur->prot == ip->ip_p &&
				cur->daddr == ip->ip_dst.s_addr)
			{
				found = true;
				break;
			}
		}

		if (found == false)
			pthread_mutex_unlock(&frags[hash].lock);

		tf = xmalloc(sizeof(struct ip_fragment));

		tf->off = (offset & IP_OFFMASK) << 3;
		tf->len = len - __IP_HDR_LENGTH(ip);

		if (tf->off + tf->len > 0xffff) {
			if (opt_enabled(LOG_FRAG)) {
				if (opt_enabled(LOG_DEST)) {
					u_char buf2[MAX_HSTLEN];

					mysyslog("Oversized IP fragment to %s from %s",
						host_lookup(&ip->ip_dst, any_res(), buf2, sizeof(buf2)),
						host_lookup(&ip->ip_src, any_res(),	buf, sizeof(buf)));
				} else {
					mysyslog("Oversized IP fragment from %s",
						host_lookup(&ip->ip_src, any_res(),	buf, sizeof(buf)));
				}
			}

			if (found == true)
				deallocate_frags(cur, hash);

			free(tf);
			return;
		}

		if (found == false) {
			struct frag_data *tfl = xmalloc(sizeof(struct frag_data));

			tfl->prot = ip->ip_p;
			tfl->id = ip->ip_id;
			tfl->saddr = ip->ip_src.s_addr;
			tfl->daddr = ip->ip_dst.s_addr;
			tfl->expire = 0;
			tfl->frag = NULL;
			tfl->bytes = 0;
			tfl->t_len = 0;
			tfl->rf = 0;

			/*
			** If we've reached the limit, get rid of the oldest. In practice,
			** this will hardly, if ever, happen, unless we're being attacked.
			*/
			if (frags[hash].count >= FRAG_MAX) {
				u_long oldt = ~0;
				struct frag_data *q, *oldest = NULL;

				pthread_mutex_lock(&frags[hash].lock);
				for (q = frags[hash].head ; q != NULL ; q = q->next) {
					if ((u_long) q->expire <= oldt) {
						oldt = q->expire;
						oldest = q;
					}
				}
				deallocate_frags(oldest, hash);
			} else
				pthread_mutex_lock(&frags[hash].lock);

			cur = dlist_prepend(tfl, &frags[hash].head);
			++frags[hash].count;
		}

		if (tf->off == 0) {
			memcpy(&cur->header, ip, sizeof(struct ip));
			++cur->rf;
		}

		if (!(offset & IP_MF)) {
			cur->t_len = tf->off + tf->len;
			++cur->rf;
		}

		if (found == false) {
			tf->data = xmalloc(tf->len);
			memcpy(tf->data, (char *) ip + h_len, tf->len);
			temp = list_prepend(tf, &cur->frag);
		} else {
			struct ip_fragment *save;

			save = temp = cur->frag;

			while (temp != NULL) {
				if (tf->off < temp->off) {
					if ((temp->next && tf->off + tf->len > temp->next->off) ||
						(save != temp && tf->off < save->off + save->len))
					{
						if (opt_enabled(LOG_FRAG)) {
							if (opt_enabled(LOG_DEST)) {
								u_char buf2[MAX_HSTLEN];

								mysyslog("Overlapping IP frags to %s from %s",
									host_lookup(&ip->ip_dst, any_res(),
												buf2, sizeof(buf2)),
									host_lookup(&ip->ip_src, any_res(),
												buf, sizeof(buf)));
							} else {
								mysyslog("Overlapping IP fragments from %s",
									host_lookup(&ip->ip_src, any_res(),
												buf, sizeof(buf)));
							}
						}

						deallocate_frags(cur, hash);
						free(tf);
						return;
					} else {
						tf->data = xmalloc(tf->len);
						memcpy(tf->data, (char *) ip + h_len, tf->len);

						if (cur->frag == temp) {
							tf->next = temp;
							cur->frag = tf;
						} else {
							tf->next = temp;
							save->next = tf;
							tf->next = tf->next;
						}

						done = true;
					}

					break;
				} else {
					if (tf->off == temp->off) {
						if (opt_enabled(LOG_FRAG)) {
							if (opt_enabled(LOG_DEST)) {
								u_char buf2[MAX_HSTLEN];

								mysyslog("Duplicate IP fragments to %s from %s",
									host_lookup(&ip->ip_dst, any_res(),
												buf2, sizeof(buf2)),
									host_lookup(&ip->ip_src, any_res(),
												buf, sizeof(buf)));
							} else {
								mysyslog("Duplicate IP fragments from %s",
									host_lookup(&ip->ip_src, any_res(),
												buf, sizeof(buf)));
							}
						}

						deallocate_frags(cur, hash);
						free(tf);
						return;
					} else {
						save = temp;
						temp = temp->next;
					}
				}
			}

			if (done == false) {
				if (tf->off < save->len + save->off) {
					if (opt_enabled(LOG_FRAG)) {
						if (opt_enabled(LOG_DEST)) {
							u_char buf2[MAX_HSTLEN];

							mysyslog("Overlapping IP fragments to %s from %s",
									host_lookup(&ip->ip_dst, any_res(),
												buf2, sizeof(buf2)),
									host_lookup(&ip->ip_src, any_res(),
												buf, sizeof(buf)));
						} else {
							mysyslog("Overlapping IP fragments from %s",
								host_lookup(&ip->ip_src, any_res(),
											buf, sizeof(buf)));
						}
					}

					deallocate_frags(cur, hash);
					free(tf);
					return;
				}

				tf->data = xmalloc(tf->len);
				memcpy(tf->data, (char *) ip + sizeof(struct ip), tf->len);
				temp = list_append(tf, &cur->frag);
			}
		}

		cur->expire = time(NULL) + FRAG_TTL;
		cur->bytes += tf->len;

		if (cur->rf < 2) {
			pthread_mutex_unlock(&frags[hash].lock);
			return;
		}

		temp_ip = iplog_reassemble(cur);

		if (temp_ip == NULL) {
			pthread_mutex_unlock(&frags[hash].lock);
			return;
		}

		ip = temp_ip;
		deallocate_frags(cur, hash);
	}

	switch (ip->ip_p) {
		case IPPROTO_TCP:
			tcp_parser(ip);
			break;
		case IPPROTO_UDP:
			udp_parser(ip);
			break;
		case IPPROTO_ICMP:
			icmp_parser(ip);
			break;
	}

	if (temp_ip != NULL)
		free(temp_ip);
}

/*
** Reasseble the fragments in "frag_data" into a packet.  Return a pointer
** to the packet on success, NULL on failure.
*/

static struct ip *iplog_reassemble(const struct frag_data *fl) {
	u_long off = sizeof(struct ip);
	size_t plen = sizeof(struct ip) + fl->bytes;
	struct ip *ip;
	struct ip_fragment *cur;

	if (fl->bytes != fl->t_len)
		return (NULL);

	/*
	** If we got here, we have all the fragments contained in a sorted, linked
	** list.  Just glue together all the pieces.
	*/
	ip = xmalloc(plen);
	memcpy(ip, &fl->header, off);

	for (cur = fl->frag ; cur != NULL ; cur = cur->next)
		memcpy((char *) ip + (cur->off + off), cur->data, cur->len);

	ip->ip_len = htons(plen);
	/* We've ignored IP options, if any were present.. */
	ip->ip_hl = (sizeof(struct ip)) >> 2;

	/* Don't bother generating a new checksum, as we'll never use it */
	return (ip);
}

/*
** Destroy a fragment list.
** This must always be called with frags[hash].lock held.
*/

static void deallocate_frags(struct frag_data *fl, u_long hash) {
	list_destroy(fl->frag, frag_cleanup);
	dlist_delete(fl, &frags[hash].head);
	--frags[hash].count;
	pthread_mutex_unlock(&frags[hash].lock);
}

/*
** Fragment cleanup function.  This is called when an entry in the fragment
** hash table is deleted.
*/

static void frag_cleanup(void *data) {
	struct ip_fragment *frag = data;

	free(frag->data);
}

/*
** Removes expired entries from the fragment hash table.
*/

void expire_frags(void) {
	struct frag_data *cur;
	u_int i;

	for (i = 0 ; i < frag_size ; i++) {
		pthread_mutex_lock(&frags[i].lock);

		for (cur = frags[i].head ; cur != NULL ;) {
			if (cur->expire >= time(NULL)) {
				list_destroy(cur->frag, frag_cleanup);
				cur = dlist_delete(cur, &frags[i]);
				--frags[i].count;
			} else
				cur = cur->next;
		}

		pthread_mutex_unlock(&frags[i].lock);
	}
}

/*
** Initialize the fragment hash table.
*/

void init_frag_table(size_t f_size) {
	size_t i;

	frag_size = f_size;
	frags = xcalloc(frag_size, sizeof(struct frag_list));

	for (i = 0 ; i < frag_size ; i++)
		pthread_mutex_init(&frags[i].lock, NULL);
}

#ifdef HAVE_PTHREAD_CANCEL

/*
** Destroy the fragment hash table.
*/

void destroy_frag_table(void) {
	struct frag_data *cur;
	u_int i;

	/*
	** We've just canceled all the threads, and the locks could be in any
	** state, but it really doesn't matter now.  Just destroy the table and
	** reset everything.
	*/
	for (i = 0 ; i < frag_size ; i++) {
		for (cur = frags[i].head ; cur != NULL ; cur = cur->next)
			list_destroy(cur->frag, frag_cleanup);
		dlist_destroy(frags[i].head, NULL);
		frags[i].head = NULL;
		frags[i].count = 0;
		pthread_mutex_init(&frags[i].lock, NULL);
	}
}
#endif

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


syntax highlighted by Code2HTML, v. 0.9.1