/* * tvheadend, CAPMT Server * Copyright (C) 2009 * * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tvhead.h" #include "tcp.h" #include "psi.h" #include "tsdemux.h" #include "ffdecsa/FFdecsa.h" #include "transports.h" #include "capmt.h" #include "notify.h" #include "dtable.h" // ca_pmt_list_management values: #define CAPMT_LIST_MORE 0x00 // append a 'MORE' CAPMT object the list and start receiving the next object #define CAPMT_LIST_FIRST 0x01 // clear the list when a 'FIRST' CAPMT object is received, and start receiving the next object #define CAPMT_LIST_LAST 0x02 // append a 'LAST' CAPMT object to the list and start working with the list #define CAPMT_LIST_ONLY 0x03 // clear the list when an 'ONLY' CAPMT object is received, and start working with the object #define CAPMT_LIST_ADD 0x04 // append an 'ADD' CAPMT object to the current list and start working with the updated list #define CAPMT_LIST_UPDATE 0x05 // replace an entry in the list with an 'UPDATE' CAPMT object, and start working with the updated list //ca_pmt_cmd_id values: #define CAPMT_CMD_OK_DESCRAMBLING 0x01 // start descrambling the service in this CAPMT object as soon as the list of CAPMT objects is complete #define CAPMT_CMD_OK_MMI 0x02 // #define CAPMT_CMD_QUERY 0x03 // #define CAPMT_CMD_NOT_SELECTED 0x04 #define CW_DUMP(buf, len, format, ...) \ printf(format, __VA_ARGS__); int j; for (j = 0; j < len; ++j) printf("%02X ", buf[j]); printf("\n"); /** * */ TAILQ_HEAD(capmt_queue, capmt); LIST_HEAD(capmt_transport_list, capmt_transport); LIST_HEAD(capmt_caid_ecm_list, capmt_caid_ecm); static struct capmt_queue capmts; static pthread_cond_t capmt_config_changed; /** * capmt descriptor */ typedef struct capmt_descriptor { uint8_t cad_type; uint8_t cad_length; uint8_t cad_data[16]; } __attribute__((packed)) capmt_descriptor_t; /** * capmt header structure */ typedef struct capmt_header { uint8_t capmt_indicator[6]; uint8_t capmt_list_management; uint16_t program_number; unsigned reserved1 : 2; unsigned version_number : 5; unsigned current_next_indicator : 1; unsigned reserved2 : 4; unsigned program_info_length : 12; uint8_t capmt_cmd_id; } __attribute__((packed)) capmt_header_t; /** * caid <-> ecm mapping */ typedef struct capmt_caid_ecm { /** ca system id */ uint16_t cce_caid; /** ecm pid */ uint16_t cce_ecmpid; /** last ecm size */ uint32_t cce_ecmsize; /** last ecm buffer */ uint8_t cce_ecm[256]; LIST_ENTRY(capmt_caid_ecm) cce_link; } capmt_caid_ecm_t; /** * */ typedef struct capmt_transport { th_descrambler_t ct_head; th_transport_t *ct_transport; struct capmt *ct_capmt; LIST_ENTRY(capmt_transport) ct_link; /** * Sequence number generated on write (based on capmt_seq), * used to pair reply message (i.e when i CT_STATE_WAIT_REPLY) * with capmt_transport */ uint16_t ct_seq; /** * list of used ca-systems with ids and last ecm */ struct capmt_caid_ecm_list ct_caid_ecm; /** * Status of the key(s) in ct_keys */ enum { CT_UNKNOWN, CT_RESOLVED, CT_FORBIDDEN } ct_keystate; void *ct_keys; /** * CSA */ int ct_cluster_size; uint8_t *ct_tsbcluster; int ct_fill; } capmt_transport_t; /** * */ typedef struct capmt { int capmt_fd; int capmt_connected; int capmt_sock_ca0; int capmt_sock; int capmt_retry_delay; pthread_mutex_t capmt_send_mutex; pthread_cond_t capmt_cond; pthread_cond_t capmt_writer_cond; /* Used to wakeup writer */ int capmt_writer_running; TAILQ_ENTRY(capmt) capmt_link; /* Linkage protected via global_lock */ struct capmt_transport_list capmt_transports; uint16_t capmt_caid; uint16_t capmt_seq; uint8_t capmt_key[16]; uint8_t capmt_buf[256]; int capmt_bufptr; /* Provder IDs */ // uint32_t capmt_provider_ids[256]; // int capmt_num_providers; /* From configuration */ // uint8_t capmt_confedkey[14]; // char *capmt_username; // char *capmt_password; // char *capmt_password_salted; /* salted version */ // char *capmt_comment; char *capmt_sockfile; char *capmt_hostname; int capmt_port; char *capmt_comment; char *capmt_id; // const char *capmt_errtxt; int capmt_enabled; int capmt_running; int capmt_reconfigure; capmt_transport_t *ct; } capmt_t; /** * */ static int capmt_send_msg(capmt_t *capmt, const uint8_t *buf, size_t len) { int n; pthread_mutex_lock(&capmt->capmt_send_mutex); n = write(capmt->capmt_sock, buf, len); pthread_mutex_unlock(&capmt->capmt_send_mutex); return n; } /** * global_lock is held * tht_stream_mutex is held */ static void capmt_transport_destroy(th_descrambler_t *td) { tvhlog(LOG_INFO, "capmt", "Removing CAPMT Server from service"); capmt_transport_t *ct = (capmt_transport_t *)td; capmt_t *capmt; TAILQ_FOREACH(capmt, &capmts, capmt_link) if (capmt->ct == ct) capmt->ct = NULL; capmt_caid_ecm_t *cce; while (!LIST_EMPTY(&ct->ct_caid_ecm)) { /* List Deletion. */ cce = LIST_FIRST(&ct->ct_caid_ecm); LIST_REMOVE(cce, cce_link); free(cce); } LIST_REMOVE(td, td_transport_link); LIST_REMOVE(ct, ct_link); free_key_struct(ct->ct_keys); free(ct->ct_tsbcluster); free(ct); } static void handle_ca0(capmt_t* capmt) { capmt_transport_t *ct; th_transport_t *t; int ret; uint8_t invalid[8], buffer[20]; memset(invalid, 0, 8); tvhlog(LOG_INFO, "capmt", "running handle_ca0"); while (capmt->capmt_running) { ret = recv(capmt->capmt_sock_ca0, buffer, 18, MSG_WAITALL); ct = capmt->ct; if (ct == NULL) continue; t = ct->ct_transport; if (ret < 0) { tvhlog(LOG_ERR, "capmt", "error receiving over socket"); // TODO reaction } else if (ret == 0) { // normal socket shutdown tvhlog(LOG_INFO, "capmt", "normal socket shutdown"); break; } else if(ret < 18) { if(ct->ct_keystate != CT_FORBIDDEN) { tvhlog(LOG_ERR, "capmt", "Can not descramble service \"%s\", access denied", t->tht_svcname); ct->ct_keystate = CT_FORBIDDEN; } continue; } /* get control words */ uint8_t *even = &buffer[2], *odd = &buffer[10]; if (memcmp(even, invalid, 8)) set_even_control_word(ct->ct_keys, even); if (memcmp(odd, invalid, 8)) set_odd_control_word(ct->ct_keys, odd); if(ct->ct_keystate != CT_RESOLVED) tvhlog(LOG_INFO, "capmt", "Obtained key for service \"%s\"",t->tht_svcname); ct->ct_keystate = CT_RESOLVED; } tvhlog(LOG_INFO, "capmt", "exiting handle_ca0"); } /** * */ static void * capmt_thread(void *aux) { capmt_t *capmt = aux; struct timespec ts; int d; while (capmt->capmt_running) { capmt->capmt_sock = -1; capmt->capmt_sock_ca0 = -1; capmt->capmt_connected = 0; pthread_mutex_lock(&global_lock); while(capmt->capmt_running && capmt->capmt_enabled == 0) pthread_cond_wait(&capmt->capmt_cond, &global_lock); pthread_mutex_unlock(&global_lock); /* open connection to camd.socket */ capmt->capmt_sock = socket(AF_LOCAL, SOCK_STREAM, 0); struct sockaddr_un serv_addr_un; memset(&serv_addr_un, 0, sizeof(serv_addr_un)); serv_addr_un.sun_family = AF_LOCAL; snprintf(serv_addr_un.sun_path, sizeof(serv_addr_un.sun_path), "%s", capmt->capmt_sockfile); if (connect(capmt->capmt_sock, (const struct sockaddr*)&serv_addr_un, sizeof(serv_addr_un)) == 0) { capmt->capmt_connected = 1; /* open connection to emulated ca0 device */ capmt->capmt_sock_ca0 = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); struct sockaddr_in serv_addr; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons( (unsigned short int)capmt->capmt_port); serv_addr.sin_family = AF_INET; if (bind(capmt->capmt_sock_ca0, (const struct sockaddr*)&serv_addr, sizeof(serv_addr)) != 0) perror("[CapmtServer] ERROR binding to ca0"); else handle_ca0(capmt); } else tvhlog(LOG_ERR, "capmt", "Error connecting to %s: %s", capmt->capmt_sockfile, strerror(errno)); capmt->capmt_connected = 0; /* close opened sockets */ if (capmt->capmt_sock > 0) close(capmt->capmt_sock); if (capmt->capmt_sock_ca0 > 0) close(capmt->capmt_sock_ca0); /* schedule reconnection */ if(subscriptions_active()) { d = 3; } else { d = 60; } ts.tv_sec = time(NULL) + d; ts.tv_nsec = 0; tvhlog(LOG_INFO, "capmt", "Automatic reconnection attempt in in %d seconds", d); pthread_mutex_lock(&global_lock); pthread_cond_timedwait(&capmt_config_changed, &global_lock, &ts); pthread_mutex_unlock(&global_lock); } return NULL; } /** * */ static void capmt_table_input(struct th_descrambler *td, struct th_transport *t, struct th_stream *st, uint8_t *data, int len) { capmt_transport_t *ct = (capmt_transport_t *)td; capmt_t *capmt = ct->ct_capmt; switch(data[0]) { case 0x80: case 0x81: { /* ECM */ uint16_t caid = st->st_caid; /* search ecmpid in list */ capmt_caid_ecm_t *cce, *cce2; LIST_FOREACH(cce, &ct->ct_caid_ecm, cce_link) if (cce->cce_caid == caid) break; if (!cce) { /* ecmpid not already seen, add it to list */ cce = calloc(1, sizeof(capmt_caid_ecm_t)); cce->cce_caid = caid; cce->cce_ecmpid = st->st_pid; LIST_INSERT_HEAD(&ct->ct_caid_ecm, cce, cce_link); /* do not send now, wait for more ecmpid's */ break; } if ((cce->cce_ecmsize == len) && !memcmp(cce->cce_ecm, data, len)) break; /* key already sent */ if(capmt->capmt_sock == -1) { // New key, but we are not connected (anymore), can not descramble ct->ct_keystate = CT_UNKNOWN; break; } uint16_t sid = t->tht_dvb_service_id; uint16_t ecmpid = st->st_pid; uint16_t transponder = 0; cce2 = LIST_FIRST(&ct->ct_caid_ecm); if (caid != cce2->cce_caid) return; static uint8_t pmtversion = 1; /* buffer for capmt */ int pos = 0; uint8_t buf[4094]; capmt_header_t head = { .capmt_indicator = { 0x9F, 0x80, 0x32, 0x82, 0x00, 0x00 }, .capmt_list_management = CAPMT_LIST_ONLY, .program_number = sid, .version_number = 0, .current_next_indicator = 0, .program_info_length = 0, .capmt_cmd_id = CAPMT_CMD_OK_DESCRAMBLING, }; memcpy(&buf[pos], &head, sizeof(head)); pos += sizeof(head); capmt_descriptor_t prd = { .cad_type = 0x81, .cad_length = 0x08, .cad_data = { 0x00, 0x00, 0x00, 0x00, sid >> 8, sid & 0xFF, transponder >> 8, transponder & 0xFF }}; memcpy(&buf[pos], &prd, prd.cad_length + 2); pos += prd.cad_length + 2; capmt_descriptor_t dmd = { .cad_type = 0x82, .cad_length = 0x02, .cad_data = { 0x01, 0x00 }}; memcpy(&buf[pos], &dmd, dmd.cad_length + 2); pos += dmd.cad_length + 2; capmt_descriptor_t ecd = { .cad_type = 0x84, .cad_length = 0x02, .cad_data = { ecmpid >> 8, ecmpid & 0xFF }}; memcpy(&buf[pos], &ecd, ecd.cad_length + 2); pos += ecd.cad_length + 2; LIST_FOREACH(cce2, &ct->ct_caid_ecm, cce_link) { capmt_descriptor_t cad = { .cad_type = 0x09, .cad_length = 0x04, .cad_data = { cce2->cce_caid >> 8, cce2->cce_caid & 0xFF, cce2->cce_ecmpid >> 8 | 0xE0, cce2->cce_ecmpid & 0xFF}}; memcpy(&buf[pos], &cad, cad.cad_length + 2); pos += cad.cad_length + 2; } uint8_t end[] = { 0x01, 0x0F, 0x00, 0x00, 0x06 }; memcpy(&buf[pos], end, sizeof(end)); pos += sizeof(end); buf[10] = ((pos - 5 - 12) & 0xF00) >> 8; buf[11] = ((pos - 5 - 12) & 0xFF); buf[4] = ((pos - 6) >> 8); buf[5] = ((pos - 6) & 0xFF); buf[7] = sid >> 8; buf[8] = sid & 0xFF; memcpy(cce->cce_ecm, data, len); cce->cce_ecmsize = len; if(ct->ct_keystate != CT_RESOLVED) tvhlog(LOG_INFO, "capmt", "Trying to obtain key for service \"%s\"",t->tht_svcname); //printf("sending capmt\n"); buf[9] = pmtversion; pmtversion = (pmtversion + 1) & 0x1F; /*int j, l; for(j = 0; j < pos;) { printf("%04x:",j); for( l=0 ; l<16 && jct = ct; ct->ct_seq = capmt_send_msg(capmt, buf, pos); break; } default: /* EMM */ break; } } /** * */ static int capmt_descramble(th_descrambler_t *td, th_transport_t *t, struct th_stream *st, uint8_t *tsb) { capmt_transport_t *ct = (capmt_transport_t *)td; int r, i; unsigned char *vec[3]; uint8_t *t0; if(ct->ct_keystate == CT_FORBIDDEN) return 1; if(ct->ct_keystate != CT_RESOLVED) return -1; memcpy(ct->ct_tsbcluster + ct->ct_fill * 188, tsb, 188); ct->ct_fill++; if(ct->ct_fill != ct->ct_cluster_size) return 0; ct->ct_fill = 0; vec[0] = ct->ct_tsbcluster; vec[1] = ct->ct_tsbcluster + ct->ct_cluster_size * 188; vec[2] = NULL; while(1) { t0 = vec[0]; r = decrypt_packets(ct->ct_keys, vec); if(r == 0) break; for(i = 0; i < r; i++) { ts_recv_packet2(t, t0); t0 += 188; } } return 0; } /** * Check if our CAID's matches, and if so, link * * global_lock is held */ void capmt_transport_start(th_transport_t *t) { capmt_t *capmt; capmt_transport_t *ct; th_descrambler_t *td; lock_assert(&global_lock); TAILQ_FOREACH(capmt, &capmts, capmt_link) { tvhlog(LOG_INFO, "capmt", "Starting capmt server for service \"%s\"", t->tht_svcname); ct = calloc(1, sizeof(capmt_transport_t)); ct->ct_cluster_size = get_suggested_cluster_size(); ct->ct_tsbcluster = malloc(ct->ct_cluster_size * 188); ct->ct_keys = get_key_struct(); ct->ct_capmt = capmt; ct->ct_transport = t; td = &ct->ct_head; td->td_stop = capmt_transport_destroy; td->td_table = capmt_table_input; td->td_descramble = capmt_descramble; LIST_INSERT_HEAD(&t->tht_descramblers, td, td_transport_link); LIST_INSERT_HEAD(&capmt->capmt_transports, ct, ct_link); } } /** * */ static void capmt_destroy(capmt_t *capmt) { lock_assert(&global_lock); TAILQ_REMOVE(&capmts, capmt, capmt_link); capmt->capmt_running = 0; pthread_cond_signal(&capmt->capmt_cond); } /** * */ static capmt_t * capmt_entry_find(const char *id, int create) { pthread_attr_t attr; pthread_t ptid; char buf[20]; capmt_t *capmt; static int tally; if(id != NULL) { TAILQ_FOREACH(capmt, &capmts, capmt_link) if(!strcmp(capmt->capmt_id, id)) return capmt; } if(create == 0) return NULL; if(id == NULL) { tally++; snprintf(buf, sizeof(buf), "%d", tally); id = buf; } else { tally = MAX(atoi(id), tally); } capmt = calloc(1, sizeof(capmt_t)); pthread_cond_init(&capmt->capmt_cond, NULL); pthread_mutex_init(&capmt->capmt_send_mutex, NULL); capmt->capmt_id = strdup(id); capmt->capmt_running = 1; TAILQ_INSERT_TAIL(&capmts, capmt, capmt_link); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&ptid, &attr, capmt_thread, capmt); pthread_attr_destroy(&attr); return capmt; } /** * */ static htsmsg_t * capmt_record_build(capmt_t *capmt) { htsmsg_t *e = htsmsg_create_map(); htsmsg_add_str(e, "id", capmt->capmt_id); htsmsg_add_u32(e, "enabled", !!capmt->capmt_enabled); htsmsg_add_u32(e, "connected", !!capmt->capmt_connected); htsmsg_add_str(e, "camdfilename", capmt->capmt_sockfile ?: ""); htsmsg_add_u32(e, "port", capmt->capmt_port); htsmsg_add_str(e, "comment", capmt->capmt_comment ?: ""); return e; } /** * */ static htsmsg_t * capmt_entry_update(void *opaque, const char *id, htsmsg_t *values, int maycreate) { capmt_t *capmt; const char *s; uint32_t u32; if((capmt = capmt_entry_find(id, maycreate)) == NULL) return NULL; lock_assert(&global_lock); if((s = htsmsg_get_str(values, "camdfilename")) != NULL) { free(capmt->capmt_sockfile); capmt->capmt_sockfile = strdup(s); } if(!htsmsg_get_u32(values, "port", &u32)) capmt->capmt_port = u32; if((s = htsmsg_get_str(values, "comment")) != NULL) { free(capmt->capmt_comment); capmt->capmt_comment = strdup(s); } if(!htsmsg_get_u32(values, "enabled", &u32)) capmt->capmt_enabled = u32; capmt->capmt_reconfigure = 1; /* if(capmt->capmt_fd != -1) shutdown(capmt->capmt_fd, SHUT_RDWR);*/ pthread_cond_signal(&capmt->capmt_cond); pthread_cond_broadcast(&capmt_config_changed); return capmt_record_build(capmt); } /** * */ static int capmt_entry_delete(void *opaque, const char *id) { capmt_t *capmt; pthread_cond_broadcast(&capmt_config_changed); if((capmt = capmt_entry_find(id, 0)) == NULL) return -1; capmt_destroy(capmt); return 0; } /** * */ static htsmsg_t * capmt_entry_get_all(void *opaque) { htsmsg_t *r = htsmsg_create_list(); capmt_t *capmt; TAILQ_FOREACH(capmt, &capmts, capmt_link) htsmsg_add_msg(r, NULL, capmt_record_build(capmt)); return r; } /** * */ static htsmsg_t * capmt_entry_get(void *opaque, const char *id) { capmt_t *capmt; if((capmt = capmt_entry_find(id, 0)) == NULL) return NULL; return capmt_record_build(capmt); } /** * */ static htsmsg_t * capmt_entry_create(void *opaque) { pthread_cond_broadcast(&capmt_config_changed); return capmt_record_build(capmt_entry_find(NULL, 1)); } /** * */ static const dtable_class_t capmt_dtc = { .dtc_record_get = capmt_entry_get, .dtc_record_get_all = capmt_entry_get_all, .dtc_record_create = capmt_entry_create, .dtc_record_update = capmt_entry_update, .dtc_record_delete = capmt_entry_delete, .dtc_read_access = ACCESS_ADMIN, .dtc_write_access = ACCESS_ADMIN, }; /** * */ void capmt_init(void) { dtable_t *dt; TAILQ_INIT(&capmts); pthread_cond_init(&capmt_config_changed, NULL); dt = dtable_create(&capmt_dtc, "capmt", NULL); dtable_load(dt); }