pageSendApp.c++
上传用户:weiyuanprp
上传日期:2020-05-20
资源大小:1169k
文件大小:41k
- /* $Id: pageSendApp.c++,v 1.10 2009/07/13 04:50:41 faxguy Exp $ */
- /*
- * Copyright (c) 1994-1996 Sam Leffler
- * Copyright (c) 1994-1996 Silicon Graphics, Inc.
- * HylaFAX is a trademark of Silicon Graphics
- *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * that (i) the above copyright notices and this permission notice appear in
- * all copies of the software and related documentation, and (ii) the names of
- * Sam Leffler and Silicon Graphics may not be used in any advertising or
- * publicity relating to the software without the specific, prior written
- * permission of Sam Leffler and Silicon Graphics.
- *
- * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
- * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
- *
- * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
- * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
- * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
- * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
- * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
- * OF THIS SOFTWARE.
- */
- #include <sys/types.h>
- #include <unistd.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <sys/file.h>
- #include <signal.h>
- #include <ctype.h>
- #include "FaxMachineInfo.h"
- #include "FaxAcctInfo.h"
- #include "UUCPLock.h"
- #include "pageSendApp.h"
- #include "FaxRequest.h"
- #include "Dispatcher.h"
- #include "StackBuffer.h"
- #include "Sys.h"
- #include "ixo.h"
- #include "config.h"
- /*
- * Send messages with IXO/TAP protocol.
- */
- pageSendApp* pageSendApp::_instance = NULL;
- pageSendApp::pageSendApp(const fxStr& devName, const fxStr& devID)
- : ModemServer(devName, devID)
- {
- ready = false;
- modemLock = NULL;
- setupConfig();
- fxAssert(_instance == NULL, "Cannot create multiple pageSendApp instances");
- _instance = this;
- }
- pageSendApp::~pageSendApp()
- {
- delete modemLock;
- }
- pageSendApp& pageSendApp::instance() { return *_instance; }
- void
- pageSendApp::initialize(int argc, char** argv)
- {
- ModemServer::initialize(argc, argv);
- faxApp::initialize(argc, argv);
- // NB: must do last to override config file information
- for (GetoptIter iter(argc, argv, getOpts()); iter.notDone(); iter++)
- switch (iter.option()) {
- case 'l': // do uucp locking
- modemLock = getUUCPLock(getModemDevice());
- break;
- case 'c': // set configuration parameter
- readConfigItem(iter.optArg());
- break;
- }
- }
- void
- pageSendApp::open()
- {
- ModemServer::open();
- faxApp::open();
- }
- void
- pageSendApp::close()
- {
- if (isRunning()) {
- if (state == ModemServer::SENDING) {
- /*
- * Terminate the active job and let the send
- * operation complete so that the transfer is
- * logged and the appropriate exit status is
- * returned to the caller.
- */
- ModemServer::abortSession();
- } else {
- ModemServer::close();
- faxApp::close();
- }
- }
- }
- # define BATCH_FIRST 1
- # define BATCH_LAST 2
- FaxSendStatus
- pageSendApp::send(const char** filenames, int num)
- {
- u_int batched = BATCH_FIRST;
- FaxSendStatus status = send_done;
- fxStr batchcommid, notice, errorcode;
- time_t retrybatchtts = 0;
- for (int i = 0; i < num; i++) {
- if (i+1 == num)
- batched |= BATCH_LAST;
-
- int fd = Sys::open(filenames[i], O_RDWR);
- if (fd >= 0) {
- if (flock(fd, LOCK_EX) >= 0) {
- FaxRequest* req = new FaxRequest(filenames[i], fd);
- bool reject;
- if (req->readQFile(reject) && !reject) {
- if (status == send_done) {
- if (req->findItem(FaxRequest::send_page) != fx_invalidArrayIndex) {
- FaxMachineInfo info;
- info.updateConfig(canonicalizePhoneNumber(req->number));
- FaxAcctInfo ai;
- ai.start = Sys::now();
- req->commid = batchcommid; // pass commid on...
- sendPage(*req, info, batched);
- batchcommid = req->commid; // ... to all batched jobs
- ai.jobid = req->jobid;
- ai.jobtag = req->jobtag;
- ai.user = req->mailaddr;
- ai.duration = Sys::now() - ai.start;
- ai.conntime = getConnectTime();
- ai.commid = req->commid;
- ai.device = getModemDeviceID();
- ai.dest = req->external;
- ai.csi = "";
- ai.params = 0;
- ai.npages = 0;
- CallID empty_callid;
- ai.callid = empty_callid;
- ai.owner = req->owner;
- if (req->status == send_done)
- ai.status = "";
- else {
- errorcode = req->errorcode;
- notice = req->notice;
- ai.status = req->notice;
- retrybatchtts = req->tts;
- }
- ai.jobinfo = fxStr::format("%u/%u/%u/%u/%u/%u/%u",
- req->totpages, req->ntries, req->ndials, req->totdials, req->maxdials, req->tottries, req->maxtries);
- if (!ai.record("PAGE"))
- logError("Error writing %s accounting record, dest=%s",
- "PAGE", (const char*) ai.dest);
- } else
- sendFailed(*req, send_failed, "Job has no PIN to send to");
- status = req->status;
- } else {
- /*
- * This job cannot get sent right now due to an error in a previous
- * job in the batch.
- *
- * In the event that the previous error was not an in-job error (e.g.
- * a busy signal) then we treat that error as if it applies to all jobs.
- * We "keep the batch together" by synchronizing their tts.
- *
- * In the event that the previous error was an in-job error, then the
- * previous job processing is essentially blocking this job from
- * processing, and so we set the notice accordingly and don't increase
- * the dial-count. In case batching itself triggered the problem, we
- * don't set the tts, allowing faxq to reschedule the job, expecting that
- * to disassemble and "shuffle" the entire batch.
- */
- if (errorcode == "E001" ||
- errorcode == "E002" ||
- errorcode == "E003") {
- /* busy, no carrier, no answer */
- req->notice = notice;
- req->status = send_retry;
- req->tts = retrybatchtts;
- req->totdials++;
- } else {
- req->notice = "Blocked by another job";
- req->status = send_retry;
- }
- }
- req->writeQFile(); // update on-disk copy
- delete req;
- } else {
- delete req;
- logError("Could not read request file");
- status = send_failed;
- }
- } else {
- logError("Could not lock request file: %m");
- Sys::close(fd);
- status = send_failed;
- }
- } else {
- logError("Could not open request file "%s": %m", filenames[i]);
- status = send_failed;
- }
- batched = 0; // disable BATCH_FIRST and BATCH_LAST routines
- }
- return (status); // return status for exit
- }
- void
- pageSendApp::sendPage(FaxRequest& req, FaxMachineInfo& info, u_int& batched)
- {
- if (!(batched & BATCH_FIRST) || lockModem()) {
- if (batched & BATCH_FIRST) {
- beginSession(req.number);
- batchid = getCommID();
- }
- req.commid = getCommID(); // set by beginSession
- traceServer("SEND PAGE (possibly batched): JOB %s DEST %s COMMID %s DEVICE '%s'"
- , (const char*) req.jobid
- , (const char*) req.external
- , (const char*) req.commid
- , (const char*) getModemDevice()
- );
- /*
- * Setup tty parity; per-destination information takes
- * precedence over command-line arguments/config params;
- * otherwise the IXO/TAP spec is used (set below).
- */
- if (info.getPagerTTYParity() != "")
- pagerTTYParity = info.getPagerTTYParity();
- // NB: may need to set tty baud rate here XXX
- if (!(batched & BATCH_FIRST) || setupModem(true)) {
- changeState(SENDING);
- setServerStatus("Sending page " | req.jobid);
- /*
- * Construct the phone number to dial by applying the
- * dialing rules to the user-specified dialing string.
- */
- fxStr msg;
- if (prepareMsg(req, info, msg))
- sendPage(req, info, prepareDialString(req.number), msg, batched);
- } else
- sendFailed(req, send_retry, "Can not setup modem", 4*pollModemWait);
- if ((batched & BATCH_LAST) || (req.status != send_done)) {
- discardModem(true);
- changeState(MODEMWAIT, 5);
- unlockModem();
- endSession();
- }
- } else {
- sendFailed(req, send_retry, "Can not lock modem device",2*pollLockWait);
- }
- }
- bool
- pageSendApp::prepareMsg(FaxRequest& req, FaxMachineInfo& info, fxStr& msg)
- {
- u_int i = req.findItem(FaxRequest::send_data);
- if (i == fx_invalidArrayIndex) // page w/o text
- return (true);
- int fd = Sys::open(req.items[i].item, O_RDONLY);
- if (fd < 0) {
- sendFailed(req, send_failed,
- "Internal error: unable to open text message file");
- return (false);
- }
- struct stat sb;
- (void) Sys::fstat(fd, sb);
- msg.resize((u_int) sb.st_size);
- if (Sys::read(fd, &msg[0], (u_int) sb.st_size) != sb.st_size) {
- sendFailed(req, send_failed,
- "Internal error: unable to read text message file");
- return (false);
- }
- Sys::close(fd);
- u_int maxMsgLen = info.getPagerMaxMsgLength();
- if (maxMsgLen == (u_int) -1) // not set, use default
- maxMsgLen = pagerMaxMsgLength;
- if (msg.length() > maxMsgLen) {
- traceServer("Pager message length %u too large; truncated to %u",
- msg.length(), maxMsgLen);
- msg.resize(maxMsgLen);
- }
- return (true);
- }
- void
- pageSendApp::sendFailed(FaxRequest& req, FaxSendStatus stat, const char* notice, u_int tts)
- {
- req.status = stat;
- req.notice = notice;
- /*
- * When requeued for the default interval (called with 3 args),
- * don't adjust the time-to-send field so that the spooler
- * will set it according to the default algorithm that
- * uses the command-line parameter or requeueOther and a random jitter.
- */
- if (tts != 0)
- req.tts = Sys::now() + tts;
- traceServer("PAGE FAILED: %s", notice);
- }
- void
- pageSendApp::sendPage(FaxRequest& req, FaxMachineInfo& info, const fxStr& number, const fxStr& msg, u_int& batched)
- {
- connTime = 0; // indicate no connection
- if ((batched & BATCH_FIRST) && !getModem()->dataService()) {
- sendFailed(req, send_failed, "Unable to configure modem for data use");
- return;
- }
- req.notice = "";
- fxStr notice;
- time_t pageStart = Sys::now();
- if (batched & BATCH_FIRST) {
- if (info.getPagerSetupCmds() != "") // use values from info file
- pagerSetupCmds = info.getPagerSetupCmds();
- if (pagerSetupCmds != "") // configure line speed, etc.
- (void) getModem()->atCmd(pagerSetupCmds);
- }
- CallStatus callstat;
- if (batched & BATCH_FIRST)
- callstat = getModem()->dial(number, req.faxnumber, notice);
- else
- callstat = ClassModem::OK;
- if (callstat == ClassModem::OK)
- connTime = Sys::now(); // connection start time
- (void) abortRequested(); // check for user abort
- if (callstat == ClassModem::OK && !abortCall) {
- req.ndials = 0; // consec. failed dial attempts
- req.tottries++; // total answered calls
- req.totdials++; // total attempted calls
- info.setCalledBefore(true);
- info.setDialFailures(0);
- req.status = send_ok; // be optimistic
- // from this point on, the treatment of the two protocols differs
- if (streq(info.getPagingProtocol(), "ixo")) {
- sendIxoPage(req, info, msg, notice, batched);
- } else if (streq(info.getPagingProtocol(), "ucp")) {
- sendUcpPage(req, info, msg, notice);
- } else {
- notice = req.notice | "; paging protocol unknown ";
- sendFailed(req, send_failed, notice);
- req.status = send_failed;
- }
- // here again, we have identical code
- if (req.status == send_ok) {
- time_t now = Sys::now();
- traceServer("SEND PAGE: FROM " | req.mailaddr
- | " TO " | req.external | " (sent in %s)",
- fmtTime(now - pageStart));
- info.setSendFailures(0);
- } else {
- info.setSendFailures(info.getSendFailures()+1);
- info.setLastSendFailure(req.notice);
- }
- } else if (!abortCall) {
- /*
- * Analyze the call status codes and selectively decide if the
- * job should be retried. We try to avoid the situations where
- * we might be calling the wrong number so that we don't end up
- * harrassing someone w/ repeated calls.
- */
- req.ndials++;
- req.totdials++; // total attempted calls
- switch (callstat) {
- case ClassModem::NOCARRIER: // no carrier detected on remote side
- /*
- * Since some modems can not distinguish between ``No Carrier''
- * and ``No Answer'' we offer this configurable hack whereby
- * we'll retry the job <n> times in the face of ``No Carrier''
- * dialing errors; if we've never previously reached a modem
- * at that number. This should not be used except if
- * the modem is incapable of distinguishing between
- * ``No Carrier'' and ``No Answer''.
- */
- if (!info.getCalledBefore() && req.ndials > retryMAX[callstat]) {
- sendFailed(req, send_failed, notice);
- break;
- }
- /* fall thru... */
- case ClassModem::NODIALTONE: // no local dialtone, possibly unplugged
- case ClassModem::ERROR: // modem might just need to be reset
- case ClassModem::FAILURE: // modem returned something unexpected
- case ClassModem::BUSY: // busy signal
- case ClassModem::NOANSWER: // no answer or ring back
- sendFailed(req, send_retry, notice, requeueTTS[callstat]);
- /* fall thru... */
- case ClassModem::OK: // call was aborted by user
- break;
- }
- if (callstat != ClassModem::OK) {
- info.setDialFailures(info.getDialFailures()+1);
- info.setLastDialFailure(req.notice);
- }
- }
- if (abortCall)
- sendFailed(req, send_retry, "Call aborted by user");
- else if (req.status == send_retry) {
- if (req.totdials == req.maxdials) {
- notice = req.notice | "; too many attempts to dial";
- sendFailed(req, send_failed, notice);
- } else if (req.tottries == req.maxtries) {
- notice = req.notice | "; too many attempts to send";
- sendFailed(req, send_failed, notice);
- }
- }
- if ((batched & BATCH_LAST) || (req.status != send_done))
- {
- /*
- * Cleanup after the call. If we have new information on
- * the client's remote capabilities, the machine info
- * database will be updated when the instance is destroyed.
- */
- getModem()->hangup();
- }
- /*
- * This may not be exact--the line may already have been
- * dropped--but it should be close enough unless the modem
- * gets wedged and the hangup work times out. Also be
- * sure to register a non-zero amount of connect time so
- * that folks doing accounting can adjust charge-back costs
- * to reflect any minimum connect time tarrifs imposted by
- * their PTT (e.g. calls < 1 minute are rounded up to 1 min.)
- */
- if (connTime) {
- connTime = Sys::now() - connTime;
- if (connTime == 0)
- connTime++;
- }
- }
- /*
- * here comes the IXO specific code for sendPage, search for the
- * string 'BEGIN UCP Support' for UCP specific code
- */
- void
- pageSendApp::sendIxoPage(FaxRequest& req, FaxMachineInfo& info, const fxStr& msg,
- fxStr& notice, u_int& batched)
- {
- if (batched & BATCH_FIRST) {
- if(!pagePrologue(req, info, notice)) {
- sendFailed(req, req.status, notice, requeueProto);
- return;
- }
- }
- while (req.items.length() > 0) { // messages
- u_int i = req.findItem(FaxRequest::send_page);
- if (i == fx_invalidArrayIndex)
- break;
- if (req.items[i].item.length() == 0) {
- sendFailed(req, send_failed, "No PIN specified");
- break;
- }
- if (!sendPagerMsg(req, req.items[i], msg, req.notice)) {
- /*
- * On protocol errors retry more quickly
- * (there's no reason to wait is there?).
- */
- if (req.status == send_retry) {
- req.tts = time(0) + requeueProto;
- break;
- }
- }
- req.items.remove(i);
- }
- if ((batched & BATCH_LAST)
- && req.status == send_ok) {
- (void) pageEpilogue(req, info, notice);
- }
- }
- u_int
- pageSendApp::getResponse(fxStackBuffer& buf, long secs)
- {
- buf.reset();
- if (secs) startTimeout(secs*1000);
- for (;;) {
- int c = getModemChar(0);
- if (c == EOF)
- break;
- if (c == 'r' || c == ' 03') {
- if (buf.getLength() > 0) // discard leading r's or ETX
- break;
- } else if (c != 'n') // discard all n's
- buf.put(c);
- }
- if (secs) stopTimeout("reading line from modem");
- if (buf.getLength() > 0)
- traceIXOCom("-->", (u_char*) (const char*) buf, buf.getLength());
- return (buf.getLength());
- }
- /*
- * Scan through a buffer looking for a potential
- * code byte return in a protocol response.
- * This is needed because some pager services such
- * as PageNet intersperse protocol messages and
- * verbose text messages.
- */
- static bool
- scanForCode(const u_char*& cp, u_int& len)
- {
- if (len > 0) {
- do {
- cp++, len--;
- } while (len > 0 &&
- *cp != ACK && *cp != NAK && *cp != ESC && *cp != RS);
- }
- return (len > 0);
- }
- bool
- pageSendApp::pagePrologue(FaxRequest& req, const FaxMachineInfo& info, fxStr& emsg)
- {
- fxStackBuffer buf;
- time_t start;
- /*
- * Send r and wait for ``ID='' response.
- * Repeat at 2 second intervals until a
- * response is received or ntries have
- * been done.
- */
- traceIXO("EXPECT ID (paging central identification)");
- start = Sys::now();
- bool gotID = false;
- do {
- putModem("r", 1);
- if (getResponse(buf, ixoIDProbe) >= 3) {
- // skip leading white space
- const char* cp;
- for (cp = buf; *cp && isspace(*cp); cp++)
- ;
- gotID = strneq(cp, "ID=", 3);
- }
- if (gotID) {
- traceIXO("RECV ID ("%.*s")",
- buf.getLength(), (const char*) buf);
- } else
- traceResponse(buf);
- } while (!gotID && (unsigned) Sys::now() - start < ixoIDTimeout);
- if (!gotID) {
- emsg = "No initial ID response from paging central {E500}";
- req.status = send_retry;
- return (false);
- }
- flushModemInput(); // paging central may send multiple ID=
- /*
- * Identify use of automatic protocol (as opposed
- * to manual) and proceed with login procedure:
- *
- * ESC SST<pwd>.
- *
- * ESC means ``automatic dump mode'' protocol.
- * SS identifies service:
- * P = Pager ID
- * G = Message (?)
- * T identifies type of terminal or device sending:
- * 1 = ``category of entry devices using the same protocol''
- * (PETs and IXO)
- * 7,8,9 = ``wild card terminals or devices which may
- * relate to a specific users' system''.
- * <pwd> is a 6-character alpha-numeric password
- * string (optional)
- */
- const fxStr& pass = info.getPagerPassword();
- fxStr prolog("