Project

General

Profile

RE: Biss key » ccw.patch

patch4ccw_tvh_20140707 - senora doddel, 2014-07-07 17:13

View differences:

Makefile 2014-07-05 08:12:15.000000000 +0100 → Makefile 2014-07-07 14:18:19.416856829 +0100
21 21
#
22 22

  
23 23
include $(dir $(lastword $(MAKEFILE_LIST))).config.mk
24
PROG    := $(BUILDDIR)/tvheadend
24
PROG	:= $(BUILDDIR)/tvheadend
25 25

  
26 26
#
27 27
# Common compiler flags
28 28
#
29 29

  
30
CFLAGS  += -g -O2
31
CFLAGS  += -Wall -Werror -Wwrite-strings -Wno-deprecated-declarations
32
CFLAGS  += -Wmissing-prototypes
33
CFLAGS  += -fms-extensions -funsigned-char -fno-strict-aliasing
34
CFLAGS  += -D_FILE_OFFSET_BITS=64
35
CFLAGS  += -I${BUILDDIR} -I${ROOTDIR}/src -I${ROOTDIR}
36
LDFLAGS += -ldl -lpthread -lm
30
CFLAGS	+= -g -O2
31
CFLAGS	+= -Wall -Werror -Wwrite-strings -Wno-deprecated-declarations
32
CFLAGS	+= -Wmissing-prototypes
33
CFLAGS	+= -fms-extensions -funsigned-char -fno-strict-aliasing
34
CFLAGS	+= -D_FILE_OFFSET_BITS=64
35
CFLAGS	+= -I${BUILDDIR} -I${ROOTDIR}/src -I${ROOTDIR}
36
LDFLAGS	+= -ldl -lpthread -lm
37 37
ifeq ($(CONFIG_LIBICONV),yes)
38 38
LDFLAGS += -liconv
39 39
endif
......
44 44
endif
45 45

  
46 46
ifeq ($(COMPILER), clang)
47
CFLAGS  += -Wno-microsoft -Qunused-arguments -Wno-unused-function
48
CFLAGS  += -Wno-unused-value -Wno-tautological-constant-out-of-range-compare
49
CFLAGS  += -Wno-parentheses-equality -Wno-incompatible-pointer-types
47
CFLAGS	+= -Wno-microsoft -Qunused-arguments -Wno-unused-function
48
CFLAGS	+= -Wno-unused-value -Wno-tautological-constant-out-of-range-compare
49
CFLAGS	+= -Wno-parentheses-equality -Wno-incompatible-pointer-types
50 50
endif
51 51

  
52 52
vpath %.c $(ROOTDIR)
......
70 70
#
71 71

  
72 72
ifndef V
73
ECHO   = printf "%-16s%s\n" $(1) $(2)
74
BRIEF  = CC MKBUNDLE CXX
75
MSG    = $(subst $(BUILDDIR)/,,$@)
73
ECHO	= printf "%-16s%s\n" $(1) $(2)
74
BRIEF	= CC MKBUNDLE CXX
75
MSG		= $(subst $(BUILDDIR)/,,$@)
76 76
$(foreach VAR,$(BRIEF), \
77 77
	$(eval $(VAR) = @$$(call ECHO,$(VAR),$$(MSG)); $($(VAR))))
78 78
endif
......
80 80
#
81 81
# Core
82 82
#
83
SRCS =  src/version.c \
83
SRCS =	src/version.c \
84 84
	src/uuid.c \
85 85
	src/main.c \
86 86
	src/tvhlog.c \
......
117 117
	src/trap.c \
118 118
	src/avg.c \
119 119
	src/htsstr.c \
120
  src/tvhpoll.c \
120
	src/tvhpoll.c \
121 121
	src/huffman.c \
122 122
	src/filebundle.c \
123 123
	src/config.c \
......
157 157
	src/parsers/parser_h264.c \
158 158
	src/parsers/parser_latm.c \
159 159
	src/parsers/parser_avc.c \
160
	src/parsers/parser_teletext.c \
160
	src/parsers/parser_teletext.c
161 161

  
162
SRCS += src/epggrab/module.c\
163
	src/epggrab/channel.c\
164
	src/epggrab/module/pyepg.c\
165
	src/epggrab/module/xmltv.c\
162
SRCS += src/epggrab/module.c \
163
	src/epggrab/channel.c \
164
	src/epggrab/module/pyepg.c \
165
	src/epggrab/module/xmltv.c
166 166

  
167 167
SRCS += src/plumbing/tsfix.c \
168 168
	src/plumbing/globalheaders.c
......
170 170
SRCS += src/dvr/dvr_db.c \
171 171
	src/dvr/dvr_rec.c \
172 172
	src/dvr/dvr_autorec.c \
173
	src/dvr/dvr_cutpoints.c \
173
	src/dvr/dvr_cutpoints.c
174 174

  
175 175
SRCS += src/webui/webui.c \
176 176
	src/webui/comet.c \
177 177
	src/webui/extjs.c \
178 178
	src/webui/simpleui.c \
179 179
	src/webui/statedump.c \
180
	src/webui/html.c\
181
	src/webui/webui_api.c\
180
	src/webui/html.c \
181
	src/webui/webui_api.c
182 182

  
183 183
SRCS += src/muxer.c \
184 184
	src/muxer/muxer_pass.c \
185 185
	src/muxer/muxer_tvh.c \
186 186
	src/muxer/tvh/ebml.c \
187
	src/muxer/tvh/mkmux.c \
187
	src/muxer/tvh/mkmux.c
188 188

  
189 189
#
190 190
# Optional code
......
204 204
	src/input/mpegts/dvb_psi.c \
205 205
	src/input/mpegts/tsdemux.c \
206 206
	src/input/mpegts/mpegts_mux_sched.c \
207
  src/input/mpegts/mpegts_network_scan.c \
207
	src/input/mpegts/mpegts_network_scan.c
208 208

  
209 209
# MPEGTS DVB
210 210
SRCS-${CONFIG_MPEGTS_DVB} += \
211
        src/input/mpegts/mpegts_network_dvb.c \
212
        src/input/mpegts/mpegts_mux_dvb.c \
213
        src/input/mpegts/scanfile.c
