@ -222,23 +222,31 @@ static struct ast_generator spygen = {
. generate = spy_generate ,
} ;
static int start_spying ( struct ast_channel * chan , struct ast_channel * spychan , struct ast_audiohook * audiohook )
static int start_spying ( struct ast_channel * chan , const char * spychan_name , struct ast_audiohook * audiohook )
{
int res = 0 ;
struct ast_channel * peer = NULL ;
ast_log ( LOG_NOTICE , " Attaching %s to %s \n " , spychan - > name, chan - > name ) ;
ast_log ( LOG_NOTICE , " Attaching %s to %s \n " , spychan _ name, chan - > name ) ;
res = ast_audiohook_attach ( chan , audiohook ) ;
if ( ! res & & ast_test_flag ( chan , AST_FLAG_NBRIDGE ) & & ( peer = ast_bridged_channel ( chan ) ) )
if ( ! res & & ast_test_flag ( chan , AST_FLAG_NBRIDGE ) & & ( peer = ast_bridged_channel ( chan ) ) ) {
ast_channel_unlock ( chan ) ;
ast_softhangup ( peer , AST_SOFTHANGUP_UNBRIDGE ) ;
} else
ast_channel_unlock ( chan ) ;
return res ;
}
static int channel_spy ( struct ast_channel * chan , struct ast_channel * spyee , int * volfactor , int fd ,
const struct ast_flags * flags , char * exitcontext )
struct chanspy_ds {
struct ast_channel * chan ;
ast_mutex_t lock ;
} ;
static int channel_spy ( struct ast_channel * chan , struct chanspy_ds * spyee_chanspy_ds ,
int * volfactor , int fd , const struct ast_flags * flags , char * exitcontext )
{
struct chanspy_translation_helper csth ;
int running = 0 , res , x = 0 ;
@ -246,6 +254,24 @@ static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int
char * name ;
struct ast_frame * f ;
struct ast_silence_generator * silgen = NULL ;
struct ast_channel * spyee = NULL ;
const char * spyer_name ;
ast_channel_lock ( chan ) ;
spyer_name = ast_strdupa ( chan - > name ) ;
ast_channel_unlock ( chan ) ;
ast_mutex_lock ( & spyee_chanspy_ds - > lock ) ;
if ( spyee_chanspy_ds - > chan ) {
spyee = spyee_chanspy_ds - > chan ;
ast_channel_lock ( spyee ) ;
}
ast_mutex_unlock ( & spyee_chanspy_ds - > lock ) ;
if ( ! spyee )
return 0 ;
/* We now hold the channel lock on spyee */
if ( ast_check_hangup ( chan ) | | ast_check_hangup ( spyee ) )
return 0 ;
@ -257,16 +283,18 @@ static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int
ast_audiohook_init ( & csth . spy_audiohook , AST_AUDIOHOOK_TYPE_SPY , " ChanSpy " ) ;
if ( start_spying ( spyee , chan , & csth . spy_audiohook ) ) {
if ( start_spying ( spyee , spyer_name , & csth . spy_audiohook ) ) { /* Unlocks spyee */
ast_audiohook_destroy ( & csth . spy_audiohook ) ;
return 0 ;
}
if ( ast_test_flag ( flags , OPTION_WHISPER ) ) {
ast_audiohook_init ( & csth . whisper_audiohook , AST_AUDIOHOOK_TYPE_WHISPER , " ChanSpy " ) ;
start_spying ( spyee , chan , & csth . whisper_audiohook ) ;
start_spying ( spyee , spyer_name , & csth . whisper_audiohook ) ; /* Unlocks spyee */
}
spyee = NULL ;
csth . volfactor = * volfactor ;
if ( csth . volfactor ) {
@ -380,12 +408,82 @@ static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int
return running ;
}
static struct ast_channel * next_channel ( const struct ast_channel * last , const char * spec ,
const char * exten , const char * context )
/*!
* \ note This relies on the embedded lock to be recursive , as it may be called
* due to a call to chanspy_ds_free with the lock held there .
*/
static void chanspy_ds_destroy ( void * data )
{
struct chanspy_ds * chanspy_ds = data ;
/* Setting chan to be NULL is an atomic operation, but we don't want this
* value to change while this lock is held . The lock is held elsewhere
* while it performs non - atomic operations with this channel pointer */
ast_mutex_lock ( & chanspy_ds - > lock ) ;
chanspy_ds - > chan = NULL ;
ast_mutex_unlock ( & chanspy_ds - > lock ) ;
}
static const struct ast_datastore_info chanspy_ds_info = {
. type = " chanspy " ,
. destroy = chanspy_ds_destroy ,
} ;
static struct chanspy_ds * chanspy_ds_free ( struct chanspy_ds * chanspy_ds )
{
if ( ! chanspy_ds )
return NULL ;
ast_mutex_lock ( & chanspy_ds - > lock ) ;
if ( chanspy_ds - > chan ) {
struct ast_datastore * datastore ;
struct ast_channel * chan ;
chan = chanspy_ds - > chan ;
ast_channel_lock ( chan ) ;
if ( ( datastore = ast_channel_datastore_find ( chan , & chanspy_ds_info , NULL ) ) ) {
ast_channel_datastore_remove ( chan , datastore ) ;
/* chanspy_ds->chan is NULL after this call */
ast_channel_datastore_free ( datastore ) ;
}
ast_channel_unlock ( chan ) ;
}
ast_mutex_unlock ( & chanspy_ds - > lock ) ;
return NULL ;
}
/*! \note Returns the channel in the chanspy_ds locked as well as the chanspy_ds locked */
static struct chanspy_ds * setup_chanspy_ds ( struct ast_channel * chan , struct chanspy_ds * chanspy_ds )
{
struct ast_datastore * datastore = NULL ;
ast_mutex_lock ( & chanspy_ds - > lock ) ;
chanspy_ds - > chan = chan ;
if ( ! ( datastore = ast_channel_datastore_alloc ( & chanspy_ds_info , NULL ) ) ) {
chanspy_ds = chanspy_ds_free ( chanspy_ds ) ;
ast_channel_unlock ( chan ) ;
return NULL ;
}
datastore - > data = chanspy_ds ;
ast_channel_datastore_add ( chan , datastore ) ;
return chanspy_ds ;
}
static struct chanspy_ds * next_channel ( struct ast_channel * chan ,
const struct ast_channel * last , const char * spec ,
const char * exten , const char * context , struct chanspy_ds * chanspy_ds )
{
struct ast_channel * this ;
redo :
redo :
if ( ! ast_strlen_zero ( spec ) )
this = ast_walk_channel_by_name_prefix_locked ( last , spec , strlen ( spec ) ) ;
@ -394,20 +492,21 @@ static struct ast_channel *next_channel(const struct ast_channel *last, const ch
else
this = ast_channel_walk_locked ( last ) ;
if ( this ) {
if ( ! this )
return NULL ;
if ( ! strncmp ( this - > name , " Zap/pseudo " , 10 ) ) {
ast_channel_unlock ( this ) ;
if ( ! strncmp ( this - > name , " Zap/pseudo " , 10 ) )
goto redo ;
goto redo ;
}
return this ;
return setup_chanspy_ds ( this , chanspy_ds ) ;
}
static int common_exec ( struct ast_channel * chan , const struct ast_flags * flags ,
int volfactor , const int fd , const char * mygroup , const char * myenforced ,
const char * spec , const char * exten , const char * context )
{
struct ast_channel * peer , * prev , * next ;
char nameprefix [ AST_NAME_STRLEN ] ;
char peer_name [ AST_NAME_STRLEN + 5 ] ;
char exitcontext [ AST_MAX_CONTEXT ] = " " ;
@ -417,6 +516,7 @@ static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
char * ptr ;
int num ;
int num_spyed_upon = 1 ;
struct chanspy_ds chanspy_ds ;
if ( ast_test_flag ( flags , OPTION_EXIT ) ) {
const char * c ;
@ -428,6 +528,8 @@ static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
ast_copy_string ( exitcontext , chan - > context , sizeof ( exitcontext ) ) ;
}
ast_mutex_init ( & chanspy_ds . lock ) ;
if ( chan - > _state ! = AST_STATE_UP )
ast_answer ( chan ) ;
@ -436,6 +538,9 @@ static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
waitms = 100 ;
for ( ; ; ) {
struct chanspy_ds * peer_chanspy_ds = NULL , * next_chanspy_ds = NULL ;
struct ast_channel * prev = NULL , * peer = NULL ;
if ( ! ast_test_flag ( flags , OPTION_QUIET ) & & num_spyed_upon ) {
res = ast_streamfile ( chan , " beep " , chan - > language ) ;
if ( ! res )
@ -472,12 +577,13 @@ static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
/* reset for the next loop around, unless overridden later */
waitms = 100 ;
peer = prev = next = NULL ;
num_spyed_upon = 0 ;
for ( peer = next_channel ( peer , spec , exten , context ) ;
peer ;
prev = peer , peer = next ? next : next_channel ( peer , spec , exten , context ) , next = NULL ) {
for ( peer_chanspy_ds = next_channel ( chan , prev , spec , exten , context , & chanspy_ds ) ;
peer_chanspy_ds ;
chanspy_ds_free ( peer_chanspy_ds ) , prev = peer ,
peer_chanspy_ds = next_chanspy_ds ? next_chanspy_ds :
next_channel ( chan , prev , spec , exten , context , & chanspy_ds ) , next_chanspy_ds = NULL ) {
const char * group ;
int igrp = ! mygroup ;
char * groups [ 25 ] ;
@ -490,18 +596,32 @@ static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
char * ext ;
char * form_enforced ;
int ienf = ! myenforced ;
struct ast_channel * peer ;
if ( peer = = prev )
peer = peer_chanspy_ds - > chan ;
ast_mutex_unlock ( & peer_chanspy_ds - > lock ) ;
if ( peer = = prev ) {
ast_channel_unlock ( peer ) ;
chanspy_ds_free ( peer_chanspy_ds ) ;
break ;
}
if ( peer = = chan )
if ( peer = = chan ) {
ast_channel_unlock ( peer ) ;
continue ;
}
if ( ast_test_flag ( flags , OPTION_BRIDGED ) & & ! ast_bridged_channel ( peer ) )
if ( ast_test_flag ( flags , OPTION_BRIDGED ) & & ! ast_bridged_channel ( peer ) ) {
ast_channel_unlock ( peer ) ;
continue ;
}
if ( ast_check_hangup ( peer ) | | ast_test_flag ( peer , AST_FLAG_SPYING ) )
if ( ast_check_hangup ( peer ) | | ast_test_flag ( peer , AST_FLAG_SPYING ) ) {
ast_channel_unlock ( peer ) ;
continue ;
}
if ( mygroup ) {
if ( ( group = pbx_builtin_getvar_helper ( peer , " SPYGROUP " ) ) ) {
@ -518,8 +638,10 @@ static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
}
}
if ( ! igrp )
if ( ! igrp ) {
ast_channel_unlock ( peer ) ;
continue ;
}
if ( myenforced ) {
@ -559,6 +681,12 @@ static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
for ( s = peer_name ; s < ptr ; s + + )
* s = tolower ( * s ) ;
/* We have to unlock the peer channel here to avoid a deadlock.
* So , when we need it again , we have to lock the datastore and get
* the pointer from there to see if the channel is still valid . */
ast_channel_unlock ( peer ) ;
peer = NULL ;
if ( ! ast_test_flag ( flags , OPTION_QUIET ) ) {
if ( ast_fileexists ( peer_name , NULL , NULL ) ! = - 1 ) {
@ -574,8 +702,8 @@ static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
}
waitms = 5000 ;
res = channel_spy ( chan , peer , & volfactor , fd , flags , exitcontext ) ;
num_spyed_upon + + ;
res = channel_spy ( chan , peer _chanspy_ds , & volfactor , fd , flags , exitcontext ) ;
num_spyed_upon + + ;
if ( res = = - 1 ) {
goto exit ;
@ -583,13 +711,28 @@ static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
res = 0 ;
goto exit ;
} else if ( res > 1 & & spec ) {
struct ast_channel * next ;
snprintf ( nameprefix , AST_NAME_STRLEN , " %s/%d " , spec , res ) ;
if ( ( next = ast_get_channel_by_name_prefix_locked ( nameprefix , strlen ( nameprefix ) ) ) ) {
ast_channel_unlock ( next ) ;
peer_chanspy_ds = chanspy_ds_free ( peer_chanspy_ds ) ;
next_chanspy_ds = setup_chanspy_ds ( next , & chanspy_ds ) ;
} else {
/* stay on this channel */
next = peer ;
/* stay on this channel, if it is still valid */
ast_mutex_lock ( & peer_chanspy_ds - > lock ) ;
if ( peer_chanspy_ds - > chan ) {
ast_channel_lock ( peer_chanspy_ds - > chan ) ;
next_chanspy_ds = peer_chanspy_ds ;
peer_chanspy_ds = NULL ;
} else {
/* the channel is gone */
ast_mutex_unlock ( & peer_chanspy_ds - > lock ) ;
next_chanspy_ds = NULL ;
}
}
peer = NULL ;
}
}
@ -600,6 +743,8 @@ exit:
ast_channel_setoption ( chan , AST_OPTION_TXGAIN , & zero_volume , sizeof ( zero_volume ) , 0 ) ;
ast_mutex_destroy ( & chanspy_ds . lock ) ;
return res ;
}