]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add cccam CA client
authorLuis Alves <ljalvs@gmail.com>
Mon, 15 May 2017 20:07:58 +0000 (21:07 +0100)
committerJaroslav Kysela <perex@perex.cz>
Fri, 19 May 2017 13:17:40 +0000 (15:17 +0200)
Makefile
Makefile.webui
configure
src/descrambler/caclient.c
src/descrambler/caclient.h
src/descrambler/cccam.c [new file with mode: 0644]
src/descrambler/descrambler.c
src/main.c
src/tvhlog.c
src/tvhlog.h

index 844edc46e72725fc902a9490dfd9152ff05ec037..c286c1b06c34ffb77b29b861f2c08732e092ba3a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -496,6 +496,12 @@ SRCS-CWC = \
 SRCS-${CONFIG_CWC} += $(SRCS-CWC)
 I18N-C += $(SRCS-CWC)
 
+# CCCAM
+SRCS-CCCAM = \
+       src/descrambler/cccam.c
+SRCS-${CONFIG_CCCAM} += $(SRCS-CCCAM)
+I18N-C += $(SRCS-CCCAM)
+
 # CAPMT
 SRCS-CAPMT = \
        src/descrambler/capmt.c
index 9bc3c1880992ee222f3ede12976bb869dec65623..eb7fbbbc897b7d2271fd65074a542e6798471c1a 100644 (file)
@@ -122,6 +122,7 @@ JAVASCRIPT += $(ROOTPATH)/app/cteditor.js
 JAVASCRIPT += $(ROOTPATH)/app/acleditor.js
 
 CACLIENT-$(CONFIG_CWC) = yes
+CACLIENT-$(CONFIG_CCCAM) = yes
 CACLIENT-$(CONFIG_CAPMT) = yes
 
 ifeq ($(CACLIENT-yes), yes)