211
	src/input/mpegts/mpegts_network_dvb.c \
212
	src/input/mpegts/mpegts_mux_dvb.c \
213
	src/input/mpegts/scanfile.c
214 214

  
215 215
# MPEGTS EPG
216 216
SRCS-$(CONFIG_MPEGTS) += \
217 217
	src/epggrab/otamux.c\
218 218
	src/epggrab/module/eit.c \
219 219
	src/epggrab/support/freesat_huffman.c \
220
	src/epggrab/module/opentv.c \
220
	src/epggrab/module/opentv.c
221 221

  
222 222
# LINUX DVB
223 223
SRCS-${CONFIG_LINUXDVB} += \
224
        src/input/mpegts/linuxdvb/linuxdvb.c \
225
        src/input/mpegts/linuxdvb/linuxdvb_adapter.c \
226
        src/input/mpegts/linuxdvb/linuxdvb_frontend.c \
227
        src/input/mpegts/linuxdvb/linuxdvb_satconf.c \
228
        src/input/mpegts/linuxdvb/linuxdvb_lnb.c \
229
        src/input/mpegts/linuxdvb/linuxdvb_switch.c \
230
        src/input/mpegts/linuxdvb/linuxdvb_rotor.c \
231
        src/input/mpegts/linuxdvb/linuxdvb_en50494.c
224
	src/input/mpegts/linuxdvb/linuxdvb.c \
225
	src/input/mpegts/linuxdvb/linuxdvb_adapter.c \
226
	src/input/mpegts/linuxdvb/linuxdvb_frontend.c \
227
	src/input/mpegts/linuxdvb/linuxdvb_satconf.c \
228
	src/input/mpegts/linuxdvb/linuxdvb_lnb.c \
229
	src/input/mpegts/linuxdvb/linuxdvb_switch.c \
230
	src/input/mpegts/linuxdvb/linuxdvb_rotor.c \
231
	src/input/mpegts/linuxdvb/linuxdvb_en50494.c
232 232

  
233 233
# SATIP
234 234
SRCS-${CONFIG_SATIP_CLIENT} += \
......
240 240
# IPTV
241 241
SRCS-${CONFIG_IPTV} += \
242 242
	src/input/mpegts/iptv/iptv.c \
243
        src/input/mpegts/iptv/iptv_mux.c \
244
        src/input/mpegts/iptv/iptv_service.c \
245
        src/input/mpegts/iptv/iptv_http.c \
246
        src/input/mpegts/iptv/iptv_udp.c \
243
	src/input/mpegts/iptv/iptv_mux.c \
244
	src/input/mpegts/iptv/iptv_service.c \
245
	src/input/mpegts/iptv/iptv_http.c \
246
	src/input/mpegts/iptv/iptv_udp.c
247 247

  
248 248
# TSfile
249 249
SRCS-$(CONFIG_TSFILE) += \
250
        src/input/mpegts/tsfile/tsfile.c \
251
        src/input/mpegts/tsfile/tsfile_input.c \
252
        src/input/mpegts/tsfile/tsfile_mux.c \
250
	src/input/mpegts/tsfile/tsfile.c \
251
	src/input/mpegts/tsfile/tsfile_input.c \
252
	src/input/mpegts/tsfile/tsfile_mux.c
253 253

  
254 254
# Timeshift
255 255
SRCS-${CONFIG_TIMESHIFT} += \
256 256
	src/timeshift.c \
257 257
	src/timeshift/timeshift_filemgr.c \
258 258
	src/timeshift/timeshift_writer.c \
259
	src/timeshift/timeshift_reader.c \
259
	src/timeshift/timeshift_reader.c
260 260

  
261 261
# Inotify
262 262
SRCS-${CONFIG_INOTIFY} += \
263
	src/dvr/dvr_inotify.c \
263
	src/dvr/dvr_inotify.c
264 264

  
265 265
# Avahi
266 266
SRCS-$(CONFIG_AVAHI) += src/avahi.c
......
271 271
# libav
272 272
SRCS-$(CONFIG_LIBAV) += src/libav.c \
273 273
	src/muxer/muxer_libav.c \
274
	src/plumbing/transcoding.c \
274
	src/plumbing/transcoding.c
275 275

  
276 276
# Tvhcsa
277 277
SRCS-${CONFIG_TVHCSA} += \
......
279 279

  
280 280
# CWC
281 281
SRCS-${CONFIG_CWC} += \
282
	src/descrambler/cwc.c \
283
	
282
	src/descrambler/cwc.c
283

  
284 284
# CAPMT
285 285
SRCS-${CONFIG_CAPMT} += \
286
	src/descrambler/capmt.c
286
	src/descrambler/capmt.c \
287
	src/descrambler/ccw.c
287 288

  
288 289
# FFdecsa
289 290
ifneq ($(CONFIG_DVBCSA),yes)
290 291
FFDECSA-$(CONFIG_CAPMT) = yes
291
FFDECSA-$(CONFIG_CWC)   = yes
292
FFDECSA-$(CONFIG_CWC)	= yes
292 293
endif
293 294

  
294 295
ifeq ($(FFDECSA-yes),yes)
295 296
SRCS-yes += src/descrambler/ffdecsa/ffdecsa_interface.c \
296
	    src/descrambler/ffdecsa/ffdecsa_int.c
297
SRCS-${CONFIG_MMX}  += src/descrambler/ffdecsa/ffdecsa_mmx.c
297
		src/descrambler/ffdecsa/ffdecsa_int.c
