Browse Source

Add support for EMM filters similar to DVBAPI filters (match + mask).

Georgi Chorbadzhiyski 11 years ago
parent
commit
dc4df6e288
4 changed files with 207 additions and 74 deletions
  1. 1
    0
      ChangeLog
  2. 52
    17
      FILTERING
  3. 11
    4
      data.h
  4. 143
    53
      filter.c

+ 1
- 0
ChangeLog View File

@@ -8,6 +8,7 @@ now : current
8 8
    working if the output address was not multicast.
9 9
  * Add support for Griffin CAS and for DGCrypt CAS.
10 10
  * Add support for setting multicast source address /SSM/ (--input-source).
11
+ * Add support for EMM filters similar to DVBAPI filters (match + mask).
11 12
 
12 13
 2012-09-10 : Version 9.0
13 14
  * Add --no-output-on-error (-u) option. By using this option output can be

+ 52
- 17
FILTERING View File

@@ -67,13 +67,47 @@ Currently defined command settings are:
67 67
  * name=X        - Sets the filter name (used when displaying filters).
68 68
                    The name can not be longer than 32 symbols.
69 69
 
70
+ * match=X1 X4 X5
71
+ * mask=M1 M4 M5
72
+                 - Match bytes are series of hex numbers separated by
73
+                   space " " or dot ".". When the bytes are processed 0x
74
+                   prefix is removed from them. The maximum match bytes
75
+                   is 16.
76
+
77
+                   The match bytes are compared to the incoming packet
78
+                   by first applying the mask (binary AND operation) set
79
+                   in mask= setting. The default mask is 0xFF.
80
+
81
+                   The match bytes are compared to first, forth and so
82
+                   on bytes in the incoming EMM packet. Second and third
83
+                   incoming bytes in the EMM are not checked (they specify
84
+                   the section length).
85
+
86
+                   Here is an example EMM and match+mask that would match:
87
+
88
+                      Pos:  1  2  3  4  5  6  7  8  9 10 11 12 13
89
+                           -- -- -- -- -- -- -- -- -- -- -- -- --
90
+                      EMM: 82 70 b4 aa bb cc d0 00 01 xx xx xx xx
91
+                    Match: 82       aa bb cc dd
92
+                     Mask: ff       ff ff ff f0
93
+
94
+                   Such filter is configured like that:
95
+                     --emm-filter "accept/name=Test_Filter,match=82 aa bb cc dd,mask=ff ff ff ff f0"
96
+
70 97
  * offset=X      - Sets the offset at which data bytes would be checked
71 98
                    against EMM. The default offset is 0.
72 99
 
100
+                   This setting is ignored when match+mask are set.
101
+
73 102
  * data=XX YY ZZ - Data bytes are series of hex numbers separated by
74 103
                    space " " or dot ".". When data bytes are processed 0x
75 104
                    prefix is removed from them. The maximum data bytes is 16.
76 105
 
106
+                   This setting is ignored when match+mask is set.
107
+
108
+                   Using offset+data you can check any bytes in the
109
+                   incoming EMM packet.
110
+
77 111
 Filter processing
78 112
 =================
79 113
 Filters are processed one by one until "accept" or "reject" filter matches.
@@ -83,32 +117,33 @@ by "accept_all" or "reject_all" is returned.
83 117
 Example filters
84 118
 ===============
85 119
 
120
+    Accept Bulcrypt EMMs that are for particular card (or card group). The card
121
+    hex serial number is "aa bb cc dd".
122
+
123
+       tsdecrypt ... \
124
+         --emm \
125
+         --emm-filter reject_all \
126
+         --emm-filter "accept/name=Bulcrypt_EMMs,match=82 aa bb cc dd,mask=ff ff ff ff f0" \
127
+         --emm-filter "accept/name=Bulcrypt_EMMs,match=84 aa bb cc dd,mask=ff ff ff ff f0" \
128
+         --emm-filter "accept/name=Bulcrypt_EMMs,match=85 aa bb cc dd,mask=ff ff ff ff f0"
129
+
86 130
   Bulcrypt white list example:
