static int camd_recv_cw(struct ts *ts) { struct camd *c = &ts->camd; struct timeval tv1, tv2, last_ts_keyset; uint16_t ca_id = 0; uint16_t idx = 0; int ret; gettimeofday(&tv1, NULL); ret = c->ops.get_cw(c, &ca_id, &idx, c->key->cw); gettimeofday(&tv2, NULL); if (ret <= 0) { if (ret == -1) { // Fatal error it is better to reconnect to server. ts_LOGf("ERR | No code word has been received (ret = %d)\n", ret); c->ops.reconnect(c); } c->ecm_recv_errors++; if (c->ecm_recv_errors >= ECM_RECV_ERRORS_LIMIT) { c->key->is_valid_cw = 0; memset(c->key->cw, 0, 16); // Invalid CW } usleep(10000); return 0; } char cw_dump[16 * 6]; ts_hex_dump_buf(cw_dump, 16 * 6, c->key->cw, 16, 0); int valid_cw = memcmp(c->key->cw, invalid_cw, 16) != 0; if (!c->key->is_valid_cw && valid_cw) { ts_LOGf("CW | OK: Valid code word was received.\n"); notify(ts, "CODE_WORD_OK", "Valid code word was received."); } c->key->is_valid_cw = valid_cw; // At first ts_keyset is not initialized last_ts_keyset = c->key->ts_keyset; if (c->key->is_valid_cw) camd_set_cw(ts, c->key->cw, 1); if (ts->ecm_cw_log) { ts_LOGf("CW | SID 0x%04x CAID: 0x%04x CW_recv: %5llu ms LastKey: %5llu ms Data: %s\n", ts->service_id, ca_id, timeval_diff_msec(&tv1, &tv2), timeval_diff_msec(&last_ts_keyset, &tv2), cw_dump ); } return 1; }
static void parse_options(struct ts *ts, int argc, char **argv) { int j, i, ca_err = 0, server_err = 1, input_addr_err = 0, output_addr_err = 0, ident_err = 0, port_set = 0; while ((j = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { if (j == '?') exit(EXIT_FAILURE); switch (j) { case 'i': // -- ident ts->ident = optarg; break; case 'd': // --daemon ts->pidfile = optarg; break; case 'N': // --notify-program ts->notify_program = optarg; break; case 'S': // --syslog ts->syslog_active = 1; ts->syslog_remote = 0; break; case 'l': // --syslog-host ts->syslog_host = optarg; ts->syslog_active = 1; ts->syslog_remote = 1; break; case 'L': // --syslog-port ts->syslog_port = atoi(optarg); break; case 'F': // --log-file log_filename = optarg; break; case 'I': // --input input_addr_err = !parse_io_param(&ts->input, optarg, O_RDONLY, 0); break; case '1': // --input-source if (!inet_aton(optarg, &ts->input.isrc)) { fprintf(stderr, "ERROR: Can't parse input-source IP address: %s\n", optarg); exit(EXIT_FAILURE); } break; case 'R': // --input-rtp ts->rtp_input = !ts->rtp_input; break; case 'z': // --input-ignore-disc ts->ts_discont = !ts->ts_discont; break; case 'M': // --input-service ts->forced_service_id = strtoul(optarg, NULL, 0) & 0xffff; break; case 'T': // --input-buffer ts->input_buffer_time = strtoul(optarg, NULL, 0); break; case 'W': // --input-dump ts->input_dump_filename = optarg; break; case 'O': // --output output_addr_err = !parse_io_param(&ts->output, optarg, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); break; case 'o': // --output-intf if (strchr(optarg, '.')) inet_aton(optarg, &ts->output.intf); else ts->output.v6_if_index = atoi(optarg); break; case 't': // --output-ttl ts->output.ttl = atoi(optarg); break; case 'r': // --output-rtp ts->rtp_output = 1; break; case 'k': // --output-rtp-ssrc ts->rtp_ssrc = strtoul(optarg, NULL, 0); break; case 'g': // --output-tos ts->output.tos = (uint8_t)strtol(optarg, NULL, 0); break; case 'u': // --no-output-on-error ts->no_output_on_error = !ts->no_output_on_error; break; case 'p': // --no-output-filter ts->pid_filter = !ts->pid_filter; break; case 'y': // --output-nit-pass ts->nit_passthrough = !ts->nit_passthrough; break; case 'w': // --output-eit-pass ts->eit_passthrough = !ts->eit_passthrough; break; case 'x': // --output-tdt-pass ts->tdt_passthrough = !ts->tdt_passthrough; break; case 'c': // --ca-system if (strcasecmp("IRDETO", optarg) == 0) ts->req_CA_sys = CA_IRDETO; else if (strcasecmp("CONNAX", optarg) == 0 || strcasecmp("CONAX", optarg) == 0) ts->req_CA_sys = CA_CONAX; else if (strcasecmp("CRYPTOWORKS", optarg) == 0) ts->req_CA_sys = CA_CRYPTOWORKS; else if (strcasecmp("SECA", optarg) == 0 || strcasecmp("MEDIAGUARD", optarg) == 0) ts->req_CA_sys = CA_SECA; else if (strcasecmp("VIACCESS", optarg) == 0) ts->req_CA_sys = CA_VIACCESS; else if (strcasecmp("VIDEOGUARD", optarg) == 0 || strcasecmp("NDS", optarg) == 0) ts->req_CA_sys = CA_VIDEOGUARD; else if (strcasecmp("NAGRA", optarg) == 0) ts->req_CA_sys = CA_NAGRA; else if (strcasecmp("DRE-CRYPT", optarg) == 0 || strcasecmp("DRECRYPT", optarg) == 0) ts->req_CA_sys = CA_DRECRYPT; else if (strcasecmp("BULCRYPT", optarg) == 0) ts->req_CA_sys = CA_BULCRYPT; else if (strcasecmp("GRIFFIN", optarg) == 0) ts->req_CA_sys = CA_GRIFFIN; else if (strcasecmp("DGCRYPT", optarg) == 0) ts->req_CA_sys = CA_DGCRYPT; else ca_err = 1; break; case 'C': // --caid ts->forced_caid = strtoul(optarg, NULL, 0) & 0xffff; break; case 'Y': // --const-cw ts->camd.constant_codeword = 1; if (strlen(optarg) > 2 && optarg[0] == '0' && optarg[1] == 'x') optarg += 2; if (strlen(optarg) != CODEWORD_LENGTH * 2) { fprintf(stderr, "ERROR: Constant code word should be %u characters long.\n", CODEWORD_LENGTH * 2); exit(EXIT_FAILURE); } if (decode_hex_string(optarg, ts->camd.key->cw, strlen(optarg)) < 0) { fprintf(stderr, "ERROR: Invalid hex string for constant code word: %s\n", optarg); exit(EXIT_FAILURE); } camd_set_cw(ts, ts->camd.key->cw, 0); ts->camd.key->is_valid_cw = 1; break; case 'Q': // --biss-key ts->camd.constant_codeword = 1; if (strlen(optarg) > 2 && optarg[0] == '0' && optarg[1] == 'x') optarg += 2; uint8_t *key = ts->camd.key->cw; // Sometimes the BISS keys are entered with their checksums already calculated (16 symbols, 8 bytes) // This is the same as constant cw with the same key for even and odd if (strlen(optarg) == (BISSKEY_LENGTH + 2) * 2) { if (decode_hex_string(optarg, key, strlen(optarg)) < 0) { fprintf(stderr, "ERROR: Invalid hex string for BISS key: %s\n", optarg); exit(EXIT_FAILURE); } } else { // BISS key without checksum (12 symbols, 6 bytes) if (strlen(optarg) != BISSKEY_LENGTH * 2) { fprintf(stderr, "ERROR: BISS key should be %u characters long.\n", BISSKEY_LENGTH * 2); exit(EXIT_FAILURE); } if (decode_hex_string(optarg, key, strlen(optarg)) < 0) { fprintf(stderr, "ERROR: Invalid hex string for BISS key: %s\n", optarg); exit(EXIT_FAILURE); } // Calculate BISS KEY crc memmove(key + 4, key + 3, 3); key[3] = (uint8_t)(key[0] + key[1] + key[2]); key[7] = (uint8_t)(key[4] + key[5] + key[6]); } // Even and odd keys are the same memcpy(key + 8, key, 8); camd_set_cw(ts, ts->camd.key->cw, 0); ts->camd.key->is_valid_cw = 1; break; case 'A': // --camd-proto if (strcasecmp(optarg, "cs378x") == 0) { camd_proto_cs378x(&ts->camd.ops); } else if (strcasecmp(optarg, "newcamd") == 0) { camd_proto_newcamd(&ts->camd.ops); } else { fprintf(stderr, "Unknown CAMD protocol: %s\n", optarg); exit(EXIT_FAILURE); } break; case 's': // --camd-server server_err = !parse_host_and_port(optarg, &ts->camd.hostname, &ts->camd.service, &port_set); break; case 'U': // --camd-user if (strlen(optarg) < 64) ts->camd.user = optarg; break; case 'P': // --camd-pass ts->camd.pass = optarg; break; case 'B': // --camd-des-key if (strlen(optarg) > 2 && optarg[0] == '0' && optarg[1] == 'x') optarg += 2; if (strlen(optarg) != DESKEY_LENGTH) { fprintf(stderr, "ERROR: des key should be %u characters long.\n", DESKEY_LENGTH); exit(EXIT_FAILURE); } strncpy(ts->camd.newcamd.hex_des_key, optarg, sizeof(ts->camd.newcamd.hex_des_key) - 1); ts->camd.newcamd.hex_des_key[sizeof(ts->camd.newcamd.hex_des_key) - 1] = 0; break; case '4': // --ipv4 ai_family = AF_INET; break; case '6': // --ipv6 ai_family = AF_INET6; break; case 'e': // --emm ts->process_emm = !ts->process_emm; break; case 'Z': // --emm-pid ts->forced_emm_pid = strtoul(optarg, NULL, 0) & 0x1fff; break; case 'E': // --emm-only ts->process_emm = 1; ts->process_ecm = 0; ts->output_stream = 0; break; case 'f': // --emm-report-time ts->emm_report_interval = strtoul(optarg, NULL, 10); if (ts->emm_report_interval > 86400) ts->emm_report_interval = 86400; break; case 'a': // --emm-filter if (ts->emm_filters_num + 1 > MAX_FILTERS) { fprintf(stderr, "ERROR: Maximum allowed filters are %d.\n", MAX_FILTERS); exit(EXIT_FAILURE); } if (filter_parse(optarg, &ts->emm_filters[ts->emm_filters_num])) { ts->emm_filters_num++; } else { fprintf(stderr, "ERROR: Can't parse EMM filter: %s\n", optarg); exit(EXIT_FAILURE); } break; case 'X': // --ecm-pid ts->forced_ecm_pid = strtoul(optarg, NULL, 0) & 0x1fff; break; case 'v': // --ecm-only ts->process_emm = 0; ts->process_ecm = 1; ts->output_stream = 0; break; case 'H': // --ecm-report-time ts->ecm_report_interval = strtoul(optarg, NULL, 10); if (ts->ecm_report_interval > 86400) ts->ecm_report_interval = 86400; break; case 'G': // --ecm-irdeto-type ts->irdeto_ecm_idx = strtoul(optarg, NULL, 0); ts->irdeto_ecm_filter_type = IRDETO_FILTER_IDX; break; case '2': // --ecm-irdeto-chid ts->irdeto_ecm_chid = strtoul(optarg, NULL, 0); ts->irdeto_ecm_filter_type = IRDETO_FILTER_CHID; break; case 'K': // --ecm-no-log ts->ecm_cw_log = !ts->ecm_cw_log; break; case 'J': // --cw-warn-time ts->cw_warn_sec = strtoul(optarg, NULL, 10); if (ts->cw_warn_sec > 86400) ts->cw_warn_sec = 86400; ts->cw_last_warn= ts->cw_last_warn + ts->cw_warn_sec; break; case 'q': // --ecm-and-emm-only ts->process_emm = 1; ts->process_ecm = 1; ts->output_stream = 0; break; case 'D': // --debug ts->debug_level = atoi(optarg); if (ts->debug_level > 0) ts->pid_report = 1; break; case 'j': // --pid-report ts->pid_report = 1; break; case 'b': // --bench csa_benchmark(); exit(EXIT_SUCCESS); case 'n': // --ecm-file case 'm': // --emm-file packet_from_file = 1; packet_buflen = file_hex2buf(optarg, packet_buf, sizeof(packet_buf)); if (!packet_buflen) { fprintf(stderr, "ERROR: Can't init packet from file.\n"); exit(1); } packet_type = j == 'n' ? ECM_MSG : EMM_MSG; break; case 'h': // --help show_help(ts); exit(EXIT_SUCCESS); case 'V': // --version printf("%s\n", program_id); exit(EXIT_SUCCESS); } } if (!ts->ident) { if (ts->syslog_active || ts->notify_program) ident_err = 1; } if (packet_from_file) { int err = 0; if (!ts->forced_caid) { fprintf(stderr, "ERROR: CAID was not set. Use --caid option.\n"); err++; } if (!ts->forced_service_id) { fprintf(stderr, "ERROR: Service id was not set. Use --input-service option.\n"); err++; } if (err) exit(EXIT_FAILURE); ts->threaded = 0; input_addr_err = 0; output_addr_err = 0; ts->input.type = FILE_IO; ts->input.fd = 0; ts->output.type = FILE_IO; ts->output.fd = 1; ts->pid_filter = 0; ts->process_ecm = 0; ts->process_emm = 0; ts->output_stream = 0; ts->camd.no_reconnect = 1; ts->camd.check_emm_errors = 1; ts->emm_filters_num = 0; } // Constant codeword is special. Disable conflicting options if (ts->camd.constant_codeword) { server_err = 0; // No server settings are required ts->process_ecm = 0; ts->process_emm = 0; ts->output_stream = 1; } if (ident_err || ca_err || server_err || input_addr_err || output_addr_err || ts->input.type == WTF_IO || ts->output.type == WTF_IO) { show_help(ts); if (ident_err) fprintf(stderr, "ERROR: Ident is not set, please use --ident option.\n"); if (ca_err) fprintf(stderr, "ERROR: Requested CA system is unsupported.\n"); if (server_err) fprintf(stderr, "ERROR: CAMD server address is not set or it is invalid.\n"); if (input_addr_err) fprintf(stderr, "ERROR: Input address is invalid.\n"); if (output_addr_err) fprintf(stderr, "ERROR: Output address is invalid.\n"); exit(EXIT_FAILURE); } if (decode_hex_string(ts->camd.newcamd.hex_des_key, ts->camd.newcamd.bin_des_key, DESKEY_LENGTH) < 0) { fprintf(stderr, "ERROR: Invalid hex string for des key: %s\n", ts->camd.newcamd.hex_des_key); exit(EXIT_FAILURE); } if (ts->camd.ops.proto == CAMD_NEWCAMD && !port_set) { fprintf(stderr, "ERROR: CAMD server port is not set. Use --camd-server %s:xxxx to set the port.\n", ts->camd.hostname); exit(EXIT_FAILURE); } if (log_filename) { log_file = fopen(log_filename, "a"); if (!log_file) { fprintf(stderr, "ERROR: Can't open log file %s: %s\n", log_filename, strerror(errno)); exit(EXIT_FAILURE); } } if (ts->ident) ts_LOGf("Ident : %s\n", ts->ident); if (ts->notify_program) ts_LOGf("Notify prg : %s\n", ts->notify_program); if (ts->pidfile) ts_LOGf("Daemonize : %s pid file.\n", ts->pidfile); if (ts->syslog_active) { if (ts->syslog_remote) ts_LOGf("Syslog : %s:%d\n", ts->syslog_host, ts->syslog_port); else ts_LOGf("Syslog : enabled\n"); } else { if (!packet_from_file) ts_LOGf("Syslog : disabled\n"); } if (!ts->camd.constant_codeword) { if (ts->forced_caid) ts->req_CA_sys = ts_get_CA_sys(ts->forced_caid); if (!ts->forced_caid) ts_LOGf("CA System : %s\n", ts_get_CA_sys_txt(ts->req_CA_sys)); else ts_LOGf("CA System : %s | CAID: 0x%04x (%d)\n", ts_get_CA_sys_txt(ts->req_CA_sys), ts->forced_caid, ts->forced_caid); } else { char cw_even[64], cw_odd[64]; ts_hex_dump_buf(cw_even, sizeof(cw_even), ts->key.cw , 8, 0); ts_hex_dump_buf(cw_odd , sizeof(cw_odd ), ts->key.cw + 8, 8, 0); ts_LOGf("Constant CW: even = %s\n", cw_even); ts_LOGf("Constant CW: odd = %s\n", cw_odd); } if (ts->input.type == NET_IO) { ts_LOGf("Input addr : %s://%s:%s/\n", ts->rtp_input ? "rtp" : "udp", ts->input.hostname, ts->input.service); ts_LOGf("Input src : %s\n", inet_ntoa(ts->input.isrc)); if (ts->input_buffer_time) { ts_LOGf("Input buff : %u ms\n", ts->input_buffer_time); } } else if (ts->input.type == FILE_IO) { if (!packet_from_file) ts_LOGf("Input file : %s\n", ts->input.fd == 0 ? "STDIN" : ts->input.fname); } if (ts->input_dump_filename) { ts->input_dump_file = fopen(ts->input_dump_filename, "w"); if (ts->input_dump_file) ts_LOGf("Input dump : %s\n", ts->input_dump_filename); else ts_LOGf("Input dump : %s | ERROR: %s\n", ts->input_dump_filename, strerror(errno)); } if (ts->forced_service_id) ts_LOGf("Service id : 0x%04x (%d)\n", ts->forced_service_id, ts->forced_service_id); if (ts->req_CA_sys == CA_IRDETO) { switch (ts->irdeto_ecm_filter_type) { case IRDETO_FILTER_IDX : ts_LOGf("Irdeto ECM : Index: 0x%02x (%d)\n", ts->irdeto_ecm_idx, ts->irdeto_ecm_idx); break; case IRDETO_FILTER_CHID: ts_LOGf("Irdeto ECM : CHID: 0x%04x (%d)\n", ts->irdeto_ecm_chid, ts->irdeto_ecm_chid); break; } } if (ts->output_stream) { if (ts->output.type == NET_IO) { ts_LOGf("Output addr: %s://%s:%s/\n", ts->rtp_output ? "rtp" : "udp", ts->output.hostname, ts->output.service); ts_LOGf("Output intf: %s (IPv6 intf index:%d)\n", inet_ntoa(ts->output.intf), ts->output.v6_if_index); ts_LOGf("Output ttl : %d\n", ts->output.ttl); if (ts->output.tos > -1) ts_LOGf("Output TOS : %u (0x%02x)\n", ts->output.tos, ts->output.tos); if (ts->rtp_output) { ts_LOGf("RTP SSRC : %u (0x%04x)\n", ts->rtp_ssrc, ts->rtp_ssrc); // It is recommended that RTP seqnum starts with random number RAND_bytes((unsigned char *)&(ts->rtp_seqnum), 2); } } else if (ts->output.type == FILE_IO) { ts_LOGf("Output file: %s\n", ts->output.fd == 1 ? "STDOUT" : ts->output.fname); } ts_LOGf("Out filter : %s (%s)%s\n", ts->pid_filter ? "enabled" : "disabled", ts->pid_filter ? "output only service related PIDs" : "output everything", ts->no_output_on_error ? " (No output on CW error)" : "" ); if (ts->pid_filter) { if (ts->nit_passthrough) ts_LOGf("Out filter : Pass through NIT.\n"); if (ts->eit_passthrough) ts_LOGf("Out filter : Pass through EIT (EPG).\n"); if (ts->tdt_passthrough) ts_LOGf("Out filter : Pass through TDT/TOT.\n"); } ts_LOGf("TS discont : %s\n", ts->ts_discont ? "report" : "ignore"); ts->threaded = !(ts->input.type == FILE_IO && ts->input.fd != 0); ts_LOGf("Decoding : %s\n", ts->threaded ? "threaded" : "single thread"); } else { ts_LOGf("Decoding : disabled\n"); } if (!ts->camd.constant_codeword) { ts_LOGf("CAMD proto : %s\n", ts->camd.ops.ident); ts_LOGf("CAMD addr : %s:%s%s\n", ts->camd.hostname, ts->camd.service, ai_family == AF_INET ? " (IPv4 only)" : ai_family == AF_INET6 ? " (IPv6 only)" : " (IPv4/IPv6)" ); ts_LOGf("CAMD user : %s\n", ts->camd.user); ts_LOGf("CAMD pass : %s\n", ts->camd.pass); if (ts->camd.ops.proto == CAMD_NEWCAMD) ts_LOGf("CAMD deskey: %s\n", ts->camd.newcamd.hex_des_key); } if (!packet_from_file) ts_LOGf("EMM process: %s\n", ts->process_emm ? "Yes" : "No"); if (ts->process_emm) { if (ts->forced_emm_pid) ts_LOGf("EMM pid : 0x%04x (%d)\n", ts->forced_emm_pid, ts->forced_emm_pid); if (ts->emm_report_interval) ts_LOGf("EMM report : %d sec\n", ts->emm_report_interval); else ts_LOGf("EMM report : disabled\n"); for (i = 0; i < ts->emm_filters_num; i++) { char tmp[512]; filter_dump(&ts->emm_filters[i], tmp, sizeof(tmp)); ts_LOGf("EMM filter : [%2d] %s\n", i + 1, tmp); } } if (!packet_from_file) ts_LOGf("ECM process: %s\n", ts->process_ecm ? "Yes" : "No"); if (ts->process_ecm) { if (ts->forced_ecm_pid) ts_LOGf("ECM pid : 0x%04x (%d)\n", ts->forced_ecm_pid, ts->forced_ecm_pid); if (ts->ecm_report_interval) ts_LOGf("ECM report : %d sec\n", ts->emm_report_interval); else ts_LOGf("ECM report : disabled\n"); if (ts->cw_warn_sec) ts_LOGf("CW warning : %d sec\n", ts->cw_warn_sec); else ts_LOGf("CW warning : disabled\n"); if (!ts->ecm_cw_log) ts_LOGf("ECM/CW log : disabled\n"); } if (ts->ident) { int len = strlen(ts->ident); for (i = 0; i < len; i++) { if (ts->ident[i] == '/') ts->ident[i] = '-'; } } }