]> git.ipfire.org Git - thirdparty/HylaFAX.git/commitdiff
Bug 547: add modern distinctive ring support, enhance external getty
authorLee Howard <faxguy@howardsilvan.com>
Sat, 1 Jan 2005 21:27:14 +0000 (21:27 +0000)
committerLee Howard <faxguy@howardsilvan.com>
Sat, 1 Jan 2005 21:27:14 +0000 (21:27 +0000)
         support, and add NoAnswerVoice config item

CHANGES
CONTRIBUTORS
faxd/ClassModem.c++
faxd/ClassModem.h
faxd/Getty.c++
faxd/Getty.h
faxd/ModemConfig.c++
faxd/ModemConfig.h
faxd/faxGettyApp.c++
faxd/faxGettyApp.h
man/hylafax-config.4f

diff --git a/CHANGES b/CHANGES
index bceeff1acbe7e0220be2c790045f63c11de93e61..d646e9ea963ddcdf5409ec9ad735aa3105c766f3 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,9 @@
 
 Changelog for HylaFAX 4.2.1
 
+* improve modern distinctive ring support, add
+  NoAnswerVoice config feature, and enhance third-party
+  getty interaction (1 Jan 2005)
 * add support for etc/resetmodem (30 Dec 2004)
 * fix NOTIFY_FAXMASTER within notify (30 Dec 2004)
 * improve tiff2pdf and use it more (29 Dec 2004)
index 6ee9307b983e83f136a1ec6226ff02cdde781281..1383a47f84e06b02e6f04ee7d029bd561b384144 100644 (file)
@@ -34,37 +34,37 @@ HylaFAX since the project was moved to hylafax.org (November 1998):
 Alvaro Aguilera      Andre Albsmeier      a.pogoda@web.de
 Ronald Appelfelder   Heinz-Ado Arnolds    Jay Ashworth
 Abramo Bagnara       Chris Bainbridge     Charly Baker