87 131
     Accept all EMMs except those starting with 8a 70 b4, 8b 70 b4, 8c or 8d.
88 132
 
89 133
        tsdecrypt ... \
134
+         --emm \
90 135
          --emm-filter accept_all \
91 136
          --emm-filter "reject/name=Bulcrypt_unknown_EMMs,offset=0,data=8a 70 b4" \
92 137
          --emm-filter "reject/name=Bulcrypt_unknown_EMMs,offset=0,data=8b 70 b4" \
93 138
          --emm-filter "reject/name=Some EMM,data=0x8c" \
94
-         --emm-filter reject/data=0x8d \
139
+         --emm-filter "reject/data=0x8d"
95 140
 
96 141
   Bulcrypt black list example:
97 142
     Reject all EMMs that don't start with 82, 84 or 85.
98 143
 
99
-      tsdecrypt ... \
100
-        --emm-filter reject_all \
101
-        --emm-filter accept/name=Bulcrypt_EMMs,offset=0,data=82 \
102
-        --emm-filter accept/name=Bulcrypt_EMMs,data=84 \
103
-        --emm-filter accept/name=Bulcrypt_EMMs,data=85 \
104
-
105
-  Example combination that builds white/black list.
106
-    Accept EMMs that start with 8a and 8b also accept EMMs which have
107
-    70 b4 at offset 1, and reject everything else.
108
-
109
-      tsdecrypt ... \
110
-        --emm-filter accept_all \
111
-        --emm-filter reject/name=Bulcrypt_unknown_EMMs,data=8a \
112
-        --emm-filter reject/name=Bulcrypt_unknown_EMMs,data=8b \
113
-        --emm-filter reject_all \
114
-        --emm-filter "accept/name=Bulcrypt_EMMs_with_correct_size,offset=1,data=70 b4" \
144
+       tsdecrypt ... \
145
+         --emm \
146
+         --emm-filter reject_all \
147
+         --emm-filter accept/name=Bulcrypt_EMMs,offset=0,data=82 \
148
+         --emm-filter accept/name=Bulcrypt_EMMs,data=84 \
149
+         --emm-filter accept/name=Bulcrypt_EMMs,data=85 \

+ 11
- 4
data.h View File

@@ -201,11 +201,18 @@ enum filter_action {
201 201
 	FILTER_REJECT     = 4,
202 202
 };
203 203
 
204
+enum filter_type {
205
+	FILTER_TYPE_DATA = 0,				// Compare data at offset X
206
+	FILTER_TYPE_MASK = 1,				// Compare data + mask
207
+};
208
+
204 209
 struct filter {
205
-	enum filter_action action;			// If set to 1, the filter is negative else the filter is positive
206
-	uint8_t		offset;					// Offset into EMM
207
-	uint8_t		data_len;				// Filter length
208
-	uint8_t		data[MAX_FILTER_LEN];	// Filter bytes
210
+	enum filter_action action;
211
+	enum filter_type type;
212
+	uint8_t		offset;					// Offset into EMM, if offset == 255 then mask exists, ignore the offset
213
+	uint8_t		filter_len;				// Filter length
214
+	uint8_t		data[MAX_FILTER_LEN];	// Data | Matched bytes
215
+	uint8_t		mask[MAX_FILTER_LEN];	// Mask bytes
209 216
 	char		name[MAX_FILTER_NAME];	// Filter name (default: NO_NAME)
210 217
 };
211 218
 

+ 143
- 53
filter.c View File

@@ -18,32 +18,92 @@
18 18
 #include "data.h"
19 19
 #include "filter.h"
20 20
 
