1 /*
2 * Tvheadend - conditional access key client superclass
3 * Copyright (C) 2014 Jaroslav Kysela <perex@perex.cz>
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 "tvheadend.h"
20 #include "settings.h"
21 #include "caclient.h"
22
23 const idclass_t *caclient_classes[] = {
24 #if ENABLE_CWC
25 &caclient_cwc_class,
26 #endif
27 #if ENABLE_CAPMT
28 &caclient_capmt_class,
29 #endif
30 #if ENABLE_CONSTCW
31 &caclient_ccw_des_class,
32 &caclient_ccw_aes_class,
33 #endif
34 NULL
35 };
36
37 struct caclient_entry_queue caclients;
38 static pthread_mutex_t caclients_mutex;
39
40 static const idclass_t *
caclient_class_find(const char * name)41 caclient_class_find(const char *name)
42 {
43 const idclass_t **r;
44 for (r = caclient_classes; *r; r++) {
45 if (strcmp((*r)->ic_class, name) == 0)
46 return *r;
47 }
48 return NULL;
49 }
50
51 static void
caclient_reindex(void)52 caclient_reindex(void)
53 {
54 caclient_t *cac;
55 int i = 1;
56
57 TAILQ_FOREACH(cac, &caclients, cac_link)
58 cac->cac_save = 0;
59 TAILQ_FOREACH(cac, &caclients, cac_link) {
60 if (cac->cac_index != i) {
61 cac->cac_index = i;
62 cac->cac_save = 1;
63 }
64 i++;
65 }
66 TAILQ_FOREACH(cac, &caclients, cac_link)
67 if (cac->cac_save) {
68 cac->cac_save = 0;
69 idnode_changed((idnode_t *)cac);
70 }
71 }
72
73 static int
cac_cmp(caclient_t * a,caclient_t * b)74 cac_cmp(caclient_t *a, caclient_t *b)
75 {
76 return a->cac_index - b->cac_index;
77 }
78
79 caclient_t *
caclient_create(const char * uuid,htsmsg_t * conf,int save)80 caclient_create
81 (const char *uuid, htsmsg_t *conf, int save)
82 {
83 caclient_t *cac = NULL;
84 const idclass_t *c = NULL;
85 const char *s;
86
87 lock_assert(&global_lock);
88
89 if ((s = htsmsg_get_str(conf, "class")) != NULL)
90 c = caclient_class_find(s);
91 if (c == NULL) {
92 tvherror(LS_CACLIENT, "wrong class %s!", s);
93 abort();
94 }
95 #if ENABLE_CWC
96 if (c == &caclient_cwc_class)
97 cac = cwc_create();
98 #endif
99 #if ENABLE_CAPMT
100 if (c == &caclient_capmt_class)
101 cac = capmt_create();
102 #endif
103 #if ENABLE_CONSTCW
104 if (c == &caclient_ccw_des_class)
105 cac = constcw_create();
106 if (c == &caclient_ccw_aes_class)
107 cac = constcw_create();
108 #endif
109 if (cac == NULL) {
110 tvherror(LS_CACLIENT, "CA Client class %s is not available!", s);
111 return NULL;
112 }
113 if (idnode_insert(&cac->cac_id, uuid, c, 0)) {
114 if (uuid)
115 tvherror(LS_CACLIENT, "invalid uuid '%s'", uuid);
116 free(cac);
117 return NULL;
118 }
119 if (conf)
120 idnode_load(&cac->cac_id, conf);
121 pthread_mutex_lock(&caclients_mutex);
122 if (cac->cac_index) {
123 TAILQ_INSERT_SORTED(&caclients, cac, cac_link, cac_cmp);
124 } else {
125 TAILQ_INSERT_TAIL(&caclients, cac, cac_link);
126 caclient_reindex();
127 }
128 pthread_mutex_unlock(&caclients_mutex);
129 if (save)
130 idnode_changed((idnode_t *)cac);
131 cac->cac_conf_changed(cac);
132 return cac;
133 }
134
135 static void
caclient_delete(caclient_t * cac,int delconf)136 caclient_delete(caclient_t *cac, int delconf)
137 {
138 char ubuf[UUID_HEX_SIZE];
139
140 idnode_save_check(&cac->cac_id, delconf);
141 cac->cac_enabled = 0;
142 cac->cac_conf_changed(cac);
143 if (delconf)
144 hts_settings_remove("caclient/%s", idnode_uuid_as_str(&cac->cac_id, ubuf));
145 pthread_mutex_lock(&caclients_mutex);
146 TAILQ_REMOVE(&caclients, cac, cac_link);
147 pthread_mutex_unlock(&caclients_mutex);
148 idnode_unlink(&cac->cac_id);
149 if (cac->cac_free)
150 cac->cac_free(cac);
151 free(cac->cac_name);
152 free(cac->cac_comment);
153 free(cac);
154 }
155
156 static void
caclient_class_changed(idnode_t * in)157 caclient_class_changed ( idnode_t *in )
158 {
159 caclient_t *cac = (caclient_t *)in;
160 cac->cac_conf_changed(cac);
161 }
162
163 static htsmsg_t *
caclient_class_save(idnode_t * in,char * filename,size_t fsize)164 caclient_class_save ( idnode_t *in, char *filename, size_t fsize )
165 {
166 char ubuf[UUID_HEX_SIZE];
167 htsmsg_t *c = htsmsg_create_map();
168 idnode_save(in, c);
169 snprintf(filename, fsize, "caclient/%s", idnode_uuid_as_str(in, ubuf));
170 return c;
171 }
172
173 static const char *
caclient_class_get_title(idnode_t * in,const char * lang)174 caclient_class_get_title ( idnode_t *in, const char *lang )
175 {
176 caclient_t *cac = (caclient_t *)in;
177 if (cac->cac_name && cac->cac_name[0])
178 return cac->cac_name;
179 snprintf(prop_sbuf, PROP_SBUF_LEN,
180 tvh_gettext_lang(lang, N_("CA client %i")), cac->cac_index);
181 return prop_sbuf;
182 }
183
184 static void
caclient_class_delete(idnode_t * self)185 caclient_class_delete(idnode_t *self)
186 {
187 caclient_t *cac = (caclient_t *)self;
188 caclient_delete(cac, 1);
189 }
190
191 static void
caclient_class_moveup(idnode_t * self)192 caclient_class_moveup(idnode_t *self)
193 {
194 caclient_t *cac = (caclient_t *)self;
195 caclient_t *prev = TAILQ_PREV(cac, caclient_entry_queue, cac_link);
196 if (prev) {
197 TAILQ_REMOVE(&caclients, cac, cac_link);
198 TAILQ_INSERT_BEFORE(prev, cac, cac_link);
199 caclient_reindex();
200 }
201 }
202
203 static void
caclient_class_movedown(idnode_t * self)204 caclient_class_movedown(idnode_t *self)
205 {
206 caclient_t *cac = (caclient_t *)self;
207 caclient_t *next = TAILQ_NEXT(cac, cac_link);
208 if (next) {
209 TAILQ_REMOVE(&caclients, cac, cac_link);
210 TAILQ_INSERT_AFTER(&caclients, next, cac, cac_link);
211 caclient_reindex();
212 }
213 }
214
215 static const void *
caclient_class_class_get(void * o)216 caclient_class_class_get(void *o)
217 {
218 caclient_t *cac = o;
219 static const char *ret;
220 ret = cac->cac_id.in_class->ic_class;
221 return &ret;
222 }
223
224 static int
caclient_class_class_set(void * o,const void * v)225 caclient_class_class_set(void *o, const void *v)
226 {
227 /* just ignore, create fcn does the right job */
228 return 0;
229 }
230
231 static const void *
caclient_class_status_get(void * o)232 caclient_class_status_get(void *o)
233 {
234 caclient_t *cac = o;
235 prop_ptr = caclient_get_status(cac);
236 return &prop_ptr;
237 }
238
239 CLASS_DOC(caclient)
240
241 const idclass_t caclient_class =
242 {
243 .ic_class = "caclient",
244 .ic_caption = N_("Conditional Access Client"),
245 .ic_changed = caclient_class_changed,
246 .ic_save = caclient_class_save,
247 .ic_event = "caclient",
248 .ic_doc = tvh_doc_caclient_class,
249 .ic_get_title = caclient_class_get_title,
250 .ic_delete = caclient_class_delete,
251 .ic_moveup = caclient_class_moveup,
252 .ic_movedown = caclient_class_movedown,
253 .ic_properties = (const property_t[]){
254 {
255 .type = PT_STR,
256 .id = "class",
257 .name = N_("Class"),
258 .opts = PO_RDONLY | PO_HIDDEN | PO_NOUI,
259 .get = caclient_class_class_get,
260 .set = caclient_class_class_set,
261 },
262 {
263 .type = PT_INT,
264 .id = "index",
265 .name = N_("Index"),
266 .opts = PO_RDONLY | PO_HIDDEN | PO_NOUI,
267 .off = offsetof(caclient_t, cac_index),
268 },
269 {
270 .type = PT_BOOL,
271 .id = "enabled",
272 .name = N_("Enabled"),
273 .desc = N_("Enable/Disable CA client."),
274 .off = offsetof(caclient_t, cac_enabled),
275 },
276 {
277 .type = PT_STR,
278 .id = "name",
279 .name = N_("Client name"),
280 .desc = N_("Name of the client."),
281 .off = offsetof(caclient_t, cac_name),
282 .notify = idnode_notify_title_changed,
283 },
284 {
285 .type = PT_STR,
286 .id = "comment",
287 .name = N_("Comment"),
288 .desc = N_("Free-form text field, enter whatever you like."),
289 .off = offsetof(caclient_t, cac_comment),
290 },
291 {
292 .type = PT_STR,
293 .id = "status",
294 .name = N_("Status"),
295 .get = caclient_class_status_get,
296 .opts = PO_RDONLY | PO_HIDDEN | PO_NOSAVE | PO_NOUI,
297 },
298 { }
299 }
300 };
301
302 void
caclient_start(struct service * t)303 caclient_start ( struct service *t )
304 {
305 caclient_t *cac;
306
307 pthread_mutex_lock(&caclients_mutex);
308 TAILQ_FOREACH(cac, &caclients, cac_link)
309 if (cac->cac_enabled)
310 cac->cac_start(cac, t);
311 pthread_mutex_unlock(&caclients_mutex);
312 #if ENABLE_TSDEBUG
313 tsdebugcw_service_start(t);
314 #endif
315 }
316
317 void
caclient_caid_update(struct mpegts_mux * mux,uint16_t caid,uint16_t pid,int valid)318 caclient_caid_update(struct mpegts_mux *mux, uint16_t caid, uint16_t pid, int valid)
319 {
320 caclient_t *cac;
321
322 lock_assert(&global_lock);
323
324 pthread_mutex_lock(&caclients_mutex);
325 TAILQ_FOREACH(cac, &caclients, cac_link)
326 if (cac->cac_caid_update && cac->cac_enabled)
327 cac->cac_caid_update(cac, mux, caid, pid, valid);
328 pthread_mutex_unlock(&caclients_mutex);
329 }
330
331 void
caclient_set_status(caclient_t * cac,caclient_status_t status)332 caclient_set_status(caclient_t *cac, caclient_status_t status)
333 {
334 if (cac->cac_status != status) {
335 cac->cac_status = status;
336 idnode_notify_changed(&cac->cac_id);
337 }
338 }
339
340 const char *
caclient_get_status(caclient_t * cac)341 caclient_get_status(caclient_t *cac)
342 {
343 switch (cac->cac_status) {
344 case CACLIENT_STATUS_NONE: return "caclientNone";
345 case CACLIENT_STATUS_READY: return "caclientReady";
346 case CACLIENT_STATUS_CONNECTED: return "caclientConnected";
347 case CACLIENT_STATUS_DISCONNECTED: return "caclientDisconnected";
348 default: return "caclientUnknown";
349 }
350 }
351
352 /*
353 * Initialize
354 */
355 void
caclient_init(void)356 caclient_init(void)
357 {
358 htsmsg_t *c, *e;
359 htsmsg_field_t *f;
360 const idclass_t **r;
361
362 pthread_mutex_init(&caclients_mutex, NULL);
363 TAILQ_INIT(&caclients);
364 idclass_register(&caclient_class);
365 #if ENABLE_TSDEBUG
366 tsdebugcw_init();
367 #endif
368
369 for (r = caclient_classes; *r; r++)
370 idclass_register(*r);
371
372 if (!(c = hts_settings_load("caclient")))
373 return;
374 HTSMSG_FOREACH(f, c) {
375 if (!(e = htsmsg_field_get_map(f)))
376 continue;
377 caclient_create(f->hmf_name, e, 0);
378 }
379 htsmsg_destroy(c);
380 }
381
382 void
caclient_done(void)383 caclient_done(void)
384 {
385 caclient_t *cac;
386
387 pthread_mutex_lock(&global_lock);
388 while ((cac = TAILQ_FIRST(&caclients)) != NULL)
389 caclient_delete(cac, 0);
390 pthread_mutex_unlock(&global_lock);
391 }
392