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