index 12966718d446703226378ff26e04601b5cfe8a96..8e8a9a01d87b7344ed8b2c59980e28d6b89c9e5c 100755 (executable)
--- a/configure
+++ b/configure
@@ -20,6 +20,7 @@ OPTIONS=(
   "pie:yes"
   "ccdebug:no"
   "cwc:yes"
+  "cccam:yes"
   "capmt:yes"
   "constcw:yes"
   "linuxdvb:yes"
@@ -597,7 +598,7 @@ fi
 #
 # libdvbcsa, tvhcsa
 #
-if enabled cwc || enabled capmt || enabled constcw; then
+if enabled cwc || enabled cccam || enabled capmt || enabled constcw; then
   enable tvhcsa
   if enabled dvbcsa; then
     (check_cc_header "dvbcsa/dvbcsa" dvbcsa_h &&\
index eefd974599527aa3a14d491c5cc43a9825788adc..c16234f00273b792e8e324ec41865ef34d902cd9 100644 (file)
@@ -24,6 +24,9 @@ const idclass_t *caclient_classes[] = {
 #if ENABLE_CWC
   &caclient_cwc_class,
 #endif
+#if ENABLE_CCCAM
+  &caclient_cccam_class,
+#endif
 #if ENABLE_CAPMT
   &caclient_capmt_class,
 #endif
@@ -96,6 +99,10 @@ caclient_create
   if (c == &caclient_cwc_class)
     cac = cwc_create();
 #endif
+#if ENABLE_CCCAM
+  if (c == &caclient_cccam_class)
+    cac = cccam_create();
+#endif
 #if ENABLE_CAPMT
   if (c == &caclient_capmt_class)
     cac = capmt_create();
index 2c18f06e877fa08df927ec710297cd116747f4dd..0c96c8fd434e1ec004ed1a5452f092ec2298aa67 100644 (file)
@@ -26,6 +26,7 @@ struct mpegts_mux;
 
 extern const idclass_t caclient_class;
 extern const idclass_t caclient_cwc_class;
+extern const idclass_t caclient_cccam_class;
 extern const idclass_t caclient_capmt_class;
 extern const idclass_t caclient_ccw_des_class;
 extern const idclass_t caclient_ccw_aes_class;
@@ -81,6 +82,7 @@ void tsdebugcw_go(void);
 void tsdebugcw_init(void);
 
 caclient_t *cwc_create(void);
+caclient_t *cccam_create(void);
 caclient_t *capmt_create(void);
 caclient_t *constcw_create(void);
 caclient_t *tsdebugcw_create(void);
diff --git a/src/descrambler/cccam.c b/src/descrambler/cccam.c
new file mode 100644 (file)
index 0000000..a0c6faa
--- /dev/null
@@ -0,0 +1,1910 @@
+/*
+ *  tvheadend, CCCAM interface
+ *  Copyright (C) 2007 Andreas Ă–man
+ *  Copyright (C) 2017 Luis Alves
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <pthread.h>
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <openssl/sha.h>
+#include <endian.h>
+#include <semaphore.h>
+
+#include "tvheadend.h"
+#include "tcp.h"
+#include "caclient.h"
+#include "descrambler/caid.h"
+#include "descrambler/emm_reass.h"
+#include "atomic.h"
+#include "subscriptions.h"
+#include "service.h"
+#include "input.h"
+
+
+/**
+ *
+ */
+#define CCCAM_KEEPALIVE_INTERVAL 30
+#define CCCAM_ES_PIDS           8
+#define CCCAM_MAX_NOKS           3
+#define CCCAM_NETMSGSIZE 0x400
+
+
+typedef enum {
+  MSG_CLI_DATA, // client -> server
+  MSG_ECM_REQUEST, // client -> server
+  MSG_EMM_REQUEST, // client -> server
+  MSG_CARD_REMOVED = 4, // server -> client
+  MSG_CMD_05,
+  MSG_KEEPALIVE, // client -> server
+  MSG_NEW_CARD, // server -> client
+  MSG_SRV_DATA, // server -> client
+  MSG_CMD_0A = 0x0a,
+  MSG_CMD_0B = 0x0b,
+  MSG_CMD_0C = 0x0c, // CCcam 2.2.x fake client checks
+  MSG_CMD_0D = 0x0d, // "
+  MSG_CMD_0E = 0x0e, // "
+  MSG_NEW_CARD_SIDINFO = 0x0f,
+  MSG_SLEEPSEND = 0x80, //Sleepsend support
+       MSG_CACHE_PUSH = 0x81, //CacheEx Cache-Push In/Out
+       MSG_CACHE_FILTER = 0x82, //CacheEx Cache-Filter Request
+  MSG_ECM_NOK1 = 0xfe, // server -> client ecm queue full, card not found
+  MSG_ECM_NOK2 = 0xff, // server -> client
+  MSG_NO_HEADER = 0xffff
+} cccam_msg_type_t;
+
+typedef enum {
+  CCCAM_VERSION_2_0_11 = 0,
+  CCCAM_VERSION_2_1_1,
+  CCCAM_VERSION_2_1_2,
+  CCCAM_VERSION_2_1_3,
+  CCCAM_VERSION_2_1_4,
+  CCCAM_VERSION_2_2_0,
+  CCCAM_VERSION_2_2_1,
+  CCCAM_VERSION_2_3_0,
+  CCCAM_VERSION_COUNT,
+} cccam_version_t;
+
+static const char *cccam_version_str[CCCAM_VERSION_COUNT] = {
+  "2.0.11", "2.1.1", "2.1.2", "2.1.3",
+  "2.1.4", "2.2.0", "2.2.1", "2.3.0"
+};
+
+/**
+ *
+ */
+//TAILQ_HEAD(cccam_queue, cccam);
+LIST_HEAD(cccam_cards_list, cs_card_data);
+LIST_HEAD(cccam_service_list, cccam_service);
+TAILQ_HEAD(cccam_message_queue, cccam_message);
+//LIST_HEAD(ecm_section_list, ecm_section);
+
+/**
+ *
+ */
+typedef struct ecm_section {
+  LIST_ENTRY(ecm_section) es_link;
+
+  enum {
+    ES_UNKNOWN,
+    ES_RESOLVED,
+    ES_FORBIDDEN,
+    ES_IDLE
+  } es_keystate;
+
+  int es_section;
+  int es_channel;
+  uint16_t es_caid;
+  uint16_t es_provid;
+
+  uint8_t es_seq;
+  char es_nok;
+  char es_pending;
+  char es_resolved;
+  int64_t es_time;  // time request was sent
+
+} ecm_section_t;
+
+/**
+ *
+ */
+typedef struct ecm_pid {
+  LIST_ENTRY(ecm_pid) ep_link;
+
+  uint16_t ep_pid;
+
+  int ep_last_section;
+  LIST_HEAD(, ecm_section) ep_sections;
+} ecm_pid_t;
+
+/**
+ *
+ */
+typedef struct cccam_service {
+  th_descrambler_t;
+
+  struct cccam *cs_cccam;
+
+  LIST_ENTRY(cccam_service) cs_link;
+
+  int cs_channel;
+  int cs_epids[CCCAM_ES_PIDS];
+  mpegts_mux_t *cs_mux;
+
+  /**
+   * ECM Status
+   */
+  enum {
+    ECM_INIT,
+    ECM_VALID,
+    ECM_RESET
+  } ecm_state;
+
+  LIST_HEAD(, ecm_pid) cs_pids;
+
+} cccam_service_t;
+
+
+/**
+ *
+ */
+typedef struct cccam_message {
+  TAILQ_ENTRY(cccam_message) cm_link;
+  int cm_len;
+  int seq;
+  int card_id;
+  uint8_t cm_data[CCCAM_NETMSGSIZE];
+} cccam_message_t;
+
+
+struct cccam;
+
+typedef struct cs_card_data {
+  
+  LIST_ENTRY(cs_card_data) cs_card;
+
+  emm_reass_t cs_ra;
+
+  struct cccam *cccam;
+  mpegts_mux_t *cccam_mux;
+  int running;
+
+  uint32_t id;
+  uint32_t remote_id;
+  uint8_t hop;
+  uint8_t reshare;
+
+} cs_card_data_t;
+
+/**
+ *
+ */
+struct cccam_crypt_block {
+  uint8_t keytable[256];
+  uint8_t state;
+  uint8_t counter;
+  uint8_t sum;
+};
+
+
+/**
+ *
+ */
+typedef struct cccam {
+  caclient_t;
+
+  int cccam_fd;
+
+  pthread_t cccam_tid;
+  tvh_cond_t cccam_cond;
+  
+  pthread_mutex_t cccam_mutex;
+  pthread_mutex_t cccam_writer_mutex; 
+  tvh_cond_t cccam_writer_cond;
+  int cccam_writer_running;
+  struct cccam_message_queue cccam_writeq;
+
+  struct cccam_service_list cccam_services;
+
+  struct cccam_cards_list cccam_cards;
+  int cccam_seq;
+
+
+#if 0
+  /* Emm forwarding */
+  int cccam_forward_emm;
+
+  /* Emm exclusive update */
+  int64_t cccam_emm_update_time;
+  void *cccam_emm_mux;
+#endif
+  
+  /* From configuration */
+  char *cccam_username;
+  char *cccam_password;
+  char *cccam_hostname;
+
+  int cccam_port;
+  int cccam_emm;
+  int cccam_emmex;
+  int cccam_version;
+  int cccam_keepalive_interval;
+
+  int cccam_running;
+  int cccam_reconfigure;
+
+  struct cccam_crypt_block sendblock;
+  struct cccam_crypt_block recvblock;
+
+  sem_t ecm_mutex;
+
+  int seq;
+  uint32_t card_id;
+
+} cccam_t;
+
+
+const uint8_t cccam_str[] = "CCcam";
+
+
+/**
+ *
+ */
+static inline void
+uint8_swap(uint8_t *p1, uint8_t *p2)
+{
+  uint8_t tmp = *p1; *p1 = *p2; *p2 = tmp;
+}
+
+/**
+ *
+ */
+static void
+cccam_crypt_xor(uint8_t *buf)
+{
+  uint8_t i;
+
+  for (i = 0; i < 8; i++) {
+    buf[i + 8] = i * buf[i];
+    if (i <= 5) {
+      buf[i] ^= cccam_str[i];
+    }
+  }
+}
+
+/**
+ *
+ */
+static void
+cccam_crypt_init(struct cccam_crypt_block *block, uint8_t *key, int32_t len)
+{
+  uint32_t i = 0;
+  uint8_t j = 0;
+
+  for (i = 0; i < 256; i++) {
+    block->keytable[i] = i;
+  }
+  for (i = 0; i < 256; i++) {
+    j += key[i % len] + block->keytable[i];
+    uint8_swap(&block->keytable[i], &block->keytable[j]);
+  }
+  block->state = *key;
+  block->counter = 0;
+  block->sum = 0;
+}
+
+/**
+ *
+ */
+static void
+cccam_decrypt(struct cccam_crypt_block *block, uint8_t *data, int32_t len)
+{
+  int32_t i;
+  uint8_t z;
+
+  for (i = 0; i < len; i++) {
+    block->counter++;
+    block->sum += block->keytable[block->counter];
+    uint8_swap(&block->keytable[block->counter], &block->keytable[block->sum]);
+    z = data[i];
+    data[i] = z ^ block->keytable[(block->keytable[block->counter] +
+              block->keytable[block->sum]) & 0xff] ^ block->state;
+    z = data[i];
+    block->state ^= z;
+  }
+}
+
+/**
+ *
+ */
+static void
+cccam_encrypt(struct cccam_crypt_block *block, uint8_t *data, int32_t len)
+{
+  int32_t i;
+  uint8_t z;
+  for (i = 0; i < len; i++) {
+    block->counter++;
+    block->sum += block->keytable[block->counter];
+    uint8_swap(&block->keytable[block->counter], &block->keytable[block->sum]);
+    z = data[i];
+    data[i] = z ^ block->keytable[(block->keytable[block->counter] +
+              block->keytable[block->sum]) & 0xff] ^ block->state;
+    block->state ^= z;
+  }
+}
+
+static void
+cccam_decrypt_cw(char *nodeid, uint32_t card_id, uint8_t *cws)
+{
+  uint8_t tmp, i;
+  uint64_t node_id = be64toh(*((uint64_t *) nodeid));
+
+  for (i = 0; i < 16; i++) {
+    tmp = cws[i] ^ (node_id >> (4 * i));
+    if (i & 1)
+      tmp = ~tmp;
+    cws[i] = (card_id >> (2 * i)) ^ tmp;
+  }
+}
+
+
+static void cccam_service_pid_free(cccam_service_t *ct);
+
+
+/**
+ *
+ */
+static cs_card_data_t *
+cccam_new_card(cccam_t *cccam, uint8_t *buf, uint8_t *ua,
+             int pcount, uint8_t **pid, uint8_t **psa)
+{
+  cs_card_data_t *pcard = NULL;
+  emm_provider_t *ep;
+  const char *n;
+  const uint8_t *id, *sa;
+  int i, allocated = 0;
+
+  uint16_t caid = (buf[12] << 8) | buf[13];
+
+  LIST_FOREACH(pcard, &cccam->cccam_cards, cs_card)
+    if (pcard->cs_ra.caid == caid)
+      break;
+
+  if (pcard == NULL) {
+    pcard = calloc(1, sizeof(cs_card_data_t));
+    emm_reass_init(&pcard->cs_ra, caid);
+    allocated = 1;
+  }
+
+  pcard->id = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+  pcard->remote_id = (buf[8] << 24) | (buf[9] << 16) | (buf[10] << 8) | buf[11];
+  pcard->hop = buf[14];
+  pcard->reshare = buf[15];
+
+  if (ua)
+    memcpy(pcard->cs_ra.ua, ua, 8);
+  else
+    memset(pcard->cs_ra.ua, 0, 8);
+
+  free(pcard->cs_ra.providers);
+  pcard->cs_ra.providers_count = pcount;
+  pcard->cs_ra.providers = calloc(pcount, sizeof(pcard->cs_ra.providers[0]));
+
+  for (i = 0, ep = pcard->cs_ra.providers; i < pcount; i++, ep++) {
+    id = pid[i];
+    if (id)
+      ep->id = (id[0] << 16) | (id[1] << 8) | id[2];
+    if (psa)
+      memcpy(ep->sa, psa[i], 8);
+  }
+
+  n = caid2name(caid & 0xff00) ?: "Unknown";
+
+  if (ua) {
+    tvhinfo(LS_CCCAM, "%s:%i: Connected as user %s "
+            "to a %s-card [0x%04x : %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x] "
+            "with %d provider%s",
+            cccam->cccam_hostname, cccam->cccam_port,
+            cccam->cccam_username, n, caid,
+            ua[0], ua[1], ua[2], ua[3], ua[4], ua[5], ua[6], ua[7],
+            pcount, pcount > 1 ? "s" : "");
+  } else {
+    tvhinfo(LS_CCCAM, "%s:%i: Connected as user %s "
+            "to a %s-card [0x%04x] id=%08x remoteid=%08x hop=%i reshare=%i with %d provider%s",
+            cccam->cccam_hostname, cccam->cccam_port,
+            cccam->cccam_username, n, caid,
+            pcard->id, pcard->remote_id, pcard->hop, pcard->reshare,
+            pcount, pcount > 1 ? "s" : "");
+  }
+
+  for (i = 0, ep = pcard->cs_ra.providers; i < pcount; i++, ep++) {
+    if (psa) {
+      sa = ep->sa;
+      tvhinfo(LS_CCCAM, "%s:%i: Provider ID #%d: 0x%04x:0x%06x %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x",
+              cccam->cccam_hostname, cccam->cccam_port, i + 1, caid, ep->id,
+              sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7]);
+    } else {
+      tvhinfo(LS_CCCAM, "%s:%i: Provider ID #%d: 0x%04x:0x%06x",
+              cccam->cccam_hostname, cccam->cccam_port, i + 1, caid, ep->id);
+    }
+  }
+
+  pcard->running = 1;
+  if (allocated)
+    LIST_INSERT_HEAD(&cccam->cccam_cards, pcard, cs_card);
+  return pcard;
+}
+
+/**
+ * Handle reply to card data request
+ */
+static int
+cccam_decode_card_data_reply(cccam_t *cccam, uint8_t *msg)
+{
+  /*int emm_allowed; */
+
+  //cs_card_data_t *pcard;
+  int i;
+  unsigned int nprov;
+  uint8_t **pid, **psa, *msg2; //, *ua
+
+  /* nr of providers */
+  nprov = msg[24];
+
+  pid = nprov ? alloca(nprov * sizeof(uint8_t *)) : NULL;
+  psa = nprov ? alloca(nprov * sizeof(uint8_t *)) : NULL;
+
+  msg2 = msg + 25;
+  for (i = 0; i < nprov; i++) {
+    pid[i] = msg2;
+    psa[i] = msg2 + 3;
+    msg2 += 7;
+  }
+
+  caclient_set_status((caclient_t *)cccam, CACLIENT_STATUS_CONNECTED);
+  cccam_new_card(cccam, msg, NULL, nprov, pid, NULL); /* TODO: psa? 4bytes cccam / 8bytes cccam */
+/* TODO: EMM
+  cccam->cccam_forward_emm = 0;
+  if (cccam->cccam_emm) {
+    ua = pcard->cs_ra.ua;
+    emm_allowed = ua[0] || ua[1] || ua[2] || ua[3] ||
+                  ua[4] || ua[5] || ua[6] || ua[7];
+
+    if (!emm_allowed) {
+      tvhinfo(LS_CCCAM, 
+              "%s:%i: Will not forward EMMs (not allowed by server)",
+              cccam->cccam_hostname, cccam->cccam_port);
+    } else if (pcard->cs_ra.type != CARD_UNKNOWN) {
+      tvhinfo(LS_CCCAM, "%s:%i: Will forward EMMs",
+              cccam->cccam_hostname, cccam->cccam_port);
+      cccam->cccam_forward_emm = 1;
+    } else {
+      tvhinfo(LS_CCCAM, 
+             "%s:%i: Will not forward EMMs (unsupported CA system)",
+              cccam->cccam_hostname, cccam->cccam_port);
+    }
+  }
+*/
+  return 0;
+}
+
+
+static int
+cccam_ecm_reset(th_descrambler_t *th)
+{
+  cccam_service_t *ct = (cccam_service_t *)th;
+  cccam_t *cccam = ct->cs_cccam;
+  ecm_pid_t *ep;
+  ecm_section_t *es;
+
+  pthread_mutex_lock(&cccam->cccam_mutex);
+  ct->td_keystate = DS_UNKNOWN;
+  LIST_FOREACH(ep, &ct->cs_pids, ep_link)
+    LIST_FOREACH(es, &ep->ep_sections, es_link)
+      es->es_keystate = ES_UNKNOWN;
+  ct->ecm_state = ECM_RESET;
+  pthread_mutex_unlock(&cccam->cccam_mutex);
+  return 0;
+}
+
+
+static void
+cccam_ecm_idle(th_descrambler_t *th)
+{
+  cccam_service_t *ct = (cccam_service_t *)th;
+  cccam_t *cccam = ct->cs_cccam;
+  ecm_pid_t *ep;
+  ecm_section_t *es;
+
+  pthread_mutex_lock(&cccam->cccam_mutex);
+  LIST_FOREACH(ep, &ct->cs_pids, ep_link)
+    LIST_FOREACH(es, &ep->ep_sections, es_link)
+      es->es_keystate = ES_IDLE;
+  ct->ecm_state = ECM_RESET;
+  pthread_mutex_unlock(&cccam->cccam_mutex);
+}
+
+
+static void
+handle_ecm_reply(cccam_service_t *ct, ecm_section_t *es, uint8_t *msg,
+                 int len, int seq, uint8_t *dcw)
+{
+  mpegts_service_t *t = (mpegts_service_t *)ct->td_service;
+  cccam_t *cccam = ct->cs_cccam;
+  ecm_pid_t *ep;
+  ecm_section_t *es2, es3;
+  char chaninfo[128];
+  int i;
+  uint32_t off;
+  int64_t delay = (getfastmonoclock() - es->es_time) / 1000LL; // in ms
+
+  uint8_t msg_type = msg[1];
+
+  es->es_pending = 0;
+
+  snprintf(chaninfo, sizeof(chaninfo), " (PID %d)", es->es_channel);
+
+  if (msg_type != MSG_ECM_REQUEST) {
+
+    /* ERROR */
+    if (es->es_nok < CCCAM_MAX_NOKS)
+      es->es_nok++;
+
+    if(es->es_keystate == ES_FORBIDDEN)
+      return; // We already know it's bad
+
+    if (es->es_nok >= CCCAM_MAX_NOKS) {
+      tvhdebug(LS_CCCAM,
+              "Too many NOKs[%i] for service \"%s\"%s from %s",
+              es->es_section, t->s_dvb_svcname, chaninfo, ct->td_nicename);
+      es->es_keystate = ES_FORBIDDEN;
+      goto forbid;
+    }
+
+    if (descrambler_resolved((service_t *)t, (th_descrambler_t *)ct)) {
+      tvhdebug(LS_CCCAM,
+              "NOK[%i] from %s: Already has a key for service \"%s\"",
+               es->es_section, ct->td_nicename, t->s_dvb_svcname);
+      es->es_nok = CCCAM_MAX_NOKS; /* do not send more ECM requests */
+      es->es_keystate = ES_IDLE;
+      if (ct->td_keystate == DS_UNKNOWN)
+        ct->td_keystate = DS_IDLE;
+    }
+
+    tvhdebug(LS_CCCAM,
+             "Received NOK[%i] for service \"%s\"%s "
+             "(seqno: %d Req delay: %"PRId64" ms)",
+             es->es_section, t->s_dvb_svcname, chaninfo, seq, delay);
+
+forbid:
+    i = 0;
+    LIST_FOREACH(ep, &ct->cs_pids, ep_link)
+      LIST_FOREACH(es2, &ep->ep_sections, es_link)
+        if(es2 && es2 != es && es2->es_nok == 0) {
+          if (es2->es_pending)
+            return;
+          i++;
+        }
+    if (i && es->es_nok < CCCAM_MAX_NOKS)
+      return;
+    
+    es->es_keystate = ES_FORBIDDEN;
+    LIST_FOREACH(ep, &ct->cs_pids, ep_link) {
+      LIST_FOREACH(es2, &ep->ep_sections, es_link)
+        if (es2->es_keystate == ES_UNKNOWN ||
+            es2->es_keystate == ES_RESOLVED)
+          break;
+      if (es2)
+        break;
+    }
+
+    if (ep == NULL) { /* !UNKNOWN && !RESOLVED */
+      tvherror(LS_CCCAM,
+               "Can not descramble service \"%s\", access denied (seqno: %d "
+               "Req delay: %"PRId64" ms) from %s",
+               t->s_dvb_svcname, seq, delay, ct->td_nicename);
+      ct->td_keystate = DS_FORBIDDEN;
+      ct->ecm_state = ECM_RESET;
+      /* this pid is not valid, force full scan */
+      if (t->s_dvb_prefcapid == ct->cs_channel && t->s_dvb_prefcapid_lock == PREFCAPID_OFF)
+        t->s_dvb_prefcapid = 0;
+    }
+    return;
+
+  } else {
+
+    es->es_nok = 0;
+    ct->cs_channel = es->es_channel;
+    ct->ecm_state = ECM_VALID;
+
+    if(t->s_dvb_prefcapid == 0 ||
+       (t->s_dvb_prefcapid != ct->cs_channel &&
+        t->s_dvb_prefcapid_lock == PREFCAPID_OFF)) {
+      t->s_dvb_prefcapid = ct->cs_channel;
+      tvhdebug(LS_CCCAM, "Saving prefered PID %d for %s",
+                               t->s_dvb_prefcapid, ct->td_nicename);
+      service_request_save((service_t*)t, 0);
+    }
+
+    //if(len < 35) {
+      tvhdebug(LS_CCCAM,
+               "Received ECM reply%s for service \"%s\" [%d] "
+               "even: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x"
+               " odd: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x (seqno: %d "
+               "Req delay: %"PRId64" ms)",
+               chaninfo,
+               t->s_dvb_svcname, es->es_section,
+               msg[3 + 0], msg[3 + 1], msg[3 + 2], msg[3 + 3], msg[3 + 4],
+               msg[3 + 5], msg[3 + 6], msg[3 + 7], msg[3 + 8], msg[3 + 9],
+               msg[3 + 10],msg[3 + 11],msg[3 + 12],msg[3 + 13],msg[3 + 14],
+               msg[3 + 15], seq, delay);
+
+      if(es->es_keystate != ES_RESOLVED)
+        tvhdebug(LS_CCCAM,
+                 "Obtained DES keys for service \"%s\" in %"PRId64" ms, from %s",
+                 t->s_dvb_svcname, delay, ct->td_nicename);
+      es->es_keystate = ES_RESOLVED;
+      es->es_resolved = 1;
+      off = 8;
+    //} 
+    //TODO: AES
+    #if 0
+    else {
+      tvhdebug(LS_CCCAM,
+           "Received ECM reply%s for service \"%s\" "
+           "even: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x"
+           " odd: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x"
+           "(seqno: %d Req delay: %"PRId64" ms)",
+           chaninfo,
+           t->s_dvb_svcname,
+           msg[3 + 0], msg[3 + 1], msg[3 + 2], msg[3 + 3], msg[3 + 4],
+           msg[3 + 5], msg[3 + 6], msg[3 + 7], msg[3 + 8], msg[3 + 9],
+           msg[3 + 10],msg[3 + 11],msg[3 + 12],msg[3 + 13],msg[3 + 14],
+           msg[3 + 15],msg[3 + 16],msg[3 + 17],msg[3 + 18],msg[3 + 19],
+           msg[3 + 20],msg[3 + 21],msg[3 + 22],msg[3 + 23],msg[3 + 24],
+           msg[3 + 25],msg[3 + 26],msg[3 + 27],msg[3 + 28],msg[3 + 29],
+           msg[3 + 30],msg[3 + 31], seq, delay);
+
+      if(es->es_keystate != ES_RESOLVED)
+        tvhdebug(LS_CCCAM,
+                 "Obtained AES keys for service \"%s\" in %"PRId64" ms, from %s",
+                 t->s_dvb_svcname, delay, ct->td_nicename);
+      es->es_keystate = ES_RESOLVED;
+      es->es_resolved = 1;
+      off = 16;
+    }
+#endif
+    es3 = *es;
+    pthread_mutex_unlock(&cccam->cccam_mutex);
+    descrambler_keys((th_descrambler_t *)ct,
+                     off == 16 ? DESCRAMBLER_AES : DESCRAMBLER_DES,
+                     dcw, dcw + off);
+    snprintf(chaninfo, sizeof(chaninfo), "%s:%i", cccam->cccam_hostname, cccam->cccam_port);
+    descrambler_notify((th_descrambler_t *)ct,
+                       es3.es_caid, es3.es_provid,
+                       caid2name(es3.es_caid),
+                       es3.es_channel, delay,
+                       1, "", chaninfo, "cccam");
+    pthread_mutex_lock(&cccam->cccam_mutex);
+  }
+}
+
+/**
+ * Handle running reply
+ * cccam_mutex is held
+ */
+static int
+cccam_running_reply(cccam_t *cccam, uint8_t *buf, int len)
+{
+  cccam_service_t *ct;
+  ecm_pid_t *ep;
+  ecm_section_t *es;
+
+  tvhtrace(LS_CCCAM, "response msg type=%d, response:", buf[1]);
+  tvhlog_hexdump(LS_CCCAM, buf, len);
+
+  switch (buf[1]) {
+    case MSG_NEW_CARD_SIDINFO:
+    case MSG_NEW_CARD:
+      tvhdebug(LS_CCCAM, "add card message received");
+      cccam_decode_card_data_reply(cccam, buf);
+      break;
+    case MSG_CARD_REMOVED:
+      tvhdebug(LS_CCCAM, "del card message received");
+      break;
+    case MSG_KEEPALIVE:
+      tvhdebug(LS_CCCAM, "keepalive");
+      break;
+    case MSG_ECM_NOK1:    /* retry */
+    case MSG_ECM_NOK2:    /* decode failed */
+    //case MSG_CMD_05:     /* ? */
+    case MSG_ECM_REQUEST: { /* request reply */
+
+      uint8_t dcw[16];
+      uint8_t seq = buf[0];
+
+      cccam_decrypt_cw(cccam->cccam_username, cccam->card_id, &buf[4]);
+      memcpy(dcw, buf + 4, 16);
+      cccam_decrypt(&cccam->recvblock, buf + 4, len - 4);
+      tvhtrace(LS_CCCAM, "HEADER:");
+      tvhlog_hexdump(LS_CCCAM, buf, 4);
+
+      seq = cccam->seq;
+      sem_post(&cccam->ecm_mutex);
+
+      LIST_FOREACH(ct, &cccam->cccam_services, cs_link)
+        LIST_FOREACH(ep, &ct->cs_pids, ep_link)
+          LIST_FOREACH(es, &ep->ep_sections, es_link)
+            if(es->es_seq == seq) {
+              if (es->es_resolved) {
+                mpegts_service_t *t = (mpegts_service_t *)ct->td_service;
+                tvhdebug(LS_CCCAM,
+                         "Ignore %sECM (PID %d) for service \"%s\" from %s (seq %i)",
+                         es->es_pending ? "duplicate " : "",
+                         ep->ep_pid, t->s_dvb_svcname, ct->td_nicename, es->es_seq);
+                return 0;
+              }
+              if (es->es_pending) {
+                handle_ecm_reply(ct, es, buf, len, seq, dcw);
+                return 0;
+              }
+            }
+      tvhwarn(LS_CCCAM, "Got unexpected ECM reply (seqno: %d)", seq);
+      break;
+
+    }
+    case MSG_SRV_DATA:
+      tvhinfo(LS_CCCAM, "CCcam server version %s nodeid=%02x%02x%02x%02x%02x%02x%02x%02x",
+          &buf[12], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]);
+      break;
+    case MSG_CLI_DATA:
+      tvhinfo(LS_CCCAM, "CCcam server authentication completed");
+      break;
+    default:
+      tvhwarn(LS_CCCAM, "Unknown message received");
+      break;
+  }
+  return 0;
+}
+
+/**
+ *
+ */
+static int
+cccam_must_break(cccam_t *cccam)
+{
+  return !cccam->cccam_running || !cccam->cac_enabled || cccam->cccam_reconfigure;
+}
+
+
+static int
+cccam_read_message(cccam_t *cccam, uint8_t *buf, const char *state, int timeout)
+{
+  int32_t ret;
+  uint16_t msglen;
+
+  pthread_mutex_unlock(&cccam->cccam_mutex);
+  ret = tcp_read_timeout(cccam->cccam_fd, buf, 4, timeout);
+  pthread_mutex_lock(&cccam->cccam_mutex);
+  if (ret) {
+    tvhdebug(LS_CCCAM, "recv error %d or timeout", ret);
+    return -1;
+  }
+  cccam_decrypt(&cccam->recvblock, buf, 4);
+  msglen = (buf[2] << 8) | buf[3];
+  if (msglen > 0) {
+    if (msglen > CCCAM_NETMSGSIZE - 2) {
+      tvhdebug(LS_CCCAM, "received message too large");
+      return -1;
+    }
+    pthread_mutex_unlock(&cccam->cccam_mutex);
+    ret = tcp_read_timeout(cccam->cccam_fd, buf + 4, msglen, 5000);
+    pthread_mutex_lock(&cccam->cccam_mutex);
+    if (ret) {
+      tvhdebug(LS_CCCAM, "timeout reading message");
+      return -1;
+    }
+    cccam_decrypt(&cccam->recvblock, buf + 4, msglen);
+  }
+  return msglen + 4;
+}
+
+
+/**
+ *
+ */
+static int
+cccam_read(cccam_t *cccam, void *buf, size_t len, int timeout)
+{
+  int r;
+
+  pthread_mutex_unlock(&cccam->cccam_mutex);
+  r = tcp_read_timeout(cccam->cccam_fd, buf, len, timeout);
+  pthread_mutex_lock(&cccam->cccam_mutex);
+
+  if (r && tvheadend_is_running())
+    tvhwarn(LS_CCCAM, "read error %d (%s)", r, strerror(r));
+
+  if(cccam_must_break(cccam))
+    return ECONNABORTED;
+
+  return r;
+}
+
+
+/**
+ *
+ */
+static void
+cccam_invalidate_cards(cccam_t *cccam)
+{
+  cs_card_data_t *cd;
+
+  LIST_FOREACH(cd, &cccam->cccam_cards, cs_card)
+    cd->running = 0;
+}
+
+/**
+ *
+ */
+static void
+cccam_free_cards(cccam_t *cccam)
+{
+  cs_card_data_t *cd;
+
+  while((cd = LIST_FIRST(&cccam->cccam_cards)) != NULL) {
+    LIST_REMOVE(cd, cs_card);
+    descrambler_close_emm(cd->cccam_mux, cd, cd->cs_ra.caid);
+    emm_reass_done(&cd->cs_ra);
+    free(cd);
+  }
+}
+
+/**
+ *
+ */
+static void
+cccam_flush_services(cccam_t *cccam)
+{
+  cccam_service_t *ct;
+
+  LIST_FOREACH(ct, &cccam->cccam_services, cs_link)
+    descrambler_flush_table_data(ct->td_service);
+}
+
+/**
+ *
+ */
+static uint8_t
+cccam_send_msg(cccam_t *cccam, 
+      cccam_msg_type_t cmd, uint8_t *buf, size_t len, int enq, int seq, uint32_t card_id)
+{
+  cccam_message_t *cm = malloc(sizeof(cccam_message_t));
+  uint8_t *netbuf = cm->cm_data;
+
+  if ((len < 4) || ((len + 4) > CCCAM_NETMSGSIZE)) {
+    free(cm);
+    return 0;
+  }
+
+  memset(netbuf, 0, len + 4);
+  if (cmd == MSG_NO_HEADER) {
+    memcpy(netbuf, buf, len);
+  } else {
+    netbuf[0] = seq;
+    netbuf[1] = cmd & 0xff;
+    netbuf[2] = len >> 8;
+    netbuf[3] = len & 0xff;
+    if (buf)
+      memcpy(netbuf + 4, buf, len);
+    len += 4;
+  }
+
+  tvhtrace(LS_CCCAM, "sending message len %zu enq %d", len, enq);
+  tvhlog_hexdump(LS_CCCAM, buf, len);
+
+  if(enq) {
+    cm->cm_len = len;
+    cm->seq = seq;
+    cm->card_id = card_id;
+    pthread_mutex_lock(&cccam->cccam_writer_mutex);
+    TAILQ_INSERT_TAIL(&cccam->cccam_writeq, cm, cm_link);
+    tvh_cond_signal(&cccam->cccam_writer_cond, 0);
+    pthread_mutex_unlock(&cccam->cccam_writer_mutex);
+  } else {
+    cccam_encrypt(&cccam->sendblock, netbuf, len);
+    if (tvh_write(cccam->cccam_fd, netbuf, len))
+      tvhinfo(LS_CCCAM, "write error");
+    free(cm);
+  }
+  return seq;
+}
+#if 0
+/**
+ * Send keep alive
+ */
+static void
+cccam_send_ka(cccam_t *cccam)
+{
+  uint8_t buf[4];
+
+  buf[0] = 0;
+  buf[1] = MSG_KEEPALIVE;
+  buf[2] = 0;
+  buf[3] = 0;
+
+  tvhdebug(LS_CCCAM, "send keepalive");
+  cccam_send_msg(cccam, MSG_NO_HEADER, buf, 4, 0, 0, 0);
+}
+#endif
+/**
+ *
+ */
+static void *
+cccam_writer_thread(void *aux)
+{
+  cccam_t *cccam = aux;
+  cccam_message_t *cm;
+
+  int64_t mono;
+  int r;
+
+  pthread_mutex_lock(&cccam->cccam_writer_mutex);
+
+  while(cccam->cccam_writer_running) {
+
+    if((cm = TAILQ_FIRST(&cccam->cccam_writeq)) != NULL) {
+      TAILQ_REMOVE(&cccam->cccam_writeq, cm, cm_link);
+
+      cccam_encrypt(&cccam->sendblock, cm->cm_data, cm->cm_len);
+      sem_wait(&cccam->ecm_mutex);
+      cccam->seq = cm->seq;
+      cccam->card_id = cm->card_id;
+
+      pthread_mutex_unlock(&cccam->cccam_writer_mutex);
+      //      int64_t ts = getfastmonoclock();
+      r = tvh_write(cccam->cccam_fd, cm->cm_data, cm->cm_len);
+      if (r)
+        tvhinfo(LS_CCCAM, "write error");
+      //      printf("Write took %lld usec\n", getfastmonoclock() - ts);
+      free(cm);
+      pthread_mutex_lock(&cccam->cccam_writer_mutex);
+      continue;
+    }
+
+    /* If nothing is to be sent in keepalive interval seconds we
+       need to send a keepalive */
+    mono = mclk() + sec2mono(cccam->cccam_keepalive_interval);
+    do {
+      r = tvh_cond_timedwait(&cccam->cccam_writer_cond, &cccam->cccam_writer_mutex, mono);
+      if(r == ETIMEDOUT) {
+        /* TODO: cccam doesn't seem to care about keepalive messages
+                 (server just sends them) */
+        //cccam_send_ka(cccam);
+        break;
+      }
+    } while (ERRNO_AGAIN(r));
+  }
+
+  pthread_mutex_unlock(&cccam->cccam_writer_mutex);
+  return NULL;
+}
+
+/**
+ *
+ */
+static void
+sha1_make_login_key(cccam_t *cccam, uint8_t *buf)
+{
+  SHA_CTX sha1;
+  uint8_t hash[SHA_DIGEST_LENGTH];
+
+  cccam_crypt_xor(buf);
+
+  SHA1_Init(&sha1);
+  SHA1_Update(&sha1, buf, 16);
+  SHA1_Final(hash, &sha1);
+
+  tvhdebug(LS_CCCAM, "sha1 hash:");
+  tvhlog_hexdump(LS_CCCAM, hash, sizeof(hash));
+
+  cccam_crypt_init(&cccam->recvblock, hash, 20);
+  cccam_decrypt(&cccam->recvblock, buf, 16);
+  
+  cccam_crypt_init(&cccam->sendblock, buf, 16);
+  cccam_decrypt(&cccam->sendblock, hash, 20);
+
+  // send crypted hash to server
+  cccam_send_msg(cccam, MSG_NO_HEADER, hash, 20, 0, 0, 0);
+}
+
+/**
+ * Login command
+ */
+static int
+cccam_send_login(cccam_t *cccam)
+{
+  uint8_t buf[CCCAM_NETMSGSIZE];
+  size_t ul;
+  char pwd[255];
+  uint8_t data[20];
+
+  if (cccam->cccam_username == NULL)
+    return 1;
+
+  ul = strlen(cccam->cccam_username) + 1;
+  if (ul > 128)
+    return 1;
+
+  /* send username */
+  memset(buf, 0, CCCAM_NETMSGSIZE);
+  memcpy(buf, cccam->cccam_username, ul);
+  cccam_send_msg(cccam, MSG_NO_HEADER, buf, 20, 0, 0, 0);
+
+  /* send password 'xored' with CCcam */
+  memset(buf, 0, CCCAM_NETMSGSIZE);
+  memset(pwd, 0, sizeof(pwd));
+  memcpy(buf, cccam_str, 5);
+  strncpy(pwd, cccam->cccam_password, sizeof(pwd) - 1);
+  cccam_encrypt(&cccam->sendblock, (uint8_t *) pwd, strlen(pwd));
+  cccam_send_msg(cccam, MSG_NO_HEADER, buf, 6, 0, 0, 0);
+
+  if (cccam_read(cccam, data, 20, 5000)) {
+    tvherror(LS_CCCAM, "login failed, pwd ack not received");
+    return -2;
+  }
+
+  cccam_decrypt(&cccam->recvblock, data, 20);
+  tvhdebug(LS_CCCAM, "login ack, response:");
+  tvhlog_hexdump(LS_CCCAM, data, 20);
+
+  if (memcmp(data, "CCcam", 5)) {
+    tvherror(LS_CCCAM, "login failed, usr/pwd invalid");
+    return -2;
+  } else {
+    tvhinfo(LS_CCCAM, "login succeeded");
+  }
+
+  return 0;
+}
+
+/**
+ *
+ */
+static void
+cccam_send_cli_data(cccam_t *cccam)
+{
+  uint8_t buf[CCCAM_NETMSGSIZE];
+
+  memset(buf, 0, CCCAM_NETMSGSIZE);
+  memcpy(buf, cccam->cccam_username, 20);
+  // TODO: add proper NODEID to config webui (using username atm)
+  memcpy(buf + 20, cccam->cccam_username, 8 ); 
+  buf[28] = 0; // TODO: wantemus = 1;
+  memcpy(buf + 29, cccam_version_str[cccam->cccam_version], 32);
+  memcpy(buf + 61, "tvh", 32); // build number (ascii)
+  cccam_send_msg(cccam, MSG_CLI_DATA, buf, 20 + 8 + 1 + 64, 0, 0, 0);
+}
+
+/**
+ *
+ */
+static void
+cccam_session(cccam_t *cccam)
+{
+  int r;
+  uint8_t buf[CCCAM_NETMSGSIZE];
+  pthread_t writer_thread_id;
+
+  /**
+   * Get init seed
+   */
+  if((r = cccam_read(cccam, buf, 16, 5000))) {
+    tvhinfo(LS_CCCAM, "%s:%i: init error (no init seed received)",
+            cccam->cccam_hostname, cccam->cccam_port);
+    return;
+  }
+  tvhdebug(LS_CCCAM, "init seed received:");
+  tvhlog_hexdump(LS_CCCAM, buf, 16);
+
+  /* check for oscam-cccam */
+  uint16_t sum = 0x1234;
+  uint16_t recv_sum = (buf[14] << 8) | buf[15];
+  int32_t i;
+  for(i = 0; i < 14; i++) {
+    sum += buf[i];
+  }
+  if (sum == recv_sum)
+    tvhinfo(LS_CCCAM, "oscam server detected");
+
+  sha1_make_login_key(cccam, buf);
+
+  /**
+   * Login
+   */
+  if (cccam_send_login(cccam))
+    return;
+
+  cccam_send_cli_data(cccam);
+
+  /**
+   * Ok, connection good, reset retry delay to zero
+   */
+  cccam_flush_services(cccam);
+
+  /**
+   * We do all requests from now on in a separate thread
+   */
+  sem_init(&cccam->ecm_mutex, 1, 1);
+  cccam->cccam_writer_running = 1;
+  tvh_cond_init(&cccam->cccam_writer_cond);
+  pthread_mutex_init(&cccam->cccam_writer_mutex, NULL);
+  TAILQ_INIT(&cccam->cccam_writeq);
+  tvhthread_create(&writer_thread_id, NULL, cccam_writer_thread, cccam, "cccam-writer");
+
+  /**
+   * Mainloop
+   */
+  while (!cccam_must_break(cccam)) {
+    if ((r = cccam_read_message(cccam, buf, "Decoderloop", 30000)) < 0)
+      break;
+    if (r > 0)
+      cccam_running_reply(cccam, buf, r);
+  };
+
+  tvhdebug(LS_CCCAM, "session thread exiting");
+
+  /**
+   * Collect the writer thread
+   */
+  cccam->cccam_writer_running = 0;
+  tvh_cond_signal(&cccam->cccam_writer_cond, 0);
+  pthread_join(writer_thread_id, NULL);
+
+  shutdown(cccam->cccam_fd, SHUT_RDWR);
+  tvhdebug(LS_CCCAM, "Write thread joined");
+}
+
+/**
+ *
+ */
+static void *
+cccam_thread(void *aux)
+{
+  char hostname[256];
+  int port;
+  cccam_t *cccam = aux;
+  int fd, d, r;
+  char errbuf[100];
+  int attempts = 0;
+  int64_t mono;
+
+  pthread_mutex_lock(&cccam->cccam_mutex);
+
+  while(cccam->cccam_running) {
+    cccam_invalidate_cards(cccam);
+    caclient_set_status((caclient_t *)cccam, CACLIENT_STATUS_READY);
+    
+    snprintf(hostname, sizeof(hostname), "%s", cccam->cccam_hostname);
+    port = cccam->cccam_port;
+
+    tvhinfo(LS_CCCAM, "Attemping to connect to %s:%d", hostname, port);
+
+    pthread_mutex_unlock(&cccam->cccam_mutex);
+    fd = tcp_connect(hostname, port, NULL, errbuf, sizeof(errbuf), 10);
+    pthread_mutex_lock(&cccam->cccam_mutex);
+
+    if(fd == -1) {
+      attempts++;
+      tvhinfo(LS_CCCAM, 
+              "Connection attempt to %s:%d failed: %s",
+              hostname, port, errbuf);
+    } else {
+
+      if(cccam->cccam_running == 0) {
+        close(fd);
+        break;
+      }
+
+      tvhinfo(LS_CCCAM, "Connected to %s:%d", hostname, port);
+      attempts = 0;
+
+      cccam->cccam_fd = fd;
+      cccam->cccam_reconfigure = 0;
+
+      cccam_session(cccam);
+
+      cccam->cccam_fd = -1;
+      close(fd);
+      tvhinfo(LS_CCCAM, "Disconnected from %s:%i", 
+              cccam->cccam_hostname, cccam->cccam_port);
+    }
+
+    if(cccam->cccam_running == 0) continue;
+    if(attempts == 1 || cccam->cccam_reconfigure) {
+      cccam->cccam_reconfigure = 0;
+      continue; // Retry immediately
+    }
+
+    caclient_set_status((caclient_t *)cccam, CACLIENT_STATUS_DISCONNECTED);
+
+    d = 3;
+
+    tvhinfo(LS_CCCAM, 
+            "%s:%i: Automatic connection attempt in %d seconds",
+            cccam->cccam_hostname, cccam->cccam_port, d-1);
+
+    mono = mclk() + sec2mono(d);
+    do {
+      r = tvh_cond_timedwait(&cccam->cccam_cond, &cccam->cccam_mutex, mono);
+      if (r == ETIMEDOUT)
+        break;
+    } while (ERRNO_AGAIN(r));
+  }
+
+  tvhinfo(LS_CCCAM, "%s:%i inactive",
+          cccam->cccam_hostname, cccam->cccam_port);
+  cccam_free_cards(cccam);
+  pthread_mutex_unlock(&cccam->cccam_mutex);
+  return NULL;
+}
+
+/**
+ *
+ */
+static int
+verify_provider(cs_card_data_t *pcard, uint32_t providerid)
+{
+  int i;
+
+  if(providerid == 0)
+    return 1;
+  
+  for(i = 0; i < pcard->cs_ra.providers_count; i++)
+    if(providerid == pcard->cs_ra.providers[i].id)
+      return 1;
+  return 0;
+}
+
+#if 0
+/**
+ *
+ */
+static void
+cccam_emm_send(void *aux, const uint8_t *radata, int ralen, void *mux)
+{
+  cs_card_data_t *pcard = aux;
+  cccam_t *cccam = pcard->cccam;
+
+  tvhtrace(LS_CCCAM, "sending EMM for %04x mux %p", pcard->cs_ra.caid, mux);
+  tvhlog_hexdump(LS_CCCAM, radata, ralen);
+  cccam_send_msg(cccam, radata, ralen, 0, 1, 0, 0);
+}
+#endif
+/**
+ *
+ */
+
+static void
+cccam_emm(void *opaque, int pid, const uint8_t *data, int len, int emm)
+{
+  cs_card_data_t *pcard = opaque;
+  cccam_t *cccam;
+  //void *mux;
+
+  if (data == NULL) {  /* end-of-data */
+    pcard->cccam_mux = NULL;
+    return;
+  }
+  if (pcard->cccam_mux == NULL)
+    return;
+  cccam = pcard->cccam;
+  pthread_mutex_lock(&cccam->cccam_mutex);
+  /* TODO: emm
+  mux = pcard->cccam_mux;
+  
+  if (pcard->running && cccam->cccam_forward_emm && cccam->cccam_writer_running) {
+    if (cccam->cccam_emmex) {
+      if (cccam->cccam_emm_mux && cccam->cccam_emm_mux != mux) {
+        if (cccam->cccam_emm_update_time + sec2mono(25) > mclk())
+          goto end_of_job;
+      }
+      cccam->cccam_emm_update_time = mclk();
+    }
+    cccam->cccam_emm_mux = mux;
+    emm_filter(&pcard->cs_ra, data, len, mux, cccam_emm_send, pcard);
+  }
+end_of_job:*/
+  pthread_mutex_unlock(&cccam->cccam_mutex);
+}
+
+
+static int
+cccam_send_ecm(cccam_t *cccam, const uint8_t *msg, size_t len, int sid,
+                uint16_t st_caid, uint32_t st_provider, uint32_t card_id)
+{
+  unsigned char buf[CCCAM_NETMSGSIZE-4];
+
+  int seq = atomic_add(&cccam->cccam_seq, 1);
+
+  buf[0] = st_caid >> 8;
+  buf[1] = st_caid & 0xff;
+  buf[2] = st_provider >> 24;
+  buf[3] = st_provider >> 16;
+  buf[4] = st_provider >> 8;
+  buf[5] = st_provider & 0xff;
+  buf[6] = card_id >> 24;
+  buf[7] = card_id >> 16;
+  buf[8] = card_id >> 8;
+  buf[9] = card_id & 0xff;
+  buf[10] = sid >> 8;
+  buf[11] = sid & 0xff;
+  buf[12] = len;
+  memcpy(buf + 13, msg, len);
+  return cccam_send_msg(cccam, MSG_ECM_REQUEST, buf, 13 + len, 1, seq, card_id);
+}
+
+
+/**
+ *
+ */
+static void
+cccam_table_input(void *opaque, int pid, const uint8_t *data, int len, int emm)
+{
+  cccam_service_t *ct = opaque;
+  elementary_stream_t *st;
+  mpegts_service_t *t = (mpegts_service_t*)ct->td_service;
+  uint16_t sid = t->s_dvb_service_id;
+  cccam_t *cccam = ct->cs_cccam;
+
+  int channel;
+  int section;
+  ecm_pid_t *ep;
+  ecm_section_t *es;
+  char chaninfo[32];
+  cs_card_data_t *pcard = NULL;
+  caid_t *c;
+  uint16_t caid;
+  uint32_t provid;
+
+  if (data == NULL)
+    return;
+
+  if(len > 4096)
+    return;
+
+  if((data[0] & 0xf0) != 0x80)
+    return;
+
+  pthread_mutex_lock(&cccam->cccam_mutex);
+  pthread_mutex_lock(&t->s_stream_mutex);
+
+  if (ct->td_keystate == DS_IDLE)
+    goto end;
+
+  if (ct->ecm_state == ECM_RESET) {
+    /* clean all */
+    cccam_service_pid_free(ct);
+    /* move to init state */
+    ct->ecm_state = ECM_INIT;
+    ct->cs_channel = -1;
+    t->s_dvb_prefcapid = 0;
+    tvhdebug(LS_CCCAM, "Reset after unexpected or no reply for service \"%s\"", t->s_dvb_svcname);
+  }
+
+  LIST_FOREACH(ep, &ct->cs_pids, ep_link)
+    if(ep->ep_pid == pid) break;
+
+  if(ep == NULL) {
+    if (ct->ecm_state == ECM_INIT) {
+      // Validate prefered ECM PID
+      tvhdebug(LS_CCCAM, "ECM state INIT");
+
+      if(t->s_dvb_prefcapid_lock != PREFCAPID_OFF) {
+        st = service_stream_find((service_t*)t, t->s_dvb_prefcapid);
+        if (st && st->es_type == SCT_CA)
+          LIST_FOREACH(c, &st->es_caids, link)
+            LIST_FOREACH(pcard, &cccam->cccam_cards, cs_card)
+              if(pcard->running &&
+                 pcard->cs_ra.caid == c->caid &&
+                 verify_provider(pcard, c->providerid))
+                goto prefcapid_ok;
+        tvhdebug(LS_CCCAM, "Invalid prefered ECM (PID %d) found for service \"%s\"", t->s_dvb_prefcapid, t->s_dvb_svcname);
+        t->s_dvb_prefcapid = 0;
+      }
+
+prefcapid_ok:
+      if(t->s_dvb_prefcapid == pid || t->s_dvb_prefcapid == 0 ||
+         t->s_dvb_prefcapid_lock == PREFCAPID_OFF) {
+        ep = calloc(1, sizeof(ecm_pid_t));
+        ep->ep_pid = pid;
+
+        LIST_INSERT_HEAD(&ct->cs_pids, ep, ep_link);
+        tvhdebug(LS_CCCAM, "Insert %s ECM (PID %d) for service \"%s\"",
+                 t->s_dvb_prefcapid ? "preferred" : "new", pid, t->s_dvb_svcname);
+      }
+    }
+    if(ep == NULL)
+      goto end;
+  }
+
+  st = service_stream_find((service_t *)t, pid);
+  if (st) {
+    LIST_FOREACH(c, &st->es_caids, link)
+      LIST_FOREACH(pcard, &cccam->cccam_cards, cs_card)
+        if(pcard->running &&
+           pcard->cs_ra.caid == c->caid &&
+           verify_provider(pcard, c->providerid))
+          goto found;
+  }
+
+  goto end;
+
+found:
+  caid = c->caid;
+  provid = c->providerid;
+
+  switch(data[0]) {
+    case 0x80:
+    case 0x81:
+      /* ECM */
+      
+      if((pcard->cs_ra.caid >> 8) == 6) {
+        ep->ep_last_section = data[5];
+        section = data[4];
+        
+      } else {
+        ep->ep_last_section = 0;
+        section = 0;
+      }
+
+      channel = pid;
+      snprintf(chaninfo, sizeof(chaninfo), " (PID %d)", channel);
+
+      LIST_FOREACH(es, &ep->ep_sections, es_link)
+        if (es->es_section == section)
+          break;
+      if (es == NULL) {
+        es = calloc(1, sizeof(ecm_section_t));
+        es->es_section = section;
+        LIST_INSERT_HEAD(&ep->ep_sections, es, es_link);
+      }
+
+      if(cccam->cccam_fd == -1) {
+        // New key, but we are not connected (anymore), can not descramble
+        ct->td_keystate = DS_UNKNOWN;
+        break;
+      }
+
+      if (es->es_keystate == ES_FORBIDDEN || es->es_keystate == ES_IDLE)
+        break;
+
+      es->es_caid = caid;
+      es->es_provid = provid;
+      es->es_channel = channel;
+      es->es_pending = 1;
+      es->es_resolved = 0;
+
+      if(ct->cs_channel >= 0 && channel != -1 &&
+         ct->cs_channel != channel) {
+        tvhdebug(LS_CCCAM, "Filtering ECM (PID %d)", channel);
+        goto end;
+      }
+
+      //tvhtrace(LS_CCCAM, "send ecm: len=%d sid=%04x caid=%04x provid=%06x", len, sid, caid, provid);
+      //tvhlog_hexdump(LS_CCCAM, data, len);
+
+      es->es_seq = cccam_send_ecm(cccam, data, len, sid, caid, provid, pcard->id);
+
+      tvhdebug(LS_CCCAM,
+               "Sending ECM%s section=%d/%d, for service \"%s\" (seqno: %d)",
+               chaninfo, section, ep->ep_last_section, t->s_dvb_svcname, es->es_seq);
+      es->es_time = getfastmonoclock();
+      break;
+
+    default:
+      /* TODO: EMM */
+      //if (cccam->cccam_forward_emm)
+      //  cccam_send_msg(cccam, data, len, sid, 1, 0, 0);
+      break;
+  }
+
+end:
+  pthread_mutex_unlock(&t->s_stream_mutex);
+  pthread_mutex_unlock(&cccam->cccam_mutex);
+}
+
+/**
+ * cccam_mutex is held
+ */
+static void 
+cccam_service_pid_free(cccam_service_t *ct)
+{
+  ecm_pid_t *ep;
+  ecm_section_t *es;
+
+  while((ep = LIST_FIRST(&ct->cs_pids)) != NULL) {
+    while ((es = LIST_FIRST(&ep->ep_sections)) != NULL) {
+      LIST_REMOVE(es, es_link);
+      free(es);
+    }
+    LIST_REMOVE(ep, ep_link);
+    free(ep);
+  }
+}
+
+/**
+ * cccam_mutex is held
+ */
+static void
+cccam_service_destroy(th_descrambler_t *td)
+{
+  cccam_service_t *ct = (cccam_service_t *)td;
+  cccam_t *cccam = ct->cs_cccam;
+  int i;
+
+  pthread_mutex_lock(&cccam->cccam_mutex);
+  for (i = 0; i < CCCAM_ES_PIDS; i++)
+    if (ct->cs_epids[i])
+      descrambler_close_pid(ct->cs_mux, ct,
+                            DESCRAMBLER_ECM_PID(ct->cs_epids[i]));
+
+  cccam_service_pid_free(ct);
+
+  LIST_REMOVE(td, td_service_link);
+
+  LIST_REMOVE(ct, cs_link);
+
+  free(ct->td_nicename);
+  free(ct);
+  pthread_mutex_unlock(&cccam->cccam_mutex);
+}
+
+/**
+ * Check if our CAID's matches, and if so, link
+ *
+ * global_lock is held. Not that we care about that, but either way, it is.
+ */
+static void
+cccam_service_start(caclient_t *cac, service_t *t)
+{
+  cccam_t *cccam = (cccam_t *)cac;
+  cccam_service_t *ct;
+  th_descrambler_t *td;
+  elementary_stream_t *st;
+  caid_t *c;
+  cs_card_data_t *pcard;
+  char buf[512];
+  int i, reuse = 0, prefpid, prefpid_lock, forcecaid;
+
+  extern const idclass_t mpegts_service_class;
+  if (!idnode_is_instance(&t->s_id, &mpegts_service_class))
+    return;
+
+
+  pthread_mutex_lock(&cccam->cccam_mutex);
+  pthread_mutex_lock(&t->s_stream_mutex);
+
+
+  LIST_FOREACH(ct, &cccam->cccam_services, cs_link) {
+    if (ct->td_service == t && ct->cs_cccam == cccam)
+      break;
+  }
+  prefpid      = ((mpegts_service_t *)t)->s_dvb_prefcapid;
+  prefpid_lock = ((mpegts_service_t *)t)->s_dvb_prefcapid_lock;
+  forcecaid    = ((mpegts_service_t *)t)->s_dvb_forcecaid;
+  LIST_FOREACH(pcard, &cccam->cccam_cards, cs_card) {
+    if (!pcard->running) continue;
+    if (pcard->cs_ra.caid == 0) continue;
+    TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
+      if (prefpid_lock == PREFCAPID_FORCE && prefpid != st->es_pid)
+        continue;
+      LIST_FOREACH(c, &st->es_caids, link) {
+        if (c->use && c->caid == pcard->cs_ra.caid)
+          if (!forcecaid || forcecaid == c->caid)
+            break;
+      }
+      if (c) break;
+    }
+    if (st) break;
+  }
+  if (!pcard) {
+    if (ct) cccam_service_destroy((th_descrambler_t*)ct);
+    goto end;
+  }
+  if (ct) {
+    reuse = 1;
+    for (i = 0; i < CCCAM_ES_PIDS; i++) {
+      if (!ct->cs_epids[i]) continue;
+      TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
+        if (st->es_pid != ct->cs_epids[i]) continue;
+        LIST_FOREACH(c, &st->es_caids, link)
+          if (c->use && c->caid == pcard->cs_ra.caid)
+            break;
+        if (c) break;
+      }
+      if (st == NULL) {
+        descrambler_close_pid(ct->cs_mux, ct,
+                              DESCRAMBLER_ECM_PID(ct->cs_epids[i]));
+        reuse |= 2;
+      }
+    }
+    goto add;
+  }
+
+  ct                   = calloc(1, sizeof(cccam_service_t));
+  ct->cs_cccam         = cccam;
+  ct->cs_channel       = -1;
+  ct->cs_mux           = ((mpegts_service_t *)t)->s_dvb_mux;
+  ct->ecm_state        = ECM_INIT;
+
+  td                   = (th_descrambler_t *)ct;
+  snprintf(buf, sizeof(buf), "cccam-%s-%i-%04X", cccam->cccam_hostname, cccam->cccam_port, pcard->cs_ra.caid);
+  td->td_nicename      = strdup(buf);
+  td->td_service       = t;
+  td->td_stop          = cccam_service_destroy;
+  td->td_ecm_reset     = cccam_ecm_reset;
+  td->td_ecm_idle      = cccam_ecm_idle;
+  LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
+
+  LIST_INSERT_HEAD(&cccam->cccam_services, ct, cs_link);
+
+add:
+  i = 0;
+  TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
+    LIST_FOREACH(c, &st->es_caids, link)
+      if (c->use && c->caid == pcard->cs_ra.caid) {
+        if (reuse && ct->cs_epids[i] != st->es_pid) reuse |= 2;
+        ct->cs_epids[i++] = st->es_pid;
+        break;
+      }
+    if (i == CCCAM_ES_PIDS) break;
+  }
+
+  for (i = 0; i < CCCAM_ES_PIDS; i++)
+    if (ct->cs_epids[i])
+      descrambler_open_pid(ct->cs_mux, ct,
+                           DESCRAMBLER_ECM_PID(ct->cs_epids[i]),
+                           cccam_table_input, t);
+
+  if (reuse & 2) {
+    ct->cs_channel = -1;
+    ct->ecm_state = ECM_INIT;
+  }
+
+  if (reuse != 1)
+    tvhdebug(LS_CCCAM, "%s %susing CCCAM %s:%d",
+             service_nicename(t), reuse ? "re" : "", cccam->cccam_hostname, cccam->cccam_port);
+end:
+  pthread_mutex_unlock(&t->s_stream_mutex);
+  pthread_mutex_unlock(&cccam->cccam_mutex);
+}
+
+/**
+ *
+ */
+static void
+cccam_free(caclient_t *cac)
+{
+  cccam_t *cccam = (cccam_t *)cac;
+  cccam_service_t *ct;
+
+  while((ct = LIST_FIRST(&cccam->cccam_services)) != NULL)
+    cccam_service_destroy((th_descrambler_t *)ct);
+
+  cccam_free_cards(cccam);
+  free((void *)cccam->cccam_password);
+  free((void *)cccam->cccam_username);
+  free((void *)cccam->cccam_hostname);
+}
+
+/**
+ *
+ */
+static void
+cccam_caid_update(caclient_t *cac, mpegts_mux_t *mux, uint16_t caid, uint16_t pid, int valid)
+{
+  cccam_t *cccam = (cccam_t *)cac;;
+  cs_card_data_t *pcard;
+
+  tvhtrace(LS_CCCAM,
+           "caid update event - client %s mux %p caid %04x (%i) pid %04x (%i) valid %i",
+           cac->cac_name, mux, caid, caid, pid, pid, valid);
+  pthread_mutex_lock(&cccam->cccam_mutex);
+  if (valid < 0 || cccam->cccam_running) {
+    LIST_FOREACH(pcard, &cccam->cccam_cards, cs_card) {
+      if (valid < 0 || pcard->cs_ra.caid == caid) {
+        if (pcard->cccam_mux && pcard->cccam_mux != mux) continue;
+        if (valid > 0) {
+          pcard->cccam       = cccam;
+          pcard->cccam_mux   = mux;
+          descrambler_open_emm(mux, pcard, caid, cccam_emm);
+        } else {
+          pcard->cccam_mux   = NULL;
+          descrambler_close_emm(mux, pcard, caid);
+        }
+      }
+    }
+  }
+  pthread_mutex_unlock(&cccam->cccam_mutex);
+}
+
+/**
+ *
+ */
+static void
+cccam_conf_changed(caclient_t *cac)
+{
+  cccam_t *cccam = (cccam_t *)cac;
+  pthread_t tid;
+
+  if (cac->cac_enabled) {
+    if (cccam->cccam_hostname == NULL || cccam->cccam_hostname[0] == '\0') {
+      caclient_set_status(cac, CACLIENT_STATUS_NONE);
+      return;
+    }
+    if (!cccam->cccam_running) {
+      cccam->cccam_running = 1;
+      tvhthread_create(&cccam->cccam_tid, NULL, cccam_thread, cccam, "cccam");
+      return;
+    }
+    pthread_mutex_lock(&cccam->cccam_mutex);
+    cccam->cccam_reconfigure = 1;
+    if(cccam->cccam_fd >= 0)
+      shutdown(cccam->cccam_fd, SHUT_RDWR);
+    tvh_cond_signal(&cccam->cccam_cond, 0);
+    pthread_mutex_unlock(&cccam->cccam_mutex);
+  } else {
+    if (!cccam->cccam_running)
+      return;
+    pthread_mutex_lock(&cccam->cccam_mutex);
+    cccam->cccam_running = 0;
+    tvh_cond_signal(&cccam->cccam_cond, 0);
+    tid = cccam->cccam_tid;
+    if (cccam->cccam_fd >= 0)
+      shutdown(cccam->cccam_fd, SHUT_RDWR);
+    pthread_mutex_unlock(&cccam->cccam_mutex);
+    pthread_kill(tid, SIGHUP);
+    pthread_join(tid, NULL);
+    caclient_set_status(cac, CACLIENT_STATUS_NONE);
+  }
+}
+
+#if 0
+static htsmsg_t *
+caclient_cccam_class_cccam_version_list ( void *o, const char *lang )
+{
+  static const struct strtab tab[] = {
+    { N_("2.0.11"), CCCAM_VERSION_2_0_11 },
+    { N_("2.1.1"), CCCAM_VERSION_2_1_1 },
+    { N_("2.1.2"), CCCAM_VERSION_2_1_2 },
+    { N_("2.1.3"), CCCAM_VERSION_2_1_3 },
+    { N_("2.1.4"), CCCAM_VERSION_2_1_4 },
+    { N_("2.2.0"), CCCAM_VERSION_2_2_0 },
+    { N_("2.2.1"), CCCAM_VERSION_2_2_1 },
+    { N_("2.3.0"), CCCAM_VERSION_2_3_0 },
+  };
+  return strtab2htsmsg(tab, 1, lang);
+}
+#endif
+
+const idclass_t caclient_cccam_class =
+{
+  .ic_super      = &caclient_class,
+  .ic_class      = "caclient_cccam",
+  .ic_caption    = N_("CCcam"),
+  .ic_properties = (const property_t[]){
+    {
+      .type     = PT_STR,
+      .id       = "username",
+      .name     = N_("Username"),
+      .desc     = N_("Login username."),
+      .off      = offsetof(cccam_t, cccam_username),
+      .opts     = PO_TRIM,
+    },
+    {
+      .type     = PT_STR,
+      .id       = "password",
+      .name     = N_("Password"),
+      .desc     = N_("Login password."),
+      .off      = offsetof(cccam_t, cccam_password),
+      .opts     = PO_PASSWORD
+    },
+    {
+      .type     = PT_STR,
+      .id       = "hostname",
+      .name     = N_("Hostname/IP"),
+      .desc     = N_("Hostname (or IP) of the server."),
+      .off      = offsetof(cccam_t, cccam_hostname),
+      .def.s    = "localhost",
+      .opts     = PO_TRIM,
+    },
+    {
+      .type     = PT_INT,
+      .id       = "port",
+      .name     = N_("Port"),
+      .desc     = N_("Port to connect to."),
+      .off      = offsetof(cccam_t, cccam_port),
+    },
+#if 0
+    {
+      .type     = PT_INT,
+      .id       = "version",
+      .name     = N_("Version"),
+      .desc     = N_("Protocol version."),
+      .off      = offsetof(cccam_t, cccam_version),
+      .list     = caclient_cccam_class_cccam_version_list,
+      .def.i    = CCCAM_VERSION_2_3_0,
+    },
+#endif
+    {
+      .type     = PT_BOOL,
+      .id       = "emm",
+      .name     = N_("Update card (EMM)"),
+      .desc     = N_("Enable/disable offering of Entitlement Management Message updates."),
+      .off      = offsetof(cccam_t, cccam_emm),
+      .def.i    = 1
+    },
+    {
+      .type     = PT_BOOL,
+      .id       = "emmex",
+      .name     = N_("Updates from one mux (EMM)"),
+      .desc     = N_("Update Entitlement Management Messages from one mux only."),
+      .off      = offsetof(cccam_t, cccam_emmex),
+      .def.i    = 1
+    },
+    {
+      .type     = PT_INT,
+      .id       = "keepalive_interval",
+      .name     = N_("Keepalive interval"),
+      .desc     = N_("Keepalive interval in seconds"),
+      .off      = offsetof(cccam_t, cccam_keepalive_interval),
+      .def.i    = CCCAM_KEEPALIVE_INTERVAL,
+    },
+    { }
+  }
+};
+
+/*
+ *
+ */
+caclient_t *cccam_create(void)
+{
+  cccam_t *cccam = calloc(1, sizeof(*cccam));
+
+  pthread_mutex_init(&cccam->cccam_mutex, NULL);
+  tvh_cond_init(&cccam->cccam_cond);
+  cccam->cac_free         = cccam_free;
+  cccam->cac_start        = cccam_service_start;
+  cccam->cac_conf_changed = cccam_conf_changed;
+  cccam->cac_caid_update  = cccam_caid_update;
+  cccam->cccam_keepalive_interval = CCCAM_KEEPALIVE_INTERVAL;
+  cccam->cccam_version = CCCAM_VERSION_2_3_0;
+  return (caclient_t *)cccam;
+}
+
index 486032c298886d332650916a045774fd36708b11..2e423dc526a01d5aea0c31c1d4456361454070a9 100644 (file)
@@ -152,7 +152,7 @@ descrambler_init ( void )
   uint32_t caid;
   int idx;
 