298
SRCS-${CONFIG_MMX}	+= src/descrambler/ffdecsa/ffdecsa_mmx.c
298 299
SRCS-${CONFIG_SSE2} += src/descrambler/ffdecsa/ffdecsa_sse2.c
299
${BUILDDIR}/src/descrambler/ffdecsa/ffdecsa_mmx.o  : CFLAGS += -mmmx
300
${BUILDDIR}/src/descrambler/ffdecsa/ffdecsa_sse2.o : CFLAGS += -msse2
300
${BUILDDIR}/src/descrambler/ffdecsa/ffdecsa_mmx.o	: CFLAGS += -mmmx
301
${BUILDDIR}/src/descrambler/ffdecsa/ffdecsa_sse2.o	: CFLAGS += -msse2
301 302
endif
302 303

  
303 304
# File bundles
304
SRCS-${CONFIG_BUNDLE}     += bundle.c
305
BUNDLES-yes               += docs/html docs/docresources src/webui/static
306
BUNDLES-yes               += data/conf
307
BUNDLES-${CONFIG_DVBSCAN} += data/dvb-scan
308
BUNDLES                    = $(BUNDLES-yes)
309
ALL-$(CONFIG_DVBSCAN)     += check_dvb_scan
305
SRCS-${CONFIG_BUNDLE}		+= bundle.c
306
BUNDLES-yes					+= docs/html docs/docresources src/webui/static
307
BUNDLES-yes					+= data/conf
308
BUNDLES-${CONFIG_DVBSCAN}	+= data/dvb-scan
309
BUNDLES						= $(BUNDLES-yes)
310
ALL-$(CONFIG_DVBSCAN)		+= check_dvb_scan
310 311

  
311 312
#
312 313
# Add-on modules
......
318 319
# Variable transformations
319 320
#
320 321

  
321
SRCS      += $(SRCS-yes)
322
OBJS       = $(SRCS:%.c=$(BUILDDIR)/%.o)
323
OBJS_EXTRA = $(SRCS_EXTRA:%.c=$(BUILDDIR)/%.so)
324
DEPS       = ${OBJS:%.o=%.d}
322
SRCS		+= $(SRCS-yes)
323
OBJS		= $(SRCS:%.c=$(BUILDDIR)/%.o)
324
OBJS_EXTRA	= $(SRCS_EXTRA:%.c=$(BUILDDIR)/%.so)
325
DEPS		= ${OBJS:%.o=%.d}
325 326

  
326 327
#
327 328
# Build Rules
......
398 399
check_dvb_scan: $(ROOTDIR)/data/dvb-scan/.stamp
399 400

  
400 401
# dvb-s / enigma2 / satellites.xml
401
$(ROOTDIR)/data/dvb-scan/dvb-s/.stamp: $(ROOTDIR)/data/satellites.xml \
402
                                       $(ROOTDIR)/data/dvb-scan/.stamp
402
$(ROOTDIR)/data/dvb-scan/dvb-s/.stamp:	$(ROOTDIR)/data/satellites.xml \
403
										$(ROOTDIR)/data/dvb-scan/.stamp
403 404
	@echo "Generating data/dvb-scan/dvb-s from data/satellites.xml"
404 405
	@if ! test -s $(ROOTDIR)/data/satellites.xml ; then echo "Put your satellites.xml file to $(ROOTDIR)/data/satellites.xml"; exit 1; fi
405 406
	@if ! test -d $(ROOTDIR)/data/dvb-scan/dvb-s ; then mkdir $(ROOTDIR)/data/dvb-scan/dvb-s ; fi
src/descrambler/capmt.c 2014-07-05 08:12:15.000000000 +0100 → src/descrambler/capmt.c 2014-07-07 13:00:27.416825228 +0100
1824 1824
                               capmt->capmt_port);
1825 1825
    td->td_nicename    = strdup(buf);
1826 1826
    td->td_service     = s;
1827
    td->dtype          = 0;
1827 1828
    td->td_stop        = capmt_service_destroy;
1828 1829
    td->td_caid_change = capmt_caid_change;
1829 1830
    LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
src/descrambler/ccw.c 1970-01-01 01:00:00.000000000 +0100 → src/descrambler/ccw.c 2014-07-07 15:28:11.740885185 +0100
1
 /*
2
 *  tvheadend, CCW
3
 *  Copyright (C) 2012
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 <pthread.h>
20
#include <assert.h>
21
#include <string.h>
22
#include <stdio.h>
23
#include <unistd.h>
24
#include <stdlib.h>
25
#include <stdarg.h>
26
#include <errno.h>
27
#include <netinet/in.h>
28
#include <arpa/inet.h>
29
#include <ctype.h>
30
#include <signal.h>
31
#include <sys/types.h>
32
#include <sys/socket.h>
33
#include <openssl/des.h>
34

  
35
#include "tvheadend.h"
36
#include "tcp.h"
37
#include "ccw.h"
38
#include "notify.h"
39
#include "atomic.h"
40
#include "dtable.h"
41
#include "subscriptions.h"
42
#include "service.h"
43
#include "input.h"
44
#include "input/mpegts/tsdemux.h"
45
#include "tvhcsa.h"
46
#include "input/mpegts/linuxdvb/linuxdvb_private.h"
47

  
48
/**
49
 *
50
 */
51
TAILQ_HEAD(ccw_queue, ccw);
52
LIST_HEAD(ccw_service_list, ccw_service);
53
static struct ccw_queue ccws;
54
static pthread_cond_t ccw_config_changed;
55

  
56
/**
57
 *
58
 */
59
typedef struct ccw_service {
60
  th_descrambler_t;
61

  
62
  mpegts_service_t *ct_service;
63

  
64
  struct ccw *ct_ccw;
65

  
66
  LIST_ENTRY(ccw_service) ct_link;
67

  
68
  /**
69
   * Status of the key(s) in ct_keys
70
   */
71
  enum {
72
    CT_UNKNOWN,
73
    CT_RESOLVED,
74
    CT_FORBIDDEN
75
  } ct_keystate;
76
  
77
  tvhcsa_t cs_csa;
78

  
79
  /* buffer for keystruct */
80
#if ENABLE_DVBCSA
81
  struct dvbcsa_bs_key_s *ct_key_even;
82
  struct dvbcsa_bs_key_s *ct_key_odd;
83
#else
84
  void *ct_keys;
85
#endif
86

  
87
  /* CSA */
88
  int      ct_cluster_size;
89
  uint8_t *ct_tsbcluster;
90
  int      ct_fill;
91
#if ENABLE_DVBCSA
92
  struct dvbcsa_bs_batch_s *ct_tsbbatch_even;
93
  struct dvbcsa_bs_batch_s *ct_tsbbatch_odd;
94
  int ct_fill_even;
95
  int ct_fill_odd;
96
#endif
97

  
98
  uint8_t ccw_evenkey[8];
99
  uint8_t ccw_oddkey[8];
100

  
101
} ccw_service_t;
102

  
103
/**
104
 *
105
 */
