Blob Blame History Raw
/* For terms of usage/redistribution/modification see the LICENSE file */
/* For authors and contributors see the AUTHORS file */

/***

othptab.c - non-TCP protocol display module

***/

#include "iptraf-ng-compat.h"

#include "tui/winops.h"

#include "arphdr.h"
#include "options.h"
#include "tcptable.h"
#include "othptab.h"
#include "deskman.h"
#include "attrs.h"
#include "log.h"
#include "revname.h"
#include "rvnamed.h"
#include "servname.h"
#include "addproto.h"
#include "packet.h"
#include "hostmon.h"
#include "sockaddr.h"

#define MSGSTRING_MAX	240
#define SHORTSTRING_MAX	40

/*
* A trick to suppress uninitialized variable warning without generating any
* code
*/
#define uninitialized_var(x) x = x

static void writeothplog(int logging, FILE *fd, char *protname,
			 char *description, char *additional, int is_ip,
			 int withmac, struct othptabent *entry)
{
	char msgbuffer[MSGSTRING_MAX];
	char scratchpad[MSGSTRING_MAX];

	if (logging) {
		memset(msgbuffer, 0, MSGSTRING_MAX);

		strcpy(msgbuffer, protname);
		strcat(msgbuffer, "; ");
		strcat(msgbuffer, entry->iface);
		sprintf(scratchpad, "; %u bytes;", entry->pkt_length);
		strcat(msgbuffer, scratchpad);

		if ((entry->smacaddr[0] != '\0') && (withmac)) {
			sprintf(scratchpad, " source MAC address %s;",
				entry->smacaddr);
			strcat(msgbuffer, scratchpad);
		}

		if (is_ip) {
			if (((entry->protocol == IPPROTO_UDP)
			     && (!(entry->fragment)))
			    || (entry->protocol == IPPROTO_TCP))
				sprintf(scratchpad, " from %s:%s to %s:%s",
					entry->s_fqdn, entry->un.udp.s_sname,
					entry->d_fqdn, entry->un.udp.d_sname);
			else
				sprintf(scratchpad, " from %s to %s",
					entry->s_fqdn, entry->d_fqdn);
		} else
			sprintf(scratchpad, " from %s to %s ", entry->smacaddr,
				entry->dmacaddr);

		strcat(msgbuffer, scratchpad);
		strcpy(scratchpad, "");
		if (strcmp(description, "") != 0) {
			sprintf(scratchpad, "; %s", description);
			strcat(msgbuffer, scratchpad);
		}
		strcpy(scratchpad, "");
		if (strcmp(additional, "") != 0) {
			sprintf(scratchpad, " (%s)", additional);
			strcat(msgbuffer, scratchpad);
		}
		writelog(logging, fd, msgbuffer);
	}
}

void init_othp_table(struct othptable *table)
{
	unsigned int winht;
	unsigned int wintop;
	unsigned int obmaxx __unused;

	winht = LINES - (LINES * 0.6) - 2;
	wintop = (LINES * 0.6) + 1;

	table->count = 0;
	table->lastpos = 0;
	table->strindex = 0;
	table->htstat = NOHTIND;
	table->head = table->tail = NULL;
	table->firstvisible = table->lastvisible = NULL;
	table->borderwin = newwin(winht, COLS, wintop, 0);
	table->borderpanel = new_panel(table->borderwin);
	wattrset(table->borderwin, BOXATTR);
	tx_box(table->borderwin, ACS_VLINE, ACS_HLINE);

	table->head = table->tail = NULL;
	table->othpwin = newwin(winht - 2, COLS - 2, wintop + 1, 1);
	table->othppanel = new_panel(table->othpwin);
	wattrset(table->othpwin, STDATTR);
	tx_colorwin(table->othpwin);
	update_panels();
	doupdate();

	tx_stdwinset(table->othpwin);
	getmaxyx(table->borderwin, table->obmaxy, obmaxx);
	table->oimaxy = table->obmaxy - 2;
}

