1 /* Shared library add-on to iptables to add string matching support. 2 * 3 * Copyright (C) 2000 Emmanuel Roger <winfield (at) freegates.be> 4 * 5 * 2005-08-05 Pablo Neira Ayuso <pablo (at) eurodev.net> 6 * - reimplemented to use new string matching iptables match 7 * - add functionality to match packets by using window offsets 8 * - add functionality to select the string matching algorithm 9 * 10 * ChangeLog 11 * 29.12.2003: Michael Rash <mbr (at) cipherdyne.org> 12 * Fixed iptables save/restore for ascii strings 13 * that contain space chars, and hex strings that 14 * contain embedded NULL chars. Updated to print 15 * strings in hex mode if any non-printable char 16 * is contained within the string. 17 * 18 * 27.01.2001: Gianni Tedesco <gianni (at) ecsc.co.uk> 19 * Changed --tos to --string in save(). Also 20 * updated to work with slightly modified 21 * ipt_string_info. 22 */ 23 #define _GNU_SOURCE 1 /* strnlen for older glibcs */ 24 #include <stdio.h> 25 #include <string.h> 26 #include <stdlib.h> 27 #include <ctype.h> 28 #include <xtables.h> 29 #include <linux/netfilter/xt_string.h> 30 31 enum { 32 O_FROM = 0, 33 O_TO, 34 O_ALGO, 35 O_ICASE, 36 O_STRING, 37 O_HEX_STRING, 38 F_STRING = 1 << O_STRING, 39 F_HEX_STRING = 1 << O_HEX_STRING, 40 F_OP_ANY = F_STRING | F_HEX_STRING, 41 }; 42 43 static void string_help(void) 44 { 45 printf( 46 "string match options:\n" 47 "--from Offset to start searching from\n" 48 "--to Offset to stop searching\n" 49 "--algo Algorithm\n" 50 "--icase Ignore case (default: 0)\n" 51 "[!] --string string Match a string in a packet\n" 52 "[!] --hex-string string Match a hex string in a packet\n"); 53 } 54 55 #define s struct xt_string_info 56 static const struct xt_option_entry string_opts[] = { 57 {.name = "from", .id = O_FROM, .type = XTTYPE_UINT16, 58 .flags = XTOPT_PUT, XTOPT_POINTER(s, from_offset)}, 59 {.name = "to", .id = O_TO, .type = XTTYPE_UINT16, 60 .flags = XTOPT_PUT, XTOPT_POINTER(s, to_offset)}, 61 {.name = "algo", .id = O_ALGO, .type = XTTYPE_STRING, 62 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, algo)}, 63 {.name = "string", .id = O_STRING, .type = XTTYPE_STRING, 64 .flags = XTOPT_INVERT, .excl = F_HEX_STRING}, 65 {.name = "hex-string", .id = O_HEX_STRING, .type = XTTYPE_STRING, 66 .flags = XTOPT_INVERT, .excl = F_STRING}, 67 {.name = "icase", .id = O_ICASE, .type = XTTYPE_NONE}, 68 XTOPT_TABLEEND, 69 }; 70 #undef s 71 72 static void string_init(struct xt_entry_match *m) 73 { 74 struct xt_string_info *i = (struct xt_string_info *) m->data; 75 76 i->to_offset = UINT16_MAX; 77 } 78 79 static void 80 parse_string(const char *s, struct xt_string_info *info) 81 { 82 /* xt_string does not need \0 at the end of the pattern */ 83 if (strlen(s) <= XT_STRING_MAX_PATTERN_SIZE) { 84 strncpy(info->pattern, s, XT_STRING_MAX_PATTERN_SIZE); 85 info->patlen = strnlen(s, XT_STRING_MAX_PATTERN_SIZE); 86 return; 87 } 88 xtables_error(PARAMETER_PROBLEM, "STRING too long \"%s\"", s); 89 } 90 91 static void 92 parse_hex_string(const char *s, struct xt_string_info *info) 93 { 94 int i=0, slen, sindex=0, schar; 95 short hex_f = 0, literal_f = 0; 96 char hextmp[3]; 97 98 slen = strlen(s); 99 100 if (slen == 0) { 101 xtables_error(PARAMETER_PROBLEM, 102 "STRING must contain at least one char"); 103 } 104 105 while (i < slen) { 106 if (s[i] == '\\' && !hex_f) { 107 literal_f = 1; 108 } else if (s[i] == '\\') { 109 xtables_error(PARAMETER_PROBLEM, 110 "Cannot include literals in hex data"); 111 } else if (s[i] == '|') { 112 if (hex_f) 113 hex_f = 0; 114 else { 115 hex_f = 1; 116 /* get past any initial whitespace just after the '|' */ 117 while (s[i+1] == ' ') 118 i++; 119 } 120 if (i+1 >= slen) 121 break; 122 else 123 i++; /* advance to the next character */ 124 } 125 126 if (literal_f) { 127 if (i+1 >= slen) { 128 xtables_error(PARAMETER_PROBLEM, 129 "Bad literal placement at end of string"); 130 } 131 info->pattern[sindex] = s[i+1]; 132 i += 2; /* skip over literal char */ 133 literal_f = 0; 134 } else if (hex_f) { 135 if (i+1 >= slen) { 136 xtables_error(PARAMETER_PROBLEM, 137 "Odd number of hex digits"); 138 } 139 if (i+2 >= slen) { 140 /* must end with a "|" */ 141 xtables_error(PARAMETER_PROBLEM, "Invalid hex block"); 142 } 143 if (! isxdigit(s[i])) /* check for valid hex char */ 144 xtables_error(PARAMETER_PROBLEM, "Invalid hex char '%c'", s[i]); 145 if (! isxdigit(s[i+1])) /* check for valid hex char */ 146 xtables_error(PARAMETER_PROBLEM, "Invalid hex char '%c'", s[i+1]); 147 hextmp[0] = s[i]; 148 hextmp[1] = s[i+1]; 149 hextmp[2] = '\0'; 150 if (! sscanf(hextmp, "%x", &schar)) 151 xtables_error(PARAMETER_PROBLEM, 152 "Invalid hex char `%c'", s[i]); 153 info->pattern[sindex] = (char) schar; 154 if (s[i+2] == ' ') 155 i += 3; /* spaces included in the hex block */ 156 else 157 i += 2; 158 } else { /* the char is not part of hex data, so just copy */ 159 info->pattern[sindex] = s[i]; 160 i++; 161 } 162 if (sindex > XT_STRING_MAX_PATTERN_SIZE) 163 xtables_error(PARAMETER_PROBLEM, "STRING too long \"%s\"", s); 164 sindex++; 165 } 166 info->patlen = sindex; 167 } 168 169 static void string_parse(struct xt_option_call *cb) 170 { 171 struct xt_string_info *stringinfo = cb->data; 172 const unsigned int revision = (*cb->match)->u.user.revision; 173 174 xtables_option_parse(cb); 175 switch (cb->entry->id) { 176 case O_STRING: 177 parse_string(cb->arg, stringinfo); 178 if (cb->invert) { 179 if (revision == 0) 180 stringinfo->u.v0.invert = 1; 181 else 182 stringinfo->u.v1.flags |= XT_STRING_FLAG_INVERT; 183 } 184 break; 185 case O_HEX_STRING: 186 parse_hex_string(cb->arg, stringinfo); /* sets length */ 187 if (cb->invert) { 188 if (revision == 0) 189 stringinfo->u.v0.invert = 1; 190 else 191 stringinfo->u.v1.flags |= XT_STRING_FLAG_INVERT; 192 } 193 break; 194 case O_ICASE: 195 if (revision == 0) 196 xtables_error(VERSION_PROBLEM, 197 "Kernel doesn't support --icase"); 198 199 stringinfo->u.v1.flags |= XT_STRING_FLAG_IGNORECASE; 200 break; 201 } 202 } 203 204 static void string_check(struct xt_fcheck_call *cb) 205 { 206 if (!(cb->xflags & F_OP_ANY)) 207 xtables_error(PARAMETER_PROBLEM, 208 "STRING match: You must specify `--string' or " 209 "`--hex-string'"); 210 } 211 212 /* Test to see if the string contains non-printable chars or quotes */ 213 static unsigned short int 214 is_hex_string(const char *str, const unsigned short int len) 215 { 216 unsigned int i; 217 for (i=0; i < len; i++) 218 if (! isprint(str[i])) 219 return 1; /* string contains at least one non-printable char */ 220 /* use hex output if the last char is a "\" */ 221 if (str[len-1] == '\\') 222 return 1; 223 return 0; 224 } 225 226 /* Print string with "|" chars included as one would pass to --hex-string */ 227 static void 228 print_hex_string(const char *str, const unsigned short int len) 229 { 230 unsigned int i; 231 /* start hex block */ 232 printf(" \"|"); 233 for (i=0; i < len; i++) 234 printf("%02x", (unsigned char)str[i]); 235 /* close hex block */ 236 printf("|\""); 237 } 238 239 static void 240 print_string(const char *str, const unsigned short int len) 241 { 242 unsigned int i; 243 printf(" \""); 244 for (i=0; i < len; i++) { 245 if (str[i] == '\"' || str[i] == '\\') 246 putchar('\\'); 247 printf("%c", (unsigned char) str[i]); 248 } 249 printf("\""); /* closing quote */ 250 } 251 252 static void 253 string_print(const void *ip, const struct xt_entry_match *match, int numeric) 254 { 255 const struct xt_string_info *info = 256 (const struct xt_string_info*) match->data; 257 const int revision = match->u.user.revision; 258 int invert = (revision == 0 ? info->u.v0.invert : 259 info->u.v1.flags & XT_STRING_FLAG_INVERT); 260 261 if (is_hex_string(info->pattern, info->patlen)) { 262 printf(" STRING match %s", invert ? "!" : ""); 263 print_hex_string(info->pattern, info->patlen); 264 } else { 265 printf(" STRING match %s", invert ? "!" : ""); 266 print_string(info->pattern, info->patlen); 267 } 268 printf(" ALGO name %s", info->algo); 269 if (info->from_offset != 0) 270 printf(" FROM %u", info->from_offset); 271 if (info->to_offset != 0) 272 printf(" TO %u", info->to_offset); 273 if (revision > 0 && info->u.v1.flags & XT_STRING_FLAG_IGNORECASE) 274 printf(" ICASE"); 275 } 276 277 static void string_save(const void *ip, const struct xt_entry_match *match) 278 { 279 const struct xt_string_info *info = 280 (const struct xt_string_info*) match->data; 281 const int revision = match->u.user.revision; 282 int invert = (revision == 0 ? info->u.v0.invert : 283 info->u.v1.flags & XT_STRING_FLAG_INVERT); 284 285 if (is_hex_string(info->pattern, info->patlen)) { 286 printf("%s --hex-string", (invert) ? " !" : ""); 287 print_hex_string(info->pattern, info->patlen); 288 } else { 289 printf("%s --string", (invert) ? " !": ""); 290 print_string(info->pattern, info->patlen); 291 } 292 printf(" --algo %s", info->algo); 293 if (info->from_offset != 0) 294 printf(" --from %u", info->from_offset); 295 if (info->to_offset != 0) 296 printf(" --to %u", info->to_offset); 297 if (revision > 0 && info->u.v1.flags & XT_STRING_FLAG_IGNORECASE) 298 printf(" --icase"); 299 } 300 301 302 static struct xtables_match string_mt_reg[] = { 303 { 304 .name = "string", 305 .revision = 0, 306 .family = NFPROTO_UNSPEC, 307 .version = XTABLES_VERSION, 308 .size = XT_ALIGN(sizeof(struct xt_string_info)), 309 .userspacesize = offsetof(struct xt_string_info, config), 310 .help = string_help, 311 .init = string_init, 312 .print = string_print, 313 .save = string_save, 314 .x6_parse = string_parse, 315 .x6_fcheck = string_check, 316 .x6_options = string_opts, 317 }, 318 { 319 .name = "string", 320 .revision = 1, 321 .family = NFPROTO_UNSPEC, 322 .version = XTABLES_VERSION, 323 .size = XT_ALIGN(sizeof(struct xt_string_info)), 324 .userspacesize = offsetof(struct xt_string_info, config), 325 .help = string_help, 326 .init = string_init, 327 .print = string_print, 328 .save = string_save, 329 .x6_parse = string_parse, 330 .x6_fcheck = string_check, 331 .x6_options = string_opts, 332 }, 333 }; 334 335 void _init(void) 336 { 337 xtables_register_matches(string_mt_reg, ARRAY_SIZE(string_mt_reg)); 338 } 339