106
typedef struct ccw {
107
  pthread_cond_t ccw_cond;
108

  
109
  TAILQ_ENTRY(ccw) ccw_link; /* Linkage protected via global_lock */
110

  
111
  struct ccw_service_list ccw_services;
112

  
113
  uint16_t ccw_caid;  // CAID
114
  uint16_t ccw_tid;   // Transponder ID
115
  uint16_t ccw_sid;   // Channel ID
116
  uint8_t ccw_confedkey[8];  // Key
117
  char *ccw_comment;
118
  char *ccw_id;
119

  
120
  int   ccw_enabled;
121
  int   ccw_running;
122
  int   ccw_reconfigure;
123

  
124
} ccw_t;
125

  
126
/**
127
 * global_lock is held
128
 * s_stream_mutex is held
129
 */
130
static void 
131
ccw_service_destroy(th_descrambler_t *td)
132
{
133
  tvhlog(LOG_INFO, "ccw", "Removing CCW key from service");
134

  
135
  ccw_service_t *ct = (ccw_service_t *)td;
136

  
137
  LIST_REMOVE(td, td_service_link);
138
  LIST_REMOVE(ct, ct_link);
139

  
140
#if ENABLE_DVBCSA
141
  dvbcsa_bs_key_free(ct->ct_key_odd);
142
  dvbcsa_bs_key_free(ct->ct_key_even);
143
  free(ct->ct_tsbbatch_odd);
144
  free(ct->ct_tsbbatch_even);
145
#else
146
  free_key_struct(ct->ct_keys);
147
#endif
148
  free(ct->ct_tsbcluster);
149
  
150
  tvhcsa_destroy(&ct->cs_csa);
151
  free(ct);
152
}
153

  
154
/**
155
 *
156
 */
157
#if ENABLE_DVBCSA
158
static int
159
ccw_descramble(th_descrambler_t *td, service_t *t, struct elementary_stream *st,
160
     const uint8_t *tsb)
161
{
162
  ccw_service_t *ct = (ccw_service_t *)td;
163
  uint8_t *pkt;
164
  int xc0;
165
  int ev_od;
166
  int len;
167
  int offset;
168
  int n;
169
  // FIXME: //int residue;
170
  int i;
171
  uint8_t *t0;
172

  
173
  tvhlog(LOG_DEBUG, "ccw", "ccw_descramble");
174

  
175
  if(ct->ct_keystate == CT_FORBIDDEN)
176
    return 1;
177

  
178
  if(ct->ct_keystate != CT_RESOLVED)
179
    return -1;
180

  
181
  pkt = ct->ct_tsbcluster + ct->ct_fill * 188;
182
  memcpy(pkt, tsb, 188);
183
  ct->ct_fill++;
184

  
185
  do { // handle this packet
186
    xc0 = pkt[3] & 0xc0;
187
    if(xc0 == 0x00) { // clear
188
      break;
189
    }
190
    if(xc0 == 0x40) { // reserved
191
      break;
192
    }
193
    if(xc0 == 0x80 || xc0 == 0xc0) { // encrypted
194
      ev_od = (xc0 & 0x40) >> 6; // 0 even, 1 odd
195
      pkt[3] &= 0x3f;  // consider it decrypted now
196
      if(pkt[3] & 0x20) { // incomplete packet
197
        offset = 4 + pkt[4] + 1;
198
        len = 188 - offset;
199
        n = len >> 3;
200
        // FIXME: //residue = len - (n << 3);
201
        if(n == 0) { // decrypted==encrypted!
202
          break; // this doesn't need more processing
203
        }
204
      } else {
205
        len = 184;
206
        offset = 4;
207
        // FIXME: //n = 23;
208
        // FIXME: //residue = 0;
209
      }
210
      if(ev_od == 0) {
211
        ct->ct_tsbbatch_even[ct->ct_fill_even].data = pkt + offset;
212
        ct->ct_tsbbatch_even[ct->ct_fill_even].len = len;
213
        ct->ct_fill_even++;
214
      } else {
215
        ct->ct_tsbbatch_odd[ct->ct_fill_odd].data = pkt + offset;
216
        ct->ct_tsbbatch_odd[ct->ct_fill_odd].len = len;
217
        ct->ct_fill_odd++;
218
      }
219
    }
220
  } while(0);
221

  
222
  if(ct->ct_fill != ct->ct_cluster_size)
223
    return 0;
224

  
225
  if(ct->ct_fill_even) {
226
    ct->ct_tsbbatch_even[ct->ct_fill_even].data = NULL;
227
    dvbcsa_bs_decrypt(ct->ct_key_even, ct->ct_tsbbatch_even, 184);
228
    ct->ct_fill_even = 0;
229
  }
230
  if(ct->ct_fill_odd) {
231
    ct->ct_tsbbatch_odd[ct->ct_fill_odd].data = NULL;
232
    dvbcsa_bs_decrypt(ct->ct_key_odd, ct->ct_tsbbatch_odd, 184);
233
    ct->ct_fill_odd = 0;
234
  }
235

  
236
    t0 = ct->ct_tsbcluster;
237
    for(i = 0; i < ct->ct_fill; i++) {
238
      ts_recv_packet2((mpegts_service_t*)t, t0);
239
      t0 += 188;
240
    }
241
  ct->ct_fill = 0;
242
  return 0;
243
} 
244
#else
245
static int
246
ccw_descramble(th_descrambler_t *td, service_t *t, struct elementary_stream *st,
247
     const uint8_t *tsb)
