diff --git a/Makefile b/Makefile index da95c4b..175f3a5 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ CC=gcc INSTALL=install SHAREDOS=chan_capi.so app_capiHOLD.so app_capiRETRIEVE.so \ - app_capiECT.so app_capiMCID.so app_capiNoES.so app_capiFax.so + app_capiECT.so app_capiMCID.so app_capiNoES.so CFLAGS+=-Wno-missing-prototypes -Wno-missing-declarations diff --git a/README b/README index 02de657..bb6d187 100644 --- a/README +++ b/README @@ -60,7 +60,7 @@ This chan_capi version includes: - Compiles with different Asterisk versions (automatic build configuration) - Added app_capiNoES for disabling the primitive echo suppressor, use this before you start recording voicemail or your files may get choppy -- Added app_capiFax to receive faxes over CAPI (see below) +- receive faxes over CAPI (see below) - Fixes for BSD (Jan Stocker) - Support 'type of number'. @@ -102,7 +102,13 @@ Call Deflection: Forwards an unanswered call to another number. Example: exten => s,1,capicommand(deflect|12345678) - + +Fax receive: + Receive a fax using CAPI. + Example: + exten => s,1,capicommand(receivefax|/tmp/${UNIQUEID}|+49 6137 555123|Asterisk) + (more see below) + Helper applications =================== @@ -176,14 +182,17 @@ Example context for incoming calls on MSN 12345678: exten => 12345678,1,Dial(SIP/phone1) exten => 12345678,2,Hangup -Short HOWTO of 'capiAnswerFax': -=============================== + +Short HOWTO of capicommand(receivefax|[||]): +========================================================================== For those of you who have a CAPI card with an on-board DSP (like some Eicon and DIVA Server), this patch allows you to receive faxes. -If you want to answer a channel in fax mode, use capiAnswerFax() instead of -Answer() +If you want to answer a channel in fax mode, use capicommand(receivefax|...) +instead of Answer() If you use Answer(), you will be in voice mode. If the hardware DSP detects -fax tone, you can switch from voice to fax mode by calling capiAnswerFax(). +fax tone, you can switch from voice to fax mode by calling capicommand(receivefax|...). +The parameter is mandatory and the parameters and + are optional. Example of use : line number 123, play something, if a fax tone is detected, handle it @@ -196,11 +205,12 @@ exten => 124,1,Goto(handle_fax,s,1) exten => fax,1,Goto(handle_fax,s,1) [handle_fax] -exten => s,1,capiAnswerFax(/tmp/${UNIQUEID}) +exten => s,1,capicommand(receivefax|/tmp/${UNIQUEID}[||]) exten => s,2,Hangup() exten => h,1,deadagi,fax.php // Run sfftobmp and mail it. -The output of capiAnswerFax is a SFF file. Use sfftobmp to convert it. +The output of capicommand(receivefax|...) is a SFF file. +Use sfftobmp to convert it. With a DIVA Server, following features are provided: - fax up to 33600 - high resolution diff --git a/app_capiFax.c b/app_capiFax.c deleted file mode 100644 index ba43b0f..0000000 --- a/app_capiFax.c +++ /dev/null @@ -1,275 +0,0 @@ -/* - * (CAPI*) - * - * Receive a fax with CAPI API. - * Usage : capiAnswerFax(path_output_file.SFF) - * - * This function can be called even after a regular Answer (voice mode), - * the channel will be changed to Fax Mode. - * - * Example of use : - * line number 123, play something, if a fax tone is detected, handle it - * line number 124, answer directly in fax mode - * - * [incoming] - * exten => 123,1,Answer() - * exten => 123,2,BackGround(jpop) - * exten => 124,1,Goto(handle_fax,s,1) - * exten => fax,1,Goto(handle_fax,s,1) - * - * [handle_fax] - * exten => s,1,capiAnswerFax(/tmp/${UNIQUEID}) - * exten => s,2,Hangup() - * exten => h,1,deadagi,fax.php // Run SFF2TIFF and mail it. - * - * This program is free software and may be modified and - * distributed under the terms of the GNU Public License. - * - * (c) 2004,2005 by Carl Sempla, Cedrik Hans, Frank Sautter - * - */ - -#include "config.h" - -#include -#include -#include -#ifndef CC_AST_HAVE_TECH_PVT -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include "chan_capi_pvt.h" -#include "chan_capi_app.h" - -/* FAX Resolutions */ -#define FAX_STANDARD_RESOLUTION 0 -#define FAX_HIGH_RESOLUTION 1 - -/* FAX Formats */ -#define FAX_SFF_FORMAT 0 -#define FAX_PLAIN_FORMAT 1 -#define FAX_PCX_FORMAT 2 -#define FAX_DCX_FORMAT 3 -#define FAX_TIFF_FORMAT 4 -#define FAX_ASCII_FORMAT 5 -#define FAX_EXTENDED_ASCII_FORMAT 6 -#define FAX_BINARY_FILE_TRANSFER_FORMAT 7 - -typedef struct fax3proto3 { - unsigned char len; - unsigned short resolution __attribute__ ((packed)); - unsigned short format __attribute__ ((packed)); - unsigned char Infos[100] __attribute__ ((packed)); -} B3_PROTO_FAXG3; - -static char *tdesc = "(CAPI*) Receive Faxes."; -static char *app = "capiAnswerFax"; -static char *synopsis = "Answer Fax with CAPI"; -STANDARD_LOCAL_USER; - -LOCAL_USER_DECL; - -void SetupB3Config(B3_PROTO_FAXG3 *B3conf, int FAX_Format) -{ - int len1; - int len2; - char *stationID = "00000000"; - char *headLine = "CAPI FAXServer"; - - B3conf->resolution = 0; - B3conf->format = (unsigned short)FAX_Format; - len1 = strlen(stationID); - B3conf->Infos[0] = (unsigned char)len1; - strcpy((char *)&B3conf->Infos[1], stationID); - len2 = strlen(headLine); - B3conf->Infos[len1 + 1] = (unsigned char)len2; - strcpy((char *)&B3conf->Infos[len1 + 2], headLine); - B3conf->len = (unsigned char)(2 * sizeof(unsigned short) + len1 + len2 + 2); -} - -static int capi_change_bchan_fax(struct ast_channel *c) -{ - struct ast_capi_pvt *i = CC_AST_CHANNEL_PVT(c); - _cmsg CMSG; - B3_PROTO_FAXG3 B3conf; - - SetupB3Config(&B3conf, FAX_SFF_FORMAT); /* Format ignored by eicon cards */ - - DISCONNECT_B3_REQ_HEADER(&CMSG, ast_capi_ApplID, get_ast_capi_MessageNumber(), 0); - DISCONNECT_B3_REQ_NCCI(&CMSG) = i->NCCI; - _capi_put_cmsg(&CMSG); - - /* wait for the B3 layer to go down */ - while (i->state != CAPI_STATE_CONNECTED) { - usleep(10000); - } - - /* TODO: if state != BCONNECTED */ - - SELECT_B_PROTOCOL_REQ_HEADER(&CMSG, ast_capi_ApplID, get_ast_capi_MessageNumber(), 0); - SELECT_B_PROTOCOL_REQ_PLCI(&CMSG) = i->PLCI; - SELECT_B_PROTOCOL_REQ_B1PROTOCOL(&CMSG) = 4; - SELECT_B_PROTOCOL_REQ_B2PROTOCOL(&CMSG) = 4; - SELECT_B_PROTOCOL_REQ_B3PROTOCOL(&CMSG) = 4; - SELECT_B_PROTOCOL_REQ_B1CONFIGURATION(&CMSG) = NULL; - SELECT_B_PROTOCOL_REQ_B2CONFIGURATION(&CMSG) = NULL; - SELECT_B_PROTOCOL_REQ_B3CONFIGURATION(&CMSG) = (_cstruct)&B3conf; - _capi_put_cmsg(&CMSG); - - return 0; -} - -static int capi_answer_fax(struct ast_channel *c) -{ - struct ast_capi_pvt *i = CC_AST_CHANNEL_PVT(c); - MESSAGE_EXCHANGE_ERROR error; - _cmsg CMSG; - char buf[AST_CAPI_MAX_STRING]; - char *dnid; - B3_PROTO_FAXG3 B3conf; - - if (i->isdnmode && (strlen(i->incomingmsn) < strlen(i->dnid))) { - dnid = i->dnid + strlen(i->incomingmsn); - } else { - dnid = i->dnid; - } - - SetupB3Config(&B3conf, FAX_SFF_FORMAT); /* Format ignored by eicon cards */ - - CONNECT_RESP_HEADER(&CMSG, ast_capi_ApplID, i->MessageNumber, 0); - CONNECT_RESP_PLCI(&CMSG) = i->PLCI; - CONNECT_RESP_REJECT(&CMSG) = 0; - buf[0] = strlen(dnid)+2; - buf[1] = 0x0; - buf[2] = 0x80; - strncpy(&buf[3],dnid,sizeof(buf)-4); - CONNECT_RESP_CONNECTEDNUMBER(&CMSG) = buf; - CONNECT_RESP_CONNECTEDSUBADDRESS(&CMSG) = NULL; - CONNECT_RESP_LLC(&CMSG) = NULL; - CONNECT_RESP_B1PROTOCOL(&CMSG) = 4; /* T.30 modem for Group 3 fax */ - CONNECT_RESP_B2PROTOCOL(&CMSG) = 4; /* T.30 for Group 3 fax */ - CONNECT_RESP_B3PROTOCOL(&CMSG) = 4; /* T.30 for Group 3 fax */ - CONNECT_RESP_B3CONFIGURATION(&CMSG) = (_cstruct)&B3conf; - - cc_ast_verbose(3, 0, VERBOSE_PREFIX_3 "CAPI Answering in fax mode for MSN %s\n", dnid); - - if ((error = _capi_put_cmsg(&CMSG)) != 0) { - return -1; - } - - i->state = CAPI_STATE_ANSWERING; - i->doB3 = AST_CAPI_B3_DONT; - i->outgoing = 0; - i->earlyB3 = -1; - - return 0; -} - -static int capianswerfax_exec(struct ast_channel *chan, void *data) -{ - int res = 0; - struct localuser *u; - char *vdata; - struct ast_capi_pvt *i = CC_AST_CHANNEL_PVT(chan); - - if (!data) { /* no data implies no filename or anything is present */ - ast_log(LOG_WARNING, "capiAnswerFax requires an argument (filename)\n"); - return -1; - } - vdata = ast_strdupa(data); - - LOCAL_USER_ADD(u); - - if (!strcasecmp("CAPI", chan->type) == 0) { - ast_log(LOG_WARNING, "capiFax only works on CAPI channels, check your extensions.conf!\n"); - LOCAL_USER_REMOVE(u); - return -1; - } - - i->fFax = fopen(vdata, "wb"); - - if (i->fFax == NULL) { - ast_log(LOG_WARNING, "capiAnswerFax: can't create the output file (%s)\n", strerror(errno)); - LOCAL_USER_REMOVE(u); - return -1; - } - - i->FaxState = 1; - if (i->state != CAPI_STATE_BCONNECTED) { - capi_answer_fax(chan); - } else { - capi_change_bchan_fax(chan); - } - while (i->FaxState) { - sleep(1); - } - - /* if the file has zero length */ - if (ftell(i->fFax) == 0L) - res = -1; - - cc_ast_verbose(2, 1, VERBOSE_PREFIX_3 "Closing fax file...\n"); - fclose(i->fFax); - i->fFax = NULL; - - switch (i->reason) { - case 0x3490: - case 0x349f: - res = (i->reasonb3 == 0) ? 0 : -1; - break; - default: - res = -1; - } - - if (res != 0) { - cc_ast_verbose(2, 0, - VERBOSE_PREFIX_1 "capiAnswerFax: fax receive failed.\n"); - unlink(vdata); - } else { - cc_ast_verbose(2, 0, - VERBOSE_PREFIX_1 "capiAnswerFax: fax received.\n"); - } - - LOCAL_USER_REMOVE(u); - return res; -} - -int unload_module(void) -{ - STANDARD_HANGUP_LOCALUSERS; - return ast_unregister_application(app); -} - -int load_module(void) -{ - return ast_register_application(app, capianswerfax_exec, synopsis, tdesc); -} - -char *description(void) -{ - return tdesc; -} - -int usecount(void) -{ - int res; - STANDARD_USECOUNT(res); - return res; -} - -char *key() -{ - return ASTERISK_GPL_KEY; -} - diff --git a/chan_capi.c b/chan_capi.c index e31dbda..a5d3e0f 100644 --- a/chan_capi.c +++ b/chan_capi.c @@ -559,7 +559,6 @@ static void interface_cleanup(struct ast_capi_pvt *i) i->cause = 0; i->faxhandled = 0; - i->FaxState = 0; i->PLCI = 0; i->NCCI = 0; @@ -857,9 +856,9 @@ int capi_call(struct ast_channel *c, char *idest, int timeout) } /* - * Asterisk tells us to answer a call + * answer a capi call */ -static int capi_answer(struct ast_channel *c) +static int capi_send_answer(struct ast_channel *c, int *bprot, _cstruct b3conf) { struct ast_capi_pvt *i = CC_AST_CHANNEL_PVT(c); _cmsg CMSG; @@ -874,9 +873,7 @@ static int capi_answer(struct ast_channel *c) dnid = i->dnid; } - i->fFax = NULL; - i->faxhandled = 0; - i->FaxState = 0; + memset(&CMSG, 0, sizeof(CMSG)); CONNECT_RESP_HEADER(&CMSG, ast_capi_ApplID, i->MessageNumber, 0); CONNECT_RESP_PLCI(&CMSG) = i->PLCI; @@ -886,11 +883,10 @@ static int capi_answer(struct ast_channel *c) buf[2] = 0x80; strncpy(&buf[3], dnid, sizeof(buf) - 4); CONNECT_RESP_CONNECTEDNUMBER(&CMSG) = buf; - CONNECT_RESP_CONNECTEDSUBADDRESS(&CMSG) = NULL; - CONNECT_RESP_LLC(&CMSG) = NULL; - CONNECT_RESP_B1PROTOCOL(&CMSG) = 1; - CONNECT_RESP_B2PROTOCOL(&CMSG) = 1; - CONNECT_RESP_B3PROTOCOL(&CMSG) = 0; + CONNECT_RESP_B1PROTOCOL(&CMSG) = bprot[0]; + CONNECT_RESP_B2PROTOCOL(&CMSG) = bprot[1]; + CONNECT_RESP_B3PROTOCOL(&CMSG) = bprot[2]; + CONNECT_RESP_B3CONFIGURATION(&CMSG) = b3conf; cc_ast_verbose(3, 0, VERBOSE_PREFIX_3 "%s: Answering for %s\n", i->name, dnid); @@ -907,6 +903,16 @@ static int capi_answer(struct ast_channel *c) return 0; } +/* + * Asterisk tells us to answer a call + */ +static int capi_answer(struct ast_channel *c) +{ + int bprot[3] = { 1, 1, 0 }; + + return capi_send_answer(c, bprot, NULL); +} + /* * Asterisk tells us to read for a channel */ @@ -1401,6 +1407,143 @@ struct ast_channel *capi_request(char *type, int format, void *data) return NULL; } +/* + * fill out fax conf struct + */ +static void setup_b3_fax_config(B3_PROTO_FAXG3 *b3conf, int fax_format, char *stationid, char *headline) +{ + int len1; + int len2; + + cc_ast_verbose(3, 1, VERBOSE_PREFIX_3 "Setup fax b3conf fmt=%d, stationid='%s' headline='%s'\n", + fax_format, stationid, headline); + b3conf->resolution = 0; + b3conf->format = (unsigned short)fax_format; + len1 = strlen(stationid); + b3conf->Infos[0] = (unsigned char)len1; + strcpy((char *)&b3conf->Infos[1], stationid); + len2 = strlen(headline); + b3conf->Infos[len1 + 1] = (unsigned char)len2; + strcpy((char *)&b3conf->Infos[len1 + 2], headline); + b3conf->len = (unsigned char)(2 * sizeof(unsigned short) + len1 + len2 + 2); +} + +/* + * change b protocol to fax + */ +static void capi_change_bchan_fax(struct ast_channel *c, B3_PROTO_FAXG3 *b3conf) +{ + struct ast_capi_pvt *i = CC_AST_CHANNEL_PVT(c); + _cmsg CMSG; + + if (i->state == CAPI_STATE_BCONNECTED) { + int waitcount = 200; + DISCONNECT_B3_REQ_HEADER(&CMSG, ast_capi_ApplID, get_ast_capi_MessageNumber(), 0); + DISCONNECT_B3_REQ_NCCI(&CMSG) = i->NCCI; + _capi_put_cmsg(&CMSG); + + /* wait for the B3 layer to go down */ + while ((waitcount > 0) && (i->state == CAPI_STATE_BCONNECTED)) { + usleep(10000); + waitcount--; + } + if (i->state != CAPI_STATE_CONNECTED) { + ast_log(LOG_WARNING, "capi receivefax disconnect b3 wait error\n"); + } + } + + SELECT_B_PROTOCOL_REQ_HEADER(&CMSG, ast_capi_ApplID, get_ast_capi_MessageNumber(), 0); + SELECT_B_PROTOCOL_REQ_PLCI(&CMSG) = i->PLCI; + SELECT_B_PROTOCOL_REQ_B1PROTOCOL(&CMSG) = 4; + SELECT_B_PROTOCOL_REQ_B2PROTOCOL(&CMSG) = 4; + SELECT_B_PROTOCOL_REQ_B3PROTOCOL(&CMSG) = 4; + SELECT_B_PROTOCOL_REQ_B1CONFIGURATION(&CMSG) = NULL; + SELECT_B_PROTOCOL_REQ_B2CONFIGURATION(&CMSG) = NULL; + SELECT_B_PROTOCOL_REQ_B3CONFIGURATION(&CMSG) = (_cstruct)b3conf; + _capi_put_cmsg(&CMSG); + + return; +} + +/* + * capicommand 'receivefax' + */ +static int capi_receive_fax(struct ast_channel *c, char *data) +{ + struct ast_capi_pvt *i = CC_AST_CHANNEL_PVT(c); + int res = 0; + char *filename, *stationid, *headline; + int bprot[3] = { 4, 4, 4 }; + B3_PROTO_FAXG3 b3conf; + + if (!data) { /* no data implies no filename or anything is present */ + ast_log(LOG_WARNING, "capi receivefax requires a filename\n"); + return -1; + } + + filename = strsep(&data, "|"); + stationid = strsep(&data, "|"); + headline = data; + + if (!stationid) + stationid = emptyid; + if (!headline) + headline = emptyid; + + if ((i->fFax = fopen(filename, "wb")) == NULL) { + ast_log(LOG_WARNING, "can't create fax output file (%s)\n", strerror(errno)); + return -1; + } + + i->FaxState = 1; + setup_b3_fax_config(&b3conf, FAX_SFF_FORMAT, stationid, headline); + + switch (i->state) { + case CAPI_STATE_ALERTING: + case CAPI_STATE_DID: + case CAPI_STATE_INCALL: + capi_send_answer(c, bprot, (_cstruct)&b3conf); + break; + case CAPI_STATE_CONNECTED: + case CAPI_STATE_BCONNECTED: + capi_change_bchan_fax(c, &b3conf); + break; + default: + i->FaxState = 0; + ast_log(LOG_WARNING, "capi receive fax in wrong state (%d)\n", + i->state); + return -1; + } + + while (i->FaxState == 1) { + usleep(10000); + } + + res = i->FaxState; + i->FaxState = 0; + + /* if the file has zero length */ + if (ftell(i->fFax) == 0L) { + res = -1; + } + + cc_ast_verbose(2, 1, VERBOSE_PREFIX_3 "Closing fax file...\n"); + fclose(i->fFax); + i->fFax = NULL; + + if (res != 0) { + cc_ast_verbose(2, 0, + VERBOSE_PREFIX_1 "capi receivefax: fax receive failed reason=0x%04x reasonB3=0x%04x\n", + i->reason, i->reasonb3); + unlink(filename); + } else { + cc_ast_verbose(2, 0, + VERBOSE_PREFIX_1 "capi receivefax: fax receive successful.\n"); + } + + return res; +} + /* * Fax guard tone -- Handle and return NULL */ @@ -1769,7 +1912,6 @@ static void handle_info_disconnect(_cmsg *CMSG, unsigned int PLCI, unsigned int i->name); if (i->FaxState) { /* in capiFax */ - i->FaxState = 0; return; } pipe_cause_control(i, 0); @@ -2233,7 +2375,7 @@ static void capi_handle_connect_b3_active_indication(_cmsg *CMSG, unsigned int P } if (i->FaxState) { - cc_ast_verbose(6, 0, VERBOSE_PREFIX_3 "%s: Fax connection, no EC/DTMF\n", + cc_ast_verbose(3, 1, VERBOSE_PREFIX_3 "%s: Fax connection, no EC/DTMF\n", i->name); } else { capi_echo_canceller(i->owner, EC_FUNCTION_ENABLE); @@ -2328,6 +2470,18 @@ static void capi_handle_disconnect_indication(_cmsg *CMSG, unsigned int PLCI, un i->reason = DISCONNECT_IND_REASON(CMSG); + if (i->FaxState == 1) { + /* in capiFax */ + switch (i->reason) { + case 0x3490: + case 0x349f: + i->FaxState = (i->reasonb3 == 0) ? 0 : -1; + break; + default: + i->FaxState = -1; + } + } + if (PLCI == i->onholdPLCI) { /* the caller onhold hung up (or ECTed away) */ interface_cleanup(i); @@ -2900,7 +3054,7 @@ static int capicommand_exec(struct ast_channel *chan, void *data) int res = 0; struct localuser *u; char *s; - char *stringp = NULL; + char *stringp; char *command, *params; if (!data) { @@ -2923,6 +3077,8 @@ static int capicommand_exec(struct ast_channel *chan, void *data) res = capi_set_earlyb3(chan, params); } else if (!strcasecmp(command, "deflect")) { res = capi_call_deflect(chan, params); + } else if (!strcasecmp(command, "receivefax")) { + res = capi_receive_fax(chan, params); } else { res = -1; ast_log(LOG_WARNING, "Unknown command '%s' for capiCommand\n", diff --git a/chan_capi_pvt.h b/chan_capi_pvt.h index b8f80f8..d4c57f1 100644 --- a/chan_capi_pvt.h +++ b/chan_capi_pvt.h @@ -64,6 +64,27 @@ static inline unsigned short read_capi_word(void *m) static ast_mutex_t mutex = AST_MUTEX_INITIALIZER #endif +/* FAX Resolutions */ +#define FAX_STANDARD_RESOLUTION 0 +#define FAX_HIGH_RESOLUTION 1 + +/* FAX Formats */ +#define FAX_SFF_FORMAT 0 +#define FAX_PLAIN_FORMAT 1 +#define FAX_PCX_FORMAT 2 +#define FAX_DCX_FORMAT 3 +#define FAX_TIFF_FORMAT 4 +#define FAX_ASCII_FORMAT 5 +#define FAX_EXTENDED_ASCII_FORMAT 6 +#define FAX_BINARY_FILE_TRANSFER_FORMAT 7 + +/* Fax struct */ +typedef struct fax3proto3 { + unsigned char len; + unsigned short resolution __attribute__ ((packed)); + unsigned short format __attribute__ ((packed)); + unsigned char Infos[100] __attribute__ ((packed)); +} B3_PROTO_FAXG3; /* duration in ms for sending and detecting dtmfs */ #define AST_CAPI_DTMF_DURATION 0x40