Asterisk - The Open Source Telephony Project GIT-master-5782b03
app_confbridge.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2007-2008, Digium, Inc.
5 *
6 * Joshua Colp <jcolp@digium.com>
7 * David Vossel <dvossel@digium.com>
8 *
9 * See http://www.asterisk.org for more information about
10 * the Asterisk project. Please do not directly contact
11 * any of the maintainers of this project for assistance;
12 * the project provides a web site, mailing lists and IRC
13 * channels for your use.
14 *
15 * This program is free software, distributed under the terms of
16 * the GNU General Public License Version 2. See the LICENSE file
17 * at the top of the source tree.
18 */
19
20/*! \file
21 *
22 * \brief Conference Bridge application
23 *
24 * \author\verbatim Joshua Colp <jcolp@digium.com> \endverbatim
25 * \author\verbatim David Vossel <dvossel@digium.com> \endverbatim
26 *
27 * This is a conference bridge application utilizing the bridging core.
28 * \ingroup applications
29 */
30
31/*! \li \ref app_confbridge.c uses the configuration file \ref confbridge.conf
32 * \addtogroup configuration_file Configuration Files
33 */
34
35/*!
36 * \page confbridge.conf confbridge.conf
37 * \verbinclude confbridge.conf.sample
38 */
39
40/*** MODULEINFO
41 <support_level>core</support_level>
42 ***/
43
44#include "asterisk.h"
45
46#include <stdio.h>
47#include <stdlib.h>
48#include <unistd.h>
49#include <string.h>
50#include <signal.h>
51
52#include "asterisk/cli.h"
53#include "asterisk/file.h"
54#include "asterisk/channel.h"
55#include "asterisk/pbx.h"
56#include "asterisk/pbx.h"
57#include "asterisk/module.h"
58#include "asterisk/lock.h"
59#include "asterisk/bridge.h"
61#include "asterisk/say.h"
62#include "asterisk/audiohook.h"
63#include "asterisk/astobj2.h"
65#include "asterisk/paths.h"
66#include "asterisk/manager.h"
67#include "asterisk/test.h"
68#include "asterisk/stasis.h"
71#include "asterisk/json.h"
74#include "asterisk/stream.h"
75#include "asterisk/message.h"
76
77/*** DOCUMENTATION
78 <application name="ConfBridge" language="en_US">
79 <synopsis>
80 Conference bridge application.
81 </synopsis>
82 <syntax>
83 <parameter name="conference" required="true">
84 <para>Name of the conference bridge. You are not limited to just
85 numbers.</para>
86 </parameter>
87 <parameter name="bridge_profile">
88 <para>The bridge profile name from confbridge.conf. When left blank,
89 a dynamically built bridge profile created by the CONFBRIDGE dialplan
90 function is searched for on the channel and used. If no dynamic
91 profile is present, the 'default_bridge' profile found in
92 confbridge.conf is used. </para>
93 <para>It is important to note that while user profiles may be unique
94 for each participant, mixing bridge profiles on a single conference
95 is _NOT_ recommended and will produce undefined results.</para>
96 </parameter>
97 <parameter name="user_profile">
98 <para>The user profile name from confbridge.conf. When left blank,
99 a dynamically built user profile created by the CONFBRIDGE dialplan
100 function is searched for on the channel and used. If no dynamic
101 profile is present, the 'default_user' profile found in
102 confbridge.conf is used.</para>
103 </parameter>
104 <parameter name="menu">
105 <para>The name of the DTMF menu in confbridge.conf to be applied to
106 this channel. When left blank, a dynamically built menu profile
107 created by the CONFBRIDGE dialplan function is searched for on
108 the channel and used. If no dynamic profile is present, the
109 'default_menu' profile found in confbridge.conf is used.</para>
110 </parameter>
111 </syntax>
112 <description>
113 <para>Enters the user into a specified conference bridge. The user can
114 exit the conference by hangup or DTMF menu option.</para>
115 <para>This application sets the following channel variable upon completion:</para>
116 <variablelist>
117 <variable name="CONFBRIDGE_RESULT">
118 <value name="FAILED">The channel encountered an error and could not enter the conference.</value>
119 <value name="HANGUP">The channel exited the conference by hanging up.</value>
120 <value name="KICKED">The channel was kicked from the conference.</value>
121 <value name="ENDMARKED">The channel left the conference as a result of the last marked user leaving.</value>
122 <value name="DTMF">The channel pressed a DTMF sequence to exit the conference.</value>
123 <value name="TIMEOUT">The channel reached its configured timeout.</value>
124 </variable>
125 </variablelist>
126 </description>
127 <see-also>
128 <ref type="application">ConfKick</ref>
129 <ref type="function">CONFBRIDGE</ref>
130 <ref type="function">CONFBRIDGE_INFO</ref>
131 <ref type="function">CONFBRIDGE_CHANNELS</ref>
132 </see-also>
133 </application>
134 <application name="ConfKick" language="en_US">
135 <since>
136 <version>16.19.0</version>
137 <version>18.5.0</version>
138 <version>19.0.0</version>
139 </since>
140 <synopsis>
141 Kicks channel(s) from the requested ConfBridge.
142 </synopsis>
143 <syntax>
144 <parameter name="conference" required="true" />
145 <parameter name="channel">
146 <para>The channel to kick, <literal>all</literal>
147 to kick all users, or <literal>participants</literal>
148 to kick all non-admin participants. Default is all.</para>
149 </parameter>
150 </syntax>
151 <description>
152 <para>Kicks the requested channel(s) from a conference bridge.</para>
153 <variablelist>
154 <variable name="CONFKICKSTATUS">
155 <value name="FAILURE">
156 Could not kick any users with the provided arguments.
157 </value>
158 <value name="SUCCESS">
159 Successfully kicked users from specified conference bridge.
160 </value>
161 </variable>
162 </variablelist>
163 </description>
164 <see-also>
165 <ref type="application">ConfBridge</ref>
166 <ref type="function">CONFBRIDGE</ref>
167 <ref type="function">CONFBRIDGE_INFO</ref>
168 <ref type="function">CONFBRIDGE_CHANNELS</ref>
169 </see-also>
170 </application>
171 <function name="CONFBRIDGE" language="en_US">
172 <synopsis>
173 Set a custom dynamic bridge, user, or menu profile on a channel for the
174 ConfBridge application using the same options available in confbridge.conf.
175 </synopsis>
176 <syntax>
177 <parameter name="type" required="true">
178 <para>To what type of conference profile the option applies.</para>
179 <enumlist>
180 <enum name="bridge"></enum>
181 <enum name="menu"></enum>
182 <enum name="user"></enum>
183 </enumlist>
184 </parameter>
185 <parameter name="option" required="true">
186 <para>Option refers to a <filename>confbridge.conf</filename> option
187 that is being set dynamically on this channel, or <literal>clear</literal>
188 to remove already applied profile options from the channel.</para>
189 </parameter>
190 </syntax>
191 <description>
192 <para>A custom profile uses the default profile type settings defined in
193 <filename>confbridge.conf</filename> as defaults if the profile template
194 is not explicitly specified first.</para>
195 <para>For <literal>bridge</literal> profiles the default template is <literal>default_bridge</literal>.</para>
196 <para>For <literal>menu</literal> profiles the default template is <literal>default_menu</literal>.</para>
197 <para>For <literal>user</literal> profiles the default template is <literal>default_user</literal>.</para>
198 <para>---- Example 1 ----</para>
199 <para>In this example the custom user profile set on the channel will
200 automatically be used by the ConfBridge application.</para>
201 <example title="Example 1">
202 exten => 1,1,Answer()
203 </example>
204 <para>; In this example the effect of the following line is</para>
205 <para>; implied:</para>
206 <example title="Example 1b">
207 same => n,Set(CONFBRIDGE(user,template)=default_user)
208 same => n,Set(CONFBRIDGE(user,announce_join_leave)=yes)
209 same => n,Set(CONFBRIDGE(user,startmuted)=yes)
210 same => n,ConfBridge(1)
211 </example>
212 <para>---- Example 2 ----</para>
213 <para>This example shows how to use a predefined user profile in
214 <filename>confbridge.conf</filename> as a template for a dynamic profile.
215 Here we make an admin/marked user out of the <literal>my_user</literal>
216 profile that you define in <filename>confbridge.conf</filename>.</para>
217 <example title="Example 2">
218 exten => 1,1,Answer()
219 same => n,Set(CONFBRIDGE(user,template)=my_user)
220 same => n,Set(CONFBRIDGE(user,admin)=yes)
221 same => n,Set(CONFBRIDGE(user,marked)=yes)
222 same => n,ConfBridge(1)
223 </example>
224 </description>
225 </function>
226 <function name="CONFBRIDGE_INFO" language="en_US">
227 <synopsis>
228 Get information about a ConfBridge conference.
229 </synopsis>
230 <syntax>
231 <parameter name="type" required="true">
232 <para>What conference information is requested.</para>
233 <enumlist>
234 <enum name="admins">
235 <para>Get the number of admin users in the conference.</para>
236 </enum>
237 <enum name="locked">
238 <para>Determine if the conference is locked. (0 or 1)</para>
239 </enum>
240 <enum name="marked">
241 <para>Get the number of marked users in the conference.</para>
242 </enum>
243 <enum name="muted">
244 <para>Determine if the conference is muted. (0 or 1)</para>
245 </enum>
246 <enum name="parties">
247 <para>Get the number of users in the conference.</para>
248 </enum>
249 </enumlist>
250 </parameter>
251 <parameter name="conf" required="true">
252 <para>The name of the conference being referenced.</para>
253 </parameter>
254 </syntax>
255 <description>
256 <para>This function returns a non-negative integer for valid conference
257 names and an empty string for invalid conference names.</para>
258 </description>
259 <see-also>
260 <ref type="function">CONFBRIDGE_CHANNELS</ref>
261 </see-also>
262 </function>
263 <function name="CONFBRIDGE_CHANNELS" language="en_US">
264 <since>
265 <version>16.26.0</version>
266 <version>18.12.0</version>
267 <version>19.4.0</version>
268 </since>
269 <synopsis>
270 Get a list of channels in a ConfBridge conference.
271 </synopsis>
272 <syntax>
273 <parameter name="type" required="true">
274 <para>What conference information is requested.</para>
275 <enumlist>
276 <enum name="admins">
277 <para>Get the number of admin users in the conference.</para>
278 </enum>
279 <enum name="marked">
280 <para>Get the number of marked users in the conference.</para>
281 </enum>
282 <enum name="parties">
283 <para>Get the number of total users in the conference.</para>
284 </enum>
285 <enum name="active">
286 <para>Get the number of active users in the conference.</para>
287 </enum>
288 <enum name="waiting">
289 <para>Get the number of waiting users in the conference.</para>
290 </enum>
291 </enumlist>
292 </parameter>
293 <parameter name="conf" required="true">
294 <para>The name of the conference being referenced.</para>
295 </parameter>
296 </syntax>
297 <description>
298 <para>This function returns a comma-separated list of channels in a ConfBridge conference, optionally filtered by a type of participant.</para>
299 </description>
300 <see-also>
301 <ref type="function">CONFBRIDGE_INFO</ref>
302 </see-also>
303 </function>
304 <manager name="ConfbridgeList" language="en_US">
305 <synopsis>
306 List participants in a conference.
307 </synopsis>
308 <syntax>
309 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
310 <parameter name="Conference" required="true">
311 <para>Conference number.</para>
312 </parameter>
313 </syntax>
314 <description>
315 <para>Lists all users in a particular ConfBridge conference.
316 ConfbridgeList will follow as separate events, followed by a final event called
317 ConfbridgeListComplete.</para>
318 </description>
319 </manager>
320 <managerEvent language="en_US" name="ConfbridgeList">
321 <managerEventInstance class="EVENT_FLAG_REPORTING">
322 <synopsis>Raised as part of the ConfbridgeList action response list.</synopsis>
323 <syntax>
324 <parameter name="Conference">
325 <para>The name of the Confbridge conference.</para>
326 </parameter>
327 <parameter name="Admin">
328 <para>Identifies this user as an admin user.</para>
329 <enumlist>
330 <enum name="Yes"/>
331 <enum name="No"/>
332 </enumlist>
333 </parameter>
334 <parameter name="MarkedUser">
335 <para>Identifies this user as a marked user.</para>
336 <enumlist>
337 <enum name="Yes"/>
338 <enum name="No"/>
339 </enumlist>
340 </parameter>
341 <parameter name="WaitMarked">
342 <para>Must this user wait for a marked user to join?</para>
343 <enumlist>
344 <enum name="Yes"/>
345 <enum name="No"/>
346 </enumlist>
347 </parameter>
348 <parameter name="EndMarked">
349 <para>Does this user get kicked after the last marked user leaves?</para>
350 <enumlist>
351 <enum name="Yes"/>
352 <enum name="No"/>
353 </enumlist>
354 </parameter>
355 <parameter name="Waiting">
356 <para>Is this user waiting for a marked user to join?</para>
357 <enumlist>
358 <enum name="Yes"/>
359 <enum name="No"/>
360 </enumlist>
361 </parameter>
362 <parameter name="Muted">
363 <para>The current mute status.</para>
364 <enumlist>
365 <enum name="Yes"/>
366 <enum name="No"/>
367 </enumlist>
368 </parameter>
369 <parameter name="Talking">
370 <para>Is this user talking?</para>
371 <enumlist>
372 <enum name="Yes"/>
373 <enum name="No"/>
374 </enumlist>
375 </parameter>
376 <parameter name="AnsweredTime">
377 <para>The number of seconds the channel has been up.</para>
378 </parameter>
379 <channel_snapshot/>
380 </syntax>
381 </managerEventInstance>
382 </managerEvent>
383 <manager name="ConfbridgeListRooms" language="en_US">
384 <synopsis>
385 List active conferences.
386 </synopsis>
387 <syntax>
388 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
389 </syntax>
390 <description>
391 <para>Lists data about all active conferences.
392 ConfbridgeListRooms will follow as separate events, followed by a final event called
393 ConfbridgeListRoomsComplete.</para>
394 </description>
395 </manager>
396 <managerEvent language="en_US" name="ConfbridgeListRooms">
397 <managerEventInstance class="EVENT_FLAG_REPORTING">
398 <synopsis>Raised as part of the ConfbridgeListRooms action response list.</synopsis>
399 <syntax>
400 <parameter name="Conference">
401 <para>The name of the Confbridge conference.</para>
402 </parameter>
403 <parameter name="Parties">
404 <para>Number of users in the conference.</para>
405 <para>This includes both active and waiting users.</para>
406 </parameter>
407 <parameter name="Marked">
408 <para>Number of marked users in the conference.</para>
409 </parameter>
410 <parameter name="Locked">
411 <para>Is the conference locked?</para>
412 <enumlist>
413 <enum name="Yes"/>
414 <enum name="No"/>
415 </enumlist>
416 </parameter>
417 <parameter name="Muted">
418 <para>Is the conference muted?</para>
419 <enumlist>
420 <enum name="Yes"/>
421 <enum name="No"/>
422 </enumlist>
423 </parameter>
424 </syntax>
425 </managerEventInstance>
426 </managerEvent>
427 <manager name="ConfbridgeMute" language="en_US">
428 <synopsis>
429 Mute a Confbridge user.
430 </synopsis>
431 <syntax>
432 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
433 <parameter name="Conference" required="true" />
434 <parameter name="Channel" required="true">
435 <para>If this parameter is not a complete channel name, the first channel with this prefix will be used.</para>
436 <para>If this parameter is "all", all channels will be muted.</para>
437 <para>If this parameter is "participants", all non-admin channels will be muted.</para>
438 </parameter>
439 </syntax>
440 <description>
441 </description>
442 </manager>
443 <manager name="ConfbridgeUnmute" language="en_US">
444 <synopsis>
445 Unmute a Confbridge user.
446 </synopsis>
447 <syntax>
448 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
449 <parameter name="Conference" required="true" />
450 <parameter name="Channel" required="true">
451 <para>If this parameter is not a complete channel name, the first channel with this prefix will be used.</para>
452 <para>If this parameter is "all", all channels will be unmuted.</para>
453 <para>If this parameter is "participants", all non-admin channels will be unmuted.</para>
454 </parameter>
455 </syntax>
456 <description>
457 </description>
458 </manager>
459 <manager name="ConfbridgeKick" language="en_US">
460 <synopsis>
461 Kick a Confbridge user.
462 </synopsis>
463 <syntax>
464 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
465 <parameter name="Conference" required="true" />
466 <parameter name="Channel" required="true" >
467 <para>If this parameter is "all", all channels will be kicked from the conference.</para>
468 <para>If this parameter is "participants", all non-admin channels will be kicked from the conference.</para>
469 </parameter>
470 </syntax>
471 <description>
472 </description>
473 </manager>
474 <manager name="ConfbridgeLock" language="en_US">
475 <synopsis>
476 Lock a Confbridge conference.
477 </synopsis>
478 <syntax>
479 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
480 <parameter name="Conference" required="true" />
481 </syntax>
482 <description>
483 </description>
484 </manager>
485 <manager name="ConfbridgeUnlock" language="en_US">
486 <synopsis>
487 Unlock a Confbridge conference.
488 </synopsis>
489 <syntax>
490 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
491 <parameter name="Conference" required="true" />
492 </syntax>
493 <description>
494 </description>
495 </manager>
496 <manager name="ConfbridgeStartRecord" language="en_US">
497 <synopsis>
498 Start recording a Confbridge conference.
499 </synopsis>
500 <syntax>
501 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
502 <parameter name="Conference" required="true" />
503 <parameter name="RecordFile" required="false" />
504 </syntax>
505 <description>
506 <para>Start recording a conference. If recording is already present an error will be returned. If RecordFile is not provided, the default record file specified in the conference's bridge profile will be used, if that is not present either a file will automatically be generated in the monitor directory.</para>
507 </description>
508 </manager>
509 <manager name="ConfbridgeStopRecord" language="en_US">
510 <synopsis>
511 Stop recording a Confbridge conference.
512 </synopsis>
513 <syntax>
514 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
515 <parameter name="Conference" required="true" />
516 </syntax>
517 <description>
518 </description>
519 </manager>
520 <manager name="ConfbridgeSetSingleVideoSrc" language="en_US">
521 <synopsis>
522 Set a conference user as the single video source distributed to all other participants.
523 </synopsis>
524 <syntax>
525 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
526 <parameter name="Conference" required="true" />
527 <parameter name="Channel" required="true">
528 <para>If this parameter is not a complete channel name, the first channel with this prefix will be used.</para>
529 </parameter>
530 </syntax>
531 <description>
532 </description>
533 </manager>
534
535***/
536
537/*!
538 * \par Playing back a file to a channel in a conference
539 * You might notice in this application that while playing a sound file
540 * to a channel the actual conference bridge lock is not held. This is done so
541 * that other channels are not blocked from interacting with the conference bridge.
542 * Unfortunately because of this it is possible for things to change after the sound file
543 * is done being played. Data must therefore be checked after reacquiring the conference
544 * bridge lock if it is important.
545 */
546
547static const char app[] = "ConfBridge";
548static const char app2[] = "ConfKick";
549
550/*! Number of buckets our conference bridges container can have */
551#define CONFERENCE_BRIDGE_BUCKETS 53
552
553/*! Initial recording filename space. */
554#define RECORD_FILENAME_INITIAL_SPACE 128
555
556/*! \brief Container to hold all conference bridges in progress */
558
559static void leave_conference(struct confbridge_user *user);
560static int play_sound_number(struct confbridge_conference *conference, int say_number);
561static int execute_menu_entry(struct confbridge_conference *conference,
562 struct confbridge_user *user,
563 struct ast_bridge_channel *bridge_channel,
564 struct conf_menu_entry *menu_entry,
565 struct conf_menu *menu);
566
567/*! \brief Hashing function used for conference bridges container */
568static int conference_bridge_hash_cb(const void *obj, const int flags)
569{
570 const struct confbridge_conference *conference = obj;
571 const char *name = obj;
572 int hash;
573
574 switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
575 default:
576 case OBJ_POINTER:
577 name = conference->name;
578 /* Fall through */
579 case OBJ_KEY:
580 hash = ast_str_case_hash(name);
581 break;
582 case OBJ_PARTIAL_KEY:
583 /* Should never happen in hash callback. */
584 ast_assert(0);
585 hash = 0;
586 break;
587 }
588 return hash;
589}
590
591/*! \brief Comparison function used for conference bridges container */
592static int conference_bridge_cmp_cb(void *obj, void *arg, int flags)
593{
594 const struct confbridge_conference *left = obj;
595 const struct confbridge_conference *right = arg;
596 const char *right_name = arg;
597 int cmp;
598
599 switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
600 default:
601 case OBJ_POINTER:
602 right_name = right->name;
603 /* Fall through */
604 case OBJ_KEY:
605 cmp = strcasecmp(left->name, right_name);
606 break;
607 case OBJ_PARTIAL_KEY:
608 cmp = strncasecmp(left->name, right_name, strlen(right_name));
609 break;
610 }
611 return cmp ? 0 : CMP_MATCH;
612}
613
614const char *conf_get_sound(enum conf_sounds sound, struct bridge_profile_sounds *custom_sounds)
615{
616 switch (sound) {
618 return S_OR(custom_sounds->hasjoin, "conf-hasjoin");
620 return S_OR(custom_sounds->hasleft, "conf-hasleft");
622 return S_OR(custom_sounds->kicked, "conf-kicked");
623 case CONF_SOUND_MUTED:
624 return S_OR(custom_sounds->muted, "conf-muted");
626 return S_OR(custom_sounds->unmuted, "conf-unmuted");
628 return S_OR(custom_sounds->binauralon, "confbridge-binaural-on");
630 return S_OR(custom_sounds->binauraloff, "confbridge-binaural-off");
632 return S_OR(custom_sounds->onlyone, "conf-onlyone");
634 return S_OR(custom_sounds->thereare, "conf-thereare");
636 return S_OR(custom_sounds->otherinparty, "conf-otherinparty");
638 return S_OR(custom_sounds->placeintoconf, "conf-placeintoconf");
640 return S_OR(custom_sounds->waitforleader, "conf-waitforleader");
642 return S_OR(custom_sounds->leaderhasleft, "conf-leaderhasleft");
644 return S_OR(custom_sounds->getpin, "conf-getpin");
646 return S_OR(custom_sounds->invalidpin, "conf-invalidpin");
648 return S_OR(custom_sounds->onlyperson, "conf-onlyperson");
650 return S_OR(custom_sounds->locked, "conf-locked");
652 return S_OR(custom_sounds->lockednow, "conf-lockednow");
654 return S_OR(custom_sounds->unlockednow, "conf-unlockednow");
656 return S_OR(custom_sounds->errormenu, "conf-errormenu");
657 case CONF_SOUND_JOIN:
658 return S_OR(custom_sounds->join, "confbridge-join");
659 case CONF_SOUND_LEAVE:
660 return S_OR(custom_sounds->leave, "confbridge-leave");
662 return S_OR(custom_sounds->participantsmuted, "conf-now-muted");
664 return S_OR(custom_sounds->participantsunmuted, "conf-now-unmuted");
665 case CONF_SOUND_BEGIN:
666 return S_OR(custom_sounds->begin, "confbridge-conf-begin");
667 }
668
669 return "";
670}
671
672
673static void send_conf_stasis(struct confbridge_conference *conference, struct ast_channel *chan,
674 struct stasis_message_type *type, struct ast_json *extras, int channel_topic)
675{
676 RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
677 RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
678
679 json_object = ast_json_pack("{s: s}",
680 "conference", conference->name);
681 if (!json_object) {
682 return;
683 }
684
685 if (extras) {
686 ast_json_object_update(json_object, extras);
687 }
688
689 ast_bridge_lock(conference->bridge);
691 conference->bridge,
692 chan,
693 json_object);
694 ast_bridge_unlock(conference->bridge);
695 if (!msg) {
696 return;
697 }
698
700 conf_send_event_to_participants(conference, chan, msg);
701 }
702
703 if (channel_topic) {
705 } else {
706 stasis_publish(ast_bridge_topic(conference->bridge), msg);
707 }
708}
709
711 struct ast_channel_snapshot *chan_snapshot, struct stasis_message_type *type,
712 struct ast_json *extras)
713{
714 RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
715 RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
716 RAII_VAR(struct ast_bridge_snapshot *, bridge_snapshot, NULL, ao2_cleanup);
717
718 json_object = ast_json_pack("{s: s}",
719 "conference", conference->name);
720 if (!json_object) {
721 return;
722 }
723
724 if (extras) {
725 ast_json_object_update(json_object, extras);
726 }
727
728 ast_bridge_lock(conference->bridge);
729 bridge_snapshot = ast_bridge_snapshot_create(conference->bridge);
730 ast_bridge_unlock(conference->bridge);
731 if (!bridge_snapshot) {
732 return;
733 }
734
736 bridge_snapshot,
737 chan_snapshot,
738 json_object);
739 if (!msg) {
740 return;
741 }
742
743 stasis_publish(ast_bridge_topic(conference->bridge), msg);
744}
745
746
747static void send_conf_start_event(struct confbridge_conference *conference)
748{
750}
751
752static void send_conf_end_event(struct confbridge_conference *conference)
753{
754 send_conf_stasis(conference, NULL, confbridge_end_type(), NULL, 0);
755}
756
757static void send_join_event(struct confbridge_user *user, struct confbridge_conference *conference)
758{
759 struct ast_json *json_object;
760
761 json_object = ast_json_pack("{s: b, s: b}",
762 "admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN),
763 "muted", user->muted);
764 if (!json_object) {
765 return;
766 }
767 send_conf_stasis(conference, user->chan, confbridge_join_type(), json_object, 0);
768 ast_json_unref(json_object);
769}
770
771static void send_leave_event(struct confbridge_user *user, struct confbridge_conference *conference)
772{
773 struct ast_json *json_object;
774
775 json_object = ast_json_pack("{s: b}",
776 "admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN)
777 );
778 if (!json_object) {
779 return;
780 }
781 send_conf_stasis(conference, user->chan, confbridge_leave_type(), json_object, 0);
782 ast_json_unref(json_object);
783}
784
785static void send_start_record_event(struct confbridge_conference *conference)
786{
788}
789
790static void send_stop_record_event(struct confbridge_conference *conference)
791{
793}
794
795static void send_mute_event(struct confbridge_user *user, struct confbridge_conference *conference)
796{
797 struct ast_json *json_object;
798
799 json_object = ast_json_pack("{s: b}",
800 "admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN)
801 );
802 if (!json_object) {
803 return;
804 }
805 send_conf_stasis(conference, user->chan, confbridge_mute_type(), json_object, 1);
806 ast_json_unref(json_object);
807}
808
809static void send_unmute_event(struct confbridge_user *user, struct confbridge_conference *conference)
810{
811 struct ast_json *json_object;
812
813 json_object = ast_json_pack("{s: b}",
814 "admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN)
815 );
816 if (!json_object) {
817 return;
818 }
819 send_conf_stasis(conference, user->chan, confbridge_unmute_type(), json_object, 1);
820 ast_json_unref(json_object);
821}
822
823static void set_rec_filename(struct confbridge_conference *conference, struct ast_str **filename, int is_new)
824{
825 char *rec_file = conference->b_profile.rec_file;
826 char *ext;
827 time_t now;
828
829 if (ast_str_strlen(*filename)
831 && !is_new) {
832 return;
833 }
834
835 time(&now);
836
837 ast_str_reset(*filename);
838 if (ast_strlen_zero(rec_file)) {
839 ast_str_set(filename, 0, "confbridge-%s-%u.wav", conference->name,
840 (unsigned int) now);
841 } else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_RECORD_FILE_TIMESTAMP)) {
842 /* insert time before file extension */
843 ext = strrchr(rec_file, '.');
844 if (ext) {
845 ast_str_set_substr(filename, 0, rec_file, ext - rec_file);
846 ast_str_append(filename, 0, "-%u%s", (unsigned int) now, ext);
847 } else {
848 ast_str_set(filename, 0, "%s-%u", rec_file, (unsigned int) now);
849 }
850 } else {
851 ast_str_set(filename, 0, "%s", rec_file);
852 }
853 ast_str_append(filename, 0, ",%s%s,%s",
854 ast_test_flag(&conference->b_profile, BRIDGE_OPT_RECORD_FILE_APPEND) ? "a" : "",
855 conference->b_profile.rec_options,
856 conference->b_profile.rec_command);
857}
858
859static int is_new_rec_file(const char *rec_file, struct ast_str **orig_rec_file)
860{
861 if (!ast_strlen_zero(rec_file)) {
862 if (!*orig_rec_file) {
864 }
865
866 if (*orig_rec_file
867 && strcmp(ast_str_buffer(*orig_rec_file), rec_file)) {
868 ast_str_set(orig_rec_file, 0, "%s", rec_file);
869 return 1;
870 }
871 }
872 return 0;
873}
874
875struct confbridge_conference *conf_find_bridge(const char *conference_name)
876{
877 return ao2_find(conference_bridges, conference_name, OBJ_KEY);
878}
879
880/*!
881 * \internal
882 * \brief Returns whether or not conference is being recorded.
883 *
884 * \param conference The bridge to check for recording
885 *
886 * \note Must be called with the conference locked
887 *
888 * \retval 1 conference is recording.
889 * \retval 0 conference is NOT recording.
890 */
891static int conf_is_recording(struct confbridge_conference *conference)
892{
893 return conference->record_chan != NULL;
894}
895
896/*!
897 * \internal
898 * \brief Stop recording a conference bridge
899 *
900 * \param conference The conference bridge on which to stop the recording
901 *
902 * \note Must be called with the conference locked
903 *
904 * \retval -1 Failure
905 * \retval 0 Success
906 */
907static int conf_stop_record(struct confbridge_conference *conference)
908{
909 struct ast_channel *chan;
910 struct ast_frame f = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_HANGUP };
911
912 if (!conf_is_recording(conference)) {
913 return -1;
914 }
915
916 /* Remove the recording channel from the conference bridge. */
917 chan = conference->record_chan;
918 conference->record_chan = NULL;
919 ast_queue_frame(chan, &f);
920 ast_channel_unref(chan);
921
922 ast_test_suite_event_notify("CONF_STOP_RECORD", "Message: stopped conference recording channel\r\nConference: %s", conference->b_profile.name);
923 send_stop_record_event(conference);
924
925 return 0;
926}
927
928/*!
929 * \internal
930 * \brief Start recording the conference
931 *
932 * \param conference The conference bridge to start recording
933 *
934 * \note Must be called with the conference locked
935 *
936 * \retval 0 success
937 * \retval non-zero failure
938 */
939static int conf_start_record(struct confbridge_conference *conference)
940{
941 struct ast_app *mixmonapp;
942 struct ast_channel *chan;
943 struct ast_format_cap *cap;
944 struct ast_bridge_features *features;
945
946 if (conf_is_recording(conference)) {
947 return -1;
948 }
949
950 mixmonapp = pbx_findapp("MixMonitor");
951 if (!mixmonapp) {
952 ast_log(LOG_WARNING, "Cannot record ConfBridge, MixMonitor app is not installed\n");
953 return -1;
954 }
955
956 features = ast_bridge_features_new();
957 if (!features) {
958 return -1;
959 }
961
963 if (!cap) {
965 return -1;
966 }
968
969 /* Create the recording channel. */
970 chan = ast_request("CBRec", cap, NULL, NULL, conference->name, NULL);
971 ao2_ref(cap, -1);
972 if (!chan) {
974 return -1;
975 }
976
977 /* Start recording. */
978 set_rec_filename(conference, &conference->record_filename,
979 is_new_rec_file(conference->b_profile.rec_file, &conference->orig_rec_file));
980 ast_answer(chan);
981 pbx_exec(chan, mixmonapp, ast_str_buffer(conference->record_filename));
982
983 /* Put the channel into the conference bridge. */
984 ast_channel_ref(chan);
985 conference->record_chan = chan;
986 if (ast_bridge_impart(conference->bridge, chan, NULL, features,
988 ast_hangup(chan);
989 ast_channel_unref(chan);
990 conference->record_chan = NULL;
991 return -1;
992 }
993
994 ast_test_suite_event_notify("CONF_START_RECORD", "Message: started conference recording channel\r\nConference: %s", conference->b_profile.name);
995 send_start_record_event(conference);
996
997 return 0;
998}
999
1000/*! \brief Playback the given filename and monitor for any dtmf interrupts.
1001 *
1002 * This function is used to playback sound files on a given channel and optionally
1003 * allow dtmf interrupts to occur.
1004 *
1005 * If the optional bridge_channel parameter is given then sound file playback
1006 * is played on that channel and dtmf interruptions are allowed. However, if
1007 * bridge_channel is not set then the channel parameter is expected to be set
1008 * instead and non interruptible playback is played on that channel.
1009 *
1010 * \param bridge_channel Bridge channel to play file on
1011 * \param channel Optional channel to play file on if bridge_channel not given
1012 * \param filename The file name to playback
1013 *
1014 * \retval -1 failure during playback.
1015 * \retval 0 on file was fully played.
1016 * \retval 1 on dtmf interrupt.
1017 */
1018static int play_file(struct ast_bridge_channel *bridge_channel, struct ast_channel *channel,
1019 const char *filename)
1020{
1021 struct ast_channel *chan;
1022 const char *stop_digits;
1023 int digit;
1024
1025 if (bridge_channel) {
1026 chan = bridge_channel->chan;
1027 stop_digits = AST_DIGIT_ANY;
1028 } else {
1029 chan = channel;
1030 stop_digits = AST_DIGIT_NONE;
1031 }
1032
1033 digit = ast_stream_and_wait(chan, filename, stop_digits);
1034 if (digit < 0) {
1035 ast_log(LOG_WARNING, "Failed to playback file '%s' to channel\n", filename);
1036 return -1;
1037 }
1038
1039 if (digit > 0) {
1042 return 1;
1043 }
1044
1045 return 0;
1046}
1047
1048/*!
1049 * \internal
1050 * \brief Complain if the given sound file does not exist.
1051 *
1052 * \param filename Sound file to check if exists.
1053 *
1054 * \retval non-zero if the file exists.
1055 */
1056static int sound_file_exists(const char *filename)
1057{
1058 if (ast_fileexists(filename, NULL, NULL)) {
1059 return -1;
1060 }
1061 ast_log(LOG_WARNING, "File %s does not exist in any format\n", filename);
1062 return 0;
1063}
1064
1065/*!
1066 * \brief Announce number of users in the conference bridge to the caller
1067 *
1068 * \param conference Conference bridge to peek at
1069 * \param user Optional Caller
1070 * \param bridge_channel The bridged channel involved
1071 *
1072 * \note if caller is NULL, the announcment will be sent to all participants in the conference.
1073 * \retval 0 on success.
1074 * \retval -1 if the user hung up.
1075 */
1076static int announce_user_count(struct confbridge_conference *conference, struct confbridge_user *user,
1078{
1079 const char *other_in_party = conf_get_sound(CONF_SOUND_OTHER_IN_PARTY, conference->b_profile.sounds);
1080 const char *only_one = conf_get_sound(CONF_SOUND_ONLY_ONE, conference->b_profile.sounds);
1081 const char *there_are = conf_get_sound(CONF_SOUND_THERE_ARE, conference->b_profile.sounds);
1082
1083 if (conference->activeusers <= 1) {
1084 /* Awww we are the only person in the conference bridge OR we only have waitmarked users */
1085 return 0;
1086 } else if (conference->activeusers == 2) {
1087 if (user) {
1088 /* Eep, there is one other person */
1089 if (play_file(bridge_channel, user->chan, only_one) < 0) {
1090 return -1;
1091 }
1092 } else {
1093 play_sound_file(conference, only_one);
1094 }
1095 } else {
1096 /* Alas multiple others in here */
1097 if (user) {
1098 if (ast_stream_and_wait(user->chan,
1099 there_are,
1100 "")) {
1101 return -1;
1102 }
1103 if (ast_say_number(user->chan, conference->activeusers - 1, "", ast_channel_language(user->chan), NULL)) {
1104 return -1;
1105 }
1106 if (play_file(bridge_channel, user->chan, other_in_party) < 0) {
1107 return -1;
1108 }
1109 } else if (sound_file_exists(there_are) && sound_file_exists(other_in_party)) {
1110 play_sound_file(conference, there_are);
1111 play_sound_number(conference, conference->activeusers - 1);
1112 play_sound_file(conference, other_in_party);
1113 }
1114 }
1115 return 0;
1116}
1117
1118/*!
1119 * \brief Play back an audio file to a channel
1120 *
1121 * \param user User to play audio prompt to
1122 * \param filename Prompt to play
1123 *
1124 * \retval 0 on success.
1125 * \retval -1 if the user hung up.
1126 *
1127 * \note Generally this should be called when the conference is unlocked to avoid blocking
1128 * the entire conference while the sound is played. But don't unlock the conference bridge
1129 * in the middle of a state transition.
1130 */
1131static int play_prompt_to_user(struct confbridge_user *user, const char *filename)
1132{
1133 return ast_stream_and_wait(user->chan, filename, "");
1134}
1135
1136static void handle_video_on_join(struct confbridge_conference *conference, struct ast_channel *chan, int marked)
1137{
1138 /* Right now, only marked users are automatically set as the single src of video.*/
1139 if (!marked) {
1140 return;
1141 }
1142
1144 int set = 1;
1145 struct confbridge_user *user = NULL;
1146
1148 /* see if anyone is already the video src */
1150 if (user->chan == chan) {
1151 continue;
1152 }
1154 set = 0;
1155 break;
1156 }
1157 }
1159 if (set) {
1161 }
1163 /* we joined and are video capable, we override anyone else that may have already been the video feed */
1165 }
1166}
1167
1169{
1170 struct confbridge_user *user = NULL;
1171
1172 /* if this isn't a video source, nothing to update */
1174 return;
1175 }
1176
1178
1179 /* If in follow talker mode, make sure to restore this mode on the
1180 * bridge when a source is removed. It is possible this channel was
1181 * only set temporarily as a video source by an AMI or DTMF action. */
1184 }
1185
1186 /* if the video_mode isn't set to automatically pick the video source, do nothing on exit. */
1189 return;
1190 }
1191
1192 /* Make the next available marked user the video src. */
1195 if (user->chan == chan) {
1196 continue;
1197 }
1198 if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
1200 break;
1201 }
1202 }
1204}
1205
1207{
1212};
1213
1214/*!
1215 * \brief Hang up the announcer channel
1216 *
1217 * This hangs up the announcer channel in the conference. This
1218 * runs in the playback queue taskprocessor since we do not want
1219 * to hang up the channel while it's trying to play an announcement.
1220 *
1221 * This task is performed synchronously, so there is no need to
1222 * perform any cleanup on the passed-in data.
1223 *
1224 * \param data A hangup_data structure
1225 * \return 0
1226 */
1227static int hangup_playback(void *data)
1228{
1229 struct hangup_data *hangup = data;
1230
1231 ast_autoservice_stop(hangup->conference->playback_chan);
1232
1233 ast_hangup(hangup->conference->playback_chan);
1234 hangup->conference->playback_chan = NULL;
1235
1236 ast_mutex_lock(&hangup->lock);
1237 hangup->hungup = 1;
1238 ast_cond_signal(&hangup->cond);
1239 ast_mutex_unlock(&hangup->lock);
1240
1241 return 0;
1242}
1243
1245{
1246 ast_mutex_init(&hangup->lock);
1247 ast_cond_init(&hangup->cond, NULL);
1248
1249 hangup->conference = conference;
1250 hangup->hungup = 0;
1251}
1252
1254{
1255 ast_mutex_destroy(&hangup->lock);
1256 ast_cond_destroy(&hangup->cond);
1257}
1258
1259/*!
1260 * \brief Destroy a conference bridge
1261 *
1262 * \param obj The conference bridge object
1263 */
1264static void destroy_conference_bridge(void *obj)
1265{
1266 struct confbridge_conference *conference = obj;
1267
1268 ast_debug(1, "Destroying conference bridge '%s'\n", conference->name);
1269
1270 if (conference->playback_chan) {
1271 if (conference->playback_queue) {
1272 struct hangup_data hangup;
1274
1276 ast_mutex_lock(&hangup.lock);
1277 while (!hangup.hungup) {
1278 ast_cond_wait(&hangup.cond, &hangup.lock);
1279 }
1280 ast_mutex_unlock(&hangup.lock);
1281 }
1282
1284 } else {
1285 /* Playback queue is not yet allocated. Just hang up the channel straight */
1288 }
1289 }
1290
1291 /* Destroying a conference bridge is simple, all we have to do is destroy the bridging object */
1292 if (conference->bridge) {
1295 }
1296
1300
1303}
1304
1305/*! \brief Call the proper join event handler for the user for the conference bridge's current state
1306 * \internal
1307 * \param user The conference bridge user that is joining
1308 * \retval 0 success
1309 * \retval -1 failure
1310 */
1312{
1314 if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
1315 handler = user->conference->state->join_marked;
1316 } else if (ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED)) {
1317 handler = user->conference->state->join_waitmarked;
1318 } else {
1319 handler = user->conference->state->join_unmarked;
1320 }
1321
1323
1324 if (!handler) {
1326 return -1;
1327 }
1328
1329 handler(user);
1330
1331 return 0;
1332}
1333
1334/*! \brief Call the proper leave event handler for the user for the conference bridge's current state
1335 * \internal
1336 * \param user The conference bridge user that is leaving
1337 * \retval 0 success
1338 * \retval -1 failure
1339 */
1341{
1343 if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
1344 handler = user->conference->state->leave_marked;
1345 } else if (ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED)) {
1346 handler = user->conference->state->leave_waitmarked;
1347 } else {
1348 handler = user->conference->state->leave_unmarked;
1349 }
1350
1352
1353 if (!handler) {
1354 /* This should never happen. If it does, though, it is bad. The user will not have been removed
1355 * from the appropriate list, so counts will be off and stuff. The conference won't be torn down, etc.
1356 * Shouldn't happen, though. */
1358 return -1;
1359 }
1360
1361 handler(user);
1362
1363 return 0;
1364}
1365
1367{
1368 int mute_user;
1369 int mute_system;
1370 int mute_effective;
1371
1372 /* User level mute request. */
1373 mute_user = user->muted;
1374
1375 /* System level mute request. */
1376 mute_system = user->playing_moh
1377 /*
1378 * Do not allow waitmarked users to talk to anyone unless there
1379 * is a marked user present.
1380 */
1381 || (!user->conference->markedusers
1382 && ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED));
1383
1384 mute_effective = mute_user || mute_system;
1385
1386 ast_debug(1, "User %s is %s: user:%d system:%d.\n",
1387 ast_channel_name(user->chan), mute_effective ? "muted" : "unmuted",
1388 mute_user, mute_system);
1389 user->features.mute = mute_effective;
1390 ast_test_suite_event_notify("CONF_MUTE_UPDATE",
1391 "Mode: %s\r\n"
1392 "Conference: %s\r\n"
1393 "Channel: %s",
1394 mute_effective ? "muted" : "unmuted",
1395 user->conference->b_profile.name,
1396 ast_channel_name(user->chan));
1397}
1398
1399/*!
1400 * \internal
1401 * \brief Mute/unmute a single user.
1402 */
1404{
1405 /* Set user level mute request. */
1406 user->muted = mute ? 1 : 0;
1407
1409 ast_test_suite_event_notify("CONF_MUTE",
1410 "Message: participant %s %s\r\n"
1411 "Conference: %s\r\n"
1412 "Channel: %s",
1413 ast_channel_name(user->chan),
1414 mute ? "muted" : "unmuted",
1416 ast_channel_name(user->chan));
1417 if (mute) {
1419 } else {
1421 }
1422}
1423
1425{
1426 user->playing_moh = 0;
1427 if (!user->suspended_moh) {
1428 int in_bridge;
1429
1430 /*
1431 * Locking the ast_bridge here is the only way to hold off the
1432 * call to ast_bridge_join() in confbridge_exec() from
1433 * interfering with the bridge and MOH operations here.
1434 */
1435 ast_bridge_lock(user->conference->bridge);
1436
1437 /*
1438 * Temporarily suspend the user from the bridge so we have
1439 * control to stop MOH if needed.
1440 */
1441 in_bridge = !ast_bridge_suspend(user->conference->bridge, user->chan);
1442 ast_moh_stop(user->chan);
1443 if (in_bridge) {
1444 ast_bridge_unsuspend(user->conference->bridge, user->chan);
1445 }
1446
1447 ast_bridge_unlock(user->conference->bridge);
1448 }
1449}
1450
1452{
1453 user->playing_moh = 1;
1454 if (!user->suspended_moh) {
1455 int in_bridge;
1456
1457 /*
1458 * Locking the ast_bridge here is the only way to hold off the
1459 * call to ast_bridge_join() in confbridge_exec() from
1460 * interfering with the bridge and MOH operations here.
1461 */
1462 ast_bridge_lock(user->conference->bridge);
1463
1464 /*
1465 * Temporarily suspend the user from the bridge so we have
1466 * control to start MOH if needed.
1467 */
1468 in_bridge = !ast_bridge_suspend(user->conference->bridge, user->chan);
1469 ast_moh_start(user->chan, user->u_profile.moh_class, NULL);
1470 if (in_bridge) {
1471 ast_bridge_unsuspend(user->conference->bridge, user->chan);
1472 }
1473
1474 ast_bridge_unlock(user->conference->bridge);
1475 }
1476}
1477
1478/*!
1479 * \internal
1480 * \brief Unsuspend MOH for the conference user.
1481 *
1482 * \param user Conference user to unsuspend MOH on.
1483 */
1485{
1486 ao2_lock(user->conference);
1487 if (--user->suspended_moh == 0 && user->playing_moh) {
1488 ast_moh_start(user->chan, user->u_profile.moh_class, NULL);
1489 }
1490 ao2_unlock(user->conference);
1491}
1492
1493/*!
1494 * \internal
1495 * \brief Suspend MOH for the conference user.
1496 *
1497 * \param user Conference user to suspend MOH on.
1498 */
1500{
1501 ao2_lock(user->conference);
1502 if (user->suspended_moh++ == 0 && user->playing_moh) {
1503 ast_moh_stop(user->chan);
1504 }
1505 ao2_unlock(user->conference);
1506}
1507
1509{
1510 /* If we have not been quieted play back that they are waiting for the leader */
1512 conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, user->conference->b_profile.sounds))) {
1513 /* user hungup while the sound was playing */
1514 return -1;
1515 }
1516 return 0;
1517}
1518
1520{
1521 /* If audio prompts have not been quieted or this prompt quieted play it on out */
1524 conf_get_sound(CONF_SOUND_ONLY_PERSON, user->conference->b_profile.sounds))) {
1525 /* user hungup while the sound was playing */
1526 return -1;
1527 }
1528 }
1529 return 0;
1530}
1531
1533{
1534 struct post_join_action *action;
1535 if (!(action = ast_calloc(1, sizeof(*action)))) {
1536 return -1;
1537 }
1538 action->func = func;
1539 AST_LIST_INSERT_TAIL(&user->post_join_list, action, list);
1540 return 0;
1541}
1542
1543
1545{
1546 ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "confbridge:%s", conference->name);
1547}
1548
1550{
1551 /* If we are the second participant we may need to stop music on hold on the first */
1552 struct confbridge_user *first_user = AST_LIST_FIRST(&conference->active_list);
1553
1554 if (ast_test_flag(&first_user->u_profile, USER_OPT_MUSICONHOLD)) {
1555 conf_moh_stop(first_user);
1556 }
1557 conf_update_user_mute(first_user);
1558}
1559
1561{
1562 struct pbx_find_info q = { .stacklen = 0 };
1563
1564 /* Called with a reference to conference */
1565 ao2_unlink(conference_bridges, conference);
1566 send_conf_end_event(conference);
1567 if (!ast_strlen_zero(conference->b_profile.regcontext) &&
1569 conference->name, 1, NULL, "", E_MATCH)) {
1571 conference->name, 1, NULL);
1572 }
1573 ao2_lock(conference);
1574 conf_stop_record(conference);
1575 ao2_unlock(conference);
1576}
1577
1578/*!
1579 * \internal
1580 * \brief Allocate playback channel for a conference.
1581 * \pre expects conference to be locked before calling this function
1582 */
1583static int alloc_playback_chan(struct confbridge_conference *conference)
1584{
1585 struct ast_format_cap *cap;
1586 char taskprocessor_name[AST_TASKPROCESSOR_MAX_NAME + 1];
1587
1589 if (!cap) {
1590 return -1;
1591 }
1593 conference->playback_chan = ast_request("CBAnn", cap, NULL, NULL,
1594 conference->name, NULL);
1595 ao2_ref(cap, -1);
1596 if (!conference->playback_chan) {
1597 return -1;
1598 }
1599
1600 /* To make sure playback_chan has the same language as the bridge */
1601 ast_channel_lock(conference->playback_chan);
1602 ast_channel_language_set(conference->playback_chan, conference->b_profile.language);
1603 ast_channel_unlock(conference->playback_chan);
1604
1605 ast_debug(1, "Created announcer channel '%s' to conference bridge '%s'\n",
1606 ast_channel_name(conference->playback_chan), conference->name);
1607
1608 ast_taskprocessor_build_name(taskprocessor_name, sizeof(taskprocessor_name),
1609 "Confbridge/%s", conference->name);
1610 conference->playback_queue = ast_taskprocessor_get(taskprocessor_name, TPS_REF_DEFAULT);
1611 if (!conference->playback_queue) {
1612 ast_hangup(conference->playback_chan);
1613 conference->playback_chan = NULL;
1614 return -1;
1615 }
1616 return 0;
1617}
1618
1619/*!
1620 * \brief Push the announcer channel into the bridge
1621 *
1622 * \param conference Conference bridge to push the announcer to
1623 * \retval 0 Success
1624 * \retval -1 Failed to push the channel to the bridge
1625 */
1626static int push_announcer(struct confbridge_conference *conference)
1627{
1628 if (conf_announce_channel_push(conference->playback_chan)) {
1629 ast_hangup(conference->playback_chan);
1630 conference->playback_chan = NULL;
1631 return -1;
1632 }
1633
1635 return 0;
1636}
1637
1638static void confbridge_unlock_and_unref(void *obj)
1639{
1640 struct confbridge_conference *conference = obj;
1641
1642 if (!obj) {
1643 return;
1644 }
1645 ao2_unlock(conference);
1646 ao2_ref(conference, -1);
1647}
1648
1650{
1651 struct ast_channel_snapshot *old_snapshot;
1652 struct ast_channel_snapshot *new_snapshot;
1653 char *confbr_name = NULL;
1654 char *comma;
1656 struct confbridge_user *user = NULL;
1657 int found_user = 0;
1658 struct ast_json *json_object;
1659
1661 && strcmp(msg->to_transferee.channel_snapshot->dialplan->appl, "ConfBridge") == 0
1662 && msg->target) {
1663 /* We're transferring a bridge to an extension */
1664 old_snapshot = msg->to_transferee.channel_snapshot;
1665 new_snapshot = msg->target;
1666 } else if (msg->to_transfer_target.channel_snapshot
1667 && strcmp(msg->to_transfer_target.channel_snapshot->dialplan->appl, "ConfBridge") == 0
1668 && msg->transferee) {
1669 /* We're transferring a call to a bridge */
1670 old_snapshot = msg->to_transfer_target.channel_snapshot;
1671 new_snapshot = msg->transferee;
1672 } else {
1673 ast_log(LOG_ERROR, "Could not determine proper channels\n");
1674 return;
1675 }
1676
1677 /*
1678 * old_snapshot->data should have the original parameters passed to
1679 * the ConfBridge app:
1680 * conference[,bridge_profile[,user_profile[,menu]]]
1681 * We'll use "conference" to look up the bridge.
1682 *
1683 * We _could_ use old_snapshot->bridgeid to get the bridge but
1684 * that would involve locking the conference_bridges container
1685 * and iterating over it looking for a matching bridge.
1686 */
1687 if (ast_strlen_zero(old_snapshot->dialplan->data)) {
1688 ast_log(LOG_ERROR, "Channel '%s' didn't have app data set\n", old_snapshot->base->name);
1689 return;
1690 }
1691 confbr_name = ast_strdupa(old_snapshot->dialplan->data);
1692 comma = strchr(confbr_name, ',');
1693 if (comma) {
1694 *comma = '\0';
1695 }
1696
1697 ast_debug(1, "Confbr: %s Leaving: %s Joining: %s\n", confbr_name, old_snapshot->base->name, new_snapshot->base->name);
1698
1699 conference = ao2_find(conference_bridges, confbr_name, OBJ_SEARCH_KEY);
1700 if (!conference) {
1701 ast_log(LOG_ERROR, "Conference bridge '%s' not found\n", confbr_name);
1702 return;
1703 }
1704 ao2_lock(conference);
1705
1706 /*
1707 * We need to grab the user profile for the departing user in order to
1708 * properly format the join/leave messages.
1709 */
1710 AST_LIST_TRAVERSE(&conference->active_list, user, list) {
1711 if (strcasecmp(ast_channel_name(user->chan), old_snapshot->base->name) == 0) {
1712 found_user = 1;
1713 break;
1714 }
1715 }
1716
1717 /*
1718 * If we didn't find the user in the active list, try the waiting list.
1719 */
1720 if (!found_user && conference->waitingusers) {
1721 AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
1722 if (strcasecmp(ast_channel_name(user->chan), old_snapshot->base->name) == 0) {
1723 found_user = 1;
1724 break;
1725 }
1726 }
1727 }
1728
1729 if (!found_user) {
1730 ast_log(LOG_ERROR, "Unable to find user profile for channel '%s' in bridge '%s'\n",
1731 old_snapshot->base->name, confbr_name);
1732 return;
1733 }
1734
1735 /*
1736 * We're going to use the existing user profile to create the messages.
1737 */
1738 json_object = ast_json_pack("{s: b}",
1739 "admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN)
1740 );
1741 if (!json_object) {
1742 return;
1743 }
1744
1745 send_conf_stasis_snapshots(conference, old_snapshot, confbridge_leave_type(), json_object);
1746 ast_json_unref(json_object);
1747
1748 json_object = ast_json_pack("{s: b, s: b}",
1749 "admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN),
1750 "muted", user->muted);
1751 if (!json_object) {
1752 return;
1753 }
1754 send_conf_stasis_snapshots(conference, new_snapshot, confbridge_join_type(), json_object);
1755 ast_json_unref(json_object);
1756}
1757
1758/*!
1759 * \brief Join a conference bridge
1760 *
1761 * \param conference_name The conference name
1762 * \param user Conference bridge user structure
1763 *
1764 * \return A pointer to the conference bridge struct, or NULL if the conference room wasn't found.
1765 */
1766static struct confbridge_conference *join_conference_bridge(const char *conference_name, struct confbridge_user *user)
1767{
1768 struct confbridge_conference *conference;
1769 struct post_join_action *action;
1770 int max_members_reached = 0;
1771
1772 /* We explicitly lock the conference bridges container ourselves so that other callers can not create duplicate conferences at the same time */
1774
1775 ast_debug(1, "Trying to find conference bridge '%s'\n", conference_name);
1776
1777 /* Attempt to find an existing conference bridge */
1778 conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
1779 if (conference && conference->b_profile.max_members) {
1780 max_members_reached = conference->b_profile.max_members > conference->activeusers ? 0 : 1;
1781 }
1782
1783 /* When finding a conference bridge that already exists make sure that it is not locked, and if so that we are not an admin */
1784 if (conference && (max_members_reached || conference->locked) && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN)) {
1786 ast_debug(1, "Conference '%s' is locked and caller is not an admin\n", conference_name);
1789 "");
1790 ao2_ref(conference, -1);
1791 return NULL;
1792 }
1793
1794 /* If no conference bridge was found see if we can create one */
1795 if (!conference) {
1796 /* Try to allocate memory for a new conference bridge, if we fail... this won't end well. */
1797 if (!(conference = ao2_alloc(sizeof(*conference), destroy_conference_bridge))) {
1799 ast_log(LOG_ERROR, "Conference '%s' could not be created.\n", conference_name);
1800 return NULL;
1801 }
1802
1803 /* Setup for the record channel */
1805 if (!conference->record_filename) {
1806 ao2_ref(conference, -1);
1808 return NULL;
1809 }
1810
1811 /* Setup conference bridge parameters */
1812 ast_copy_string(conference->name, conference_name, sizeof(conference->name));
1813 conf_bridge_profile_copy(&conference->b_profile, &user->b_profile);
1814
1815 /* Create an actual bridge that will do the audio mixing */
1818 app, conference_name, NULL);
1819 if (!conference->bridge) {
1820 ao2_ref(conference, -1);
1822 ast_log(LOG_ERROR, "Conference '%s' mixing bridge could not be created.\n", conference_name);
1823 return NULL;
1824 }
1825
1826 /* Set the internal sample rate on the bridge from the bridge profile */
1828 /* Set the maximum sample rate on the bridge from the bridge profile */
1830 /* Set the internal mixing interval on the bridge from the bridge profile */
1833
1836 } else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_SFU)) {
1841 } else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_LOWEST)) {
1843 } else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST)) {
1851 } else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_FORCE)) {
1854 }
1855 }
1856
1857 /* Always set the minimum interval between video updates, to avoid infinite video updates. */
1859
1861 ast_bridge_set_send_sdp_label(conference->bridge, 1);
1862 }
1863
1864 /* Link it into the conference bridges container */
1865 if (!ao2_link(conference_bridges, conference)) {
1866 ao2_ref(conference, -1);
1869 "Conference '%s' could not be added to the conferences list.\n", conference_name);
1870 return NULL;
1871 }
1872
1873 /* Set the initial state to EMPTY */
1874 conference->state = CONF_STATE_EMPTY;
1875
1876 if (alloc_playback_chan(conference)) {
1877 ao2_unlink(conference_bridges, conference);
1878 ao2_ref(conference, -1);
1880 ast_log(LOG_ERROR, "Could not allocate announcer channel for conference '%s'\n", conference_name);
1881 return NULL;
1882 }
1883
1884 if (push_announcer(conference)) {
1885 ao2_unlink(conference_bridges, conference);
1886 ao2_ref(conference, -1);
1888 ast_log(LOG_ERROR, "Could not add announcer channel for conference '%s' bridge\n", conference_name);
1889 return NULL;
1890 }
1891
1893 ao2_lock(conference);
1894 conf_start_record(conference);
1895 ao2_unlock(conference);
1896 }
1897
1898 send_conf_start_event(conference);
1899
1900 if (!ast_strlen_zero(conference->b_profile.regcontext)) {
1901 if (!ast_exists_extension(NULL, conference->b_profile.regcontext, conference->name, 1, NULL)) {
1902 ast_add_extension(conference->b_profile.regcontext, 1, conference->name, 1, NULL, NULL, "Noop", NULL, NULL, "ConfBridge");
1903 }
1904 }
1905
1906 ast_debug(1, "Created conference '%s' and linked to container.\n", conference_name);
1907 }
1908
1910
1911 /* Setup conference bridge user parameters */
1912 user->conference = conference;
1913
1914 ao2_lock(conference);
1915
1916 /* Determine if the new user should join the conference muted. */
1917 if (ast_test_flag(&user->u_profile, USER_OPT_STARTMUTED)
1918 || (!ast_test_flag(&user->u_profile, USER_OPT_ADMIN) && conference->muted)) {
1919 /* Set user level mute request. */
1920 user->muted = 1;
1921 }
1922
1923 /*
1924 * Suspend any MOH until the user actually joins the bridge of
1925 * the conference. This way any pre-join file playback does not
1926 * need to worry about MOH.
1927 */
1928 user->suspended_moh = 1;
1929
1931 /* Invalid event, nothing was done, so we don't want to process a leave. */
1932 ao2_unlock(conference);
1933 ao2_ref(conference, -1);
1934 user->conference = NULL;
1935 return NULL;
1936 }
1937
1938 if (ast_check_hangup(user->chan)) {
1939 ao2_unlock(conference);
1941 return NULL;
1942 }
1943
1944 ao2_unlock(conference);
1945
1946 /* If an announcement is to be played play it */
1947 if (!ast_strlen_zero(user->u_profile.announcement)) {
1949 user->u_profile.announcement)) {
1951 return NULL;
1952 }
1953 }
1954
1955 /* Announce number of users if need be */
1956 if (ast_test_flag(&user->u_profile, USER_OPT_ANNOUNCEUSERCOUNT)) {
1957 if (announce_user_count(conference, user, NULL)) {
1959 return NULL;
1960 }
1961 }
1962
1964 (conference->activeusers > user->u_profile.announce_user_count_all_after)) {
1965 int user_count_res;
1966
1967 /*
1968 * We have to autoservice the new user because he has not quite
1969 * joined the conference yet.
1970 */
1972 user_count_res = announce_user_count(conference, NULL, NULL);
1974 if (user_count_res) {
1976 return NULL;
1977 }
1978 }
1979
1980 /* Handle post-join actions */
1981 while ((action = AST_LIST_REMOVE_HEAD(&user->post_join_list, list))) {
1982 action->func(user);
1983 ast_free(action);
1984 }
1985
1986 return conference;
1987}
1988
1989/*!
1990 * \brief Leave a conference
1991 *
1992 * \param user The conference user
1993 */
1995{
1996 struct post_join_action *action;
1997
1998 ao2_lock(user->conference);
2000 ao2_unlock(user->conference);
2001
2002 /* Discard any post-join actions */
2003 while ((action = AST_LIST_REMOVE_HEAD(&user->post_join_list, list))) {
2004 ast_free(action);
2005 }
2006
2007 /* Done mucking with the conference, huzzah */
2008 ao2_ref(user->conference, -1);
2009 user->conference = NULL;
2010}
2011
2012static void playback_common(struct confbridge_conference *conference, const char *filename, int say_number)
2013{
2014 /* Don't try to play if the playback channel has been hung up */
2015 if (!conference->playback_chan) {
2016 return;
2017 }
2018
2020
2021 /* The channel is all under our control, in goes the prompt */
2022 if (!ast_strlen_zero(filename)) {
2023 ast_stream_and_wait(conference->playback_chan, filename, "");
2024 } else if (say_number >= 0) {
2025 ast_say_number(conference->playback_chan, say_number, "",
2027 }
2028
2030}
2031
2034 const char *filename;
2039};
2040
2041/*!
2042 * \brief Play an announcement into a confbridge
2043 *
2044 * This runs in the playback queue taskprocessor. This ensures that
2045 * all playbacks are handled in sequence and do not play over top one
2046 * another.
2047 *
2048 * This task runs synchronously so there is no need for performing any
2049 * sort of cleanup on the input parameter.
2050 *
2051 * \param data A playback_task_data
2052 * \return 0
2053 */
2054static int playback_task(void *data)
2055{
2056 struct playback_task_data *ptd = data;
2057
2059
2060 ast_mutex_lock(&ptd->lock);
2061 ptd->playback_finished = 1;
2062 ast_cond_signal(&ptd->cond);
2063 ast_mutex_unlock(&ptd->lock);
2064
2065 return 0;
2066}
2067
2069 const char *filename, int say_number)
2070{
2071 ast_mutex_init(&ptd->lock);
2072 ast_cond_init(&ptd->cond, NULL);
2073
2074 ptd->filename = filename;
2075 ptd->say_number = say_number;
2076 ptd->conference = conference;
2077 ptd->playback_finished = 0;
2078}
2079
2081{
2082 ast_mutex_destroy(&ptd->lock);
2083 ast_cond_destroy(&ptd->cond);
2084}
2085
2087{
2088 struct playback_task_data ptd;
2089
2090 /* Do not waste resources trying to play files that do not exist */
2092 if (say_number < 0) {
2093 return 0;
2094 }
2095 } else if (!sound_file_exists(filename)) {
2096 return 0;
2097 }
2098
2101 if (!ast_strlen_zero(filename)) {
2102 ast_log(LOG_WARNING, "Unable to play file '%s' to conference %s\n",
2104 } else {
2105 ast_log(LOG_WARNING, "Unable to say number '%d' to conference %s\n",
2107 }
2109 return -1;
2110 }
2111
2112 /* Wait for the playback to complete */
2113 ast_mutex_lock(&ptd.lock);
2114 while (!ptd.playback_finished) {
2115 ast_cond_wait(&ptd.cond, &ptd.lock);
2116 }
2117 ast_mutex_unlock(&ptd.lock);
2118
2120
2121 return 0;
2122}
2123
2125{
2127}
2128
2133 char filename[0];
2134};
2135
2139 int wait;
2140};
2141
2142static void async_datastore_data_destroy(void *data)
2143{
2144 struct async_datastore_data *add = data;
2145
2146 ast_mutex_destroy(&add->lock);
2147 ast_cond_destroy(&add->cond);
2148
2149 ast_free(add);
2150}
2151
2152/*!
2153 * \brief Datastore used for timing of async announcement playback
2154 *
2155 * Announcements that are played to the entire conference can be played
2156 * asynchronously (i.e. The channel that queues the playback does not wait
2157 * for the playback to complete before continuing)
2158 *
2159 * The thing about async announcements is that the channel that queues the
2160 * announcement is either not in the bridge or is in some other way "occupied"
2161 * at the time the announcement is queued. Because of that, the initiator of
2162 * the announcement may enter after the announcement has already started,
2163 * resulting in the sound being "clipped".
2164 *
2165 * This datastore makes it so that the channel that queues the async announcement
2166 * can say "I'm ready now". This way the announcement does not start until the
2167 * initiator of the announcement is ready to hear the sound.
2168 */
2170 .type = "Confbridge async playback",
2172};
2173
2175{
2176 struct async_datastore_data *add;
2177
2178 add = ast_malloc(sizeof(*add));
2179 if (!add) {
2180 return NULL;
2181 }
2182
2183 ast_mutex_init(&add->lock);
2184 ast_cond_init(&add->cond, NULL);
2185 add->wait = 1;
2186
2187 return add;
2188}
2189
2190/*!
2191 * \brief Prepare the async playback datastore
2192 *
2193 * This is done prior to queuing an async announcement. If the
2194 * datastore has not yet been created, it is allocated and initialized.
2195 * If it already exists, we set it to be in "waiting" mode.
2196 *
2197 * \param initiator The channel that is queuing the async playback
2198 * \retval 0 Success
2199 * \retval -1 Failure :(
2200 */
2201static int setup_async_playback_datastore(struct ast_channel *initiator)
2202{
2203 struct ast_datastore *async_datastore;
2204
2205 async_datastore = ast_channel_datastore_find(initiator, &async_datastore_info, NULL);
2206 if (async_datastore) {
2207 struct async_datastore_data *add;
2208
2209 add = async_datastore->data;
2210 add->wait = 1;
2211
2212 return 0;
2213 }
2214
2215 async_datastore = ast_datastore_alloc(&async_datastore_info, NULL);
2216 if (!async_datastore) {
2217 return -1;
2218 }
2219
2220 async_datastore->data = async_datastore_data_alloc();
2221 if (!async_datastore->data) {
2222 ast_datastore_free(async_datastore);
2223 return -1;
2224 }
2225
2226 ast_channel_datastore_add(initiator, async_datastore);
2227 return 0;
2228}
2229
2231 struct confbridge_conference *conference, const char *filename, int say_number,
2232 struct ast_channel *initiator)
2233{
2234 struct async_playback_task_data *aptd;
2235
2236 aptd = ast_malloc(sizeof(*aptd) + strlen(filename) + 1);
2237 if (!aptd) {
2238 return NULL;
2239 }
2240
2241 /* Safe */
2242 strcpy(aptd->filename, filename);
2243 aptd->say_number = say_number;
2244
2245 /* You may think that we need to bump the conference refcount since we are pushing
2246 * this task to the taskprocessor.
2247 *
2248 * In this case, that actually causes a problem. The destructor for the conference
2249 * pushes a hangup task into the taskprocessor and waits for it to complete before
2250 * continuing. If the destructor gets called from a taskprocessor task, we're
2251 * deadlocked.
2252 *
2253 * So is there a risk of the conference being freed out from under us? No. Since
2254 * the destructor pushes a task into the taskprocessor and waits for it to complete,
2255 * the destructor cannot free the conference out from under us. No further tasks
2256 * can be queued onto the taskprocessor after the hangup since no channels are referencing
2257 * the conference at that point any more.
2258 */
2259 aptd->conference = conference;
2260
2261 aptd->initiator = initiator;
2262 if (initiator) {
2265 /* We don't really care if this fails. If the datastore fails to get set up
2266 * we'll still play the announcement. It's possible that the sound will be
2267 * clipped for the initiator, but that's not the end of the world.
2268 */
2271 }
2272
2273 return aptd;
2274}
2275
2277{
2279 ast_free(aptd);
2280}
2281
2282/*!
2283 * \brief Wait for the initiator of an async playback to be ready
2284 *
2285 * See the description on the async_datastore_info structure for more
2286 * information about what this is about.
2287 *
2288 * \param initiator The channel that queued the async announcement
2289 */
2291{
2292 struct ast_datastore *async_datastore;
2293 struct async_datastore_data *add;
2294
2295 ast_channel_lock(initiator);
2296 async_datastore = ast_channel_datastore_find(initiator, &async_datastore_info, NULL);
2297 ast_channel_unlock(initiator);
2298
2299 if (!async_datastore) {
2300 return;
2301 }
2302
2303 add = async_datastore->data;
2304
2305 ast_mutex_lock(&add->lock);
2306 while (add->wait) {
2307 ast_cond_wait(&add->cond, &add->lock);
2308 }
2309 ast_mutex_unlock(&add->lock);
2310}
2311
2312/*!
2313 * \brief Play an announcement into a confbridge asynchronously
2314 *
2315 * This runs in the playback queue taskprocessor. This ensures that
2316 * all playbacks are handled in sequence and do not play over top one
2317 * another.
2318 *
2319 * \param data An async_playback_task_data
2320 * \return 0
2321 */
2322static int async_playback_task(void *data)
2323{
2324 struct async_playback_task_data *aptd = data;
2325
2326 /* Wait for the initiator to get back in the bridge or be hung up */
2327 if (aptd->initiator) {
2329 }
2330
2331 playback_common(aptd->conference, aptd->filename, aptd->say_number);
2332
2334 return 0;
2335}
2336
2338 const char *filename, int say_number, struct ast_channel *initiator)
2339{
2340 struct async_playback_task_data *aptd;
2341
2342 /* Do not waste resources trying to play files that do not exist */
2344 if (say_number < 0) {
2345 return 0;
2346 }
2347 } else if (!sound_file_exists(filename)) {
2348 return 0;
2349 }
2350
2352 if (!aptd) {
2353 return -1;
2354 }
2355
2357 if (!ast_strlen_zero(filename)) {
2358 ast_log(LOG_WARNING, "Unable to play file '%s' to conference '%s'\n",
2360 } else {
2361 ast_log(LOG_WARNING, "Unable to say number '%d' to conference '%s'\n",
2363 }
2365 return -1;
2366 }
2367
2368 return 0;
2369}
2370
2372 const char *filename, struct ast_channel *initiator)
2373{
2375}
2376
2378{
2379 struct ast_datastore *async_datastore;
2380 struct async_datastore_data *add;
2381
2382 ast_channel_lock(chan);
2383 async_datastore = ast_channel_datastore_find(chan, &async_datastore_info, NULL);
2384 ast_channel_unlock(chan);
2385 if (!async_datastore) {
2386 return;
2387 }
2388
2389 add = async_datastore->data;
2390
2391 ast_mutex_lock(&add->lock);
2392 add->wait = 0;
2393 ast_cond_signal(&add->cond);
2394 ast_mutex_unlock(&add->lock);
2395}
2396
2397/*!
2398 * \brief Play number into the conference bridge
2399 *
2400 * \param conference The conference bridge to say the number into
2401 * \param say_number number to say
2402 *
2403 * \retval 0 success
2404 * \retval -1 failure
2405 */
2406static int play_sound_number(struct confbridge_conference *conference, int say_number)
2407{
2408 return play_sound_helper(conference, NULL, say_number);
2409}
2410
2411static int conf_handle_talker_cb(struct ast_bridge_channel *bridge_channel, void *hook_pvt, int talking)
2412{
2413 struct confbridge_user *user = hook_pvt;
2415 struct ast_json *talking_extras;
2416
2417 conference = ao2_find(conference_bridges, user->conference->name, OBJ_KEY);
2418 if (!conference) {
2419 /* Remove the hook since the conference does not exist. */
2420 return -1;
2421 }
2422
2423 ao2_lock(conference);
2424 user->talking = talking;
2425 ao2_unlock(conference);
2426
2427 talking_extras = ast_json_pack("{s: s, s: b}",
2428 "talking_status", talking ? "on" : "off",
2429 "admin", ast_test_flag(&user->u_profile, USER_OPT_ADMIN));
2430 if (!talking_extras) {
2431 return 0;
2432 }
2433
2434 send_conf_stasis(conference, bridge_channel->chan, confbridge_talking_type(), talking_extras, 0);
2435 ast_json_unref(talking_extras);
2436 return 0;
2437}
2438
2439static int conf_get_pin(struct ast_channel *chan, struct confbridge_user *user)
2440{
2441 char pin_guess[MAX_PIN+1] = { 0, };
2442 const char *pin = user->u_profile.pin;
2443 char *tmp = pin_guess;
2444 int i, res;
2445 unsigned int len = MAX_PIN;
2446
2447 /*
2448 * NOTE: We have not joined a conference yet so we have to use
2449 * the bridge profile requested by the user.
2450 */
2451
2452 /* give them three tries to get the pin right */
2453 for (i = 0; i < 3; i++) {
2454 if (ast_app_getdata(chan,
2455 conf_get_sound(CONF_SOUND_GET_PIN, user->b_profile.sounds),
2456 tmp, len, 0) >= 0) {
2457 if (!strcasecmp(pin, pin_guess)) {
2458 return 0;
2459 }
2460 }
2461 ast_streamfile(chan,
2462 conf_get_sound(CONF_SOUND_INVALID_PIN, user->b_profile.sounds),
2463 ast_channel_language(chan));
2464 res = ast_waitstream(chan, AST_DIGIT_ANY);
2465 if (res > 0) {
2466 /* Account for digit already read during ivalid pin playback
2467 * resetting pin buf. */
2468 pin_guess[0] = res;
2469 pin_guess[1] = '\0';
2470 tmp = pin_guess + 1;
2471 len = MAX_PIN - 1;
2472 } else {
2473 /* reset pin buf as empty buffer. */
2474 tmp = pin_guess;
2475 len = MAX_PIN;
2476 }
2477 }
2478 return -1;
2479}
2480
2481static int user_timeout(struct ast_bridge_channel *bridge_channel, void *ignore)
2482{
2484 pbx_builtin_setvar_helper(bridge_channel->chan, "CONFBRIDGE_RESULT", "TIMEOUT");
2485 return -1;
2486}
2487
2488static int conf_rec_name(struct confbridge_user *user, const char *conf_name)
2489{
2490 char destdir[PATH_MAX];
2491 int res;
2492 int duration = 20;
2493
2494 snprintf(destdir, sizeof(destdir), "%s/confbridge", ast_config_AST_SPOOL_DIR);
2495
2496 if (ast_mkdir(destdir, 0777) != 0) {
2497 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", destdir, strerror(errno));
2498 return -1;
2499 }
2500 snprintf(user->name_rec_location, sizeof(user->name_rec_location),
2501 "%s/confbridge-name-%s-%s", destdir,
2502 conf_name, ast_channel_uniqueid(user->chan));
2503
2505 res = ast_play_and_record(user->chan,
2506 "vm-rec-name",
2507 user->name_rec_location,
2508 10,
2509 "sln",
2510 &duration,
2511 NULL,
2513 0,
2514 NULL);
2515 } else {
2516 res = ast_record_review(user->chan,
2517 "vm-rec-name",
2518 user->name_rec_location,
2519 10,
2520 "sln",
2521 &duration,
2522 NULL);
2523 }
2524
2525 if (res == -1) {
2526 ast_filedelete(user->name_rec_location, NULL);
2527 user->name_rec_location[0] = '\0';
2528 return -1;
2529 }
2530 return 0;
2531}
2532
2535 char filename[0];
2536};
2537
2539 struct confbridge_conference *conference, const char *filename)
2540{
2542
2543 atd = ast_malloc(sizeof(*atd) + strlen(filename) + 1);
2544 if (!atd) {
2545 return NULL;
2546 }
2547
2548 /* Safe */
2549 strcpy(atd->filename, filename);
2550 atd->conference = conference;
2551
2552 return atd;
2553}
2554
2556{
2557 ast_free(atd);
2558}
2559
2560/*!
2561 * \brief Delete user's name file asynchronously
2562 *
2563 * This runs in the playback queue taskprocessor. This ensures that
2564 * sound file is removed after playback is finished and not before.
2565 *
2566 * \param data An async_delete_name_rec_task_data
2567 * \return 0
2568 */
2569static int async_delete_name_rec_task(void *data)
2570{
2571 struct async_delete_name_rec_task_data *atd = data;
2572
2574 ast_log(LOG_DEBUG, "Conference '%s' removed user name file '%s'\n",
2575 atd->conference->name, atd->filename);
2576
2578 return 0;
2579}
2580
2582 const char *filename)
2583{
2585
2587 return 0;
2588 } else if (!sound_file_exists(filename)) {
2589 return 0;
2590 }
2591
2593 if (!atd) {
2594 return -1;
2595 }
2596
2598 ast_log(LOG_WARNING, "Conference '%s' was unable to remove user name file '%s'\n",
2601 return -1;
2602 }
2603
2604 return 0;
2605}
2606
2607static int join_callback(struct ast_bridge_channel *bridge_channel, void *ignore)
2608{
2609 async_play_sound_ready(bridge_channel->chan);
2610 return 0;
2611}
2612
2617};
2618
2619static int send_event_hook_callback(struct ast_bridge_channel *bridge_channel, void *data)
2620{
2621 struct confbridge_hook_data *hook_data = data;
2622
2623 if (hook_data->hook_type == AST_BRIDGE_HOOK_TYPE_JOIN) {
2624 send_join_event(hook_data->user, hook_data->conference);
2625 } else {
2626 send_leave_event(hook_data->user, hook_data->conference);
2627 }
2628
2629 return 0;
2630}
2631
2632/*! \brief The ConfBridge application */
2633static int confbridge_exec(struct ast_channel *chan, const char *data)
2634{
2635 int res = 0, volume_adjustments[2];
2636 int quiet = 0;
2637 int async_delete_task_pushed = 0;
2638 char *parse;
2639 const char *b_profile_name = NULL;
2640 const char *u_profile_name = NULL;
2641 const char *menu_profile_name = NULL;
2642 struct confbridge_conference *conference = NULL;
2643 struct confbridge_user user = {
2644 .chan = chan,
2645 .tech_args.talking_threshold = DEFAULT_TALKING_THRESHOLD,
2646 .tech_args.silence_threshold = DEFAULT_SILENCE_THRESHOLD,
2647 .tech_args.drop_silence = 0,
2648 };
2649 struct confbridge_hook_data *join_hook_data;
2650 struct confbridge_hook_data *leave_hook_data;
2651
2653 AST_APP_ARG(conf_name);
2654 AST_APP_ARG(b_profile_name);
2655 AST_APP_ARG(u_profile_name);
2656 AST_APP_ARG(menu_profile_name);
2657 );
2658
2659 if (ast_bridge_features_init(&user.features)) {
2660 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
2661 res = -1;
2662 goto confbridge_cleanup;
2663 }
2664
2665 /* We need to make a copy of the input string if we are going to modify it! */
2666 parse = ast_strdupa(data);
2667
2669
2670 if (ast_strlen_zero(args.conf_name)) {
2671 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
2672 ast_log(LOG_WARNING, "%s requires an argument (conference name[,options])\n", app);
2673 res = -1;
2674 goto confbridge_cleanup;
2675 }
2676
2677 if (strlen(args.conf_name) >= MAX_CONF_NAME) {
2678 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
2679 ast_log(LOG_WARNING, "%s does not accept conference names longer than %d\n", app, MAX_CONF_NAME - 1);
2680 res = -1;
2681 goto confbridge_cleanup;
2682 }
2683
2684 /* bridge profile name */
2685 if (args.argc > 1 && !ast_strlen_zero(args.b_profile_name)) {
2686 b_profile_name = args.b_profile_name;
2687 }
2688 if (!conf_find_bridge_profile(chan, b_profile_name, &user.b_profile)) {
2689 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
2690 ast_log(LOG_WARNING, "Conference bridge profile %s does not exist\n", b_profile_name ?
2691 b_profile_name : DEFAULT_BRIDGE_PROFILE);
2692 res = -1;
2693 goto confbridge_cleanup;
2694 }
2695
2696 /* user profile name */
2697 if (args.argc > 2 && !ast_strlen_zero(args.u_profile_name)) {
2698 u_profile_name = args.u_profile_name;
2699 }
2700 if (!conf_find_user_profile(chan, u_profile_name, &user.u_profile)) {
2701 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
2702 ast_log(LOG_WARNING, "Conference user profile %s does not exist\n", u_profile_name ?
2703 u_profile_name : DEFAULT_USER_PROFILE);
2704 res = -1;
2705 goto confbridge_cleanup;
2706 }
2707
2708 /* If channel hasn't been answered already, answer it, unless we're explicitly not supposed to */
2709 if ((ast_channel_state(chan) != AST_STATE_UP) && (ast_test_flag(&user.u_profile, USER_OPT_ANSWER_CHANNEL))) {
2710 ast_answer(chan);
2711 }
2712
2713 quiet = ast_test_flag(&user.u_profile, USER_OPT_QUIET);
2714
2715 /* ask for a PIN immediately after finding user profile. This has to be
2716 * prompted for requardless of quiet setting. */
2717 if (!ast_strlen_zero(user.u_profile.pin)) {
2718 if (conf_get_pin(chan, &user)) {
2719 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
2720 res = -1; /* invalid PIN */
2721 goto confbridge_cleanup;
2722 }
2723 }
2724
2725 /* See if we need them to record a intro name */
2726 if (!quiet &&
2729 if (conf_rec_name(&user, args.conf_name)) {
2730 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
2731 res = -1; /* Hangup during name recording */
2732 goto confbridge_cleanup;
2733 }
2734 }
2735
2736 /* menu name */
2737 if (args.argc > 3 && !ast_strlen_zero(args.menu_profile_name)) {
2738 menu_profile_name = args.menu_profile_name;
2739 }
2740
2741 if (conf_set_menu_to_user(chan, &user, menu_profile_name)) {
2742 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
2743 ast_log(LOG_WARNING, "Conference menu profile %s does not exist\n", menu_profile_name ?
2744 menu_profile_name : DEFAULT_MENU_PROFILE);
2745 res = -1;
2746 goto confbridge_cleanup;
2747 }
2748
2749 /* Set if DTMF should pass through for this user or not */
2750 if (ast_test_flag(&user.u_profile, USER_OPT_DTMF_PASS)) {
2751 user.features.dtmf_passthrough = 1;
2752 } else {
2753 user.features.dtmf_passthrough = 0;
2754 }
2755
2756 /* Set if text messaging is enabled for this user or not */
2757 if (ast_test_flag(&user.u_profile, USER_OPT_TEXT_MESSAGING)) {
2758 user.features.text_messaging = 1;
2759 } else {
2760 user.features.text_messaging = 0;
2761 }
2762
2763 /* Set dsp threshold values if present */
2764 if (user.u_profile.talking_threshold) {
2765 user.tech_args.talking_threshold = user.u_profile.talking_threshold;
2766 }
2767 if (user.u_profile.silence_threshold) {
2768 user.tech_args.silence_threshold = user.u_profile.silence_threshold;
2769 }
2770
2771 /* Set a talker indicate call back if talking detection is requested */
2772 if (ast_test_flag(&user.u_profile, USER_OPT_TALKER_DETECT)) {
2775 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
2776 res = -1;
2777 goto confbridge_cleanup;
2778 }
2779 }
2780
2781 /* Look for a conference bridge matching the provided name */
2782 if (!(conference = join_conference_bridge(args.conf_name, &user))) {
2783 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
2784 res = -1;
2785 goto confbridge_cleanup;
2786 }
2787
2788 /* Keep a copy of volume adjustments so we can restore them later if need be */
2789 volume_adjustments[0] = ast_audiohook_volume_get(chan, AST_AUDIOHOOK_DIRECTION_READ);
2790 volume_adjustments[1] = ast_audiohook_volume_get(chan, AST_AUDIOHOOK_DIRECTION_WRITE);
2791
2792 if (ast_test_flag(&user.u_profile, USER_OPT_DROP_SILENCE)) {
2793 user.tech_args.drop_silence = 1;
2794 }
2795
2796 if (ast_test_flag(&user.u_profile, USER_OPT_JITTERBUFFER)) {
2797 ast_func_write(chan, "JITTERBUFFER(adaptive)", "default");
2798 }
2799
2800 if (ast_test_flag(&user.u_profile, USER_OPT_DENOISE)) {
2801 ast_func_write(chan, "DENOISE(rx)", "on");
2802 }
2803
2804 /* if this user has a intro, play it before entering */
2805 if (!ast_strlen_zero(user.name_rec_location)) {
2807 play_sound_file(conference, user.name_rec_location);
2811 }
2812
2813 if (!quiet) {
2814 const char *join_sound = conf_get_sound(CONF_SOUND_JOIN, conference->b_profile.sounds);
2815
2816 /* if hear_own_join_sound is enabled play the Join sound to everyone */
2818 if (strcmp(conference->b_profile.language, ast_channel_language(chan))) {
2819 ast_stream_and_wait(chan, join_sound, "");
2821 play_sound_file(conference, join_sound);
2823 } else {
2824 async_play_sound_file(conference, join_sound, chan);
2825 }
2826 /* if hear_own_join_sound is disabled only play the Join sound to just the conference */
2827 } else {
2829 play_sound_file(conference, join_sound);
2831 }
2832 }
2833
2834 if (user.u_profile.timeout) {
2836 0,
2837 user.u_profile.timeout * 1000,
2839 NULL,
2840 NULL,
2842 }
2843
2844 /* See if we need to automatically set this user as a video source or not */
2846
2848
2849 join_hook_data = ast_malloc(sizeof(*join_hook_data));
2850 if (!join_hook_data) {
2851 res = -1;
2852 goto confbridge_cleanup;
2853 }
2854 join_hook_data->user = &user;
2855 join_hook_data->conference = conference;
2856 join_hook_data->hook_type = AST_BRIDGE_HOOK_TYPE_JOIN;
2858 join_hook_data, ast_free_ptr, 0);
2859 if (res) {
2860 ast_free(join_hook_data);
2861 ast_log(LOG_ERROR, "Couldn't add bridge join hook for channel '%s'\n", ast_channel_name(chan));
2862 goto confbridge_cleanup;
2863 }
2864
2865 leave_hook_data = ast_malloc(sizeof(*leave_hook_data));
2866 if (!leave_hook_data) {
2867 /* join_hook_data is cleaned up by ast_bridge_features_cleanup via the goto */
2868 res = -1;
2869 goto confbridge_cleanup;
2870 }
2871 leave_hook_data->user = &user;
2872 leave_hook_data->conference = conference;
2873 leave_hook_data->hook_type = AST_BRIDGE_HOOK_TYPE_LEAVE;
2875 leave_hook_data, ast_free_ptr, 0);
2876 if (res) {
2877 /* join_hook_data is cleaned up by ast_bridge_features_cleanup via the goto */
2878 ast_free(leave_hook_data);
2879 ast_log(LOG_ERROR, "Couldn't add bridge leave hook for channel '%s'\n", ast_channel_name(chan));
2880 goto confbridge_cleanup;
2881 }
2882
2883 if (ast_bridge_join_hook(&user.features, join_callback, NULL, NULL, 0)) {
2885 }
2886
2888 chan,
2889 NULL,
2890 &user.features,
2891 &user.tech_args,
2892 0);
2893
2894 /* This is a catch-all in case joining the bridge failed or for some reason
2895 * an async announcement got queued up and hasn't been told to play yet
2896 */
2898
2899 if (!user.kicked && ast_check_hangup(chan)) {
2900 pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "HANGUP");
2901 }
2902
2903 /* if we're shutting down, don't attempt to do further processing */
2904 if (ast_shutting_down()) {
2905 /*
2906 * Not taking any new calls at this time. We cannot create
2907 * the announcer channel if this is the first channel into
2908 * the conference and we certainly cannot create any
2909 * recording channel.
2910 */
2912 conference = NULL;
2913 goto confbridge_cleanup;
2914 }
2915
2916 /* If this user was a video source, we need to clean up and possibly pick a new source. */
2918
2919 /* if this user has a intro, play it when leaving */
2920 if (!quiet && !ast_strlen_zero(user.name_rec_location)) {
2921 async_play_sound_file(conference, user.name_rec_location, NULL);
2924 async_delete_name_rec(conference, user.name_rec_location);
2925 async_delete_task_pushed = 1;
2926 }
2927
2928 /* play the leave sound */
2929 if (!quiet) {
2930 const char *leave_sound = conf_get_sound(CONF_SOUND_LEAVE, conference->b_profile.sounds);
2931 async_play_sound_file(conference, leave_sound, NULL);
2932 }
2933
2934 /* If the user was kicked from the conference play back the audio prompt for it */
2935 if (!quiet && user.kicked) {
2936 res = ast_stream_and_wait(chan,
2938 "");
2939 }
2940
2941 /* Easy as pie, depart this channel from the conference bridge */
2943 conference = NULL;
2944
2945 /* Restore volume adjustments to previous values in case they were changed */
2946 if (volume_adjustments[0]) {
2947 ast_audiohook_volume_set(chan, AST_AUDIOHOOK_DIRECTION_READ, volume_adjustments[0]);
2948 }
2949 if (volume_adjustments[1]) {
2950 ast_audiohook_volume_set(chan, AST_AUDIOHOOK_DIRECTION_WRITE, volume_adjustments[1]);
2951 }
2952
2953confbridge_cleanup:
2954 if (!async_delete_task_pushed && !ast_strlen_zero(user.name_rec_location)) {
2955 ast_filedelete(user.name_rec_location, NULL);
2956 }
2959 return res;
2960}
2961
2963 struct confbridge_user *user,
2964 struct ast_bridge_channel *bridge_channel)
2965{
2966 int mute;
2967
2968 /* Toggle user level mute request. */
2969 mute = !user->muted;
2971
2972 return play_file(bridge_channel, NULL,
2975}
2976
2978 struct confbridge_user *user,
2979 struct ast_bridge_channel *bridge_channel)
2980{
2981 unsigned int binaural;
2982 ast_bridge_channel_lock_bridge(bridge_channel);
2983 binaural = !bridge_channel->binaural_suspended;
2984 bridge_channel->binaural_suspended = binaural;
2985 ast_bridge_unlock(bridge_channel->bridge);
2986 return play_file(bridge_channel, NULL, (binaural ?
2987 conf_get_sound(CONF_SOUND_BINAURAL_OFF, user->b_profile.sounds) :
2988 conf_get_sound(CONF_SOUND_BINAURAL_ON, user->b_profile.sounds))) < 0;
2989}
2990
2992{
2993 struct confbridge_user *cur_user = NULL;
2994 const char *sound_to_play;
2995 int mute;
2996
2998
2999 /* Toggle bridge level mute request. */
3000 mute = !conference->muted;
3001 conference->muted = mute;
3002
3004 if (!ast_test_flag(&cur_user->u_profile, USER_OPT_ADMIN)) {
3005 /* Set user level to bridge level mute request. */
3006 cur_user->muted = mute;
3007 conf_update_user_mute(cur_user);
3008 }
3009 }
3010
3012
3013 sound_to_play = conf_get_sound(
3016
3017 if (strcmp(conference->b_profile.language, ast_channel_language(user->chan))) {
3018 /* The host needs to hear it seperately, as they don't get the audio from play_sound_helper */
3019 ast_stream_and_wait(user->chan, sound_to_play, "");
3020
3021 /* Announce to the group that all participants are muted */
3023 play_sound_file(conference, sound_to_play);
3025 } else {
3026 /* Playing the sound asynchronously lets the sound be heard by everyone at once */
3027 async_play_sound_file(conference, sound_to_play, user->chan);
3028 }
3029
3030 return 0;
3031}
3032
3033static int action_playback(struct ast_bridge_channel *bridge_channel, const char *playback_file)
3034{
3035 char *file_copy = ast_strdupa(playback_file);
3036 char *file = NULL;
3037
3038 while ((file = ast_strsep(&file_copy, '&', AST_STRSEP_STRIP | AST_STRSEP_TRIM))) {
3039 if (ast_stream_and_wait(bridge_channel->chan, file, "")) {
3040 ast_log(LOG_WARNING, "Failed to playback file %s to channel\n", file);
3041 return -1;
3042 }
3043 }
3044 return 0;
3045}
3046
3048 struct confbridge_user *user,
3049 struct ast_bridge_channel *bridge_channel,
3050 struct conf_menu *menu,
3051 const char *playback_file,
3052 const char *cur_dtmf,
3053 int *stop_prompts)
3054{
3055 int i;
3056 int digit = 0;
3057 char dtmf[MAXIMUM_DTMF_FEATURE_STRING];
3058 struct conf_menu_entry new_menu_entry = { { 0, }, };
3059 char *file_copy = ast_strdupa(playback_file);
3060 char *file = NULL;
3061
3062 while ((file = ast_strsep(&file_copy, '&', AST_STRSEP_STRIP | AST_STRSEP_TRIM))) {
3063 if (ast_streamfile(bridge_channel->chan, file, ast_channel_language(bridge_channel->chan))) {
3064 ast_log(LOG_WARNING, "Failed to playback file %s to channel\n", file);
3065 return -1;
3066 }
3067
3068 /* now wait for more digits. */
3069 if (!(digit = ast_waitstream(bridge_channel->chan, AST_DIGIT_ANY))) {
3070 /* streaming finished and no DTMF was entered */
3071 continue;
3072 } else if (digit == -1) {
3073 /* error */
3074 return -1;
3075 } else {
3076 break; /* dtmf was entered */
3077 }
3078 }
3079 if (!digit) {
3080 /* streaming finished on all files and no DTMF was entered */
3081 return -1;
3082 }
3083 ast_stopstream(bridge_channel->chan);
3084
3085 /* If we get here, then DTMF has been entered, This means no
3086 * additional prompts should be played for this menu entry */
3087 *stop_prompts = 1;
3088
3089 /* If a digit was pressed during the payback, update
3090 * the dtmf string and look for a new menu entry in the
3091 * menu structure */
3092 ast_copy_string(dtmf, cur_dtmf, sizeof(dtmf));
3093 for (i = 0; i < (MAXIMUM_DTMF_FEATURE_STRING - 1); i++) {
3094 dtmf[i] = cur_dtmf[i];
3095 if (!dtmf[i]) {
3096 dtmf[i] = (char) digit;
3097 dtmf[i + 1] = '\0';
3098 i = -1;
3099 break;
3100 }
3101 }
3102 /* If i is not -1 then the new dtmf digit was _NOT_ added to the string.
3103 * If this is the case, no new DTMF sequence should be looked for. */
3104 if (i != -1) {
3105 return 0;
3106 }
3107
3108 if (conf_find_menu_entry_by_sequence(dtmf, menu, &new_menu_entry)) {
3109 execute_menu_entry(conference,
3110 user,
3111 bridge_channel,
3112 &new_menu_entry, menu);
3113 conf_menu_entry_destroy(&new_menu_entry);
3114 }
3115 return 0;
3116}
3117
3118static int action_kick_last(struct confbridge_conference *conference,
3119 struct ast_bridge_channel *bridge_channel,
3120 struct confbridge_user *user)
3121{
3122 struct confbridge_user *last_user = NULL;
3123 int isadmin = ast_test_flag(&user->u_profile, USER_OPT_ADMIN);
3124
3125 if (!isadmin) {
3126 play_file(bridge_channel, NULL,
3128 ast_log(LOG_WARNING, "Only admin users can use the kick_last menu action. Channel %s of conf %s is not an admin.\n",
3129 ast_channel_name(bridge_channel->chan),
3130 conference->name);
3131 return -1;
3132 }
3133
3135 last_user = AST_LIST_LAST(&conference->active_list);
3136 if (!last_user) {
3138 return 0;
3139 }
3140
3141 if (last_user == user || ast_test_flag(&last_user->u_profile, USER_OPT_ADMIN)) {
3143 play_file(bridge_channel, NULL,
3145 } else if (!last_user->kicked) {
3146 last_user->kicked = 1;
3147 pbx_builtin_setvar_helper(last_user->chan, "CONFBRIDGE_RESULT", "KICKED");
3150 }
3151
3152 return 0;
3153}
3154
3155static int action_dialplan_exec(struct ast_bridge_channel *bridge_channel, struct conf_menu_action *menu_action)
3156{
3157 struct ast_pbx_args args;
3158 struct ast_pbx *pbx;
3159 char *exten;
3160 char *context;
3161 int priority;
3162 int res;
3163
3164 memset(&args, 0, sizeof(args));
3165 args.no_hangup_chan = 1;
3166
3167 ast_channel_lock(bridge_channel->chan);
3168
3169 /*save off*/
3170 exten = ast_strdupa(ast_channel_exten(bridge_channel->chan));
3171 context = ast_strdupa(ast_channel_context(bridge_channel->chan));
3172 priority = ast_channel_priority(bridge_channel->chan);
3173 pbx = ast_channel_pbx(bridge_channel->chan);
3174 ast_channel_pbx_set(bridge_channel->chan, NULL);
3175
3176 /*set new*/
3177 ast_channel_exten_set(bridge_channel->chan, menu_action->data.dialplan_args.exten);
3178 ast_channel_context_set(bridge_channel->chan, menu_action->data.dialplan_args.context);
3179 ast_channel_priority_set(bridge_channel->chan, menu_action->data.dialplan_args.priority);
3180
3181 ast_channel_unlock(bridge_channel->chan);
3182
3183 /*execute*/
3184 res = ast_pbx_run_args(bridge_channel->chan, &args);
3185
3186 /*restore*/
3187 ast_channel_lock(bridge_channel->chan);
3188
3189 ast_channel_exten_set(bridge_channel->chan, exten);
3190 ast_channel_context_set(bridge_channel->chan, context);
3191 ast_channel_priority_set(bridge_channel->chan, priority);
3192 ast_channel_pbx_set(bridge_channel->chan, pbx);
3193
3194 ast_channel_unlock(bridge_channel->chan);
3195
3196 return res;
3197}
3198
3199static int execute_menu_entry(struct confbridge_conference *conference,
3200 struct confbridge_user *user,
3201 struct ast_bridge_channel *bridge_channel,
3202 struct conf_menu_entry *menu_entry,
3203 struct conf_menu *menu)
3204{
3205 struct conf_menu_action *menu_action;
3206 int isadmin = ast_test_flag(&user->u_profile, USER_OPT_ADMIN);
3207 int stop_prompts = 0;
3208 int res = 0;
3209
3210 AST_LIST_TRAVERSE(&menu_entry->actions, menu_action, action) {
3211 switch (menu_action->id) {
3213 res |= action_toggle_mute(conference, user, bridge_channel);
3214 break;
3216 action_toggle_binaural(conference, user, bridge_channel);
3217 break;
3219 if (!isadmin) {
3220 break;
3221 }
3223 break;
3225 announce_user_count(conference, user, bridge_channel);
3226 break;
3228 if (!stop_prompts) {
3229 res |= action_playback(bridge_channel, menu_action->data.playback_file);
3230 ast_test_suite_event_notify("CONF_MENU_PLAYBACK",
3231 "Message: %s\r\nChannel: %s",
3232 menu_action->data.playback_file, ast_channel_name(bridge_channel->chan));
3233 }
3234 break;
3237 break;
3240 break;
3244 break;
3248 break;
3252 break;
3256 break;
3258 if (!(stop_prompts)) {
3259 res |= action_playback_and_continue(conference,
3260 user,
3261 bridge_channel,
3262 menu,
3263 menu_action->data.playback_file,
3264 menu_entry->dtmf,
3265 &stop_prompts);
3266 }
3267 break;
3269 res |= action_dialplan_exec(bridge_channel, menu_action);
3270 break;
3272 if (!isadmin) {
3273 break;
3274 }
3275 conference->locked = (!conference->locked ? 1 : 0);
3276 res |= play_file(bridge_channel, NULL,
3279 conference->b_profile.sounds)) < 0;
3280 break;
3282 res |= action_kick_last(conference, bridge_channel, user);
3283 break;
3284 case MENU_ACTION_LEAVE:
3285 pbx_builtin_setvar_helper(bridge_channel->chan, "CONFBRIDGE_RESULT", "DTMF");
3286 ao2_lock(conference);
3287 ast_bridge_remove(conference->bridge, bridge_channel->chan);
3288 ast_test_suite_event_notify("CONF_MENU_LEAVE",
3289 "Channel: %s",
3290 ast_channel_name(bridge_channel->chan));
3291 ao2_unlock(conference);
3292 break;
3293 case MENU_ACTION_NOOP:
3294 break;
3296 ao2_lock(conference);
3297 if (!ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_SFU)) {
3298 ast_bridge_set_single_src_video_mode(conference->bridge, bridge_channel->chan);
3299 }
3300 ao2_unlock(conference);
3301 break;
3303 handle_video_on_exit(conference, bridge_channel->chan);
3304 break;
3305 }
3306 }
3307 return res;
3308}
3309
3310int conf_handle_dtmf(struct ast_bridge_channel *bridge_channel,
3311 struct confbridge_user *user,
3312 struct conf_menu_entry *menu_entry,
3313 struct conf_menu *menu)
3314{
3315 /* See if music on hold is playing */
3317
3318 /* execute the list of actions associated with this menu entry */
3319 execute_menu_entry(user->conference, user, bridge_channel, menu_entry, menu);
3320
3321 /* See if music on hold needs to be started back up again */
3323
3324 async_play_sound_ready(bridge_channel->chan);
3325
3326 return 0;
3327}
3328
3330 const char *channel)
3331{
3332 int res = -1;
3333 int match;
3334 struct confbridge_user *user = NULL;
3335 int all = !strcasecmp("all", channel);
3336 int participants = !strcasecmp("participants", channel);
3337
3338 SCOPED_AO2LOCK(bridge_lock, conference);
3339
3341 if (user->kicked) {
3342 continue;
3343 }
3344 match = !strcasecmp(channel, ast_channel_name(user->chan));
3345 if (match || all
3346 || (participants && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN))) {
3347 user->kicked = 1;
3348 pbx_builtin_setvar_helper(user->chan, "CONFBRIDGE_RESULT", "KICKED");
3350 res = 0;
3351 if (match) {
3352 return res;
3353 }
3354 }
3355 }
3357 if (user->kicked) {
3358 continue;
3359 }
3360 match = !strcasecmp(channel, ast_channel_name(user->chan));
3361 if (match || all
3362 || (participants && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN))) {
3363 user->kicked = 1;
3364 pbx_builtin_setvar_helper(user->chan, "CONFBRIDGE_RESULT", "KICKED");
3366 res = 0;
3367 if (match) {
3368 return res;
3369 }
3370 }
3371 }
3372
3373 return res;
3374}
3375
3376static char *complete_confbridge_name(const char *line, const char *word, int pos, int state)
3377{
3378 int which = 0;
3379 struct confbridge_conference *conference;
3380 char *res = NULL;
3381 int wordlen = strlen(word);
3382 struct ao2_iterator iter;
3383
3385 while ((conference = ao2_iterator_next(&iter))) {
3386 if (!strncasecmp(conference->name, word, wordlen) && ++which > state) {
3387 res = ast_strdup(conference->name);
3388 ao2_ref(conference, -1);
3389 break;
3390 }
3391 ao2_ref(conference, -1);
3392 }
3393 ao2_iterator_destroy(&iter);
3394
3395 return res;
3396}
3397
3398static char *complete_confbridge_participant(const char *conference_name, const char *line, const char *word, int pos, int state)
3399{
3400 int which = 0;
3401 RAII_VAR(struct confbridge_conference *, conference, NULL, ao2_cleanup);
3402 struct confbridge_user *user;
3403 char *res = NULL;
3404 int wordlen = strlen(word);
3405
3406 conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
3407 if (!conference) {
3408 return NULL;
3409 }
3410
3411 if (!strncasecmp("all", word, wordlen) && ++which > state) {
3412 return ast_strdup("all");
3413 }
3414
3415 if (!strncasecmp("participants", word, wordlen) && ++which > state) {
3416 return ast_strdup("participants");
3417 }
3418
3419 {
3420 SCOPED_AO2LOCK(bridge_lock, conference);
3422 if (!strncasecmp(ast_channel_name(user->chan), word, wordlen) && ++which > state) {
3423 res = ast_strdup(ast_channel_name(user->chan));
3424 return res;
3425 }
3426 }
3428 if (!strncasecmp(ast_channel_name(user->chan), word, wordlen) && ++which > state) {
3429 res = ast_strdup(ast_channel_name(user->chan));
3430 return res;
3431 }
3432 }
3433 }
3434
3435 return NULL;
3436}
3437
3438static char *handle_cli_confbridge_kick(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3439{
3440 struct confbridge_conference *conference;
3441 int not_found;
3442
3443 switch (cmd) {
3444 case CLI_INIT:
3445 e->command = "confbridge kick";
3446 e->usage =
3447 "Usage: confbridge kick <conference> <channel>\n"
3448 " Kicks a channel out of the conference bridge.\n"
3449 " (all to kick everyone, participants to kick non-admins).\n";
3450 return NULL;
3451 case CLI_GENERATE:
3452 if (a->pos == 2) {
3453 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
3454 }
3455 if (a->pos == 3) {
3456 return complete_confbridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
3457 }
3458 return NULL;
3459 }
3460
3461 if (a->argc != 4) {
3462 return CLI_SHOWUSAGE;
3463 }
3464
3465 conference = ao2_find(conference_bridges, a->argv[2], OBJ_KEY);
3466 if (!conference) {
3467 ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]);
3468 return CLI_SUCCESS;
3469 }
3470 not_found = kick_conference_participant(conference, a->argv[3]);
3471 ao2_ref(conference, -1);
3472 if (not_found) {
3473 if (!strcasecmp("all", a->argv[3]) || !strcasecmp("participants", a->argv[3])) {
3474 ast_cli(a->fd, "No participants found!\n");
3475 } else {
3476 ast_cli(a->fd, "No participant named '%s' found!\n", a->argv[3]);
3477 }
3478 return CLI_SUCCESS;
3479 }
3480 ast_cli(a->fd, "Kicked '%s' out of conference '%s'\n", a->argv[3], a->argv[2]);
3481 return CLI_SUCCESS;
3482}
3483
3484static void handle_cli_confbridge_list_item(struct ast_cli_args *a, struct confbridge_user *user, int waiting)
3485{
3486 char flag_str[6 + 1];/* Max flags + terminator */
3487 int pos = 0;
3488
3489 /* Build flags column string. */
3490 if (ast_test_flag(&user->u_profile, USER_OPT_ADMIN)) {
3491 flag_str[pos++] = 'A';
3492 }
3493 if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
3494 flag_str[pos++] = 'M';
3495 }
3496 if (ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED)) {
3497 flag_str[pos++] = 'W';
3498 }
3499 if (ast_test_flag(&user->u_profile, USER_OPT_ENDMARKED)) {
3500 flag_str[pos++] = 'E';
3501 }
3502 if (user->muted) {
3503 flag_str[pos++] = 'm';
3504 }
3505 if (waiting) {
3506 flag_str[pos++] = 'w';
3507 }
3508 flag_str[pos] = '\0';
3509
3510 ast_cli(a->fd, "%-30s %-6s %-16s %-16s %-16s %s\n",
3511 ast_channel_name(user->chan),
3512 flag_str,
3513 user->u_profile.name,
3514 user->conference->b_profile.name,
3515 user->menu_name,
3517 ast_channel_caller(user->chan)->id.number.str, "<unknown>"));
3518}
3519
3520static char *handle_cli_confbridge_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3521{
3522 struct confbridge_conference *conference;
3523
3524 switch (cmd) {
3525 case CLI_INIT:
3526 e->command = "confbridge list";
3527 e->usage =
3528 "Usage: confbridge list [<name>]\n"
3529 " Lists all currently active conference bridges or a specific conference bridge.\n"
3530 "\n"
3531 " When a conference bridge name is provided, flags may be shown for users. Below\n"
3532 " are the flags and what they represent.\n"
3533 "\n"
3534 " Flags:\n"
3535 " A - The user is an admin\n"
3536 " M - The user is a marked user\n"
3537 " W - The user must wait for a marked user to join\n"
3538 " E - The user will be kicked after the last marked user leaves the conference\n"
3539 " m - The user is muted\n"
3540 " w - The user is waiting for a marked user to join\n";
3541 return NULL;
3542 case CLI_GENERATE:
3543 if (a->pos == 2) {
3544 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
3545 }
3546 return NULL;
3547 }
3548
3549 if (a->argc == 2) {
3550 struct ao2_iterator iter;
3551
3552 ast_cli(a->fd, "Conference Bridge Name Users Marked Locked Muted\n");
3553 ast_cli(a->fd, "================================ ====== ====== ====== =====\n");
3555 while ((conference = ao2_iterator_next(&iter))) {
3556 ast_cli(a->fd, "%-32s %6u %6u %-6s %s\n",
3557 conference->name,
3558 conference->activeusers + conference->waitingusers,
3559 conference->markedusers,
3560 AST_CLI_YESNO(conference->locked),
3561 AST_CLI_YESNO(conference->muted));
3562 ao2_ref(conference, -1);
3563 }
3564 ao2_iterator_destroy(&iter);
3565 return CLI_SUCCESS;
3566 }
3567
3568 if (a->argc == 3) {
3569 struct confbridge_user *user;
3570
3572 if (!conference) {
3573 ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]);
3574 return CLI_SUCCESS;
3575 }
3576 ast_cli(a->fd, "Channel Flags User Profile Bridge Profile Menu CallerID\n");
3577 ast_cli(a->fd, "============================== ====== ================ ================ ================ ================\n");
3581 }
3584 }
3586 ao2_ref(conference, -1);
3587 return CLI_SUCCESS;
3588 }
3589
3590 return CLI_SHOWUSAGE;
3591}
3592
3593/*! \internal
3594 * \brief finds a conference by name and locks/unlocks.
3595 *
3596 * \retval 0 success
3597 * \retval -1 conference not found
3598 */
3599static int generic_lock_unlock_helper(int lock, const char *conference_name)
3600{
3601 struct confbridge_conference *conference;
3602 int res = 0;
3603
3604 conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
3605 if (!conference) {
3606 return -1;
3607 }
3608 ao2_lock(conference);
3609 conference->locked = lock;
3610 ast_test_suite_event_notify("CONF_LOCK", "Message: conference %s\r\nConference: %s", conference->locked ? "locked" : "unlocked", conference->b_profile.name);
3611 ao2_unlock(conference);
3612 ao2_ref(conference, -1);
3613
3614 return res;
3615}
3616
3617/*! \internal
3618 * \brief finds a conference user by channel name and mutes/unmutes them.
3619 *
3620 * \retval 0 success
3621 * \retval -1 conference not found
3622 * \retval -2 user not found
3623 */
3624static int generic_mute_unmute_helper(int mute, const char *conference_name,
3625 const char *chan_name)
3626{
3627 RAII_VAR(struct confbridge_conference *, conference, NULL, ao2_cleanup);
3628 struct confbridge_user *user;
3629 int all = !strcasecmp("all", chan_name);
3630 int participants = !strcasecmp("participants", chan_name);
3631 int res = -2;
3632
3633 conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
3634 if (!conference) {
3635 return -1;
3636 }
3637
3638 {
3639 SCOPED_AO2LOCK(bridge_lock, conference);
3641 int match = !strncasecmp(chan_name, ast_channel_name(user->chan),
3642 strlen(chan_name));
3643 if (match || all
3644 || (participants && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN))) {
3646 res = 0;
3647 if (match) {
3648 return res;
3649 }
3650 }
3651 }
3652
3654 int match = !strncasecmp(chan_name, ast_channel_name(user->chan),
3655 strlen(chan_name));
3656 if (match || all
3657 || (participants && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN))) {
3659 res = 0;
3660 if (match) {
3661 return res;
3662 }
3663 }
3664 }
3665 }
3666
3667 return res;
3668}
3669
3670static int cli_mute_unmute_helper(int mute, struct ast_cli_args *a)
3671{
3672 int res = generic_mute_unmute_helper(mute, a->argv[2], a->argv[3]);
3673
3674 if (res == -1) {
3675 ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]);
3676 return -1;
3677 } else if (res == -2) {
3678 if (!strcasecmp("all", a->argv[3]) || !strcasecmp("participants", a->argv[3])) {
3679 ast_cli(a->fd, "No participants found in conference %s\n", a->argv[2]);
3680 } else {
3681 ast_cli(a->fd, "No channel named '%s' found in conference %s\n", a->argv[3], a->argv[2]);
3682 }
3683 return -1;
3684 }
3685 ast_cli(a->fd, "%s %s from confbridge %s\n", mute ? "Muting" : "Unmuting", a->argv[3], a->argv[2]);
3686 return 0;
3687}
3688
3689static char *handle_cli_confbridge_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3690{
3691 switch (cmd) {
3692 case CLI_INIT:
3693 e->command = "confbridge mute";
3694 e->usage =
3695 "Usage: confbridge mute <conference> <channel>\n"
3696 " Mute a channel in a conference.\n"
3697 " (all to mute everyone, participants to mute non-admins)\n"
3698 " If the specified channel is a prefix,\n"
3699 " the action will be taken on the first\n"
3700 " matching channel.\n";
3701 return NULL;
3702 case CLI_GENERATE:
3703 if (a->pos == 2) {
3704 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
3705 }
3706 if (a->pos == 3) {
3707 return complete_confbridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
3708 }
3709 return NULL;
3710 }
3711 if (a->argc != 4) {
3712 return CLI_SHOWUSAGE;
3713 }
3714
3716
3717 return CLI_SUCCESS;
3718}
3719
3720static char *handle_cli_confbridge_unmute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3721{
3722 switch (cmd) {
3723 case CLI_INIT:
3724 e->command = "confbridge unmute";
3725 e->usage =
3726 "Usage: confbridge unmute <conference> <channel>\n"
3727 " Unmute a channel in a conference.\n"
3728 " (all to unmute everyone, participants to unmute non-admins)\n"
3729 " If the specified channel is a prefix,\n"
3730 " the action will be taken on the first\n"
3731 " matching channel.\n";
3732 return NULL;
3733 case CLI_GENERATE:
3734 if (a->pos == 2) {
3735 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
3736 }
3737 if (a->pos == 3) {
3738 return complete_confbridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
3739 }
3740 return NULL;
3741 }
3742 if (a->argc != 4) {
3743 return CLI_SHOWUSAGE;
3744 }
3745
3747
3748 return CLI_SUCCESS;
3749}
3750
3751static char *handle_cli_confbridge_lock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3752{
3753 switch (cmd) {
3754 case CLI_INIT:
3755 e->command = "confbridge lock";
3756 e->usage =
3757 "Usage: confbridge lock <conference>\n"
3758 " Lock a conference. While locked, no new non-admins\n"
3759 " may join the conference.\n";
3760 return NULL;
3761 case CLI_GENERATE:
3762 if (a->pos == 2) {
3763 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
3764 }
3765 return NULL;
3766 }
3767 if (a->argc != 3) {
3768 return CLI_SHOWUSAGE;
3769 }
3770 if (generic_lock_unlock_helper(1, a->argv[2])) {
3771 ast_cli(a->fd, "Conference %s is not found\n", a->argv[2]);
3772 } else {
3773 ast_cli(a->fd, "Conference %s is locked.\n", a->argv[2]);
3774 }
3775 return CLI_SUCCESS;
3776}
3777
3778static char *handle_cli_confbridge_unlock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3779{
3780 switch (cmd) {
3781 case CLI_INIT:
3782 e->command = "confbridge unlock";
3783 e->usage =
3784 "Usage: confbridge unlock <conference>\n"
3785 " Unlock a previously locked conference.\n";
3786 return NULL;
3787 case CLI_GENERATE:
3788 if (a->pos == 2) {
3789 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
3790 }
3791 return NULL;
3792 }
3793 if (a->argc != 3) {
3794 return CLI_SHOWUSAGE;
3795 }
3796 if (generic_lock_unlock_helper(0, a->argv[2])) {
3797 ast_cli(a->fd, "Conference %s is not found\n", a->argv[2]);
3798 } else {
3799 ast_cli(a->fd, "Conference %s is unlocked.\n", a->argv[2]);
3800 }
3801 return CLI_SUCCESS;
3802}
3803
3804static char *handle_cli_confbridge_start_record(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3805{
3806 const char *rec_file = NULL;
3807 struct confbridge_conference *conference;
3808
3809 switch (cmd) {
3810 case CLI_INIT:
3811 e->command = "confbridge record start";
3812 e->usage =
3813 "Usage: confbridge record start <conference> <file>\n"
3814 " <file> is optional, Otherwise the bridge profile\n"
3815 " record file will be used. If the bridge profile\n"
3816 " has no record file specified, a file will automatically\n"
3817 " be generated in the monitor directory\n";
3818 return NULL;
3819 case CLI_GENERATE:
3820 if (a->pos == 3) {
3821 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
3822 }
3823 return NULL;
3824 }
3825 if (a->argc < 4) {
3826 return CLI_SHOWUSAGE;
3827 }
3828 if (a->argc == 5) {
3829 rec_file = a->argv[4];
3830 }
3831
3832 conference = ao2_find(conference_bridges, a->argv[3], OBJ_KEY);
3833 if (!conference) {
3834 ast_cli(a->fd, "Conference not found.\n");
3835 return CLI_FAILURE;
3836 }
3837 ao2_lock(conference);
3838 if (conf_is_recording(conference)) {
3839 ast_cli(a->fd, "Conference is already being recorded.\n");
3840 ao2_unlock(conference);
3841 ao2_ref(conference, -1);
3842 return CLI_SUCCESS;
3843 }
3844 if (!ast_strlen_zero(rec_file)) {
3845 ast_copy_string(conference->b_profile.rec_file, rec_file, sizeof(conference->b_profile.rec_file));
3846 }
3847
3848 if (conf_start_record(conference)) {
3849 ast_cli(a->fd, "Could not start recording due to internal error.\n");
3850 ao2_unlock(conference);
3851 ao2_ref(conference, -1);
3852 return CLI_FAILURE;
3853 }
3854 ao2_unlock(conference);
3855
3856 ast_cli(a->fd, "Recording started\n");
3857 ao2_ref(conference, -1);
3858 return CLI_SUCCESS;
3859}
3860
3861static char *handle_cli_confbridge_stop_record(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3862{
3863 struct confbridge_conference *conference;
3864 int ret;
3865
3866 switch (cmd) {
3867 case CLI_INIT:
3868 e->command = "confbridge record stop";
3869 e->usage =
3870 "Usage: confbridge record stop <conference>\n"
3871 " Stop a previously started recording.\n";
3872 return NULL;
3873 case CLI_GENERATE:
3874 if (a->pos == 3) {
3875 return complete_confbridge_name(a->line, a->word, a->pos, a->n);
3876 }
3877 return NULL;
3878 }
3879 if (a->argc != 4) {
3880 return CLI_SHOWUSAGE;
3881 }
3882
3883 conference = ao2_find(conference_bridges, a->argv[3], OBJ_KEY);
3884 if (!conference) {
3885 ast_cli(a->fd, "Conference not found.\n");
3886 return CLI_SUCCESS;
3887 }
3888 ao2_lock(conference);
3889 ret = conf_stop_record(conference);
3890 ao2_unlock(conference);
3891 ast_cli(a->fd, "Recording %sstopped.\n", ret ? "could not be " : "");
3892 ao2_ref(conference, -1);
3893 return CLI_SUCCESS;
3894}
3895
3897 AST_CLI_DEFINE(handle_cli_confbridge_list, "List conference bridges and participants."),
3898 AST_CLI_DEFINE(handle_cli_confbridge_kick, "Kick participants out of conference bridges."),
3899 AST_CLI_DEFINE(handle_cli_confbridge_mute, "Mute participants."),
3900 AST_CLI_DEFINE(handle_cli_confbridge_unmute, "Unmute participants."),
3901 AST_CLI_DEFINE(handle_cli_confbridge_lock, "Lock a conference."),
3902 AST_CLI_DEFINE(handle_cli_confbridge_unlock, "Unlock a conference."),
3903 AST_CLI_DEFINE(handle_cli_confbridge_start_record, "Start recording a conference"),
3904 AST_CLI_DEFINE(handle_cli_confbridge_stop_record, "Stop recording a conference."),
3905};
3907 .name = "CONFBRIDGE",
3908 .write = func_confbridge_helper,
3909};
3910
3911static int func_confbridge_info(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len);
3913 .name = "CONFBRIDGE_INFO",
3914 .read = func_confbridge_info,
3915};
3916
3917static int func_confbridge_channels(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
3918{
3919 char *parse, *outbuf;
3920 struct confbridge_conference *conference;
3921 struct confbridge_user *user;
3922 int bytes, count = 0;
3923 size_t outlen;
3926 AST_APP_ARG(confno);
3927 );
3928
3929 /* parse all the required arguments and make sure they exist. */
3930 if (ast_strlen_zero(data)) {
3931 return -1;
3932 }
3933 parse = ast_strdupa(data);
3935 if (ast_strlen_zero(args.confno) || ast_strlen_zero(args.type)) {
3936 ast_log(LOG_WARNING, "Usage: %s(category,confno)", cmd);
3937 return -1;
3938 }
3940 if (!conference) {
3941 ast_debug(1, "No such conference: %s\n", args.confno);
3942 return -1;
3943 }
3944
3945 outbuf = buf;
3946 outlen = len;
3947
3949 if (!strcasecmp(args.type, "parties")) {
3951 bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
3952 outbuf += bytes;
3953 outlen -= bytes;
3954 }
3956 bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
3957 outbuf += bytes;
3958 outlen -= bytes;
3959 }
3960 } else if (!strcasecmp(args.type, "active")) {
3962 bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
3963 outbuf += bytes;
3964 outlen -= bytes;
3965 }
3966 } else if (!strcasecmp(args.type, "waiting")) {
3968 bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
3969 outbuf += bytes;
3970 outlen -= bytes;
3971 }
3972 } else if (!strcasecmp(args.type, "admins")) {
3974 if (ast_test_flag(&user->u_profile, USER_OPT_ADMIN)) {
3975 bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
3976 outbuf += bytes;
3977 outlen -= bytes;
3978 }
3979 }
3980 } else if (!strcasecmp(args.type, "marked")) {
3982 if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
3983 bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
3984 outbuf += bytes;
3985 outlen -= bytes;
3986 }
3987 }
3988 } else {
3989 ast_log(LOG_ERROR, "Invalid keyword '%s' passed to %s.\n", args.type, cmd);
3990 }
3992 ao2_ref(conference, -1);
3993 return 0;
3994}
3995
3997 .name = "CONFBRIDGE_CHANNELS",
3999};
4000
4001static int action_confbridgelist_item(struct mansession *s, const char *id_text, struct confbridge_conference *conference, struct confbridge_user *user, int waiting)
4002{
4003 struct ast_channel_snapshot *snapshot;
4004 struct ast_str *snap_str;
4005
4007 if (!snapshot) {
4008 return 0;
4009 }
4010
4011 snap_str = ast_manager_build_channel_state_string(snapshot);
4012 if (!snap_str) {
4013 ao2_ref(snapshot, -1);
4014 return 0;
4015 }
4016
4017 astman_append(s,
4018 "Event: ConfbridgeList\r\n"
4019 "%s"
4020 "Conference: %s\r\n"
4021 "Admin: %s\r\n"
4022 "MarkedUser: %s\r\n"
4023 "WaitMarked: %s\r\n"
4024 "EndMarked: %s\r\n"
4025 "EndMarkedAny: %s\r\n"
4026 "Waiting: %s\r\n"
4027 "Muted: %s\r\n"
4028 "Talking: %s\r\n"
4029 "AnsweredTime: %d\r\n"
4030 "%s"
4031 "\r\n",
4032 id_text,
4033 conference->name,
4039 AST_YESNO(waiting),
4040 AST_YESNO(user->muted),
4041 AST_YESNO(user->talking),
4043 ast_str_buffer(snap_str));
4044
4045 ast_free(snap_str);
4046 ao2_ref(snapshot, -1);
4047
4048 return 1;
4049}
4050
4051static int action_confbridgelist(struct mansession *s, const struct message *m)
4052{
4053 const char *actionid = astman_get_header(m, "ActionID");
4054 const char *conference_name = astman_get_header(m, "Conference");
4055 struct confbridge_user *user;
4056 struct confbridge_conference *conference;
4057 char id_text[80];
4058 int total = 0;
4059
4060 id_text[0] = '\0';
4061 if (!ast_strlen_zero(actionid)) {
4062 snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", actionid);
4063 }
4064 if (ast_strlen_zero(conference_name)) {
4065 astman_send_error(s, m, "No Conference name provided.");
4066 return 0;
4067 }
4069 astman_send_error(s, m, "No active conferences.");
4070 return 0;
4071 }
4072 conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
4073 if (!conference) {
4074 astman_send_error(s, m, "No Conference by that name found.");
4075 return 0;
4076 }
4077
4078 astman_send_listack(s, m, "Confbridge user list will follow", "start");
4079
4080 ao2_lock(conference);
4081 AST_LIST_TRAVERSE(&conference->active_list, user, list) {
4082 total += action_confbridgelist_item(s, id_text, conference, user, 0);
4083 }
4084 AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
4085 total += action_confbridgelist_item(s, id_text, conference, user, 1);
4086 }
4087 ao2_unlock(conference);
4088 ao2_ref(conference, -1);
4089
4090 astman_send_list_complete_start(s, m, "ConfbridgeListComplete", total);
4092
4093 return 0;
4094}
4095
4096static int action_confbridgelistrooms(struct mansession *s, const struct message *m)
4097{
4098 const char *actionid = astman_get_header(m, "ActionID");
4099 struct confbridge_conference *conference;
4100 struct ao2_iterator iter;
4101 char id_text[512] = "";
4102 int totalitems = 0;
4103
4104 if (!ast_strlen_zero(actionid)) {
4105 snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", actionid);
4106 }
4107
4109 astman_send_error(s, m, "No active conferences.");
4110 return 0;
4111 }
4112
4113 astman_send_listack(s, m, "Confbridge conferences will follow", "start");
4114
4115 /* Traverse the conference list */
4117 while ((conference = ao2_iterator_next(&iter))) {
4118 totalitems++;
4119
4120 ao2_lock(conference);
4121 astman_append(s,
4122 "Event: ConfbridgeListRooms\r\n"
4123 "%s"
4124 "Conference: %s\r\n"
4125 "Parties: %u\r\n"
4126 "Marked: %u\r\n"
4127 "Locked: %s\r\n"
4128 "Muted: %s\r\n"
4129 "\r\n",
4130 id_text,
4131 conference->name,
4132 conference->activeusers + conference->waitingusers,
4133 conference->markedusers,
4134 AST_YESNO(conference->locked),
4135 AST_YESNO(conference->muted));
4136 ao2_unlock(conference);
4137
4138 ao2_ref(conference, -1);
4139 }
4140 ao2_iterator_destroy(&iter);
4141
4142 /* Send final confirmation */
4143 astman_send_list_complete_start(s, m, "ConfbridgeListRoomsComplete", totalitems);
4145 return 0;
4146}
4147
4148static int action_mute_unmute_helper(struct mansession *s, const struct message *m, int mute)
4149{
4150 const char *conference_name = astman_get_header(m, "Conference");
4151 const char *channel_name = astman_get_header(m, "Channel");
4152 int res = 0;
4153
4154 if (ast_strlen_zero(conference_name)) {
4155 astman_send_error(s, m, "No Conference name provided.");
4156 return 0;
4157 }
4158 if (ast_strlen_zero(channel_name)) {
4159 astman_send_error(s, m, "No channel name provided.");
4160 return 0;
4161 }
4163 astman_send_error(s, m, "No active conferences.");
4164 return 0;
4165 }
4166
4167 res = generic_mute_unmute_helper(mute, conference_name, channel_name);
4168
4169 if (res == -1) {
4170 astman_send_error(s, m, "No Conference by that name found.");
4171 return 0;
4172 } else if (res == -2) {
4173 astman_send_error(s, m, "No Channel by that name found in Conference.");
4174 return 0;
4175 }
4176
4177 astman_send_ack(s, m, mute ? "User muted" : "User unmuted");
4178 return 0;
4179}
4180
4181static int action_confbridgeunmute(struct mansession *s, const struct message *m)
4182{
4183 return action_mute_unmute_helper(s, m, 0);
4184}
4185static int action_confbridgemute(struct mansession *s, const struct message *m)
4186{
4187 return action_mute_unmute_helper(s, m, 1);
4188}
4189
4190static int action_lock_unlock_helper(struct mansession *s, const struct message *m, int lock)
4191{
4192 const char *conference_name = astman_get_header(m, "Conference");
4193 int res = 0;
4194
4195 if (ast_strlen_zero(conference_name)) {
4196 astman_send_error(s, m, "No Conference name provided.");
4197 return 0;
4198 }
4200 astman_send_error(s, m, "No active conferences.");
4201 return 0;
4202 }
4203 if ((res = generic_lock_unlock_helper(lock, conference_name))) {
4204 astman_send_error(s, m, "No Conference by that name found.");
4205 return 0;
4206 }
4207 astman_send_ack(s, m, lock ? "Conference locked" : "Conference unlocked");
4208 return 0;
4209}
4210static int action_confbridgeunlock(struct mansession *s, const struct message *m)
4211{
4212 return action_lock_unlock_helper(s, m, 0);
4213}
4214static int action_confbridgelock(struct mansession *s, const struct message *m)
4215{
4216 return action_lock_unlock_helper(s, m, 1);
4217}
4218
4219static int action_confbridgekick(struct mansession *s, const struct message *m)
4220{
4221 const char *conference_name = astman_get_header(m, "Conference");
4222 const char *channel = astman_get_header(m, "Channel");
4223 struct confbridge_conference *conference;
4224 int found;
4225
4226 if (ast_strlen_zero(conference_name)) {
4227 astman_send_error(s, m, "No Conference name provided.");
4228 return 0;
4229 }
4231 astman_send_error(s, m, "No active conferences.");
4232 return 0;
4233 }
4234
4235 conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
4236 if (!conference) {
4237 astman_send_error(s, m, "No Conference by that name found.");
4238 return 0;
4239 }
4240
4241 found = !kick_conference_participant(conference, channel);
4242 ao2_ref(conference, -1);
4243
4244 if (found) {
4245 astman_send_ack(s, m, !strcmp("all", channel) ? "All participants kicked" : "User kicked");
4246 } else {
4247 astman_send_error(s, m, "No Channel by that name found in Conference.");
4248 }
4249 return 0;
4250}
4251
4252static int action_confbridgestartrecord(struct mansession *s, const struct message *m)
4253{
4254 const char *conference_name = astman_get_header(m, "Conference");
4255 const char *recordfile = astman_get_header(m, "RecordFile");
4256 struct confbridge_conference *conference;
4257
4258 if (ast_strlen_zero(conference_name)) {
4259 astman_send_error(s, m, "No Conference name provided.");
4260 return 0;
4261 }
4263 astman_send_error(s, m, "No active conferences.");
4264 return 0;
4265 }
4266
4267 conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
4268 if (!conference) {
4269 astman_send_error(s, m, "No Conference by that name found.");
4270 return 0;
4271 }
4272
4273 ao2_lock(conference);
4274 if (conf_is_recording(conference)) {
4275 astman_send_error(s, m, "Conference is already being recorded.");
4276 ao2_unlock(conference);
4277 ao2_ref(conference, -1);
4278 return 0;
4279 }
4280
4281 if (!ast_strlen_zero(recordfile)) {
4282 ast_copy_string(conference->b_profile.rec_file, recordfile, sizeof(conference->b_profile.rec_file));
4283 }
4284
4285 if (conf_start_record(conference)) {
4286 astman_send_error(s, m, "Internal error starting conference recording.");
4287 ao2_unlock(conference);
4288 ao2_ref(conference, -1);
4289 return 0;
4290 }
4291 ao2_unlock(conference);
4292
4293 ao2_ref(conference, -1);
4294 astman_send_ack(s, m, "Conference Recording Started.");
4295 return 0;
4296}
4297static int action_confbridgestoprecord(struct mansession *s, const struct message *m)
4298{
4299 const char *conference_name = astman_get_header(m, "Conference");
4300 struct confbridge_conference *conference;
4301
4302 if (ast_strlen_zero(conference_name)) {
4303 astman_send_error(s, m, "No Conference name provided.");
4304 return 0;
4305 }
4307 astman_send_error(s, m, "No active conferences.");
4308 return 0;
4309 }
4310
4311 conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
4312 if (!conference) {
4313 astman_send_error(s, m, "No Conference by that name found.");
4314 return 0;
4315 }
4316
4317 ao2_lock(conference);
4318 if (conf_stop_record(conference)) {
4319 ao2_unlock(conference);
4320 astman_send_error(s, m, "Internal error while stopping recording.");
4321 ao2_ref(conference, -1);
4322 return 0;
4323 }
4324 ao2_unlock(conference);
4325
4326 ao2_ref(conference, -1);
4327 astman_send_ack(s, m, "Conference Recording Stopped.");
4328 return 0;
4329}
4330
4331static int action_confbridgesetsinglevideosrc(struct mansession *s, const struct message *m)
4332{
4333 const char *conference_name = astman_get_header(m, "Conference");
4334 const char *channel = astman_get_header(m, "Channel");
4335 struct confbridge_user *user;
4336 struct confbridge_conference *conference;
4337
4338 if (ast_strlen_zero(conference_name)) {
4339 astman_send_error(s, m, "No Conference name provided.");
4340 return 0;
4341 }
4342 if (ast_strlen_zero(channel)) {
4343 astman_send_error(s, m, "No channel name provided.");
4344 return 0;
4345 }
4347 astman_send_error(s, m, "No active conferences.");
4348 return 0;
4349 }
4350
4351 conference = ao2_find(conference_bridges, conference_name, OBJ_KEY);
4352 if (!conference) {
4353 astman_send_error(s, m, "No Conference by that name found.");
4354 return 0;
4355 }
4356
4357 /* find channel and set as video src. */
4358 ao2_lock(conference);
4359 AST_LIST_TRAVERSE(&conference->active_list, user, list) {
4360 if (!strncmp(channel, ast_channel_name(user->chan), strlen(channel))) {
4362 break;
4363 }
4364 }
4365 ao2_unlock(conference);
4366 ao2_ref(conference, -1);
4367
4368 /* do not access user after conference unlock. We are just
4369 * using this check to see if it was found or not */
4370 if (!user) {
4371 astman_send_error(s, m, "No channel by that name found in conference.");
4372 return 0;
4373 }
4374 astman_send_ack(s, m, "Conference single video source set.");
4375 return 0;
4376}
4377
4378static int func_confbridge_info(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
4379{
4380 char *parse;
4381 struct confbridge_conference *conference;
4382 struct confbridge_user *user;
4383 int count = 0;
4386 AST_APP_ARG(confno);
4387 );
4388
4389 /* parse all the required arguments and make sure they exist. */
4390 if (ast_strlen_zero(data)) {
4391 return -1;
4392 }
4393 parse = ast_strdupa(data);
4395 if (ast_strlen_zero(args.confno) || ast_strlen_zero(args.type)) {
4396 return -1;
4397 }
4399 if (!conference) {
4400 snprintf(buf, len, "0");
4401 return 0;
4402 }
4403
4404 /* get the correct count for the type requested */
4406 if (!strcasecmp(args.type, "parties")) {
4408 count++;
4409 }
4411 count++;
4412 }
4413 } else if (!strcasecmp(args.type, "admins")) {
4415 if (ast_test_flag(&user->u_profile, USER_OPT_ADMIN)) {
4416 count++;
4417 }
4418 }
4419 } else if (!strcasecmp(args.type, "marked")) {
4421 if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
4422 count++;
4423 }
4424 }
4425 } else if (!strcasecmp(args.type, "locked")) {
4426 count = conference->locked;
4427 } else if (!strcasecmp(args.type, "muted")) {
4428 count = conference->muted;
4429 } else {
4430 ast_log(LOG_ERROR, "Invalid keyword '%s' passed to CONFBRIDGE_INFO.\n", args.type);
4431 }
4432 snprintf(buf, len, "%d", count);
4434 ao2_ref(conference, -1);
4435 return 0;
4436}
4437
4438static int confkick_exec(struct ast_channel *chan, const char *data)
4439{
4440 char *parse;
4441 struct confbridge_conference *conference;
4442 int not_found;
4443
4445 AST_APP_ARG(confbridge);
4446 AST