1/* 2 Shared library add-on to iptables to add layer 7 matching support. 3 4 By Matthew Strait <quadong@users.sf.net>, Oct 2003. 5 6 http://l7-filter.sf.net 7 8 This program is free software; you can redistribute it and/or 9 modify it under the terms of the GNU General Public License 10 as published by the Free Software Foundation; either version 11 2 of the License, or (at your option) any later version. 12 http://www.gnu.org/licenses/gpl.txt 13 14 Based on libipt_string.c (C) 2000 Emmanuel Roger <winfield@freegates.be> 15*/ 16 17#define _GNU_SOURCE 18#include <stdio.h> 19#include <netdb.h> 20#include <string.h> 21#include <stdlib.h> 22#include <getopt.h> 23#include <ctype.h> 24#include <dirent.h> 25 26#include "iptables.h" 27#include <xtables.h> 28#include <linux/netfilter/x_tables.h> 29#include <linux/netfilter/xt_layer7.h> 30 31#define MAX_FN_LEN 256 32 33static char l7dir[MAX_FN_LEN] = "\0"; 34 35/* Function which prints out usage message. */ 36static void layer7_help(void) 37{ 38 printf( 39 "LAYER7 match v%s options:\n" 40 "--l7dir <directory> : Look for patterns here instead of /etc_ro/l7-protocols/\n" 41 " (--l7dir must be specified before --l7proto if used!)\n" 42 "--l7proto [!] <name> : Match the protocol defined in /etc_ro/l7-protocols/name.pat\n", 43 XTABLES_VERSION); 44} 45 46static struct option layer7_opts[] = { 47 {.name = "l7proto", .has_arg = true, .val = '1' }, 48 {.name = "l7dir", .has_arg = true, .val = '2' }, 49 XT_GETOPT_TABLEEND, 50}; 51 52/* reads filename, puts protocol info into layer7_protocol_info, number of protocols to numprotos */ 53static int parse_protocol_file(char * filename, const char * protoname, struct xt_layer7_info *info) 54{ 55 FILE * f; 56 char * linebuf = NULL; 57 size_t len = 0; 58 59 enum { protocol, pattern, done } datatype = protocol; 60 61 f = fopen(filename, "r"); 62 63 if(!f) 64 return 0; 65 66 while(getline(&linebuf, &len, f) != -1) 67 { 68 if(strlen(linebuf) < 2 || linebuf[0] == '#') 69 continue; 70 71 /* strip the pesky newline... */ 72 if(linebuf[strlen(linebuf) - 1] == '\n') 73 linebuf[strlen(linebuf) - 1] = '\0'; 74 75 if(datatype == protocol) 76 { 77 /* Ignore everything on the linebuf beginning with the 78 first space or tab . For instance, this allows the 79 protocol linebuf in http.pat to be "http " (or 80 "http I am so cool") instead of just "http". */ 81 if(strchr(linebuf, ' ')){ 82 char * space = strchr(linebuf, ' '); 83 space[0] = '\0'; 84 } 85 if(strchr(linebuf, '\t')){ 86 char * space = strchr(linebuf, '\t'); 87 space[0] = '\0'; 88 } 89 90 /* sanity check. First non-comment non-blank 91 linebuf must be the same as the file name. */ 92 if(strcmp(linebuf, protoname)) 93 xtables_error(OTHER_PROBLEM, 94 "Protocol name (%s) doesn't match file name (%s). Bailing out\n", 95 linebuf, filename); 96 97 if(strlen(linebuf) >= MAX_PROTOCOL_LEN) 98 xtables_error(PARAMETER_PROBLEM, 99 "Protocol name in %s too long!", filename); 100 strncpy(info->protocol, linebuf, MAX_PROTOCOL_LEN); 101 102 datatype = pattern; 103 } 104 else if(datatype == pattern) 105 { 106 if(strlen(linebuf) >= MAX_PATTERN_LEN) 107 xtables_error(PARAMETER_PROBLEM, "Pattern in %s too long!", filename); 108 strncpy(info->pattern, linebuf, MAX_PATTERN_LEN); 109 110 datatype = done; 111 break; 112 } 113 else 114 xtables_error(OTHER_PROBLEM, "Internal error"); 115 } 116 117 if(datatype != done) 118 xtables_error(OTHER_PROBLEM, "Failed to get all needed data from %s", filename); 119 120 if(linebuf) free(linebuf); 121 fclose(f); 122 123 return 1; 124 125/* 126 fprintf(stderr, "protocol: %s\npattern: %s\n\n", 127 info->protocol, 128 info->pattern); 129*/ 130} 131 132static int hex2dec(char c) 133{ 134 switch (c) 135 { 136 case '0' ... '9': 137 return c - '0'; 138 case 'a' ... 'f': 139 return c - 'a' + 10; 140 case 'A' ... 'F': 141 return c - 'A' + 10; 142 default: 143 xtables_error(OTHER_PROBLEM, "hex2dec: bad value!\n"); 144 return 0; 145 } 146} 147 148/* takes a string with \xHH escapes and returns one with the characters 149they stand for */ 150static char * pre_process(char * s) 151{ 152 char * result = malloc(strlen(s) + 1); 153 int sindex = 0, rindex_1 = 0; 154 while( sindex < strlen(s) ) 155 { 156 if( sindex + 3 < strlen(s) && 157 s[sindex] == '\\' && s[sindex+1] == 'x' && 158 isxdigit(s[sindex + 2]) && isxdigit(s[sindex + 3]) ) 159 { 160 /* carefully remember to call tolower here... */ 161 result[rindex_1] = tolower( hex2dec(s[sindex + 2])*16 + 162 hex2dec(s[sindex + 3] ) ); 163 164 switch ( result[rindex_1] ) 165 { 166 case 0x24: 167 case 0x28: 168 case 0x29: 169 case 0x2a: 170 case 0x2b: 171 case 0x2e: 172 case 0x3f: 173 case 0x5b: 174 case 0x5c: 175 case 0x5d: 176 case 0x5e: 177 case 0x7c: 178 fprintf(stderr, 179 "Warning: layer7 regexp contains a control character, %c, in hex (\\x%c%c).\n" 180 "I recommend that you write this as %c or \\%c, depending on what you meant.\n", 181 result[rindex_1], s[sindex + 2], s[sindex + 3], result[rindex_1], result[rindex_1]); 182 break; 183 case 0x00: 184 fprintf(stderr, 185 "Warning: null (\\x00) in layer7 regexp. A null terminates the regexp string!\n"); 186 break; 187 default: 188 break; 189 } 190 191 192 sindex += 3; /* 4 total */ 193 } 194 else 195 result[rindex_1] = tolower(s[sindex]); 196 197 sindex++; 198 rindex_1++; 199 } 200 result[rindex_1] = '\0'; 201 202 return result; 203} 204 205#define MAX_SUBDIRS 128 206static char ** readl7dir(char * dirname) 207{ 208 DIR * scratchdir; 209 struct dirent ** namelist; 210 char ** subdirs = malloc(MAX_SUBDIRS * sizeof(char *)); 211 212 int n, d = 1; 213 subdirs[0] = ""; 214 215 n = scandir(dirname, &namelist, 0, alphasort); 216 217 if (n < 0) 218 { 219 perror("scandir"); 220 xtables_error(OTHER_PROBLEM, "Couldn't open %s\n", dirname); 221 } 222 else 223 { 224 while(n--) 225 { 226 char fulldirname[MAX_FN_LEN]; 227 228 snprintf(fulldirname, MAX_FN_LEN, "%s/%s", dirname, namelist[n]->d_name); 229 230 if((scratchdir = opendir(fulldirname)) != NULL) 231 { 232 closedir(scratchdir); 233 234 if(!strcmp(namelist[n]->d_name, ".") || 235 !strcmp(namelist[n]->d_name, "..")) 236 /* do nothing */ ; 237 else 238 { 239 subdirs[d] = malloc(strlen(namelist[n]->d_name) + 1); 240 strcpy(subdirs[d], namelist[n]->d_name); 241 d++; 242 if(d >= MAX_SUBDIRS - 1) 243 { 244 fprintf(stderr, 245 "Too many subdirectories, skipping the rest!\n"); 246 break; 247 } 248 } 249 } 250 free(namelist[n]); 251 } 252 free(namelist); 253 } 254 255 subdirs[d] = NULL; 256 257 return subdirs; 258} 259 260static void 261parse_layer7_protocol(const char *s, struct xt_layer7_info *info) 262{ 263 char filename[MAX_FN_LEN]; 264 char * dir = NULL; 265 char ** subdirs; 266 int n = 0, done = 0; 267 268 if(strlen(l7dir) > 0) 269 dir = l7dir; 270 else 271 dir = "/etc_ro/l7-protocols"; 272 273 subdirs = readl7dir(dir); 274 275 while(subdirs[n] != NULL) 276 { 277 int c = snprintf(filename, MAX_FN_LEN, "%s/%s/%s.pat", dir, subdirs[n], s); 278 279 //fprintf(stderr, "Trying to find pattern in %s ... ", filename); 280 281 if(c > MAX_FN_LEN) 282 { 283 xtables_error(OTHER_PROBLEM, 284 "Filename beginning with %s is too long!\n", filename); 285 } 286 287 /* read in the pattern from the file */ 288 if(parse_protocol_file(filename, s, info)) 289 { 290 //fprintf(stderr, "found\n"); 291 done = 1; 292 break; 293 } 294 295 //fprintf(stderr, "not found\n"); 296 297 n++; 298 } 299 300 if(!done) 301 xtables_error(OTHER_PROBLEM, 302 "Couldn't find a pattern definition file for %s.\n", s); 303 304 /* process \xHH escapes and tolower everything. (our regex lib has no 305 case insensitivity option.) */ 306 strncpy(info->pattern, pre_process(info->pattern), MAX_PATTERN_LEN); 307} 308 309/* Function which parses command options; returns true if it ate an option */ 310static int layer7_parse(int c, char **argv, int invert, unsigned int *flags, 311 const void *entry, struct xt_entry_match **match) 312{ 313 struct xt_layer7_info *layer7info = 314 (struct xt_layer7_info *)(*match)->data; 315 316 switch (c) { 317 case '1': 318 parse_layer7_protocol(argv[optind-1], layer7info); 319 if (invert) 320 layer7info->invert = 1; 321 *flags = 1; 322 break; 323 324 case '2': 325 /* not going to use this, but maybe we need to strip a ! anyway (?) */ 326 if(strlen(argv[optind-1]) >= MAX_FN_LEN) 327 xtables_error(PARAMETER_PROBLEM, "directory name too long\n"); 328 329 strncpy(l7dir, argv[optind-1], MAX_FN_LEN); 330 331 *flags = 1; 332 break; 333 334 default: 335 return 0; 336 } 337 338 return 1; 339} 340 341/* Final check; must have specified --l7proto */ 342static void layer7_final_check(unsigned int flags) 343{ 344 if (!flags) 345 xtables_error(PARAMETER_PROBLEM, 346 "LAYER7 match: You must specify `--l7proto'"); 347} 348 349static void print_protocol(char s[], int invert, int numeric) 350{ 351 fputs("l7proto ", stdout); 352 if (invert) fputc('!', stdout); 353 printf("%s ", s); 354} 355 356/* Prints out the matchinfo. */ 357static void layer7_print(const void *ip, 358 const struct xt_entry_match *match, 359 int numeric) 360{ 361 printf("LAYER7 "); 362 363 print_protocol(((struct xt_layer7_info *)match->data)->protocol, 364 ((struct xt_layer7_info *)match->data)->invert, numeric); 365} 366/* Saves the union ipt_matchinfo in parsable form to stdout. */ 367static void layer7_save(const void *ip, const struct xt_entry_match *match) 368{ 369 const struct xt_layer7_info *info = 370 (const struct xt_layer7_info*) match->data; 371 372 printf("--l7proto %s%s ", (info->invert) ? "! ": "", info->protocol); 373} 374 375static struct xtables_match layer7_match = { 376 .family = NFPROTO_IPV4, 377 .name = "layer7", 378 .version = XTABLES_VERSION, 379 .size = XT_ALIGN(sizeof(struct xt_layer7_info)), 380 .userspacesize = XT_ALIGN(sizeof(struct xt_layer7_info)), 381 .help = layer7_help, 382 .parse = layer7_parse, 383 .final_check = layer7_final_check, 384 .print = layer7_print, 385 .save = layer7_save, 386 .extra_opts = layer7_opts 387}; 388 389void _init(void) 390{ 391 xtables_register_match(&layer7_match); 392} 393