-Toki Barron          Stephan Bauer        Dmitry Bely
-Thomas Biege         David Birnbaum       Trevor Blackwell
-Siegfried Bosch      Marc Boucher         Ross Boylan
-Frank Brock          Glen Burkhardt       Yves Carlier
-Ed Casas             Jonathon Chen        Robert Colquhoun
-Ken Cornetet         Seth Cuaiklin        Albert DeKnuydt
-Paul Eggert          Juergen Fischer      Kevin Fleming
-John Florian         Patrice Fournier     Vyacheslav Frolov
-John Gilman          Kris Henderson       Sascha Herrmann
-Carsten Hoeger       Lee Howard           John Interrante
-Damiam Ivereigh      Bernard Jech         Nico Kadel-Garcia
-Sean Kamath          Jonathan Kamens      Dimitry Ketov
-Steffan Klipsch      Hannu Koivisto       Arjen de Korte
-Ken Lalonde          Ilguiz Latypov       Rob Leadbeater
-Guillaume Legoupil   Greg Luck            Lionel Mamane
-Pierluigi Mangani    Campbell McKilligan  Bodo Meissner
-John Miller          Aldo Mini            Boris Mironov        
-Seth Mos             Darren Nickerson     Andrea Nicolini      
-Giulio Orsero        Chris Parsons        Michael Pedersen     
-John Perkins         Simon Perry          Frank Peters         
-Vu Pham              John Patrick Poet    Harald Pollack       
-Andreas Pretzsch     Bernd Proissl        Matthew Rice         
-Tim Rice             Daniel Robbins       Giuseppe Sacco       
-Michael Salzmann     Sven Schmidt         John Sellens         
-Simon <iahnl@iah.nl> Vilmos Soti          Alan Sparks          
-Andy Sparrow         Marco Steinacher     Iain Stevenson       
-Marius Strobl        Kazuhide Takahashi   Daryl Thachuk        
-Steve Tuckner        Aidan Van Dyk        Norbert Warmuth      
-Phil Watkinson       James Werkowski      Chas Williams        
-John Williams        Steve Williams       John Wolfe           
-Bill Young           Bruce Young
+Toki Barron          Stephan Bauer        George Bell
+Dmitry Bely          Thomas Biege         David Birnbaum       
+Trevor Blackwell     Siegfried Bosch      Marc Boucher         
+Ross Boylan          Frank Brock          Glen Burkhardt       
+Yves Carlier         Ed Casas             Jonathon Chen        
+Robert Colquhoun     Ken Cornetet         Seth Cuaiklin        
+Albert DeKnuydt      Paul Eggert          Juergen Fischer      
+Kevin Fleming        John Florian         Patrice Fournier     
+Vyacheslav Frolov    John Gilman          Kris Henderson       
+Sascha Herrmann      Carsten Hoeger       Lee Howard           
+John Interrante      Damiam Ivereigh      Bernard Jech         
+Nico Kadel-Garcia    Sean Kamath          Jonathan Kamens      
+Dimitry Ketov        Steffan Klipsch      Hannu Koivisto       
+Arjen de Korte       Ken Lalonde          Ilguiz Latypov       
+Rob Leadbeater       Guillaume Legoupil   Greg Luck            
+Lionel Mamane        Pierluigi Mangani    Campbell McKilligan  
+Bodo Meissner        John Miller          Aldo Mini            
+Boris Mironov        Seth Mos             Darren Nickerson     
+Andrea Nicolini      Giulio Orsero        Chris Parsons        
+Michael Pedersen     John Perkins         Simon Perry          
+Frank Peters         Vu Pham              John Patrick Poet    
+Harald Pollack       Andreas Pretzsch     Bernd Proissl        
+Matthew Rice         Tim Rice             Daniel Robbins       
+Giuseppe Sacco       Michael Salzmann     Sven Schmidt         
+John Sellens         Simon <iahnl@iah.nl> Vilmos Soti          
+Alan Sparks          Andy Sparrow         Marco Steinacher     
+Iain Stevenson       Marius Strobl        Kazuhide Takahashi   
+Daryl Thachuk        Steve Tuckner        Aidan Van Dyk        
+Norbert Warmuth      Phil Watkinson       James Werkowski      
+Chas Williams        John Williams        Steve Williams       
+John Wolfe           Bill Young           Bruce Young
 
 We appreciate and recognize the upstreamed contributions from these 
 distributions:
index 1da3f9238b592a27eff2acc406b484c8989b54b9..18d9aada083ba964b7b019faacfdcbd29e72eff7 100644 (file)
@@ -1296,6 +1296,8 @@ bool
 ClassModem::waitForRings(u_short rings, CallType& type, CallerID& cid)
 {
     bool gotring = false;
+    u_int i = 0, count = 0;
+    int incadence[5] = { 0, 0, 0, 0, 0 };
     time_t timeout = conf.ringTimeout/1000;    // 6 second/ring
     time_t start = Sys::now();
     do {
@@ -1307,6 +1309,16 @@ ClassModem::waitForRings(u_short rings, CallType& type, CallerID& cid)
                type = CALLTYPE_FAX;
            else if (streq(conf.ringVoice, rbuf))
                type = CALLTYPE_VOICE;
+           else if (conf.dringOff.length() && strneq(conf.dringOff, rbuf, conf.dringOff.length())) {
+               if (count++ == 0) break;         //discard initial DROFF code if present
+               incadence[i++] = -atoi(rbuf + conf.dringOff.length());
+               break;
+           }
+           else if (conf.dringOn.length() && strneq(conf.dringOn, rbuf, conf.dringOn.length())) {
+               ++count;
+               incadence[i++] = atoi(rbuf + conf.dringOn.length());
+               break;
+           }
            else {
                if (conf.ringExtended.length() && strneq(rbuf, conf.ringExtended, conf.ringExtended.length()))  // extended RING
                    gotring = true;
@@ -1356,6 +1368,10 @@ ClassModem::waitForRings(u_short rings, CallType& type, CallerID& cid)
                        return (false);
                }
            }
+           if (conf.dringOn.length()) {              // Compare with all distinctive ring cadences
+               modemTrace("WFR: received cadence = %d, %d, %d, %d, %d", incadence[0], incadence[1], incadence[2], incadence[3], incadence[4]);
+               type = findCallType(incadence);
+           }
            gotring = true;
            break;
        case AT_NOANSWER:
@@ -1367,3 +1383,22 @@ ClassModem::waitForRings(u_short rings, CallType& type, CallerID& cid)
     } while (!gotring && Sys::now()-start < timeout);
     return (gotring);
 }