248
{
249
  ccw_service_t *ct = (ccw_service_t *)td;
250
  int r, i;
251
  unsigned char *vec[3];
252
  uint8_t *t0;
253

  
254
  tvhlog(LOG_DEBUG, "ccw", "ccw_descramble");
255

  
256
  if(ct->ct_keystate == CT_FORBIDDEN)
257
    return 1;
258

  
259
  if(ct->ct_keystate != CT_RESOLVED)
260
    return -1;
261

  
262
  memcpy(ct->ct_tsbcluster + ct->ct_fill * 188, tsb, 188);
263
  ct->ct_fill++;
264

  
265
  if(ct->ct_fill != ct->ct_cluster_size)
266
    return 0;
267

  
268
  ct->ct_fill = 0;
269

  
270
  vec[0] = ct->ct_tsbcluster;
271
  vec[1] = ct->ct_tsbcluster + ct->ct_cluster_size * 188;
272
  vec[2] = NULL;
273

  
274
  while(1) {
275
    t0 = vec[0];
276
    r = decrypt_packets(ct->ct_keys, vec);
277
    if(r == 0)
278
      break;
279
    for(i = 0; i < r; i++) {
280
      ts_recv_packet2((mpegts_service_t*)t, t0);
281
      t0 += 188;
282
    }
283
  }
284
  return 0;
285
}
286
#endif
287

  
288
/**
289
 *
290
 */
291
static inline elementary_stream_t *
292
ccw_find_stream_by_caid(service_t *t, int caid)
293
{
294
  elementary_stream_t *st;
295
  caid_t *c;
296

  
297
  TAILQ_FOREACH(st, &t->s_components, es_link) {
298
    LIST_FOREACH(c, &st->es_caids, link) {
299
      if(c->caid == caid)
300
    return st;
301
    }
302
  }
303
  return NULL;
304
}
305

  
306
/**
307
 * Check if our CAID's matches, and if so, link
308
 *
309
 * global_lock is held
310
 */
311
void
312
ccw_service_start(service_t *t)
313
{
314
  ccw_t *ccw;
315
  ccw_service_t *ct;
316
  th_descrambler_t *td;
317
  mpegts_service_t *ms = (mpegts_service_t*)t;
318
  linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)ms->s_dvb_active_input;
319
  int adapter_num = lfe->lfe_adapter->la_dvb_number;
320
  char buf[512];
321

  
322
  lock_assert(&global_lock);
323
  
324
  extern const idclass_t mpegts_service_class;
325
  if (!idnode_is_instance(&t->s_id, &mpegts_service_class))
326
    return;
327

  
328
  TAILQ_FOREACH(ccw, &ccws, ccw_link) {
329
    if(ccw->ccw_caid == 0)
330
      continue;
331

  
332
    if(ccw_find_stream_by_caid(t, ccw->ccw_caid) == NULL)
333
      continue;
334

  
335
    if(ms->s_dvb_service_id != ccw->ccw_sid)
336
      continue;
337

  
338
    if(ms->s_dvb_mux->mm_tsid != ccw->ccw_tid)
339
      continue;
340

  
341
    tvhlog(LOG_INFO, "ccw","Starting ccw key for service \"%s\" on tuner %d", ms->s_dvb_svcname,adapter_num);
342

  
343
    /* create new ccw service */
344
    ct                  = calloc(1, sizeof(ccw_service_t));
345
#if ENABLE_DVBCSA
346
    ct->ct_cluster_size  = dvbcsa_bs_batch_size();
347
#else
348
    ct->ct_cluster_size  = get_suggested_cluster_size();
349
#endif
350
    ct->ct_tsbcluster   = malloc(ct->ct_cluster_size * 188);
351
    
352
#if ENABLE_DVBCSA
353
    ct->ct_tsbbatch_even = malloc((ct->ct_cluster_size + 1) *
354
                             sizeof(struct dvbcsa_bs_batch_s));
355
    ct->ct_tsbbatch_odd  = malloc((ct->ct_cluster_size + 1) *
356
                             sizeof(struct dvbcsa_bs_batch_s));
357
#endif
358

  
359
#if ENABLE_DVBCSA
360
    ct->ct_key_even   = dvbcsa_bs_key_alloc();
361
    ct->ct_key_odd    = dvbcsa_bs_key_alloc();
362
#else
363
    ct->ct_keys       = get_key_struct();
364
#endif
365
    ct->ct_ccw      = ccw;
366
    ct->ct_service  = ms;
367

  
368
    memcpy(ct->ccw_evenkey,ccw->ccw_confedkey,8);
369
    memcpy(ct->ccw_oddkey,ccw->ccw_confedkey,8);
370

  
371
#if ENABLE_DVBCSA
372
	dvbcsa_bs_key_set(ct->ccw_evenkey, ct->ct_key_even);
373
	dvbcsa_bs_key_set(ct->ccw_oddkey, ct->ct_key_odd);
374
#else
375
	set_even_control_word(ct->ct_keys, ct->ccw_evenkey);
376
	set_odd_control_word(ct->ct_keys, ct->ccw_oddkey);
377
#endif
378

  
379
    if(ct->ct_keystate != CT_RESOLVED)
380
        tvhlog(LOG_INFO, "ccw", "Obtained key for service \"%s\"",ms->s_dvb_svcname);
381

  
382
    tvhlog(LOG_DEBUG, "ccw",
383
       "Key for service \"%s\" "
384
       "even: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x"
385
       " odd: %02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x",
386
       ms->s_dvb_svcname,
387
       ct->ccw_evenkey[0], ct->ccw_evenkey[1], ct->ccw_evenkey[2], ct->ccw_evenkey[3],
388
           ct->ccw_evenkey[4], ct->ccw_evenkey[5], ct->ccw_evenkey[6], ct->ccw_evenkey[7],
389
           ct->ccw_oddkey[0], ct->ccw_oddkey[1], ct->ccw_oddkey[2], ct->ccw_oddkey[3],
390
           ct->ccw_oddkey[4], ct->ccw_oddkey[5], ct->ccw_oddkey[6],  ct->ccw_oddkey[7]);
391
    ct->ct_keystate = CT_RESOLVED;
392

  
393
    td = (th_descrambler_t *)ct;
394
    tvhcsa_init(td->td_csa = &ct->cs_csa);
395
    snprintf(buf, sizeof(buf), "ccw-%i-%i", ccw->ccw_tid, ccw->ccw_sid);
396
    td->td_nicename      = strdup(buf);
397
    td->td_service       = t;
398
    td->dtype            = 1;
399
    td->td_stop          = ccw_service_destroy;
400
    td->td_descramble    = ccw_descramble;
401
    LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
402

  
403
    LIST_INSERT_HEAD(&ccw->ccw_services, ct, ct_link);
404
  }