void process_dest_unreach(struct tcptable *table, char *packet, char *ifname)
{
	struct iphdr *ip;
	struct ip6_hdr *ip6;
	struct tcphdr *tcp;
	struct tcptableent *tcpentry;

	ip = (struct iphdr *) (packet + 8);

	/*
	 * Timeout checking won't be performed either, so we just pass 0
	 * as timeout variable.
	 */

	if (ip->version == 6) {
		ip6 = (struct ip6_hdr *) (packet + 8);
		if (ip6->ip6_nxt != IPPROTO_TCP)
			return;
		tcp = (struct tcphdr *) (packet + 48);
		struct sockaddr_storage saddr, daddr;
		sockaddr_make_ipv6(&saddr, &ip6->ip6_src);
		sockaddr_set_port(&saddr, ntohs(tcp->source));
		sockaddr_make_ipv6(&daddr, &ip6->ip6_dst);
		sockaddr_set_port(&daddr, ntohs(tcp->dest));
		tcpentry =
		    in_table(table, &saddr, &daddr, ifname, 0, NULL, 0);
	} else {
		if (ip->protocol != IPPROTO_TCP)
			return;
		tcp = (struct tcphdr *) (packet + 8 + (ip->ihl * 4));
		struct sockaddr_storage saddr, daddr;
		sockaddr_make_ipv4(&saddr, ip->saddr);
		sockaddr_set_port(&saddr, ntohs(tcp->source));
		sockaddr_make_ipv4(&daddr, ip->daddr);
		sockaddr_set_port(&daddr, ntohs(tcp->dest));
		tcpentry =
		    in_table(table, &saddr, &daddr, ifname, 0, NULL, 0);
	}

	if (tcpentry != NULL) {
		tcpentry->stat = tcpentry->oth_connection->stat = FLAG_RST;
		addtoclosedlist(table, tcpentry);
	}
}

