diff --git a/Makefile b/Makefile index 76e68c4162..02d6fa55da 100644 --- a/Makefile +++ b/Makefile @@ -456,7 +456,7 @@ doc/core-en_US.xml: makeopts $(foreach dir,$(MOD_SUBDIRS),$(shell $(GREP) -l "la @echo "" >> $@ @for x in $(MOD_SUBDIRS); do \ printf "$$x " ; \ - for i in $$x/*.c; do \ + for i in `find $$x -name *.c`; do \ $(AWK) -f build_tools/get_documentation $$i >> $@ ; \ done ; \ done diff --git a/apps/app_skel.c b/apps/app_skel.c index 6fdbebbf26..8a5b5bc750 100644 --- a/apps/app_skel.c +++ b/apps/app_skel.c @@ -86,6 +86,51 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") from. It shows you the basic structure to create your own Asterisk applications. + + + + + Options that apply globally to app_skel + + The number of games a single execution of SkelGuessNumber will play + + + Should the computer cheat? + If enabled, the computer will ignore winning guesses. + + + + Prompts for SkelGuessNumber to play + + A prompt directing the user to enter a number less than the max number + + + The sound file to play when a wrong guess is made + + + The sound file to play when a correct guess is made + + + The sound file to play when a guess is too low + + + The sound file to play when a guess is too high + + + The sound file to play when a player loses + + + + Defined levels for the SkelGuessNumber game + + The maximum in the range of numbers to guess (1 is the implied minimum) + + + The maximum number of guesses before a game is considered lost + + + + ***/ static char *app = "SkelGuessNumber"; @@ -197,6 +242,7 @@ static void *skel_level_find(struct ao2_container *tmp_container, const char *ca /*! \brief An aco_type structure to link the "general" category to the skel_global_config type */ static struct aco_type global_option = { .type = ACO_GLOBAL, + .name = "globals", .item_offset = offsetof(struct skel_config, global), .category_match = ACO_WHITELIST, .category = "^general$", @@ -207,6 +253,7 @@ struct aco_type *global_options[] = ACO_TYPES(&global_option); /*! \brief An aco_type structure to link the "sounds" category to the skel_global_config type */ static struct aco_type sound_option = { .type = ACO_GLOBAL, + .name = "sounds", .item_offset = offsetof(struct skel_config, global), .category_match = ACO_WHITELIST, .category = "^sounds$", @@ -217,6 +264,7 @@ struct aco_type *sound_options[] = ACO_TYPES(&sound_option); /*! \brief An aco_type structure to link the everything but the "general" and "sounds" categories to the skel_level type */ static struct aco_type level_option = { .type = ACO_ITEM, + .name = "level", .category_match = ACO_BLACKLIST, .category = "^(general|sounds)$", .item_alloc = skel_level_alloc, diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c index c915b184c2..6c5ea0bb8e 100644 --- a/apps/confbridge/conf_config_parser.c +++ b/apps/confbridge/conf_config_parser.c @@ -40,6 +40,464 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stringfields.h" #include "asterisk/pbx.h" + +/*** DOCUMENTATION + + Conference Bridge Application + + + Unused, but reserved. + + + A named profile to apply to specific callers. + Callers in a ConfBridge have a profile associated with them + that determine their options. A configuration section is determined to be a + user_profile when the type parameter has a value + of user. + + + Define this configuration category as a user profile. + The type parameter determines how a context in the + configuration file is interpreted. + + Configure the context as a user_profile + Configure the context as a bridge_profile + Configure the context as a menu + + + + + Sets if the user is an admin or not + + + Sets if this is a marked user or not + + + Sets if all users should start out muted + + + Play MOH when user is alone or waiting on a marked user + + + Silence enter/leave prompts and user intros for this user + + + Sets if the number of users should be announced to the user + + + Announce user count to all the other users when this user joins + Sets if the number of users should be announced to all the other users + in the conference when this user joins. This option can be either set to 'yes' or + a number. When set to a number, the announcement will only occur once the user + count is above the specified number. + + + + Announce to a user when they join an empty conference + + + Sets if the user must wait for a marked user to enter before joining a conference + + + Kick the user from the conference when the last marked user leaves + + + Set whether or not notifications of when a user begins and ends talking should be sent out as events over AMI + + + Sets whether or not DTMF should pass through the conference + + + Prompt user for their name when joining a conference and play it to the conference when they enter + + + Sets a PIN the user must enter before joining the conference + + + The MOH class to use for this user + + + Sound file to play to the user when they join a conference + + + Apply a denoise filter to the audio before mixing + Sets whether or not a denoise filter should be applied + to the audio before mixing or not. Off by default. Requires + codec_speex to be built and installed. Do not confuse this option + with drop_silence. Denoise is useful if there is a lot of background + noise for a user as it attempts to remove the noise while preserving + the speech. This option does NOT remove silence from being mixed into + the conference and does come at the cost of a slight performance hit. + + + + Drop what Asterisk detects as silence from audio sent to the bridge + + This option drops what Asterisk detects as silence from + entering into the bridge. Enabling this option will drastically + improve performance and help remove the buildup of background + noise from the conference. Highly recommended for large conferences + due to its performance enhancements. + + + + The number of milliseconds of detected silence necessary to trigger silence detection + + The time in milliseconds of sound falling within the what + the dsp has established as baseline silence before a user + is considered be silent. This value affects several + operations and should not be changed unless the impact + on call quality is fully understood. + What this value affects internally: + + 1. When talk detection AMI events are enabled, this value + determines when the user has stopped talking after a + period of talking. If this value is set too low + AMI events indicating the user has stopped talking + may get falsely sent out when the user briefly pauses + during mid sentence. + + + 2. The drop_silence option depends on this value to + determine when the user's audio should begin to be + dropped from the conference bridge after the user + stops talking. If this value is set too low the user's + audio stream may sound choppy to the other participants. + This is caused by the user transitioning constantly from + silence to talking during mid sentence. + + + The best way to approach this option is to set it slightly above + the maximum amount of ms of silence a user may generate during + natural speech. + + By default this value is 2500ms. Valid values are 1 through 2^31. + + + + The number of milliseconds of detected non-silence necessary to triger talk detection + + The time in milliseconds of sound above what the dsp has + established as base line silence for a user before a user + is considered to be talking. This value affects several + operations and should not be changed unless the impact on + call quality is fully understood. + + What this value affects internally: + + + 1. Audio is only mixed out of a user's incoming audio stream + if talking is detected. If this value is set too + loose the user will hear themselves briefly each + time they begin talking until the dsp has time to + establish that they are in fact talking. + + + 2. When talk detection AMI events are enabled, this value + determines when talking has begun which results in + an AMI event to fire. If this value is set too tight + AMI events may be falsely triggered by variants in + room noise. + + + 3. The drop_silence option depends on this value to determine + when the user's audio should be mixed into the bridge + after periods of silence. If this value is too loose + the beginning of a user's speech will get cut off as they + transition from silence to talking. + + By default this value is 160 ms. Valid values are 1 through 2^31 + + + + Place a jitter buffer on the user's audio stream before audio mixing is performed + + Enabling this option places a jitterbuffer on the user's audio stream + before audio mixing is performed. This is highly recommended but will + add a slight delay to the audio. This option is using the JITTERBUFFER + dialplan function's default adaptive jitterbuffer. For a more fine tuned + jitterbuffer, disable this option and use the JITTERBUFFER dialplan function + on the user before entering the ConfBridge application. + + + + When using the CONFBRIDGE dialplan function, use a user profile as a template for creating a new temporary profile + + + + A named profile to apply to specific bridges. + ConfBridge bridges have a profile associated with them + that determine their options. A configuration section is determined to be a + bridge_profile when the type parameter has a value + of bridge. + + + Define this configuration category as a bridge profile + The type parameter determines how a context in the + configuration file is interpreted. + + Configure the context as a user_profile + Configure the context as a bridge_profile + Configure the context as a menu + + + + + Place a jitter buffer on the conference's audio stream + + + Set the internal native sample rate for mixing the conference + + Sets the internal native sample rate the + conference is mixed at. This is set to automatically + adjust the sample rate to the best quality by default. + Other values can be anything from 8000-192000. If a + sample rate is set that Asterisk does not support, the + closest sample rate Asterisk does support to the one requested + will be used. + + + + Sets the internal mixing interval in milliseconds for the bridge + + Sets the internal mixing interval in milliseconds for the bridge. This + number reflects how tight or loose the mixing will be for the conference. + In order to improve performance a larger mixing interval such as 40ms may + be chosen. Using a larger mixing interval comes at the cost of introducing + larger amounts of delay into the bridge. Valid values here are 10, 20, 40, + or 80. + + + + Record the conference starting with the first active user's entrance and ending with the last active user's exit + + Records the conference call starting when the first user + enters the room, and ending when the last user exits the room. + The default recorded filename is + 'confbridge-${name of conference bridge}-${start time}.wav + and the default format is 8khz slinear. This file will be + located in the configured monitoring directory in asterisk.conf. + + + + The filename of the conference recording + + When record_conference is set to yes, the specific name of the + record file can be set using this option. Note that since multiple + conferences may use the same bridge profile, this may cause issues + depending on the configuration. It is recommended to only use this + option dynamically with the CONFBRIDGE() dialplan function. This + allows the record name to be specified and a unique name to be chosen. + By default, the record_file is stored in Asterisk's spool/monitor directory + with a unique filename starting with the 'confbridge' prefix. + + + + Sets how confbridge handles video distribution to the conference participants + + Sets how confbridge handles video distribution to the conference participants. + Note that participants wanting to view and be the source of a video feed + _MUST_ be sharing the same video codec. Also, using video in conjunction with + with the jitterbuffer currently results in the audio being slightly out of sync + with the video. This is a result of the jitterbuffer only working on the audio + stream. It is recommended to disable the jitterbuffer when video is used. + + + No video sources are set by default in the conference. It is still + possible for a user to be set as a video source via AMI or DTMF action + at any time. + + + The video feed will follow whoever is talking and providing video. + + + The last marked user to join the conference with video capabilities + will be the single source of video distributed to all participants. + If multiple marked users are capable of video, the last one to join + is always the source, when that user leaves it goes to the one who + joined before them. + + + The first marked user to join the conference with video capabilities + is the single source of video distribution among all participants. If + that user leaves, the marked user to join after them becomes the source. + + + + + + Limit the maximum number of participants for a single conference + + This option limits the number of participants for a single + conference to a specific number. By default conferences + have no participant limit. After the limit is reached, the + conference will be locked until someone leaves. Note however + that an Admin user will always be alowed to join the conference + regardless if this limit is reached or not. + + + + Override the various conference bridge sound files + + All sounds in the conference are customizable using the bridge profile options below. + Simply state the option followed by the filename or full path of the filename after + the option. Example: sound_had_joined=conf-hasjoin This will play the conf-hasjoin + sound file found in the sounds directory when announcing someone's name is joining the + conference. + + The sound played to everyone when someone enters the conference. + The sound played to everyone when someone leaves the conference. + The sound played before announcing someone's name has + joined the conference. This is used for user intros. + Example "_____ has joined the conference" + The sound played when announcing someone's name has + left the conference. This is used for user intros. + Example "_____ has left the conference" + The sound played to a user who has been kicked from the conference. + The sound played when the mute option it toggled on. + The sound played when the mute option it toggled off. + The sound played when the user is the only person in the conference. + The sound played to a user when there is only one other + person is in the conference. + The sound played when announcing how many users there + are in a conference. + This file is used in conjunction with sound_there_are + when announcing how many users there are in the conference. + The sounds are stringed together like this. + "sound_there_are" ${number of participants} "sound_other_in_party" + The sound played when someone is placed into the conference + after waiting for a marked user. + The sound played when a user is placed into a conference that + can not start until a marked user enters. + The sound played when the last marked user leaves the conference. + The sound played when prompting for a conference pin number. + The sound played when an invalid pin is entered too many times. + The sound played to a user trying to join a locked conference. + The sound played to an admin after toggling the conference to locked mode. + The sound played to an admin after toggling the conference to unlocked mode. + The sound played when an invalid menu option is entered. + + + + + When using the CONFBRIDGE dialplan function, use a bridge profile as a template for creating a new temporary profile + + + + A conference user menu + + Conference users, as defined by a conf_user, + can have a DTMF menu assigned to their profile when they enter the + ConfBridge application. + + + Define this configuration category as a menu + The type parameter determines how a context in the + configuration file is interpreted. + + Configure the context as a user_profile + Configure the context as a bridge_profile + Configure the context as a menu + + + + + DTMF sequences to assign various confbridge actions to + --- ConfBridge Menu Options --- + The ConfBridge application also has the ability to apply custom DTMF menus to + each channel using the application. Like the User and Bridge profiles a menu + is passed in to ConfBridge as an argument in the dialplan. + Below is a list of menu actions that can be assigned to a DTMF sequence. + + A single DTMF sequence can have multiple actions associated with it. This is + accomplished by stringing the actions together and using a , as the + delimiter. Example: Both listening and talking volume is reset when 5 is + pressed. 5=reset_talking_volume, reset_listening_volume + + + playback will play back an audio file to a channel + and then immediately return to the conference. + This file can not be interupted by DTMF. + Multiple files can be chained together using the + & character. + + playback_and_continue will + play back a prompt while continuing to + collect the dtmf sequence. This is useful + when using a menu prompt that describes all + the menu options. Note however that any DTMF + during this action will terminate the prompts + playback. Prompt files can be chained together + using the & character as a delimiter. + + Toggle turning on and off mute. Mute will make the user silent + to everyone else, but the user will still be able to listen in. + continue to collect the dtmf sequence. + + This action does nothing (No Operation). Its only real purpose exists for + being able to reserve a sequence in the config as a menu exit sequence. + + Decreases the channel's listening volume. + + Increases the channel's listening volume. + + Reset channel's listening volume to default level. + + Decreases the channel's talking volume. + + Increases the channel's talking volume. + + Reset channel's talking volume to default level. + + The dialplan_exec action allows a user + to escape from the conference and execute + commands in the dialplan. Once the dialplan + exits the user will be put back into the + conference. The possibilities are endless! + + This action allows a user to exit the conference and continue + execution in the dialplan. + + This action allows an Admin to kick the last participant from the + conference. This action will only work for admins which allows + a single menu to be used for both users and admins. + + This action allows an Admin to toggle locking and + unlocking the conference. Non admins can not use + this action even if it is in their menu. + + This action allows any user to set themselves as the + single video source distributed to all participants. + This will make the video feed stick to them regardless + of what the video_mode is set to. + + This action allows a user to release themselves as + the video source. If video_mode is not set to none + this action will result in the conference returning to + whatever video mode the bridge profile is using. + Note that this action will have no effect if the user + is not currently the video source. Also, the user is + not guaranteed by using this action that they will not + become the video source again. The bridge will return + to whatever operation the video_mode option is set to + upon release of the video src. + + This action allows an administrator to toggle the mute + state for all non-admins within a conference. All + admin users are unaffected by this option. Note that all + users, regardless of their admin status, are notified + that the conference is muted. + + This action plays back the number of participants currently + in a conference + + + + + + +***/ + struct confbridge_cfg { struct ao2_container *bridge_profiles; struct ao2_container *user_profiles; @@ -81,6 +539,7 @@ static void *bridge_profile_find(struct ao2_container *container, const char *ca static struct aco_type bridge_type = { .type = ACO_ITEM, + .name = "bridge_profile", .category_match = ACO_BLACKLIST, .category = "^general$", .matchfield = "type", @@ -117,6 +576,7 @@ static void *user_profile_find(struct ao2_container *container, const char *cate static struct aco_type user_type = { .type = ACO_ITEM, + .name = "user_profile", .category_match = ACO_BLACKLIST, .category = "^general$", .matchfield = "type", @@ -147,6 +607,7 @@ static void *menu_find(struct ao2_container *container, const char *category) static struct aco_type menu_type = { .type = ACO_ITEM, + .name = "menu", .category_match = ACO_BLACKLIST, .category = "^general$", .matchfield = "type", @@ -164,6 +625,7 @@ static struct aco_type *user_types[] = ACO_TYPES(&user_type); /* The general category is reserved, but unused */ static struct aco_type general_type = { .type = ACO_GLOBAL, + .name = "global", .category_match = ACO_WHITELIST, .category = "^general$", }; @@ -1293,8 +1755,6 @@ int conf_load_config(int reload) /* User options */ aco_option_register(&cfg_info, "type", ACO_EXACT, user_types, NULL, OPT_NOOP_T, 0, 0); - aco_option_register(&cfg_info, "type", ACO_EXACT, bridge_types, NULL, OPT_NOOP_T, 0, 0); - aco_option_register(&cfg_info, "type", ACO_EXACT, menu_types, NULL, OPT_NOOP_T, 0, 0); aco_option_register(&cfg_info, "admin", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ADMIN); aco_option_register(&cfg_info, "marked", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_MARKEDUSER); aco_option_register(&cfg_info, "startmuted", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_STARTMUTED); @@ -1321,6 +1781,7 @@ int conf_load_config(int reload) aco_option_register_custom(&cfg_info, "template", ACO_EXACT, user_types, NULL, user_template_handler, 0); /* Bridge options */ + aco_option_register(&cfg_info, "type", ACO_EXACT, bridge_types, NULL, OPT_NOOP_T, 0, 0); aco_option_register(&cfg_info, "jitterbuffer", ACO_EXACT, bridge_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), USER_OPT_JITTERBUFFER); /* "auto" will fail to parse as a uint, but we use PARSE_DEFAULT to set the value to 0 in that case, which is the value that auto resolves to */ aco_option_register(&cfg_info, "internal_sample_rate", ACO_EXACT, bridge_types, "0", OPT_UINT_T, PARSE_DEFAULT, FLDSET(struct bridge_profile, internal_sample_rate), 0); @@ -1334,6 +1795,7 @@ int conf_load_config(int reload) aco_option_register_custom(&cfg_info, "template", ACO_EXACT, bridge_types, NULL, bridge_template_handler, 0); /* Menu options */ + aco_option_register(&cfg_info, "type", ACO_EXACT, menu_types, NULL, OPT_NOOP_T, 0, 0); aco_option_register_custom(&cfg_info, "^[0-9A-D*#]+$", ACO_REGEX, menu_types, NULL, menu_option_handler, 0); if (aco_process_config(&cfg_info, reload) == ACO_PROCESS_ERROR) { diff --git a/channels/chan_motif.c b/channels/chan_motif.c index cc1e370fd1..4dbfb8e3a7 100644 --- a/channels/chan_motif.c +++ b/channels/chan_motif.c @@ -77,6 +77,145 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/abstract_jb.h" #include "asterisk/xmpp.h" +/*** DOCUMENTATION + + Jingle Channel Driver + + Transports + There are three different transports and protocol derivatives + supported by chan_motif. They are in order of + preference: Jingle using ICE-UDP, Google Jingle, and Google-V1. + Jingle as defined in XEP-0166 supports the widest range of + features. It is referred to as ice-udp. This is + the specification that Jingle clients implement. + Google Jingle follows the Jingle specification for signaling + but uses a custom transport for media. It is supported by the + Google Talk Plug-in in Gmail and by some other Jingle clients. It + is referred to as google in this file. + Google-V1 is the original Google Talk signaling protocol + which uses an initial preliminary version of Jingle. It also uses + the same custom transport as Google Jingle for media. It is + supported by Google Voice, some other Jingle clients, and the + Windows Google Talk client. It is referred to as google-v1 + in this file. + Incoming sessions will automatically switch to the correct + transport once it has been determined. + Outgoing sessions are capable of determining if the target + is capable of Jingle or a Google transport if the target is in the + roster. Unfortunately it is not possible to differentiate between + a Google Jingle or Google-V1 capable resource until a session + initiate attempt occurs. If a resource is determined to use a + Google transport it will initially use Google Jingle but will fall + back to Google-V1 if required. + If an outgoing session attempt fails due to failure to + support the given transport chan_motif will + fall back in preference order listed previously until all + transports have been exhausted. + Dialing and Resource Selection Strategy + Placing a call through an endpoint can be accomplished using the + following dial string: + Motif/[endpoint name]/[target] + When placing an outgoing call through an endpoint the requested + target is searched for in the roster list. If present the first Jingle + or Google Jingle capable resource is specifically targeted. Since the + capabilities of the resource are known the outgoing session initiation + will disregard the configured transport and use the determined one. + If the target is not found in the roster the target will be used + as-is and a session will be initiated using the transport specified + in this configuration file. If no transport has been specified the + endpoint defaults to ice-udp. + Video Support + Support for video does not need to be explicitly enabled. + Configuring any video codec on your endpoint will automatically enable + it. + DTMF + The only supported method for DTMF is RFC2833. This is always + enabled on audio streams and negotiated if possible. + Incoming Calls + Incoming calls will first look for the extension matching the + name of the endpoint in the configured context. If no such extension + exists the call will automatically fall back to the s extension. + CallerID + The incoming caller id number is populated with the username of + the caller and the name is populated with the full identity of the + caller. If you would like to perform authentication or filtering + of incoming calls it is recommended that you use these fields to do so. + Outgoing caller id can not be set. + + Multiple endpoints using the + same connection is NOT supported. Doing so + may result in broken calls. + + + + + The configuration for an endpoint. + + Default dialplan context that incoming sessions will be routed to + + + A callgroup to assign to this endpoint. + + + A pickup group to assign to this endpoint. + + + The default language for this endpoint. + + + Default music on hold class for this endpoint. + + + Default parking lot for this endpoint. + + + Accout code for CDR purposes + + + Codecs to allow + + + Codecs to disallow + + + Connection to accept traffic on and on which to send traffic out + + + The transport to use for the endpoint. + + The default outbound transport for this endpoint. Inbound + messages are inferred. Allowed transports are ice-udp, + google, or google-v1. Note + that chan_motif will fall back to transport + preference order if the transport value chosen here fails. + + + The Jingle protocol, as defined in XEP 0166. + + + The Google Jingle protocol, which follows the Jingle + specification for signaling but uses a custom transport for + media. + + + Google-V1 is the original Google Talk signaling + protocol which uses an initial preliminary version of Jingle. + It also uses the same custom transport as google for media. + + + + + + Maximum number of ICE candidates to offer + + + Maximum number of pyaloads to offer + + + + +***/ + /*! \brief Default maximum number of ICE candidates we will offer */ #define DEFAULT_MAX_ICE_CANDIDATES "10" @@ -406,6 +545,7 @@ static int jingle_endpoint_cmp(void *obj, void *arg, int flags) static struct aco_type endpoint_option = { .type = ACO_ITEM, + .name = "endpoint", .category_match = ACO_BLACKLIST, .category = "^general$", .item_alloc = jingle_endpoint_alloc, diff --git a/configs/motif.conf.sample b/configs/motif.conf.sample index ae3ab30d72..9d5bc808c9 100644 --- a/configs/motif.conf.sample +++ b/configs/motif.conf.sample @@ -75,18 +75,25 @@ context=incoming-motif ; Default context that incoming sessions will land in ;maxpayloads = 30 ; Maximum number of payloads we will offer ; Sample configuration entry for Jingle -[jingle-endpoint](default) -transport=ice-udp ; Change the default protocol of outgoing sessions to Jingle ICE-UDP -allow=g722 ; Add G.722 as an allowed format since the other side may support it -connection=local-jabber-account ; Connection to accept traffic on and send traffic out -accountcode=jingle ; Account code for CDR purposes +;[jingle-endpoint](default) +;transport=ice-udp ; Change the default protocol of outgoing sessions to Jingle ICE-UDP +;allow=g722 ; Add G.722 as an allowed format since the other side may support it +;connection=local-jabber-account ; Connection to accept traffic on and send traffic out +;accountcode=jingle ; Account code for CDR purposes ; Sample configuration entry for Google Talk [gtalk-endpoint](default) -transport=google ; Since this is a Google Talk endpoint we want to offer Google Jingle for outgoing sessions -connection=gtalk-account +;transport=google ; Since this is a Google Talk endpoint we want to offer Google Jingle for outgoing sessions +;connection=gtalk-account ; Sample configuration entry for Google Voice -[gvoice](default) -transport=google-v1 ; Google Voice uses the original Google Talk protocol -connection=gvoice-account +;[gvoice](default) +;transport=google-v1 ; Google Voice uses the original Google Talk protocol +;connection=gvoice-account + +; Additional options +; callgroup +; pickupgroup +; language +; musicclass +; parkinglot diff --git a/configs/xmpp.conf.sample b/configs/xmpp.conf.sample index a838568678..dad0f79ef2 100644 --- a/configs/xmpp.conf.sample +++ b/configs/xmpp.conf.sample @@ -37,3 +37,6 @@ ;sendtodialplan=yes ; Send incoming messages into the dialplan. Off by default. ;context=messages ; Dialplan context to send incoming messages to. If not set, ; "default" will be used. +;forceoldssl=no ; Force the use of old-style SSL. +;keepalive= + diff --git a/doc/appdocsxml.dtd b/doc/appdocsxml.dtd index 561e3d38cd..a475cd32f9 100644 --- a/doc/appdocsxml.dtd +++ b/doc/appdocsxml.dtd @@ -1,13 +1,13 @@ - + - @@ -39,6 +39,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -52,7 +76,7 @@ - + @@ -91,7 +115,7 @@ - + diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index cded90ef7d..7e1ef13ec4 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -49,6 +49,7 @@ int ast_ssl_init(void); /*!< Provided by ssl.c */ int ast_test_init(void); /*!< Provided by test.c */ int ast_msg_init(void); /*!< Provided by message.c */ void ast_msg_shutdown(void); /*!< Provided by message.c */ +int aco_init(void); /*!< Provided by config_options.c */ /*! * \brief Reload asterisk modules. diff --git a/include/asterisk/config_options.h b/include/asterisk/config_options.h index 1b0beb206a..64d8d50893 100644 --- a/include/asterisk/config_options.h +++ b/include/asterisk/config_options.h @@ -109,6 +109,7 @@ typedef int (*aco_matchvalue_func)(const char *text); struct aco_type { /* common stuff */ enum aco_type_t type; /*!< Whether this is a global or item type */ + const char *name; /*!< The name of this type (must match XML documentation) */ const char *category; /*!< A regular expression for matching categories to be allowed or denied */ const char *matchfield; /*!< An option name to match for this type (i.e. a 'type'-like column) */ const char *matchvalue; /*!< The value of the option to require for matching (i.e. 'peer' for type= in sip.conf) */ @@ -202,6 +203,14 @@ static struct aco_info name = { \ __VA_ARGS__ \ }; +#define CONFIG_INFO_CORE(mod, name, arr, alloc, ...) \ +static struct aco_info name = { \ + .module = mod, \ + .global_obj = &arr, \ + .snapshot_alloc = alloc, \ + __VA_ARGS__ \ +}; + /*! \brief Initialize an aco_info structure * \note aco_info_destroy must be called if this succeeds * \param info The address of an aco_info struct to initialize diff --git a/include/asterisk/xml.h b/include/asterisk/xml.h index ddfcc25d90..063e8c0b39 100644 --- a/include/asterisk/xml.h +++ b/include/asterisk/xml.h @@ -23,6 +23,7 @@ struct ast_xml_node; struct ast_xml_doc; +struct ast_xml_xpath_results; /*! * \brief Initialize the XML library implementation. @@ -207,6 +208,44 @@ struct ast_xml_node *ast_xml_node_get_parent(struct ast_xml_node *node); * \brief Dump the specified document to a file. */ int ast_xml_doc_dump_file(FILE *output, struct ast_xml_doc *doc); +/*! + * \brief Free the XPath results + * \param results The XPath results object to dispose of + * + * \since 12 + */ +void ast_xml_xpath_results_free(struct ast_xml_xpath_results *results); + +/*! + * \brief Return the number of results from an XPath query + * \param results The XPath results object to count + * \retval The number of results in the XPath object + * + * \since 12 + */ +int ast_xml_xpath_num_results(struct ast_xml_xpath_results *results); + +/*! + * \brief Return the first result node of an XPath query + * \param results The XPath results object to get the first result from + * \retval The first result in the XPath object on success + * \retval NULL on error + * + * \since 12 + */ +struct ast_xml_node *ast_xml_xpath_get_first_result(struct ast_xml_xpath_results *results); + +/*! + * \brief Execute an XPath query on an XML document + * \param doc The XML document to query + * \param xpath_str The XPath query string to execute on the document + * \retval An object containing the results of the XPath query on success + * \retval NULL on failure + * + * \since 12 + */ +struct ast_xml_xpath_results *ast_xml_query(struct ast_xml_doc *doc, const char *xpath_str); + /* Features using ast_xml_ */ #ifdef HAVE_LIBXML2 #define AST_XML_DOCS diff --git a/include/asterisk/xmldoc.h b/include/asterisk/xmldoc.h index 9bf647612f..c09f693c88 100644 --- a/include/asterisk/xmldoc.h +++ b/include/asterisk/xmldoc.h @@ -35,6 +35,7 @@ enum ast_doc_src { #ifdef AST_XML_DOCS struct ao2_container; +struct ast_xml_node; /*! \brief Struct that contains the XML documentation for a particular item. Note * that this is an ao2 ref counted object. @@ -61,11 +62,27 @@ struct ast_xml_doc_item { AST_STRING_FIELD(name); /*! The type of the item */ AST_STRING_FIELD(type); + /*! Reference to another field */ + AST_STRING_FIELD(ref); ); + /*! The node that this item was created from. Note that the life time of + * the node is not tied to the lifetime of this object. + */ + struct ast_xml_node *node; /*! The next XML documentation item that matches the same name/item type */ struct ast_xml_doc_item *next; }; +/*! \brief Execute an XPath query on the loaded XML documentation + * \param query The XPath query string to execute + * \param ... Variable printf style format arguments + * \retval An XPath results object on success + * \retval NULL if no match found + * + * \since 12 + */ +struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_query(const char *fmt, ...); + /*! * \brief Get the syntax for a specified application or function. * \param type Application, Function or AGI ? @@ -138,6 +155,17 @@ char *ast_xmldoc_build_description(const char *type, const char *name, const cha */ struct ao2_container *ast_xmldoc_build_documentation(const char *type); +/*! + * \brief Regenerate the documentation for a particular item + * \param item The documentation item to regenerate + * + * \retval -1 on error + * \retval 0 on success + * + * \since 12 + */ +int ast_xmldoc_regenerate_doc_item(struct ast_xml_doc_item *item); + #endif /* AST_XML_DOCS */ #endif /* _ASTERISK_XMLDOC_H */ diff --git a/main/asterisk.c b/main/asterisk.c index 243321dda3..1d53719389 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -4170,6 +4170,8 @@ int main(int argc, char *argv[]) ast_xmldoc_load_documentation(); #endif + aco_init(); + if (astdb_init()) { printf("%s", term_quit()); exit(1); diff --git a/main/config_options.c b/main/config_options.c index 5e76a7a7be..7a65cc5553 100644 --- a/main/config_options.c +++ b/main/config_options.c @@ -31,11 +31,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include +#include "asterisk/_private.h" #include "asterisk/config.h" #include "asterisk/config_options.h" #include "asterisk/stringfields.h" #include "asterisk/acl.h" #include "asterisk/frame.h" +#include "asterisk/xmldoc.h" +#include "asterisk/cli.h" +#include "asterisk/term.h" #ifdef LOW_MEMORY #define CONFIG_OPT_BUCKETS 5 @@ -70,6 +74,26 @@ struct aco_option { intptr_t args[0]; }; +#ifdef AST_XML_DOCS +static struct ao2_container *xmldocs; +#endif /* AST_XML_DOCS */ + +/*! \brief Value of the aco_option_type enum as strings */ +static char *aco_option_type_string[] = { + "ACL", /* OPT_ACL_T, */ + "Boolean", /* OPT_BOOL_T, */ + "Boolean", /* OPT_BOOLFLAG_T, */ + "String", /* OPT_CHAR_ARRAY_T, */ + "Codec", /* OPT_CODEC_T, */ + "Custom", /* OPT_CUSTOM_T, */ + "Double", /* OPT_DOUBLE_T, */ + "Integer", /* OPT_INT_T, */ + "None", /* OPT_NOOP_T, */ + "IP Address", /* OPT_SOCKADDR_T, */ + "String", /* OPT_STRINGFIELD_T, */ + "Unsigned Integer", /* OPT_UINT_T, */ +}; + void *aco_pending_config(struct aco_info *info) { if (!(info && info->internal)) { @@ -100,6 +124,11 @@ static int codec_handler_fn(const struct aco_option *opt, struct ast_variable *v static int noop_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj); static int chararray_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj); +#ifdef AST_XML_DOCS +static int xmldoc_update_config_type(const char *module, const char *name, const char *category, const char *matchfield, const char *matchvalue, unsigned int matches); +static int xmldoc_update_config_option(struct aco_type **types, const char *module, const char *name, const char *object_name, const char *default_value, unsigned int regex, enum aco_option_type type); +#endif + static aco_option_handler ast_config_option_default_handler(enum aco_option_type type) { switch(type) { @@ -142,7 +171,7 @@ static regex_t *build_regex(const char *text) return regex; } -static int link_option_to_types(struct aco_type **types, struct aco_option *opt) +static int link_option_to_types(struct aco_info *info, struct aco_type **types, struct aco_option *opt) { size_t idx = 0; struct aco_type *type; @@ -152,7 +181,11 @@ static int link_option_to_types(struct aco_type **types, struct aco_option *opt) ast_log(LOG_ERROR, "Attempting to register option using uninitialized type\n"); return -1; } - if (!ao2_link(type->internal->opts, opt)) { + if (!ao2_link(type->internal->opts, opt) +#ifdef AST_XML_DOCS + || xmldoc_update_config_option(types, info->module, opt->name, type->name, opt->default_val, opt->match_type == ACO_REGEX, opt->type) +#endif /* AST_XML_DOCS */ + ) { while (--idx) { ao2_unlink(types[idx]->internal->opts, opt); } @@ -181,7 +214,7 @@ int aco_option_register_deprecated(struct aco_info *info, const char *name, stru opt->deprecated = 1; opt->match_type = ACO_EXACT; - if (link_option_to_types(types, opt)) { + if (link_option_to_types(info, types, opt)) { ao2_ref(opt, -1); return -1; } @@ -189,6 +222,53 @@ int aco_option_register_deprecated(struct aco_info *info, const char *name, stru return 0; } +#ifdef AST_XML_DOCS +/*! \internal + * \brief Find a particular ast_xml_doc_item from it's parent config_info, types, and name + */ +static struct ast_xml_doc_item *find_xmldoc_option(struct ast_xml_doc_item *config_info, struct aco_type **types, const char *name) +{ + struct ast_xml_doc_item *iter = config_info; + + if (!iter) { + return NULL; + } + /* First is just the configInfo, we can skip it */ + while ((iter = iter->next)) { + size_t x; + if (strcasecmp(iter->name, name)) { + continue; + } + for (x = 0; types[x]; x++) { + /* All we care about is that at least one type has the option */ + if (!strcasecmp(types[x]->name, iter->ref)) { + return iter; + } + } + } + return NULL; +} + +/*! \internal + * \brief Find a particular ast_xml_doc_item from it's parent config_info and name + */ +static struct ast_xml_doc_item *find_xmldoc_type(struct ast_xml_doc_item *config_info, const char *name) +{ + struct ast_xml_doc_item *iter = config_info; + if (!iter) { + return NULL; + } + /* First is just the config Info, skip it */ + while ((iter = iter->next)) { + if (!strcasecmp(iter->type, "configObject") && !strcasecmp(iter->name, name)) { + break; + } + } + return iter; +} + +#endif /* AST_XML_DOCS */ + int __aco_option_register(struct aco_info *info, const char *name, enum aco_matchtype matchtype, struct aco_type **types, const char *default_val, enum aco_option_type kind, aco_option_handler handler, unsigned int flags, size_t argc, ...) { @@ -235,7 +315,7 @@ int __aco_option_register(struct aco_info *info, const char *name, enum aco_matc return -1; }; - if (link_option_to_types(types, opt)) { + if (link_option_to_types(info, types, opt)) { ao2_ref(opt, -1); return -1; } @@ -535,10 +615,10 @@ try_alias: } if (res != ACO_PROCESS_OK) { - goto end; + goto end; } - if (info->pre_apply_config && (info->pre_apply_config())) { + if (info->pre_apply_config && (info->pre_apply_config())) { res = ACO_PROCESS_ERROR; goto end; } @@ -648,18 +728,26 @@ static int internal_type_init(struct aco_type *type) int aco_info_init(struct aco_info *info) { - size_t x, y; + size_t x = 0, y = 0; + struct aco_file *file; + struct aco_type *type; if (!(info->internal = ast_calloc(1, sizeof(*info->internal)))) { return -1; } - for (x = 0; info->files[x]; x++) { - for (y = 0; info->files[x]->types[y]; y++) { - if (internal_type_init(info->files[x]->types[y])) { + while ((file = info->files[x++])) { + while ((type = file->types[y++])) { + if (internal_type_init(type)) { goto error; } +#ifdef AST_XML_DOCS + if (xmldoc_update_config_type(info->module, type->name, type->category, type->matchfield, type->matchvalue, type->category_match == ACO_WHITELIST)) { + goto error; + } +#endif /* AST_XML_DOCS */ } + y = 0; } return 0; @@ -714,6 +802,428 @@ int aco_set_defaults(struct aco_type *type, const char *category, void *obj) return 0; } +#ifdef AST_XML_DOCS + +/*! \internal + * \brief Complete the name of the module the user is looking for + */ +static char *complete_config_module(const char *word, int pos, int state) +{ + char *c = NULL; + size_t wordlen = strlen(word); + int which = 0; + struct ao2_iterator i; + struct ast_xml_doc_item *cur; + + if (pos != 3) { + return NULL; + } + + i = ao2_iterator_init(xmldocs, 0); + while ((cur = ao2_iterator_next(&i))) { + if (!strncasecmp(word, cur->name, wordlen) && ++which > state) { + c = ast_strdup(cur->name); + ao2_ref(cur, -1); + break; + } + ao2_ref(cur, -1); + } + ao2_iterator_destroy(&i); + + return c; +} + +/*! \internal + * \brief Complete the name of the configuration type the user is looking for + */ +static char *complete_config_type(const char *module, const char *word, int pos, int state) +{ + char *c = NULL; + size_t wordlen = strlen(word); + int which = 0; + RAII_VAR(struct ast_xml_doc_item *, info, NULL, ao2_cleanup); + struct ast_xml_doc_item *cur; + + if (pos != 4) { + return NULL; + } + + if (!(info = ao2_find(xmldocs, module, OBJ_KEY))) { + return NULL; + } + + cur = info; + while ((cur = cur->next)) { + if (!strcasecmp(cur->type, "configObject") && !strncasecmp(word, cur->name, wordlen) && ++which > state) { + c = ast_strdup(cur->name); + break; + } + } + return c; +} + +/*! \internal + * \brief Complete the name of the configuration option the user is looking for + */ +static char *complete_config_option(const char *module, const char *option, const char *word, int pos, int state) +{ + char *c = NULL; + size_t wordlen = strlen(word); + int which = 0; + RAII_VAR(struct ast_xml_doc_item *, info, NULL, ao2_cleanup); + struct ast_xml_doc_item *cur; + + if (pos != 5) { + return NULL; + } + + if (!(info = ao2_find(xmldocs, module, OBJ_KEY))) { + return NULL; + } + + cur = info; + while ((cur = cur->next)) { + if (!strcasecmp(cur->type, "configOption") && !strcasecmp(cur->ref, option) && !strncasecmp(word, cur->name, wordlen) && ++which > state) { + c = ast_strdup(cur->name); + break; + } + } + return c; +} + +/* Define as 0 if we want to allow configurations to be registered without + * documentation + */ +#define XMLDOC_STRICT 1 + +/*! \internal + * \brief Update the XML documentation for a config type based on its registration + */ +static int xmldoc_update_config_type(const char *module, const char *name, const char *category, const char *matchfield, const char *matchvalue, unsigned int matches) +{ + RAII_VAR(struct ast_xml_xpath_results *, results, NULL, ast_xml_xpath_results_free); + RAII_VAR(struct ast_xml_doc_item *, config_info, ao2_find(xmldocs, module, OBJ_KEY), ao2_cleanup); + struct ast_xml_doc_item *config_type; + struct ast_xml_node *type, *syntax, *matchinfo, *tmp; + + /* If we already have a syntax element, bail. This isn't an error, since we may unload a module which + * has updated the docs and then load it again. */ + if ((results = ast_xmldoc_query("//configInfo[@name='%s']/*/configObject[@name='%s']/syntax", module, name))) { + return 0; + } + + if (!(results = ast_xmldoc_query("//configInfo[@name='%s']/*/configObject[@name='%s']", module, name))) { + ast_log(LOG_WARNING, "Cannot update type '%s' in module '%s' because it has no existing documentation!\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(type = ast_xml_xpath_get_first_result(results))) { + ast_log(LOG_WARNING, "Could not retrieve documentation for type '%s' in module '%s'\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(syntax = ast_xml_new_child(type, "syntax"))) { + ast_log(LOG_WARNING, "Could not create syntax node for type '%s' in module '%s'\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(matchinfo = ast_xml_new_child(syntax, "matchInfo"))) { + ast_log(LOG_WARNING, "Could not create matchInfo node for type '%s' in module '%s'\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(tmp = ast_xml_new_child(matchinfo, "category"))) { + ast_log(LOG_WARNING, "Could not create category node for type '%s' in module '%s'\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + ast_xml_set_text(tmp, category); + ast_xml_set_attribute(tmp, "match", matches ? "true" : "false"); + + if (!ast_strlen_zero(matchfield) && !(tmp = ast_xml_new_child(matchinfo, "field"))) { + ast_log(LOG_WARNING, "Could not add %s attribute for type '%s' in module '%s'\n", matchfield, name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + ast_xml_set_attribute(tmp, "name", matchfield); + ast_xml_set_text(tmp, matchvalue); + + if (!config_info || !(config_type = find_xmldoc_type(config_info, name))) { + ast_log(LOG_WARNING, "Could not obtain XML documentation item for config type %s\n", name); + return XMLDOC_STRICT ? -1 : 0; + } + + if (ast_xmldoc_regenerate_doc_item(config_type)) { + ast_log(LOG_WARNING, "Could not update type '%s' with values from config type registration\n", name); + return XMLDOC_STRICT ? -1 : 0; + } + + return 0; +} + +/*! \internal + * \brief Update the XML documentation for a config option based on its registration + */ +static int xmldoc_update_config_option(struct aco_type **types, const char *module, const char *name, const char *object_name, const char *default_value, unsigned int regex, enum aco_option_type type) +{ + RAII_VAR(struct ast_xml_xpath_results *, results, NULL, ast_xml_xpath_results_free); + RAII_VAR(struct ast_xml_doc_item *, config_info, ao2_find(xmldocs, module, OBJ_KEY), ao2_cleanup); + struct ast_xml_doc_item * config_option; + struct ast_xml_node *option; + + ast_assert(ARRAY_LEN(aco_option_type_string) > type); + + if (!config_info || !(config_option = find_xmldoc_option(config_info, types, name))) { + ast_log(LOG_ERROR, "XML Documentation for option '%s' in modules '%s' not found!\n", name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(results = ast_xmldoc_query("//configInfo[@name='%s']/*/configObject[@name='%s']/configOption[@name='%s']", module, object_name, name))) { + ast_log(LOG_WARNING, "Could not find option '%s' with type '%s' in module '%s'\n", name, object_name, module); + return XMLDOC_STRICT ? -1 : 0; + } + + if (!(option = ast_xml_xpath_get_first_result(results))) { + ast_log(LOG_WARNING, "Could obtain results for option '%s' with type '%s' in module '%s'\n", name, object_name, module); + return XMLDOC_STRICT ? -1 : 0; + } + ast_xml_set_attribute(option, "regex", regex ? "true" : "false"); + ast_xml_set_attribute(option, "default", default_value); + ast_xml_set_attribute(option, "type", aco_option_type_string[type]); + + if (ast_xmldoc_regenerate_doc_item(config_option)) { + ast_log(LOG_WARNING, "Could not update option '%s' with values from config option registration\n", name); + return XMLDOC_STRICT ? -1 : 0; + } + + return 0; +} + +/*! \internal + * \brief Show the modules with configuration information + */ +static void cli_show_modules(struct ast_cli_args *a) +{ + struct ast_xml_doc_item *item; + struct ao2_iterator it_items; + + ast_assert(a->argc == 3); + + if (ao2_container_count(xmldocs) == 0) { + ast_cli(a->fd, "No modules found.\n"); + return; + } + + it_items = ao2_iterator_init(xmldocs, 0); + ast_cli(a->fd, "The following modules have configuration information:\n"); + while ((item = ao2_iterator_next(&it_items))) { + ast_cli(a->fd, "\t%s\n", item->name); + ao2_ref(item, -1); + } + ao2_iterator_destroy(&it_items); +} + +/*! \internal + * \brief Show the configuration types for a module + */ +static void cli_show_module_types(struct ast_cli_args *a) +{ + RAII_VAR(struct ast_xml_doc_item *, item, NULL, ao2_cleanup); + struct ast_xml_doc_item *tmp; + + ast_assert(a->argc == 4); + + if (!(item = ao2_find(xmldocs, a->argv[3], OBJ_KEY))) { + ast_cli(a->fd, "Module %s not found.\n", a->argv[3]); + return; + } + + if (ast_str_strlen(item->synopsis)) { + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(ast_str_buffer(item->synopsis), 1)); + } + if (ast_str_strlen(item->description)) { + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(ast_str_buffer(item->description), 1)); + } + + tmp = item; + ast_cli(a->fd, "Configuration option types for %s:\n", tmp->name); + while ((tmp = tmp->next)) { + if (!strcasecmp(tmp->type, "configObject")) { + ast_cli(a->fd, "%-25s -- %-65.65s\n", tmp->name, + ast_str_buffer(tmp->synopsis)); + } + } +} + +/*! \internal + * \brief Show the information for a configuration type + */ +static void cli_show_module_type(struct ast_cli_args *a) +{ + RAII_VAR(struct ast_xml_doc_item *, item, NULL, ao2_cleanup); + struct ast_xml_doc_item *tmp; + char option_type[64]; + int match = 0; + + ast_assert(a->argc == 5); + + if (!(item = ao2_find(xmldocs, a->argv[3], OBJ_KEY))) { + ast_cli(a->fd, "Unknown module %s\n", a->argv[3]); + return; + } + + tmp = item; + while ((tmp = tmp->next)) { + if (!strcasecmp(tmp->type, "configObject") && !strcasecmp(tmp->name, a->argv[4])) { + match = 1; + term_color(option_type, tmp->name, COLOR_MAGENTA, COLOR_BLACK, sizeof(option_type)); + ast_cli(a->fd, "%s", option_type); + if (ast_str_strlen(tmp->syntax)) { + ast_cli(a->fd, ": [%s]\n\n", ast_xmldoc_printable(ast_str_buffer(tmp->syntax), 1)); + } else { + ast_cli(a->fd, "\n\n"); + } + if (ast_str_strlen(tmp->synopsis)) { + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(ast_str_buffer(tmp->synopsis), 1)); + } + if (ast_str_strlen(tmp->description)) { + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(ast_str_buffer(tmp->description), 1)); + } + } + } + + if (!match) { + ast_cli(a->fd, "Unknown configuration type %s\n", a->argv[4]); + return; + } + + /* Now iterate over the options for the type */ + tmp = item; + while ((tmp = tmp->next)) { + if (!strcasecmp(tmp->type, "configOption") && !strcasecmp(tmp->ref, a->argv[4])) { + ast_cli(a->fd, "%-25s -- %-65.65s\n", tmp->name, + ast_str_buffer(tmp->synopsis)); + } + } +} + +/*! \internal + * \brief Show detailed information for an option + */ +static void cli_show_module_options(struct ast_cli_args *a) +{ + RAII_VAR(struct ast_xml_doc_item *, item, NULL, ao2_cleanup); + struct ast_xml_doc_item *tmp; + char option_name[64]; + int match = 0; + + ast_assert(a->argc == 6); + + if (!(item = ao2_find(xmldocs, a->argv[3], OBJ_KEY))) { + ast_cli(a->fd, "Unknown module %s\n", a->argv[3]); + return; + } + tmp = item; + while ((tmp = tmp->next)) { + if (!strcasecmp(tmp->type, "configOption") && !strcasecmp(tmp->ref, a->argv[4]) && !strcasecmp(tmp->name, a->argv[5])) { + if (match) { + ast_cli(a->fd, "\n"); + } + term_color(option_name, tmp->ref, COLOR_MAGENTA, COLOR_BLACK, sizeof(option_name)); + ast_cli(a->fd, "[%s%s]\n", option_name, term_end()); + if (ast_str_strlen(tmp->syntax)) { + ast_cli(a->fd, "%s\n", ast_xmldoc_printable(ast_str_buffer(tmp->syntax), 1)); + } + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(AS_OR(tmp->synopsis, "No information available"), 1)); + if (ast_str_strlen(tmp->description)) { + ast_cli(a->fd, "%s\n\n", ast_xmldoc_printable(ast_str_buffer(tmp->description), 1)); + } + + match = 1; + } + } + + if (!match) { + ast_cli(a->fd, "No option %s found for %s:%s\n", a->argv[5], a->argv[3], a->argv[4]); + } +} + +static char *cli_show_help(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "config show help"; + e->usage = + "Usage: config show help [ [ [