Thu Apr 28 2011 17:15:14

Asterisk developer's documentation


app_disa.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  *
00006  *
00007  * Made only slightly more sane by Mark Spencer <markster@digium.com>
00008  *
00009  * See http://www.asterisk.org for more information about
00010  * the Asterisk project. Please do not directly contact
00011  * any of the maintainers of this project for assistance;
00012  * the project provides a web site, mailing lists and IRC
00013  * channels for your use.
00014  *
00015  * This program is free software, distributed under the terms of
00016  * the GNU General Public License Version 2. See the LICENSE file
00017  * at the top of the source tree.
00018  */
00019 
00020 /*! \file
00021  *
00022  * \brief DISA -- Direct Inward System Access Application
00023  *
00024  * \author Jim Dixon <jim@lambdatel.com>
00025  *
00026  * \ingroup applications
00027  */
00028 
00029 #include "asterisk.h"
00030 
00031 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 220292 $")
00032 
00033 #include <math.h>
00034 #include <sys/time.h>
00035 
00036 #include "asterisk/lock.h"
00037 #include "asterisk/file.h"
00038 #include "asterisk/channel.h"
00039 #include "asterisk/app.h"
00040 #include "asterisk/indications.h"
00041 #include "asterisk/pbx.h"
00042 #include "asterisk/module.h"
00043 #include "asterisk/translate.h"
00044 #include "asterisk/ulaw.h"
00045 #include "asterisk/callerid.h"
00046 #include "asterisk/stringfields.h"
00047 
00048 /*** DOCUMENTATION
00049    <application name="DISA" language="en_US">
00050       <synopsis>
00051          Direct Inward System Access.
00052       </synopsis>
00053       <syntax>
00054          <parameter name="passcode|filename" required="true">
00055             <para>If you need to present a DISA dialtone without entering a password,
00056             simply set <replaceable>passcode</replaceable> to <literal>no-password</literal></para>
00057             <para>You may specified a <replaceable>filename</replaceable> instead of a
00058             <replaceable>passcode</replaceable>, this filename must contain individual passcodes</para>
00059          </parameter>
00060          <parameter name="context">
00061             <para>Specifies the dialplan context in which the user-entered extension
00062             will be matched. If no context is specified, the DISA application defaults
00063             to the <literal>disa</literal> context. Presumably a normal system will have a special
00064             context set up for DISA use with some or a lot of restrictions.</para>
00065          </parameter>
00066          <parameter name="cid">
00067             <para>Specifies a new (different) callerid to be used for this call.</para>
00068          </parameter>
00069          <parameter name="mailbox" argsep="@">
00070             <para>Will cause a stutter-dialtone (indication <emphasis>dialrecall</emphasis>)
00071             to be used, if the specified mailbox contains any new messages.</para>
00072             <argument name="mailbox" required="true" />
00073             <argument name="context" required="false" />
00074          </parameter>
00075          <parameter name="options">
00076             <optionlist>
00077                <option name="n">
00078                   <para>The DISA application will not answer initially.</para>
00079                </option>
00080                <option name="p">
00081                   <para>The extension entered will be considered complete when a <literal>#</literal>
00082                   is entered.</para>
00083                </option>
00084             </optionlist>
00085          </parameter>
00086       </syntax>
00087       <description>
00088          <para>The DISA, Direct Inward System Access, application allows someone from
00089          outside the telephone switch (PBX) to obtain an <emphasis>internal</emphasis> system
00090          dialtone and to place calls from it as if they were placing a call from
00091          within the switch.
00092          DISA plays a dialtone. The user enters their numeric passcode, followed by
00093          the pound sign <literal>#</literal>. If the passcode is correct, the user is then given
00094          system dialtone within <replaceable>context</replaceable> on which a call may be placed.
00095          If the user enters an invalid extension and extension <literal>i</literal> exists in the specified
00096          <replaceable>context</replaceable>, it will be used.
00097          </para>
00098          <para>Be aware that using this may compromise the security of your PBX.</para>
00099          <para>The arguments to this application (in <filename>extensions.conf</filename>) allow either
00100          specification of a single global <replaceable>passcode</replaceable> (that everyone uses), or
00101          individual passcodes contained in a file (<replaceable>filename</replaceable>).</para>
00102          <para>The file that contains the passcodes (if used) allows a complete
00103          specification of all of the same arguments available on the command
00104          line, with the sole exception of the options. The file may contain blank
00105          lines, or comments starting with <literal>#</literal> or <literal>;</literal>.</para>
00106       </description>
00107       <see-also>
00108          <ref type="application">Authenticate</ref>
00109          <ref type="application">VMAuthenticate</ref>
00110       </see-also>
00111    </application>
00112  ***/
00113 static char *app = "DISA";
00114 
00115 enum {
00116    NOANSWER_FLAG = (1 << 0),
00117    POUND_TO_END_FLAG = (1 << 1),
00118 } option_flags;
00119 
00120 AST_APP_OPTIONS(app_opts, {
00121    AST_APP_OPTION('n', NOANSWER_FLAG),
00122    AST_APP_OPTION('p', POUND_TO_END_FLAG),
00123 });
00124 
00125 static void play_dialtone(struct ast_channel *chan, char *mailbox)
00126 {
00127    struct ast_tone_zone_sound *ts = NULL;
00128 
00129    if (ast_app_has_voicemail(mailbox, NULL)) {
00130       ts = ast_get_indication_tone(chan->zone, "dialrecall");
00131    } else {
00132       ts = ast_get_indication_tone(chan->zone, "dial");
00133    }
00134 
00135    if (ts) {
00136       ast_playtones_start(chan, 0, ts->data, 0);
00137       ts = ast_tone_zone_sound_unref(ts);
00138    } else {
00139       ast_tonepair_start(chan, 350, 440, 0, 0);
00140    }
00141 }
00142 
00143 static int disa_exec(struct ast_channel *chan, void *data)
00144 {
00145    int i = 0, j, k = 0, did_ignore = 0, special_noanswer = 0;
00146    int firstdigittimeout = (chan->pbx ? chan->pbx->rtimeoutms : 20000);
00147    int digittimeout = (chan->pbx ? chan->pbx->dtimeoutms : 10000);
00148    struct ast_flags flags;
00149    char *tmp, exten[AST_MAX_EXTENSION] = "", acctcode[20]="";
00150    char pwline[256];
00151    char ourcidname[256],ourcidnum[256];
00152    struct ast_frame *f;
00153    struct timeval lastdigittime;
00154    int res;
00155    FILE *fp;
00156    AST_DECLARE_APP_ARGS(args,
00157       AST_APP_ARG(passcode);
00158       AST_APP_ARG(context);
00159       AST_APP_ARG(cid);
00160       AST_APP_ARG(mailbox);
00161       AST_APP_ARG(options);
00162    );
00163 
00164    if (ast_strlen_zero(data)) {
00165       ast_log(LOG_WARNING, "DISA requires an argument (passcode/passcode file)\n");
00166       return -1;
00167    }
00168 
00169    ast_debug(1, "Digittimeout: %d\n", digittimeout);
00170    ast_debug(1, "Responsetimeout: %d\n", firstdigittimeout);
00171 
00172    tmp = ast_strdupa(data);
00173 
00174    AST_STANDARD_APP_ARGS(args, tmp);
00175 
00176    if (ast_strlen_zero(args.context))
00177       args.context = "disa";
00178    if (ast_strlen_zero(args.mailbox))
00179       args.mailbox = "";
00180    if (!ast_strlen_zero(args.options))
00181       ast_app_parse_options(app_opts, &flags, NULL, args.options);
00182 
00183    ast_debug(1, "Mailbox: %s\n",args.mailbox);
00184 
00185    if (!ast_test_flag(&flags, NOANSWER_FLAG)) {
00186       if (chan->_state != AST_STATE_UP) {
00187          /* answer */
00188          ast_answer(chan);
00189       }
00190    } else special_noanswer = 1;
00191 
00192    ast_debug(1, "Context: %s\n",args.context);
00193 
00194    if (!strcasecmp(args.passcode, "no-password")) {
00195       k |= 1; /* We have the password */
00196       ast_debug(1, "DISA no-password login success\n");
00197    }
00198 
00199    lastdigittime = ast_tvnow();
00200 
00201    play_dialtone(chan, args.mailbox);
00202 
00203    ast_set_flag(chan, AST_FLAG_END_DTMF_ONLY);
00204 
00205    for (;;) {
00206         /* if outa time, give em reorder */
00207       if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > ((k&2) ? digittimeout : firstdigittimeout)) {
00208          ast_debug(1,"DISA %s entry timeout on chan %s\n",
00209             ((k&1) ? "extension" : "password"),chan->name);
00210          break;
00211       }
00212 
00213       if ((res = ast_waitfor(chan, -1) < 0)) {
00214          ast_debug(1, "Waitfor returned %d\n", res);
00215          continue;
00216       }
00217 
00218       if (!(f = ast_read(chan))) {
00219          ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
00220          return -1;
00221       }
00222 
00223       if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
00224          if (f->data.uint32)
00225             chan->hangupcause = f->data.uint32;
00226          ast_frfree(f);
00227          ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
00228          return -1;
00229       }
00230 
00231       /* If the frame coming in is not DTMF, just drop it and continue */
00232       if (f->frametype != AST_FRAME_DTMF) {
00233          ast_frfree(f);
00234          continue;
00235       }
00236 
00237       j = f->subclass;  /* save digit */
00238       ast_frfree(f);
00239 
00240       if (!i) {
00241          k |= 2; /* We have the first digit */
00242          ast_playtones_stop(chan);
00243       }
00244 
00245       lastdigittime = ast_tvnow();
00246 
00247       /* got a DTMF tone */
00248       if (i < AST_MAX_EXTENSION) { /* if still valid number of digits */
00249          if (!(k&1)) { /* if in password state */
00250             if (j == '#') { /* end of password */
00251                  /* see if this is an integer */
00252                if (sscanf(args.passcode,"%30d",&j) < 1) { /* nope, it must be a filename */
00253                   fp = fopen(args.passcode,"r");
00254                   if (!fp) {
00255                      ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",args.passcode,chan->name);
00256                      ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
00257                      return -1;
00258                   }
00259                   pwline[0] = 0;
00260                   while(fgets(pwline,sizeof(pwline) - 1,fp)) {
00261                      if (!pwline[0])
00262                         continue;
00263                      if (pwline[strlen(pwline) - 1] == '\n')
00264                         pwline[strlen(pwline) - 1] = 0;
00265                      if (!pwline[0])
00266                         continue;
00267                       /* skip comments */
00268                      if (pwline[0] == '#')
00269                         continue;
00270                      if (pwline[0] == ';')
00271                         continue;
00272 
00273                      AST_STANDARD_APP_ARGS(args, pwline);
00274 
00275                      ast_debug(1, "Mailbox: %s\n",args.mailbox);
00276 
00277                      /* password must be in valid format (numeric) */
00278                      if (sscanf(args.passcode,"%30d", &j) < 1)
00279                         continue;
00280                       /* if we got it */
00281                      if (!strcmp(exten,args.passcode)) {
00282                         if (ast_strlen_zero(args.context))
00283                            args.context = "disa";
00284                         if (ast_strlen_zero(args.mailbox))
00285                            args.mailbox = "";
00286                         break;
00287                      }
00288                   }
00289                   fclose(fp);
00290                }
00291                /* compare the two */
00292                if (strcmp(exten,args.passcode)) {
00293                   ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
00294                   goto reorder;
00295 
00296                }
00297                 /* password good, set to dial state */
00298                ast_debug(1,"DISA on chan %s password is good\n",chan->name);
00299                play_dialtone(chan, args.mailbox);
00300 
00301                k|=1; /* In number mode */
00302                i = 0;  /* re-set buffer pointer */
00303                exten[sizeof(acctcode)] = 0;
00304                ast_copy_string(acctcode, exten, sizeof(acctcode));
00305                exten[0] = 0;
00306                ast_debug(1,"Successful DISA log-in on chan %s\n", chan->name);
00307                continue;
00308             }
00309          } else {
00310             if (j == '#') { /* end of extension .. maybe */
00311                if (i == 0 && 
00312                      (ast_matchmore_extension(chan, args.context, "#", 1, chan->cid.cid_num) ||
00313                       ast_exists_extension(chan, args.context, "#", 1, chan->cid.cid_num)) ) {
00314                   /* Let the # be the part of, or the entire extension */
00315                } else {
00316                   break;
00317                }
00318             }
00319          }
00320 
00321          exten[i++] = j;  /* save digit */
00322          exten[i] = 0;
00323          if (!(k&1))
00324             continue; /* if getting password, continue doing it */
00325          /* if this exists */
00326 
00327          /* user wants end of number, remove # */
00328          if (ast_test_flag(&flags, POUND_TO_END_FLAG) && j == '#') {
00329             exten[--i] = 0;
00330             break;
00331          }
00332 
00333          if (ast_ignore_pattern(args.context, exten)) {
00334             play_dialtone(chan, "");
00335             did_ignore = 1;
00336          } else
00337             if (did_ignore) {
00338                ast_playtones_stop(chan);
00339                did_ignore = 0;
00340             }
00341 
00342          /* if can do some more, do it */
00343          if (!ast_matchmore_extension(chan,args.context,exten,1, chan->cid.cid_num)) {
00344             break;
00345          }
00346       }
00347    }
00348 
00349    ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
00350 
00351    if (k == 3) {
00352       int recheck = 0;
00353       struct ast_flags cdr_flags = { AST_CDR_FLAG_POSTED };
00354 
00355       if (!ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
00356          pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
00357          exten[0] = 'i';
00358          exten[1] = '\0';
00359          recheck = 1;
00360       }
00361       if (!recheck || ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
00362          ast_playtones_stop(chan);
00363          /* We're authenticated and have a target extension */
00364          if (!ast_strlen_zero(args.cid)) {
00365             ast_callerid_split(args.cid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
00366             ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
00367          }
00368 
00369          if (!ast_strlen_zero(acctcode))
00370             ast_string_field_set(chan, accountcode, acctcode);
00371 
00372          if (special_noanswer) cdr_flags.flags = 0;
00373          ast_cdr_reset(chan->cdr, &cdr_flags);
00374          ast_explicit_goto(chan, args.context, exten, 1);
00375          return 0;
00376       }
00377    }
00378 
00379    /* Received invalid, but no "i" extension exists in the given context */
00380 
00381 reorder:
00382    /* Play congestion for a bit */
00383    ast_indicate(chan, AST_CONTROL_CONGESTION);
00384    ast_safe_sleep(chan, 10*1000);
00385 
00386    ast_playtones_stop(chan);
00387 
00388    return -1;
00389 }
00390 
00391 static int unload_module(void)
00392 {
00393    return ast_unregister_application(app);
00394 }
00395 
00396 static int load_module(void)
00397 {
00398    return ast_register_application_xml(app, disa_exec) ?
00399       AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
00400 }
00401 
00402 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DISA (Direct Inward System Access) Application");