diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | apps/app_skel.c | 48 | ||||
-rw-r--r-- | apps/confbridge/conf_config_parser.c | 466 | ||||
-rw-r--r-- | channels/chan_motif.c | 140 | ||||
-rw-r--r-- | configs/motif.conf.sample | 27 | ||||
-rw-r--r-- | configs/xmpp.conf.sample | 3 | ||||
-rw-r--r-- | doc/appdocsxml.dtd | 34 | ||||
-rw-r--r-- | include/asterisk/_private.h | 1 | ||||
-rw-r--r-- | include/asterisk/config_options.h | 9 | ||||
-rw-r--r-- | include/asterisk/xml.h | 39 | ||||
-rw-r--r-- | include/asterisk/xmldoc.h | 28 | ||||
-rw-r--r-- | main/asterisk.c | 2 | ||||
-rw-r--r-- | main/config_options.c | 534 | ||||
-rw-r--r-- | main/named_acl.c | 51 | ||||
-rw-r--r-- | main/udptl.c | 37 | ||||
-rw-r--r-- | main/xml.c | 39 | ||||
-rw-r--r-- | main/xmldoc.c | 254 | ||||
-rw-r--r-- | res/res_xmpp.c | 122 |
18 files changed, 1762 insertions, 74 deletions
@@ -456,7 +456,7 @@ doc/core-en_US.xml: makeopts $(foreach dir,$(MOD_SUBDIRS),$(shell $(GREP) -l "la @echo "<docs xmlns:xi=\"http://www.w3.org/2001/XInclude\">" >> $@ @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 6fdbebbf2..8a5b5bc75 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.</para> </description> </application> + + <configInfo name="app_skel" language="en_US"> + <configFile name="app_skel.conf"> + <configObject name="globals"> + <synopsis>Options that apply globally to app_skel</synopsis> + <configOption name="games"> + <synopsis>The number of games a single execution of SkelGuessNumber will play</synopsis> + </configOption> + <configOption name="cheat"> + <synopsis>Should the computer cheat?</synopsis> + <description><para>If enabled, the computer will ignore winning guesses.</para></description> + </configOption> + </configObject> + <configObject name="sounds"> + <synopsis>Prompts for SkelGuessNumber to play</synopsis> + <configOption name="prompt" default="please-enter-your&number&queue-less-than"> + <synopsis>A prompt directing the user to enter a number less than the max number</synopsis> + </configOption> + <configOption name="wrong_guess" default="vm-pls-try-again"> + <synopsis>The sound file to play when a wrong guess is made</synopsis> + </configOption> + <configOption name="right_guess" default="auth-thankyou"> + <synopsis>The sound file to play when a correct guess is made</synopsis> + </configOption> + <configOption name="too_low"> + <synopsis>The sound file to play when a guess is too low</synopsis> + </configOption> + <configOption name="too_high"> + <synopsis>The sound file to play when a guess is too high</synopsis> + </configOption> + <configOption name="lose" default="vm-goodbye"> + <synopsis>The sound file to play when a player loses</synopsis> + </configOption> + </configObject> + <configObject name="level"> + <synopsis>Defined levels for the SkelGuessNumber game</synopsis> + <configOption name="max_number"> + <synopsis>The maximum in the range of numbers to guess (1 is the implied minimum)</synopsis> + </configOption> + <configOption name="max_guesses"> + <synopsis>The maximum number of guesses before a game is considered lost</synopsis> + </configOption> + </configObject> + </configFile> + </configInfo> ***/ 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 c915b184c..6c5ea0bb8 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 + <configInfo name="app_confbridge" language="en_US"> + <synopsis>Conference Bridge Application</synopsis> + <configFile name="confbridge.conf"> + <configObject name="global"> + <synopsis>Unused, but reserved.</synopsis> + </configObject> + <configObject name="user_profile"> + <synopsis>A named profile to apply to specific callers.</synopsis> + <description><para>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 <literal>type</literal> parameter has a value + of <literal>user</literal>. + </para></description> + <configOption name="type"> + <synopsis>Define this configuration category as a user profile.</synopsis> + <description><para>The type parameter determines how a context in the + configuration file is interpreted.</para> + <enumlist> + <enum name="user"><para>Configure the context as a <replaceable>user_profile</replaceable></para></enum> + <enum name="bridge"><para>Configure the context as a <replaceable>bridge_profile</replaceable></para></enum> + <enum name="menu"><para>Configure the context as a <replaceable>menu</replaceable></para></enum> + </enumlist> + </description> + </configOption> + <configOption name="admin"> + <synopsis>Sets if the user is an admin or not</synopsis> + </configOption> + <configOption name="marked"> + <synopsis>Sets if this is a marked user or not</synopsis> + </configOption> + <configOption name="startmuted"> + <synopsis>Sets if all users should start out muted</synopsis> + </configOption> + <configOption name="music_on_hold_when_empty"> + <synopsis>Play MOH when user is alone or waiting on a marked user</synopsis> + </configOption> + <configOption name="quiet"> + <synopsis>Silence enter/leave prompts and user intros for this user</synopsis> + </configOption> + <configOption name="announce_user_count"> + <synopsis>Sets if the number of users should be announced to the user</synopsis> + </configOption> + <configOption name="announce_user_count_all"> + <synopsis>Announce user count to all the other users when this user joins</synopsis> + <description><para>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. + </para></description> + </configOption> + <configOption name="announce_only_user"> + <synopsis>Announce to a user when they join an empty conference</synopsis> + </configOption> + <configOption name="wait_marked"> + <synopsis>Sets if the user must wait for a marked user to enter before joining a conference</synopsis> + </configOption> + <configOption name="end_marked"> + <synopsis>Kick the user from the conference when the last marked user leaves</synopsis> + </configOption> + <configOption name="talk_detection_events"> + <synopsis>Set whether or not notifications of when a user begins and ends talking should be sent out as events over AMI</synopsis> + </configOption> + <configOption name="dtmf_passthrough"> + <synopsis>Sets whether or not DTMF should pass through the conference</synopsis> + </configOption> + <configOption name="announce_join_leave"> + <synopsis>Prompt user for their name when joining a conference and play it to the conference when they enter</synopsis> + </configOption> + <configOption name="pin"> + <synopsis>Sets a PIN the user must enter before joining the conference</synopsis> + </configOption> + <configOption name="music_on_hold_class"> + <synopsis>The MOH class to use for this user</synopsis> + </configOption> + <configOption name="announcement"> + <synopsis>Sound file to play to the user when they join a conference</synopsis> + </configOption> + <configOption name="denoise"> + <synopsis>Apply a denoise filter to the audio before mixing</synopsis> + <description><para>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. + </para></description> + </configOption> + <configOption name="dsp_drop_silence"> + <synopsis>Drop what Asterisk detects as silence from audio sent to the bridge</synopsis> + <description><para> + 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. + </para></description> + </configOption> + <configOption name="dsp_silence_threshold"> + <synopsis>The number of milliseconds of detected silence necessary to trigger silence detection</synopsis> + <description><para> + 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.</para> + <para>What this value affects internally:</para> + <para> + 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. + </para> + <para> + 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. + </para> + <para> + 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. + </para> + <para>By default this value is 2500ms. Valid values are 1 through 2^31.</para> + </description> + </configOption> + <configOption name="dsp_talking_threshold"> + <synopsis>The number of milliseconds of detected non-silence necessary to triger talk detection</synopsis> + <description><para> + 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.</para> + <para> + What this value affects internally: + </para> + <para> + 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. + </para> + <para> + 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. + </para> + <para> + 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. + </para> + <para>By default this value is 160 ms. Valid values are 1 through 2^31</para> + </description> + </configOption> + <configOption name="jitterbuffer"> + <synopsis>Place a jitter buffer on the user's audio stream before audio mixing is performed</synopsis> + <description><para> + 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 <literal>JITTERBUFFER</literal> + dialplan function's default adaptive jitterbuffer. For a more fine tuned + jitterbuffer, disable this option and use the <literal>JITTERBUFFER</literal> dialplan function + on the user before entering the ConfBridge application. + </para></description> + </configOption> + <configOption name="template"> + <synopsis>When using the CONFBRIDGE dialplan function, use a user profile as a template for creating a new temporary profile</synopsis> + </configOption> + </configObject> + <configObject name="bridge_profile"> + <synopsis>A named profile to apply to specific bridges.</synopsis> + <description><para>ConfBridge bridges have a profile associated with them + that determine their options. A configuration section is determined to be a + <literal>bridge_profile</literal> when the <literal>type</literal> parameter has a value + of <literal>bridge</literal>. + </para></description> + <configOption name="type"> + <synopsis>Define this configuration category as a bridge profile</synopsis> + <description><para>The type parameter determines how a context in the + configuration file is interpreted.</para> + <enumlist> + <enum name="user"><para>Configure the context as a <replaceable>user_profile</replaceable></para></enum> + <enum name="bridge"><para>Configure the context as a <replaceable>bridge_profile</replaceable></para></enum> + <enum name="menu"><para>Configure the context as a <replaceable>menu</replaceable></para></enum> + </enumlist> + </description> + </configOption> + <configOption name="jitterbuffer"> + <synopsis>Place a jitter buffer on the conference's audio stream</synopsis> + </configOption> + <configOption name="internal_sample_rate"> + <synopsis>Set the internal native sample rate for mixing the conference</synopsis> + <description><para> + 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. + </para></description> + </configOption> + <configOption name="mixing_interval"> + <synopsis>Sets the internal mixing interval in milliseconds for the bridge</synopsis> + <description><para> + 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. + </para></description> + </configOption> + <configOption name="record_conference"> + <synopsis>Record the conference starting with the first active user's entrance and ending with the last active user's exit</synopsis> + <description><para> + 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 + <filename>'confbridge-${name of conference bridge}-${start time}.wav</filename> + and the default format is 8khz slinear. This file will be + located in the configured monitoring directory in asterisk.conf. + </para></description> + </configOption> + <configOption name="record_file" default="confbridge-${name of conference bridge}-${start time}.wav"> + <synopsis>The filename of the conference recording</synopsis> + <description><para> + 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 <literal>CONFBRIDGE()</literal> 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. + </para></description> + </configOption> + <configOption name="video_mode"> + <synopsis>Sets how confbridge handles video distribution to the conference participants</synopsis> + <description><para> + 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.</para> + <enumlist> + <enum name="none"> + <para>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.</para> + </enum> + <enum name="follow_talker"> + <para>The video feed will follow whoever is talking and providing video.</para> + </enum> + <enum name="last_marked"> + <para>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.</para> + </enum> + <enum name="first_marked"> + <para>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.</para> + </enum> + </enumlist> + </description> + </configOption> + <configOption name="max_members"> + <synopsis>Limit the maximum number of participants for a single conference</synopsis> + <description><para> + 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. + </para></description> + </configOption> + <configOption name="^sound_"> + <synopsis>Override the various conference bridge sound files</synopsis> + <description><para> + 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: <literal>sound_had_joined=conf-hasjoin</literal> This will play the <literal>conf-hasjoin</literal> + sound file found in the sounds directory when announcing someone's name is joining the + conference.</para> + <enumlist> + <enum name="sound_join"><para>The sound played to everyone when someone enters the conference.</para></enum> + <enum name="sound_leave"><para>The sound played to everyone when someone leaves the conference.</para></enum> + <enum name="sound_has_joined"><para>The sound played before announcing someone's name has + joined the conference. This is used for user intros. + Example <literal>"_____ has joined the conference"</literal></para></enum> + <enum name="sound_has_left"><para>The sound played when announcing someone's name has + left the conference. This is used for user intros. + Example <literal>"_____ has left the conference"</literal></para></enum> + <enum name="sound_kicked"><para>The sound played to a user who has been kicked from the conference.</para></enum> + <enum name="sound_muted"><para>The sound played when the mute option it toggled on.</para></enum> + <enum name="sound_unmuted"><para>The sound played when the mute option it toggled off.</para></enum> + <enum name="sound_only_person"><para>The sound played when the user is the only person in the conference.</para></enum> + <enum name="sound_only_one"><para>The sound played to a user when there is only one other + person is in the conference.</para></enum> + <enum name="sound_there_are"><para>The sound played when announcing how many users there + are in a conference.</para></enum> + <enum name="sound_other_in_party"><para>This file is used in conjunction with <literal>sound_there_are</literal> + when announcing how many users there are in the conference. + The sounds are stringed together like this. + <literal>"sound_there_are" ${number of participants} "sound_other_in_party"</literal></para></enum> + <enum name="sound_place_into_conference"><para>The sound played when someone is placed into the conference + after waiting for a marked user.</para></enum> + <enum name="sound_wait_for_leader"><para>The sound played when a user is placed into a conference that + can not start until a marked user enters.</para></enum> + <enum name="sound_leader_has_left"><para>The sound played when the last marked user leaves the conference.</para></enum> + <enum name="sound_get_pin"><para>The sound played when prompting for a conference pin number.</para></enum> + <enum name="sound_invalid_pin"><para>The sound played when an invalid pin is entered too many times.</para></enum> + <enum name="sound_locked"><para>The sound played to a user trying to join a locked conference.</para></enum> + <enum name="sound_locked_now"><para>The sound played to an admin after toggling the conference to locked mode.</para></enum> + <enum name="sound_unlocked_now"><para>The sound played to an admin after toggling the conference to unlocked mode.</para></enum> + <enum name="sound_error_menu"><para>The sound played when an invalid menu option is entered.</para></enum> + </enumlist> + </description> + </configOption> + <configOption name="template"> + <synopsis>When using the CONFBRIDGE dialplan function, use a bridge profile as a template for creating a new temporary profile</synopsis> + </configOption> + </configObject> + <configObject name="menu"> + <synopsis>A conference user menu</synopsis> + <description> + <para>Conference users, as defined by a <literal>conf_user</literal>, + can have a DTMF menu assigned to their profile when they enter the + <literal>ConfBridge</literal> application.</para> + </description> + <configOption name="type"> + <synopsis>Define this configuration category as a menu</synopsis> + <description><para>The type parameter determines how a context in the + configuration file is interpreted.</para> + <enumlist> + <enum name="user"><para>Configure the context as a <replaceable>user_profile</replaceable></para></enum> + <enum name="bridge"><para>Configure the context as a <replaceable>bridge_profile</replaceable></para></enum> + <enum name="menu"><para>Configure the context as a <replaceable>menu</replaceable></para></enum> + </enumlist> + </description> + </configOption> + <configOption name="^[0-9A-D*#]+$"> + <synopsis>DTMF sequences to assign various confbridge actions to</synopsis> + <description><para>--- ConfBridge Menu Options ---</para> + <para>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.</para> + <para>Below is a list of menu actions that can be assigned to a DTMF sequence.</para> + <note><para> + A single DTMF sequence can have multiple actions associated with it. This is + accomplished by stringing the actions together and using a <literal>,</literal> as the + delimiter. Example: Both listening and talking volume is reset when <literal>5</literal> is + pressed. <literal>5=reset_talking_volume, reset_listening_volume</literal></para></note> + <enumlist> + <enum name="playback(filename&filename2&...)"><para> + <literal>playback</literal> 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 + <literal>&</literal> character.</para></enum> + <enum name="playback_and_continue(filename&filename2&...)"><para> + <literal>playback_and_continue</literal> 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 <literal>&</literal> character as a delimiter.</para></enum> + <enum name="toggle_mute"><para> + 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.</para></enum> + <enum name="no_op"><para> + 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.</para></enum> + <enum name="decrease_listening_volume"><para> + Decreases the channel's listening volume.</para></enum> + <enum name="increase_listening_volume"><para> + Increases the channel's listening volume.</para></enum> + <enum name="reset_listening_volume"><para> + Reset channel's listening volume to default level.</para></enum> + <enum name="decrease_talking_volume"><para> + Decreases the channel's talking volume.</para></enum> + <enum name="increase_talking_volume"><para> + Increases the channel's talking volume.</para></enum> + <enum name="reset_talking_volume"><para> + Reset channel's talking volume to default level.</para></enum> + <enum name="dialplan_exec(context,exten,priority)"><para> + The <literal>dialplan_exec</literal> 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!</para></enum> + <enum name="leave_conference"><para> + This action allows a user to exit the conference and continue + execution in the dialplan.</para></enum> + <enum name="admin_kick_last"><para> + 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.</para></enum> + <enum name="admin_toggle_conference_lock"><para> + 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.</para></enum> + <enum name="set_as_single_video_src"><para> + 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 <literal>video_mode</literal> is set to.</para></enum> + <enum name="release_as_single_video_src"><para> + This action allows a user to release themselves as + the video source. If <literal>video_mode</literal> is not set to <literal>none</literal> + this action will result in the conference returning to + whatever video mode the bridge profile is using.</para> + <para>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 <literal>video_mode</literal> option is set to + upon release of the video src.</para></enum> + <enum name="admin_toggle_mute_participants"><para> + 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.</para></enum> + <enum name="participant_count"><para> + This action plays back the number of participants currently + in a conference</para></enum> + </enumlist> + </description> + </configOption> + </configObject> + </configFile> + </configInfo> +***/ + 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 cc1e370fd..4dbfb8e3a 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 + <configInfo name="chan_motif" language="en_US"> + <synopsis>Jingle Channel Driver</synopsis> + <description> + <para><emphasis>Transports</emphasis></para> + <para>There are three different transports and protocol derivatives + supported by <literal>chan_motif</literal>. They are in order of + preference: Jingle using ICE-UDP, Google Jingle, and Google-V1.</para> + <para>Jingle as defined in XEP-0166 supports the widest range of + features. It is referred to as <literal>ice-udp</literal>. This is + the specification that Jingle clients implement.</para> + <para>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 <literal>google</literal> in this file.</para> + <para>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 <literal>google-v1</literal> + in this file.</para> + <para>Incoming sessions will automatically switch to the correct + transport once it has been determined.</para> + <para>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.</para> + <para>If an outgoing session attempt fails due to failure to + support the given transport <literal>chan_motif</literal> will + fall back in preference order listed previously until all + transports have been exhausted.</para> + <para><emphasis>Dialing and Resource Selection Strategy</emphasis></para> + <para>Placing a call through an endpoint can be accomplished using the + following dial string:</para> + <para><literal>Motif/[endpoint name]/[target]</literal></para> + <para>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.</para> + <para>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 <literal>ice-udp</literal>.</para> + <para><emphasis>Video Support</emphasis></para> + <para>Support for video does not need to be explicitly enabled. + Configuring any video codec on your endpoint will automatically enable + it.</para> + <para><emphasis>DTMF</emphasis></para> + <para>The only supported method for DTMF is RFC2833. This is always + enabled on audio streams and negotiated if possible.</para> + <para><emphasis>Incoming Calls</emphasis></para> + <para>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 <literal>s</literal> extension.</para> + <para><emphasis>CallerID</emphasis></para> + <para>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.</para> + <para>Outgoing caller id can <emphasis>not</emphasis> be set.</para> + <warning> + <para>Multiple endpoints using the + same connection is <emphasis>NOT</emphasis> supported. Doing so + may result in broken calls.</para> + </warning> + </description> + <configFile name="motif.conf"> + <configObject name="endpoint"> + <synopsis>The configuration for an endpoint.</synopsis> + <configOption name="context"> + <synopsis>Default dialplan context that incoming sessions will be routed to</synopsis> + </configOption> + <configOption name="callgroup"> + <synopsis>A callgroup to assign to this endpoint.</synopsis> + </configOption> + <configOption name="pickupgroup"> + <synopsis>A pickup group to assign to this endpoint.</synopsis> + </configOption> + <configOption name="language"> + <synopsis>The default language for this endpoint.</synopsis> + </configOption> + <configOption name="musicclass"> + <synopsis>Default music on hold class for this endpoint.</synopsis> + </configOption> + <configOption name="parkinglot"> + <synopsis>Default parking lot for this endpoint.</synopsis> + </configOption> + <configOption name="accountcode"> + <synopsis>Accout code for CDR purposes</synopsis> + </configOption> + <configOption name="allow"> + <synopsis>Codecs to allow</synopsis> + </configOption> + <configOption name="disallow"> + <synopsis>Codecs to disallow</synopsis> + </configOption> + <configOption name="connection"> + <synopsis>Connection to accept traffic on and on which to send traffic out</synopsis> + </configOption> + <configOption name="transport"> + <synopsis>The transport to use for the endpoint.</synopsis> + <description> + <para>The default outbound transport for this endpoint. Inbound + messages are inferred. Allowed transports are <literal>ice-udp</literal>, + <literal>google</literal>, or <literal>google-v1</literal>. Note + that <literal>chan_motif</literal> will fall back to transport + preference order if the transport value chosen here fails.</para> + <enumlist> + <enum name="ice-udp"> + <para>The Jingle protocol, as defined in XEP 0166.</para> + </enum> + <enum name="google"> + <para>The Google Jingle protocol, which follows the Jingle + specification for signaling but uses a custom transport for + media.</para> + </enum> + <enum name="google-v1"> + <para>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 <literal>google</literal> for media.</para> + </enum> + </enumlist> + </description> + </configOption> + <configOption name="maxicecandidates"> + <synopsis>Maximum number of ICE candidates to offer</synopsis> + </configOption> + <configOption name="maxpayloads"> + <synopsis>Maximum number of pyaloads to offer</synopsis> + </configOption> + </configObject> + </configFile> + </configInfo> +***/ + /*! \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 ae3ab30d7..9d5bc808c 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 a83856867..dad0f79ef 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 561e3d38c..a475cd32f 100644 --- a/doc/appdocsxml.dtd +++ b/doc/appdocsxml.dtd @@ -1,13 +1,13 @@ - <!ELEMENT docs (application|function|agi|manager|managerEvent|info)*> + <!ELEMENT docs (application|function|agi|manager|managerEvent|info|configInfo)*> <!ATTLIST docs xmlns:xi CDATA #FIXED "http://www.w3.org/2001/XInclude"> <!ELEMENT xi:include (xi:fallback?) > - <!ATTLIST xi:include + <!ATTLIST xi:include xmlns:xi CDATA #FIXED "http://www.w3.org/2001/XInclude" href CDATA #IMPLIED parse (xml|text) "xml" xpointer CDATA #IMPLIED - encoding CDATA #IMPLIED + encoding CDATA #IMPLIED accept CDATA #IMPLIED accept-language CDATA #IMPLIED > @@ -39,6 +39,30 @@ <!ELEMENT managerEventInstance (synopsis?,syntax?,description?,see-also?)*> <!ATTLIST managerEventInstance class CDATA #REQUIRED> + <!ELEMENT configInfo (synopsis?,description?,configFile+)> + <!ATTLIST configInfo name CDATA #REQUIRED> + <!ATTLIST configInfo language CDATA #REQUIRED> + + <!ELEMENT configFile (configObject+)> + <!ATTLIST configFile name CDATA #REQUIRED> + + <!ELEMENT configObject (synopsis?|description?|syntax?|see-also?|configOption)*> + <!ATTLIST configObject name CDATA #REQUIRED> + + <!ELEMENT configOption (synopsis,description?,syntax?,see-also?)*> + <!ATTLIST configOption name CDATA #REQUIRED> + <!ATTLIST configOption regex (yes|no|true|false) "false"> + <!ATTLIST configOption default CDATA #IMPLIED> + <!ATTLIST configOption type CDATA #IMPLIED> + + <!ELEMENT matchInfo (category|field?)> + + <!ELEMENT category (#PCDATA)> + <!ATTLIST category match (yes|no|true|false) #REQUIRED> + + <!ELEMENT field (#PCDATA)> + <!ATTLIST field name CDATA #REQUIRED> + <!ELEMENT info (para|note|warning|variablelist|enumlist|info|xi:include)*> <!ATTLIST info name CDATA #REQUIRED> <!ATTLIST info language CDATA #REQUIRED> @@ -52,7 +76,7 @@ <!ELEMENT synopsis (#PCDATA)> - <!ELEMENT syntax (parameter|xi:include)*> + <!ELEMENT syntax (parameter|dataType|category|matchInfo|xi:include)*> <!ATTLIST syntax argsep CDATA ","> <!ELEMENT description (para|note|warning|variablelist|enumlist|info|xi:include)*> @@ -91,7 +115,7 @@ <!ELEMENT replaceable (#PCDATA)> <!ELEMENT directory (#PCDATA)> <!ELEMENT astcli (#PCDATA)> - + <!ELEMENT note (para+|xi:include*)> <!ELEMENT warning (para+|xi:include*)> diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index cded90ef7..7e1ef13ec 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 1b0beb206..64d8d5089 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 ddfcc25d9..063e8c0b3 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 9bf647612..c09f693c8 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 243321dda..1d5371938 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 5e76a7a7b..7a65cc555 100644 --- a/main/config_options.c +++ b/main/config_options.c @@ -31,11 +31,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include <regex.h> +#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 [<module> [<type> [<option>]]]\n" + " Display detailed information about module configuration.\n" + " * If nothing is specified, the modules that have\n" + " configuration information are listed.\n" + " * If <module> is specified, the configuration types\n" + " for that module will be listed, along with brief\n" + " information about that type.\n" + " * If <module> and <type> are specified, detailed\n" + " information about the type is displayed, as well\n" + " as the available options.\n" + " * If <module>, <type>, and <option> are specified,\n" + " detailed information will be displayed about that\n" + " option.\n" + " NOTE: the help documentation is partially generated at run\n" + " time when a module is loaded. If a module is not loaded,\n" + " configuration help for that module may be incomplete.\n"; + return NULL; + case CLI_GENERATE: + switch(a->pos) { + case 3: return complete_config_module(a->word, a->pos, a->n); + case 4: return complete_config_type(a->argv[3], a->word, a->pos, a->n); + case 5: return complete_config_option(a->argv[3], a->argv[4], a->word, a->pos, a->n); + default: return NULL; + } + } + + switch (a->argc) { + case 3: + cli_show_modules(a); + break; + case 4: + cli_show_module_types(a); + break; + case 5: + cli_show_module_type(a); + break; + case 6: + cli_show_module_options(a); + break; + default: + return CLI_SHOWUSAGE; + } + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_aco[] = { + AST_CLI_DEFINE(cli_show_help, "Show configuration help for a module"), +}; + +static void aco_deinit(void) +{ + ast_cli_unregister(cli_aco); + ao2_cleanup(xmldocs); +} +#endif /* AST_XML_DOCS */ + +int aco_init(void) +{ +#ifdef AST_XML_DOCS + ast_register_atexit(aco_deinit); + if (!(xmldocs = ast_xmldoc_build_documentation("configInfo"))) { + ast_log(LOG_ERROR, "Couldn't build config documentation\n"); + return -1; + } + ast_cli_register_multiple(cli_aco, ARRAY_LEN(cli_aco)); +#endif /* AST_XML_DOCS */ + return 0; +} + /* Default config option handlers */ /*! \brief Default option handler for signed integers @@ -730,7 +1240,7 @@ static int int_handler_fn(const struct aco_option *opt, struct ast_variable *var ast_parse_arg(var->value, flags, field, (int) opt->args[1], (int) opt->args[2]); if (res) { if (opt->flags & PARSE_RANGE_DEFAULTS) { - ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]); + ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]); res = 0; } else if (opt->flags & PARSE_DEFAULT) { ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field); @@ -760,7 +1270,7 @@ static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *va ast_parse_arg(var->value, flags, field, (unsigned int) opt->args[1], (unsigned int) opt->args[2]); if (res) { if (opt->flags & PARSE_RANGE_DEFAULTS) { - ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]); + ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]); res = 0; } else if (opt->flags & PARSE_DEFAULT) { ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field); diff --git a/main/named_acl.c b/main/named_acl.c index 074f4c57f..142693194 100644 --- a/main/named_acl.c +++ b/main/named_acl.c @@ -44,11 +44,21 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define NACL_CONFIG "acl.conf" #define ACL_FAMILY "acls" -struct named_acl_global_config { - AST_DECLARE_STRING_FIELDS( - /* Nothing here yet. */ - ); -}; +/*** DOCUMENTATION + <configInfo name="named_acl" language="en_US"> + <configFile name="named_acl.conf"> + <configObject name="named_acl"> + <synopsis>Options for configuring a named ACL</synopsis> + <configOption name="permit"> + <synopsis>An address/subnet from which to allow access</synopsis> + </configOption> + <configOption name="deny"> + <synopsis>An address/subnet from which to disallow access</synopsis> + </configOption> + </configObject> + </configFile> + </configInfo> +***/ /* * Configuration structure - holds pointers to ao2 containers used for configuration @@ -56,7 +66,6 @@ struct named_acl_global_config { * time, it's really a config options friendly wrapper for the named ACL container */ struct named_acl_config { - struct named_acl_global_config *global; struct ao2_container *named_acl_list; }; @@ -70,6 +79,7 @@ static void *named_acl_find(struct ao2_container *container, const char *cat); /* Config type for named ACL profiles (must not be named general) */ static struct aco_type named_acl_type = { .type = ACO_ITEM, /*!< named_acls are items stored in containers, not individual global objects */ + .name = "named_acl", .category_match = ACO_BLACKLIST, .category = "^general$", /*!< Match everything but "general" */ .item_alloc = named_acl_alloc, /*!< A callback to allocate a new named_acl based on category */ @@ -77,26 +87,16 @@ static struct aco_type named_acl_type = { .item_offset = offsetof(struct named_acl_config, named_acl_list), /*!< Could leave this out since 0 */ }; -/* Config type for the general part of the ACL profile (must be named general) */ -static struct aco_type global_option = { - .type = ACO_GLOBAL, - .item_offset = offsetof(struct named_acl_config, global), - .category_match = ACO_WHITELIST, - .category = "^general$", -}; - /* This array of aco_type structs is necessary to use aco_option_register */ struct aco_type *named_acl_types[] = ACO_TYPES(&named_acl_type); -struct aco_type *global_options[] = ACO_TYPES(&global_option); - struct aco_file named_acl_conf = { .filename = "acl.conf", - .types = ACO_TYPES(&named_acl_type, &global_option), + .types = ACO_TYPES(&named_acl_type), }; /* Create a config info struct that describes the config processing for named ACLs. */ -CONFIG_INFO_STANDARD(cfg_info, globals, named_acl_config_alloc, +CONFIG_INFO_CORE("named_acl", cfg_info, globals, named_acl_config_alloc, .files = ACO_FILES(&named_acl_conf), ); @@ -124,13 +124,6 @@ static void named_acl_config_destructor(void *obj) { struct named_acl_config *cfg = obj; ao2_cleanup(cfg->named_acl_list); - ao2_cleanup(cfg->global); -} - -static void named_acl_global_config_destructor(void *obj) -{ - struct named_acl_global_config *global = obj; - ast_string_field_free_memory(global); } /*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by @@ -144,14 +137,6 @@ static void *named_acl_config_alloc(void) return NULL; } - if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), named_acl_global_config_destructor))) { - goto error; - } - - if (ast_string_field_init(cfg->global, 128)) { - goto error; - } - if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) { goto error; } diff --git a/main/udptl.c b/main/udptl.c index 37f04d530..8ae8334a9 100644 --- a/main/udptl.c +++ b/main/udptl.c @@ -80,6 +80,40 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #include "asterisk/unaligned.h" +/*** DOCUMENTATION + <configInfo name="udptl" language="en_US"> + <configFile name="udptl.conf"> + <configObject name="global"> + <synopsis>Global options for configuring UDPTL</synopsis> + <configOption name="udptlstart"> + <synopsis>The start of the UDPTL port range</synopsis> + </configOption> + <configOption name="udptlend"> + <synopsis>The end of the UDPTL port range</synopsis> + </configOption> + <configOption name="udptlchecksums"> + <synopsis>Whether to enable or disable UDP checksums on UDPTL traffic</synopsis> + </configOption> + <configOption name="udptlfecentries"> + <synopsis>The number of error correction entries in a UDPTL packet</synopsis> + </configOption> + <configOption name="udptlfecspan"> + <synopsis>The span over which parity is calculated for FEC in a UDPTL packet</synopsis> + </configOption> + <configOption name="use_even_ports"> + <synopsis>Whether to only use even-numbered UDPTL ports</synopsis> + </configOption> + <configOption name="t38faxudpec"> + <synopsis>Removed</synopsis> + </configOption> + <configOption name="t38faxmaxdatagram"> + <synopsis>Removed</synopsis> + </configOption> + </configObject> + </configFile> + </configInfo> +***/ + #define UDPTL_MTU 1200 #if !defined(FALSE) @@ -206,6 +240,7 @@ static int udptl_pre_apply_config(void); static struct aco_type general_option = { .type = ACO_GLOBAL, + .name = "global", .category_match = ACO_WHITELIST, .item_offset = offsetof(struct udptl_config, general), .category = "^general$", @@ -218,7 +253,7 @@ static struct aco_file udptl_conf = { .types = ACO_TYPES(&general_option), }; -CONFIG_INFO_STANDARD(cfg_info, globals, udptl_snapshot_alloc, +CONFIG_INFO_CORE("udptl", cfg_info, globals, udptl_snapshot_alloc, .files = ACO_FILES(&udptl_conf), .pre_apply_config = udptl_pre_apply_config, ); diff --git a/main/xml.c b/main/xml.c index bdf983e51..5ca4d4ff1 100644 --- a/main/xml.c +++ b/main/xml.c @@ -28,6 +28,7 @@ #include "asterisk.h" #include "asterisk/xml.h" #include "asterisk/logger.h" +#include "asterisk/utils.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$") @@ -35,6 +36,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include <libxml/parser.h> #include <libxml/tree.h> #include <libxml/xinclude.h> +#include <libxml/xpath.h> /* libxml2 ast_xml implementation. */ @@ -316,5 +318,42 @@ struct ast_xml_node *ast_xml_node_get_parent(struct ast_xml_node *node) return (struct ast_xml_node *) ((xmlNode *) node)->parent; } +struct ast_xml_node *ast_xml_xpath_get_first_result(struct ast_xml_xpath_results *results) +{ + return (struct ast_xml_node *) ((xmlXPathObjectPtr) results)->nodesetval->nodeTab[0]; +} + +void ast_xml_xpath_results_free(struct ast_xml_xpath_results *results) +{ + xmlXPathFreeObject((xmlXPathObjectPtr) results); +} + +int ast_xml_xpath_num_results(struct ast_xml_xpath_results *results) +{ + return ((xmlXPathObjectPtr) results)->nodesetval->nodeNr; +} + +struct ast_xml_xpath_results *ast_xml_query(struct ast_xml_doc *doc, const char *xpath_str) +{ + xmlXPathContextPtr context; + xmlXPathObjectPtr result; + if (!(context = xmlXPathNewContext((xmlDoc *) doc))) { + ast_log(LOG_ERROR, "Could not create XPath context!\n"); + return NULL; + } + result = xmlXPathEvalExpression((xmlChar *) xpath_str, context); + xmlXPathFreeContext(context); + if (!result) { + ast_log(LOG_WARNING, "Error for query: %s\n", xpath_str); + return NULL; + } + if (xmlXPathNodeSetIsEmpty(result->nodesetval)) { + xmlXPathFreeObject(result); + ast_debug(5, "No results for query: %s\n", xpath_str); + return NULL; + } + return (struct ast_xml_xpath_results *) result; +} + #endif /* defined(HAVE_LIBXML2) */ diff --git a/main/xmldoc.c b/main/xmldoc.c index 225ed6611..a38b59852 100644 --- a/main/xmldoc.c +++ b/main/xmldoc.c @@ -38,6 +38,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/term.h" #include "asterisk/astobj2.h" #include "asterisk/xmldoc.h" +#include "asterisk/cli.h" #ifdef AST_XML_DOCS @@ -979,6 +980,10 @@ static char *xmldoc_get_syntax_cmd(struct ast_xml_node *fixnode, const char *nam const char *paramtype, *attrname, *literal; int required, isenum, first = 1, isliteral; + if (!fixnode) { + return NULL; + } + syntax = ast_str_create(128); if (!syntax) { /* at least try to return something... */ @@ -1078,6 +1083,10 @@ static char *xmldoc_get_syntax_manager(struct ast_xml_node *fixnode, const char int required; char *ret; + if (!fixnode) { + return NULL; + } + syntax = ast_str_create(128); if (!syntax) { return ast_strdup(name); @@ -1118,11 +1127,76 @@ static char *xmldoc_get_syntax_manager(struct ast_xml_node *fixnode, const char return ret; } +static char *xmldoc_get_syntax_config_object(struct ast_xml_node *fixnode, const char *name) +{ + struct ast_xml_node *matchinfo, *tmp; + int match; + const char *attr_value; + const char *text; + RAII_VAR(struct ast_str *, syntax, ast_str_create(128), ast_free); + + if (!syntax || !fixnode) { + return NULL; + } + if (!(matchinfo = ast_xml_find_element(ast_xml_node_get_children(fixnode), "matchInfo", NULL, NULL))) { + return NULL; + } + if (!(tmp = ast_xml_find_element(ast_xml_node_get_children(matchinfo), "category", NULL, NULL))) { + return NULL; + } + attr_value = ast_xml_get_attribute(tmp, "match"); + if (attr_value) { + match = ast_true(attr_value); + text = ast_xml_get_text(tmp); + ast_str_set(&syntax, 0, "category %s /%s/", match ? "=~" : "!~", text); + ast_xml_free_attr(attr_value); + } + + if ((tmp = ast_xml_find_element(ast_xml_node_get_children(matchinfo), "field", NULL, NULL))) { + text = ast_xml_get_text(tmp); + attr_value = ast_xml_get_attribute(tmp, "name"); + ast_str_append(&syntax, 0, " matchfield: %s = %s", S_OR(attr_value, "Unknown"), text); + ast_xml_free_attr(attr_value); + } + return ast_strdup(ast_str_buffer(syntax)); +} + +static char *xmldoc_get_syntax_config_option(struct ast_xml_node *fixnode, const char *name) +{ + const char *type; + const char *default_value; + const char *regex; + RAII_VAR(struct ast_str *, syntax, ast_str_create(128), ast_free); + + if (!syntax || !fixnode) { + return NULL; + } + type = ast_xml_get_attribute(fixnode, "type"); + default_value = ast_xml_get_attribute(fixnode, "default"); + + regex = ast_xml_get_attribute(fixnode, "regex"); + ast_str_set(&syntax, 0, "%s = [%s] (Default: %s) (Regex: %s)\n", + name, + type, + default_value, + regex ? regex : "False"); + + ast_xml_free_attr(type); + ast_xml_free_attr(default_value); + ast_xml_free_attr(regex); + + return ast_strdup(ast_str_buffer(syntax)); +} + /*! \brief Types of syntax that we are able to generate. */ enum syntaxtype { FUNCTION_SYNTAX, MANAGER_SYNTAX, MANAGER_EVENT_SYNTAX, + CONFIG_INFO_SYNTAX, + CONFIG_FILE_SYNTAX, + CONFIG_OPTION_SYNTAX, + CONFIG_OBJECT_SYNTAX, COMMAND_SYNTAX }; @@ -1131,11 +1205,15 @@ static struct strsyntaxtype { const char *type; enum syntaxtype stxtype; } stxtype[] = { - { "function", FUNCTION_SYNTAX }, - { "application", FUNCTION_SYNTAX }, - { "manager", MANAGER_SYNTAX }, - { "managerEvent", MANAGER_EVENT_SYNTAX }, - { "agi", COMMAND_SYNTAX } + { "function", FUNCTION_SYNTAX }, + { "application", FUNCTION_SYNTAX }, + { "manager", MANAGER_SYNTAX }, + { "managerEvent", MANAGER_EVENT_SYNTAX }, + { "configInfo", CONFIG_INFO_SYNTAX }, + { "configFile", CONFIG_FILE_SYNTAX }, + { "configOption", CONFIG_OPTION_SYNTAX }, + { "configObject", CONFIG_OBJECT_SYNTAX }, + { "agi", COMMAND_SYNTAX }, }; /*! \internal @@ -1170,9 +1248,10 @@ static enum syntaxtype xmldoc_get_syntax_type(const char *type) * * \since 11 */ -static char *_ast_xmldoc_build_syntax(struct ast_xml_node *node, const char *type, const char *name) +static char *_ast_xmldoc_build_syntax(struct ast_xml_node *root_node, const char *type, const char *name) { char *syntax = NULL; + struct ast_xml_node *node = root_node; for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { if (!strcasecmp(ast_xml_node_get_name(node), "syntax")) { @@ -1180,10 +1259,6 @@ static char *_ast_xmldoc_build_syntax(struct ast_xml_node *node, const char *typ } } - if (!node) { - return syntax; - } - switch (xmldoc_get_syntax_type(type)) { case FUNCTION_SYNTAX: syntax = xmldoc_get_syntax_fun(node, name, "parameter", 1, 1); @@ -1197,6 +1272,12 @@ static char *_ast_xmldoc_build_syntax(struct ast_xml_node *node, const char *typ case MANAGER_EVENT_SYNTAX: syntax = xmldoc_get_syntax_manager(node, name, "Event"); break; + case CONFIG_OPTION_SYNTAX: + syntax = xmldoc_get_syntax_config_option(root_node, name); + break; + case CONFIG_OBJECT_SYNTAX: + syntax = xmldoc_get_syntax_config_object(node, name); + break; default: syntax = xmldoc_get_syntax_fun(node, name, "parameter", 1, 1); } @@ -2198,6 +2279,7 @@ static struct ast_xml_doc_item *xmldoc_build_documentation_item(struct ast_xml_n if (!(item = ast_xml_doc_item_alloc(name, type))) { return NULL; } + item->node = node; syntax = _ast_xmldoc_build_syntax(node, type, name); seealso = _ast_xmldoc_build_seealso(node); @@ -2230,6 +2312,105 @@ static struct ast_xml_doc_item *xmldoc_build_documentation_item(struct ast_xml_n return item; } +struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_query(const char *fmt, ...) +{ + struct ast_xml_xpath_results *results = NULL; + struct documentation_tree *doctree; + RAII_VAR(struct ast_str *, xpath_str, ast_str_create(128), ast_free); + va_list ap; + + if (!xpath_str) { + return NULL; + } + + va_start(ap, fmt); + ast_str_set_va(&xpath_str, 0, fmt, ap); + va_end(ap); + + AST_RWLIST_RDLOCK(&xmldoc_tree); + AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) { + if (!(results = ast_xml_query(doctree->doc, ast_str_buffer(xpath_str)))) { + continue; + } + break; + } + AST_RWLIST_UNLOCK(&xmldoc_tree); + + return results; +} + +static void build_config_docs(struct ast_xml_node *cur, struct ast_xml_doc_item **tail) +{ + struct ast_xml_node *iter; + struct ast_xml_doc_item *item; + + for (iter = ast_xml_node_get_children(cur); iter; iter = ast_xml_node_get_next(iter)) { + if (strncasecmp(ast_xml_node_get_name(iter), "config", 6)) { + continue; + } + /* Now add all of the child config-related items to the list */ + if (!(item = xmldoc_build_documentation_item(iter, ast_xml_get_attribute(iter, "name"), ast_xml_node_get_name(iter)))) { + ast_log(LOG_ERROR, "Could not build documentation for '%s:%s'\n", ast_xml_node_get_name(iter), ast_xml_get_attribute(iter, "name")); + break; + } + if (!strcasecmp(ast_xml_node_get_name(iter), "configOption")) { + ast_string_field_set(item, ref, ast_xml_get_attribute(cur, "name")); + } + (*tail)->next = item; + *tail = (*tail)->next; + build_config_docs(iter, tail); + } +} + +int ast_xmldoc_regenerate_doc_item(struct ast_xml_doc_item *item) +{ + const char *name; + char *syntax; + char *seealso; + char *arguments; + char *synopsis; + char *description; + + if (!item || !item->node) { + return -1; + } + + name = ast_xml_get_attribute(item->node, "name"); + if (!name) { + return -1; + } + + syntax = _ast_xmldoc_build_syntax(item->node, item->type, name); + seealso = _ast_xmldoc_build_seealso(item->node); + arguments = _ast_xmldoc_build_arguments(item->node); + synopsis = _ast_xmldoc_build_synopsis(item->node); + description = _ast_xmldoc_build_description(item->node); + + if (syntax) { + ast_str_set(&item->syntax, 0, "%s", syntax); + } + if (seealso) { + ast_str_set(&item->seealso, 0, "%s", seealso); + } + if (arguments) { + ast_str_set(&item->arguments, 0, "%s", arguments); + } + if (synopsis) { + ast_str_set(&item->synopsis, 0, "%s", synopsis); + } + if (description) { + ast_str_set(&item->description, 0, "%s", description); + } + + ast_free(syntax); + ast_free(seealso); + ast_free(arguments); + ast_free(synopsis); + ast_free(description); + ast_xml_free_attr(name); + return 0; +} + struct ao2_container *ast_xmldoc_build_documentation(const char *type) { struct ao2_container *docs; @@ -2282,6 +2463,19 @@ struct ao2_container *ast_xmldoc_build_documentation(const char *type) } item = root; break; + case CONFIG_INFO_SYNTAX: + { + struct ast_xml_doc_item *tail; + if (item || !ast_xml_node_get_children(node) || strcasecmp(ast_xml_node_get_name(node), "configInfo")) { + break; + } + if (!(item = xmldoc_build_documentation_item(node, ast_xml_get_attribute(node, "name"), "configInfo"))) { + break; + } + tail = item; + build_config_docs(node, &tail); + break; + } default: item = xmldoc_build_documentation_item(node, name, type); } @@ -2299,6 +2493,9 @@ struct ao2_container *ast_xmldoc_build_documentation(const char *type) return docs; } +int ast_xmldoc_regenerate_doc_item(struct ast_xml_doc_item *item); + + #if !defined(HAVE_GLOB_NOMAGIC) || !defined(HAVE_GLOB_BRACE) || defined(DEBUG_NONGNU) static int xml_pathmatch(char *xmlpattern, int xmlpattern_maxlen, glob_t *globbuf) { @@ -2342,11 +2539,47 @@ static int xml_pathmatch(char *xmlpattern, int xmlpattern_maxlen, glob_t *globbu } #endif +static char *handle_dump_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct documentation_tree *doctree; + FILE *f; + + switch (cmd) { + case CLI_INIT: + e->command = "xmldoc dump"; + e->usage = + "Usage: xmldoc dump <filename>\n" + " Dump XML documentation to a file\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 3) { + return CLI_SHOWUSAGE; + } + if (!(f = fopen(a->argv[2], "w"))) { + ast_log(LOG_ERROR, "Could not open file '%s': %s\n", a->argv[2], strerror(errno)); + return CLI_FAILURE; + } + AST_RWLIST_RDLOCK(&xmldoc_tree); + AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) { + ast_xml_doc_dump_file(f, doctree->doc); + } + AST_RWLIST_UNLOCK(&xmldoc_tree); + fclose(f); + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_dump_xmldocs = AST_CLI_DEFINE(handle_dump_docs, "Dump the XML docs to the specified file"); + /*! \brief Close and unload XML documentation. */ static void xmldoc_unload_documentation(void) { struct documentation_tree *doctree; + ast_cli_unregister(&cli_dump_xmldocs); + AST_RWLIST_WRLOCK(&xmldoc_tree); while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) { ast_free(doctree->filename); @@ -2390,6 +2623,7 @@ int ast_xmldoc_load_documentation(void) /* initialize the XML library. */ ast_xml_init(); + ast_cli_register(&cli_dump_xmldocs); /* register function to be run when asterisk finish. */ ast_register_atexit(xmldoc_unload_documentation); diff --git a/res/res_xmpp.c b/res/res_xmpp.c index 3ba849e56..966432dc0 100644 --- a/res/res_xmpp.c +++ b/res/res_xmpp.c @@ -288,6 +288,126 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") account defined in <literal>xmpp.conf</literal> to send the message from. Note that this field is required for XMPP messages.</para> </info> + <configInfo name="res_xmpp" language="en_US"> + <synopsis>XMPP Messaging</synopsis> + <configFile name="xmpp.conf"> + <configObject name="global"> + <synopsis>Global configuration settings</synopsis> + <configOption name="debug"> + <synopsis>Enable/disable XMPP message debugging</synopsis> + </configOption> + <configOption name="autoprune"> + <synopsis>Auto-remove users from buddy list.</synopsis> + <description><para>Auto-remove users from buddy list. Depending on the setup + (e.g., using your personal Gtalk account for a test) this could cause loss of + the contact list. + </para></description> + </configOption> + <configOption name="autoregister"> + <synopsis>Auto-register users from buddy list</synopsis> + </configOption> + <configOption name="collection_nodes"> + <synopsis>Enable support for XEP-0248 for use with distributed device state</synopsis> + </configOption> + <configOption name="pubsub_autocreate"> + <synopsis>Whether or not the PubSub server supports/is using auto-create for nodes</synopsis> + </configOption> + <configOption name="auth_policy"> + <synopsis>Whether to automatically accept or deny users' subscription requests</synopsis> + </configOption> + </configObject> + <configObject name="client"> + <synopsis>Configuration options for an XMPP client</synopsis> + <configOption name="username"> + <synopsis>XMPP username with optional resource</synopsis> + </configOption> + <configOption name="secret"> + <synopsis>XMPP password</synopsis> + </configOption> + <configOption name="serverhost"> + <synopsis>Route to server, e.g. talk.google.com</synopsis> + </configOption> + <configOption name="statusmessage"> + <synopsis>Custom status message</synopsis> + </configOption> + <configOption name="pubsub_node"> + <synopsis>Node for publishing events via PubSub</synopsis> + </configOption> + <configOption name="context"> + <synopsis>Dialplan context to send incoming messages to</synopsis> + </configOption> + <configOption name="priority"> + <synopsis>XMPP resource priority</synopsis> + </configOption> + <configOption name="port"> + <synopsis>XMPP server port</synopsis> + </configOption> + <configOption name="timeout"> + <synopsis>Timeout in seconds to hold incoming messages</synopsis> + <description><para>Timeout (in seconds) on the message stack. Messages stored longer + than this value will be deleted by Asterisk. This option applies to incoming messages only + which are intended to be processed by the <literal>JABBER_RECEIVE</literal> dialplan function. + </para></description> + </configOption> + <configOption name="debug"> + <synopsis>Enable debugging</synopsis> + </configOption> + <configOption name="type"> + <synopsis>Connection is either a client or a component</synopsis> + </configOption> + <configOption name="distribute_events"> + <synopsis>Whether or not to distribute events using this connection</synopsis> + </configOption> + <configOption name="usetls"> + <synopsis>Whether to use TLS for the connection or not</synopsis> + </configOption> + <configOption name="usesasl"> + <synopsis>Whether to use SASL for the connection or not</synopsis> + </configOption> + <configOption name="forceoldssl"> + <synopsis>Force the use of old-style SSL for the connection</synopsis> + </configOption> + <configOption name="keepalive"> + <synopsis>If enabled, periodically send an XMPP message from this client with an empty message</synopsis> + </configOption> + <configOption name="autoprune"> + <synopsis>Auto-remove users from buddy list.</synopsis> + <description><para>Auto-remove users from buddy list. Depending on the setup + (e.g., using your personal Gtalk account for a test) this could cause loss of + the contact list. + </para></description> + </configOption> + <configOption name="autoregister"> + <synopsis>Auto-register users bfrom buddy list</synopsis> + </configOption> + <configOption name="auth_policy"> + <synopsis>Whether to automatically accept or deny users' subscription requests</synopsis> + </configOption> + <configOption name="sendtodialplan"> + <synopsis>Send incoming messages into the dialplan</synopsis> + </configOption> + <configOption name="status"> + <synopsis>Default XMPP status for the client</synopsis> + <description><para>Can be one of the following XMPP statuses:</para> + <enumlist> + <enum name="chat"/> + <enum name="available"/> + <enum name="away"/> + <enum name="xaway"/> + <enum name="dnd"/> + </enumlist> + </description> + </configOption> + <configOption name="buddy"> + <synopsis>Manual addition of buddy to list</synopsis> + <description><para> + Manual addition of buddy to the buddy list. For distributed events, these budies are + automatically added in the whitelist as 'owners' of the node(s). + </para></description> + </configOption> + </configObject> + </configFile> + </configInfo> ***/ /*! \brief Supported general configuration flags */ @@ -658,6 +778,7 @@ static void xmpp_config_post_apply(void) static struct aco_type global_option = { .type = ACO_GLOBAL, + .name = "global", .item_offset = offsetof(struct xmpp_config, global), .category_match = ACO_WHITELIST, .category = "^general$", @@ -667,6 +788,7 @@ struct aco_type *global_options[] = ACO_TYPES(&global_option); static struct aco_type client_option = { .type = ACO_ITEM, + .name = "client", .category_match = ACO_BLACKLIST, .category = "^(general)$", .item_alloc = ast_xmpp_client_config_alloc, |