21
+struct filter_actions_map {
22
+	const char			*token;
23
+	bool				last;
24
+	enum filter_action	type;
25
+};
26
+
27
+static struct filter_actions_map action_tokens[] = {
28
+	{ "accept_all", true,  FILTER_ACCEPT_ALL },
29
+	{ "acceptall",  true,  FILTER_ACCEPT_ALL },
30
+	{ "reject_all", true,  FILTER_REJECT_ALL },
31
+	{ "rejectall",  true,  FILTER_REJECT_ALL },
32
+	{ "accept",     false, FILTER_ACCEPT     },
33
+	{ "reject",     false, FILTER_REJECT     },
34
+	{ NULL,         true,  FILTER_NO_MATCH   },
35
+};
36
+
37
+enum filter_token { T_UNKNOWN, T_NAME, T_OFFSET, T_DATA, T_MATCH, T_MASK };
38
+
39
+struct filter_data_map {
40
+	const char			*token;
41
+	enum filter_token	type;
42
+};
43
+
44
+static struct filter_data_map data_tokens[] = {
45
+	{ "name",    T_NAME    },
46
+	{ "ofs",     T_OFFSET  },
47
+	{ "offset",  T_OFFSET  },
48
+	{ "data",    T_DATA    },
49
+	{ "match",   T_MATCH   },
50
+	{ "mask",    T_MASK    },
51
+	{ NULL,      T_UNKNOWN },
52
+};
53
+
54
+int parse_hex(char *data, uint8_t *output, uint8_t *output_len, uint8_t output_size) {
55
+	int i, len = strlen(data), consumed = 0;
56
+	uint8_t local_output_len = 0;
57
+	if (!output_len)
58
+		output_len = &local_output_len;
59
+	for (i = 0; i < len; i++) { // Parse data (01 02 03 04 ...)
60
+		char ch = toupper(data[i]);
61
+		// Skip 0x prefixes
62
+		if (i + 1 < len && ch == '0' && toupper(data[i + 1]) == 'X')
63
+			continue;
64
+		if (!isxdigit(ch))
65
+			continue;
66
+		ch -= ch > 64 ? 55 : 48; // hex2dec
67
+		if (consumed % 2 == 0) {
68
+			output[*output_len] = 0; // Reset
69
+			output[*output_len] += ch << 4;
70
+		} else {
71
+			output[*output_len] += ch;
72
+			(*output_len)++;
73
+			if (*output_len + 1 >= output_size) {
74
+				fprintf(stderr, "WARN : Too much filter data (max %d bytes), ignoring last bytes: %s\n",
75
+					output_size, data + i + 2);
76
+				break;
77
+			}
78
+		}
79
+		consumed++;
80
+	}
81
+	return *output_len;
82
+}
83
+
21 84
 int filter_parse(char *filter_def, struct filter *filter) {
22
-	int i, j, k, ret = 0;
85
+	int j, k, ret = 0;
23 86
 	char *str1, *saveptr1;
24 87
 	char *f = strdup(filter_def);
25 88
 	memset(filter, 0, sizeof(struct filter));
89
+	memset(filter->mask, 0xff, sizeof(filter->mask));
26 90
 	snprintf(filter->name, sizeof(filter->name), "NONAME");
27 91
 	for (j = 1, str1 = f; ; j++, str1 = NULL) {
28 92
 		char *token = strtok_r(str1, "/", &saveptr1);
29 93
 		if (token == NULL)
30 94
 			break;
31 95
 		if (j == 1) { // First token, the command
32
-			if (strstr(token, "accept_all") == token || strstr(token, "acceptall") == token) {
33
-				filter->action = FILTER_ACCEPT_ALL;
34
-				ret = 1;
35
-				goto OUT; // Other tokens are not needed
36
-			} else if (strstr(token, "reject_all") == token || strstr(token, "rejectall") == token) {
37
-				filter->action = FILTER_REJECT_ALL;
38
-				ret = 1;
39
-				goto OUT; // Other tokens are not needed
40
-			} else if (strstr(token, "accept") == token) {
41
-				filter->action = FILTER_ACCEPT;
42
-				continue; // Continue looking for other tokes
43
-			} else if (strstr(token, "reject") == token) {
44
-				filter->action = FILTER_REJECT;
45
-				continue; // Continue looking for other tokes
46
-			} else {
96
+			struct filter_actions_map *m;
97
+			for (m = action_tokens; m->token; m++) {
98
+				if (strstr(token, m->token) == token) {
99
+					filter->action = m->type;
100
+					ret = 1;
101
+					if (m->last)
102
+						goto OUT; // Other tokens are not needed
103
+					break;
104
+				}
105
+			}
106
+			if (filter->action == FILTER_NO_MATCH) {
47 107
 				fprintf(stderr, "ERROR: Unknown filter command: %s\n", token);
48 108
 				ret = 0;
49 109
 				goto OUT; // Other tokens are not needed
@@ -55,37 +115,36 @@ int filter_parse(char *filter_def, struct filter *filter) {
55 115
 				char *token2 = strtok_r(str2, ",", &saveptr2);
56 116
 				if (token2 == NULL)
57 117
 					break;
58
-				char *eq = strrchr(token2, '=');
59
-				if (eq) {
60
-					if (strstr(token2, "ofs") == token2 || strstr(token2, "offset") == token2) {
61
-						filter->offset = strtoul(eq + 1, NULL, 0);
62
-					} else if (strstr(token2, "name") == token2) {
63
-						snprintf(filter->name, sizeof(filter->name), "%s", eq + 1);
64
-					} else if (strstr(token2, "data") == token2) {
65
-						char *data = eq + 1;
66
-						int len = strlen(data), consumed = 0;
67
-						for (i = 0; i < len; i++) { // Parse data (01 02 03 04 ...)
68
-							char ch = toupper(data[i]);
69
-							// Skip 0x prefixes
70
-							if (i + 1 < len && ch == '0' && toupper(data[i + 1]) == 'X')
71
-								continue;
72
-							if (!isxdigit(ch))
73
-								continue;
74
-							ch -= ch > 64 ? 55 : 48; // hex2dec
75
-							if (consumed % 2 == 0) {
76
-								filter->data[filter->data_len  ] += ch << 4;
77
-							} else {
78
-								filter->data[filter->data_len++] += ch;
79
-								if (filter->data_len + 1 >= MAX_FILTER_LEN) {
80
-									fprintf(stderr, "WARN : Too much filter data (max %d bytes), ignoring last bytes: %s\n",
81
-										MAX_FILTER_LEN, data + i + 2);
82
-									break;
83
-								}
84
-							}
85
-							consumed++;
118
+				char *tokdata = strrchr(token2, '=');
119
+				if (tokdata) {
120
+					tokdata++; // Skip =
121
+					struct filter_data_map *m;
122
+					enum filter_token data_type = T_UNKNOWN;
123
+					for (m = data_tokens; m->token; m++) {
124
+						if (strstr(token2, m->token) == token2) {
125
+							data_type = m->type;
126
+							break;
86 127
 						}
87
-						ret = filter->data_len;
88
-					} else {
128
+					}
129
+					switch (data_type) {
130
+					case T_NAME:
131
+						snprintf(filter->name, sizeof(filter->name), "%s", tokdata);
132
+						break;
133
+					case T_OFFSET:
134
+						filter->offset = strtoul(tokdata, NULL, 0);
135
+						break;
136
+					case T_DATA:
137
+						ret = parse_hex(tokdata, filter->data, &filter->filter_len, MAX_FILTER_LEN);
138
+						break;
139
+					case T_MATCH:
140
+						filter->type = FILTER_TYPE_MASK;
141
+						ret = parse_hex(tokdata, filter->data, &filter->filter_len, MAX_FILTER_LEN);
142
+						break;
143
+					case T_MASK:
144
+						filter->type = FILTER_TYPE_MASK;
145
+						ret = parse_hex(tokdata, filter->mask, NULL, MAX_FILTER_LEN);
146
+						break;
147
+					default:
89 148
 						fprintf(stderr, "WARN : Unknown filter setting: %s\n", token2);
90 149
 					}
91 150
 				}
@@ -108,9 +167,17 @@ void filter_dump(struct filter *filter, char *buffer, unsigned int buf_len) {
108 167
 	if (filter->action == FILTER_ACCEPT || filter->action == FILTER_REJECT)
109 168
 		pos += snprintf(buffer + pos, buf_len - pos, " Name: %-20s", filter->name);
110 169
 	if (filter->action == FILTER_ACCEPT || filter->action == FILTER_REJECT) {
111
-		char tmp[MAX_FILTER_LEN * 6];
112
-		ts_hex_dump_buf(tmp, sizeof(tmp), filter->data, filter->data_len, 0);
113
-		pos += snprintf(buffer + pos, buf_len - pos, " Offset: %2d Data: %s", filter->offset, tmp);
170
+		char tmp_data[MAX_FILTER_LEN * 6], tmp_mask[MAX_FILTER_LEN * 6];
171
+		ts_hex_dump_buf(tmp_data, sizeof(tmp_data), filter->data, filter->filter_len, 0);
172
+		switch (filter->type) {
173
+		case FILTER_TYPE_DATA:
174
+			pos += snprintf(buffer + pos, buf_len - pos, " Offset: %2d Data: %s", filter->offset, tmp_data);
175
+			break;
176
+		case FILTER_TYPE_MASK:
177
+			ts_hex_dump_buf(tmp_mask, sizeof(tmp_mask), filter->mask, filter->filter_len, 0);
178
+			pos += snprintf(buffer + pos, buf_len - pos, " Match: %s Mask: %s", tmp_data, tmp_mask);
179
+			break;
180
+		} // switch (filter->type)
114 181
 	}
115 182
 }
116 183
 
@@ -118,10 +185,33 @@ static enum filter_action filter_match(uint8_t *data, unsigned int data_len, str
118 185
 	if (filter->action == FILTER_ACCEPT_ALL || filter->action == FILTER_REJECT_ALL)
119 186
 		return filter->action;
120 187
 	if (filter->action == FILTER_ACCEPT || filter->action == FILTER_REJECT) {
121
-		if (filter->data_len + filter->offset > data_len)
122
-			return FILTER_NO_MATCH;
123
-		if (memcmp(data + filter->offset, filter->data, filter->data_len) == 0)
124
-			return filter->action;
188
+		switch (filter->type) {
189
+		case FILTER_TYPE_DATA: {
190
+			if (filter->filter_len + filter->offset > data_len)
191
+				return FILTER_NO_MATCH;
192
+			if (memcmp(data + filter->offset, filter->data, filter->filter_len) == 0)
193
+				return filter->action;
194
+			break;
195
+		}
196
+		case FILTER_TYPE_MASK: {
197
+			if ((unsigned int)filter->filter_len + 3 > data_len)
198
+				return FILTER_NO_MATCH;
199
+			int matched = 0;
200
+			// Check data[0] against filter->data[0]
201
+			if ((data[0] & filter->mask[0]) == (filter->data[0] & filter->mask[0])) {
202
+				matched++;
203
+				int i;
204
+				for (i = 1; i < filter->filter_len; i++) {
205
+					// Check data[3...] against filter->data[1...]
206
+					if ((data[i + 2] & filter->mask[i]) == (filter->data[i] & filter->mask[i]))
207
+						matched++;
208
+				}
209
+			}
210
+			if (matched == filter->filter_len)
211
+				return filter->action;
212
+			break;
213
+		}
214
+		} // switch (filter->type)
125 215
 	}
126 216
 	return FILTER_NO_MATCH;
127 217
 }

Loading…
Cancel
Save