405
}
406

  
407
/**
408
 *
409
 */
410
static void
411
ccw_destroy(ccw_t *ccw)
412
{
413
  lock_assert(&global_lock);
414
  TAILQ_REMOVE(&ccws, ccw, ccw_link);
415
  ccw->ccw_running = 0;
416
  pthread_cond_signal(&ccw->ccw_cond);
417
}
418

  
419
/**
420
 *
421
 */
422
static ccw_t *
423
ccw_entry_find(const char *id, int create)
424
{
425
//  pthread_attr_t attr;
426
//  pthread_t ptid;
427
  char buf[20];
428
  ccw_t *ccw;
429
  static int tally;
430

  
431
  if(id != NULL) {
432
    TAILQ_FOREACH(ccw, &ccws, ccw_link)
433
      if(!strcmp(ccw->ccw_id, id))
434
  return ccw;
435
  }
436
  if(create == 0)
437
    return NULL;
438

  
439
  if(id == NULL) {
440
    tally++;
441
    snprintf(buf, sizeof(buf), "%d", tally);
442
    id = buf;
443
  } else {
444
    tally = MAX(atoi(id), tally);
445
  }
446

  
447
  ccw = calloc(1, sizeof(ccw_t));
448
  pthread_cond_init(&ccw->ccw_cond, NULL);
449
  ccw->ccw_id      = strdup(id);
450
  ccw->ccw_running = 1;
451

  
452
  TAILQ_INSERT_TAIL(&ccws, ccw, ccw_link);
453

  
454
//  pthread_attr_init(&attr);
455
//  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
456
//  pthread_create(&ptid, &attr, ccw_thread, ccw);
457
//  pthread_attr_destroy(&attr);
458

  
459
  return ccw;
460
}
461

  
462
/**
463
 *
464
 */
465
static htsmsg_t *
466
ccw_record_build(ccw_t *ccw)
467
{
468
  htsmsg_t *e = htsmsg_create_map();
469
  char buf[100];
470

  
471
  htsmsg_add_str(e, "id", ccw->ccw_id);
472
  htsmsg_add_u32(e, "enabled",  !!ccw->ccw_enabled);
473

  
474
  htsmsg_add_u32(e, "caid", ccw->ccw_caid);
475
  htsmsg_add_u32(e, "tid", ccw->ccw_tid);
476
  htsmsg_add_u32(e, "sid", ccw->ccw_sid);
477

  
478
  snprintf(buf, sizeof(buf),
479
       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
480
       ccw->ccw_confedkey[0],
481
       ccw->ccw_confedkey[1],
482
       ccw->ccw_confedkey[2],
483
       ccw->ccw_confedkey[3],
484
       ccw->ccw_confedkey[4],
485
       ccw->ccw_confedkey[5],
486
       ccw->ccw_confedkey[6],
487
       ccw->ccw_confedkey[7]);
488

  
489
  htsmsg_add_str(e, "key", buf);
490
  htsmsg_add_str(e, "comment", ccw->ccw_comment ?: "");
491

  
492
  return e;
493
}
494

  
495
/**
496
 *
497
 */
498
static int
499
nibble(char c)
500
{
501
  switch(c) {
502
  case '0' ... '9':
503
    return c - '0';
504
  case 'a' ... 'f':
505
    return c - 'a' + 10;
506
  case 'A' ... 'F':
507
    return c - 'A' + 10;
508
  default:
509
    return 0;
510
  }
511
}
512

  
513
/**
514
 *
515
 */
516
static htsmsg_t *
517
ccw_entry_update(void *opaque, const char *id, htsmsg_t *values, int maycreate)
518
{
519
  ccw_t *ccw;
520
  const char *s;
521
  uint32_t u32;
522
  uint8_t key[8];
523
  int u, l, i;
524

  
525
  if((ccw = ccw_entry_find(id, maycreate)) == NULL)
526
    return NULL;
527

  
528
  lock_assert(&global_lock);
529

  
530
  if(!htsmsg_get_u32(values, "caid", &u32))
531
    ccw->ccw_caid = u32;
532
  if(!htsmsg_get_u32(values, "tid", &u32))
533
    ccw->ccw_tid = u32;
534
  if(!htsmsg_get_u32(values, "sid", &u32))
535
    ccw->ccw_sid = u32;
536

  
537
  if((s = htsmsg_get_str(values, "key")) != NULL) {
538
    for(i = 0; i < 8; i++) {
539
      while(*s != 0 && !isxdigit(*s)) s++;
540
      if(*s == 0)
541
    break;
542
      u = nibble(*s++);
543
      while(*s != 0 && !isxdigit(*s)) s++;
544
      if(*s == 0)
545
    break;
546
      l = nibble(*s++);
547
      key[i] = (u << 4) | l;
548
    }
549
    memcpy(ccw->ccw_confedkey, key, 8);
550
  }
551

  
552
  if((s = htsmsg_get_str(values, "comment")) != NULL) {
553
    free(ccw->ccw_comment);
554
    ccw->ccw_comment = strdup(s);
555
  }
556

  
557
  if(!htsmsg_get_u32(values, "enabled", &u32)) 
558
    ccw->ccw_enabled = u32;
559

  
560
  ccw->ccw_reconfigure = 1;
561

  
562
  pthread_cond_signal(&ccw->ccw_cond);
563

  
564
  pthread_cond_broadcast(&ccw_config_changed);
565

  
566
  return ccw_record_build(ccw);
567
}
568

  
569
/**
570
 *
571
 */
572
static int
573
ccw_entry_delete(void *opaque, const char *id)
574
{
575
  ccw_t *ccw;
576

  
577
  pthread_cond_broadcast(&ccw_config_changed);
578

  
579
  if((ccw = ccw_entry_find(id, 0)) == NULL)
580
    return -1;
581
  ccw_destroy(ccw);
582
  return 0;
583
}
584

  
585
/**
586
 *
587
 */