+
+CallType ClassModem::findCallType(int vec[])
+{
+// compare x^2 than x to avoid use of math functions
+
+    double limit = 0.33*0.33;
+    double dif, sum;
+    u_int n, k;
+    for (n=0; n < conf.NoDRings; ++n) {
+       for (k=0, sum=0; k < 5; ++k) {
+            dif = vec[k] - conf.distinctiveRings[n].cadence[k];
+            sum += dif*dif;
+        }
+       if (sum/conf.distinctiveRings[n].magsqrd < limit)  
+           return conf.distinctiveRings[n].type;
+    }
+    return CALLTYPE_UNKNOWN;
+}
+
index 1f46d54669c0a4633fcc921469ed577ca02a6275..86b54001d618b7994d2de8a07f990c64ae1c9509 100644 (file)
@@ -227,6 +227,7 @@ protected:
     void       modemCapability(const char* fmt, ...);
     void       traceBits(u_int bits, const char* bitNames[]);
     void       traceBitMask(u_int bits, const char* bitNames[]);
+    CallType   findCallType(int vec[]);
 public:
     virtual ~ClassModem();
 
index 0e4016d8e17e02bbc7e538460e735d1519f721a0..7002a238a6cf78ebbf20abd0d99f64af5a4ccfcf 100644 (file)
@@ -76,9 +76,12 @@ sigHUP(int)
  * exec getty below.
  */
 void