struct othptabent *add_othp_entry(struct othptable *table, struct pkt_hdr *pkt,
				  struct sockaddr_storage *saddr,
				  struct sockaddr_storage *daddr,
				  int is_ip,
				  int protocol,
				  char *packet2,
				  char *ifname, int *rev_lookup, int rvnfd,
				  int logging, FILE *logfile, int fragment)
{
	struct othptabent *new_entry;
	struct othptabent *temp;

	new_entry = xmallocz(sizeof(struct othptabent));

	new_entry->is_ip = is_ip;
	new_entry->fragment = fragment;

	if (options.mac || !is_ip) {
		if (pkt->pkt_hatype == ARPHRD_ETHER) {
			convmacaddr((char *) pkt->ethhdr->h_source, new_entry->smacaddr);
			convmacaddr((char *) pkt->ethhdr->h_dest, new_entry->dmacaddr);
		} else if (pkt->pkt_hatype == ARPHRD_FDDI) {
			convmacaddr((char *) pkt->fddihdr->saddr, new_entry->smacaddr);
			convmacaddr((char *) pkt->fddihdr->daddr, new_entry->dmacaddr);
		}
	}

	if (is_ip) {
		sockaddr_copy(&new_entry->saddr, saddr);
		sockaddr_copy(&new_entry->daddr, daddr);

		revname(rev_lookup, saddr, new_entry->s_fqdn,
			sizeof(new_entry->s_fqdn), rvnfd);
		revname(rev_lookup, daddr, new_entry->d_fqdn,
			sizeof(new_entry->d_fqdn), rvnfd);

		if (!fragment) {
			if (protocol == IPPROTO_ICMP) {
				new_entry->un.icmp.type =
				    ((struct icmphdr *) packet2)->type;
				new_entry->un.icmp.code =
				    ((struct icmphdr *) packet2)->code;
			} else if (protocol == IPPROTO_ICMPV6) {
				new_entry->un.icmp6.type =
				    ((struct icmp6_hdr *) packet2)->icmp6_type;
				new_entry->un.icmp6.code =
				    ((struct icmp6_hdr *) packet2)->icmp6_code;
			} else if (protocol == IPPROTO_UDP) {
				servlook(ntohs(((struct udphdr *) packet2)->source),
					 IPPROTO_UDP, new_entry->un.udp.s_sname,
					 10);
				servlook(ntohs(((struct udphdr *) packet2)->dest),
					 IPPROTO_UDP, new_entry->un.udp.d_sname,
					 10);
			} else if (protocol == IPPROTO_OSPFIGP) {
				new_entry->un.ospf.type =
				    ((struct ospfhdr *) packet2)->ospf_type;
				new_entry->un.ospf.area =
				    ntohl(((struct ospfhdr *) packet2)->
					  ospf_areaid.s_addr);
				inet_ntop(AF_INET,
					  &((struct ospfhdr *)packet2)->ospf_routerid,
					  new_entry->un.ospf.routerid,
					  sizeof(new_entry->un.ospf.routerid));
			}
		}
	} else {
		new_entry->linkproto = pkt->pkt_hatype;

		if (protocol == ETH_P_ARP) {
			new_entry->un.arp.opcode =
			    ((struct arp_hdr *) packet2)->ar_op;
			memcpy(&(new_entry->un.arp.src_ip_address),
			       &(((struct arp_hdr *) packet2)->ar_sip), 4);
			memcpy(&(new_entry->un.arp.dest_ip_address),
			       &(((struct arp_hdr *) packet2)->ar_tip), 4);
		} else if (protocol == ETH_P_RARP) {
			new_entry->un.rarp.opcode =
			    ((struct arphdr *) packet2)->ar_op;
			memcpy(&(new_entry->un.rarp.src_mac_address),
			       &(((struct arp_hdr *) packet2)->ar_sha), 6);
			memcpy(&(new_entry->un.rarp.dest_mac_address),
			       &(((struct arp_hdr *) packet2)->ar_tha), 6);
		}
	}

	new_entry->protocol = protocol;
	strcpy(new_entry->iface, ifname);

	new_entry->pkt_length = pkt->pkt_len;

	if (table->head == NULL) {
		new_entry->prev_entry = NULL;
		table->head = new_entry;
		table->firstvisible = new_entry;
	}
	/*
	 * Max number of entries in the lower window is 512.  Upon reaching
	 * this figure, oldest entries are thrown out.
	 */

	if (table->count == 512) {
		if (table->firstvisible == table->head) {
			wscrl(table->othpwin, 1);
			printothpentry(table, table->lastvisible->next_entry,
				       table->oimaxy - 1, logging, logfile);
			table->firstvisible = table->firstvisible->next_entry;
			table->lastvisible = table->lastvisible->next_entry;
		}
		temp = table->head;
		table->head = table->head->next_entry;
		table->head->prev_entry = NULL;
		free(temp);
	} else
		table->count++;

	if (table->tail != NULL) {
		new_entry->prev_entry = table->tail;
		table->tail->next_entry = new_entry;
	}
	table->tail = new_entry;
	new_entry->next_entry = NULL;

	table->lastpos++;
	new_entry->index = table->lastpos;

	if (table->count <= table->oimaxy) {
		table->lastvisible = new_entry;
		printothpentry(table, new_entry, table->count - 1, logging,
			       logfile);
	} else if (table->lastvisible == table->tail->prev_entry) {
		wscrl(table->othpwin, 1);
		table->firstvisible = table->firstvisible->next_entry;
		table->lastvisible = table->tail;
		printothpentry(table, new_entry, table->oimaxy - 1, logging,
			       logfile);
	}
	return new_entry;
}

/*
 * Function to retrieve non-IP packet tags.  No further details are
 * provided beyond the type.
 */