588
static htsmsg_t *
589
ccw_entry_get_all(void *opaque)
590
{
591
  htsmsg_t *r = htsmsg_create_list();
592
  ccw_t *ccw;
593

  
594
  TAILQ_FOREACH(ccw, &ccws, ccw_link)
595
    htsmsg_add_msg(r, NULL, ccw_record_build(ccw));
596

  
597
  return r;
598
}
599

  
600
/**
601
 *
602
 */
603
static htsmsg_t *
604
ccw_entry_get(void *opaque, const char *id)
605
{
606
  ccw_t *ccw;
607

  
608

  
609
  if((ccw = ccw_entry_find(id, 0)) == NULL)
610
    return NULL;
611
  return ccw_record_build(ccw);
612
}
613

  
614
/**
615
 *
616
 */
617
static htsmsg_t *
618
ccw_entry_create(void *opaque)
619
{
620
  pthread_cond_broadcast(&ccw_config_changed);
621
  return ccw_record_build(ccw_entry_find(NULL, 1));
622
}
623

  
624
/**
625
 *
626
 */
627
static const dtable_class_t ccw_dtc = {
628
  .dtc_record_get     = ccw_entry_get,
629
  .dtc_record_get_all = ccw_entry_get_all,
630
  .dtc_record_create  = ccw_entry_create,
631
  .dtc_record_update  = ccw_entry_update,
632
  .dtc_record_delete  = ccw_entry_delete,
633
  .dtc_read_access = ACCESS_ADMIN,
634
  .dtc_write_access = ACCESS_ADMIN,
635
  .dtc_mutex = &global_lock,
636
};
637

  
638
/**
639
 *
640
 */
641
void
642
ccw_init(void)
643
{
644
  dtable_t *dt;
645

  
646
  TAILQ_INIT(&ccws);
647

  
648
  pthread_cond_init(&ccw_config_changed, NULL);
649

  
650
  dt = dtable_create(&ccw_dtc, "ccw", NULL);
651
  dtable_load(dt);
652

  
653
}
654

  
655
/**
656
 *
657
 */
658
void
659
ccw_done(void)
660
{
661
  ccw_t *ccw;
662

  
663
  dtable_delete("ccw");
664
  pthread_mutex_lock(&global_lock);
665
  //pthread_mutex_lock(&ccw_mutex);
666
  while ((ccw = TAILQ_FIRST(&ccws)) != NULL)
667
    ccw_destroy(ccw);
668
  //pthread_mutex_unlock(&ccw_mutex);
669
  pthread_mutex_unlock(&global_lock);
670
}
src/descrambler/ccw.h 1970-01-01 01:00:00.000000000 +0100 → src/descrambler/ccw.h 2014-07-07 14:57:01.756872537 +0100
1
 /*
2
 *  tvheadend, CCW
3
 *  Copyright (C) 2012
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
#ifndef CCW_H_
20
#define CCW_H_
21

  
22
#if ENABLE_DVBCSA
23
#include <dvbcsa/dvbcsa.h>
24
#else
25
#include "ffdecsa/FFdecsa.h"
26
#endif
27

  
28
void ccw_init(void);
29

  
30
void ccw_done(void);
31

  
32
void ccw_service_start(struct service *t);
33

  
34
#endif /* CCW_H_ */
35

  
src/descrambler/cwc.c 2014-07-05 08:12:15.000000000 +0100 → src/descrambler/cwc.c 2014-07-07 13:44:58.872843298 +0100
1997 1997
    snprintf(buf, sizeof(buf), "cwc-%s-%i", cwc->cwc_hostname, cwc->cwc_port);
1998 1998
    td->td_nicename      = strdup(buf);
1999 1999
    td->td_service       = t;
2000
    td->dtype            = 0;
2000 2001
    td->td_stop          = cwc_service_destroy;
2001 2002
    LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
2002 2003

  
src/descrambler/descrambler.c 2014-07-05 08:12:15.000000000 +0100 → src/descrambler/descrambler.c 2014-07-07 13:48:48.664844852 +0100
19 19
#include "tvheadend.h"
20 20
#include "descrambler.h"
21 21
#include "cwc.h"
22
#include "ccw.h"
22 23
#include "capmt.h"
23 24
#include "ffdecsa/FFdecsa.h"
24 25
#include "input.h"
......
86 87
#endif
87 88
#if ENABLE_CAPMT
88 89
  capmt_init();
90
  ccw_init();
89 91
#endif
90 92
#if (ENABLE_CWC || ENABLE_CAPMT) && !ENABLE_DVBCSA
91 93
  ffdecsa_init();
