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