2 * Asterisk -- A telephony toolkit for Linux.
4 * Simple fax applications
6 * 2007-2008, Dmitry Andrianov <asterisk@dima.spb.ru>
8 * Code based on original implementation by Steve Underwood <steveu@coppice.org>
10 * This program is free software, distributed under the terms of
11 * the GNU General Public License
16 <depend>spandsp</depend>
21 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
32 #ifdef HAVE_SPANDSP_EXPOSE_H
33 #include <spandsp/expose.h>
35 #include <spandsp/version.h>
37 #include "asterisk/lock.h"
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/pbx.h"
42 #include "asterisk/app.h"
43 #include "asterisk/dsp.h"
44 #include "asterisk/module.h"
45 #include "asterisk/manager.h"
48 <application name="SendFAX" language="en_US">
53 <parameter name="filename" required="true">
54 <para>Filename of TIFF file to fax</para>
56 <parameter name="a" required="false">
57 <para>Makes the application behave as the answering machine</para>
58 <para>(Default behavior is as calling machine)</para>
62 <para>Send a given TIFF file to the channel as a FAX.</para>
63 <para>This application sets the following channel variables:</para>
65 <variable name="LOCALSTATIONID">
66 <para>To identify itself to the remote end</para>
68 <variable name="LOCALHEADERINFO">
69 <para>To generate a header line on each page</para>
71 <variable name="FAXSTATUS">
72 <value name="SUCCESS"/>
73 <value name="FAILED"/>
75 <variable name="FAXERROR">
76 <para>Cause of failure</para>
78 <variable name="REMOTESTATIONID">
79 <para>The CSID of the remote side</para>
81 <variable name="FAXPAGES">
82 <para>Number of pages sent</para>
84 <variable name="FAXBITRATE">
85 <para>Transmission rate</para>
87 <variable name="FAXRESOLUTION">
88 <para>Resolution of sent fax</para>
93 <application name="ReceiveFAX" language="en_US">
98 <parameter name="filename" required="true">
99 <para>Filename of TIFF file save incoming fax</para>
101 <parameter name="c" required="false">
102 <para>Makes the application behave as the calling machine</para>
103 <para>(Default behavior is as answering machine)</para>
107 <para>Receives a FAX from the channel into the given filename
108 overwriting the file if it already exists.</para>
109 <para>File created will be in TIFF format.</para>
111 <para>This application sets the following channel variables:</para>
113 <variable name="LOCALSTATIONID">
114 <para>To identify itself to the remote end</para>
116 <variable name="LOCALHEADERINFO">
117 <para>To generate a header line on each page</para>
119 <variable name="FAXSTATUS">
120 <value name="SUCCESS"/>
121 <value name="FAILED"/>
123 <variable name="FAXERROR">
124 <para>Cause of failure</para>
126 <variable name="REMOTESTATIONID">
127 <para>The CSID of the remote side</para>
129 <variable name="FAXPAGES">
130 <para>Number of pages sent</para>
132 <variable name="FAXBITRATE">
133 <para>Transmission rate</para>
135 <variable name="FAXRESOLUTION">
136 <para>Resolution of sent fax</para>
144 static const char app_sndfax_name[] = "SendFAX";
145 static const char app_rcvfax_name[] = "ReceiveFAX";
147 #define MAX_SAMPLES 240
149 /* Watchdog. I have seen situations when remote fax disconnects (because of poor line
150 quality) while SpanDSP continues staying in T30_STATE_IV_CTC state forever.
151 To avoid this, we terminate when we see that T30 state does not change for 5 minutes.
152 We also terminate application when more than 30 minutes passed regardless of
153 state changes. This is just a precaution measure - no fax should take that long */
155 #define WATCHDOG_TOTAL_TIMEOUT 30 * 60
156 #define WATCHDOG_STATE_TIMEOUT 5 * 60
159 struct ast_channel *chan;
160 enum ast_t38_state t38state; /* T38 state of the channel */
161 int direction; /* Fax direction: 0 - receiving, 1 - sending */
165 volatile int finished;
168 static void span_message(int level, const char *msg)
170 if (level == SPAN_LOG_ERROR) {
171 ast_log(LOG_ERROR, "%s", msg);
172 } else if (level == SPAN_LOG_WARNING) {
173 ast_log(LOG_WARNING, "%s", msg);
175 ast_log(LOG_DEBUG, "%s", msg);
179 static int t38_tx_packet_handler(t38_core_state_t *s, void *user_data, const uint8_t *buf, int len, int count)
181 struct ast_channel *chan = (struct ast_channel *) user_data;
183 struct ast_frame outf = {
184 .frametype = AST_FRAME_MODEM,
185 .subclass = AST_MODEM_T38,
189 /* TODO: Asterisk does not provide means of resending the same packet multiple
190 times so count is ignored at the moment */
192 AST_FRAME_SET_BUFFER(&outf, buf, 0, len);
194 if (ast_write(chan, &outf) < 0) {
195 ast_log(LOG_WARNING, "Unable to write frame to channel; %s\n", strerror(errno));
202 static void phase_e_handler(t30_state_t *f, void *user_data, int result)
204 const char *local_ident;
205 const char *far_ident;
207 fax_session *s = (fax_session *) user_data;
209 int pages_transferred;
211 ast_debug(1, "Fax phase E handler. result=%d\n", result);
213 t30_get_transfer_statistics(f, &stat);
215 s = (fax_session *) user_data;
217 if (result != T30_ERR_OK) {
220 /* FAXSTATUS is already set to FAILED */
221 pbx_builtin_setvar_helper(s->chan, "FAXERROR", t30_completion_code_to_str(result));
223 ast_log(LOG_WARNING, "Error transmitting fax. result=%d: %s.\n", result, t30_completion_code_to_str(result));
230 local_ident = t30_get_tx_ident(f);
231 far_ident = t30_get_rx_ident(f);
232 pbx_builtin_setvar_helper(s->chan, "FAXSTATUS", "SUCCESS");
233 pbx_builtin_setvar_helper(s->chan, "FAXERROR", NULL);
234 pbx_builtin_setvar_helper(s->chan, "REMOTESTATIONID", far_ident);
235 #if SPANDSP_RELEASE_DATE >= 20090220
236 pages_transferred = (s->direction) ? stat.pages_tx : stat.pages_rx;
238 pages_transferred = stat.pages_transferred;
240 snprintf(buf, sizeof(buf), "%d", pages_transferred);
241 pbx_builtin_setvar_helper(s->chan, "FAXPAGES", buf);
242 snprintf(buf, sizeof(buf), "%d", stat.y_resolution);
243 pbx_builtin_setvar_helper(s->chan, "FAXRESOLUTION", buf);
244 snprintf(buf, sizeof(buf), "%d", stat.bit_rate);
245 pbx_builtin_setvar_helper(s->chan, "FAXBITRATE", buf);
247 ast_debug(1, "Fax transmitted successfully.\n");
248 ast_debug(1, " Remote station ID: %s\n", far_ident);
249 ast_debug(1, " Pages transferred: %d\n", pages_transferred);
250 ast_debug(1, " Image resolution: %d x %d\n", stat.x_resolution, stat.y_resolution);
251 ast_debug(1, " Transfer Rate: %d\n", stat.bit_rate);
253 manager_event(EVENT_FLAG_CALL,
254 s->direction ? "FaxSent" : "FaxReceived",
258 "RemoteStationID: %s\r\n"
259 "LocalStationID: %s\r\n"
260 "PagesTransferred: %d\r\n"
262 "TransferRate: %d\r\n"
266 S_OR(s->chan->cid.cid_num, ""),
275 /* === Helper functions to configure fax === */
277 /* Setup SPAN logging according to Asterisk debug level */
278 static int set_logging(logging_state_t *state)
280 int level = SPAN_LOG_WARNING + option_debug;
282 span_log_set_message_handler(state, span_message);
283 span_log_set_level(state, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | level);
288 static void set_local_info(t30_state_t *state, fax_session *s)
292 x = pbx_builtin_getvar_helper(s->chan, "LOCALSTATIONID");
293 if (!ast_strlen_zero(x))
294 t30_set_tx_ident(state, x);
296 x = pbx_builtin_getvar_helper(s->chan, "LOCALHEADERINFO");
297 if (!ast_strlen_zero(x))
298 t30_set_tx_page_header_info(state, x);
301 static void set_file(t30_state_t *state, fax_session *s)
304 t30_set_tx_file(state, s->file_name, -1, -1);
306 t30_set_rx_file(state, s->file_name, -1);
309 static void set_ecm(t30_state_t *state, int ecm)
311 t30_set_ecm_capability(state, ecm);
312 t30_set_supported_compressions(state, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION);
315 /* === Generator === */
317 /* This function is only needed to return passed params so
318 generator_activate will save it to channel's generatordata */
319 static void *fax_generator_alloc(struct ast_channel *chan, void *params)
324 static int fax_generator_generate(struct ast_channel *chan, void *data, int len, int samples)
326 fax_state_t *fax = (fax_state_t*) data;
327 uint8_t buffer[AST_FRIENDLY_OFFSET + MAX_SAMPLES * sizeof(uint16_t)];
328 int16_t *buf = (int16_t *) (buffer + AST_FRIENDLY_OFFSET);
330 struct ast_frame outf = {
331 .frametype = AST_FRAME_VOICE,
332 .subclass = AST_FORMAT_SLINEAR,
336 if (samples > MAX_SAMPLES) {
337 ast_log(LOG_WARNING, "Only generating %d samples, where %d requested\n", MAX_SAMPLES, samples);
338 samples = MAX_SAMPLES;
341 if ((len = fax_tx(fax, buf, samples)) > 0) {
343 AST_FRAME_SET_BUFFER(&outf, buffer, AST_FRIENDLY_OFFSET, len * sizeof(int16_t));
345 if (ast_write(chan, &outf) < 0) {
346 ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
354 static struct ast_generator generator = {
355 alloc: fax_generator_alloc,
356 generate: fax_generator_generate,
360 /* === Transmission === */
362 static int transmit_audio(fax_session *s)
365 int original_read_fmt = AST_FORMAT_SLINEAR;
366 int original_write_fmt = AST_FORMAT_SLINEAR;
368 t30_state_t *t30state;
369 struct ast_dsp *dsp = NULL;
371 struct ast_frame *inf = NULL;
372 struct ast_frame *fr;
374 struct timeval now, start, state_change;
375 enum ast_control_t38 t38control;
377 #if SPANDSP_RELEASE_DATE >= 20081012
378 /* for spandsp shaphots 0.0.6 and higher */
381 /* for spandsp release 0.0.5 */
382 t30state = &fax.t30_state;
385 original_read_fmt = s->chan->readformat;
386 if (original_read_fmt != AST_FORMAT_SLINEAR) {
387 res = ast_set_read_format(s->chan, AST_FORMAT_SLINEAR);
389 ast_log(LOG_WARNING, "Unable to set to linear read mode, giving up\n");
394 original_write_fmt = s->chan->writeformat;
395 if (original_write_fmt != AST_FORMAT_SLINEAR) {
396 res = ast_set_write_format(s->chan, AST_FORMAT_SLINEAR);
398 ast_log(LOG_WARNING, "Unable to set to linear write mode, giving up\n");
403 /* Initialize T30 terminal */
404 fax_init(&fax, s->caller_mode);
407 set_logging(&fax.logging);
408 set_logging(&t30state->logging);
410 /* Configure terminal */
411 set_local_info(t30state, s);
412 set_file(t30state, s);
413 set_ecm(t30state, TRUE);
415 fax_set_transmit_on_idle(&fax, TRUE);
417 t30_set_phase_e_handler(t30state, phase_e_handler, s);
419 if (s->t38state == T38_STATE_UNAVAILABLE) {
420 ast_debug(1, "T38 is unavailable on %s\n", s->chan->name);
421 } else if (!s->direction) {
422 /* We are receiving side and this means we are the side which should
423 request T38 when the fax is detected. Use DSP to detect fax tone */
424 ast_debug(1, "Setting up CNG detection on %s\n", s->chan->name);
426 ast_dsp_set_features(dsp, DSP_FEATURE_FAX_DETECT);
427 ast_dsp_set_faxmode(dsp, DSP_FAXMODE_DETECT_CNG);
431 start = state_change = ast_tvnow();
433 ast_activate_generator(s->chan, &generator, &fax);
435 while (!s->finished) {
436 res = ast_waitfor(s->chan, 20);
442 inf = ast_read(s->chan);
444 ast_debug(1, "Channel hangup\n");
449 ast_debug(10, "frame %d/%d, len=%d\n", inf->frametype, inf->subclass, inf->datalen);
451 /* Detect fax tone */
452 if (detect_tone && inf->frametype == AST_FRAME_VOICE) {
453 /* Duplicate frame because ast_dsp_process may free the frame passed */
456 /* Do not pass channel to ast_dsp_process otherwise it may queue modified audio frame back */
457 fr = ast_dsp_process(NULL, dsp, fr);
458 if (fr && fr->frametype == AST_FRAME_DTMF && fr->subclass == 'f') {
459 ast_debug(1, "Fax tone detected. Requesting T38\n");
460 t38control = AST_T38_REQUEST_NEGOTIATE;
461 ast_indicate_data(s->chan, AST_CONTROL_T38, &t38control, sizeof(t38control));
469 /* Check the frame type. Format also must be checked because there is a chance
470 that a frame in old format was already queued before we set chanel format
471 to slinear so it will still be received by ast_read */
472 if (inf->frametype == AST_FRAME_VOICE && inf->subclass == AST_FORMAT_SLINEAR) {
474 if (fax_rx(&fax, inf->data.ptr, inf->samples) < 0) {
475 /* I know fax_rx never returns errors. The check here is for good style only */
476 ast_log(LOG_WARNING, "fax_rx returned error\n");
482 if (last_state != t30state->state) {
483 state_change = ast_tvnow();
484 last_state = t30state->state;
486 } else if (inf->frametype == AST_FRAME_CONTROL && inf->subclass == AST_CONTROL_T38 &&
487 inf->datalen == sizeof(enum ast_control_t38)) {
488 t38control =*((enum ast_control_t38 *) inf->data.ptr);
489 if (t38control == AST_T38_NEGOTIATED) {
490 /* T38 switchover completed */
491 ast_debug(1, "T38 negotiated, finishing audio loop\n");
502 if (ast_tvdiff_sec(now, start) > WATCHDOG_TOTAL_TIMEOUT || ast_tvdiff_sec(now, state_change) > WATCHDOG_STATE_TIMEOUT) {
503 ast_log(LOG_WARNING, "It looks like we hung. Aborting.\n");
509 ast_debug(1, "Loop finished, res=%d\n", res);
517 ast_deactivate_generator(s->chan);
519 /* If we are switching to T38, remove phase E handler. Otherwise it will be executed
520 by t30_terminate, display diagnostics and set status variables although no transmittion
521 has taken place yet. */
523 t30_set_phase_e_handler(t30state, NULL, NULL);
526 t30_terminate(t30state);
530 if (original_write_fmt != AST_FORMAT_SLINEAR) {
531 if (ast_set_write_format(s->chan, original_write_fmt) < 0)
532 ast_log(LOG_WARNING, "Unable to restore write format on '%s'\n", s->chan->name);
535 if (original_read_fmt != AST_FORMAT_SLINEAR) {
536 if (ast_set_read_format(s->chan, original_read_fmt) < 0)
537 ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", s->chan->name);
544 static int transmit_t38(fax_session *s)
547 t38_terminal_state_t t38;
548 struct ast_frame *inf = NULL;
550 struct timeval now, start, state_change, last_frame;
551 enum ast_control_t38 t38control;
553 t30_state_t *t30state;
554 t38_core_state_t *t38state;
556 #if SPANDSP_RELEASE_DATE >= 20081012
557 /* for spandsp shaphots 0.0.6 and higher */
559 t38state = &t38.t38_fe.t38;
561 /* for spandsp releases 0.0.5 */
562 t30state = &t38.t30_state;
566 /* Initialize terminal */
567 memset(&t38, 0, sizeof(t38));
568 if (t38_terminal_init(&t38, s->caller_mode, t38_tx_packet_handler, s->chan) == NULL) {
569 ast_log(LOG_WARNING, "Unable to start T.38 termination.\n");
574 set_logging(&t38.logging);
575 set_logging(&t30state->logging);
576 set_logging(&t38state->logging);
578 /* Configure terminal */
579 set_local_info(t30state, s);
580 set_file(t30state, s);
581 set_ecm(t30state, TRUE);
583 t30_set_phase_e_handler(t30state, phase_e_handler, s);
585 now = start = state_change = ast_tvnow();
587 while (!s->finished) {
589 res = ast_waitfor(s->chan, 20);
597 t38_terminal_send_timeout(&t38, ast_tvdiff_us(now, last_frame) / (1000000 / 8000));
599 inf = ast_read(s->chan);
601 ast_debug(1, "Channel hangup\n");
606 ast_debug(10, "frame %d/%d, len=%d\n", inf->frametype, inf->subclass, inf->datalen);
608 if (inf->frametype == AST_FRAME_MODEM && inf->subclass == AST_MODEM_T38) {
609 t38_core_rx_ifp_packet(t38state, inf->data.ptr, inf->datalen, inf->seqno);
612 if (last_state != t30state->state) {
613 state_change = ast_tvnow();
614 last_state = t30state->state;
616 } else if (inf->frametype == AST_FRAME_CONTROL && inf->subclass == AST_CONTROL_T38 &&
617 inf->datalen == sizeof(enum ast_control_t38)) {
619 t38control = *((enum ast_control_t38 *) inf->data.ptr);
621 if (t38control == AST_T38_TERMINATED || t38control == AST_T38_REFUSED) {
622 ast_debug(1, "T38 down, terminating\n");
632 if (ast_tvdiff_sec(now, start) > WATCHDOG_TOTAL_TIMEOUT || ast_tvdiff_sec(now, state_change) > WATCHDOG_STATE_TIMEOUT) {
633 ast_log(LOG_WARNING, "It looks like we hung. Aborting.\n");
639 ast_debug(1, "Loop finished, res=%d\n", res);
644 t30_terminate(t30state);
645 t38_terminal_release(&t38);
650 static int transmit(fax_session *s)
654 /* Clear all channel variables which to be set by the application.
655 Pre-set status to error so in case of any problems we can just leave */
656 pbx_builtin_setvar_helper(s->chan, "FAXSTATUS", "FAILED");
657 pbx_builtin_setvar_helper(s->chan, "FAXERROR", "Channel problems");
659 pbx_builtin_setvar_helper(s->chan, "FAXMODE", NULL);
660 pbx_builtin_setvar_helper(s->chan, "REMOTESTATIONID", NULL);
661 pbx_builtin_setvar_helper(s->chan, "FAXPAGES", NULL);
662 pbx_builtin_setvar_helper(s->chan, "FAXRESOLUTION", NULL);
663 pbx_builtin_setvar_helper(s->chan, "FAXBITRATE", NULL);
665 if (s->chan->_state != AST_STATE_UP) {
666 /* Shouldn't need this, but checking to see if channel is already answered
667 * Theoretically asterisk should already have answered before running the app */
668 res = ast_answer(s->chan);
670 ast_log(LOG_WARNING, "Could not answer channel '%s'\n", s->chan->name);
675 s->t38state = ast_channel_get_t38_state(s->chan);
676 if (s->t38state != T38_STATE_NEGOTIATED) {
677 /* T38 is not negotiated on the channel yet. First start regular transmission. If it switches to T38, follow */
678 pbx_builtin_setvar_helper(s->chan, "FAXMODE", "audio");
679 res = transmit_audio(s);
681 /* transmit_audio reports switchover to T38. Update t38state */
682 s->t38state = ast_channel_get_t38_state(s->chan);
683 if (s->t38state != T38_STATE_NEGOTIATED) {
684 ast_log(LOG_ERROR, "Audio loop reports T38 switchover but t38state != T38_STATE_NEGOTIATED\n");
689 if (s->t38state == T38_STATE_NEGOTIATED) {
690 pbx_builtin_setvar_helper(s->chan, "FAXMODE", "T38");
691 res = transmit_t38(s);
695 ast_log(LOG_WARNING, "Transmission error\n");
697 } else if (s->finished < 0) {
698 ast_log(LOG_WARNING, "Transmission failed\n");
699 } else if (s->finished > 0) {
700 ast_debug(1, "Transmission finished Ok\n");
706 /* === Application functions === */
708 static int sndfax_exec(struct ast_channel *chan, const char *data)
713 char restore_digit_detect = 0;
715 AST_DECLARE_APP_ARGS(args,
716 AST_APP_ARG(file_name);
717 AST_APP_ARG(options);
721 ast_log(LOG_ERROR, "Fax channel is NULL. Giving up.\n");
725 /* The next few lines of code parse out the filename and header from the input string */
726 if (ast_strlen_zero(data)) {
727 /* No data implies no filename or anything is present */
728 ast_log(LOG_ERROR, "SendFAX requires an argument (filename)\n");
732 parse = ast_strdupa(data);
733 AST_STANDARD_APP_ARGS(args, parse);
735 session.caller_mode = TRUE;
738 if (strchr(args.options, 'a'))
739 session.caller_mode = FALSE;
743 session.direction = 1;
744 session.file_name = args.file_name;
746 session.finished = 0;
748 /* get current digit detection mode, then disable digit detection if enabled */
750 int dummy = sizeof(restore_digit_detect);
752 ast_channel_queryoption(chan, AST_OPTION_DIGIT_DETECT, &restore_digit_detect, &dummy, 0);
755 if (restore_digit_detect) {
756 char new_digit_detect = 0;
758 ast_channel_setoption(chan, AST_OPTION_DIGIT_DETECT, &new_digit_detect, sizeof(new_digit_detect), 0);
761 /* disable FAX tone detection if enabled */
763 char new_fax_detect = 0;
765 ast_channel_setoption(chan, AST_OPTION_FAX_DETECT, &new_fax_detect, sizeof(new_fax_detect), 0);
768 res = transmit(&session);
770 if (restore_digit_detect) {
771 ast_channel_setoption(chan, AST_OPTION_DIGIT_DETECT, &restore_digit_detect, sizeof(restore_digit_detect), 0);
777 static int rcvfax_exec(struct ast_channel *chan, const char *data)
782 char restore_digit_detect = 0;
784 AST_DECLARE_APP_ARGS(args,
785 AST_APP_ARG(file_name);
786 AST_APP_ARG(options);
790 ast_log(LOG_ERROR, "Fax channel is NULL. Giving up.\n");
794 /* The next few lines of code parse out the filename and header from the input string */
795 if (ast_strlen_zero(data)) {
796 /* No data implies no filename or anything is present */
797 ast_log(LOG_ERROR, "ReceiveFAX requires an argument (filename)\n");
801 parse = ast_strdupa(data);
802 AST_STANDARD_APP_ARGS(args, parse);
804 session.caller_mode = FALSE;
807 if (strchr(args.options, 'c'))
808 session.caller_mode = TRUE;
812 session.direction = 0;
813 session.file_name = args.file_name;
815 session.finished = 0;
817 /* get current digit detection mode, then disable digit detection if enabled */
819 int dummy = sizeof(restore_digit_detect);
821 ast_channel_queryoption(chan, AST_OPTION_DIGIT_DETECT, &restore_digit_detect, &dummy, 0);
824 if (restore_digit_detect) {
825 char new_digit_detect = 0;
827 ast_channel_setoption(chan, AST_OPTION_DIGIT_DETECT, &new_digit_detect, sizeof(new_digit_detect), 0);
830 /* disable FAX tone detection if enabled */
832 char new_fax_detect = 0;
834 ast_channel_setoption(chan, AST_OPTION_FAX_DETECT, &new_fax_detect, sizeof(new_fax_detect), 0);
837 res = transmit(&session);
839 if (restore_digit_detect) {
840 ast_channel_setoption(chan, AST_OPTION_DIGIT_DETECT, &restore_digit_detect, sizeof(restore_digit_detect), 0);
846 static int unload_module(void)
850 res = ast_unregister_application(app_sndfax_name);
851 res |= ast_unregister_application(app_rcvfax_name);
856 static int load_module(void)
860 res = ast_register_application_xml(app_sndfax_name, sndfax_exec);
861 res |= ast_register_application_xml(app_rcvfax_name, rcvfax_exec);
863 /* The default SPAN message handler prints to stderr. It is something we do not want */
864 span_set_message_handler(NULL);
870 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Simple FAX Application",
872 .unload = unload_module,