-#if (ENABLE_CWC || ENABLE_CAPMT) && !ENABLE_DVBCSA
+#if (ENABLE_CWC || ENABLE_CAPMT || ENABLE_CCCAM) && !ENABLE_DVBCSA
   ffdecsa_init();
 #endif
   caclient_init();
index 7c2aac2a1d5fefc2d600934f36d5d6d90dc81706..1a918097df614c7513531af0f12d76905eea80e0 100644 (file)
@@ -144,7 +144,7 @@ const char      *tvheadend_cwd0;
 const char      *tvheadend_cwd;
 const char      *tvheadend_webroot;
 const tvh_caps_t tvheadend_capabilities[] = {
-#if ENABLE_CWC || ENABLE_CAPMT || ENABLE_CONSTCW
+#if ENABLE_CWC || ENABLE_CAPMT || ENABLE_CONSTCW || ENABLE_CCCAM
   { "caclient", NULL },
 #endif
 #if ENABLE_LINUXDVB || ENABLE_SATIP_CLIENT || ENABLE_HDHOMERUN_CLIENT
index 9780888eecc16a54555fa0cbbcd400b9f329b22a..acd2ab0b71d233d54c468ca8dcd4ec1c039eff9e 100644 (file)
@@ -138,6 +138,7 @@ tvhlog_subsys_t tvhlog_subsystems[] = {
   [LS_CSA]           = { "csa",           N_("CSA (descrambling)") },
   [LS_CAPMT]         = { "capmt",         N_("CAPMT CA Client") },
   [LS_CWC]           = { "cwc",           N_("CWC CA Client") },
+  [LS_CCCAM]         = { "cccam",         N_("CWC CCCam Client") },
   [LS_DVBCAM]        = { "dvbcam",        N_("DVB CAM Client") },
   [LS_DVR]           = { "dvr",           N_("Digital Video Recorder") },
   [LS_EPG]           = { "epg",           N_("Electronic Program Guide") },
index 320309334cb6aa6f1d17b30af2a7c8af4f87848d..53c2c985211d2f83050daf1f4eafef64c2d34f75 100644 (file)
@@ -162,6 +162,7 @@ enum {
   LS_CSA,
   LS_CAPMT,
   LS_CWC,
+  LS_CCCAM,
   LS_DVBCAM,
   LS_DVR,
   LS_EPG,