1 /*
2 * tvheadend, constant code word interface
3 * Copyright (C) 2014 Jaroslav Kysela
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <ctype.h>
20 #include "tvheadend.h"
21 #include "caclient.h"
22 #include "service.h"
23 #include "input.h"
24
25 /**
26 *
27 */
28 typedef struct constcw_service {
29 th_descrambler_t;
30 LIST_ENTRY(constcw_service) cs_link;
31 } constcw_service_t;
32
33 /**
34 *
35 */
36 typedef struct constcw {
37 caclient_t;
38
39 /* From configuration */
40 uint16_t ccw_caid; /* CA ID */
41 uint32_t ccw_providerid; /* CA provider ID */
42 uint16_t ccw_tsid; /* transponder ID */
43 uint16_t ccw_sid; /* service ID */
44 uint8_t ccw_key_even[16]; /* DES or AES key */
45 uint8_t ccw_key_odd [16]; /* DES or AES key */
46 LIST_HEAD(, constcw_service) ccw_services; /* active services */
47 } constcw_t;
48
49 /*
50 *
51 */
52 static const char *
constcw_name(constcw_t * ccw)53 constcw_name(constcw_t *ccw)
54 {
55 return idnode_get_title(&ccw->cac_id, NULL);
56 }
57
58 /**
59 *
60 */
61 static int
constcw_key_size(caclient_t * cac)62 constcw_key_size(caclient_t *cac)
63 {
64 constcw_t *ccw = (constcw_t *)cac;
65
66 if (idnode_is_instance(&ccw->cac_id, &caclient_ccw_des_class))
67 return 8;
68 return 16;
69 }
70
71 /*
72 *
73 */
74 static int
constcw_ecm_reset(th_descrambler_t * th)75 constcw_ecm_reset(th_descrambler_t *th)
76 {
77 return 1;
78 }
79
80 /**
81 * s_stream_mutex is held
82 */
83 static void
constcw_service_destroy(th_descrambler_t * td)84 constcw_service_destroy(th_descrambler_t *td)
85 {
86 constcw_service_t *ct = (constcw_service_t *)td;
87
88 LIST_REMOVE(td, td_service_link);
89 LIST_REMOVE(ct, cs_link);
90 free(ct->td_nicename);
91 free(ct);
92 }
93
94 /**
95 * global_lock is held. Not that we care about that, but either way, it is.
96 */
97 static void
constcw_service_start(caclient_t * cac,service_t * t)98 constcw_service_start(caclient_t *cac, service_t *t)
99 {
100 constcw_t *ccw = (constcw_t *)cac;
101 constcw_service_t *ct;
102 th_descrambler_t *td;
103 elementary_stream_t *st;
104 mpegts_service_t *mt;
105 char buf[128];
106 caid_t *c;
107
108 extern const idclass_t mpegts_service_class;
109 if (!idnode_is_instance(&t->s_id, &mpegts_service_class))
110 return;
111 mt = (mpegts_service_t *)t;
112
113 if (mt->s_dvb_forcecaid && mt->s_dvb_forcecaid != ccw->ccw_caid)
114 return;
115
116 if (mt->s_dvb_service_id != ccw->ccw_sid)
117 return;
118
119 if (mt->s_dvb_mux->mm_tsid != ccw->ccw_tsid)
120 return;
121
122 LIST_FOREACH(ct, &ccw->ccw_services, cs_link)
123 if (ct->td_service == t)
124 break;
125 if (ct)
126 return;
127
128 if (!mt->s_dvb_forcecaid) {
129 pthread_mutex_lock(&t->s_stream_mutex);
130 TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
131 LIST_FOREACH(c, &st->es_caids, link) {
132 if (c->use && c->caid == ccw->ccw_caid &&
133 c->providerid == ccw->ccw_providerid)
134 break;
135 }
136 if (c) break;
137 }
138 pthread_mutex_unlock(&t->s_stream_mutex);
139 if (st == NULL)
140 return;
141 }
142
143 ct = calloc(1, sizeof(constcw_service_t));
144 td = (th_descrambler_t *)ct;
145 snprintf(buf, sizeof(buf), "constcw-%s", constcw_name(ccw));
146 td->td_nicename = strdup(buf);
147 td->td_service = t;
148 td->td_stop = constcw_service_destroy;
149 td->td_ecm_reset = constcw_ecm_reset;
150 LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
151
152 LIST_INSERT_HEAD(&ccw->ccw_services, ct, cs_link);
153
154 descrambler_keys(td, constcw_key_size(cac) == 8 ?
155 DESCRAMBLER_DES : DESCRAMBLER_AES,
156 ccw->ccw_key_even, ccw->ccw_key_odd);
157 }
158
159
160 /**
161 *
162 */
163 static void
constcw_free(caclient_t * cac)164 constcw_free(caclient_t *cac)
165 {
166 constcw_t *ccw = (constcw_t *)cac;
167 constcw_service_t *ct;
168
169 while((ct = LIST_FIRST(&ccw->ccw_services)) != NULL) {
170 service_t *t = ct->td_service;
171 pthread_mutex_lock(&t->s_stream_mutex);
172 constcw_service_destroy((th_descrambler_t *)ct);
173 pthread_mutex_unlock(&t->s_stream_mutex);
174 }
175 }
176
177 /**
178 *
179 */
180 static int
nibble(char c)181 nibble(char c)
182 {
183 switch(c) {
184 case '0' ... '9':
185 return c - '0';
186 case 'a' ... 'f':
187 return c - 'a' + 10;
188 case 'A' ... 'F':
189 return c - 'A' + 10;
190 default:
191 return 0;
192 }
193 }
194
195 /**
196 *
197 */
198 static void
constcw_conf_changed(caclient_t * cac)199 constcw_conf_changed(caclient_t *cac)
200 {
201 if (cac->cac_enabled) {
202 caclient_set_status(cac, CACLIENT_STATUS_CONNECTED);
203 } else {
204 caclient_set_status(cac, CACLIENT_STATUS_NONE);
205 }
206 }
207
208 /**
209 *
210 */
211 static int
constcw_class_key_set(void * o,const void * v,uint8_t * dkey)212 constcw_class_key_set(void *o, const void *v, uint8_t *dkey)
213 {
214 const char *s = v ?: "";
215 int keysize = constcw_key_size(o);
216 char key[16];
217 int i, u, l;
218
219 for(i = 0; i < keysize; i++) {
220 while(*s != 0 && !isxdigit(*s)) s++;
221 u = *s ? nibble(*s++) : 0;
222 while(*s != 0 && !isxdigit(*s)) s++;
223 l = *s ? nibble(*s++) : 0;
224 key[i] = (u << 4) | l;
225 }
226 i = memcmp(dkey, key, keysize) != 0;
227 memcpy(dkey, key, keysize);
228 return i;
229 }
230
231 static int
constcw_class_key_even_set(void * o,const void * v)232 constcw_class_key_even_set(void *o, const void *v)
233 {
234 constcw_t *ccw = o;
235 return constcw_class_key_set(o, v, ccw->ccw_key_even);
236 }
237
238 static int
constcw_class_key_odd_set(void * o,const void * v)239 constcw_class_key_odd_set(void *o, const void *v)
240 {
241 constcw_t *ccw = o;
242 return constcw_class_key_set(o, v, ccw->ccw_key_odd);
243 }
244
245 static const void *
constcw_class_key_get(void * o,const uint8_t * key)246 constcw_class_key_get(void *o, const uint8_t *key)
247 {
248 if (constcw_key_size(o) == 8) {
249 snprintf(prop_sbuf, PROP_SBUF_LEN,
250 "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
251 key[0x0], key[0x1], key[0x2], key[0x3],
252 key[0x4], key[0x5], key[0x6], key[0x7]);
253 } else {
254 snprintf(prop_sbuf, PROP_SBUF_LEN,
255 "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:"
256 "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
257 key[0x0], key[0x1], key[0x2], key[0x3],
258 key[0x4], key[0x5], key[0x6], key[0x7],
259 key[0x8], key[0x9], key[0xa], key[0xb],
260 key[0xc], key[0xd], key[0xe], key[0xf]);
261 }
262 return &prop_sbuf_ptr;
263 }
264
265 static const void *
constcw_class_key_even_get(void * o)266 constcw_class_key_even_get(void *o)
267 {
268 constcw_t *ccw = o;
269 return constcw_class_key_get(o, ccw->ccw_key_even);
270 }
271
272 static const void *
constcw_class_key_odd_get(void * o)273 constcw_class_key_odd_get(void *o)
274 {
275 constcw_t *ccw = o;
276 return constcw_class_key_get(o, ccw->ccw_key_odd);
277 }
278
279 const idclass_t caclient_ccw_des_class =
280 {
281 .ic_super = &caclient_class,
282 .ic_class = "caclient_ccw_des",
283 .ic_caption = N_("DES Constant Code Word"),
284 .ic_properties = (const property_t[]){
285 {
286 .type = PT_U16,
287 .id = "caid",
288 .name = N_("CA ID"),
289 .desc = N_("Conditional Access Identification."),
290 .off = offsetof(constcw_t, ccw_caid),
291 .opts = PO_HEXA,
292 .def.u16 = 0x2600
293 },
294 {
295 .type = PT_U32,
296 .id = "providerid",
297 .name = N_("Provider ID"),
298 .desc = N_("The provider's ID."),
299 .off = offsetof(constcw_t, ccw_providerid),
300 .opts = PO_HEXA,
301 .def.u32 = 0
302 },
303 {
304 .type = PT_U16,
305 .id = "tsid",
306 .name = N_("Transponder ID"),
307 .desc = N_("The transponder ID."),
308 .off = offsetof(constcw_t, ccw_tsid),
309 .opts = PO_HEXA,
310 .def.u16 = 1,
311 },
312 {
313 .type = PT_U16,
314 .id = "sid",
315 .name = N_("Service ID"),
316 .desc = N_("The service ID."),
317 .off = offsetof(constcw_t, ccw_sid),
318 .opts = PO_HEXA,
319 .def.u16 = 1,
320 },
321 {
322 .type = PT_STR,
323 .id = "key_even",
324 .name = N_("Even key"),
325 .desc = N_("Even key."),
326 .set = constcw_class_key_even_set,
327 .get = constcw_class_key_even_get,
328 .opts = PO_PASSWORD,
329 .def.s = "00:00:00:00:00:00:00:00",
330 },
331 {
332 .type = PT_STR,
333 .id = "key_odd",
334 .name = N_("Odd key"),
335 .desc = N_("Odd key."),
336 .set = constcw_class_key_odd_set,
337 .get = constcw_class_key_odd_get,
338 .opts = PO_PASSWORD,
339 .def.s = "00:00:00:00:00:00:00:00",
340 },
341 { }
342 }
343 };
344
345 const idclass_t caclient_ccw_aes_class =
346 {
347 .ic_super = &caclient_class,
348 .ic_class = "caclient_ccw_aes",
349 .ic_caption = N_("AES Constant Code Word"),
350 .ic_properties = (const property_t[]){
351 {
352 .type = PT_U16,
353 .id = "caid",
354 .name = N_("CA ID"),
355 .desc = N_("Conditional Access Identification."),
356 .off = offsetof(constcw_t, ccw_caid),
357 .opts = PO_HEXA,
358 .def.u16 = 0x2600,
359 },
360 {
361 .type = PT_U32,
362 .id = "providerid",
363 .name = N_("Provider ID"),
364 .desc = N_("The provider's ID."),
365 .off = offsetof(constcw_t, ccw_providerid),
366 .opts = PO_HEXA,
367 .def.u32 = 0
368 },
369 {
370 .type = PT_U16,
371 .id = "tsid",
372 .name = N_("Transponder ID"),
373 .desc = N_("The transponder ID."),
374 .off = offsetof(constcw_t, ccw_tsid),
375 .opts = PO_HEXA,
376 .def.u16 = 1,
377 },
378 {
379 .type = PT_U16,
380 .id = "sid",
381 .name = N_("Service ID"),
382 .desc = N_("The service ID"),
383 .off = offsetof(constcw_t, ccw_sid),
384 .opts = PO_HEXA,
385 .def.u16 = 1,
386 },
387 {
388 .type = PT_STR,
389 .id = "key_even",
390 .name = N_("Even key"),
391 .desc = N_("Even key."),
392 .set = constcw_class_key_even_set,
393 .get = constcw_class_key_even_get,
394 .opts = PO_PASSWORD,
395 .def.s = "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
396 },
397 {
398 .type = PT_STR,
399 .id = "key_odd",
400 .name = N_("Odd key"),
401 .desc = N_("Odd key."),
402 .set = constcw_class_key_odd_set,
403 .get = constcw_class_key_odd_get,
404 .opts = PO_PASSWORD,
405 .def.s = "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
406 },
407 { }
408 }
409 };
410
411 /*
412 *
413 */
constcw_create(void)414 caclient_t *constcw_create(void)
415 {
416 constcw_t *ccw = calloc(1, sizeof(*ccw));
417
418 ccw->cac_free = constcw_free;
419 ccw->cac_start = constcw_service_start;
420 ccw->cac_conf_changed = constcw_conf_changed;
421 return (caclient_t *)ccw;
422 }
423