diff -ruN asterisk.original/asterisk-1.2.27.ebuild asterisk/asterisk-1.2.27.ebuild --- asterisk.original/asterisk-1.2.27.ebuild 1970-01-01 01:00:00.000000000 +0100 +++ asterisk/asterisk-1.2.27.ebuild 2008-03-19 09:56:41.000000000 +0100 @@ -0,0 +1,479 @@ +# Copyright 1999-2008 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: $ + +inherit eutils multilib toolchain-funcs + +IUSE="alsa bri curl debug doc gtk h323 hardened lowmem mmx \ + nosamples odbc osp postgres pri speex sqlite ssl zaptel \ + elibc_uclibc backports" + +BRI_VERSION="0.3.0-PRE-1y-p" +AST_PATCHES="1.2.26.2-patches-1.0" +JB_PATCHES="1.2.11-jb" + +MY_P="${P/_p/.}" + +DESCRIPTION="Asterisk: A Modular Open Source PBX System" +HOMEPAGE="http://www.asterisk.org/" +SRC_URI="http://ftp.digium.com/pub/asterisk/releases/${MY_P}.tar.gz + http://www.netdomination.org/pub/asterisk/${PN}-${AST_PATCHES}.tar.bz2 + mirror://gentoo/${PN}-${AST_PATCHES}.tar.bz2 + bri? ( mirror://gentoo/${MY_P}-bristuff-${BRI_VERSION}.diff.bz2 + http://www.junghanns.net/downloads/bristuff-${BRI_VERSION}.tar.gz )" + +S="${WORKDIR}/${MY_P}" +S_BRI="${WORKDIR}/bristuff-${BRI_VERSION}" + +SLOT="0" +LICENSE="GPL-2" +KEYWORDS="~alpha amd64 ~hppa ~ppc sparc x86" + +RDEPEND="dev-libs/newt + media-sound/sox + ssl? ( dev-libs/openssl ) + gtk? ( =x11-libs/gtk+-1.2* ) + pri? ( >=net-libs/libpri-1.2.5 ) + h323? ( >=dev-libs/pwlib-1.8.3 + >=net-libs/openh323-1.15.0 ) + alsa? ( media-libs/alsa-lib ) + curl? ( net-misc/curl ) + odbc? ( dev-db/unixODBC ) + backports? ( dev-db/unixODBC ) + speex? ( media-libs/speex ) + sqlite? ( =net-misc/zaptel-1.2.16 ) + postgres? ( dev-db/postgresql ) + osp? ( >=net-libs/osptoolkit-3.3.4 ) + bri? ( >=net-libs/libpri-1.2.5 + >=net-misc/zaptel-1.2.18 )" + +DEPEND="${RDEPEND} + sys-devel/flex + sys-devel/bison + doc? ( app-doc/doxygen ) + virtual/logger" + +#asterisk uses special mpg123 functions and does not work with mpeg321, bug #42703 +PDEPEND="|| ( media-sound/mpg123 net-misc/asterisk-addons )" + +QA_TEXTRELS_x86="usr/lib/asterisk/modules/codec_gsm.so" +QA_EXECSTACK_x86="usr/lib/asterisk/modules/codec_gsm.so" + +# +# List of modules to ignore during scan (because they have been removed in 1.2.x) +# +SCAN_IGNORE_MODS=" + app_qcall + chan_modem + chan_modem_i4l + chan_modem_bestdata + chan_modme_aopen" + +# +# shortcuts +# +is_ast10update() { + return $(has_version "=net-misc/asterisk-1.0*") +} + +is_astupdate() { + if ! is_ast10update; then + return $(has_version "/dev/null + + # remove installed sample files if nosamples flag is set + if use nosamples; then + einfo "Skipping installation of sample files..." + rm -rf "${D}"var/spool/asterisk/voicemail/default + rm -f "${D}"var/lib/asterisk/mohmp3/* + rm -f "${D}"var/lib/asterisk/sounds/demo-* + rm -f "${D}"var/lib/asterisk/agi-bin/* + else + einfo "Sample files have been installed" + keepdir /var/spool/asterisk/voicemail/default/1234/INBOX + fi + + # move sample configuration files to doc directory + if is_ast10update; then + elog "Updating from old (pre-1.2) asterisk version, new configuration files have been installed" + elog "into ${ROOT}etc/asterisk, use etc-update or dispatch-conf to update them" + elif has_version "net-misc/asterisk"; then + elog "Configuration samples have been moved to: $ROOT/usr/share/doc/${PF}/conf" + insinto /usr/share/doc/${PF}/conf + doins "${D}"etc/asterisk/*.conf* + rm -f "${D}"etc/asterisk/*.conf* 2>/dev/null + fi + + # don't delete these directories, even if they are empty + for x in voicemail meetme system dictate monitor tmp; do + keepdir /var/spool/asterisk/${x} + done + keepdir /var/lib/asterisk/sounds/priv-callerintros + keepdir /var/lib/asterisk/mohmp3 + keepdir /var/lib/asterisk/agi-bin + keepdir /var/log/asterisk/cdr-csv + keepdir /var/log/asterisk/cdr-custom + keepdir /var/run/asterisk + + # install astxs + dobin contrib/scripts/astxs + + newinitd "${FILESDIR}"/1.2.0/asterisk.rc6 asterisk + newconfd "${FILESDIR}"/1.2.0/asterisk.confd asterisk + + # install standard docs... + dodoc BUGS CREDITS LICENSE ChangeLog HARDWARE README README.fpm + dodoc SECURITY doc/CODING-GUIDELINES doc/linkedlists.README UPGRADE.txt + dodoc doc/README.* + dodoc doc/*.txt + + docinto scripts + dodoc contrib/scripts/* + + docinto utils + dodoc contrib/utils/* + + docinto configs + dodoc configs/* + + # install api docs + if use doc; then + insinto /usr/share/doc/${PF}/api/html + doins doc/api/html/* + fi + + # install ISDNguard + if use bri; then + cd "${S_BRI}"/ISDNguard + dosbin ISDNguard + + docinto ISDNguard + dodoc INSTALL.ISDNguard + + cd "${S}" + fi + + insinto /usr/share/doc/${PF}/cgi + doins contrib/scripts/vmail.cgi + doins images/*.gif + + # install asterisk-updater + dosbin "${FILESDIR}"/1.2.0/asterisk-updater + + # install asterisk.h, a lot of external modules need this + insinto /usr/include/asterisk + doins include/asterisk.h + + # make sure misdn/capi stuff is not installed, provided by asterisk-chan_.. + rm -f "${D}"/etc/asterisk/misdn.conf "${D}"/usr/lib/asterisk/modules/chan_misdn.so \ + "${D}"/usr/share/doc/${PF}/{conf/misdn.conf,configs/misdn.conf.sample.gz,README.misdn.gz} + rm -f "${D}"/usr/include/asterisk/chan_capi{,_app}.h \ + "${D}"/usr/share/doc/${PF}/{conf/capi.conf,configs/capi.conf.sample.gz} +} + +pkg_preinst() { + enewgroup asterisk + enewuser asterisk -1 -1 /var/lib/asterisk asterisk +} + +pkg_postinst() { + einfo "Fixing permissions" + chown -R asterisk:asterisk "${ROOT}"var/log/asterisk + chmod -R u=rwX,g=rX,o= "${ROOT}"var/log/asterisk + + for x in lib run spool; do + chown -R asterisk:asterisk "${ROOT}"var/${x}/asterisk + chmod -R u=rwX,g=rwX,o= "${ROOT}"var/${x}/asterisk + done + + chown asterisk:asterisk "${ROOT}"etc/asterisk/ + chown asterisk:asterisk "${ROOT}"etc/asterisk/*.adsi + chown asterisk:asterisk "${ROOT}"etc/asterisk/extensions.ael + chmod u=rwX,g=rwX,o= "${ROOT}"etc/asterisk/ + chmod u=rwX,g=rwX,o= "${ROOT}"etc/asterisk/*.adsi + chmod u=rwX,g=rwX,o= "${ROOT}"etc/asterisk/extensions.ael + echo + + # + # Announcements, warnings, reminders... + # + einfo "Asterisk has been installed" + einfo "" + elog "If you want to know more about asterisk, visit these sites:" + elog "http://www.asteriskdocs.org/" + elog "http://www.voip-info.org/wiki-Asterisk" + elog + elog "http://www.automated.it/guidetoasterisk.htm" + elog + elog "Gentoo VoIP IRC Channel:" + elog "#gentoo-voip @ irc.freenode.net" + elog + elog "Please note that AEL is no longer built because of security bugs" + elog "See http://bugs.gentoo.org/show_bug.cgi?id=171884" + elog + echo + echo + + # + # Warning about 1.0 -> 1.2 changes... + # + if is_ast10update; then + ewarn "" + ewarn "- Please read ${ROOT}usr/share/doc/${PF}/UPGRADE.txt.gz before continuing" + ewarn "" + fi + + if is_astupdate; then + ewarn "" + ewarn " - The initgroups patch has been dropped, please update your" + ewarn " \"conf.d/asterisk\" and \"init.d/asterisk\" file!" + ewarn "" + fi + + # scan for old modules + if is_ast10update; then + einfo "Asterisk has been updated from pre-1.2.x, scanning for old modules" + scan_modules + fi +} + +pkg_config() { + einfo "Do you want to reset file permissions and ownerships (y/N)?" + + read tmp + tmp="$(echo $tmp | tr [:upper:] [:lower:])" + + if [[ "$tmp" = "y" ]] ||\ + [[ "$tmp" = "yes" ]] + then + einfo "Resetting permissions to defaults..." + + for x in spool run lib log; do + chown -R asterisk:asterisk "${ROOT}"var/${x}/asterisk + chmod -R u=rwX,g=rX,o= "${ROOT}"var/${x}/asterisk + done + + chown -R root:asterisk "${ROOT}"etc/asterisk + chmod -R u=rwX,g=rX,o= "${ROOT}"etc/asterisk + + einfo "done" + else + einfo "skipping" + fi +} diff -ruN asterisk.original/files/1.2.0/backports/app_stack/app_stack2.c asterisk/files/1.2.0/backports/app_stack/app_stack2.c --- asterisk.original/files/1.2.0/backports/app_stack/app_stack2.c 1970-01-01 01:00:00.000000000 +0100 +++ asterisk/files/1.2.0/backports/app_stack/app_stack2.c 2008-03-03 20:05:51.000000000 +0100 @@ -0,0 +1,267 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (c) 2004-2006 Tilghman Lesher . + * + * This code is released by the author with no restrictions on usage. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Stack applications Gosub, Return, etc. + * + * \ingroup applications + */ + +#include +#include +#include +#include + +#include "asterisk/options.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/chanvars.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/config.h" +#include "asterisk/app.h" + +#define STACKVAR "~GOSUB2~STACK~" + +static const char *tdesc = "Stack Routines"; + +static const char *app_gosub = "Gosub2"; +static const char *app_gosubif = "GosubIf2"; +static const char *app_return = "Return2"; +static const char *app_pop = "StackPop2"; + +static const char *gosub_synopsis = "Jump to label, saving return address"; +static const char *gosubif_synopsis = "Jump to label, saving return address"; +static const char *return_synopsis = "Return from gosub routine"; +static const char *pop_synopsis = "Remove one address from gosub stack"; + +static const char *gosub_descrip = +"Gosub2([[context|]exten|]priority[(arg1[|...][|argN])])\n" +" Jumps to the label specified, saving the return address.\n"; +static const char *gosubif_descrip = +"GosubIf2(condition?labeliftrue[(arg1[|...])][:labeliffalse[(arg1[|...])]])\n" +" If the condition is true, then jump to labeliftrue. If false, jumps to\n" +"labeliffalse, if specified. In either case, a jump saves the return point\n" +"in the dialplan, to be returned to with a Return.\n"; +static const char *return_descrip = +"Return2([return-value])\n" +" Jumps to the last label on the stack, removing it. The return value, if\n" +"any, is saved in the channel variable GOSUB_RETVAL.\n"; +static const char *pop_descrip = +"StackPop2()\n" +" Removes last label on the stack, discarding it.\n"; + +STANDARD_LOCAL_USER; + +LOCAL_USER_DECL; + +static int pop_exec(struct ast_channel *chan, void *data) +{ + const char *frame = pbx_builtin_getvar_helper(chan, STACKVAR); + int numargs = 0, i; + char argname[15]; + + /* Pop any arguments for this stack frame off the variable stack */ + if (frame) { + numargs = atoi(frame); + for (i = 1; i <= numargs; i++) { + snprintf(argname, sizeof(argname), "ARG%d", i); + pbx_builtin_setvar_helper(chan, argname, NULL); + } + } + + /* Remove the last frame from the Gosub stack */ + pbx_builtin_setvar_helper(chan, STACKVAR, NULL); + + return 0; +} + +static int return_exec(struct ast_channel *chan, void *data) +{ + char *label = pbx_builtin_getvar_helper(chan, STACKVAR); + char argname[15], *retval = data; + int numargs, i; + + if (ast_strlen_zero(label)) { + ast_log(LOG_ERROR, "Return2 without Gosub2: stack is empty\n"); + return -1; + } + + /* Pop any arguments for this stack frame off the variable stack */ + numargs = atoi(label); + for (i = 1; i <= numargs; i++) { + snprintf(argname, sizeof(argname), "ARG%d", i); + pbx_builtin_setvar_helper(chan, argname, NULL); + } + + /* If the label exists, it will always have a ':' */ + label = strchr(label, ':') + 1; + + if (ast_parseable_goto(chan, label)) { + ast_log(LOG_WARNING, "No next statement after Gosub2?\n"); + return -1; + } + + /* Remove the current frame from the Gosub stack */ + pbx_builtin_setvar_helper(chan, STACKVAR, NULL); + + /* Set a return value, if any */ + pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", !ast_strlen_zero(retval) ? retval : ""); + return 0; +} + +static int gosub_exec(struct ast_channel *chan, void *data) +{ + char newlabel[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 11 + 11 + 4]; + char argname[15], *tmp = ast_strdupa(data), *label, *endparen; + int i; + struct localuser *u; + AST_DECLARE_APP_ARGS(args2, + AST_APP_ARG(argval)[100]; + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Gosub2 requires an argument: Gosub2([[context|]exten|]priority[(arg1[|...][|argN])])\n"); + return -1; + } + + LOCAL_USER_ADD(u); + + /* Separate the arguments from the label */ + /* NOTE: you cannot use ast_app_separate_args for this, because '(' cannot be used as a delimiter. */ + label = strsep(&tmp, "("); + if (tmp) { + endparen = strrchr(tmp, ')'); + if (endparen) + *endparen = '\0'; + else + ast_log(LOG_WARNING, "Ouch. No closing paren: '%s'?\n", (char *)data); + AST_STANDARD_APP_ARGS(args2, tmp); + } else + args2.argc = 0; + + /* Create the return address, but don't save it until we know that the Gosub destination exists */ + snprintf(newlabel, sizeof(newlabel), "%d:%s|%s|%d", args2.argc == 2 ? args2.argc : 0, chan->context, chan->exten, chan->priority + 1); + + if (ast_parseable_goto(chan, data)) { + ast_log(LOG_WARNING, "Destination '%s' does not exist in the dialplan\n", (char *)data); + LOCAL_USER_REMOVE(u); + return -1; + } + + /* Now that we know for certain that we're going to a new location, set our arguments */ + for (i = 0; i < args2.argc; i++) { + snprintf(argname, sizeof(argname), "ARG%d", i + 1); + pbx_builtin_pushvar_helper(chan, argname, args2.argval[i]); + ast_log(LOG_DEBUG, "Setting %s to '%s'\n", argname, args2.argval[i]); + } + + /* And finally, save our return address */ + pbx_builtin_pushvar_helper(chan, STACKVAR, newlabel); + ast_log(LOG_DEBUG, "Setting gosub return address to %s\n", newlabel); + + LOCAL_USER_REMOVE(u); + + return 0; +} + +static int gosubif_exec(struct ast_channel *chan, void *data) +{ + struct localuser *u; + char *args; + int res=0; + AST_DECLARE_APP_ARGS(cond, + AST_APP_ARG(ition); + AST_APP_ARG(labels); + ); + AST_DECLARE_APP_ARGS(label, + AST_APP_ARG(iftrue); + AST_APP_ARG(iffalse); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "GosubIf2 requires an argument: GosubIf2(cond?label1(args):label2(args)\n"); + return 0; + } + + LOCAL_USER_ADD(u); + + args = ast_strdupa((char *)data); + /* AST_NONSTANDARD_APP_ARGS(cond, args, '?'); */ + cond.argc = ast_app_separate_args(args, '?', cond.argv, (sizeof(cond) - sizeof(cond.argc)) / sizeof(cond.argv[0])); + if (cond.argc != 2) { + ast_log(LOG_WARNING, "GosubIf2 requires an argument: GosubIf2(cond?label1(args):label2(args)\n"); + LOCAL_USER_REMOVE(u); + return 0; + } + + /* AST_NONSTANDARD_APP_ARGS(label, cond.labels, ':'); */ + label.argc = ast_app_separate_args(cond.labels, ':', label.argv, (sizeof(label) - sizeof(label.argc)) / sizeof(label.argv[0])); + + if (pbx_checkcondition(cond.ition)) { + if (!ast_strlen_zero(label.iftrue)) + res = gosub_exec(chan, label.iftrue); + } else if (!ast_strlen_zero(label.iffalse)) { + res = gosub_exec(chan, label.iffalse); + } + + LOCAL_USER_REMOVE(u); + return res; +} + +int unload_module(void) +{ + ast_unregister_application(app_return); + ast_unregister_application(app_pop); + ast_unregister_application(app_gosubif); + ast_unregister_application(app_gosub); + + STANDARD_HANGUP_LOCALUSERS; + + return 0; +} + +int load_module(void) +{ + ast_register_application(app_pop, pop_exec, pop_synopsis, pop_descrip); + ast_register_application(app_return, return_exec, return_synopsis, return_descrip); + ast_register_application(app_gosubif, gosubif_exec, gosubif_synopsis, gosubif_descrip); + ast_register_application(app_gosub, gosub_exec, gosub_synopsis, gosub_descrip); + + return 0; +} + +char *description(void) +{ + return (char *) tdesc; +} + +int usecount(void) +{ + int res; + + STANDARD_USECOUNT(res); + + return res; +} + +char *key() +{ + return ASTERISK_GPL_KEY; +} diff -ruN asterisk.original/files/1.2.0/backports/app_stack/asterisk-app_stack2-makefile.diff asterisk/files/1.2.0/backports/app_stack/asterisk-app_stack2-makefile.diff --- asterisk.original/files/1.2.0/backports/app_stack/asterisk-app_stack2-makefile.diff 1970-01-01 01:00:00.000000000 +0100 +++ asterisk/files/1.2.0/backports/app_stack/asterisk-app_stack2-makefile.diff 2008-03-03 21:10:33.000000000 +0100 @@ -0,0 +1,11 @@ +--- asterisk-1.2.26.2-original/apps/Makefile 2006-04-30 15:38:22.000000000 +0200 ++++ asterisk-1.2.26.2/apps/Makefile 2008-03-03 21:07:33.000000000 +0100 +@@ -44,6 +44,8 @@ + #APPS+=app_skel.so + #APPS+=app_rpt.so + ++APPS+=app_stack2.so ++ + ifndef WITHOUT_ZAPTEL + ifneq ($(wildcard $(CROSS_COMPILE_TARGET)/usr/include/linux/zaptel.h)$(wildcard $(CROSS_COMPILE_TARGET)/usr/local/include/zaptel.h),) + APPS+=app_zapras.so app_meetme.so app_flash.so app_zapbarge.so app_zapscan.so app_page.so diff -ruN asterisk.original/files/1.2.0/backports/func_odbc/func_array.c asterisk/files/1.2.0/backports/func_odbc/func_array.c --- asterisk.original/files/1.2.0/backports/func_odbc/func_array.c 1970-01-01 01:00:00.000000000 +0100 +++ asterisk/files/1.2.0/backports/func_odbc/func_array.c 2008-03-03 19:17:40.000000000 +0100 @@ -0,0 +1,332 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005, Tilghman Lesher. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief ARRAY dialplan function + * + * \author Tilghman Lesher + */ + +#include +#include +#include +#include +#include + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 20003 $") + +#include "asterisk/module.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/logger.h" +#include "asterisk/utils.h" +#include "asterisk/app.h" +#include "asterisk/localtime.h" +#include "asterisk/linkedlists.h" + +#include "separate.h" + +#define HASH_PREFIX "~HASH~%s~" +#define HASH_FORMAT HASH_PREFIX "%s~" + +AST_MUTEX_DEFINE_STATIC(local_lock); +static int use_count = 0; + +static char *app_clearhash = "ClearHash"; +static char *syn_clearhash = "Clear the keys from a specified hashname"; +static char *desc_clearhash = +"ClearHash()\n" +" Clears all keys out of the specified hashname\n"; + +/* This function probably should migrate to main/pbx.c, as pbx_builtin_clearvar_prefix() */ +static void clearvar_prefix(struct ast_channel *chan, const char *prefix) +{ + struct ast_var_t *newvar; + int len = strlen(prefix); + AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->varshead, newvar, entries) { + if (strncasecmp(prefix, ast_var_name(newvar), len) == 0) { + AST_LIST_REMOVE_CURRENT(&chan->varshead, entries); + free(newvar); + } + } + AST_LIST_TRAVERSE_SAFE_END +} + +static int exec_clearhash(struct ast_channel *chan, void *data) +{ + char prefix[80]; + snprintf(prefix, sizeof(prefix), HASH_PREFIX, data ? (char *)data : "null"); + clearvar_prefix(chan, prefix); + return 0; +} + +static void array(struct ast_channel *chan, char *cmd, char *var, const char *value) +{ + AST_DECLARE_APP_ARGS(arg1, + AST_APP_ARG(var)[100]; + ); + AST_DECLARE_APP_ARGS(arg2, + AST_APP_ARG(val)[100]; + ); + char *origvar = "", *value2, varname[256]; + int i, ishash = 0; + + value2 = ast_strdupa(value); + if (!var || !value2) + return; + + ast_mutex_lock(&local_lock); + use_count++; + ast_mutex_unlock(&local_lock); + + if (!strcmp(cmd, "HASH")) { + const char *var2 = pbx_builtin_getvar_helper(chan, "~ODBCFIELDS~"); + origvar = var; + if (var2) + var = ast_strdupa(var2); + else + return; + ishash = 1; + } + + /* The functions this will generally be used with are SORT and ODBC_*, which + * both return comma-delimited lists. However, if somebody uses literal lists, + * their commas will be translated to vertical bars by the load, and I don't + * want them to be surprised by the result. Hence, we prefer commas as the + * delimiter, but we'll fall back to vertical bars if commas aren't found. + */ + ast_log(LOG_DEBUG, "array (%s=%s)\n", var, value2); + if (strchr(var, ',')) + TRUNK_NONSTANDARD_APP_ARGS(arg1, var, ','); + else + TRUNK_STANDARD_APP_ARGS(arg1, var); + + if (strchr(value2, ',')) + TRUNK_NONSTANDARD_APP_ARGS(arg2, value2, ','); + else + TRUNK_STANDARD_APP_ARGS(arg2, value2); + + for (i = 0; i < arg1.argc; i++) { + ast_log(LOG_DEBUG, "array set value (%s=%s)\n", arg1.var[i], + arg2.val[i]); + if (i < arg2.argc) { + if (ishash) { + snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]); + pbx_builtin_setvar_helper(chan, varname, arg2.val[i]); + } else { + pbx_builtin_setvar_helper(chan, arg1.var[i], arg2.val[i]); + } + } else { + /* We could unset the variable, by passing a NULL, but due to + * pushvar semantics, that could create some undesired behavior. */ + if (ishash) { + snprintf(varname, sizeof(varname), HASH_FORMAT, origvar, arg1.var[i]); + pbx_builtin_setvar_helper(chan, varname, ""); + } else { + pbx_builtin_setvar_helper(chan, arg1.var[i], ""); + } + } + } + + ast_mutex_lock(&local_lock); + use_count--; + ast_mutex_unlock(&local_lock); + + return; +} + +static char *hashkeys_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) +{ + struct ast_var_t *newvar; + int plen; + char prefix[80]; + snprintf(prefix, sizeof(prefix), HASH_PREFIX, data); + plen = strlen(prefix); + + memset(buf, 0, len); + AST_LIST_TRAVERSE(&chan->varshead, newvar, entries) { + if (strncasecmp(prefix, ast_var_name(newvar), plen) == 0) { + /* Copy everything after the prefix */ + strncat(buf, ast_var_name(newvar) + plen, len); + /* Trim the trailing ~ */ + buf[strlen(buf) - 1] = ','; + } + } + /* Trim the trailing comma */ + buf[strlen(buf) - 1] = '\0'; + return buf; +} + +static void hash_write(struct ast_channel *chan, char *cmd, char *var, const char *value) +{ + char varname[256]; + AST_DECLARE_APP_ARGS(arg, + AST_APP_ARG(hashname); + AST_APP_ARG(hashkey); + ); + + ast_mutex_lock(&local_lock); + use_count++; + ast_mutex_unlock(&local_lock); + + if (!strchr(var, '|')) { + /* Single argument version */ + array(chan, "HASH", var, value); + return; + } + + TRUNK_STANDARD_APP_ARGS(arg, var); + snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey); + pbx_builtin_setvar_helper(chan, varname, value); + + ast_mutex_lock(&local_lock); + use_count--; + ast_mutex_unlock(&local_lock); +} + +static char *hash_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) +{ + char varname[256]; + const char *varvalue; + AST_DECLARE_APP_ARGS(arg, + AST_APP_ARG(hashname); + AST_APP_ARG(hashkey); + ); + + ast_mutex_lock(&local_lock); + use_count++; + ast_mutex_unlock(&local_lock); + + TRUNK_STANDARD_APP_ARGS(arg, data); + if (arg.argc == 2) { + snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg.hashkey); + varvalue = pbx_builtin_getvar_helper(chan, varname); + if (varvalue) + ast_copy_string(buf, varvalue, len); + else + *buf = '\0'; + } else if (arg.argc == 1) { + char colnames[4096]; + int i; + AST_DECLARE_APP_ARGS(arg2, + AST_APP_ARG(col)[100]; + ); + + /* Get column names, in no particular order */ + hashkeys_read(chan, "HASHKEYS", arg.hashname, colnames, sizeof(colnames)); + pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames); + + TRUNK_NONSTANDARD_APP_ARGS(arg2, colnames, ','); + *buf = '\0'; + + /* Now get the corresponding column values, in exactly the same order */ + for (i = 0; i < arg2.argc; i++) { + snprintf(varname, sizeof(varname), HASH_FORMAT, arg.hashname, arg2.col[i]); + varvalue = pbx_builtin_getvar_helper(chan, varname); + strncat(buf, varvalue, len); + strncat(buf, ",", len); + } + + /* Strip trailing comma */ + buf[strlen(buf) - 1] = '\0'; + } + + return buf; +} + +static struct ast_custom_function array_function = { + .name = "ARRAY", + .synopsis = "Allows setting multiple variables at once", + .syntax = "ARRAY(var1[|var2[...][|varN]])", + .write = array, + .desc = + "The comma-separated list passed as a value to which the function is set will\n" + "be interpreted as a set of values to which the comma-separated list of\n" + "variable names in the argument should be set.\n" + "Hence, Set(ARRAY(var1,var2)=1,2) will set var1 to 1 and var2 to 2\n" + "Note: remember to either backslash your commas in extensions.conf or quote the\n" + "entire argument, since Set can take multiple arguments itself.\n", +}; + +static struct ast_custom_function hash_function = { + .name = "HASH", + .synopsis = "Implementation of a dialplan associative array", + .syntax = "HASH(hashname[|hashkey])", + .write = hash_write, + .read = hash_read, + .desc = + "In two argument mode, gets and sets values to corresponding keys within a named\n" + "associative array. The single-argument mode will only work when assigned to from\n" + "a function defined by func_odbc.so.\n", +}; + +static struct ast_custom_function hashkeys_function = { + .name = "HASHKEYS", + .synopsis = "Retrieve the keys of a HASH()", + .syntax = "HASHKEYS()", + .read = hashkeys_read, + .desc = + "Returns a comma-delimited list of the current keys of an associative array\n" + "defined by the HASH() function. Note that if you iterate over the keys of\n" + "the result, adding keys during iteration will cause the result of the HASHKEYS\n" + "function to change.\n", +}; + +static char *tdesc = "String handling dialplan functions"; + +int unload_module(void) +{ + int res = 0; + + res |= ast_custom_function_unregister(&array_function); + res |= ast_custom_function_unregister(&hash_function); + res |= ast_custom_function_unregister(&hashkeys_function); + res |= ast_unregister_application(app_clearhash); + sched_yield(); /* Any remaining process gets time to clear out. Increases safety if a force unload is attempted. */ + + return res; +} + +int load_module(void) +{ + int res = 0; + + res |= ast_custom_function_register(&array_function); + res |= ast_custom_function_register(&hash_function); + res |= ast_custom_function_register(&hashkeys_function); + res |= ast_register_application(app_clearhash, exec_clearhash, syn_clearhash, desc_clearhash); + + return res; +} + +char *description(void) +{ + return tdesc; +} + +int usecount(void) +{ + return use_count; +} + +char *key(void) +{ + return ASTERISK_GPL_KEY; +} + diff -ruN asterisk.original/files/1.2.0/backports/func_odbc/func_odbc.c asterisk/files/1.2.0/backports/func_odbc/func_odbc.c --- asterisk.original/files/1.2.0/backports/func_odbc/func_odbc.c 1970-01-01 01:00:00.000000000 +0100 +++ asterisk/files/1.2.0/backports/func_odbc/func_odbc.c 2008-03-03 19:18:11.000000000 +0100 @@ -0,0 +1,800 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (c) 2005 Tilghman Lesher + * + * Tilghman Lesher + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * + * \brief ODBC lookups + * + * \author Tilghman Lesher + */ + +#include +#include +#include +#include +#include +#include + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/config.h" +#include "asterisk/res_odbc.h" +#include "asterisk/app.h" + +#include "separate.h" + +static char *tdesc = "ODBC lookups"; + +static char *config = "func_odbc.conf"; + +enum { + OPT_ESCAPECOMMAS = (1 << 0), +} odbc_option_flags; + +struct acf_odbc_query { + AST_LIST_ENTRY(acf_odbc_query) list; + char readhandle[5][30]; + char writehandle[5][30]; + char sql_read[2048]; + char sql_write[2048]; + unsigned int flags; + struct ast_custom_function *acf; +}; + +AST_LIST_HEAD_STATIC(queries, acf_odbc_query); + +#ifdef NEEDTRACE +static void acf_odbc_error(SQLHSTMT stmt, int res) +{ + char state[10] = "", diagnostic[256] = ""; + SQLINTEGER nativeerror = 0; + SQLSMALLINT diagbytes = 0; + SQLGetDiagRec(SQL_HANDLE_STMT, stmt, 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes); + ast_log(LOG_WARNING, "SQL return value %d: error %s: %s (len %d)\n", res, state, diagnostic, diagbytes); +} +#endif + +/* + * Master control routine + */ +#define INCREMENT_DSN if (++dsn < 5 && !ast_strlen_zero(query->writehandle[dsn])) goto retry_write +static void acf_odbc_write(struct ast_channel *chan, char *cmd, char *data, const char *value) +{ + odbc_obj *obj; + struct acf_odbc_query *query; + char *s, *t, buf[2048]="", varname[15]; + int res, i, dsn = 0, alloc, prepare, execute; + SQLHSTMT stmt; + SQLINTEGER nativeerror=0, numfields=0, rows=0; + SQLSMALLINT diagbytes=0; + unsigned char state[10], diagnostic[256]; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(arg)[100]; + ); + AST_DECLARE_APP_ARGS(values, + AST_APP_ARG(val)[100]; + ); +#ifdef NEEDTRACE + SQLINTEGER enable = 1; + char *tracefile = "/tmp/odbc.trace"; +#endif + + AST_LIST_LOCK(&queries); + AST_LIST_TRAVERSE(&queries, query, list) { + if (!strcmp(query->acf->name, cmd)) { + break; + } + } + + if (!query) { + ast_log(LOG_ERROR, "No such function '%s'\n", cmd); + AST_LIST_UNLOCK(&queries); + return; + } + + /* Parse our arguments */ + s = ast_strdupa(data); + if (value) { + t = ast_strdupa(value); + } else { + t = ""; + } + + if (!s || !t) { + ast_log(LOG_ERROR, "Out of memory\n"); + AST_LIST_UNLOCK(&queries); + return; + } + + TRUNK_STANDARD_APP_ARGS(args, s); + for (i = 0; i < args.argc; i++) { + snprintf(varname, sizeof(varname), "ARG%d", i + 1); + pbx_builtin_pushvar_helper(chan, varname, args.arg[i]); + } + + TRUNK_NONSTANDARD_APP_ARGS(values, t, ','); + for (i = 0; i < values.argc; i++) { + snprintf(varname, sizeof(varname), "VAL%d", i + 1); + pbx_builtin_pushvar_helper(chan, varname, values.val[i]); + } + + /* Additionally set the value as a whole */ + /* Note that pbx_builtin_setvar_helper will quite happily take a NULL for the 3rd argument */ + pbx_builtin_pushvar_helper(chan, "VALUE", value ? value : ""); + + pbx_substitute_variables_helper(chan, query->sql_write, buf, sizeof(buf) - 1); + + /* Restore prior values */ + for (i = 0; i < args.argc; i++) { + snprintf(varname, sizeof(varname), "ARG%d", i + 1); + pbx_builtin_setvar_helper(chan, varname, NULL); + } + + for (i = 0; i < values.argc; i++) { + snprintf(varname, sizeof(varname), "VAL%d", i + 1); + pbx_builtin_setvar_helper(chan, varname, NULL); + } + pbx_builtin_setvar_helper(chan, "VALUE", NULL); + + AST_LIST_UNLOCK(&queries); + +retry_write: + obj = fetch_odbc_obj(query->writehandle[dsn], 0); + + if (!obj) { + INCREMENT_DSN; + ast_log(LOG_ERROR, "Unable to load ODBC write class (check res_odbc.conf)\n"); + return; + } + +#ifdef NEEDTRACE + SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER); + SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile)); +#endif + + for (execute = 0; execute < 2; execute++) { + for (prepare = 0; prepare < 2; prepare++) { + for (alloc = 0; alloc < 2; alloc++) { + res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt); + if ((res == SQL_SUCCESS) || (res == SQL_SUCCESS_WITH_INFO)) + break; + else if (alloc == 0) + odbc_sanity_check(obj); + else { + INCREMENT_DSN; + ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n"); + pbx_builtin_setvar_helper(chan, "ODBCROWS", "-1"); + return; + } + } + + res = SQLPrepare(stmt, (unsigned char *)buf, SQL_NTS); + if ((res == SQL_SUCCESS) || (res == SQL_SUCCESS_WITH_INFO)) + break; + else if (prepare == 0) + odbc_sanity_check(obj); + else { + INCREMENT_DSN; + ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", buf); + SQLCloseCursor(stmt); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + pbx_builtin_setvar_helper(chan, "ODBCROWS", "-1"); + return; + } + } + + res = SQLExecute(stmt); + if ((res == SQL_SUCCESS) || (res == SQL_SUCCESS_WITH_INFO)) { + /* Rows affected */ + SQLRowCount(stmt, &rows); + break; + } else if (execute == 0) + odbc_sanity_check(obj); + else { + if (res == SQL_ERROR) { + SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes); + for (i = 0; i <= numfields; i++) { + SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes); + ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes); + if (i > 10) { + ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields); + break; + } + } + } + INCREMENT_DSN; + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", buf); + } + rows = -1; + } + } + + /* Output the affected rows, for all cases. In the event of failure, we + * flag this as -1 rows. Note that this is different from 0 affected rows + * which would be the case if we succeeded in our query, but the values did + * not change. */ + snprintf(varname, sizeof(varname), "%d", (int)rows); + pbx_builtin_setvar_helper(chan, "ODBCROWS", varname); + + SQLCloseCursor(stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); +} +#undef INCREMENT_DSN + +#define INCREMENT_DSN if (++dsn < 5 && !ast_strlen_zero(query->readhandle[dsn])) goto restartread; +static char *acf_odbc_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) +{ + odbc_obj *obj; + struct acf_odbc_query *query; + char *s, sql[2048] = "", varname[15], colnames[2048] = ""; + int res, x, buflen = 0, dsn = 0, escapecommas, alloc, prepare, execute; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(arg)[100]; + ); + SQLHSTMT stmt; + SQLSMALLINT colcount=0; + SQLINTEGER indicator; + SQLSMALLINT collength; +#ifdef NEEDTRACE + SQLINTEGER enable = 1; + char *tracefile = "/tmp/odbc.trace"; +#endif + + AST_LIST_LOCK(&queries); + AST_LIST_TRAVERSE(&queries, query, list) { + if (!strcmp(query->acf->name, cmd)) { + break; + } + } + + if (!query) { + ast_log(LOG_ERROR, "No such function '%s'\n", cmd); + AST_LIST_UNLOCK(&queries); + return ""; + } + + /* Parse our arguments */ + if (!(s = ast_strdupa(data))) { + AST_LIST_UNLOCK(&queries); + return ""; + } + + TRUNK_STANDARD_APP_ARGS(args, s); + for (x = 0; x < args.argc; x++) { + snprintf(varname, sizeof(varname), "ARG%d", x + 1); + pbx_builtin_pushvar_helper(chan, varname, args.arg[x]); + } + + pbx_substitute_variables_helper(chan, query->sql_read, sql, sizeof(sql) - 1); + + /* Restore prior values */ + for (x = 0; x < args.argc; x++) { + snprintf(varname, sizeof(varname), "ARG%d", x + 1); + pbx_builtin_setvar_helper(chan, varname, NULL); + } + + /* Save this flag, so we can release the lock */ + escapecommas = ast_test_flag(query, OPT_ESCAPECOMMAS); + + AST_LIST_UNLOCK(&queries); + +restartread: + obj = fetch_odbc_obj(query->readhandle[dsn], 0); + + if (!obj) { + INCREMENT_DSN; + ast_log(LOG_ERROR, "Unable to load ODBC read class (check res_odbc.conf)\n"); + return ""; + } + +#ifdef NEEDTRACE + SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER); + SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile)); +#endif + + for (execute = 0; execute < 2; execute++) { + for (prepare = 0; prepare < 2; prepare++) { + for (alloc = 0; alloc < 2; alloc++) { + res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt); + if ((res == SQL_SUCCESS) || (res == SQL_SUCCESS_WITH_INFO)) + break; + else if (alloc == 0) + odbc_sanity_check(obj); + else { + INCREMENT_DSN; + ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n"); + return ""; + } + } + + res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS); + if ((res == SQL_SUCCESS) || (res == SQL_SUCCESS_WITH_INFO)) + break; + else if (prepare == 0) + odbc_sanity_check(obj); + else { + INCREMENT_DSN; + SQLCloseCursor(stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql); + return ""; + } + } + + res = odbc_smart_execute(obj, stmt); + if ((res == SQL_SUCCESS) || (res == SQL_SUCCESS_WITH_INFO)) + break; + else if (execute == 0) + odbc_sanity_check(obj); + else { + INCREMENT_DSN; + SQLCloseCursor(stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql); + return ""; + } + } + + res = SQLNumResultCols(stmt, &colcount); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql); + SQLCloseCursor(stmt); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + return ""; + } + + memset(buf, 0, len); + + res = SQLFetch(stmt); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + if (res == SQL_NO_DATA) { + if (option_verbose > 3) { + ast_verbose(VERBOSE_PREFIX_4 "Found no rows [%s]\n", sql); + } + } else if (option_verbose > 3) { + ast_log(LOG_WARNING, "Error %d in FETCH [%s]\n", res, sql); + } + goto acf_out; + } + + for (x = 0; x < colcount; x++) { + int i, namelen; + char coldata[256], colname[256]; + + res = SQLDescribeCol(stmt, x + 1, (unsigned char *)colname, sizeof(colname), &collength, NULL, NULL, NULL, NULL); + if (((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) || collength == 0) { + snprintf(colname, sizeof(colname), "field%d", x); + } + + if (!ast_strlen_zero(colnames)) + strncat(colnames, ",", sizeof(colnames) - 1); + namelen = strlen(colnames); + + /* Copy data, encoding '\' and ',' for the argument parser */ + for (i = 0; i < sizeof(colname); i++) { + if (escapecommas && (colname[i] == '\\' || colname[i] == ',')) { + colnames[namelen++] = '\\'; + } + colnames[namelen++] = colname[i]; + + if (namelen >= sizeof(colnames) - 2) { + colnames[namelen >= sizeof(colnames) ? sizeof(colnames) - 1 : namelen] = '\0'; + break; + } + + if (colname[i] == '\0') + break; + } + + buflen = strlen(buf); + res = SQLGetData(stmt, x + 1, SQL_CHAR, coldata, sizeof(coldata), &indicator); + if (indicator == SQL_NULL_DATA) { + coldata[0] = '\0'; + res = SQL_SUCCESS; + } + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql); + SQLCloseCursor(stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return ""; + } + + /* Copy data, encoding '\' and ',' for the argument parser */ + for (i = 0; i < sizeof(coldata); i++) { + if (escapecommas && (coldata[i] == '\\' || coldata[i] == ',')) { + buf[buflen++] = '\\'; + } + buf[buflen++] = coldata[i]; + + if (buflen >= len - 2) { + buf[buflen >= len ? len - 1 : buflen] = '\0'; + break; + } + + if (coldata[i] == '\0') + break; + } + + buf[buflen - 1] = ','; + } + /* Trim trailing comma */ + buf[buflen - 1] = '\0'; + + pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", colnames); + +acf_out: + SQLCloseCursor(stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return buf; +} +#undef INCREMENT_DSN + +static char *acf_escape(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) +{ + char *in, *out = buf; + for (in = data; *in && out - buf < len; in++) { + if (*in == '\'') { + *out = '\''; + out++; + } + *out = *in; + out++; + } + *out = '\0'; + return buf; +} + +static struct ast_custom_function escape_function = { + .name = "SQL_ESC", + .synopsis = "Escapes single ticks for use in SQL statements", + .syntax = "SQL_ESC()", + .desc = +"Used in SQL templates to escape data which may contain single ticks (') which\n" +"are otherwise used to delimit data. For example:\n" +"SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'\n", + .read = acf_escape, + .write = NULL, +}; + + +static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query) +{ + const char *tmp; + int i; + + if (!cfg || !catg) { + return -1; + } + + *query = calloc(1, sizeof(struct acf_odbc_query)); + if (! (*query)) + return ENOMEM; + + if (((tmp = ast_variable_retrieve(cfg, catg, "writehandle"))) || ((tmp = ast_variable_retrieve(cfg, catg, "dsn")))) { + char *tmp2 = ast_strdupa(tmp); + AST_DECLARE_APP_ARGS(write, + AST_APP_ARG(dsn)[5]; + ); + TRUNK_NONSTANDARD_APP_ARGS(write, tmp2, ','); + for (i = 0; i < 5; i++) { + if (!ast_strlen_zero(write.dsn[i])) + ast_copy_string((*query)->writehandle[i], write.dsn[i], sizeof((*query)->writehandle[i])); + } + } + + if ((tmp = ast_variable_retrieve(cfg, catg, "readhandle"))) { + char *tmp2 = ast_strdupa(tmp); + AST_DECLARE_APP_ARGS(read, + AST_APP_ARG(dsn)[5]; + ); + TRUNK_NONSTANDARD_APP_ARGS(read, tmp2, ','); + for (i = 0; i < 5; i++) { + if (!ast_strlen_zero(read.dsn[i])) + ast_copy_string((*query)->readhandle[i], read.dsn[i], sizeof((*query)->readhandle[i])); + } + } else { + /* If no separate readhandle, then use the writehandle for reading */ + for (i = 0; i < 5; i++) { + if (!ast_strlen_zero((*query)->writehandle[i])) + ast_copy_string((*query)->readhandle[i], (*query)->writehandle[i], sizeof((*query)->readhandle[i])); + } + } + + if ((tmp = ast_variable_retrieve(cfg, catg, "read"))) { + ast_log(LOG_WARNING, "Parameter 'read' is deprecated for category %s. Please use 'readsql' instead.\n", catg); + ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read)); + } else if ((tmp = ast_variable_retrieve(cfg, catg, "readsql"))) + ast_copy_string((*query)->sql_read, tmp, sizeof((*query)->sql_read)); + + if (!ast_strlen_zero((*query)->sql_read) && ast_strlen_zero((*query)->readhandle[0])) { + free(*query); + *query = NULL; + ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for reading: %s\n", catg); + return EINVAL; + } + + if ((tmp = ast_variable_retrieve(cfg, catg, "write"))) { + ast_log(LOG_WARNING, "Parameter 'write' is deprecated for category %s. Please use 'writesql' instead.\n", catg); + ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write)); + } else if ((tmp = ast_variable_retrieve(cfg, catg, "writesql"))) + ast_copy_string((*query)->sql_write, tmp, sizeof((*query)->sql_write)); + + if (!ast_strlen_zero((*query)->sql_write) && ast_strlen_zero((*query)->writehandle[0])) { + free(*query); + *query = NULL; + ast_log(LOG_ERROR, "There is SQL, but no ODBC class to be used for writing: %s\n", catg); + return EINVAL; + } + + /* Allow escaping of embedded commas in fields to be turned off */ + ast_set_flag((*query), OPT_ESCAPECOMMAS); + if ((tmp = ast_variable_retrieve(cfg, catg, "escapecommas"))) { + if (ast_false(tmp)) + ast_clear_flag((*query), OPT_ESCAPECOMMAS); + } + + (*query)->acf = calloc(1, sizeof(struct ast_custom_function)); + if (! (*query)->acf) { + free(*query); + *query = NULL; + return ENOMEM; + } + + if ((tmp = ast_variable_retrieve(cfg, catg, "prefix")) && !ast_strlen_zero(tmp)) { + asprintf((char **)&((*query)->acf->name), "%s_%s", tmp, catg); + } else { + asprintf((char **)&((*query)->acf->name), "ODBC_%s", catg); + } + + if (!((*query)->acf->name)) { + free((*query)->acf); + free(*query); + *query = NULL; + return ENOMEM; + } + + asprintf((char **)&((*query)->acf->syntax), "%s([...[,]])", (*query)->acf->name); + + if (!((*query)->acf->syntax)) { + free((char *)(*query)->acf->name); + free((*query)->acf); + free(*query); + *query = NULL; + return ENOMEM; + } + + (*query)->acf->synopsis = "Runs the referenced query with the specified arguments"; + if (!ast_strlen_zero((*query)->sql_read) && !ast_strlen_zero((*query)->sql_write)) { + asprintf((char **)&((*query)->acf->desc), + "Runs the following query, as defined in func_odbc.conf, performing\n" + "substitution of the arguments into the query as specified by ${ARG1},\n" + "${ARG2}, ... ${ARGn}. When setting the function, the values are provided\n" + "either in whole as ${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n" + "\nRead:\n%s\n\nWrite:\n%s\n", + (*query)->sql_read, + (*query)->sql_write); + } else if (!ast_strlen_zero((*query)->sql_read)) { + asprintf((char **)&((*query)->acf->desc), + "Runs the following query, as defined in func_odbc.conf, performing\n" + "substitution of the arguments into the query as specified by ${ARG1},\n" + "${ARG2}, ... ${ARGn}. This function may only be read, not set.\n\nSQL:\n%s\n", + (*query)->sql_read); + } else if (!ast_strlen_zero((*query)->sql_write)) { + asprintf((char **)&((*query)->acf->desc), + "Runs the following query, as defined in func_odbc.conf, performing\n" + "substitution of the arguments into the query as specified by ${ARG1},\n" + "${ARG2}, ... ${ARGn}. The values are provided either in whole as\n" + "${VALUE} or parsed as ${VAL1}, ${VAL2}, ... ${VALn}.\n" + "This function may only be set.\nSQL:\n%s\n", + (*query)->sql_write); + } else { + free((char *)(*query)->acf->syntax); + free((char *)(*query)->acf->name); + free((*query)->acf); + free(*query); + *query = NULL; + ast_log(LOG_WARNING, "Section %s was found, but there was no SQL to execute. Ignoring.\n", catg); + return EINVAL; + } + + /* Could be out of memory, or could be we have neither sql_read nor sql_write */ + if (! ((*query)->acf->desc)) { + free((char *)(*query)->acf->syntax); + free((char *)(*query)->acf->name); + free((*query)->acf); + free(*query); + *query = NULL; + return ENOMEM; + } + + if (ast_strlen_zero((*query)->sql_read)) { + (*query)->acf->read = NULL; + } else { + (*query)->acf->read = acf_odbc_read; + } + + if (ast_strlen_zero((*query)->sql_write)) { + (*query)->acf->write = NULL; + } else { + (*query)->acf->write = acf_odbc_write; + } + + return 0; +} + +static int free_acf_query(struct acf_odbc_query *query) +{ + if (query) { + if (query->acf) { + if (query->acf->name) + free(query->acf->name); + if (query->acf->syntax) + free(query->acf->syntax); + if (query->acf->desc) + free(query->acf->desc); + free(query->acf); + } + free(query); + } + return 0; +} + +static int odbc_load_module(void) +{ + int res = 0; + struct ast_config *cfg; + char *catg; + + AST_LIST_LOCK(&queries); + + cfg = ast_config_load(config); + if (!cfg) { + ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config); + AST_LIST_UNLOCK(&queries); + return -1; + } + + for (catg = ast_category_browse(cfg, NULL); + catg; + catg = ast_category_browse(cfg, catg)) { + struct acf_odbc_query *query = NULL; + int err; + + if ((err = init_acf_query(cfg, catg, &query))) { + if (err == ENOMEM) + ast_log(LOG_ERROR, "Out of memory\n"); + else if (err == EINVAL) + ast_log(LOG_ERROR, "Invalid parameters for category %s\n", catg); + else + ast_log(LOG_ERROR, "%s (%d)\n", strerror(err), err); + } else { + AST_LIST_INSERT_HEAD(&queries, query, list); + ast_custom_function_register(query->acf); + } + } + + ast_config_destroy(cfg); + ast_custom_function_register(&escape_function); + + AST_LIST_UNLOCK(&queries); + return res; +} + +static int odbc_unload_module(void) +{ + struct acf_odbc_query *query; + + AST_LIST_LOCK(&queries); + while (!AST_LIST_EMPTY(&queries)) { + query = AST_LIST_REMOVE_HEAD(&queries, list); + ast_custom_function_unregister(query->acf); + free_acf_query(query); + } + + ast_custom_function_unregister(&escape_function); + + /* Allow any threads waiting for this lock to pass (avoids a race) */ + AST_LIST_UNLOCK(&queries); + AST_LIST_LOCK(&queries); + + AST_LIST_UNLOCK(&queries); + return 0; +} + +int reload(void) +{ + int res = 0; + struct ast_config *cfg; + struct acf_odbc_query *oldquery; + char *catg; + + AST_LIST_LOCK(&queries); + + while (!AST_LIST_EMPTY(&queries)) { + oldquery = AST_LIST_REMOVE_HEAD(&queries, list); + ast_custom_function_unregister(oldquery->acf); + free_acf_query(oldquery); + } + + cfg = ast_config_load(config); + if (!cfg) { + ast_log(LOG_WARNING, "Unable to load config for func_odbc: %s\n", config); + goto reload_out; + } + + for (catg = ast_category_browse(cfg, NULL); + catg; + catg = ast_category_browse(cfg, catg)) { + struct acf_odbc_query *query = NULL; + + if (init_acf_query(cfg, catg, &query)) { + ast_log(LOG_ERROR, "Cannot initialize query %s\n", catg); + } else { + AST_LIST_INSERT_HEAD(&queries, query, list); + ast_custom_function_register(query->acf); + } + } + + ast_config_destroy(cfg); +reload_out: + AST_LIST_UNLOCK(&queries); + return res; +} + +int unload_module(void) +{ + return odbc_unload_module(); +} + +int load_module(void) +{ + return odbc_load_module(); +} + +char *description(void) +{ + return tdesc; +} + +int usecount(void) +{ + if (! ast_mutex_trylock(&(&queries)->lock)) { + ast_mutex_unlock(&(&queries)->lock); + return 0; + } else { + return 1; + } +} + +char *key() +{ + return ASTERISK_GPL_KEY; +} diff -ruN asterisk.original/files/1.2.0/backports/func_odbc/func_odbc.conf.sample asterisk/files/1.2.0/backports/func_odbc/func_odbc.conf.sample --- asterisk.original/files/1.2.0/backports/func_odbc/func_odbc.conf.sample 1970-01-01 01:00:00.000000000 +0100 +++ asterisk/files/1.2.0/backports/func_odbc/func_odbc.conf.sample 2008-03-03 19:18:45.000000000 +0100 @@ -0,0 +1,43 @@ +; +; func_odbc.conf +; +; Each context is a separately defined function. By convention, all +; functions are entirely uppercase, so the defined contexts should also +; be all-uppercase, but there is nothing that enforces this. All functions +; are case-sensitive, however. +; +; For substitution, you have ${ARG1}, ${ARG2} ... ${ARGn} +; for the arguments to each SQL statement. +; +; In addition, for write statements, you have ${VAL1}, ${VAL2} ... ${VALn} +; parsed, just like arguments, for the values. In addition, if you want the +; whole value, never mind the parsing, you can get that with ${VALUE}. +; +; +; If you have data which may potentially contain single ticks, you may wish +; to use the dialplan function SQL_ESC() to escape the data prior to its +; inclusion in the SQL statement. + + +; ODBC_SQL - Allow an SQL statement to be built entirely in the dialplan +[SQL] +readhandle=mysql1 +readsql=${ARG1} + +; ODBC_ANTIGF - A blacklist. +[ANTIGF] +readhandle=mysql1 +readsql=SELECT COUNT(*) FROM exgirlfriends WHERE callerid='${SQL_ESC(${ARG1})}' + +; ODBC_PRESENCE - Retrieve and update presence +[PRESENCE] +readhandle=mysql1 +writehandle=mysql1 +readsql=SELECT location FROM presence WHERE id='${SQL_ESC(${ARG1})}' +writesql=UPDATE presence SET location='${SQL_ESC(${VAL1})}' WHERE id='${SQL_ESC(${ARG1})}' +; +;prefix=OFFICE ; Changes this function from ODBC_PRESENCE to OFFICE_PRESENCE +;escapecommas=no ; Normally, commas within a field are escaped such that each + ; field may be separated into individual variables with ARRAY or HASH. + ; This option turns that behavior off [default=yes]. + diff -ruN asterisk.original/files/1.2.0/backports/func_odbc/separate.h asterisk/files/1.2.0/backports/func_odbc/separate.h --- asterisk.original/files/1.2.0/backports/func_odbc/separate.h 1970-01-01 01:00:00.000000000 +0100 +++ asterisk/files/1.2.0/backports/func_odbc/separate.h 2008-03-03 19:19:17.000000000 +0100 @@ -0,0 +1,47 @@ +#define TRUNK_STANDARD_APP_ARGS(args, parse) \ + args.argc = trunk_app_separate_args(parse, '|', args.argv, (sizeof(args) - sizeof(args.argc)) / sizeof(args.argv[0])) +#define TRUNK_NONSTANDARD_APP_ARGS(args, parse, sep) \ + args.argc = trunk_app_separate_args(parse, sep, args.argv, (sizeof(args) - sizeof(args.argc)) / sizeof(args.argv[0])) + +static unsigned int trunk_app_separate_args(char *buf, char delim, char **array, int arraylen) +{ + int argc; + char *scan; + int paren = 0, quote = 0; + + if (!buf || !array || !arraylen) + return 0; + + memset(array, 0, arraylen * sizeof(*array)); + + scan = buf; + + for (argc = 0; *scan && (argc < arraylen - 1); argc++) { + array[argc] = scan; + for (; *scan; scan++) { + if (*scan == '(') + paren++; + else if (*scan == ')') { + if (paren) + paren--; + } else if (*scan == '"') { + quote = quote ? 0 : 1; + /* Remove quote character from argument */ + memmove(scan, scan + 1, strlen(scan)); + scan--; + } else if (*scan == '\\') { + /* Literal character, don't parse */ + memmove(scan, scan + 1, strlen(scan)); + } else if ((*scan == delim) && !paren && !quote) { + *scan++ = '\0'; + break; + } + } + } + + if (*scan) + array[argc++] = scan; + + return argc; +} +