diff --git a/iptstate.cc b/iptstate.cc index 78e7e16..c7d3971 100644 --- a/iptstate.cc +++ b/iptstate.cc @@ -2179,7 +2179,6 @@ int main(int argc, char *argv[]) // Command Line Arguments while ((tmpint = getopt_long(argc, argv, "Cd:D:hlmcoLfpR:r1b:s:S:tv", long_options, &option_index)) != EOF) { - printf("loop: %d\n", tmpint); switch (tmpint) { case 0: /* Apparently this test is needed?! Seems lame! */ diff --git a/iptstate.cc.no_debug b/iptstate.cc.no_debug new file mode 100644 index 0000000..78e7e16 --- /dev/null +++ b/iptstate.cc.no_debug @@ -0,0 +1,2816 @@ +/* + * vim:textwidth=80:tabstop=2:shiftwidth=2:expandtab:ai + * + * iptstate.cc + * IPTables State + * + * ----------------------------------- + * + * Copyright (C) 2002 - present Phil Dibowitz + * + * This software is provided 'as-is', without any express or + * implied warranty. In no event will the authors be held + * liable for any damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it + * and redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * ----------------------------------- + * + * The idea of statetop comes from IP Filter by Darren Reed. + * + * This package's main purpose is to provide a state-top type + * interface for IP Tables. I've added in the "single run" + * option since there's no nice way to do that either. + * + * NOTE: If you are planning on packaging and/or submitting my software for/to + * a Linux distribution, I would appreciate a heads up. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// There are no C++-ified versions of these. +#include +#include +extern "C" { + #include +}; +#include +#include +#include +using namespace std; + +#define VERSION "2.2.6" +/* + * MAXCONS is set to 16k, the default number of states in iptables. Generally + * speaking the ncurses pad is this many lines long, but since ncurses + * uses a short for their dimensions, a pad can never be longer than 32767. + * Thus we define both of these values and NLINES as the lesser of the two. + */ +#define MAXCONS 16384 +#define MAXLINES 32767 +#if MAXCONS < MAXLINES + #define NLINES MAXCONS +#else + #define NLINES MAXLINES +#endif +#define MAXFIELDS 20 +// This is the default format string if we don't dynamically determine it +#define DEFAULT_FORMAT "%-21s %-21s %-7s %-12s %-9s\n" +// The following MUST be the same as the above +#define DEFAULT_SRC 21 +#define DEFAULT_DST 21 +#define DEFAULT_PROTO 7 +#define DEFAULT_STATE 12 +#define DEFAULT_TTL 9 +// This is the format string for the "totals" line, always. +#define TOTALS_FORMAT \ + "Total States: %i -- TCP: %i UDP: %i ICMP: %i Other: %i (Filtered: %i)\n" +// Options for truncating from the front or the back +#define TRUNC_FRONT 0 +#define TRUNC_END 1 +// maxlength for string we pass to inet_ntop() +#define NAMELEN 100 +// Sorting options +#define SORT_SRC 0 +#define SORT_SRC_PT 1 +#define SORT_DST 2 +#define SORT_DST_PT 3 +#define SORT_PROTO 4 +#define SORT_STATE 5 +#define SORT_TTL 6 +#define SORT_BYTES 7 +#define SORT_PACKETS 8 +#define SORT_MAX 8 + +/* + * GLOBAL CONSTANTS + */ + +/* + * GLOBAL VARS + */ +int sort_factor = 1; +bool need_resize = false; + +/* shameless stolen from libnetfilter_conntrack_tcp.c */ +static const char *states[] = { + "NONE", + "SYN_SENT", + "SYN_RECV", + "ESTABLISHED", + "FIN_WAIT", + "CLOSE_WAIT", + "LAST_ACK", + "TIME_WAIT", + "CLOSE", + "LISTEN" +}; + + +/* + * STRUCTS + */ +// One state-table entry +struct tentry_t { + string proto, state, ttl, sname, dname, spname, dpname; + in6_addr src, dst; + uint8_t family; + unsigned long srcpt, dstpt, bytes, packets, s; +}; +// x/y of the terminal window +struct screensize_t { + unsigned int x, y; +}; +// Struct 'o flags +struct flags_t { + bool single, totals, lookup, skiplb, staticsize, skipdns, tag_truncate, + filter_src, filter_dst, filter_srcpt, filter_dstpt, noscroll, nocolor, + counters; +}; +// Struct 'o counters +struct counters_t { + unsigned int total, tcp, udp, icmp, other, skipped; +}; +// Various filters to be applied pending the right flags in flags_t +struct filters_t { + in6_addr src, dst; + uint8_t srcfam, dstfam; + unsigned long srcpt, dstpt; +}; +// The max-length of fields in the stable table +struct max_t { + unsigned int src, dst, proto, state, ttl; + unsigned long bytes, packets; +}; +struct hook_data { + vector *stable; + flags_t *flags; + max_t *max; + counters_t *counts; + const filters_t *filters; +}; + + +/* + * GENERAL HELPER FUNCTIONS + */ + +/* + * split a string into two strings based on the first occurance + * of any character + */ +void split(char s, string line, string &p1, string &p2) +{ + int pos = line.find(s); + p1 = line.substr(0, pos); + p2 = line.substr(pos+1, line.size()-pos); +} + +/* + * split a string into an array of strings based on + * any character + */ +void splita(char s, string line, vector &result) +{ + int pos, size; + int i=0; + string temp, temp1; + temp = line; + while ((temp.find(s) != string::npos) && (i < MAXFIELDS-1)) { + pos = temp.find(s); + result[i] = temp.substr(0, pos); + size = temp.size(); + temp = temp.substr(pos+1, size-pos-1); + if (result[i] != "") { + i++; + } + } + result[i] = temp; +} + +/* + * This determines the length of an integer (i.e. number of digits) + */ +unsigned int digits(unsigned long x) +{ + return (unsigned int) floor(log10((double)x))+1; +} + +/* + * Check to ensure an IP is valid + */ +bool check_ip(const char *arg, in6_addr *addr, uint8_t *family) +{ + int ret; + ret = inet_pton(AF_INET6, arg, addr); + if (ret) { + *family = AF_INET6; + return true; + } + ret = inet_pton(AF_INET, arg, addr); + if (ret) { + *family = AF_INET; + return true; + } + return false; +} + +/* + * The help + */ +void version() +{ + cout << "IPTables State Top Version " << VERSION << endl << endl; +} + +void help() +{ + cout << "IPTables State Top Version " << VERSION << endl; + cout << "Usage: iptstate []\n\n"; + cout << " -c, --no-color\n"; + cout << "\tToggle color-code by protocol\n\n"; + cout << " -C, --counters\n"; + cout << "\tToggle display of bytes/packets counters\n\n"; + cout << " -d, --dst-filter \n"; + cout << "\tOnly show states with a destination of \n"; + cout << "\tNote, that this must be an IP, hostname matching is" + << " not yet supported.\n\n"; + cout << " -D --dstpt-filter \n"; + cout << "\tOnly show states with a destination port of \n\n"; + cout << " -h, --help\n"; + cout << "\tThis help message\n\n"; + cout << " -l, --lookup\n"; + cout << "\tShow hostnames instead of IP addresses. Enabling this will also" + << " enable\n\t-L to prevent an ever-growing number of DNS requests.\n\n"; + cout << " -m, --mark-truncated\n"; + cout << "\tMark truncated hostnames with a '+'\n\n"; + cout << " -o, --no-dynamic\n"; + cout << "\tToggle dynamic formatting\n\n"; + cout << " -L, --no-dns\n"; + cout << "\tSkip outgoing DNS lookup states\n\n"; + cout << " -f, --no-loopback\n"; + cout << "\tFilter states on loopback\n\n"; + cout << " -p, --no-scroll\n"; + cout << "\tNo scrolling (don't use a \"pad\")\n\n"; + cout << " -r, --reverse\n"; + cout << "\tReverse sort order\n\n"; + cout << " -R, --rate \n"; + cout << "\tRefresh rate, followed by rate in seconds\n"; + cout << "\tNote: For statetop, not applicable for -s\n\n"; + cout << " -1, --single\n"; + cout << "\tSingle run (no curses)\n\n"; + cout << " -b, --sort \n"; + cout << "\tThis determines what column to sort by. Options:\n"; + cout << "\t d: Destination IP (or Name)\n"; + cout << "\t p: Protocol\n"; + cout << "\t s: State\n"; + cout << "\t t: TTL\n"; + cout << "\t b: Bytes\n"; + cout << "\t P: Packets\n"; + cout << "\tTo sort by Source IP (or Name), don't use -b.\n"; + cout << "\tNote that bytes/packets are only available when" + << " supported in the kernel,\n"; + cout << "\tand enabled with -C\n\n"; + cout << " -s, --src-filter \n"; + cout << "\tOnly show states with a source of \n"; + cout << "\tNote, that this must be an IP, hostname matching is" + << " not yet supported.\n\n"; + cout << " -S, --srcpt-filter \n"; + cout << "\tOnly show states with a source port of \n\n"; + cout << " -t, --totals\n"; + cout << "\tToggle display of totals\n\n"; + cout << "See man iptstate(8) or the interactive help for more" + << " information.\n"; + exit(0); +} + +/* + * Resolve hostnames + */ +void resolve_host(const uint8_t &family, const in6_addr &ip, string &name) +{ + struct hostent *hostinfo = NULL; + + if ((hostinfo = gethostbyaddr((char *)&ip, sizeof(ip), family)) != NULL) { + name = hostinfo->h_name; + } else { + char str[NAMELEN]; + name = inet_ntop(family, (void *)&ip, str, NAMELEN-1) ; + } +} + +void resolve_port(const unsigned int &port, string &name, const string &proto) +{ + struct servent *portinfo = NULL; + + if ((portinfo = getservbyport(htons(port), proto.c_str())) != NULL) { + name = portinfo->s_name; + } else { + ostringstream buf; + buf.str(""); + buf << port; + name = buf.str(); + } +} + +/* + * If lookup mode is on, we lookup the names and put them in the structure. + * + * If lookup mode is not on, we generate strings of the addresses and put + * those in the structure. + * + * Finally, we update the max_t structure. + * + * NOTE: We stringify addresses largely because in the IPv6 case we need + * to treat them like truncate-able strings. + */ +void stringify_entry(tentry_t *entry, max_t &max, const flags_t &flags) +{ + unsigned int size = 0; + ostringstream buffer; + char tmp[NAMELEN]; + + bool have_port = entry->proto == "tcp" || entry->proto == "udp"; + if (!have_port) { + entry->spname = entry->dpname = ""; + } + + if (flags.lookup) { + resolve_host(entry->family, entry->src, entry->sname); + resolve_host(entry->family, entry->dst, entry->dname); + if (have_port) { + resolve_port(entry->srcpt, entry->spname, entry->proto); + resolve_port(entry->dstpt, entry->dpname, entry->proto); + } + } else { + buffer << inet_ntop(entry->family, (void*)&(entry->src), tmp, NAMELEN-1); + entry->sname = buffer.str(); + buffer.str(""); + buffer << inet_ntop(entry->family, (void*)&(entry->dst), tmp, NAMELEN-1); + entry->dname = buffer.str(); + buffer.str(""); + if (have_port) { + buffer << entry->srcpt; + entry->spname = buffer.str(); + buffer.str(""); + buffer << entry->dstpt; + entry->dpname = buffer.str(); + buffer.str(""); + } + } + + size = entry->sname.size() + entry->spname.size() + 1; + if (size > max.src) + max.src = size; + + size = entry->dname.size() + entry->dpname.size() + 1; + if (size > max.dst) + max.dst = size; +} + + +/* + * SORT FUNCTIONS + */ +bool src_sort(tentry_t *one, tentry_t *two) +{ + /* + * memcmp() will properly sort v4 or v6 addresses, but not cross-family + * (presumably because of garbage in the top 96 bytes when you store + * a v4 address in a in6_addr), so we sort by family and then memcmp() + * within the same family. + */ + if (one->family == two->family) { + return memcmp(one->src.s6_addr, two->src.s6_addr, 16) * sort_factor < 0; + } else if (one->family == AF_INET) { + return sort_factor > 0; + } else { + return sort_factor < 0; + } +} + +bool srcname_sort(tentry_t *one, tentry_t *two) +{ + return one->sname.compare(two->sname) * sort_factor < 0; +} + +bool dst_sort(tentry_t *one, tentry_t *two) +{ + // See src_sort() for details + if (one->family == two->family) { + return memcmp(one->dst.s6_addr, two->dst.s6_addr, 16) * sort_factor < 0; + } else if (one->family == AF_INET) { + return sort_factor > 0; + } else { + return sort_factor < 0; + } +} + +bool dstname_sort(tentry_t *one, tentry_t *two) +{ + return one->dname.compare(two->dname) * sort_factor < 0; +} + +/* + * int comparison that takes care of sort_factor + * used for ports, bytes, etc... + */ +bool cmpint(int one, int two) +{ + return (sort_factor > 0) ? one < two : one > two; +} + +bool srcpt_sort(tentry_t *one, tentry_t *two) +{ + return cmpint(one->srcpt, two->srcpt); +} + +bool dstpt_sort(tentry_t *one, tentry_t *two) +{ + return cmpint(one->dstpt, two->dstpt); +} + +bool proto_sort(tentry_t *one, tentry_t *two) +{ + return one->proto.compare(two->proto) * sort_factor < 0; +} + +bool state_sort(tentry_t *one, tentry_t *two) +{ + return one->state.compare(two->state) * sort_factor < 0; +} + +bool ttl_sort(tentry_t *one, tentry_t *two) +{ + return one->ttl.compare(two->ttl) * sort_factor < 0; +} + +bool bytes_sort(tentry_t *one, tentry_t *two) +{ + return cmpint(one->bytes, two->bytes); +} + +bool packets_sort(tentry_t *one, tentry_t *two) +{ + return cmpint(one->packets, two->packets); +} + +/* + * CURSES HELPER FUNCTIONS + */ + +/* + * Finish-up for curses environment + */ +void end_curses() +{ + curs_set(1); + nocbreak(); + endwin(); + cout << endl; +} + +/* + * SIGWINCH signal handler. + */ +void winch_handler(int sig) +{ + sigset_t mask_set; + sigset_t old_set; + // Reset signal handler + signal(28, winch_handler); + // ignore this signal for a bit + sigfillset(&mask_set); + sigprocmask(SIG_SETMASK, &mask_set, &old_set); + + need_resize = true; +} + +/* + * SIGKILL signal handler + */ +void kill_handler(int sig) +{ + end_curses(); + printf("Caught signal %d, cleaning up.\n", sig); + exit(0); +} + +/* + * Start-up for curses environment + * + * NOTE: That by default we create a pad. A pad is a special type of window that + * can be bigger than the screen. See the comments in interactive_help() + * below for how to use it and how it works. + * + * However, pad's lack the double-buffering and other features of standard + * ncurses windows and thus can appear slower. Thus we allow the user to + * downgrade to standard windows if they choose. See the comments + * switch_scroll() for more details. + * + */ +static WINDOW* start_curses(flags_t &flags) +{ + int y, x; + initscr(); + cbreak(); + noecho(); + halfdelay(1); + + /* + * If we're starting curses, we care about SIGWNCH, SIGINT, and SIGTERM + * so this seems like as good a place as any to setup our signal + * handler. + */ + // Resize + signal(28, winch_handler); + // Shutdown + signal(2, kill_handler); + signal(15, kill_handler); + + if (has_colors()) { + start_color(); + // for tcp + init_pair(1, COLOR_GREEN, COLOR_BLACK); + // for udp + init_pair(2, COLOR_YELLOW, COLOR_BLACK); + // for icmp + init_pair(3, COLOR_RED, COLOR_BLACK); + // for prompts + init_pair(4, COLOR_BLACK, COLOR_RED); + // for the currently selected row + init_pair(5, COLOR_BLACK, COLOR_GREEN); + init_pair(6, COLOR_BLACK, COLOR_YELLOW); + init_pair(7, COLOR_BLACK, COLOR_RED); + } else { + flags.nocolor = true; + } + + if (!flags.noscroll) { + getmaxyx(stdscr, y, x); + return newpad(NLINES, x); + } + return stdscr; +} + +/* + * Figure out the best way to get the screensize_t, and then do it + */ +screensize_t get_size(const bool &single) +{ + int maxx = 0, maxy = 0; + if (!single) { + getmaxyx(stdscr, maxy, maxx); + } else { + maxx = 72; + if (getenv("COLS")) + maxx=atoi(getenv("COLS")); + } + + screensize_t a; + a.x = maxx; + a.y = maxy; + + return a; +} + +/* + * Error function for screen being too small. + */ +void term_too_small() +{ + end_curses(); + cout << "I'm sorry, your terminal must be atleast 72 columns" + << "wide to run iptstate\n"; + exit(3); +} + +/* + * This is one of those "well, I should impliment it to be complete, but + * I doubt it'll get used very often features." It was a nice-thing-to-do + * to impliment the ability for iptstate to use stdscr instead of a pad + * as this provides the doulbe-buffering and other features that pads + * do not. This is probably useful to a small subset of users. It's pretty + * unlikely people will want to interactively want to change this during + * runtime, but since I implimented noscroll, it's only proper to impliment + * interactive toggling. + * + * TECH NOTE: + * This is just a note for myself so I remember why this is the way it is. + * + * The syntax WINDOW *&mainwin is right, thought it's doing what you'd + * expect WINDOW &*mainwin to do... except that's invalid. So it's just a + * &foo pass on a WINDOW*. + */ +void switch_scroll(flags_t &flags, WINDOW *&mainwin) +{ + int x, y; + if (flags.noscroll) { + getmaxyx(stdscr, y, x); + // remove stuff from the bottom window + erase(); + // build pad + wmove(mainwin, 0, 0); + mainwin = newpad(NLINES, x); + wmove(mainwin, 0, 0); + keypad(mainwin,1); + halfdelay(1); + } else { + // delete pad + delwin(mainwin); + mainwin = stdscr; + keypad(mainwin,1); + halfdelay(1); + } + + flags.noscroll = !flags.noscroll; +} + +/* + * Prompt the user for something, and get an answer. + */ +void get_input(WINDOW *win, string &input, const string &prompt, + const flags_t &flags) +{ + + /* + * This function is here so that we can prompt and get an answer + * and the user can get an echo of what they're inputting. This is + * already a non-straight-forward thing to do in cbreak() mode, but + * it turns out that using pads makes it even more difficult. + * + * It's worth noting that I tried doin a simple waddch() and then + * prefresh as one would expect, but it didn't echo the chars. + * Because we're using pads I have to do a waddchar() and then + * a prefresh(). + * + * Note, that the documentation says that if we're using waddchar() + * we shouldn't need any refresh, but it doesn't echo without it. + * This is probably because waddch() calls wrefresh() instead of + * prefresh(). + */ + + input = ""; + int x, y; + getmaxyx(stdscr, y, x); + WINDOW *cmd = subpad(win, 1, x, 0, 0); + if (!flags.nocolor) + wattron(cmd, COLOR_PAIR(4)); + keypad(cmd, true); + wprintw(cmd, prompt.c_str()); + wclrtoeol(cmd); + prefresh(cmd, 0, 0, 0, 0, 0, x); + + + int ch; + int charcount = 0; + echo(); + nodelay(cmd,0); + + while (1) { + ch = wgetch(cmd); + switch (ch) { + case '\n': + // 7 is ^G + case 7: + if (ch == 7) + input = ""; + if (!flags.nocolor) + wattroff(cmd, COLOR_PAIR(4)); + delwin(cmd); + noecho(); + wmove(win, 0, 0); + return; + break; + // 8 is shift-backspace - just incase + case KEY_BACKSPACE: + case 8: + if (charcount > 0) { + input = input.substr(0, input.size()-1); + wechochar(cmd, '\b'); + wechochar(cmd, ' '); + wechochar(cmd, '\b'); + charcount--; + } + break; + case ERR: + continue; + break; + default: + input += ch; + charcount++; + wechochar(cmd, ch); + } + prefresh(cmd, 0, 0, 0, 0, 0, x); + } +} + +/* + * Create a window with noticable colors (if colors are enabled) + * and print a warning. Means curses_warning. + */ +void c_warn(WINDOW *win, const string &warning, const flags_t &flags) +{ + + /* + * This function is here so that we can warn a user in curses, + * usually about bad input. + */ + + int x, y; + getmaxyx(stdscr, y, x); + WINDOW *warn = subpad(win, 1, x, 0, 0); + if (!flags.nocolor) + wattron(warn, COLOR_PAIR(4)); + wprintw(warn, warning.c_str()); + wprintw(warn, " Press any key to continue..."); + wclrtoeol(warn); + prefresh(warn, 0, 0, 0, 0, 0, x); + while ((y = getch())) { + if (y != ERR) { + break; + } + prefresh(warn, 0, 0, 0, 0, 0, x); + } + if (!flags.nocolor) + wattroff(warn, COLOR_PAIR(4)); + delwin(warn); + noecho(); + wmove(win, 0, 0); + return; +} + +/* + * Initialize the max_t structure with some sane defaults. We'll grow + * them later as needed. + */ +void initialize_maxes(max_t &max, flags_t &flags) +{ + /* + * For NO lookup: + * src/dst IP can be no bigger than 21 chars: + * IP (max of 15) + colon (1) + port (max of 5) = 21 + * + * For lookup: + * if it's a name, we start with the width of the header, and we can + * grow from there as needed. + */ + if (flags.lookup) { + max.src = 6; + max.dst = 11; + } else { + max.src = max.dst = 21; + } + /* + * The proto starts at 3, since tcp/udp are the most common, but will + * grow if we see bigger proto strings such as "ICMP". + */ + max.proto = 3; + /* + * "ESTABLISHED" is generally the longest state, we almost always have + * several, so we'll start with this. It also looks really bad if state + * is changing size a lot, so we start with a common minumum. + */ + max.state = 11; + // TTL we statically make 7: xxx:xx:xx + max.ttl = 9; + + // Start with something sane + max.bytes = 2; + max.packets = 2; +} + +/* + * The actual work of handling a resize. + */ +void handle_resize(WINDOW *&win, const flags_t &flags, screensize_t &ssize) +{ + if (flags.noscroll) { + endwin(); + refresh(); + return; + } + + /* + * OK, the above case without pads is easy. But pads is tricker. + * In order to properly handle SIGWINCH we need to: + * + * - Tear down the pad (delwin) + * - Reset the terminal settings to non-visual mode (endwin) + * - Return to visual mode (refresh) + * - Get the new size (getmaxyx) + * - Rebuild the pad + * + * Note that we don't get the new size without the endwin/refresh + * and thus the new pad doesn't get built right, and everything wraps. + * + * This order must be preserved. + */ + + /* + * Tear down... + */ + delwin(win); + endwin(); + /* + * Start up... + */ + refresh(); + getmaxyx(stdscr, ssize.y, ssize.x); + win = newpad(NLINES, ssize.x); + keypad(win, true); + wmove(win, 0, 0); + + return; +} + +/* + * Take in a 'curr' value, and delete a given conntrack + */ +void delete_state(WINDOW *&win, const tentry_t *entry, const flags_t &flags) +{ + struct nfct_handle *cth; + struct nf_conntrack *ct; + cth = nfct_open(CONNTRACK, 0); + ct = nfct_new(); + int ret; + string response; + char str[NAMELEN]; + string src, dst; + src = inet_ntop(entry->family, (void *)&(entry->src), str, NAMELEN-1); + dst = inet_ntop(entry->family, (void *)&(entry->dst), str, NAMELEN-1); + + ostringstream msg; + msg.str(""); + msg << "Deleting state: "; + if (entry->proto == "tcp" || entry->proto == "udp") { + msg << src << ":" << entry->srcpt << " -> " << dst << ":" << entry->dstpt; + } else { + msg << src << " -> " << dst; + } + msg << " -- Are you sure? (y/n)"; + get_input(win, response, msg.str(), flags); + + if (response != "y" && response != "Y" && response != "yes" && + response != "YES" && response != "Yes") { + c_warn(win, "NOT deleting state.", flags); + return; + } + + nfct_set_attr_u8(ct, ATTR_ORIG_L3PROTO, entry->family); + + if (entry->family == AF_INET) { + nfct_set_attr(ct, ATTR_ORIG_IPV4_SRC, (void *)&(entry->src.s6_addr)); + nfct_set_attr(ct, ATTR_ORIG_IPV4_DST, (void *)&(entry->dst.s6_addr)); + } else if (entry->family == AF_INET6) { + nfct_set_attr(ct, ATTR_ORIG_IPV6_SRC, (void *)&(entry->src.s6_addr)); + nfct_set_attr(ct, ATTR_ORIG_IPV6_DST, (void *)&(entry->dst.s6_addr)); + } + + // undo our space optimization so the kernel can find the state. + protoent *pn; + if (entry->proto == "icmp6") + pn = getprotobyname("ipv6-icmp"); + else + pn = getprotobyname(entry->proto.c_str()); + + nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, pn->p_proto); + + if (entry->proto == "tcp" || entry->proto == "udp") { + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, htons(entry->srcpt)); + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, htons(entry->dstpt)); + } else if (entry->proto == "icmp" || entry->proto == "icmp6") { + string type, code, id, tmp; + split('/', entry->state, type, tmp); + split(' ', tmp, code, tmp); + split('(', tmp, tmp, id); + split(')', id, id, tmp); + + nfct_set_attr_u8(ct, ATTR_ICMP_TYPE, atoi(type.c_str())); + nfct_set_attr_u8(ct, ATTR_ICMP_CODE, atoi(code.c_str())); + nfct_set_attr_u16(ct, ATTR_ICMP_ID, atoi(id.c_str())); + } + + ret = nfct_query(cth, NFCT_Q_DESTROY, ct); + if (ret < 0) { + string msg = "Failed to delete state: "; + msg += strerror(errno); + c_warn(win, msg.c_str(), flags); + } + +} + + +/* + * CORE FUNCTIONS + */ + +/* + * Callback for conntrack + */ +int conntrack_hook(enum nf_conntrack_msg_type nf_type, struct nf_conntrack *ct, + void *tmp) +{ + + /* + * start by getting our struct back + */ + struct hook_data *data = static_cast(tmp); + + /* + * and pull out the pieces + */ + vector *stable = data->stable; + flags_t *flags = data->flags; + max_t *max = data->max; + counters_t *counts = data->counts; + const filters_t *filters = data->filters; + + // our table entry + tentry_t *entry = new tentry_t; + + // some vars + struct protoent* pe = NULL; + int seconds, minutes, hours; + char ttlc[11]; + ostringstream buffer; + + /* + * Clear the entry + */ + entry->sname = ""; + entry->dname = ""; + entry->srcpt = 0; + entry->dstpt = 0; + entry->proto = ""; + entry->ttl = ""; + entry->state = ""; + + /* + * First, we read stuff into the array that's always the + * same regardless of protocol + */ + + short int pr = nfct_get_attr_u8(ct, ATTR_ORIG_L4PROTO); + pe = getprotobynumber(pr); + if (pe == NULL) { + buffer << pr; + entry->proto = buffer.str(); + buffer.str(""); + } else { + entry->proto = pe->p_name; + /* + * if proto is "ipv6-icmp" we can just say "icmp6" to save space... + * it's more common/standard anyway + */ + if (entry->proto == "ipv6-icmp") + entry->proto = "icmp6"; + } + + // ttl + seconds = nfct_get_attr_u32(ct, ATTR_TIMEOUT); + minutes = seconds/60; + hours = minutes/60; + minutes = minutes%60; + seconds = seconds%60; + // Format it with snprintf and store it in the table + snprintf(ttlc,11, "%3i:%02i:%02i", hours, minutes, seconds); + entry->ttl = ttlc; + + entry->family = nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO); + // Everything has addresses + if (entry->family == AF_INET) { + memcpy(entry->src.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV4_SRC), + sizeof(uint8_t[16])); + memcpy(entry->dst.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV4_DST), + sizeof(uint8_t[16])); + } else if (entry->family == AF_INET6) { + memcpy(entry->src.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV6_SRC), + sizeof(uint8_t[16])); + memcpy(entry->dst.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV6_DST), + sizeof(uint8_t[16])); + } else { + fprintf(stderr, "UNKNOWN FAMILY!\n"); + exit(1); + } + + // Counters (summary, in + out) + entry->bytes = nfct_get_attr_u32(ct, ATTR_ORIG_COUNTER_BYTES) + + nfct_get_attr_u32(ct, ATTR_REPL_COUNTER_BYTES); + entry->packets = nfct_get_attr_u32(ct, ATTR_ORIG_COUNTER_PACKETS) + + nfct_get_attr_u32(ct, ATTR_REPL_COUNTER_PACKETS); + + if (digits(entry->bytes) > max->bytes) { + max->bytes = digits(entry->bytes); + } + if (digits(entry->packets) > max->packets) { + max->packets = digits(entry->packets); + } + + if (entry->proto.size() > max->proto) + max->proto = entry->proto.size(); + + // OK, proto dependent stuff + if (entry->proto == "tcp" || entry->proto == "udp") { + entry->srcpt = htons(nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); + entry->dstpt = htons(nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); + } + + if (entry->proto == "tcp") { + entry->state = states[nfct_get_attr_u8(ct, ATTR_TCP_STATE)]; + counts->tcp++; + } else if (entry->proto == "udp") { + entry->state = ""; + counts->udp++; + } else if (entry->proto == "icmp" || entry->proto == "icmp6") { + buffer.str(""); + buffer << (int)nfct_get_attr_u8(ct, ATTR_ICMP_TYPE) << "/" + << (int)nfct_get_attr_u8(ct, ATTR_ICMP_CODE) << " (" + << nfct_get_attr_u16(ct, ATTR_ICMP_ID) << ")"; + entry->state = buffer.str(); + counts->icmp++; + if (entry->state.size() > max->state) + max->state = entry->state.size(); + } else { + counts->other++; + } + + /* + * FILTERING + */ + + /* + * FIXME: Filtering needs to be pulled into it's own function. + */ + struct in_addr lb; + struct in6_addr lb6; + inet_pton(AF_INET, "127.0.0.1", &lb); + inet_pton(AF_INET6, "::1", &lb6); + size_t entrysize = entry->family == AF_INET + ? sizeof(in_addr) + : sizeof(in6_addr); + if (flags->skiplb && (entry->family == AF_INET + ? !memcmp(&(entry->src), &lb, sizeof(in_addr)) + : !memcmp(&(entry->src), &lb6, sizeof(in6_addr)))) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + if (flags->skipdns && (entry->dstpt == 53)) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + if (flags->filter_src && (memcmp(&(entry->src), &(filters->src), entrysize))) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + if (flags->filter_srcpt && (entry->srcpt != filters->srcpt)) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + if (flags->filter_dst && (memcmp(&(entry->dst), &(filters->dst), entrysize))) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + if (flags->filter_dstpt && (entry->dstpt != filters->dstpt)) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + /* + * RESOLVE + */ + + // Resolve names - if necessary - or generate strings of address, + // and calculate max sizes + stringify_entry(entry, *max, *flags); + + /* + * Add this to the array + */ + stable->push_back(entry); + + return NFCT_CB_CONTINUE; +} + +/* + * This is the core of this program - build a table of states. + * + * For the new libnetfilter_conntrack code, the bulk of build_table was moved + * to the conntrack callback function. + */ +void build_table(flags_t &flags, const filters_t &filters, vector + &stable, counters_t &counts, max_t &max) +{ + /* + * Variables + */ + int res=0; + vector fields(MAXFIELDS); + static struct nfct_handle *cth; + u_int8_t family = AF_UNSPEC; + + /* + * This is the ugly struct for the nfct hook, that holds pointers to + * all of the things the callback will need to fill our table + */ + struct hook_data hook; + hook.stable = &stable; + hook.flags = &flags; + hook.max = &max; + hook.counts = &counts; + hook.filters = &filters; + + /* + * Initialization + */ + // Nuke the tentry_t's we made before deleting the vector of pointers + for ( + vector::iterator it = stable.begin(); + it != stable.end(); + it++ + ) { + delete *it; + } + stable.clear(); + counts.tcp = counts.udp = counts.icmp = counts.other = counts.skipped = 0; + + cth = nfct_open(CONNTRACK, 0); + if (!cth) { + end_curses(); + printf("ERROR: couldn't establish conntrack connection\n"); + exit(2); + } + nfct_callback_register(cth, NFCT_T_ALL, conntrack_hook, (void *)&hook); + res = nfct_query(cth, NFCT_Q_DUMP, &family); + if (res < 0) { + end_curses(); + printf("ERROR: Couldn't retreive conntrack table: %s\n", strerror(errno)); + exit(2); + } + nfct_close(cth); +} + +/* + * This sorts the table based on the current sorting preference + */ +void sort_table(const int &sortby, const bool &lookup, const int &sort_factor, + vector &stable, string &sorting) +{ + switch (sortby) { + case SORT_SRC: + if (lookup) { + std::sort(stable.begin(), stable.end(), srcname_sort); + sorting = "SrcName"; + } else { + std::sort(stable.begin(), stable.end(), src_sort); + sorting = "SrcIP"; + } + break; + + case SORT_SRC_PT: + std::sort(stable.begin(), stable.end(), srcpt_sort); + sorting = "SrcPort"; + break; + + case SORT_DST: + if (lookup) { + std::sort(stable.begin(), stable.end(), dstname_sort); + sorting = "DstName"; + } else { + std::sort(stable.begin(), stable.end(), dst_sort); + sorting = "DstIP"; + } + break; + + case SORT_DST_PT: + std::sort(stable.begin(), stable.end(), dstpt_sort); + sorting = "DstPort"; + break; + + case SORT_PROTO: + std::sort(stable.begin(), stable.end(), proto_sort); + sorting = "Prt"; + break; + + case SORT_STATE: + std::sort(stable.begin(), stable.end(), state_sort); + sorting = "State"; + break; + + case SORT_TTL: + std::sort(stable.begin(), stable.end(), ttl_sort); + sorting = "TTL"; + break; + + case SORT_BYTES: + std::sort(stable.begin(), stable.end(), bytes_sort); + sorting = "Bytes"; + break; + + case SORT_PACKETS: + std::sort(stable.begin(), stable.end(), packets_sort); + sorting = "Packets"; + break; + + default: + //we should never get here + sorting = "??unknown??"; + break; + + } //switch + + if (sort_factor == -1) + sorting = sorting + " reverse"; + +} + +void print_headers(const flags_t &flags, const string &format, + const string &sorting, const filters_t &filters, + const counters_t &counts, const screensize_t &ssize, + int table_size, WINDOW *mainwin) +{ + if (flags.single) { + cout << "IP Tables State Top -- Sort by: " << sorting << endl; + } else { + wmove(mainwin, 0, 0); + wclrtoeol(mainwin); + wmove(mainwin,0, ssize.x/2-15); + wattron(mainwin, A_BOLD); + wprintw(mainwin, "IPTState - IPTables State Top\n"); + + wprintw(mainwin, "Version: "); + wattroff(mainwin, A_BOLD); + wprintw(mainwin, "%-13s", VERSION); + + wattron(mainwin, A_BOLD); + wprintw(mainwin, "Sort: "); + wattroff(mainwin, A_BOLD); + wprintw(mainwin, "%-16s", sorting.c_str()); + + wattron(mainwin, A_BOLD); + wprintw(mainwin, "b"); + wattroff(mainwin, A_BOLD); + wprintw(mainwin, "%-19s", ": change sorting"); + + wattron(mainwin, A_BOLD); + wprintw(mainwin, "h"); + wattroff(mainwin, A_BOLD); + wprintw(mainwin, "%-s\n", ": help"); + } + + /* + * If enabled, print totals + */ + if (flags.totals) { + if (flags.single) + printf(TOTALS_FORMAT, table_size+counts.skipped, counts.tcp, counts.udp, + counts.icmp, counts.other, counts.skipped); + else + wprintw(mainwin, TOTALS_FORMAT, table_size+counts.skipped, counts.tcp, + counts.udp, counts.icmp, counts.other, counts.skipped); + } + + /* + * If any, print filters + */ + char tmp[NAMELEN]; + if (flags.filter_src || flags.filter_dst || flags.filter_srcpt + || flags.filter_dstpt) { + + if (flags.single) { + printf("Filters: "); + } else { + wattron(mainwin, A_BOLD); + wprintw(mainwin, "Filters: "); + wattroff(mainwin, A_BOLD); + } + + bool printed_a_filter = false; + + if (flags.filter_src) { + inet_ntop(filters.srcfam, &filters.src, tmp, NAMELEN-1); + if (flags.single) + printf("src: %s", tmp); + else + wprintw(mainwin, "src: %s", tmp); + printed_a_filter = true; + } + if (flags.filter_srcpt) { + if (printed_a_filter) { + if (flags.single) + printf(", "); + else + waddstr(mainwin, ", "); + } + if (flags.single) + printf("sport: %lu", filters.srcpt); + else + wprintw(mainwin, "sport: %lu", filters.srcpt); + printed_a_filter = true; + } + if (flags.filter_dst) { + if (printed_a_filter) { + if (flags.single) + printf(", "); + else + waddstr(mainwin, ", "); + } + inet_ntop(filters.dstfam, &filters.dst, tmp, NAMELEN-1); + if (flags.single) + printf("dst: %s", tmp); + else + wprintw(mainwin, "dst: %s", tmp); + printed_a_filter = true; + } + if (flags.filter_dstpt) { + if (printed_a_filter) { + if (flags.single) + printf(", "); + else + waddstr(mainwin, ", "); + } + if (flags.single) + printf("dport: %lu", filters.dstpt); + else + wprintw(mainwin, "dport: %lu", filters.dstpt); + printed_a_filter = true; + } + if (flags.single) + printf("\n"); + else + wprintw(mainwin, "\n"); + } + + /* + * Print column headers + */ + if (flags.single) { + if (flags.counters) + printf(format.c_str(), "Source", "Destination", "Prt", "State", "TTL", + "B", "P"); + else + printf(format.c_str(), "Source", "Destination", "Prt", "State", "TTL"); + } else { + wattron(mainwin, A_BOLD); + if (flags.counters) + wprintw(mainwin, format.c_str(), "Source", "Destination", "Prt", + "State", "TTL", "B", "P"); + else + wprintw(mainwin, format.c_str(), "Source", "Destination", "Prt", + "State", "TTL"); + wattroff(mainwin, A_BOLD); + } + +} + + +void truncate(string &string, int length, bool mark, char direction) +{ + int s = (direction == 'f') ? string.size() - length : 0; + + string = string.substr(s, length); + if (mark) { + int m = (direction == 'f') ? 0 : string.size() - 1; + string[m] = '+'; + } +} + +/* + * Based on the format pre-chosen, truncate src/dst as needed, and then + * generate the host:port strings and drop them off in the src/dst string + * objects passed in. + */ +void format_src_dst(tentry_t *table, string &src, string &dst, + const flags_t &flags, const max_t &max) +{ + ostringstream buffer; + bool have_port = table->proto == "tcp" || table->proto == "udp"; + char direction; + unsigned int length; + + // What length would we currently use? + length = table->sname.size(); + if (have_port) + length += table->spname.size() + 1; + + // If it's too long, figure out how room we have and truncate it + if (length > max.src) { + length = max.src; + if (have_port) + length -= 1 + table->spname.size(); + direction = (flags.lookup) ? 'e' : 'f'; + truncate(table->sname, length, flags.tag_truncate, direction); + } + + // ... and repeat + length = table->dname.size(); + if (have_port) + length += table->dpname.size() + 1; + if (length > max.dst) { + length = max.dst; + if (have_port) + length -= 1 + table->dpname.size(); + direction = (flags.lookup) ? 'f' : 'e'; + truncate(table->dname, length, flags.tag_truncate, direction); + } + + buffer << table->sname; + if (have_port) + buffer << ":" << table->spname; + src = buffer.str(); + buffer.str(""); + buffer << table->dname; + if (have_port) + buffer << ":" << table->dpname; + dst = buffer.str(); + buffer.str(""); +} + +/* + * An abstraction of priting a line for both single/curses modes + */ +void printline(tentry_t *table, const flags_t &flags, const string &format, + const max_t &max, WINDOW *mainwin, const bool curr) +{ + ostringstream buffer; + buffer.str(""); + string src, dst, b, p; + + // Generate strings for src/dest, truncating and marking as necessary + format_src_dst(table, src, dst, flags, max); + + if (flags.counters) { + buffer << table->bytes; + b = buffer.str(); + buffer.str(""); + buffer << table->packets; + p = buffer.str(); + buffer.str(""); + } + + if (flags.single) { + if (flags.counters) + printf(format.c_str(), src.c_str(), dst.c_str(), table->proto.c_str(), + table->state.c_str(), table->ttl.c_str(), b.c_str(), p.c_str()); + else + printf(format.c_str(), src.c_str(), dst.c_str(), table->proto.c_str(), + table->state.c_str(), table->ttl.c_str()); + } else { + int color = 0; + if (!flags.nocolor) { + if (table->proto == "tcp") + color = 1; + else if (table->proto == "udp") + color = 2; + else if (table->proto == "icmp" || table->proto == "icmp6") + color = 3; + if (curr) + color += 4; + wattron(mainwin, COLOR_PAIR(color)); + } + if (flags.counters) + wprintw(mainwin, format.c_str(), src.c_str(), dst.c_str(), + table->proto.c_str(), table->state.c_str(), table->ttl.c_str(), + b.c_str(), p.c_str()); + else + wprintw(mainwin, format.c_str(), src.c_str(), dst.c_str(), + table->proto.c_str(), table->state.c_str(), table->ttl.c_str()); + + if (!flags.nocolor && color != 0) + wattroff(mainwin, COLOR_PAIR(color)); + } +} + +/* + * This does all the work of actually printing the table including + * various bits of formatting. It handles both curses and non-curses runs. + */ +void print_table(vector &stable, const flags_t &flags, + const string &format, const string &sorting, + const filters_t &filters, const counters_t &counts, + const screensize_t &ssize, const max_t &max, WINDOW *mainwin, + unsigned int &curr) +{ + /* + * Print headers + */ + print_headers(flags, format, sorting, filters, counts, ssize, stable.size(), + mainwin); + + /* + * Print the state table + */ + unsigned int limit = (stable.size() < NLINES) ? stable.size() : NLINES; + for (unsigned int tmpint=0; tmpint < limit; tmpint++) { + printline(stable[tmpint], flags, format, max, mainwin, (curr == tmpint)); + if (!flags.single && flags.noscroll + && (tmpint >= ssize.y-4 || (flags.totals && tmpint >= ssize.y-5))) + break; + + } + + /* + * We don't want to lave things on the screen we didn't draw + * this time. + */ + if (!flags.single) + wclrtobot(mainwin); + +} + +/* + * Dynamically build a format to fit the most amount of data on the screen + */ +void determine_format(WINDOW *mainwin, max_t &max, screensize_t &ssize, + string &format, flags_t &flags) +{ + + /* + * NOTE: When doing proper dynamic format building, we fill the + * entire screen, so curses puts in a newline for us. However + * with "staticsize" we must add a newline. Also with "single" + * mode we must add it as well since there's no curses there. + * + * Thus DEFAULT_FORMAT (only used for staticsize) has it, and + * at the bottom of this function we add a \n if flags.single + * is set. + */ + if (flags.staticsize) { + format = DEFAULT_FORMAT; + max.src = DEFAULT_SRC; + max.dst = DEFAULT_DST; + max.proto = DEFAULT_PROTO; + max.state = DEFAULT_STATE; + max.ttl = DEFAULT_TTL; + return; + } + + ssize = get_size(flags.single); + + /* The screen must be 85 chars wide to be able to fit in + * counters when we display IP addresses... + * + * in lookup mode, we can truncate names, but in IP mode, + * truncation makes no sense, so we just disable counters if + * we run into this. + */ + if (ssize.x < 85 && flags.counters && !flags.lookup) { + string prompt = "Window too narrow for counters! Disabling."; + c_warn(mainwin, prompt, flags); + flags.counters = false; + } + + /* what's left is the above three, plus 4 spaces + * (one between each of 5 fields) + */ + unsigned int left = ssize.x - max.ttl - max.state - max.proto - 4; + if (flags.counters) + left -= (max.bytes + max.packets + 2); + + /* + * The rest is *prolly* going to be divided between src + * and dst, so we see if that works. If 'left' is odd though + * we give the extra space to src. + */ + unsigned int src, dst; + src = dst = left / 2; + bool left_odd = false; + if ((left % 2) == 1) { + left_odd = true; + src++; + } + if ((max.src + max.dst) < left) { + /* + * This means we can fit without an truncation, but it doesn't + * necessarily mean that we can just give half to src and half + * to dst... so lets figure that out. + */ + + if (max.src < src && max.dst < dst) { + /* + * This case applies if: + * we're even and they both fit in left/2 + * OR + * we're odd and dst fits in left/2 + * and src fits in left/2+1 + * + * Since we've already calculated src/dst that way + * we just combine this check as they both require + * the same outcome. + */ + } else if (left_odd && (src < left / 2) && (dst < left / 2 + 1)) { + /* + * If src can fit in left/2 and dst in left/2+1 + * then we switch them. + */ + src = dst; + dst++; + } else if (max.src > max.dst) { + /* + * If we're here, we can fit them, but we can't fit them + * and still keep the two columns relatively equal. Ah + * well. + * + * Either max gets the bigger chunk and everything else + * to dst... + */ + src = max.src; + dst = left - max.src; + } else { + /* + * ...or the other way around + */ + dst = max.dst; + src = left - max.dst; + } + } else if (max.src < src) { + /* + * If we're here, we do have to truncate, but if one column is + * very small, we should not give it more space than it needs. + */ + src = max.src; + dst = left - max.src; + } else if (max.dst < dst) { + /* + * same as above. + */ + dst = max.dst; + src = left - max.dst; + } + + /* + * If nothing matched, then they're both bigger than left/2, so we'll + * leave the default we set above. + */ + + ostringstream buffer; + buffer << "\%-" << src << "s \%-" << dst << "s \%-" << max.proto << "s \%-" + << max.state << "s \%-" << max.ttl << "s"; + + if (flags.counters) + buffer << " \%-" << max.bytes << "s \%-" << max.packets << "s"; + + if (flags.single) + buffer << "\n"; + + format = buffer.str(); + + max.dst = dst; + max.src = src; +} + +/* + * Interactive help + */ +void interactive_help(const string &sorting, const flags_t &flags, + const filters_t &filters) +{ + + /* + * This is the max we need the pad to be, and thus how + * big we're going to create the pad. + * + * In many cases we'd make the pad very very large and not + * worry about it. However, in this case: + * 1. We know exactly how big we need it to be, and it's + * not going to change interactively. + * 2. We want to draw a "box" around the window and if the + * pad is huge then the box will get drawn around that. + * + * So... we have 32 lines of help, plus a top and bottom border, + * thus maxrows is 34. + * + * Our help text is not wider than 80, so we'll se that standard + * width. + * + * If the screen is bigger than this, we deal with it below. + */ + unsigned int maxrows = 41; + unsigned int maxcols = 80; + + /* + * The actual screen size + */ + screensize_t ssize = get_size(flags.single); + + /* + * If the biggest we think we'll need is smaller than the screen, + * then lets grow the pad to the size of the screen so that the + * main window isn't peeking through. + */ + if (maxrows < ssize.y) + maxrows = ssize.y; + if (maxcols < ssize.x) + maxcols = ssize.x; + + /* + * Where we are withing the pad (for printing). We can't just print + * newlines and expect it to work. Cause, well, it doesn't. You have + * to tell it where on the pad to print, specifically. + */ + unsigned int x, y; + x = y = 0; + + /* + * The current position on the pad we're showing (top left) + */ + unsigned int px, py; + px = py = 0; + + /* + * As noted above, we create the biggest pad we might need + */ + static WINDOW *helpwin; + helpwin = newpad(maxrows, maxcols); + + /* + * Create a box, and then add one to "x" and "y" so we don't write + * on the line, + */ + box(helpwin, ACS_VLINE, ACS_HLINE); + x++; + y++; + + + /* + * we want arrow keys to work + */ + keypad(helpwin, true); + + // Prolly not needed + wmove(helpwin, 0, 0); + + // Print opener + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, "IPTState "); + waddstr(helpwin, VERSION); + wattroff(helpwin, A_BOLD); + // this is \n + y++; + + // We don't want anything except the title up against the + // border + x++; + + string nav = "Up/j, Down/k, Left/h, Right/l, PageUp/^u, PageDown/^d, "; + nav += " Home, or End"; + // Print instructions first + mvwaddstr(helpwin, y++, x, "Navigation:"); + mvwaddstr(helpwin, y++, x, nav.c_str()); + mvwaddstr(helpwin, y++, x, " Press any other key to continue..."); + y++; + + // Print settings + mvwaddstr(helpwin, y++, x, "Current settings:"); + + mvwaddstr(helpwin, y++, x, " Sorting by: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin, sorting.c_str()); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Dynamic formatting: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(!flags.staticsize) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Skip loopback states: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.skiplb) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Resolve hostnames: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.lookup) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Mark truncated hostnames: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.tag_truncate) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Colors: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(!flags.nocolor) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Skip outgoing DNS lookup states: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.skipdns) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Enable scroll: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(!flags.noscroll) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Display totals: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.totals) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Display counters: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.counters) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + char tmp[NAMELEN]; + if (flags.filter_src) { + inet_ntop(filters.srcfam, &filters.src, tmp, + filters.srcfam == AF_INET ? sizeof(in_addr) : sizeof(in6_addr)); + mvwaddstr(helpwin, y++, x, " Source filter: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin, tmp); + wattroff(helpwin, A_BOLD); + } + if (flags.filter_dst) { + inet_ntop(filters.dstfam, &filters.dst, tmp, + filters.dstfam == AF_INET ? sizeof(in_addr) : sizeof(in6_addr)); + mvwaddstr(helpwin, y++, x, " Destination filter: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin, tmp); + wattroff(helpwin, A_BOLD); + } + if (flags.filter_srcpt) { + mvwaddstr(helpwin, y++, x, " Source port filter: "); + wattron(helpwin, A_BOLD); + wprintw(helpwin, "%lu", filters.srcpt); + wattroff(helpwin, A_BOLD); + } + if (flags.filter_dstpt) { + mvwaddstr(helpwin, y++, x, " Destination port filter: "); + wattron(helpwin, A_BOLD); + wprintw(helpwin, "%lu", filters.dstpt); + wattroff(helpwin, A_BOLD); + } + + y++; + + // Print commands + mvwaddstr(helpwin, y++, x, "Interactive commands:"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " c"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tUse colors"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " C"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle display of bytes/packets counters"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " b"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tSort by next column"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " B"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tSort by previous column"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " d"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tChange destination filter"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " D"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tChange destination port filter"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " f"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle display of loopback states"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " h"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tDisplay this help message"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " l"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle DNS lookups"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " L"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle display of outgoing DNS states"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " m"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle marking truncated hostnames with a '+'"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " o"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle dynamic or old formatting"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " p"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle scrolling"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " q"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tQuit"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " r"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle reverse sorting"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " R"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tChange the refresh rate"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " s"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tChange source filter"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " S"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tChange source port filter"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " t"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle display of totals"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " x"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tDelete the currently highlighted state from netfilter"); + + y++; + + wmove(helpwin, 0, 0); + + /* + * refresh from wherever we are the pad + * and the top of the window to the bottom of the window. + */ + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + // kill line buffering + cbreak(); + // nodelay with a 0 here causes getch() to block until key is pressed. + nodelay( helpwin, 0 ); + int c; + while ((c = wgetch(helpwin))) { + switch (c) { + case ERR: + continue; + break; + case KEY_DOWN: + case 'j': + /* + * py is the top of the window, + * ssize.y is the height of the window, + * so py+ssize.y is the bottom of the window. + * + * Since y is the bottom of the text we've + * written, if + * py+ssize.y == y + * then the bottom of the screen as at the + * bottom of the text, no more scrolling. + */ + if (py + ssize.y < y) + py++; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_UP: + case 'k': + if (py > 0) + py--; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_RIGHT: + case 'l': + /* + * px is the left of the window, + * ssize.x is the width of the window, + * so px+ssize.x os the right side of the window. + * + * So if px+ssize.x == 80 (more than the width + * of our text), no more scrolling. + */ + if (px + ssize.x < 80) + px++; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_LEFT: + case 'h': + if (px > 0) + px--; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_HOME: + case KEY_SHOME: + case KEY_FIND: + px = py = 0; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_END: + py = y-ssize.y; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case 4: + case KEY_NPAGE: + case KEY_SNEXT: + if (flags.noscroll) + break; + /* + * If the screen is bigger than the text, + * ignore + */ + if (y < ssize.y) + break; + /* + * Otherwise, if the bottom of the screen + * (current position + screen size + * == py + ssize.y) + * were to go down one screen (thus: + * py + ssize.y*2), + * and that is bigger than the whole text, just + * go to the bottom. + * + * Otherwise, go down a screen size. + */ + if ((py + (ssize.y * 2)) > y) { + py = y-ssize.y; + } else { + py += ssize.y; + } + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case 21: + case KEY_PPAGE: + case KEY_SPREVIOUS: + if (flags.noscroll) + break; + /* + * If we're at the top, ignore this. + */ + if (py == 0) + break; + /* + * Otherwise if we're less than a page from the + * top, go to the top, else, go up a page. + */ + if (py < ssize.y) + py = 0; + else + py -= ssize.y; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + + case 'q': + default: + goto out; + break; + } + if (need_resize) { + goto out; + } + } +out: + // once a key is pressed, tear down the help window. + delwin(helpwin); + refresh(); + halfdelay(1); +} + + +/* + * MAIN + */ +int main(int argc, char *argv[]) +{ + + // Variables + string line, src, dst, srcpt, dstpt, proto, code, type, state, ttl, mins, + secs, hrs, sorting, tmpstring, format, prompt; + ostringstream ostream; + vector stable; + int tmpint = 0, sortby = 0, rate = 1, hdrs = 0; + unsigned int py = 0, px = 0, curr_state = 0; + timeval selecttimeout; + fd_set readfd; + flags_t flags; + counters_t counts; + screensize_t ssize; + filters_t filters; + max_t max; + + /* + * Initialize + */ + flags.single = flags.totals = flags.lookup = flags.skiplb = flags.staticsize + = flags.skipdns = flags.tag_truncate = flags.filter_src + = flags.filter_dst = flags.filter_srcpt = flags.filter_dstpt + = flags.noscroll = flags.nocolor = flags.counters = false; + ssize.x = ssize.y = 0; + counts.tcp = counts.udp = counts.icmp = counts.other = counts.skipped = 0; + filters.src = filters.dst = in6addr_any; + filters.srcpt = filters.dstpt = 0; + max.src = max.dst = max.proto = max.state = max.ttl = 0; + px = py = 0; + + static struct option long_options[] = { + {"counters", no_argument , 0, 'C'}, + {"dst-filter", required_argument, 0, 'd'}, + {"dstpt-filter", required_argument, 0, 'D'}, + {"help", no_argument, 0, 'h'}, + {"lookup", no_argument, 0, 'l'}, + {"mark-truncated", no_argument, 0, 'm'}, + {"no-color", no_argument, 0, 'c'}, + {"no-dynamic", no_argument, 0, 'o'}, + {"no-dns", no_argument, 0, 'L'}, + {"no-loopback", no_argument, 0, 'f'}, + {"no-scroll", no_argument, 0, 'p'}, + {"rate", required_argument, 0, 'R'}, + {"reverse", no_argument, 0, 'r'}, + {"single", no_argument, 0, '1'}, + {"sort", required_argument, 0, 'b'}, + {"src-filter", required_argument, 0, 's'}, + {"srcpt-filter", required_argument, 0, 'S'}, + {"totals", no_argument, 0, 't'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0,0} + }; + int option_index = 0; + + // Command Line Arguments + while ((tmpint = getopt_long(argc, argv, "Cd:D:hlmcoLfpR:r1b:s:S:tv", + long_options, &option_index)) != EOF) { + printf("loop: %d\n", tmpint); + switch (tmpint) { + case 0: + /* Apparently this test is needed?! Seems lame! */ + if (long_options[option_index].flag != 0) + break; + + /* + * Long-only options go here, like so: + * + * tmpstring = long_options[option_index].name; + * if (tmpstring == "srcpt-filter") { + * ... + * } else if (...) { + * ... + * } + * + */ + + break; + // --counters + case 'C': + flags.counters = true; + break; + // --dst-filter + case 'd': + if (optarg == NULL) + break; + // See check_ip() note above + if (!check_ip(optarg, &filters.dst, &filters.dstfam)) { + cerr << "Invalid IP address: " << optarg + << endl; + exit(1); + } + flags.filter_dst = true; + break; + // --dstpt-filter + case 'D': + /* + * even though this won't be an IP address + * aton() won't complain about anything that's + * just digits, so it's an easy check. + */ + if (optarg == NULL) + break; + flags.filter_dstpt = true; + filters.dstpt = atoi(optarg); + break; + // --help + case 'h': + help(); + break; + // --lookup + case 'l': + flags.lookup = true; + // and also set skipdns for sanity + flags.skipdns = true; + break; + // --mark-truncated + case 'm': + flags.tag_truncate = true; + break; + // --color + case 'c': + flags.nocolor = false; + break; + // --no-dynamic + case 'o': + flags.staticsize = true; + break; + // --no-dns + case 'L': + flags.skipdns = true; + break; + // --no-loopback + case 'f': + flags.skiplb = true; + break; + // --no-scroll + case 'p': + flags.noscroll = true; + break; + // --reverse + case 'r': + sort_factor = -1; + break; + // --rate + case 'R': + rate = atoi(optarg); + break; + // --sort + case 'b': + if (*optarg == 'd') + sortby = SORT_DST; + else if (*optarg == 'D') { + sortby = SORT_DST_PT; + } else if (*optarg == 'S') + sortby = SORT_SRC_PT; + else if (*optarg == 'p') + sortby = SORT_PROTO; + else if (*optarg == 's') + sortby = SORT_STATE; + else if (*optarg == 't') + sortby = SORT_TTL; + else if (*optarg == 'b' && flags.counters) + sortby = SORT_BYTES; + else if (*optarg == 'P' && flags.counters) + sortby = SORT_PACKETS; + break; + // --single + case '1': + flags.single = true; + break; + // --src-filter + case 's': + if (optarg == NULL) + break; + if (!check_ip(optarg, &filters.src, &filters.srcfam)) { + cerr << "Invalid IP address: " << optarg << endl; + exit(1); + } + flags.filter_src = true; + break; + // --srcpt-filter + case 'S': + if (optarg == NULL) + break; + flags.filter_srcpt = true; + filters.srcpt = atoi(optarg); + break; + // --totals + case 't': + flags.totals = true; + break; + // --version + case 'v': + version(); + exit(0); + break; + // catch-all + default: + // getopts should already have printed a message + exit(1); + break; + } + } + + if (rate < 0 || rate > 60) { + rate = 1; + } + + // Initialize Curses Stuff + static WINDOW *mainwin = NULL; + if (!flags.single) { + mainwin = start_curses(flags); + keypad(mainwin, true); + } + + /* + * We want to keep going until the user stops us + * unless they use single run mode + * in which case, we'll deal with that down below + */ + while (1) { + + /* + * We get the screensize_t up-front so we can die if the + * screen doesn't meet our minimum requirements without making + * the user wait while we gather and process all the data. + * We'll do it again afterwards just in case + */ + + ssize = get_size(flags.single); + + if (ssize.x < 72) { + term_too_small(); + } + + // And our header size + hdrs = 3; + if (flags.totals) { + hdrs++; + } + if (flags.filter_src || flags.filter_dst || flags.filter_srcpt + || flags.filter_dstpt) { + hdrs++; + } + + // clear maxes + initialize_maxes(max, flags); + + // Build our table + build_table(flags, filters, stable, counts, max); + + /* + * Now that we have the new table, make sure our page/cursor + * positions still make sense. + */ + if (curr_state > stable.size() - 1) { + curr_state = stable.size() - 1; + } + + /* + * The bottom of the screen is stable.size()+hdrs+1 + * (the +1 is so we can have a blank line at the end) + * but we want to never have py be more than that - ssize.y + * so we're showing a page full of states. + */ + int bottom = stable.size() + hdrs + 1 - ssize.y; + if (bottom < 0) + bottom = 0; + if (py > (unsigned)bottom) + py = bottom; + + /* + * Originally I strived to do this the "right" way by calling + * nfct_is_set(ct, ATTR_ORIG_COUNGERS) to determine if + * counters were enabled. BUT, if counters are not enabled, + * nfct_get_attr() returns NULL, so this test is just as + * valid. + * + * Conversely checking is_set and then get_attr() inside our + * callback is twice the calls per-state if they are enabled, + * for no additional benefit. + */ + if (flags.counters && stable.size() > 0 && stable[0]->bytes == 0) { + prompt = "Counters requested, but not enabled in the"; + prompt += " kernel!"; + flags.counters = 0; + c_warn(mainwin, prompt, flags); + } + + // Sort our table + sort_table(sortby, flags.lookup, sort_factor, stable, sorting); + + /* + * From here on out 'max' is no longer "the maximum size of + * this field throughout the table", but is instead the actual + * size to print each field. + * + * BTW, we do "get_size" again here incase the window changed + * while we were off parsing and sorting data. + */ + determine_format(mainwin, max, ssize, format, flags); + + /* + * Now we print out the table in whichever format we're + * configured for + */ + print_table(stable, flags, format, sorting, filters, counts, ssize, max, + mainwin, curr_state); + + // Exit if we're only supposed to run once + if (flags.single) + exit(0); + + // Otherwise refresh the curses display + if (flags.noscroll) { + refresh(); + } else { + prefresh(mainwin, py, px, 0, 0, ssize.y-1, ssize.x-1); + } + + //check for key presses for one second + //or whatever the user said + selecttimeout.tv_sec = rate; + selecttimeout.tv_usec = 0; + // I don't care about fractions of seconds. I don't want them. + FD_ZERO(&readfd); + FD_SET(0, &readfd); + select(1,&readfd, NULL, NULL, &selecttimeout); + if (FD_ISSET(0, &readfd)) { + tmpint = wgetch(mainwin); + switch (tmpint) { + // This is ^L + case 12: + handle_resize(mainwin, flags, ssize); + break; + /* + * This is at the top because the rest are in + * order of longopts, and q isn't in longopts + */ + case 'q': + goto out; + break; + /* + * General option toggles + */ + case 'c': + /* + * we only want to pay attention to this + * command if colors are available + */ + if (has_colors()) + flags.nocolor = !flags.nocolor; + break; + case 'C': + flags.counters = !flags.counters; + if (sortby >= SORT_BYTES) + sortby = SORT_BYTES-1; + break; + case 'h': + interactive_help(sorting, flags, filters); + break; + case 'l': + flags.lookup = !flags.lookup; + /* + * If we just turned on lookup, also turn on filtering DNS states. + * They can turn it off if they want, but generally this is the safer + * approach. + */ + if (flags.lookup) { + flags.skipdns = true; + } + break; + case 'm': + flags.tag_truncate = !flags.tag_truncate; + break; + case 'o': + flags.staticsize = !flags.staticsize; + break; + case 'L': + flags.skipdns = !flags.skipdns; + break; + case 'f': + flags.skiplb = !flags.skiplb; + break; + case 'p': + switch_scroll(flags, mainwin); + break; + case 'r': + sort_factor = -sort_factor; + break; + case 'b': + if (sortby < SORT_MAX) { + sortby++; + if (!flags.counters && sortby >= SORT_BYTES) + sortby = 0; + } else { + sortby = 0; + } + break; + case 'B': + if (sortby > 0) { + sortby--; + } else { + if (flags.counters) + sortby=SORT_MAX; + else + sortby=SORT_BYTES-1; + } + break; + case 't': + flags.totals = !flags.totals; + break; + /* + * Update-filters + */ + case 'd': + prompt = "New Destination Filter? (leave blank"; + prompt += " for none): "; + get_input(mainwin, tmpstring, prompt, flags); + if (tmpstring == "") { + flags.filter_dst = false; + filters.dst = in6addr_any; + } else { + if (!check_ip(tmpstring.c_str(), &filters.dst, &filters.dstfam)) { + prompt = "Invalid IP,"; + prompt += " ignoring!"; + c_warn(mainwin, prompt, flags); + } else { + flags.filter_dst = true; + } + } + break; + case 'D': + prompt = "New dstpt filter? (leave blank for"; + prompt += " none): "; + get_input(mainwin, tmpstring, prompt, flags); + if (tmpstring == "") { + flags.filter_dstpt = false; + filters.dstpt = 0; + } else { + flags.filter_dstpt = true; + filters.dstpt = atoi(tmpstring.c_str()); + } + wmove(mainwin, 0, 0); + wclrtoeol(mainwin); + break; + case 'R': + prompt = "Rate: "; + get_input(mainwin, tmpstring, prompt, flags); + if (tmpstring != "") { + int i = atoi(tmpstring.c_str()); + if (i < 1) { + prompt = "Invalid rate,"; + prompt += " ignoring!"; + c_warn(mainwin, prompt, flags); + } else { + rate = i; + } + } + break; + case 's': + prompt = "New src filter? (leave blank for"; + prompt += " none): "; + get_input(mainwin, tmpstring, prompt, flags); + if (tmpstring == "") { + flags.filter_src = false; + filters.src = in6addr_any; + } else { + if (!check_ip(tmpstring.c_str(), &filters.src, &filters.srcfam)) { + prompt = "Invalid IP,"; + prompt += " ignoring!"; + c_warn(mainwin, prompt, flags); + } else { + flags.filter_src = true; + } + } + wmove(mainwin, 0, 0); + wclrtoeol(mainwin); + break; + case 'S': + prompt = "New srcpt filter? (leave blank for"; + prompt += " none): "; + get_input(mainwin, tmpstring, prompt, flags); + if (tmpstring == "") { + flags.filter_srcpt = false; + filters.srcpt = 0; + } else { + flags.filter_srcpt = true; + filters.srcpt = atoi(tmpstring.c_str()); + } + wmove(mainwin, 0, 0); + wclrtoeol(mainwin); + break; + case 'x': + delete_state(mainwin, stable[curr_state], flags); + break; + /* + * Window navigation + */ + case KEY_DOWN: + case 'j': + if (flags.noscroll) + break; + /* + * GENERAL NOTE: + * py is the top of the window, + * ssize.y is the height of the window, + * so py+ssize.y is the bottom of the window. + * + * BOTTOM OF SCROLLING: + * Since stable.size()+hdrs+1 is + * the bottom of the text we've written, if + * py+ssize.y == stable.size()+hdrs+1 + * then the bottom of the screen as at the + * bottom of the text, no more scrolling. + * + * However, we only want to scroll the page + * when the cursor is at the bottom, i.e. + * when curr_state+4 == py+ssize.y + */ + + /* + * If we have room to scroll down AND if cur is + * at the bottom of a page scroll down. + */ + if ((py + ssize.y <= stable.size() + hdrs + 1) + && (curr_state + 4 == py + ssize.y)) + py++; + + /* + * As long as the cursor isn't at the end, + * move it down one. + */ + if (curr_state < stable.size() - 1) + curr_state++; + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_UP: + case 'k': + if (flags.noscroll) + break; + + /* + * This one is tricky. + * + * First thing we need to know is when the cursor + * is at the top of the page. This is simply when + * curr_state+hdrs+1 cursor location), is + * exactly one more than the top of the window + * (py), * i.e. when curr_state+hdrs+1 == py+1. + * + * PAGE SCROLLING: + * IF we're not page-scrolled all the way up + * (i.e. py > 0) + * AND the cursor is at the top of the page + * OR the cursor is at the top of the list, + * AND we're not yet at the top (showing + * the headers). + * THEN we scroll up. + * + * CURSOR SCROLLING: + * Unlike KEY_DOWN, we don't break just because + * the cursor can't move anymore - on the way + * ip we may still have page-scrolling to do. So + * test to make sure we're not at state 0, and + * if so, we scroll up. + */ + + /* + * Basically: + * IF the cursor bumps the top of the screen + * OR we need to scroll up for headers + */ + if ((py > 0 && (curr_state + hdrs + 1) == (py + 1)) + || (curr_state == 0 && py > 0)) + py--; + + if (curr_state > 0) + curr_state--; + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + // 4 is ^d + case 4: + case KEY_NPAGE: + case KEY_SNEXT: + if (flags.noscroll) + break; + /* + * If the screen is bigger than the text, + * and the cursor is at the bottom, ignore. + */ + if (stable.size() + hdrs + 1 < ssize.y && curr_state == stable.size()) + break; + + /* + * Otherwise, if the bottom of the screen + * (current position + screen size + * == py + ssize.y) + * were to go down one screen (thus: + * py + ssize.y*2), + * and that is bigger than the whole pad, just + * go to the bottom. + * + * Otherwise, go down a screen size. + */ + if (py + ssize.y * 2 > stable.size() + hdrs + 1) { + py = stable.size() + hdrs + 1 - ssize.y; + } else { + py += ssize.y; + } + + /* + * For the cursor, we try to move it down one + * screen as well, but if that's too far, + * we bring it up to the largest number it can + * be. + */ + curr_state += ssize.y; + if (curr_state > stable.size()) { + curr_state = stable.size(); + } + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + // 21 is ^u + case 21: + case KEY_PPAGE: + case KEY_SPREVIOUS: + if (flags.noscroll) + break; + /* + * If we're at the top, ignore + */ + if (py == 0 && curr_state == 0) + break; + /* + * Otherwise if we're less than a page from the + * top, go to the top, else go up a page. + */ + if (py < ssize.y) { + py = 0; + } else { + py -= ssize.y; + } + + /* + * We bring the cursor up a page too, unless + * that's too far. + */ + if (curr_state < ssize.y) { + curr_state = 0; + } else { + curr_state -= ssize.y; + } + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_HOME: + if (flags.noscroll) + break; + px = py = curr_state = 0; + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_END: + if (flags.noscroll) + break; + py = stable.size() + hdrs + 1 - ssize.y; + if (py < 0) + py = 0; + curr_state = stable.size(); + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + } + } + /* + * If we got a sigwinch, we need to redraw + */ + if (need_resize) { + handle_resize(mainwin, flags, ssize); + need_resize = false; + } + } // end while(1) + + out: + + /* + * The user has broken out of the loop, take down the curses + */ + end_curses(); + + // And we're done + return(0); + +} // end main