-Getty::setupArgv(const char* args)
+Getty::setupArgv(const char* args, const fxStr& name, const fxStr& number)
 {
     argbuf = args;
+    nambuf = name;
+    numbuf = number;
+    bool insertName = false, insertNumber = false;
     u_int l;
     /*
      * Substitute escape sequences.
@@ -98,6 +101,14 @@ Getty::setupArgv(const char* args)
            argbuf.insert(speed, l);
            l += speed.length();        // avoid loops
            break;
+        case 'a':
+            argbuf.remove(l-1,3);
+            insertName = true;
+            break;
+        case 'u':
+            argbuf.remove(l-1,3);
+            insertNumber = true;
+            break;            
        case '%':                       // %% = %
            argbuf.remove(l,1);
            break;
@@ -118,6 +129,10 @@ Getty::setupArgv(const char* args)
            argv[nargs++] = &argbuf[token];
        }
     }
+    if (nargs < GETTY_MAXARGS-1 && insertName && nambuf.length()) 
+        argv[nargs++] = &nambuf[0];
+    if (nargs < GETTY_MAXARGS-1 && insertNumber && numbuf.length()) 
+        argv[nargs++] = &numbuf[0];
     argv[nargs] = NULL;
 }
 
@@ -217,7 +232,7 @@ void
 Getty::hangup()
 {
     // NB: this is executed in the parent
-    fxStr device = fxStr::format("%s/" | line, _PATH_DEV);
+    fxStr device = fxStr::format("%s" | line, _PATH_DEV);
     Sys::chown(device, UUCPLock::getUUCPUid(), UUCPLock::getUUCPGid());
     Sys::chmod(device, 0600);                  // reset protection
 }
index 17bfe2758ac3854491235b6059b5d7e39b10a06e..25092e3296fef01bcba9f7da57139fcf46b87739 100644 (file)
@@ -44,6 +44,8 @@ private:
     fxStr      argbuf;                 // stash for argv strings
     fxStr      tzVar;                  // TZ environment variable
     fxStr      langVar;                // LANG environment variable
+    fxStr       nambuf;
+    fxStr       numbuf;
 protected:
     Getty(const char* program, const fxStr& line, const fxStr& speed);
 
@@ -52,8 +54,8 @@ protected:
     virtual void fatal(const char* fmt ...);
 public:
     virtual ~Getty();
-
-    void setupArgv(const char* args);  // setup arguments for getty process
+                                       // setup arguments for getty process
+    void setupArgv(const char* args, const fxStr&, const fxStr&);
                                        // run getty process
     virtual void run(int fd, bool parentIsInit);
     virtual bool wait(int& status, bool block = false);
index 45b4118ec3f959504860e5df9a875cb3eecaee5b..41d1f89dabdbc5ed92e0b4cfe9fd6f502222f1c7 100644 (file)
@@ -166,6 +166,8 @@ static struct {
 { "ringextended",              &ModemConfig::ringExtended },
 { "cidname",                   &ModemConfig::cidName },
 { "cidnumber",                 &ModemConfig::cidNumber },
+{ "dringon",                   &ModemConfig::dringOn  },
+{ "dringoff",                  &ModemConfig::dringOff  },
 };
 static struct {
     const char*                 name;
@@ -226,6 +228,13 @@ ModemConfig::setupConfig()
     for (i = N(numbers)-1; i >= 0; i--)
        (*this).*numbers[i].p = numbers[i].def;
 
+    for (i=0; i < 5; ++i) {
+       distinctiveRings[i].type = ClassModem::CALLTYPE_UNKNOWN;
+       for (u_int j=0; j < 5; ++j)
+           distinctiveRings[i].cadence[j] = 0;
+       distinctiveRings[i].magsqrd = 0;
+    }
+
     flowControl         = ClassModem::FLOW_XONXOFF;// software flow control
     maxRate            = ClassModem::BR19200;  // reasonable for most modems
     minSpeed           = BR_2400;              // minimum transmit speed
@@ -247,6 +256,7 @@ ModemConfig::setupConfig()
     rtnHandling         = FaxModem::RTN_RETRANSMIT; // retransmit until MCF/MPS
     saveUnconfirmedPages = true;               // keep unconfirmed pages
     softRTFCC          = true;                 // real-time fax comp. conv. (software)
+    noAnswerVoice      = false;                // answer voice calls
 }
 
 void
@@ -556,6 +566,53 @@ ModemConfig::parseCID(const char* rbuf, CallerID& cid) const
        cid.number = cid.number | rbuf+cidNumber.length();
 }
 
+void
+ModemConfig::parseDR(const char* cin)
+{
+    if (strlen(cin) < 3) return;
+    char buf[2048];
+    strncpy(buf, cin, sizeof (buf));
+    char* cp = buf;
+    u_int i = 0;
+    char* cp1 = cp;
+    while (*cp++) {
+       if (*cp == ',') {
+               *cp = '\0';                     // Nuke the ','
+               processDRString(cp1, i++);
+               cp1 = ++cp;
+       }
+    }
+    processDRString(cp1, i);
+    NoDRings = i + 1;
+}
+
+void
+ModemConfig::processDRString(char* cp, const u_int i)
+{
+    if (*cp == 'V')
+       distinctiveRings[i].type = ClassModem::CALLTYPE_VOICE;
+    else if (*cp == 'F')
+       distinctiveRings[i].type = ClassModem::CALLTYPE_FAX;
+    else if (*cp == 'D')
+       distinctiveRings[i].type = ClassModem::CALLTYPE_DATA;
+
+    u_int j = 0;
+    char *cp1 = cp += 2;
+    while (*cp++) {
+       if (*cp == ':') {
+           *cp = '\0';                             // Nuke the ':'
+           distinctiveRings[i].cadence[j++] = atoi(cp1);
+           cp1 = ++cp;
+       }
+    }
+    distinctiveRings[i].cadence[j] =  atoi(cp1);
+
+    double sum = 0;
+    for ( u_int k=0; k < 5; ++k ) 
+        sum += distinctiveRings[i].cadence[k]*distinctiveRings[i].cadence[k];
+    distinctiveRings[i].magsqrd = sum;
+}
+
 bool
 ModemConfig::setConfigItem(const char* tag, const char* value)
 {
@@ -609,10 +666,14 @@ ModemConfig::setConfigItem(const char* tag, const char* value)
        class2UseLineCount = getBoolean(value);
     else if (streq(tag, "class2rtfcc"))
        class2RTFCC = getBoolean(value);
+    else if (streq(tag, "noanswervoice"))
+       noAnswerVoice = getBoolean(value);
     else if (streq(tag, "modemsoftrtfcc"))
        softRTFCC = getBoolean(value);
     else if (streq(tag, "saveunconfirmedpages"))
        saveUnconfirmedPages = getBoolean(value);
+    else if (streq(tag, "distinctiverings"))
+       parseDR(value);
     else
        return (false);
     return (true);
index f89a16d759ab14fb2005bf74b1e9680d896ab429..7553e9e2c6bdcb5fab5c66619c17480201ef6fb6 100644 (file)
@@ -102,12 +102,14 @@ public:
     fxStr      ringFax;                // fax call ring string
     fxStr      ringVoice;              // voice call ring string
     fxStr      ringExtended;           // extended ring
+    fxStr      dringOn;                // pattern for distinctive ring silence interval
+    fxStr      dringOff;               // pattern for distinctive ring ring interval
+    bool       noAnswerVoice;          // leave voice calls unanswered
                                        // caller id
     fxStr      cidName;                // pattern for name info
     fxStr      cidNumber;              // pattern for number info
     u_int      cidNameAnswerLength;    // answer when CID received
     u_int      cidNumberAnswerLength;  // answer when CID received
-
                                        // protocol timers
     u_int      t1Timer;                // T.30 T1 timer (ms)
     u_int      t2Timer;                // T.30 T2 timer (ms)
@@ -209,9 +211,19 @@ public:
     RTNHandling rtnHandling;            // RTN signal handling method
     bool       saveUnconfirmedPages;   // don't delete unconfirmed pages
     
+                                       // Distinctive ring data as sequences of DRON/DROF intervals
+    struct {
+       int cadence[5];                 // the ring cadence as a five-dimensional vector
+       double magsqrd;                 // magnitude of the vector squared
+       u_int type;                     // call type of voice, fax, or data
+    } distinctiveRings[5];              // up to 5 distinctive ring numbers
+    u_int NoDRings;                     // number of distinctive rings numbers found
+
         virtual ~ModemConfig();
 
     void parseCID(const char*, CallerID&) const;
     const fxStr& getFlowCmd(FlowControl) const;
+    void parseDR(const char*);
+    void processDRString(char*, const u_int);
 };
 #endif /* _ModemConfig_ */
index 1c2462434a274c7bc7b755dcce5d98913f680c67..1bac1ae26bdef9e7982532b6f447e8b59d362497 100644 (file)
@@ -236,6 +236,9 @@ faxGettyApp::listenForRing()
                    (const char*) received_cid.number, (const char*) received_cid.name);
            }
            ++ringsHeard;
