diff --git a/CMakeLists.txt b/CMakeLists.txt index fd95e41c..df776bb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -720,6 +720,13 @@ if (CONFIG_WITH_EMU MATCHES "Y" AND NOT WITH_EMU EQUAL 1) message (STATUS " EMU is added by config compiling with EMU") endif (CONFIG_WITH_EMU MATCHES "Y" AND NOT WITH_EMU EQUAL 1) +execute_process (COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/config.sh --enabled WITH_ECMBIN OUTPUT_VARIABLE CONFIG_WITH_ECMBIN OUTPUT_STRIP_TRAILING_WHITESPACE) +if (CONFIG_WITH_ECMBIN MATCHES "Y" AND NOT WITH_ECMBIN EQUAL 1) + add_definitions ("-DWITH_ECMBIN") + set (WITH_ECMBIN "1") + message (STATUS " ECMBIN is added by config compiling with ECMBIN") +endif (CONFIG_WITH_ECMBIN MATCHES "Y" AND NOT WITH_ECMBIN EQUAL 1) + if (WITH_EMU) execute_process (COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/config.sh --enabled WITH_SOFTCAM OUTPUT_VARIABLE CONFIG_WITH_SOFTCAM OUTPUT_STRIP_TRAILING_WHITESPACE) if (CONFIG_WITH_SOFTCAM MATCHES "Y" AND NOT WITH_SOFTCAM EQUAL 1) @@ -1233,6 +1240,10 @@ if (WITH_EMU) if (WITH_SOFTCAM) message (STATUS " SoftCam.Key will be linked as well") endif (WITH_SOFTCAM) + endif (WITH_EMU) +if (WITH_ECMBIN) + message (STATUS " Compile with ECMBIN support") +endif (WITH_ECMBIN) message (STATUS "") diff --git a/Makefile b/Makefile index 9192eec0..993e915a 100644 --- a/Makefile +++ b/Makefile @@ -411,6 +411,7 @@ SRC-$(CONFIG_CS_CACHEEX) += module-cccam-cacheex.c SRC-$(CONFIG_MODULE_CCCAM) += module-cccam.c SRC-$(CONFIG_MODULE_CCCSHARE) += module-cccshare.c SRC-$(CONFIG_MODULE_CONSTCW) += module-constcw.c +SRC-$(CONFIG_WITH_ECMBIN) += module-ecmbin.c SRC-$(CONFIG_WITH_EMU) += module-emulator.c SRC-$(CONFIG_WITH_EMU) += module-emulator-nemu.c SRC-$(CONFIG_WITH_EMU) += module-emulator-biss.c diff --git a/config.h b/config.h index 066263f2..ff232b71 100644 --- a/config.h +++ b/config.h @@ -3,6 +3,7 @@ #define WITH_EMU 1 #define WITH_SOFTCAM 1 +#define WITH_ECMBIN 1 #define WEBIF 1 #define WEBIF_LIVELOG 1 #define WEBIF_JQUERY 1 diff --git a/config.sh b/config.sh index c640b76a..fa0be107 100755 --- a/config.sh +++ b/config.sh @@ -1,6 +1,6 @@ #!/bin/sh -addons="WEBIF WEBIF_LIVELOG WEBIF_JQUERY WITH_COMPRESS_WEBIF TOUCH WITH_SSL HAVE_DVBAPI WITH_NEUTRINO READ_SDT_CHARSETS IRDETO_GUESSING CS_ANTICASC WITH_DEBUG MODULE_MONITOR WITH_LB CS_CACHEEX CS_CACHEEX_AIO CW_CYCLE_CHECK LCDSUPPORT LEDSUPPORT CLOCKFIX IPV6SUPPORT WITH_SIGNING WITH_CARDLIST WITH_EMU WITH_SOFTCAM" +addons="WEBIF WEBIF_LIVELOG WEBIF_JQUERY WITH_COMPRESS_WEBIF TOUCH WITH_SSL HAVE_DVBAPI WITH_NEUTRINO READ_SDT_CHARSETS IRDETO_GUESSING CS_ANTICASC WITH_DEBUG MODULE_MONITOR WITH_LB CS_CACHEEX CS_CACHEEX_AIO CW_CYCLE_CHECK LCDSUPPORT LEDSUPPORT CLOCKFIX IPV6SUPPORT WITH_SIGNING WITH_CARDLIST WITH_EMU WITH_SOFTCAM WITH_ECMBIN" protocols="MODULE_CAMD33 MODULE_CAMD35 MODULE_CAMD35_TCP MODULE_NEWCAMD MODULE_CCCAM MODULE_CCCSHARE MODULE_GBOX MODULE_RADEGAST MODULE_SCAM MODULE_SERIAL MODULE_CONSTCW MODULE_PANDORA MODULE_GHTTP MODULE_STREAMRELAY" readers="READER_NAGRA READER_NAGRA_MERLIN READER_IRDETO READER_CONAX READER_CRYPTOWORKS READER_SECA READER_VIACCESS READER_VIDEOGUARD READER_DRE READER_TONGFANG READER_STREAMGUARD READER_JET READER_BULCRYPT READER_GRIFFIN READER_DGCRYPT" card_readers="CARDREADER_PHOENIX CARDREADER_INTERNAL CARDREADER_SC8IN1 CARDREADER_MP35 CARDREADER_SMARGO CARDREADER_DB2COM CARDREADER_STAPI CARDREADER_STAPI5 CARDREADER_GXAPI CARDREADER_STINGER CARDREADER_DRECAS" @@ -29,6 +29,7 @@ CONFIG_CW_CYCLE_CHECK=y # CONFIG_IPV6SUPPORT=n # CONFIG_WITH_SIGNING=n # CONFIG_WITH_CARDLIST=n +# CONFIG_WITH_ECMBIN=n CONFIG_WITH_EMU=y CONFIG_WITH_SOFTCAM=y # CONFIG_MODULE_CAMD33=n @@ -335,16 +336,16 @@ get_opts() { update_deps() { # Calculate dependencies - enabled_any $(get_opts readers) $(get_opts card_readers) WITH_EMU && enable_opt WITH_CARDREADER >/dev/null - disabled_all $(get_opts readers) $(get_opts card_readers) WITH_EMU && disable_opt WITH_CARDREADER >/dev/null + enabled_any $(get_opts readers) $(get_opts card_readers) WITH_EMU WITH_ECMBIN && enable_opt WITH_CARDREADER >/dev/null + disabled_all $(get_opts readers) $(get_opts card_readers) WITH_EMU WITH_ECMBIN && disable_opt WITH_CARDREADER >/dev/null disabled WITH_CARDREADER && disable_opt WITH_CARDLIST >/dev/null disabled WEBIF && disable_opt WEBIF_LIVELOG >/dev/null disabled WEBIF && disable_opt WEBIF_JQUERY >/dev/null enabled MODULE_CCCSHARE && enable_opt MODULE_CCCAM >/dev/null enabled_any CARDREADER_DB2COM CARDREADER_MP35 CARDREADER_SC8IN1 CARDREADER_STINGER && enable_opt CARDREADER_PHOENIX >/dev/null disabled CS_CACHEEX && disable_opt CS_CACHEEX_AIO >/dev/null - enabled WITH_EMU && enable_opt READER_VIACCESS >/dev/null - enabled WITH_EMU && enable_opt MODULE_NEWCAMD >/dev/null + enabled WITH_EMU WITH_ECMBIN && enable_opt READER_VIACCESS >/dev/null + enabled WITH_EMU WITH_ECMBIN && enable_opt MODULE_NEWCAMD >/dev/null disabled WITH_EMU && disable_opt WITH_SOFTCAM >/dev/null enabled WITH_SIGNING && enable_opt WITH_SSL >/dev/null } @@ -406,10 +407,10 @@ list_config() { not_have_flag USE_LIBCRYPTO && echo "CONFIG_LIB_AES=y" || echo "# CONFIG_LIB_AES=n" enabled MODULE_CCCAM && echo "CONFIG_LIB_RC6=y" || echo "# CONFIG_LIB_RC6=n" not_have_flag USE_LIBCRYPTO && enabled MODULE_CCCAM && echo "CONFIG_LIB_SHA1=y" || echo "# CONFIG_LIB_SHA1=n" - enabled_any READER_DRE MODULE_SCAM READER_VIACCESS READER_TONGFANG READER_STREAMGUARD READER_JET READER_NAGRA READER_NAGRA_MERLIN READER_VIDEOGUARD READER_CONAX READER_TONGFANG WITH_EMU && echo "CONFIG_LIB_DES=y" || echo "# CONFIG_LIB_DES=n" - enabled_any MODULE_CCCAM READER_NAGRA READER_NAGRA_MERLIN READER_SECA WITH_EMU && echo "CONFIG_LIB_IDEA=y" || echo "# CONFIG_LIB_IDEA=n" + enabled_any READER_DRE MODULE_SCAM READER_VIACCESS READER_TONGFANG READER_STREAMGUARD READER_JET READER_NAGRA READER_NAGRA_MERLIN READER_VIDEOGUARD READER_CONAX READER_TONGFANG WITH_EMU WITH_ECMBIN && echo "CONFIG_LIB_DES=y" || echo "# CONFIG_LIB_DES=n" + enabled_any MODULE_CCCAM READER_NAGRA READER_NAGRA_MERLIN READER_SECA WITH_EMU WITH_ECMBIN && echo "CONFIG_LIB_IDEA=y" || echo "# CONFIG_LIB_IDEA=n" enabled_any READER_JET && echo "CONFIG_LIB_TWOFISH=y" || echo "CONFIG_LIB_TWOFISH=n" - not_have_flag USE_LIBCRYPTO && enabled_any READER_CONAX READER_CRYPTOWORKS READER_NAGRA READER_NAGRA_MERLIN WITH_EMU && echo "CONFIG_LIB_BIGNUM=y" || echo "# CONFIG_LIB_BIGNUM=n" + not_have_flag USE_LIBCRYPTO && enabled_any READER_CONAX READER_CRYPTOWORKS READER_NAGRA READER_NAGRA_MERLIN WITH_EMU WITH_ECMBIN && echo "CONFIG_LIB_BIGNUM=y" || echo "# CONFIG_LIB_BIGNUM=n" enabled READER_NAGRA_MERLIN && echo "CONFIG_LIB_MDC2=y" || echo "# CONFIG_LIB_MDC2=n" enabled READER_NAGRA_MERLIN && echo "CONFIG_LIB_FAST_AES=y" || echo "# CONFIG_LIB_FAST_AES=n" enabled_any READER_NAGRA_MERLIN WITH_SIGNING && echo "CONFIG_LIB_SHA256=y" || echo "# CONFIG_LIB_SHA256=n" @@ -539,6 +540,7 @@ menu_addons() { WITH_CARDLIST "Cardlist support" $(check_test "WITH_CARDLIST") \ WITH_EMU "Emulator support" $(check_test "WITH_EMU") \ WITH_SOFTCAM "Built-in SoftCam.Key" $(check_test "WITH_SOFTCAM") \ + WITH_ECMBIN "ECM Emulator support" $(check_test "WITH_ECMBIN") \ WITH_SIGNING "Binary signing with X.509 certificate" $(check_test "WITH_SIGNING") \ 2> ${tempfile} diff --git a/csctapi/cardreaders.h b/csctapi/cardreaders.h index 38f3bba7..43267cd8 100644 --- a/csctapi/cardreaders.h +++ b/csctapi/cardreaders.h @@ -16,5 +16,6 @@ extern const struct s_cardreader cardreader_gxapi; extern const struct s_cardreader cardreader_stinger; extern const struct s_cardreader cardreader_drecas; extern const struct s_cardreader cardreader_emu; +extern const struct s_cardreader cardreader_ecmbin; #endif diff --git a/csctapi/icc_async.c b/csctapi/icc_async.c index aa79099e..964c11e7 100644 --- a/csctapi/icc_async.c +++ b/csctapi/icc_async.c @@ -296,7 +296,7 @@ int32_t ICC_Async_Activate(struct s_reader *reader, ATR *atr, uint16_t deprecate { reader->crdr_flush = crdr_ops->flush; // Flush flag may be changed for each reader call(crdr_ops->activate(reader, atr)); - if(reader->typ == R_EMU) + if(reader->typ == R_EMU || reader->typ == R_ECMBIN) { return OK; } diff --git a/globals.h b/globals.h index be5efe5d..5805e87f 100644 --- a/globals.h +++ b/globals.h @@ -475,6 +475,7 @@ typedef uint8_t uint8_t; #define R_PCSC 0x8 // PCSC #define R_DRECAS 0x9 // Reader DRECAS #define R_EMU 0x17 // Reader EMU +#define R_ECMBIN 0x19 // Reader ECMBIN /////////////////// proxy readers after R_CS378X #define R_CAMD35 0x20 // Reader cascading camd 3.5x #define R_CAMD33 0x21 // Reader cascading camd 3.3x @@ -2205,6 +2206,12 @@ struct s_config IN_ADDR_T srvip; char *usrfile; char *cwlogdir; + char *ecmcwlogdir; + uint8_t record_ecm_start_byte; + uint8_t record_ecm_end_byte; + char *bin_folder; + uint8_t ecmbin_ecm_start_byte; + uint8_t ecmbin_ecm_end_byte; char *emmlogdir; char *logfile; char *mailfile; diff --git a/module-ecmbin.c b/module-ecmbin.c new file mode 100644 index 00000000..8ed0c02c --- /dev/null +++ b/module-ecmbin.c @@ -0,0 +1,460 @@ +#define MODULE_LOG_PREFIX "ecmbin" +#include "globals.h" + +#ifdef WITH_ECMBIN + +#include "ncam-conf-chk.h" +#include "ncam-config.h" +#include "ncam-reader.h" +#include "ncam-string.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CW_SIZE 16 +#define FILENAME_SIZE 256 +#define MAX_CHANNEL_FILES 1 +#define MAX_CHANNELS 300 +#define HASH_SIZE 64 +#define CS_OK 1 +#define CS_ERROR 0 +#define CR_OK 0 +#define CR_ERROR 1 +#define PARTITION_SIZE 32 + +struct ECMEntry { + uint8_t *ecm; + uint8_t cw[CW_SIZE]; + uint32_t next_likely_index; // For cache optimization +} __attribute__((packed)); + +struct BinFile { + char filename[FILENAME_SIZE]; + struct ECMEntry *entries; + size_t count; + uint16_t caid; + uint16_t srvid; +}; + +struct FileIndex { + uint16_t caid; + uint16_t srvid; + size_t file_index; +}; + +struct FilePartition { + struct BinFile files[PARTITION_SIZE]; + int file_count; + pthread_rwlock_t lock; +}; + +static struct PreloadedData { + struct FilePartition partitions[MAX_CHANNELS * MAX_CHANNEL_FILES / PARTITION_SIZE]; + struct FileIndex *indices; + int total_files; + int partition_count; + time_t last_update; +} g_preloaded = { + .total_files = 0, + .partition_count = 0, + .indices = NULL, + .last_update = 0 +}; + +static int compare_file_indices(const void *a, const void *b) { + const struct FileIndex *fa = (const struct FileIndex *)a; + const struct FileIndex *fb = (const struct FileIndex *)b; + + if (fa->caid != fb->caid) + return fa->caid - fb->caid; + return fa->srvid - fb->srvid; +} + +static int compare_ecm(const void *a, const void *b) { + size_t ecm_size = cfg.ecmbin_ecm_end_byte - cfg.ecmbin_ecm_start_byte; + const struct ECMEntry *entry_a = (const struct ECMEntry *)a; + const struct ECMEntry *entry_b = (const struct ECMEntry *)b; + return memcmp(entry_a->ecm, entry_b->ecm, ecm_size); +} + +static struct ECMEntry *load_and_sort_entries(const char *filename, size_t *count) { + struct stat sb; + size_t ecm_size = cfg.ecmbin_ecm_end_byte - cfg.ecmbin_ecm_start_byte; + size_t entry_size = ecm_size + CW_SIZE; + size_t i, j; + int fd; + struct ECMEntry *entries = NULL; + uint8_t *temp_buffer = NULL; + + if ((fd = open(filename, O_RDONLY)) == -1) { + cs_log("Error opening %s: %s", filename, strerror(errno)); + return NULL; + } + + if (fstat(fd, &sb) == -1) { + cs_log("Error getting size of %s: %s", filename, strerror(errno)); + close(fd); + return NULL; + } + + if (sb.st_size == 0 || sb.st_size % entry_size != 0) { + cs_log("Invalid file size or format: %s", filename); + close(fd); + return NULL; + } + + *count = sb.st_size / entry_size; + if (!(entries = malloc(*count * sizeof(struct ECMEntry)))) { + close(fd); + return NULL; + } + + if (!(temp_buffer = malloc(entry_size))) { + free(entries); + close(fd); + return NULL; + } + + for (i = 0; i < *count; i++) { + if (read(fd, temp_buffer, entry_size) != (ssize_t)entry_size) { + for (j = 0; j < i; j++) + free(entries[j].ecm); + free(entries); + free(temp_buffer); + close(fd); + return NULL; + } + + if (!(entries[i].ecm = malloc(ecm_size))) { + for (j = 0; j < i; j++) + free(entries[j].ecm); + free(entries); + free(temp_buffer); + close(fd); + return NULL; + } + + memcpy(entries[i].ecm, temp_buffer, ecm_size); + memcpy(entries[i].cw, temp_buffer + ecm_size, CW_SIZE); + } + + free(temp_buffer); + close(fd); + + qsort(entries, *count, sizeof(struct ECMEntry), compare_ecm); + + // Set next_likely_index for cache optimization + for (i = 0; i < *count - 1; i++) + entries[i].next_likely_index = i + 1; + entries[*count - 1].next_likely_index = 0; + + return entries; +} + +static size_t find_file_index(uint16_t caid, uint16_t srvid) { + struct FileIndex key = {.caid = caid, .srvid = srvid}; + struct FileIndex *result = bsearch(&key, g_preloaded.indices, + g_preloaded.total_files, + sizeof(struct FileIndex), + compare_file_indices); + return result ? result->file_index : (size_t)-1; +} + +static int32_t search_ecm_in_sorted_entries( + const struct ECMEntry *entries, + size_t count, + const uint8_t *ecm, + struct s_ecm_answer *ea) { + + size_t ecm_size = cfg.ecmbin_ecm_end_byte - cfg.ecmbin_ecm_start_byte; + size_t left = 0, right = count > 0 ? count - 1 : 0; + + while (left <= right) { + size_t mid = left + (right - left) / 2; + + // Prefetch next likely entry + if (count > 1 && mid < count - 1) { + __builtin_prefetch(&entries[entries[mid].next_likely_index]); + } + + int cmp = memcmp(ecm, entries[mid].ecm, ecm_size); + if (cmp == 0) { + memcpy(ea->cw, entries[mid].cw, CW_SIZE); + return CS_OK; + } + if (cmp < 0) { + if (mid == 0) break; + right = mid - 1; + } else { + left = mid + 1; + } + } + return CS_ERROR; +} + +static int parse_filename(const char *filename, uint16_t *caid, uint16_t *srvid) { + unsigned int ca, srv; + if (sscanf(filename, "%04X@%04X", &ca, &srv) == 2) { + *caid = (uint16_t)ca; + *srvid = (uint16_t)srv; + return 1; + } + return 0; +} + +static void preload_all_bin_files(const char *directory) +{ + DIR *dir; + struct dirent *entry; + char fullpath[FILENAME_SIZE * 2]; + int i, current_file = 0, total_files = 0; + struct ECMEntry *entries; + size_t count; + size_t dir_len; + + dir_len = strlen(directory); + if (dir_len >= FILENAME_SIZE) { + cs_log("Directory path too long: %s", directory); + return; + } + + // Initialize partitions + for (i = 0; i < MAX_CHANNELS * MAX_CHANNEL_FILES / PARTITION_SIZE; i++) { + pthread_rwlock_init(&g_preloaded.partitions[i].lock, NULL); + g_preloaded.partitions[i].file_count = 0; + } + + if (!(dir = opendir(directory))) { + cs_log("Cannot open directory %s: %s", directory, strerror(errno)); + return; + } + + // First pass: count files + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] != '.') total_files++; // Count all non-hidden files + } + + // Allocate indices array + if (!(g_preloaded.indices = malloc(total_files * sizeof(struct FileIndex)))) { + closedir(dir); + return; + } + + // Reset directory pointer + rewinddir(dir); + + // Second pass: load files + while ((entry = readdir(dir)) != NULL) { + uint16_t caid, srvid; + size_t name_len; + + if (entry->d_name[0] == '.') continue; + + name_len = strlen(entry->d_name); + if (dir_len + name_len + 2 >= sizeof(fullpath)) { + cs_log("Skipping long filename: %s/%s", directory, entry->d_name); + continue; + } + + if (!parse_filename(entry->d_name, &caid, &srvid)) { + cs_log("Skipping invalid file: %s", entry->d_name); + continue; + } + + if (snprintf(fullpath, sizeof(fullpath), "%s/%s", directory, entry->d_name) < 0) { + cs_log("Error creating path for: %s", entry->d_name); + continue; + } + + int partition_idx = current_file / PARTITION_SIZE; + struct FilePartition *partition = &g_preloaded.partitions[partition_idx]; + + pthread_rwlock_wrlock(&partition->lock); + + entries = load_and_sort_entries(fullpath, &count); + if (entries && count > 0) { + struct BinFile *file = &partition->files[partition->file_count]; + + strncpy(file->filename, entry->d_name, FILENAME_SIZE); + file->entries = entries; + file->count = count; + file->caid = caid; + file->srvid = srvid; + + // Add to indices + g_preloaded.indices[current_file].caid = caid; + g_preloaded.indices[current_file].srvid = srvid; + g_preloaded.indices[current_file].file_index = + partition_idx * PARTITION_SIZE + partition->file_count; + + partition->file_count++; + current_file++; + + cs_log("Loaded %s (CAID: %04X, SRVID: %04X) with %zu entries", + entry->d_name, caid, srvid, count); + } + + pthread_rwlock_unlock(&partition->lock); + } + + closedir(dir); + + g_preloaded.total_files = current_file; + g_preloaded.partition_count = (current_file + PARTITION_SIZE - 1) / PARTITION_SIZE; + g_preloaded.last_update = time(NULL); + + // Sort indices for binary search + qsort(g_preloaded.indices, g_preloaded.total_files, + sizeof(struct FileIndex), compare_file_indices); + + cs_log("Successfully preloaded %d binary files in %d partitions", + current_file, g_preloaded.partition_count); +} + +static int32_t ecmbin_do_ecm(struct s_reader *UNUSED(rdr), const ECM_REQUEST *er, struct s_ecm_answer *ea) { + size_t file_idx = find_file_index(er->caid, er->srvid); + if (file_idx == (size_t)-1) { + cs_log("No matching bin file for CAID: %04X, SRVID: %04X", er->caid, er->srvid); + return CS_ERROR; + } + + size_t partition_idx = file_idx / PARTITION_SIZE; + size_t local_idx = file_idx % PARTITION_SIZE; + struct FilePartition *partition = &g_preloaded.partitions[partition_idx]; + + pthread_rwlock_rdlock(&partition->lock); + + const uint8_t *ecm = &er->ecm[cfg.ecmbin_ecm_start_byte]; + int32_t result = search_ecm_in_sorted_entries( + partition->files[local_idx].entries, + partition->files[local_idx].count, + ecm, + ea + ); + + pthread_rwlock_unlock(&partition->lock); + + return result; +} + +static void cleanup(void) { + int i, j; + size_t k; + // Clean up partitions + for (i = 0; i < g_preloaded.partition_count; i++) { + struct FilePartition *partition = &g_preloaded.partitions[i]; + pthread_rwlock_wrlock(&partition->lock); + + for (j = 0; j < partition->file_count; j++) { + struct BinFile *file = &partition->files[j]; + if (file->entries) { + for (k = 0; k < file->count; k++) { + free(file->entries[k].ecm); + } + free(file->entries); + } + } + + partition->file_count = 0; + pthread_rwlock_unlock(&partition->lock); + pthread_rwlock_destroy(&partition->lock); + } + + // Clean up indices + free(g_preloaded.indices); + g_preloaded.indices = NULL; + + g_preloaded.total_files = 0; + g_preloaded.partition_count = 0; + g_preloaded.last_update = 0; +} + +static int32_t ecmbin_card_info(struct s_reader *rdr) { + rdr->card_status = CARD_INSERTED; + return CS_OK; +} + +const struct s_cardsystem reader_ecmbin = { + .desc = "ecmbin", + .caids = (uint16_t[]){ 0x0B, 0 }, + .do_ecm = ecmbin_do_ecm, + .card_info = ecmbin_card_info, +}; + +static int32_t ecmbin_reader_init(struct s_reader *UNUSED(reader)) { + preload_all_bin_files(cfg.bin_folder); + return CR_OK; +} + +static int32_t ecmbin_close(struct s_reader *UNUSED(reader)) { + cs_log("ECMBin reader shutting down"); + cleanup(); + return CR_OK; +} + +static int32_t ecmbin_get_status(struct s_reader *UNUSED(reader), int32_t *in) { *in = 1; return CR_OK; } +static int32_t ecmbin_activate(struct s_reader *UNUSED(reader), struct s_ATR *UNUSED(atr)) { return CR_OK; } +static int32_t ecmbin_transmit(struct s_reader *UNUSED(reader), uint8_t *UNUSED(buffer), + uint32_t UNUSED(size), uint32_t UNUSED(expectedlen), uint32_t UNUSED(delay), + uint32_t UNUSED(timeout)) { return CR_OK; } +static int32_t ecmbin_receive(struct s_reader *UNUSED(reader), uint8_t *UNUSED(buffer), + uint32_t UNUSED(size), uint32_t UNUSED(delay), uint32_t UNUSED(timeout)) { return CR_OK; } +static int32_t ecmbin_write_settings(struct s_reader *UNUSED(reader), + struct s_cardreader_settings *UNUSED(s)) { return CR_OK; } +static int32_t ecmbin_card_write(struct s_reader *UNUSED(pcsc_reader), + const uint8_t *UNUSED(buf), uint8_t *UNUSED(cta_res), + uint16_t *UNUSED(cta_lr), int32_t UNUSED(l)) { return CR_OK; } +static int32_t ecmbin_set_protocol(struct s_reader *UNUSED(rdr), + uint8_t *UNUSED(params), uint32_t *UNUSED(length), + uint32_t UNUSED(len_request)) { return CR_OK; } + +const struct s_cardreader cardreader_ecmbin = { + .desc = "ecmbin", + .typ = R_ECMBIN, + .reader_init = ecmbin_reader_init, + .get_status = ecmbin_get_status, + .activate = ecmbin_activate, + .transmit = ecmbin_transmit, + .receive = ecmbin_receive, + .close = ecmbin_close, + .write_settings = ecmbin_write_settings, + .card_write = ecmbin_card_write, + .set_protocol = ecmbin_set_protocol, +}; + +void add_ecmbin_reader(void) { + LL_ITER itr; + struct s_reader *rdr; + int8_t haveBinReader = 0; + char ecmbinName[] = "ecmemu"; + + itr = ll_iter_create(configured_readers); + while ((rdr = ll_iter_next(&itr))) { + if (rdr->typ == R_ECMBIN) { + haveBinReader = 1; + break; + } + } + + if (!haveBinReader) { + if (!cs_malloc(&rdr, sizeof(struct s_reader))) { + return; + } + reader_set_defaults(rdr); + rdr->enable = 1; + rdr->typ = R_ECMBIN; + cs_strncpy(rdr->label, ecmbinName, sizeof(ecmbinName)); + cs_strncpy(rdr->device, ecmbinName, sizeof(ecmbinName)); + rdr->grp = 0x2ULL; + rdr->crdr = &cardreader_ecmbin; + reader_fixups_fn(rdr); + ll_append(configured_readers, rdr); + } +} +#endif diff --git a/module-stat.c b/module-stat.c index 1e02af6d..1d050b62 100644 --- a/module-stat.c +++ b/module-stat.c @@ -897,7 +897,7 @@ void check_lb_auto_betatunnel_mode(ECM_REQUEST *er) uint16_t get_rdr_caid(struct s_reader *rdr) { - if(is_network_reader(rdr) || rdr->typ == R_EMU) + if(is_network_reader(rdr) || rdr->typ == R_EMU || rdr->typ == R_ECMBIN) { return 0; // reader caid is not real caid } @@ -1288,7 +1288,7 @@ void stat_get_best_reader(ECM_REQUEST *er) for(ea = er->matching_rdr; ea; ea = ea->next) { rdr = ea->reader; - if(is_network_reader(rdr) || rdr->typ == R_EMU) // reader caid is not real caid + if(is_network_reader(rdr) || rdr->typ == R_EMU || rdr->typ == R_ECMBIN) // reader caid is not real caid { prv = ea; continue; // proxy can convert or reject diff --git a/module-webif-tpl.c b/module-webif-tpl.c index aaa76d77..a97a4bb7 100644 --- a/module-webif-tpl.c +++ b/module-webif-tpl.c @@ -466,6 +466,7 @@ char *tpl_getUnparsedTpl(const char *name, int8_t removeHeader, const char *subd check_conf(WITH_CARDLIST, ptr2); check_conf(WITH_EMU, ptr2); check_conf(WITH_SOFTCAM, ptr2); + check_conf(WITH_ECMBIN, ptr2); } // for if(ok == 0) { diff --git a/module-webif.c b/module-webif.c index 74810f6c..a0f28e74 100644 --- a/module-webif.c +++ b/module-webif.c @@ -665,6 +665,20 @@ static char *send_ncam_config_global(struct templatevars *vars, struct uriparams tpl_printf(vars, TPLADD, "INITIALDEBUGLEVEL", "%u", cfg.initial_debuglevel); if(cfg.cwlogdir != NULL) { tpl_addVar(vars, TPLADD, "CWLOGDIR", cfg.cwlogdir); } + if(cfg.ecmcwlogdir != NULL) { tpl_addVar(vars, TPLADD, "ECMCWLOGDIR", cfg.ecmcwlogdir); } + if(cfg.bin_folder != NULL) { tpl_addVar(vars, TPLADD, "BIN_FOLDER", cfg.bin_folder); } + char buf[4]; + snprintf(buf, sizeof(buf), "%d", cfg.record_ecm_start_byte); + tpl_addVar(vars, TPLADD, "RECORD_ECM_START_BYTE", buf); + + snprintf(buf, sizeof(buf), "%d", cfg.record_ecm_end_byte); + tpl_addVar(vars, TPLADD, "RECORD_ECM_END_BYTE", buf); + + snprintf(buf, sizeof(buf), "%d", cfg.ecmbin_ecm_start_byte); + tpl_addVar(vars, TPLADD, "ECMBIN_ECM_START_BYTE", buf); + + snprintf(buf, sizeof(buf), "%d", cfg.ecmbin_ecm_end_byte); + tpl_addVar(vars, TPLADD, "ECMBIN_ECM_END_BYTE", buf); if(cfg.emmlogdir != NULL) { tpl_addVar(vars, TPLADD, "EMMLOGDIR", cfg.emmlogdir); } tpl_addVar(vars, TPLADD, "ECMFMT", cfg.ecmfmt); tpl_printf(vars, TPLADD, "LOGHISTORYLINES", "%u", cfg.loghistorylines); @@ -2407,7 +2421,7 @@ static char *send_ncam_reader_config(struct templatevars *vars, struct uriparams chk_reader("lb_whitelist_services", servicelabelslb, rdr); chk_reader("lb_priority_services", servicelabelslbprio, rdr); - if(is_network_reader(rdr) || rdr->typ == R_EMU) //physical readers make trouble if re-started + if(is_network_reader(rdr) || rdr->typ == R_EMU || rdr->typ == R_ECMBIN) //physical readers make trouble if re-started { if(rdr) { @@ -3262,6 +3276,9 @@ static char *send_ncam_reader_config(struct templatevars *vars, struct uriparams case R_EMU : tpl_addVar(vars, TPLAPPEND, "READERDEPENDINGCONFIG", tpl_getTpl(vars, "READERCONFIGEMUBIT")); break; + case R_ECMBIN : + tpl_addVar(vars, TPLAPPEND, "READERDEPENDINGCONFIG", tpl_getTpl(vars, "READERCONFIGEMUBIT")); + break; case R_CS378X : tpl_addVar(vars, TPLAPPEND, "READERDEPENDINGCONFIG", tpl_getTpl(vars, "READERCONFIGCS378XBIT")); break; diff --git a/ncam-chk.c b/ncam-chk.c index a4bded9a..d456619c 100644 --- a/ncam-chk.c +++ b/ncam-chk.c @@ -913,7 +913,7 @@ int32_t matching_reader(ECM_REQUEST *er, struct s_reader *rdr) return 0; } - if(!(rdr->typ == R_EMU) && !is_network_reader(rdr) && ((rdr->caid >> 8) != ((er->caid >> 8) & 0xFF) && (rdr->caid >> 8) != ((er->ocaid >> 8) & 0xFF))) + if(!(rdr->typ == R_EMU) && !(rdr->typ == R_ECMBIN) && !is_network_reader(rdr) && ((rdr->caid >> 8) != ((er->caid >> 8) & 0xFF) && (rdr->caid >> 8) != ((er->ocaid >> 8) & 0xFF))) { if (!rdr->csystem) { return 0; } @@ -1012,7 +1012,7 @@ int32_t matching_reader(ECM_REQUEST *er, struct s_reader *rdr) } // Checking entitlements: - if(ll_count(rdr->ll_entitlements) > 0 && !(rdr->typ == R_EMU)) + if(ll_count(rdr->ll_entitlements) > 0 && !(rdr->typ == R_EMU || rdr->typ == R_ECMBIN)) { LL_ITER itr = ll_iter_create(rdr->ll_entitlements); S_ENTITLEMENT *item; @@ -1193,7 +1193,7 @@ int32_t chk_caid(uint16_t caid, CAIDTAB *ctab) int32_t chk_caid_rdr(struct s_reader *rdr, uint16_t caid) { - if(is_network_reader(rdr) || rdr->typ == R_EMU) + if(is_network_reader(rdr) || rdr->typ == R_EMU || rdr->typ == R_ECMBIN) { return 1; // reader caid is not real caid } diff --git a/ncam-config-global.c b/ncam-config-global.c index 1f7c6c13..da701e12 100644 --- a/ncam-config-global.c +++ b/ncam-config-global.c @@ -366,6 +366,12 @@ static const struct config_list global_opts[] = DEF_OPT_STR("usrfile" , OFS(usrfile) , NULL), DEF_OPT_STR("mailfile" , OFS(mailfile) , NULL), DEF_OPT_STR("cwlogdir" , OFS(cwlogdir) , NULL), + DEF_OPT_STR("ecmcwlogdir" , OFS(ecmcwlogdir) , NULL), + DEF_OPT_UINT8("record_ecm_start_byte" , OFS(record_ecm_start_byte) , 0), + DEF_OPT_UINT8("record_ecm_end_byte" , OFS(record_ecm_end_byte) , 0), + DEF_OPT_STR("bin_folder" , OFS(bin_folder) , "NULL"), + DEF_OPT_UINT8("ecmbin_ecm_start_byte" , OFS(ecmbin_ecm_start_byte) , 0), + DEF_OPT_UINT8("ecmbin_ecm_end_byte" , OFS(ecmbin_ecm_end_byte) , 0), DEF_OPT_STR("emmlogdir" , OFS(emmlogdir) , NULL), #ifdef WITH_LB DEF_OPT_INT32("lb_mode" , OFS(lb_mode) , DEFAULT_LB_MODE), diff --git a/ncam-config-reader.c b/ncam-config-reader.c index 04ba16aa..3fde0951 100644 --- a/ncam-config-reader.c +++ b/ncam-config-reader.c @@ -120,6 +120,7 @@ static void protocol_fn(const char *token, char *value, void *setting, FILE *f) { "newcamd524", R_NEWCAMD }, { "drecas", R_DRECAS }, { "emu", R_EMU }, + { "ecmbin", R_ECMBIN }, { NULL, 0 } }, *p; int i; diff --git a/ncam-ecm.c b/ncam-ecm.c index 25154c6e..48b7ee0a 100644 --- a/ncam-ecm.c +++ b/ncam-ecm.c @@ -1702,8 +1702,69 @@ uint32_t chk_provid(uint8_t *ecm, uint16_t caid) void update_chid(ECM_REQUEST *er) { er->chid = get_subid(er); + } +#define CW_LENGTH 16 +#define MAX_FILENAME_LEN 512 +#define MAX_BUFFER_SIZE 1024 + +/** + * Log ECM and CW to a file named by caid@srvid with no file extension + * @param er ECM request structure + * @param cw Control Word (16 bytes) + */ +static void logECMCWtoFile(ECM_REQUEST *er, uint8_t *cw) +{ + char srvname[CS_SERVICENAME_SIZE]; + char filename[MAX_FILENAME_LEN]; + FILE *pfCWL; + size_t i; + int j; + + if (!er || !cw || !cfg.ecmcwlogdir) { + cs_log("Invalid parameters or configuration for ECM/CW logging"); + return; + } + + if (get_servicename(cur_client(), er->srvid, er->prid, er->caid, srvname, sizeof(srvname))) { + for (i = 0; i < sizeof(srvname) && srvname[i]; i++) { + if (srvname[i] == ' ') srvname[i] = '_'; + if (srvname[i] == '/' || srvname[i] == '\\' || srvname[i] == '.') { + srvname[i] = '_'; + } + } + } + + if (snprintf(filename, sizeof(filename), "%s/%04X@%04X", + cfg.ecmcwlogdir, er->caid, er->srvid) < 0) { + cs_log("Error creating filename for ECM/CW logging"); + return; + } + + pfCWL = fopen(filename, "a+"); + if (!pfCWL) { + cs_log("Error opening ECM/CW file (%s): %s", filename, strerror(errno)); + return; + } + + flockfile(pfCWL); + + for (i = cfg.record_ecm_start_byte; i < cfg.record_ecm_end_byte; i++) { + fprintf(pfCWL, "%02X", er->ecm[i]); + } + + for (j = 0; j < CW_LENGTH; j++) { + fprintf(pfCWL, "%02X", cw[j]); + } + + fprintf(pfCWL, "\n"); + + fflush(pfCWL); + + funlockfile(pfCWL); + fclose(pfCWL); +} /* * This function writes the current CW from ECM struct to a cwl file. @@ -2079,6 +2140,8 @@ int32_t write_ecm_answer(struct s_reader *reader, ECM_REQUEST *er, int8_t rc, ui { if(cfg.cwlogdir != NULL) { logCWtoFile(er, ea->cw); } // CWL logging only if cwlogdir is set in config + if(cfg.ecmcwlogdir != NULL) + { logECMCWtoFile(er, ea->cw); } reader->ecmsok++; #ifdef CS_CACHEEX_AIO diff --git a/ncam.c b/ncam.c index c376e3ec..2bf199cb 100644 --- a/ncam.c +++ b/ncam.c @@ -45,6 +45,9 @@ #ifdef WITH_EMU void add_emu_reader(void); #endif +#ifdef WITH_ECMBIN + void add_ecmbin_reader(void); +#endif #ifdef WITH_SSL #include @@ -492,6 +495,7 @@ static void write_versionfile(bool use_stdout) write_conf(WITH_LIBCURL, "Curl to access URLs"); write_conf(WITH_EMU, "Emulator support"); write_conf(WITH_SOFTCAM, "Built-in SoftCam.Key"); + write_conf(WITH_ECMBIN, "ECM Emulator support"); #ifdef WITH_SIGNING write_conf(WITH_SIGNING, "Binary signing support"); #endif @@ -1744,6 +1748,9 @@ const struct s_cardreader *cardreaders[] = #ifdef WITH_EMU &cardreader_emu, #endif +#ifdef WITH_ECMBIN + &cardreader_ecmbin, +#endif NULL }; @@ -1935,6 +1942,9 @@ int32_t main(int32_t argc, char *argv[]) init_readerdb(); #ifdef WITH_EMU add_emu_reader(); +#endif +#ifdef WITH_ECMBIN + add_ecmbin_reader(); #endif cfg.account = init_userdb(); init_signal(); diff --git a/reader-common.c b/reader-common.c index 3c2f8e70..8383bb6c 100644 --- a/reader-common.c +++ b/reader-common.c @@ -162,6 +162,17 @@ static int32_t reader_get_cardsystem(struct s_reader *reader, ATR *atr) return (reader->csystem_active); } #endif +#ifdef WITH_ECMBIN + if(reader->typ == R_ECMBIN) + { + NULLFREE(reader->csystem_data); + rdr_log(reader, "found card system %s", reader_ecmbin.desc); + reader->csystem = &reader_ecmbin; + reader->csystem_active = true; + led_status_found_cardsystem(); + return (reader->csystem_active); + } +#endif for(i = 0; cardsystems[i]; i++) { diff --git a/readers.h b/readers.h index 48f742db..edafe48d 100644 --- a/readers.h +++ b/readers.h @@ -20,5 +20,6 @@ extern const struct s_cardsystem reader_dgcrypt; extern const struct s_cardsystem reader_streamguard; extern const struct s_cardsystem reader_jet; extern const struct s_cardsystem reader_emu; +extern const struct s_cardsystem reader_ecmbin; #endif diff --git a/webif/config/global.html b/webif/config/global.html index c8d02f38..21228184 100644 --- a/webif/config/global.html +++ b/webif/config/global.html @@ -92,6 +92,12 @@ Initial debug level: Pid file: CW log dir: + Ecmcw log dir: + Record ecm start byte: + Record ecm end byte: + Bin folder: + Ecmbin start byte: Same value (Record ecm start byte) + Ecmbin end byte: Same value (Record ecm end byte) EMM log dir: ECM log format: Loghistory Lines: