diff -Naur asterisk-addons-451.orig/Makefile asterisk-addons-451.patched-fax/Makefile --- asterisk-addons-451.orig/Makefile 2007-07-18 14:17:51.000000000 +0700 +++ asterisk-addons-451.patched-fax/Makefile 2007-10-01 16:37:45.000000000 +0700 @@ -49,7 +49,7 @@ endif MODULES_DIR=$(ASTLIBDIR)/modules -MODS:=app_addon_sql_mysql app_saycountpl cdr_addon_mysql chan_ooh323 format_mp3 res_config_mysql chan_mobile +MODS:=app_addon_sql_mysql app_saycountpl cdr_addon_mysql chan_ooh323 format_mp3 res_config_mysql chan_mobile app_fax SELECTED_MODS:=$(patsubst %,%.so,$(filter-out $(MENUSELECT_ADDONS),$(MODS))) diff -Naur asterisk-addons-451.orig/app_fax.c asterisk-addons-451.patched-fax/app_fax.c --- asterisk-addons-451.orig/app_fax.c 1970-01-01 06:00:00.000000000 +0600 +++ asterisk-addons-451.patched-fax/app_fax.c 2007-10-01 16:05:11.000000000 +0700 @@ -0,0 +1,491 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Simple fax applications + * + * 2007, Dmitry Andrianov + * + * Code based on original implementation by Steve Underwood + * + * This program is free software, distributed under the terms of + * the GNU General Public License + * + */ + +/*** MODULEINFO + spandsp +***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision:$") + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/module.h" +#include "asterisk/manager.h" + +#ifndef AST_MODULE +#define AST_MODULE "app_fax" +#endif + +static char *app_sndfax_name = "SendFAX"; +static char *app_sndfax_synopsis = "Send a FAX"; +static char *app_sndfax_desc = +" SendFAX(filename[|options]):\n" +"Send a given TIFF file to the channel as a FAX.\n" +"The option string may contain zero or more of the following characters:\n" +" 'a' -- makes the application behave as an answering machine\n" +" The default behaviour is to behave as a calling machine.\n" +" 'd' -- turn on debug output. This option may be specified multiple\n" +" times to increase verbosity.\n" +"Uses LOCALSTATIONID to identify itself to the remote end.\n" +" LOCALHEADERINFO to generate a header line on each page.\n" +"Sets REMOTESTATIONID to the receiver CSID.\n" +" FAXPAGES to the number of pages sent.\n" +" FAXBITRATE to the transmition rate.\n" +" FAXRESOLUTION to the resolution.\n" +"Returns -1 in case of eny error or user hang up.\n" +"Returns 0 on success.\n"; + +static char *app_rcvfax_name = "ReceiveFAX"; +static char *app_rcvfax_synopsis = "Receive a FAX"; +static char *app_rcvfax_desc = +" ReceiveFAX(filename[|options]):\n" +"Receives a fax from the channel into the given filename overwriting\n" +"the file if it already exists. File created will have TIFF format.\n" +"The option string may contain zero or more of the following characters:\n" +" 'c' -- makes the application behave as a calling machine\n" +" The default behaviour is to behave as an answering machine.\n" +" 'd' -- turn on debug output. This option may be specified multiple\n" +" times to increase verbosity.\n" +"Uses LOCALSTATIONID to identify itself to the remote end.\n" +" LOCALHEADERINFO to generate a header line on each page.\n" +"Sets REMOTESTATIONID to the receiver CSID.\n" +" FAXPAGES to the number of pages received.\n" +" FAXBITRATE to the transmition rate.\n" +" FAXRESOLUTION to the resolution.\n" +"Returns -1 in case of eny error or user hang up.\n" +"Returns 0 on success.\n"; + +#define MAX_BLOCK_SIZE 240 + +typedef struct { + struct ast_channel *chan; + fax_state_t fax; + volatile int finished; +} fax_session; + +static void span_message(int level, const char *msg) +{ + if (level == SPAN_LOG_ERROR) + ast_log(LOG_ERROR, msg); + else if (level == SPAN_LOG_WARNING) + ast_log(LOG_WARNING, msg); + else + ast_log(LOG_WARNING, msg); +} + +static void phase_e_handler(t30_state_t *f, void *user_data, int result) +{ + char local_ident[T30_MAX_IDENT_LEN]; + char far_ident[T30_MAX_IDENT_LEN]; + char buf[20]; + fax_session *s = (fax_session *) user_data; + t30_stats_t stat; + + t30_get_transfer_statistics(f, &stat); + + s = (fax_session *) user_data; + + if (result == T30_ERR_OK) { + s->finished = 1; + + t30_get_local_ident(f, local_ident); + t30_get_far_ident(f, far_ident); + pbx_builtin_setvar_helper(s->chan, "REMOTESTATIONID", far_ident); + snprintf(buf, sizeof(buf), "%i", stat.pages_transferred); + pbx_builtin_setvar_helper(s->chan, "FAXPAGES", buf); + snprintf(buf, sizeof(buf), "%i", stat.y_resolution); + pbx_builtin_setvar_helper(s->chan, "FAXRESOLUTION", buf); + snprintf(buf, sizeof(buf), "%i", stat.bit_rate); + pbx_builtin_setvar_helper(s->chan, "FAXBITRATE", buf); + + ast_log(LOG_DEBUG, "Fax transmitted successfully.\n"); + ast_log(LOG_DEBUG, " Remote station ID: %s\n", far_ident); + ast_log(LOG_DEBUG, " Pages transferred: %i\n", stat.pages_transferred); + ast_log(LOG_DEBUG, " Image resolution: %i x %i\n", stat.x_resolution, stat.y_resolution); + ast_log(LOG_DEBUG, " Transfer Rate: %i\n", stat.bit_rate); + + manager_event(EVENT_FLAG_CALL, + f->tx_file ? "FaxSent" : "FaxReceived", + "Channel: %s\nExten: %s\nCallerID: %s\n" + "RemoteStationID: %s\nLocalStationID: %s\n" + "PagesTransferred: %i\nResolution: %i\n" + "TransferRate: %i\nFileName: %s\n", + s->chan->name, + s->chan->exten, + S_OR(s->chan->cid.cid_num, ""), + far_ident, + local_ident, + stat.pages_transferred, + stat.y_resolution, + stat.bit_rate, + f->tx_file ? f->tx_file : f->rx_file); + } else { + s->finished = -1; + ast_log(LOG_WARNING, "Error sending fax - result (%d) %s.\n", result, t30_completion_code_to_str(result)); + } +} + +static int transmit(fax_session *s) +{ + int res = 0, res2; + + int original_read_fmt; + int original_write_fmt; + + const char *x; + int len; + int samples; + + struct ast_frame *inf = NULL; + struct ast_frame outf; + + uint8_t buffer[AST_FRIENDLY_OFFSET + MAX_BLOCK_SIZE * sizeof(uint16_t)]; + int16_t *buf = (int16_t *) (buffer + AST_FRIENDLY_OFFSET); + + int last_state = 0; + struct timeval now, start, state_change; + + if (s->chan->_state != AST_STATE_UP) { + /* Shouldn't need this, but checking to see if channel is already answered + * Theoretically asterisk should already have answered before running the app */ + res = ast_answer(s->chan); + if (res) { + ast_log(LOG_WARNING, "Could not answer channel '%s'\n", s->chan->name); + goto done; + } + } + + original_read_fmt = s->chan->readformat; + if (original_read_fmt != AST_FORMAT_SLINEAR) { + res = ast_set_read_format(s->chan, AST_FORMAT_SLINEAR); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set to linear read mode, giving up\n"); + res = -1; + goto done; + } + } + + original_write_fmt = s->chan->writeformat; + if (original_write_fmt != AST_FORMAT_SLINEAR) { + res = ast_set_write_format(s->chan, AST_FORMAT_SLINEAR); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set to linear write mode, giving up\n"); + res = -1; + goto done1; + } + } + + + x = pbx_builtin_getvar_helper(s->chan, "LOCALSTATIONID"); + if (!ast_strlen_zero(x)) + t30_set_local_ident(&s->fax.t30_state, x); + + x = pbx_builtin_getvar_helper(s->chan, "LOCALHEADERINFO"); + if (!ast_strlen_zero(x)) + t30_set_header_info(&s->fax.t30_state, x); + + t30_set_phase_e_handler(&s->fax.t30_state, phase_e_handler, s); + + t30_set_ecm_capability(&s->fax.t30_state, TRUE); + t30_set_supported_compressions(&s->fax.t30_state, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION); + + start = state_change = ast_tvnow(); + while (!s->finished && (res = ast_waitfor(s->chan, -1)) > -1) { + inf = ast_read(s->chan); + if (inf == NULL) + break; + + /* Check the frame type. Format also must be checked because there is a chance + that a frame in old format was already queued before we set chanel format + to slinear so it will still be received by ast_read */ + if (inf->frametype == AST_FRAME_VOICE && inf->subclass == AST_FORMAT_SLINEAR) { + + if (fax_rx(&s->fax, inf->data, inf->samples) < 0) { + /* I know fax_rx never returns errors. The check here is for good style only */ + ast_log(LOG_WARNING, "fax_rx returned error\n"); + res = -1; + break; + } + + samples = (inf->samples <= MAX_BLOCK_SIZE) ? inf->samples : MAX_BLOCK_SIZE; + len = fax_tx(&s->fax, buf, samples); + + if (len < 0) { + /* fax_tx also never returns errors. The check here is for good style only */ + ast_log(LOG_WARNING, "fax_rx returned error\n"); + res = -1; + break; + } else if (len) { + memset(&outf, 0, sizeof(outf)); + outf.frametype = AST_FRAME_VOICE; + outf.subclass = AST_FORMAT_SLINEAR; + outf.samples = len; + AST_FRAME_SET_BUFFER(&outf, buffer, AST_FRIENDLY_OFFSET, len * sizeof(int16_t)); + if (ast_write(s->chan, &outf) < 0) { + ast_log(LOG_WARNING, "Unable to write frame to channel; %s\n", strerror(errno)); + res = -1; + break; + } + } + + if (last_state != s->fax.t30_state.state) { + state_change = ast_tvnow(); + last_state = s->fax.t30_state.state; + } + } + + ast_frfree(inf); + inf = NULL; + + /* Watchdog. I have seen situations when remote fax disconnects (because of poor line + quality) while SpanDSP continues staying in T30_STATE_IV_CTC state forever. + To avoid this, we terminate when we see that T30 state does not change for 5 minutes. + We also terminate application when more than 30 minutes passed regardless of + state changes. This is just a precaution measure - no fax should take that long */ + + now = ast_tvnow(); + if (ast_tvdiff_ms(now, start) > 30 * 60 * 1000 || ast_tvdiff_ms(now, state_change) > 5 * 60 * 1000) { + ast_log(LOG_WARNING, "It looks like we hung. Aborting.\n"); + res = -1; + break; + } + } + + if (inf) + ast_frfree(inf); + + if (res) { + ast_log(LOG_WARNING, "Transmission loop error\n"); + res = -1; + } else if (s->finished < 0) { + ast_log(LOG_WARNING, "Transmission error\n"); + res = -1; + } else if (s->finished > 0) { + ast_log(LOG_DEBUG, "Transmission finished Ok\n"); + } else if (inf == NULL) { + ast_log(LOG_DEBUG, "Channel hangup\n"); + res = -1; + } + + + if (original_write_fmt != AST_FORMAT_SLINEAR) { + res2 = ast_set_write_format(s->chan, original_write_fmt); + if (res2) + ast_log(LOG_WARNING, "Unable to restore write format on '%s'\n", s->chan->name); + } + +done1: + if (original_read_fmt != AST_FORMAT_SLINEAR) { + res2 = ast_set_read_format(s->chan, original_read_fmt); + if (res2) + ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", s->chan->name); + } + +done: + + return res; +} + +static int sndfax_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + char *parse; + char *pos; + int calling_party; + int verbose; + + fax_session session; + + struct ast_module_user *u; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(file_name); + AST_APP_ARG(options); + ); + + if (chan == NULL) { + ast_log(LOG_ERROR, "Fax channel is NULL. Giving up.\n"); + return -1; + } + + span_set_message_handler(span_message); + + /* The next few lines of code parse out the filename and header from the input string */ + if (ast_strlen_zero(data)) { + /* No data implies no filename or anything is present */ + ast_log(LOG_ERROR, "Txfax requires an argument (filename)\n"); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + calling_party = TRUE; + verbose = 0; + + if (args.options) { + if (strchr(args.options, 'a')) + calling_party = FALSE; + + pos = args.options; + while ((pos = strchr(pos, 'd')) != NULL) { + verbose++; + pos++; + } + } + + /* Done parsing */ + + u = ast_module_user_add(chan); + + session.chan = chan; + session.finished = 0; + + /* Initialize SpanDSP fax for transmission */ + fax_init(&session.fax, calling_party); + t30_set_tx_file(&session.fax.t30_state, args.file_name, -1, -1); + + if (verbose) { + span_log_set_level(&session.fax.logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW); + if (verbose > 1) + span_log_set_level(&session.fax.t30_state.logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW); + } + + res = transmit(&session); + + t30_terminate(&session.fax.t30_state); + + ast_module_user_remove(u); + + return res; +} + +static int rcvfax_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + char *parse; + char *pos; + int calling_party; + int verbose; + + fax_session session; + + struct ast_module_user *u; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(file_name); + AST_APP_ARG(options); + ); + + if (chan == NULL) { + ast_log(LOG_ERROR, "Fax channel is NULL. Giving up.\n"); + return -1; + } + + span_set_message_handler(span_message); + + /* The next few lines of code parse out the filename and header from the input string */ + if (ast_strlen_zero(data)) { + /* No data implies no filename or anything is present */ + ast_log(LOG_ERROR, "Txfax requires an argument (filename)\n"); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + calling_party = FALSE; + verbose = 0; + + if (args.options) { + if (strchr(args.options, 'c')) + calling_party = TRUE; + + pos = args.options; + while ((pos = strchr(pos, 'd')) != NULL) { + verbose++; + pos++; + } + } + + /* Done parsing */ + + u = ast_module_user_add(chan); + + session.chan = chan; + session.finished = 0; + + /* Initialize SpanDSP fax for receiving */ + fax_init(&session.fax, calling_party); + t30_set_rx_file(&session.fax.t30_state, args.file_name, -1); + + if (verbose) { + span_log_set_level(&session.fax.logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW); + if (verbose > 1) + span_log_set_level(&session.fax.t30_state.logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW); + } + + res = transmit(&session); + + t30_terminate(&session.fax.t30_state); + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + ast_module_user_hangup_all(); + + res = ast_unregister_application(app_sndfax_name); + res |= ast_unregister_application(app_rcvfax_name); + + return res; +} + +static int load_module(void) +{ + int res ; + + res = ast_register_application(app_sndfax_name, sndfax_exec, app_sndfax_synopsis, app_sndfax_desc); + res |= ast_register_application(app_rcvfax_name, rcvfax_exec, app_rcvfax_synopsis, app_rcvfax_desc); + + return res; +} + + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Simple FAX Application", + .load = load_module, + .unload = unload_module, + ); + diff -Naur asterisk-addons-451.orig/build_tools/menuselect-deps.in asterisk-addons-451.patched-fax/build_tools/menuselect-deps.in --- asterisk-addons-451.orig/build_tools/menuselect-deps.in 2007-06-04 09:36:32.000000000 +0700 +++ asterisk-addons-451.patched-fax/build_tools/menuselect-deps.in 2007-10-01 16:37:31.000000000 +0700 @@ -1,3 +1,4 @@ BLUETOOTH=@PBX_BLUETOOTH@ MYSQLCLIENT=@PBX_MYSQLCLIENT@ ASTERISK=@PBX_ASTERISK@ +SPANDSP=@PBX_SPANDSP@ diff -Naur asterisk-addons-451.orig/configure.ac asterisk-addons-451.patched-fax/configure.ac --- asterisk-addons-451.orig/configure.ac 2007-06-04 09:36:42.000000000 +0700 +++ asterisk-addons-451.patched-fax/configure.ac 2007-10-01 16:37:42.000000000 +0700 @@ -164,10 +164,13 @@ AST_EXT_LIB_SETUP([NCURSES], [ncurses], [ncurses]) AST_EXT_LIB_SETUP([MYSQLCLIENT], [mysqlclient], [mysqlclient]) AST_EXT_LIB_SETUP([ASTERISK], [asterisk], [asterisk]) +AST_EXT_LIB_SETUP([SPANDSP], [spandsp Library], [spandsp]) AST_EXT_LIB_CHECK([BLUETOOTH], [bluetooth], [ba2str], [bluetooth/bluetooth.h]) AST_EXT_LIB_CHECK([CURSES], [curses], [initscr], [curses.h]) AST_EXT_LIB_CHECK([NCURSES], [ncurses], [initscr], [curses.h]) +AST_EXT_LIB_CHECK([SPANDSP], [spandsp], [fax_init], [spandsp.h], [-ltiff]) + MYSQL_CONFIG=No PBX_MYSQLCLIENT=0 diff -Naur asterisk-addons-451.orig/makeopts.in asterisk-addons-451.patched-fax/makeopts.in --- asterisk-addons-451.orig/makeopts.in 2007-06-04 09:36:42.000000000 +0700 +++ asterisk-addons-451.patched-fax/makeopts.in 2007-10-01 16:37:40.000000000 +0700 @@ -46,3 +46,6 @@ NCURSES_LIB=@NCURSES_LIB@ NCURSES_INCLUDE=@NCURSES_INCLUDE@ + +SPANDSP_INCLUDE=@SPANDSP_INCLUDE@ +SPANDSP_LIB=@SPANDSP_LIB@ diff -Naur asterisk-addons-451.orig/menuselect-tree asterisk-addons-451.patched-fax/menuselect-tree --- asterisk-addons-451.orig/menuselect-tree 2007-06-04 09:36:42.000000000 +0700 +++ asterisk-addons-451.patched-fax/menuselect-tree 2007-10-01 16:52:07.000000000 +0700 @@ -6,7 +6,11 @@ mysqlclient asterisk - + + asterisk + + + spandsp asterisk