1/* Shared library add-on to iptables to add string matching support. 2 * 3 * Copyright (C) 2000 Emmanuel Roger <winfield@freegates.be> 4 * 5 * 2005-08-05 Pablo Neira Ayuso <pablo@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@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@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 31enum { 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 43static 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 56static 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 72static 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 79static void 80parse_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 91static void 92parse_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 169static 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 204static 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 */ 213static unsigned short int 214is_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 */ 227static void 228print_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 239static void 240print_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 252static void 253string_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 277static 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 302static 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 335void _init(void) 336{ 337 xtables_register_matches(string_mt_reg, ARRAY_SIZE(string_mt_reg)); 338} 339