+            if (dringOn.length() && ctype == ClassModem::CALLTYPE_UNKNOWN) --ringsHeard;
+             // distinctive ring failed to identify - get another ring
+            
            /* DID modems may only signal a call with DID data - no RING */
            if (ringsBeforeAnswer && (ringsHeard >= ringsBeforeAnswer) ||
                (cid.name.length() >= cidNameAnswerLength &&
@@ -393,7 +396,8 @@ faxGettyApp::answerPhone(AnswerType atype, CallType ctype, const CallerID& cid,
                advanceRotary = false;
            } else {
                // NB: answer based on ctype, not atype
-               ctype = modemAnswerCall(ctype, emsg, dialnumber);
+               if (!(noAnswerVoice && ctype == ClassModem::CALLTYPE_VOICE)) 
+                   ctype = modemAnswerCall(ctype, emsg, dialnumber);
                callResolved = processCall(ctype, emsg, cid);
            }
        } else if (atype == ClassModem::ANSTYPE_ANY) {
@@ -501,7 +505,7 @@ faxGettyApp::answerCall(AnswerType atype, CallType& ctype, fxStr& emsg, const Ca
             * then we take action based on the returned call type.
             */
            ctype = runGetty("EXTERN GETTY", OSnewEGetty,
-               egettyArgs, emsg, lockExternCalls, true);
+               egettyArgs, emsg, lockExternCalls, cid, true);
            if (ctype == ClassModem::CALLTYPE_DONE)     // NB: call completed
                return (true);
            if (ctype != ClassModem::CALLTYPE_ERROR)
@@ -554,7 +558,7 @@ faxGettyApp::processCall(CallType ctype, fxStr& emsg, const CallerID& cid)
        traceServer("ANSWER: DATA CONNECTION");
        if (gettyArgs != "") {
            sendModemStatus("d");
-           runGetty("GETTY", OSnewGetty, gettyArgs, emsg, lockDataCalls);
+           runGetty("GETTY", OSnewGetty, gettyArgs, emsg, lockDataCalls, cid);
            sendModemStatus("e");
        } else
            traceServer("ANSWER: Data connections are not permitted");
@@ -564,7 +568,7 @@ faxGettyApp::processCall(CallType ctype, fxStr& emsg, const CallerID& cid)
        traceServer("ANSWER: VOICE CONNECTION");
        if (vgettyArgs != "") {
            sendModemStatus("v");
-           runGetty("VGETTY", OSnewVGetty, vgettyArgs, emsg, lockVoiceCalls);
+           runGetty("VGETTY", OSnewVGetty, vgettyArgs, emsg, lockVoiceCalls, cid);
            sendModemStatus("w");
        } else
            traceServer("ANSWER: Voice connections are not permitted");
@@ -589,6 +593,7 @@ faxGettyApp::runGetty(
     const char* args,
     fxStr& emsg,
     bool keepLock,
+    const CallerID& cid,
     bool keepModem
 )
 {
@@ -601,7 +606,7 @@ faxGettyApp::runGetty(
        emsg = fxStr::format("%s: could not create", what);
        return (ClassModem::CALLTYPE_ERROR);
     }
-    getty->setupArgv(args);
+    getty->setupArgv(args, cid.name, cid.number);
     /*
      * The getty process should not inherit the lock file.
      * Remove it here before the fork so that our state is
index 03e7841b3f30891a27d3bc6af99c933584473a5e..57eed74f44079e56eae916b1e4587ae38ffebbc8 100644 (file)
@@ -114,7 +114,8 @@ private:
     CallType   runGetty(const char* what,
                    Getty* (*newgetty)(const fxStr&, const fxStr&),
                    const char* args, fxStr &emsg,
-                   bool keepLock, bool keepModem = false);
+                    bool keepLock, const CallerID& cid, 
+                   bool keepModem = false);
     void       setRingsBeforeAnswer(int rings);
     void       listenBegin();
     void       listenForRing();
index bd44bded98269e0a3ff4bd46b9ba4e80574ff65c..0b9d4c350c788abbcd4e83812e45956f5f830c32 100644 (file)
@@ -131,6 +131,9 @@ CountryCode\(S2     string  \-      local country code
 DestControls\(S1       string  \-      per-destination controls file
 DeviceMode     octal   \s-10600\s+1    protection mode to use for modem device
 DialStringRules\(S2    string  \-      dial string rules file
+DistinctiveRings       string  \-      configuration for distinctive ring cadences
+DRingOff       string  \-      distinctive ring ``off'' cadence indicator
+DRingOn        string  \-      distinctive ring ``on'' cadence indicator
 DynamicConfig  string  \-      script for dynamic receive configuration
 FAXNumber      string  \-      facsimile modem phone number
 FaxRcvdCmd     string  \s-1bin/faxrcvd\s+1     notification script for received facsimile
@@ -162,6 +165,7 @@ ModemPriority       integer 255     scheduling priority for outbound jobs
 ModemReadyState        string  \s-1R\s+1       ``ready state'' sent by \fIfaxgetty\fP
 ModemRingResponse      string  \-      command to respond after hearing RING
 ModemRingsBeforeResponse       integer \s-10\s+1       the number of rings before ModemRingResponse
+NoAnswerVoice  boolean \s-1false\s+1   disable the answering of voice-indicated calls
 NoCarrierRetrys        integer \s-11\s+1       number of times to retry dialing on ``\s-2NO CARRIER\s0''
 NotifyCmd\(S2  string  \s-1bin/notify\s+1      user notification command script
 PageChop\(S1   string  \s-1last\s+1    control automatic truncation of trailing whitespace
@@ -670,6 +674,65 @@ The specified pathname must be relative to the top of the fax server's
 spooling area; e.g.
 .BR etc/dialrules .
 .TP
+.B DistinctiveRings
+Modern distinctive ring support on most modems indicates the ring cadence
+rather than the older style of ``RING1'', ``RING2'', etc.  To indicate
+the ring cadence, 
+.B DRingOn
+and
+.B DRingOff
+values are presented by the modem to the
+.I faxgetty
+process.  The modem indicates the entire cadence between ``RING'' indications
+Like this:
+.nf
+
+  RING
+  DROF=40
+  DRON=8
+  DROF=4
+  DRON=8
+  RING
+
+.fi
+The corresponding
+.B DistinctiveRings
+parameter for this ring cadence would be:
+.nf
+
+  DistinctiveRings:  F-8-4-8
+
+.fi
+where ``F'' tells the
+.I faxgetty
+process that the ring cadence is for a facsimile (``V'' for voice 
+and ``D'' for data), and the other values describe the ring cadence
+with leading and ending
+.B DRingOff
+values ignored.  Multiple ring cadences are indicated by delimiting
+them with commas in this fashion:
+.nf
+
+  DistinctiveRings:  V-20,F-8-4-8,D-4-2-4-8
+
+.fi
+.TP
+.B DRingOff
+A string that identifies the ``off'' value in any distinctive
+ring cadence, for example ``\s-1DROF=\s+1''.
+See also
+.B DRingOn
+and
+.BR DistinctiveRings .
+.TP
+.B DRingOn
+A string that identifies the ``on'' value in any distinctive
+ring cadence, for example ``\s-1DRON=\s+1''.
+See also
+.B DRingOff
+and
+.BR DistinctiveRings .
+.TP
 .B DynamicConfig
 The pathname of the optional shell script, e.g. ``etc/localid'', that 
 makes dynamic configuration changes, i.e., to 
@@ -944,6 +1007,12 @@ have the same
 value, then the \*(Fx scheduler assigns jobs to them in 
 a round-robin fashion.
 .TP
+.B NoAnswerVoice
+Whether or not to disable the answering of calls indicated as
+voice (e.g. by
+.B DistinctiveRings
+).
+.TP
 .B NoCarrierRetrys
 The number of times to redial a phone number after receiving
 a ``\s-1NO CARRIER\s+1'' result code when the number has not