2026-02-16 09:02:48 +00:00
# define MODULE_LOG_PREFIX "work"
# include "globals.h"
# include "module-cacheex.h"
# include "oscam-client.h"
# include "oscam-ecm.h"
# include "oscam-emm.h"
# include "oscam-lock.h"
# include "oscam-net.h"
# include "oscam-reader.h"
# include "oscam-string.h"
# include "oscam-work.h"
# include "reader-common.h"
# include "module-cccam.h"
# include "module-cccam-data.h"
# include "module-cccshare.h"
# include "oscam-time.h"
extern CS_MUTEX_LOCK system_lock ;
extern int32_t thread_pipe [ 2 ] ;
struct job_data
{
enum actions action ;
struct s_reader * rdr ;
struct s_client * cl ;
void * ptr ;
struct timeb time ;
uint16_t len ;
} ;
static void free_job_data ( struct job_data * data )
{
if ( ! data )
{ return ; }
if ( data - > len & & data - > ptr )
{
// special free checks
if ( data - > action = = ACTION_ECM_ANSWER_CACHE )
{
NULLFREE ( ( ( struct s_write_from_cache * ) data - > ptr ) - > er_cache ) ;
}
NULLFREE ( data - > ptr ) ;
}
NULLFREE ( data ) ;
}
void free_joblist ( struct s_client * cl )
{
int32_t lock_status = pthread_mutex_trylock ( & cl - > thread_lock ) ;
LL_ITER it = ll_iter_create ( cl - > joblist ) ;
struct job_data * data ;
while ( ( data = ll_iter_next ( & it ) ) )
{
free_job_data ( data ) ;
}
ll_destroy ( & cl - > joblist ) ;
cl - > account = NULL ;
if ( cl - > work_job_data ) // Free job_data that was not freed by work_thread
{ free_job_data ( cl - > work_job_data ) ; }
cl - > work_job_data = NULL ;
if ( lock_status = = 0 )
{ SAFE_MUTEX_UNLOCK ( & cl - > thread_lock ) ; }
pthread_mutex_destroy ( & cl - > thread_lock ) ;
}
/*
Work threads are named like this :
w [ r | c ] XX - [ rdr - > label | client - > username ]
w - work thread prefix
[ r | c ] - depending whether the the action is related to reader or client
XX - two digit action code from enum actions
label - reader label or client username ( see username ( ) function )
*/
static void set_work_thread_name ( struct job_data * data )
{
char thread_name [ 16 + 1 ] ;
snprintf ( thread_name , sizeof ( thread_name ) , " w%c%02d-%s " ,
data - > action < ACTION_CLIENT_FIRST ? ' r ' : ' c ' ,
data - > action ,
username ( data - > cl )
) ;
set_thread_name ( thread_name ) ;
}
# define __free_job_data(client, job_data) \
do { \
client - > work_job_data = NULL ; \
if ( job_data & & job_data ! = & tmp_data ) { \
free_job_data ( job_data ) ; \
} \
job_data = NULL ; \
} while ( 0 )
void * work_thread ( void * ptr )
{
struct job_data * data = ( struct job_data * ) ptr ;
struct s_client * cl = data - > cl ;
struct s_reader * reader = cl - > reader ;
struct timeb start , end ; // start time poll, end time poll
struct job_data tmp_data ;
struct pollfd pfd [ 1 ] ;
SAFE_SETSPECIFIC ( getclient , cl ) ;
cl - > thread = pthread_self ( ) ;
cl - > thread_active = 1 ;
set_work_thread_name ( data ) ;
struct s_module * module = get_module ( cl ) ;
uint16_t bufsize = module - > bufsize ; // CCCam needs more than 1024bytes!
if ( ! bufsize )
2026-02-23 16:40:08 +00:00
{ bufsize = DEFAULT_MODULE_BUFSIZE ; }
2026-02-16 09:02:48 +00:00
uint8_t * mbuf ;
if ( ! cs_malloc ( & mbuf , bufsize ) )
{ return NULL ; }
cl - > work_mbuf = mbuf ; // Track locally allocated data, because some callback may call cs_exit/cs_disconect_client/pthread_exit and then mbuf would be leaked
int32_t n = 0 , rc = 0 , i , idx , s , dblvl ;
( void ) dblvl ;
uint8_t dcw [ 16 ] ;
int8_t restart_reader = 0 ;
while ( cl - > thread_active )
{
cs_ftime ( & start ) ; // register start time
while ( cl - > thread_active )
{
if ( ! cl | | cl - > kill | | ! is_valid_client ( cl ) )
{
SAFE_MUTEX_LOCK ( & cl - > thread_lock ) ;
cl - > thread_active = 0 ;
SAFE_MUTEX_UNLOCK ( & cl - > thread_lock ) ;
cs_log_dbg ( D_TRACE , " ending thread (kill) " ) ;
__free_job_data ( cl , data ) ;
cl - > work_mbuf = NULL ; // Prevent free_client from freeing mbuf (->work_mbuf)
free_client ( cl ) ;
if ( restart_reader )
{ restart_cardreader ( reader , 0 ) ; }
NULLFREE ( mbuf ) ;
pthread_exit ( NULL ) ;
return NULL ;
}
if ( data & & data - > action ! = ACTION_READER_CHECK_HEALTH )
{ cs_log_dbg ( D_TRACE , " data from add_job action=%d client %c %s " , data - > action , cl - > typ , username ( cl ) ) ; }
if ( ! data )
{
if ( ! cl - > kill & & cl - > typ ! = ' r ' )
{ client_check_status ( cl ) ; } // do not call for physical readers as this might cause an endless job loop
SAFE_MUTEX_LOCK ( & cl - > thread_lock ) ;
if ( cl - > joblist & & ll_count ( cl - > joblist ) > 0 )
{
LL_ITER itr = ll_iter_create ( cl - > joblist ) ;
data = ll_iter_next_remove ( & itr ) ;
if ( data )
{ set_work_thread_name ( data ) ; }
//cs_log_dbg(D_TRACE, "start next job from list action=%d", data->action);
}
SAFE_MUTEX_UNLOCK ( & cl - > thread_lock ) ;
}
if ( ! data )
{
/* for serial client cl->pfd is file descriptor for serial port not socket
for example : pfd = open ( " /dev/ttyUSB0 " ) ; */
if ( ! cl - > pfd | | module - > listenertype = = LIS_SERIAL )
{ break ; }
pfd [ 0 ] . fd = cl - > pfd ;
pfd [ 0 ] . events = POLLIN | POLLPRI ;
SAFE_MUTEX_LOCK ( & cl - > thread_lock ) ;
cl - > thread_active = 2 ;
SAFE_MUTEX_UNLOCK ( & cl - > thread_lock ) ;
rc = poll ( pfd , 1 , 3000 ) ;
SAFE_MUTEX_LOCK ( & cl - > thread_lock ) ;
cl - > thread_active = 1 ;
SAFE_MUTEX_UNLOCK ( & cl - > thread_lock ) ;
if ( rc > 0 )
{
cs_ftime ( & end ) ; // register end time
cs_log_dbg ( D_TRACE , " [OSCAM-WORK] new event %d occurred on fd %d after % " PRId64 " ms inactivity " , pfd [ 0 ] . revents ,
pfd [ 0 ] . fd , comp_timeb ( & end , & start ) ) ;
data = & tmp_data ;
data - > ptr = NULL ;
cs_ftime ( & start ) ; // register start time for new poll next run
if ( reader )
{ data - > action = ACTION_READER_REMOTE ; }
else
{
if ( cl - > is_udp )
{
data - > action = ACTION_CLIENT_UDP ;
data - > ptr = mbuf ;
data - > len = bufsize ;
}
else
{ data - > action = ACTION_CLIENT_TCP ; }
if ( pfd [ 0 ] . revents & ( POLLHUP | POLLNVAL | POLLERR ) )
{ cl - > kill = 1 ; }
}
}
}
if ( ! data )
{ continue ; }
if ( ! reader & & data - > action < ACTION_CLIENT_FIRST )
{
__free_job_data ( cl , data ) ;
break ;
}
if ( ! data - > action )
{ break ; }
struct timeb actualtime ;
cs_ftime ( & actualtime ) ;
int64_t gone = comp_timeb ( & actualtime , & data - > time ) ;
if ( data ! = & tmp_data & & gone > ( int ) cfg . ctimeout + 1000 )
{
cs_log_dbg ( D_TRACE , " dropping client data for %s time % " PRId64 " ms " , username ( cl ) , gone ) ;
__free_job_data ( cl , data ) ;
continue ;
}
if ( data ! = & tmp_data )
{ cl - > work_job_data = data ; } // Track the current job_data
switch ( data - > action )
{
case ACTION_READER_IDLE :
reader_do_idle ( reader ) ;
break ;
case ACTION_READER_REMOTE :
s = check_fd_for_data ( cl - > pfd ) ;
if ( s = = 0 ) // no data, another thread already read from fd?
{ break ; }
if ( s < 0 )
{
if ( cl - > reader - > ph . type = = MOD_CONN_TCP )
{ network_tcp_connection_close ( reader , " disconnect " ) ; }
break ;
}
rc = cl - > reader - > ph . recv ( cl , mbuf , bufsize ) ;
if ( rc < 0 )
{
if ( cl - > reader - > ph . type = = MOD_CONN_TCP )
{
network_tcp_connection_close ( reader , " disconnect on receive " ) ;
# ifdef CS_CACHEEX_AIO
cl - > cacheex_aio_checked = 0 ;
# endif
}
break ;
}
cl - > last = time ( NULL ) ; // *********************************** TO BE REPLACE BY CS_FTIME() LATER ****************
idx = cl - > reader - > ph . c_recv_chk ( cl , dcw , & rc , mbuf , rc ) ;
if ( idx < 0 ) { break ; } // no dcw received
if ( ! idx ) { idx = cl - > last_idx ; }
cl - > reader - > last_g = time ( NULL ) ; // *********************************** TO BE REPLACE BY CS_FTIME() LATER **************** // for reconnect timeout
for ( i = 0 , n = 0 ; i < cfg . max_pending & & n = = 0 ; i + + )
{
if ( cl - > ecmtask [ i ] . idx = = idx )
{
cl - > pending - - ;
casc_check_dcw ( reader , i , rc , dcw ) ;
n + + ;
}
}
break ;
case ACTION_READER_RESET :
cardreader_do_reset ( reader ) ;
break ;
case ACTION_READER_ECM_REQUEST :
reader_get_ecm ( reader , data - > ptr ) ;
break ;
case ACTION_READER_EMM :
reader_do_emm ( reader , data - > ptr ) ;
break ;
case ACTION_READER_SENDCMD :
# ifdef READER_VIDEOGUARD
if ( ! reader )
{ break ; }
dblvl = cs_dblevel ;
cs_dblevel = dblvl | D_READER ;
rc = cardreader_do_rawcmd ( reader , data - > ptr ) ;
cs_log_dbg ( D_TRACE , " sendcmd rc: %i, csystem: %s " , rc , reader - > csystem - > desc ) ;
if ( rc = = - 9 )
{
CMD_PACKET * cp = data - > ptr ;
uint8_t response [ MAX_CMD_SIZE ] ;
memset ( response , 0 , sizeof ( response ) ) ;
uint16_t response_length [ 1 ] = { 0 } ;
rc = reader_cmd2icc ( reader , cp - > cmd , cp - > cmdlen , response , response_length ) ;
cs_log_dbg ( D_TRACE , " sendcmd rc: %i, len: %i " , rc , * response_length ) ;
if ( * response_length )
{
cs_log_dump_dbg ( D_TRACE , response , * response_length , " sendcmd response: " ) ;
}
}
cs_dblevel = dblvl ;
# endif
break ;
case ACTION_READER_CARDINFO :
reader_do_card_info ( reader ) ;
break ;
case ACTION_READER_POLL_STATUS :
# ifdef READER_VIDEOGUARD
cardreader_poll_status ( reader ) ;
# endif
break ;
case ACTION_READER_INIT :
if ( ! cl - > init_done )
{ reader_init ( reader ) ; }
break ;
case ACTION_READER_RESTART :
cl - > kill = 1 ;
restart_reader = 1 ;
break ;
case ACTION_READER_RESET_FAST :
cl - > reader - > card_status = CARD_NEED_INIT ;
cardreader_do_reset ( reader ) ;
break ;
case ACTION_READER_CHECK_HEALTH :
cardreader_do_checkhealth ( reader ) ;
break ;
case ACTION_READER_CAPMT_NOTIFY :
if ( cl - > reader - > ph . c_capmt ) { cl - > reader - > ph . c_capmt ( cl , data - > ptr ) ; }
break ;
case ACTION_CLIENT_UDP :
n = module - > recv ( cl , data - > ptr , data - > len ) ;
if ( n < 0 ) { break ; }
module - > s_handler ( cl , data - > ptr , n ) ;
break ;
case ACTION_CLIENT_TCP :
s = check_fd_for_data ( cl - > pfd ) ;
if ( s = = 0 ) // no data, another thread already read from fd?
{ break ; }
if ( s < 0 ) // system error or fd wants to be closed
{
cl - > kill = 1 ; // kill client on next run
continue ;
}
n = module - > recv ( cl , mbuf , bufsize ) ;
if ( n < 0 )
{
cl - > kill = 1 ; // kill client on next run
continue ;
}
module - > s_handler ( cl , mbuf , n ) ;
break ;
case ACTION_CACHEEX1_DELAY :
cacheex_mode1_delay ( data - > ptr ) ;
break ;
case ACTION_CACHEEX_TIMEOUT :
cacheex_timeout ( data - > ptr ) ;
break ;
case ACTION_FALLBACK_TIMEOUT :
fallback_timeout ( data - > ptr ) ;
break ;
case ACTION_CLIENT_TIMEOUT :
ecm_timeout ( data - > ptr ) ;
break ;
case ACTION_ECM_ANSWER_READER :
chk_dcw ( data - > ptr ) ;
break ;
case ACTION_ECM_ANSWER_CACHE :
write_ecm_answer_fromcache ( data - > ptr ) ;
break ;
case ACTION_CLIENT_INIT :
if ( module - > s_init )
{ module - > s_init ( cl ) ; }
cl - > is_udp = module - > type = = MOD_CONN_UDP ;
cl - > init_done = 1 ;
break ;
case ACTION_CLIENT_IDLE :
if ( module - > s_idle )
{ module - > s_idle ( cl ) ; }
else
{
cs_log ( " user %s reached %d sec idle limit. " , username ( cl ) , cfg . cmaxidle ) ;
cl - > kill = 1 ;
}
break ;
case ACTION_CACHE_PUSH_OUT :
cacheex_push_out ( cl , data - > ptr ) ;
break ;
case ACTION_CLIENT_KILL :
cl - > kill = 1 ;
break ;
case ACTION_CLIENT_SEND_MSG :
{
if ( config_enabled ( MODULE_CCCAM ) )
{
struct s_clientmsg * clientmsg = ( struct s_clientmsg * ) data - > ptr ;
cc_cmd_send ( cl , clientmsg - > msg , clientmsg - > len , clientmsg - > cmd ) ;
}
break ;
}
case ACTION_PEER_IDLE :
if ( module - > s_peer_idle )
{ module - > s_peer_idle ( cl ) ; }
break ;
case ACTION_CLIENT_HIDECARDS :
{
# ifdef CS_ANTICASC
if ( config_enabled ( MODULE_CCCSHARE ) )
{
int32_t hidetime = ( cl - > account - > acosc_penalty_duration = = - 1 ? cfg . acosc_penalty_duration : cl - > account - > acosc_penalty_duration ) ;
if ( hidetime )
{
int32_t hide_count ;
int32_t cardsize ;
int32_t ii , uu = 0 ;
LLIST * * sharelist = get_and_lock_sharelist ( ) ;
LLIST * sharelist2 = ll_create ( " hidecards-sharelist " ) ;
for ( ii = 0 ; ii < CAID_KEY ; ii + + )
{
if ( sharelist [ ii ] )
{
ll_putall ( sharelist2 , sharelist [ ii ] ) ;
}
}
unlock_sharelist ( ) ;
struct cc_card * * cardarray = get_sorted_card_copy ( sharelist2 , 0 , & cardsize ) ;
ll_destroy ( & sharelist2 ) ;
for ( ii = 0 ; ii < cardsize ; ii + + )
{
if ( hidecards_card_valid_for_client ( cl , cardarray [ ii ] ) )
{
if ( cardarray [ ii ] - > id )
{
hide_count = hide_card_to_client ( cardarray [ ii ] , cl ) ;
if ( hide_count )
{
cs_log_dbg ( D_TRACE , " Hiding card_%d caid=%04x remoteid=%08x from %s for %d %s " ,
uu , cardarray [ ii ] - > caid , cardarray [ ii ] - > remote_id , username ( cl ) , hidetime , hidetime > 1 ? " secconds " : " seccond " ) ;
uu + = 1 ;
}
}
}
}
cs_sleepms ( hidetime * 1000 ) ;
uu = 0 ;
for ( ii = 0 ; ii < cardsize ; ii + + )
{
if ( hidecards_card_valid_for_client ( cl , cardarray [ ii ] ) )
{
if ( cardarray [ ii ] - > id )
{
hide_count = unhide_card_to_client ( cardarray [ ii ] , cl ) ;
if ( hide_count )
{
cs_log_dbg ( D_TRACE , " Unhiding card_%d caid=%04x remoteid=%08x for %s " ,
uu , cardarray [ ii ] - > caid , cardarray [ ii ] - > remote_id , username ( cl ) ) ;
uu + = 1 ;
}
}
}
}
NULLFREE ( cardarray ) ;
}
}
# endif
break ;
} // case ACTION_CLIENT_HIDECARDS
} // switch
__free_job_data ( cl , data ) ;
}
if ( thread_pipe [ 1 ] & & ( mbuf [ 0 ] ! = 0x00 ) )
{
cs_log_dump_dbg ( D_TRACE , mbuf , 1 , " [OSCAM-WORK] Write to pipe: " ) ;
if ( write ( thread_pipe [ 1 ] , mbuf , 1 ) = = - 1 ) // wakeup client check
{
cs_log_dbg ( D_TRACE , " [OSCAM-WORK] Writing to pipe failed (errno=%d %s) " , errno , strerror ( errno ) ) ;
}
}
// Check for some race condition where while we ended, another thread added a job
SAFE_MUTEX_LOCK ( & cl - > thread_lock ) ;
if ( cl - > joblist & & ll_count ( cl - > joblist ) > 0 )
{
SAFE_MUTEX_UNLOCK ( & cl - > thread_lock ) ;
continue ;
}
else
{
cl - > thread_active = 0 ;
SAFE_MUTEX_UNLOCK ( & cl - > thread_lock ) ;
break ;
}
}
cl - > thread_active = 0 ;
cl - > work_mbuf = NULL ; // Prevent free_client from freeing mbuf (->work_mbuf)
NULLFREE ( mbuf ) ;
pthread_exit ( NULL ) ;
return NULL ;
}
/**
* adds a job to the job queue
* if ptr should be free ( ) after use , set len to the size
* else set size to 0
* */
int32_t add_job ( struct s_client * cl , enum actions action , void * ptr , int32_t len )
{
if ( ! cl | | cl - > kill )
{
if ( ! cl )
{ cs_log ( " WARNING: add_job failed. Client killed! " ) ; } // Ignore jobs for killed clients
if ( len & & ptr )
{ NULLFREE ( ptr ) ; }
return 0 ;
}
if ( action = = ACTION_CACHE_PUSH_OUT & & cacheex_check_queue_length ( cl ) )
{
if ( len & & ptr )
{ NULLFREE ( ptr ) ; }
return 0 ;
}
struct job_data * data ;
if ( ! cs_malloc ( & data , sizeof ( struct job_data ) ) )
{
if ( len & & ptr )
{ NULLFREE ( ptr ) ; }
return 0 ;
}
data - > action = action ;
data - > ptr = ptr ;
data - > cl = cl ;
data - > len = len ;
cs_ftime ( & data - > time ) ;
SAFE_MUTEX_LOCK ( & cl - > thread_lock ) ;
if ( cl & & ! cl - > kill & & cl - > thread_active )
{
if ( ! cl - > joblist )
{ cl - > joblist = ll_create ( " joblist " ) ; }
ll_append ( cl - > joblist , data ) ;
if ( cl - > thread_active = = 2 )
{ pthread_kill ( cl - > thread , OSCAM_SIGNAL_WAKEUP ) ; }
SAFE_MUTEX_UNLOCK ( & cl - > thread_lock ) ;
cs_log_dbg ( D_TRACE , " add %s job action %d queue length %d %s " ,
action > ACTION_CLIENT_FIRST ? " client " : " reader " , action ,
ll_count ( cl - > joblist ) , username ( cl ) ) ;
return 1 ;
}
/* pcsc doesn't like this; segfaults on x86, x86_64 */
int8_t modify_stacksize = 0 ;
struct s_reader * rdr = cl - > reader ;
if ( cl - > typ ! = ' r ' | | ! rdr | | rdr - > typ ! = R_PCSC )
{ modify_stacksize = 1 ; }
if ( action ! = ACTION_READER_CHECK_HEALTH )
{
cs_log_dbg ( D_TRACE , " start %s thread action %d " ,
action > ACTION_CLIENT_FIRST ? " client " : " reader " , action ) ;
}
int32_t ret = start_thread ( " client work " , work_thread , ( void * ) data , & cl - > thread , 1 , modify_stacksize ) ;
if ( ret )
{
cs_log ( " ERROR: can't create thread for %s (errno=%d %s) " ,
action > ACTION_CLIENT_FIRST ? " client " : " reader " , ret , strerror ( ret ) ) ;
free_job_data ( data ) ;
}
cl - > thread_active = 1 ;
SAFE_MUTEX_UNLOCK ( & cl - > thread_lock ) ;
return 1 ;
}