static char *packetlookup(unsigned int protocol)
{
	unsigned int i = 0;
	static struct packetstruct packettypes[] = {
		{"DEC MOP dump/load", 0x6001},
		{"DEC MOP remote console", 0x6002},
		{"DEC DECnet Phase IV", 0x6003},
		{"DEC LAT", 0x6004},
		{"DEC DECnet Diagnostics", 0x6005},
		{"DEC DECnet Customer Use", 0x6006},
		{"DEC DECnet SCA", 0x6007},
		{"IPX", 0x8137},
		{NULL, 0x0}
	};


	while ((packettypes[i].packet_name != NULL)
	       && (packettypes[i].protocol != protocol))
		i++;

	return packettypes[i].packet_name;

}

void printothpentry(struct othptable *table, struct othptabent *entry,
		    unsigned int target_row, int logging, FILE * logfile)
{
	char protname[SHORTSTRING_MAX];
	char description[SHORTSTRING_MAX];
	char additional[MSGSTRING_MAX];
	char msgstring[MSGSTRING_MAX];
	char scratchpad[MSGSTRING_MAX];
	char sp_buf[SHORTSTRING_MAX];
	char *startstr;

	char *packet_type;

	struct in_addr uninitialized_var(saddr);

	char rarp_mac_addr[18];

	unsigned int unknown = 0;

	struct protoent *protptr;

	sprintf(sp_buf, "%%%dc", COLS - 2);

	wmove(table->borderwin, table->obmaxy - 1, 1);
	if ((table->lastvisible == table->tail) && (table->htstat != TIND)
	    && (table->count >= table->oimaxy)) {
		wprintw(table->borderwin, " Bottom ");
		table->htstat = TIND;
	} else if ((table->firstvisible == table->head)
		   && (table->htstat != HIND)) {
		wprintw(table->borderwin, " Top ");
		table->htstat = HIND;
	}
	if (!(entry->is_ip)) {
		wmove(table->othpwin, target_row, 0);
		scrollok(table->othpwin, 0);
		wattrset(table->othpwin, UNKNATTR);
		wprintw(table->othpwin, sp_buf, ' ');
		scrollok(table->othpwin, 1);
		wmove(table->othpwin, target_row, 1);

		switch (entry->protocol) {
		case ETH_P_ARP:
			sprintf(msgstring, "ARP ");
			switch (ntohs(entry->un.arp.opcode)) {
			case ARPOP_REQUEST:
				strcat(msgstring, "request for ");
				memcpy(&(saddr.s_addr),
				       entry->un.arp.dest_ip_address, 4);
				break;
			case ARPOP_REPLY:
				strcat(msgstring, "reply from ");
				memcpy(&(saddr.s_addr),
				       entry->un.arp.src_ip_address, 4);
				break;
			}

			inet_ntop(AF_INET, &saddr, scratchpad, sizeof(scratchpad));
			strcat(msgstring, scratchpad);
			wattrset(table->othpwin, ARPATTR);
			break;
		case ETH_P_RARP:
			sprintf(msgstring, "RARP ");
			memset(rarp_mac_addr, 0, sizeof(rarp_mac_addr));
			switch (ntohs(entry->un.rarp.opcode)) {
			case ARPOP_RREQUEST:
				strcat(msgstring, "request for ");
				convmacaddr(entry->un.rarp.dest_mac_address,
					    rarp_mac_addr);
				break;
			case ARPOP_RREPLY:
				strcat(msgstring, "reply from ");
				convmacaddr(entry->un.rarp.src_mac_address,
					    rarp_mac_addr);
				break;
			}

			sprintf(scratchpad, rarp_mac_addr);
			strcat(msgstring, scratchpad);
			wattrset(table->othpwin, ARPATTR);
			break;
		default:
			packet_type = packetlookup(entry->protocol);
			if (packet_type == NULL)
				sprintf(msgstring, "Non-IP (0x%x)",
					entry->protocol);
			else
				sprintf(msgstring, "Non-IP (%s)", packet_type);

			wattrset(table->othpwin, UNKNATTR);
		}

		strcpy(protname, msgstring);
		sprintf(scratchpad, " (%u bytes)", entry->pkt_length);
		strcat(msgstring, scratchpad);

		if ((entry->linkproto == ARPHRD_ETHER)
		    || (entry->linkproto == ARPHRD_FDDI)) {
			sprintf(scratchpad, " from %s to %s on %s",
				entry->smacaddr, entry->dmacaddr, entry->iface);

			strcat(msgstring, scratchpad);
		}
		startstr = msgstring + table->strindex;
		waddnstr(table->othpwin, startstr, COLS - 4);
		writeothplog(logging, logfile, protname, "", "", 0, 0, entry);
		return;
	}
	strcpy(additional, "");
	strcpy(description, "");

	switch (entry->protocol) {
	case IPPROTO_UDP:
		wattrset(table->othpwin, UDPATTR);
		strcpy(protname, "UDP");
		break;
	case IPPROTO_ICMP:
		wattrset(table->othpwin, STDATTR);
		strcpy(protname, "ICMP");
		break;
	case IPPROTO_OSPFIGP:
		wattrset(table->othpwin, OSPFATTR);
		strcpy(protname, "OSPF");
		break;
	case IPPROTO_IGP:
		wattrset(table->othpwin, IGPATTR);
		strcpy(protname, "IGP");
		break;
	case IPPROTO_IGMP:
		wattrset(table->othpwin, IGMPATTR);
		strcpy(protname, "IGMP");
		break;
	case IPPROTO_IGRP:
		wattrset(table->othpwin, IGRPATTR);
		strcpy(protname, "IGRP");
		break;
	case IPPROTO_GRE:
		wattrset(table->othpwin, GREATTR);
		strcpy(protname, "GRE");
		break;
	case IPPROTO_ICMPV6:
		wattrset(table->othpwin, ICMPV6ATTR);
		strcpy(protname, "ICMPv6");
		break;
	case IPPROTO_IPV6:
		wattrset(table->othpwin, IPV6ATTR);
		strcpy(protname, "IPv6 tun");
		break;
	default:
		wattrset(table->othpwin, UNKNIPATTR);
		protptr = getprotobynumber(entry->protocol);
		if (protptr != NULL) {
			sprintf(protname, protptr->p_aliases[0]);
		} else {
			sprintf(protname, "IP protocol");
			unknown = 1;
		}
	}

	if (!(entry->fragment)) {
		if (entry->protocol == IPPROTO_ICMP) {
			switch (entry->un.icmp.type) {
			case ICMP_ECHOREPLY:
				strcpy(description, "echo rply");
				break;
			case ICMP_ECHO:
				strcpy(description, "echo req");
				break;
			case ICMP_DEST_UNREACH:
				strcpy(description, "dest unrch");
				switch (entry->un.icmp.code) {
				case ICMP_NET_UNREACH:
					strcpy(additional, "ntwk");
					break;
				case ICMP_HOST_UNREACH:
					strcpy(additional, "host");
					break;
				case ICMP_PROT_UNREACH:
					strcpy(additional, "proto");
					break;
				case ICMP_PORT_UNREACH:
					strcpy(additional, "port");
					break;
				case ICMP_FRAG_NEEDED:
					strcpy(additional, "DF set");
					break;
				case ICMP_SR_FAILED:
					strcpy(additional, "src rte fail");
					break;
				case ICMP_NET_UNKNOWN:
					strcpy(additional, "net unkn");
					break;
				case ICMP_HOST_UNKNOWN:
					strcpy(additional, "host unkn");
					break;
				case ICMP_HOST_ISOLATED:
					strcpy(additional, "src isltd");
					break;
				case ICMP_NET_ANO:
					strcpy(additional, "net comm denied");
					break;
				case ICMP_HOST_ANO:
					strcpy(additional, "host comm denied");
					break;
				case ICMP_NET_UNR_TOS:
					strcpy(additional, "net unrch for TOS");
					break;
				case ICMP_HOST_UNR_TOS:
					strcpy(additional,
					       "host unrch for TOS");
					break;
				case ICMP_PKT_FILTERED:
					strcpy(additional, "pkt fltrd");
					break;
				case ICMP_PREC_VIOLATION:
					strcpy(additional, "prec violtn");
					break;
				case ICMP_PREC_CUTOFF:
					strcpy(additional, "prec cutoff");
					break;
				}

				break;
			case ICMP_SOURCE_QUENCH:
				strcpy(description, "src qnch");
				break;
			case ICMP_REDIRECT:
				strcpy(description, "redirct");
				break;
			case ICMP_TIME_EXCEEDED:
				strcpy(description, "time excd");
				break;
			case ICMP_PARAMETERPROB:
				strcpy(description, "param prob");
				break;
			case ICMP_TIMESTAMP:
				strcpy(description, "timestmp req");
				break;
			case ICMP_INFO_REQUEST:
				strcpy(description, "info req");
				break;
			case ICMP_INFO_REPLY:
				strcpy(description, "info rep");
				break;
			case ICMP_ADDRESS:
				strcpy(description, "addr mask req");
				break;
			case ICMP_ADDRESSREPLY:
				strcpy(description, "addr mask rep");
				break;
			default:
				strcpy(description, "bad/unkn");
				break;
			}
		} else if (entry->protocol == IPPROTO_ICMPV6) {
			switch (entry->un.icmp6.type) {
			case ICMP6_DST_UNREACH:
				strcpy(description, "dest unrch");
				switch (entry->un.icmp6.code) {
				case ICMP6_DST_UNREACH_NOROUTE:
					strcpy(additional, "no route");
					break;
				case ICMP6_DST_UNREACH_ADMIN:
					strcpy(additional, "admin");
					break;
#ifdef ICMP6_DST_UNREACH_NOTNEIGHBOR
				case ICMP6_DST_UNREACH_NOTNEIGHBOR:
					strcpy(additional, "not neigh");
#else
				case ICMP6_DST_UNREACH_BEYONDSCOPE:
					strcpy(additional, "not beyondsp");
#endif
					break;
				case ICMP6_DST_UNREACH_ADDR:
					strcpy(additional, "unreach addr");
					break;
				case ICMP6_DST_UNREACH_NOPORT:
					strcpy(additional, "no port");
					break;
				}
				break;
			case ICMP6_PACKET_TOO_BIG:
				strcpy(description, "pkt too big");
				break;
			case ICMP6_TIME_EXCEEDED:
				strcpy(description, "time exceeded");
				break;
			case ICMP6_PARAM_PROB:
				strcpy(description, "param prob");
				break;
			case ICMP6_ECHO_REQUEST:
				strcpy(description, "echo req");
				break;
			case ICMP6_ECHO_REPLY:
				strcpy(description, "echo rply");
				break;
			case ND_ROUTER_SOLICIT:
				strcpy(description, "router sol");
				break;
			case ND_ROUTER_ADVERT:
				strcpy(description, "router adv");
				break;
#ifdef ICMP6_MEMBERSHIP_QUERY
			case ICMP6_MEMBERSHIP_QUERY:
				strcpy(description, "mbrship query");
				break;
#endif
#ifdef ICMP6_MEMBERSHIP_REPORT
			case ICMP6_MEMBERSHIP_REPORT:
				strcpy(description, "mbrship report");
				break;
#endif
#ifdef ICMP6_MEMBERSHIP_REDUCTION
			case ICMP6_MEMBERSHIP_REDUCTION:
				strcpy(description, "mbrship reduc");
				break;
#endif
			case ND_NEIGHBOR_SOLICIT:
				strcpy(description, "neigh sol");
				break;
			case ND_NEIGHBOR_ADVERT:
				strcpy(description, "neigh adv");
				break;
			case ND_REDIRECT:
				strcpy(description, "redirect");
				break;
			default:
				strcpy(description, "bad/unkn");
				break;
			}
		} else if (entry->protocol == IPPROTO_OSPFIGP) {
			switch (entry->un.ospf.type) {
			case OSPF_TYPE_HELLO:
				strcpy(description, "hlo");
				break;
			case OSPF_TYPE_DB:
				strcpy(description, "DB desc");
				break;
			case OSPF_TYPE_LSR:
				strcpy(description, "LSR");
				break;
			case OSPF_TYPE_LSU:
				strcpy(description, "LSU");
				break;
			case OSPF_TYPE_LSA:
				strcpy(description, "LSA");
				break;
			}
			sprintf(additional, "a=%lu r=%s", entry->un.ospf.area,
				entry->un.ospf.routerid);
		}
	} else
		strcpy(description, "fragment");

	strcpy(msgstring, protname);
	strcat(msgstring, " ");

	if (strcmp(description, "") != 0) {
		strcat(msgstring, description);
		strcat(msgstring, " ");
	}
	if (strcmp(additional, "") != 0) {
		sprintf(scratchpad, "(%s) ", additional);
		strcat(msgstring, scratchpad);
	}
	if (unknown) {
		sprintf(scratchpad, "%u ", entry->protocol);
		strcat(msgstring, scratchpad);
	}
	sprintf(scratchpad, "(%u bytes) ", entry->pkt_length);
	strcat(msgstring, scratchpad);

	if ((entry->protocol == IPPROTO_UDP) && (!(entry->fragment))) {
		sprintf(scratchpad, "from %.40s:%s to %.40s:%s", entry->s_fqdn,
			entry->un.udp.s_sname, entry->d_fqdn,
			entry->un.udp.d_sname);
	} else {
		sprintf(scratchpad, "from %.40s to %.40s", entry->s_fqdn,
			entry->d_fqdn);
	}

	strcat(msgstring, scratchpad);

	if (((entry->smacaddr)[0] != '\0') && options.mac) {
		snprintf(scratchpad, MSGSTRING_MAX, " (src HWaddr %s)",
			 entry->smacaddr);
		strcat(msgstring, scratchpad);
	}
	strcat(msgstring, " on ");
	strcat(msgstring, entry->iface);

	wmove(table->othpwin, target_row, 0);
	scrollok(table->othpwin, 0);
	wprintw(table->othpwin, sp_buf, ' ');
	scrollok(table->othpwin, 1);
	wmove(table->othpwin, target_row, 1);
	startstr = msgstring + table->strindex;
	waddnstr(table->othpwin, startstr, COLS - 4);

	if (logging)
		writeothplog(logging, logfile, protname, description,
			     additional, 1, options.mac, entry);
}

void refresh_othwindow(struct othptable *table)
{
	int target_row = 0;
	struct othptabent *entry;

	wattrset(table->othpwin, STDATTR);
	tx_colorwin(table->othpwin);

	entry = table->firstvisible;

	while ((entry != NULL) && (entry != table->lastvisible->next_entry)) {
		printothpentry(table, entry, target_row, 0, NULL);
		target_row++;
		entry = entry->next_entry;
	}

	update_panels();
	doupdate();
}

void destroyothptable(struct othptable *table)
{
	struct othptabent *ctemp;
	struct othptabent *ctemp_next;

	if (table->head != NULL) {
		ctemp = table->head;
		ctemp_next = table->head->next_entry;

		while (ctemp != NULL) {
			free(ctemp);
			ctemp = ctemp_next;

			if (ctemp_next != NULL)
				ctemp_next = ctemp_next->next_entry;
		}
	}
}