......
97 99
{
98 100
#if ENABLE_CAPMT
99 101
  capmt_done();
102
  ccw_done();
100 103
#endif
101 104
#if ENABLE_CWC
102 105
  cwc_done();
......
118 121
#endif
119 122
#if ENABLE_CAPMT
120 123
  capmt_service_start(t);
124
  ccw_service_start(t);
121 125
#endif
122 126
  if (t->s_descramble == NULL) {
123 127
    t->s_descramble = dr = calloc(1, sizeof(th_descrambler_runtime_t));
......
254 258
    }
255 259
    if (td->td_keystate != DS_RESOLVED)
256 260
      continue;
261
    if (td->dtype == 1) 
262
      continue;
257 263
    if (dr->dr_buf.sb_ptr > 0) {
258 264
      for (off = 0, size = dr->dr_buf.sb_ptr; off < size; off += 188) {
259 265
        tsb2 = dr->dr_buf.sb_data + off;
src/descrambler.h 2014-07-05 08:12:15.000000000 +0100 → src/descrambler.h 2014-07-07 13:50:39.412845601 +0100
52 52
  void (*td_stop)       (struct th_descrambler *d);
53 53
  void (*td_caid_change)(struct th_descrambler *d);
54 54

  
55
  int dtype;
56

  
57
  int (*td_descramble)(struct th_descrambler *d, struct service *t,
58
           struct elementary_stream *st, const uint8_t *tsb);
59

  
55 60
} th_descrambler_t;
56 61

  
57 62
typedef struct th_descrambler_runtime {
src/input/mpegts/tsdemux.c 2014-07-05 08:12:15.000000000 +0100 → src/input/mpegts/tsdemux.c 2014-07-07 13:54:04.816846990 +0100
156 156
  (mpegts_service_t *t, const uint8_t *tsb, int64_t *pcrp, int table)
157 157
{
158 158
  elementary_stream_t *st;
159
  int pid, r;
159
  int pid, n, m, r;
160
  th_descrambler_t *td;
160 161
  int error = 0;
161 162
  int64_t pcr = PTS_UNSET;
162 163
  
......
198 199

  
199 200
  pid = (tsb[1] & 0x1f) << 8 | tsb[2];
200 201

  
202
  if(pid==0x1FFF){  // ignore NULL stream
203
    pthread_mutex_unlock(&t->s_stream_mutex);
204
    return 0;
205
  }
206

  
201 207
  st = service_stream_find((service_t*)t, pid);
202 208

  
203 209
  /* Extract PCR */
......
224 230
      t->s_scrambled_seen |= service_is_encrypted((service_t*)t);
225 231

  
226 232
    /* scrambled stream */
233
    n = m = 0;
234

  
235
    LIST_FOREACH(td, &t->s_descramblers, td_service_link) {
236
      n++;
237
      
238
      if(td->dtype == 1) {
239
        r = td->td_descramble(td, (service_t*)t, st, tsb);
240
        if(r == 0) {
241
          pthread_mutex_unlock(&t->s_stream_mutex);
242
          return 1;
243
        }
244
      }
245

  
246
      if(r == 1)
247
        m++;
248
    }
249

  
227 250
    r = descrambler_descramble((service_t *)t, st, tsb);
228 251
    if(r > 0) {
229 252
      pthread_mutex_unlock(&t->s_stream_mutex);
src/version.c 1970-01-01 01:00:00.000000000 +0100 → src/version.c 2014-07-07 14:18:30.796856906 +0100
1
const char *tvheadend_version = "0.0.0~unknown";
src/webui/extjs.c 2014-07-05 08:12:15.000000000 +0100 → src/webui/extjs.c 2014-07-07 13:55:31.760847578 +0100
150 150
#endif
151 151
#if ENABLE_CAPMT
152 152
  extjs_load(hq, "static/app/capmteditor.js");
153
  extjs_load(hq, "static/app/ccweditor.js");
153 154
#endif
154 155
  extjs_load(hq, "static/app/tvadapters.js");
155 156
  extjs_load(hq, "static/app/idnode.js");
src/webui/static/app/ccweditor.js 1970-01-01 01:00:00.000000000 +0100 → src/webui/static/app/ccweditor.js 2014-07-07 12:49:32.100820796 +0100
1
tvheadend.ccweditor = function() {
2
    var fm = Ext.form;
3

  
4
    var enabledColumn = new Ext.grid.CheckColumn({
5
	header : "Enabled",
6
	dataIndex : 'enabled',
7
	width : 60
8
    });
9

  
10
    function setMetaAttr(meta, record) {
11
	var enabled = record.get('enabled');
12
	if (enabled == 1) {
13
	    meta.attr = 'style="color:green;"';
14
	}
15
	else {
16
	    meta.attr = 'style="color:red;"';
17
	}
18
    }
19

  
20
    var cm = new Ext.grid.ColumnModel({
21
            defaultSortable: true,
22
            columns: [ enabledColumn, {
23
	header: "CAID",
24
	dataIndex: 'caid',
25
	renderer: function(value, metadata, record, row, col, store) {
26
	    setMetaAttr(metadata, record);
27
	    return value;
28
	},
29
	editor: new fm.TextField({allowBlank: false})
30
    }, {
31
	header: "Mux ID",
32
	dataIndex: 'tid',
33
	renderer: function(value, metadata, record, row, col, store) {
34
	    setMetaAttr(metadata, record);
35
	    return value;
36
	},
37
	editor: new fm.TextField({allowBlank: false})
38
    }, {
39
	header: "SID",
40
	dataIndex: 'sid',
41
	renderer: function(value, metadata, record, row, col, store) {
42
	    setMetaAttr(metadata, record);
43
	    return value;
44
	},
45
	editor: new fm.TextField({allowBlank: false})
46
    }, {
47
	header: "Key",
48
	dataIndex: 'key',
49
	width: 200,
50
	renderer: function(value, metadata, record, row, col, store) {
51
	    setMetaAttr(metadata, record);
52
	    return value;
53
	},
54
	editor: new fm.TextField({allowBlank: false})
55
    }, {
56
	header : "Comment",
57
	dataIndex : 'comment',
58
	width : 400,
59
	renderer : function(value, metadata, record, row, col, store) {
60
	    setMetaAttr(metadata, record);
61
	    return value;
62
	},
63
	editor : new fm.TextField()
64
    } ]});
65

  
66
    var rec = Ext.data.Record.create([ 'enabled','caid','tid','sid','key','comment' ]);
67

  
68
    store = new Ext.data.JsonStore({
69
	root: 'entries',
70
	fields: rec,
71
	url: "tablemgr",
72
	autoLoad: true,
73
	id: 'id',
74
	baseParams: {table: 'ccw', op: "get"}
75
    });
76

  
77
    tvheadend.comet.on('ccwStatus', function(server) {
78
	var rec = store.getById(server.id);
79
	if(rec){
80
	    rec.set('connected', server.connected);
81
	}
82
    });
83

  
84
    return new tvheadend.tableEditor('CCW Keys', 'ccw', cm, rec,
85
		     [enabledColumn], store,
86
		'config_ccw.html', 'key');
87
}
src/webui/static/app/tvheadend.js 2014-07-05 08:12:15.000000000 +0100 → src/webui/static/app/tvheadend.js 2014-07-07 13:57:53.308848536 +0100
285 285
        tabs2 = [];
286 286
        if (tvheadend.capabilities.indexOf('cwc')   !== -1)
287 287
          tabs2.push(new tvheadend.cwceditor);
288
        if (tvheadend.capabilities.indexOf('capmt') !== -1)
288
        if (tvheadend.capabilities.indexOf('capmt') !== -1) {
289 289
          tabs2.push(new tvheadend.capmteditor);
290
          tabs2.push(new tvheadend.ccweditor);
291
        }
290 292
        if (tabs2.length > 0) {
291 293
            tvheadend.conf_csa = new Ext.TabPanel({
292 294
                activeTab: 0,
    (1-1/1)