/* * Copyright (c) 2001 Jean-Baptiste Marchand, Hervé Schauer Consultants. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Jean-Baptiste Marchand * at Hervé Schauer Consultants. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Control program for the pktfltsrv Win32 packet filtering service */ #include #include #include #include #define THE_PIPE "\\\\.\\pipe\\PktFltPipe" #define MAX_RULE_LENGTH 256 #define SYN_ERROR_MSG "Syntax error: " #define SYN_ERROR_MSG_LEN 14 #define FILTER_FAILURE 0 /* filter was correctly added */ #define FILTER_SUCCESS 1 /* filter had a syntax error */ #define FILTER_MESSAGE 2 /* informative message returned to the client */ void usage(char **argv) { char *progname = "pktctl"; fprintf(stderr, "usage:\n", progname); fprintf(stderr, " %s -a filtering_rule: add a filtering rule\n", progname); fprintf(stderr, " %s -d rule interface: delete a rule on specificied interface\n", progname); fprintf(stderr, " %s -f filters_file: source filters file\n", progname); fprintf(stderr, " %s -i : interactive mode\n", progname); fprintf(stderr, " %s -F filters_file: flush all interfaces and load filters file\n", progname); fprintf(stderr, " %s -l interface: list rules on specified interface\n", progname); fprintf(stderr, " %s -L interface: list rules with rule numbers on specified interface\n", progname); fprintf(stderr, " %s -s interface: get brief statistics on specified interface\n", progname); fprintf(stderr, " %s -S interface: get detailed statistics on specified interface\n", progname); fprintf(stderr, " %s -Fa interface: flush all rules on specified interface\n", progname); fprintf(stderr, " %s -I: list mapping of Ethernet interfaces\n", progname); } void display_help(void) { fprintf(stderr, "Command Action\n"); fprintf(stderr, "-------------------------------------------------------------------------------\n"); fprintf(stderr, "rule add 'rule' to the filtering engine\n"); fprintf(stderr, "delete rule on ifname delete rule numbered 'rule' on 'ifname'\n"); fprintf(stderr, "flush on ifname flush rules on interface 'ifname'\n"); fprintf(stderr, "list on ifname list current rules on interface 'ifname'\n"); fprintf(stderr, "List on ifname list current rules with rules numbers on interface 'ifname'\n"); fprintf(stderr, "stats on ifname give brief statistics for interface 'ifname'\n"); fprintf(stderr, "Stats on ifname give detailed statistics for interface 'ifname'\n"); fprintf(stderr, "source filename load rules from file 'filename'\n"); fprintf(stderr, "reload filename flush all interfaces and load rules from file 'filename'"); fprintf(stderr, "help | h display this help text\n"); fprintf(stderr, "quit | q | exit | x exit\n"); } /* get the width of the output terminal */ short get_output_width(void) { HANDLE stdout_handle; CONSOLE_SCREEN_BUFFER_INFO screen_infos; BOOL status; short width = 80; /* default value */ stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); if (stdout_handle == INVALID_HANDLE_VALUE) return width; status = GetConsoleScreenBufferInfo(stdout_handle, &screen_infos); if (status == 0) return width; return screen_infos.dwSize.X; } /* display the position of a syntax error */ void display_syn_error(char *filter, unsigned char pos) { unsigned char pad_width = pos; unsigned short msg_width; unsigned short output_width; unsigned short chars_before; char *one_line_format_str = "%%s%%s%\n%%%ds\n"; char *multi_lines_format_str = "%%s%%.%ds\n%%%ds\n%%s\n"; char format_str_expanded[42]; output_width = get_output_width(); msg_width = SYN_ERROR_MSG_LEN + strlen(filter); if (output_width > msg_width) { /* everything on one line */ _snprintf(format_str_expanded, 42, one_line_format_str, pad_width + SYN_ERROR_MSG_LEN + 1); printf(format_str_expanded, SYN_ERROR_MSG, filter, "^"); } else { /* multi-lines */ chars_before = pad_width + (output_width - ((SYN_ERROR_MSG_LEN + pad_width) % output_width)); _snprintf(format_str_expanded, 42, multi_lines_format_str, chars_before, output_width - (chars_before - pad_width) +1); if (chars_before < strlen(filter)) printf(format_str_expanded, SYN_ERROR_MSG, filter, "^", filter + chars_before); else printf(format_str_expanded, SYN_ERROR_MSG, filter, "^", ""); } } /* list (Ethernet) interfaces */ int list_ip_interfaces(void) { IP_ADAPTER_INFO *adapt_info; DWORD status; ULONG size; /* trying with only one interface */ size = sizeof(IP_ADAPTER_INFO); adapt_info = malloc(size); status = GetAdaptersInfo(adapt_info, &size); if (status != ERROR_SUCCESS) { /* not enough space */ free(adapt_info); adapt_info = malloc(size); status = GetAdaptersInfo(adapt_info, &size); } while (adapt_info) { /* we use the ComboIndex field value to identify network interfaces The Index field value is the one found in the output of the route print command and is not really interesting in our case... */ switch (adapt_info->Type) { case MIB_IF_TYPE_ETHERNET: printf("eth%d: (%s): %s\n", adapt_info->ComboIndex, adapt_info->Description, adapt_info->IpAddressList.IpAddress.String); break; case MIB_IF_TYPE_PPP: printf("ppp%d: (%s): %s\n", adapt_info->ComboIndex, adapt_info->Description, adapt_info->IpAddressList.IpAddress.String); break; case MIB_IF_TYPE_SLIP: printf("sl%d: (%s): %s\n", adapt_info->ComboIndex, adapt_info->Description, adapt_info->IpAddressList.IpAddress.String); break; case MIB_IF_TYPE_LOOPBACK: /* XXX Probably never returned, only with GetIfTable() API */ printf("lo%d: (%s): %s\n", adapt_info->ComboIndex, adapt_info->Description, adapt_info->IpAddressList.IpAddress.String); break; case MIB_IF_TYPE_TOKENRING: printf("tr%d: (%s): %s\n", adapt_info->ComboIndex, adapt_info->Description, adapt_info->IpAddressList.IpAddress.String); break; case MIB_IF_TYPE_FDDI: printf("fd%d: (%s): %s\n", adapt_info->ComboIndex, adapt_info->Description, adapt_info->IpAddressList.IpAddress.String); break; default: printf("if%d: (%s): %s\n", adapt_info->ComboIndex, adapt_info->Description, adapt_info->IpAddressList.IpAddress.String); break; } adapt_info = adapt_info->Next; } #if 0 /* trying with only one interface */ size = sizeof(MIB_IFTABLE); intf_info = malloc(size); status = GetIfTable(intf_info, &size, TRUE); if (status != NO_ERROR) { /* not enough space */ free(intf_info); intf_info = malloc(size); status = GetIfTable(intf_info, &size, FALSE); } intf_count = intf_info->dwNumEntries; printf("intf_count = %d\n", intf_count); intf_row = intf_info->table; while (intf_count--) { printf("if%d, type = %d : (%s): \n", intf_row->dwIndex, intf_row->dwType, intf_row->bPhysAddr); intf_row++; } #endif return 1; } /* send a message through named pipe and print an error message if necessary */ char write_message(char *filter) { BOOL bool; DWORD read; DWORD written; HANDLE my_pipe; char status_code; char syntax_error; unsigned char *msg_buf = NULL; unsigned int msg_buf_size; bool = WaitNamedPipe(THE_PIPE, NMPWAIT_USE_DEFAULT_WAIT); if (!bool) { fprintf(stderr, "error: unable to connect to named pipe\n"); return 1; } /* open the pipe */ my_pipe = CreateFile(THE_PIPE, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (my_pipe == INVALID_HANDLE_VALUE) { fprintf(stderr, "error: unable to read from %s\n", THE_PIPE); return 1; } /* write message in the pipe */ bool = WriteFile(my_pipe, filter, strlen(filter), &written, NULL); if (!bool) { fprintf(stderr, "error: unable to write to %s\n", THE_PIPE); return 1; } /* read the result */ bool = ReadFile(my_pipe, &status_code, 1, &read, NULL); if (!bool || read != 1) { fprintf(stderr, "error: unable to read from %s\n", THE_PIPE); return 1; } if (status_code == FILTER_FAILURE) { /* syntax error */ bool = ReadFile(my_pipe, &syntax_error, 1, &read, NULL); if (!bool || read != 1) { fprintf(stderr, "error: unable to read from %s\n", THE_PIPE); return 1; } display_syn_error(filter, syntax_error); return FILTER_FAILURE; } if (status_code == FILTER_MESSAGE) { /* informative message */ /* read length of the message */ bool = ReadFile(my_pipe, &msg_buf_size, sizeof(msg_buf_size), &read, NULL); if (!bool || read != sizeof(msg_buf_size)) { fprintf(stderr, "error: unable to read from %s\n", THE_PIPE); return 1; } /* read message */ msg_buf = malloc((msg_buf_size + 1)* sizeof(char)); memset(msg_buf, 0, (msg_buf_size + 1)); bool = ReadFile(my_pipe, msg_buf, msg_buf_size, &read, NULL); if (!bool || read != msg_buf_size) { fprintf(stderr, "error: unable to read from %s\n", THE_PIPE); return 1; } printf("%s", msg_buf); free(msg_buf); return FILTER_MESSAGE; } /* close named pipe instance */ CloseHandle(my_pipe); if (status_code != FILTER_SUCCESS) fprintf(stderr, "warning: unknown status code\n"); return FILTER_SUCCESS; } /* get brief filtering statistics on given interface */ void brief_stats_on_interface(char *iface) { char stats_msg[32] = "stats on "; strncat(stats_msg, iface, 32 - strlen(stats_msg)); write_message(stats_msg); } /* get detailed filtering statistics on given interface */ void detailed_stats_on_interface(char *iface) { char stats_msg[32] = "Stats on "; strncat(stats_msg, iface, 32 - strlen(stats_msg)); write_message(stats_msg); } /* flush rules on given interface */ void flush_interface(char *iface) { char flush_msg[32] = "flush on "; strncat(flush_msg, iface, 32 - strlen(flush_msg)); write_message(flush_msg); } /* delete a rule on given interface */ void delete_rule(unsigned char rule, char *iface) { char delete_msg[32] = ""; _snprintf(delete_msg, 31, "delete %d on %s", rule, iface); write_message(delete_msg); } /* list the rules on given interface */ void list_rules_on_interface(char *iface) { char list_msg[32] = "list on "; strncat(list_msg, iface, 32 - strlen(list_msg)); write_message(list_msg); } /* list with rule numbers the rules on given interface */ void numbered_list_rules_on_interface(char *iface) { char list_msg[32] = "List on "; strncat(list_msg, iface, 32 - strlen(list_msg)); write_message(list_msg); } /* read filters from a file */ char read_filters_from_file(char *filename) { FILE *filters; char line[MAX_RULE_LENGTH]; /* XXX one rule can't exceed 512 bytes XXX */ char success; filters = fopen(filename, "r"); if (filters == NULL) { fprintf(stderr,"error: could not open filters file %s\n", filename); return -1; } while (fgets(line, MAX_RULE_LENGTH, filters) != NULL) { if (*line != '#') success = write_message(line); else /* ignore comments */ continue; if (success == FILTER_FAILURE) /* stop if a line contains a syntax error */ break; } fclose(filters); return success; } /* launch 'pktctl>' shell */ char start_pktctl_shell(void) { char rule_buf[MAX_RULE_LENGTH]; char *word; char *p; char *ret; while (1) { printf("pktctl> ", 8); memset(rule_buf, 0, MAX_RULE_LENGTH); ret = fgets(rule_buf, MAX_RULE_LENGTH, stdin); if (ret == NULL) { fprintf(stderr, "error: rule too long\n"); continue; } /* test 'help' command */ if ((strncmp(rule_buf, "help", 4) == 0) || (strncmp(rule_buf, "h\n", 2) == 0)) { display_help(); continue; } /* test 'source' command */ if (strncmp(rule_buf, "source", 6) == 0) { word = rule_buf + 6; while ((word < (rule_buf + MAX_RULE_LENGTH)) && (*word == ' ')) word++; p = word; while ((word < (rule_buf + MAX_RULE_LENGTH)) && (*word != '\n')) word++; *word = 0; read_filters_from_file(p); continue; } /* test 'reload' command */ if (strncmp(rule_buf, "reload", 6) == 0) { flush_interface("all"); word = rule_buf + 6; while ((word < (rule_buf + MAX_RULE_LENGTH)) && (*word == ' ')) word++; p = word; while ((word < (rule_buf + MAX_RULE_LENGTH)) && (*word != '\n')) word++; *word = 0; read_filters_from_file(p); continue; } /* test exit conditions : q, exit or ^D */ if (strncmp(rule_buf, "q\n", 2) == 0) return 1; if (strncmp(rule_buf, "quit", 4) == 0) return 1; if (strncmp(rule_buf, "exit", 4) == 0) return 1; if (strncmp(rule_buf, "\004\n", 2) == 0) return 1; if (strncmp(rule_buf, "x\n", 2) == 0) return 1; /* test "" condition */ if (strcmp(rule_buf, "\n") == 0) continue; write_message(rule_buf); } } /* control program for the packet filtering service */ int main(int argc, char **argv) { DWORD status; unsigned char arg; char list_iface = 0; char flush_all = 0; unsigned char rule_to_delete = 0; char interactive = 0; char *iface = NULL; char *rule = NULL; char *filters_file = NULL; char *brief_stats_iface = NULL; char *delete_rule_iface = NULL; char *detailed_stats_iface = NULL; char *list_rules_iface = NULL; char *numbered_list_rules_iface = NULL; char *flush_iface = NULL; if (argc < 2) { usage(argv); return 1; } arg = 1; while (arg < argc) { if (!strcmp(argv[arg], "-Fa")) { if (++arg == argc) /* assume 'flush on all' */ flush_iface = "all"; else { flush_iface = strdup(argv[arg]); if (++arg != argc) { fprintf(stderr, "error: option '-Fa' accepts only one parameter\n"); usage(argv); return 1; } } continue; } if (!strcmp(argv[arg], "-i")) { interactive = 1; if (++arg != argc) { fprintf(stderr, "error: option '-i' accepts no parameter\n"); usage(argv); return 1; } continue; } if (!strcmp(argv[arg], "-I")) { list_iface = 1; if (++arg != argc) { fprintf(stderr, "error: option '-I' accepts no parameter\n"); usage(argv); return 1; } continue; } if (!strcmp(argv[arg], "-a")) { if (++arg == argc) { fprintf(stderr, "error: option '-a' needs one parameter\n"); usage(argv); return 1; } rule = strdup(argv[arg]); if (++arg != argc) { fprintf(stderr, "error: option '-a' accepts only one parameter\n"); usage(argv); return 1; } continue; } if (!strcmp(argv[arg], "-f")) { if (++arg == argc) { fprintf(stderr, "error: option '-f' needs one parameter\n"); usage(argv); return 1; } filters_file = strdup(argv[arg]); if (++arg != argc) { fprintf(stderr, "error: option '-f' accepts only one parameter\n"); usage(argv); return 1; } continue; } if (!strcmp(argv[arg], "-F")) { flush_all = 1; if (++arg == argc) { fprintf(stderr, "error: option '-F' needs one parameter\n"); usage(argv); return 1; } filters_file = strdup(argv[arg]); if (++arg != argc) { fprintf(stderr, "error: option '-F' accepts only one parameter\n"); usage(argv); return 1; } continue; } if (!strcmp(argv[arg], "-d")) { if (++arg == argc) { fprintf(stderr, "error: option '-d' needs two parameters\n"); usage(argv); return 1; } rule_to_delete = atoi(argv[arg]); if (++arg == argc) { fprintf(stderr, "error: option '-d' needs two parameters\n"); usage(argv); return 1; } delete_rule_iface = strdup(argv[arg]); if (strlen(delete_rule_iface) != 4) { fprintf(stderr, "error: invalid interface name\n"); usage(argv); return 1; } if (++arg != argc) { fprintf(stderr, "error: option '-d' accepts exactly two parameters\n"); usage(argv); return 1; } continue; } if (!strcmp(argv[arg], "-s")) { if (++arg == argc) { fprintf(stderr, "error: option '-s' needs one parameter\n"); usage(argv); return 1; } brief_stats_iface = strdup(argv[arg]); if (++arg != argc) { fprintf(stderr, "error: option '-s' accepts only one parameter\n"); usage(argv); return 1; } continue; } if (!strcmp(argv[arg], "-S")) { if (++arg == argc) { fprintf(stderr, "error: option '-S' needs one parameter\n"); usage(argv); return 1; } detailed_stats_iface = strdup(argv[arg]); if (++arg != argc) { fprintf(stderr, "error: option '-S' accepts only one parameter\n"); usage(argv); return 1; } continue; } if (!strcmp(argv[arg], "-l")) { if (++arg == argc) { fprintf(stderr, "error: option '-l' needs one parameter\n"); usage(argv); return 1; } list_rules_iface = strdup(argv[arg]); if (++arg != argc) { fprintf(stderr, "error: option '-l' accepts only one parameter\n"); usage(argv); return 1; } continue; } if (!strcmp(argv[arg], "-L")) { if (++arg == argc) { fprintf(stderr, "error: option '-L' needs one parameter\n"); usage(argv); return 1; } numbered_list_rules_iface = strdup(argv[arg]); if (++arg != argc) { fprintf(stderr, "error: option '-L' accepts only one parameter\n"); usage(argv); return 1; } continue; } fprintf(stderr, "error: unknown option '%s'\n", argv[arg]); usage(argv); return 1; } /* -I option */ if (list_iface) { list_ip_interfaces(); return 0; } /* -i option : interactive */ if (interactive) { status = start_pktctl_shell(); return 0; } /* -a option */ if (rule) { if (strlen(rule) > MAX_RULE_LENGTH) { fprintf(stderr, "error: rule too long\n"); return 1; } write_message(rule); } /* -d option */ if (delete_rule_iface) delete_rule(rule_to_delete, delete_rule_iface); /* -f | -F option */ if (filters_file) { if (flush_all) { FILE *filters; /* test if file exists before flushing */ filters = fopen(filters_file, "r"); if (filters) { flush_interface("all"); fclose(filters); } else { fprintf(stderr, "error: could not open filters file %s\n", filters_file); return 1; } } status = read_filters_from_file(filters_file); } /* -l option */ if (list_rules_iface) list_rules_on_interface(list_rules_iface); /* -L option */ if (numbered_list_rules_iface) numbered_list_rules_on_interface(numbered_list_rules_iface); /* -s option */ if (brief_stats_iface) brief_stats_on_interface(brief_stats_iface); /* -S option */ if (detailed_stats_iface) detailed_stats_on_interface(detailed_stats_iface); /* -Fa option */ if (flush_iface) flush_interface(flush_iface); return 0; }