#ident "$Id: faxlib.c,v 4.80 2014/02/02 13:44:19 gert Exp $ Copyright (c) Gert Doering"

/* faxlib.c
 *
 * Module containing generic faxmodem functions (as: send a command, wait
 * for modem responses, parse modem responses)
 *
 * Only class 2 and class 2.0 stuff is here. Class 1 stuff is so
 * different that it goes to a separate library.
 *
 * $Log: faxlib.c,v $
 * Revision 4.80  2014/02/02 13:44:19  gert
 * warning cleanup:
 *   convert all "char" expressions to (uch) when calling ctype.h macros (*sigh*)
 *   fix signed/unsigned char warning in function calls
 *
 * Revision 4.79  2014/02/02 12:05:46  gert
 * Michal Sekletar:
 *   avoid potential buffer overrun (off-by-one) when copying a maximum-length
 *   NSF (theoretical possiblity as NSF frame length is bounded, but code is
 *   still not correct)
 *
 * Revision 4.78  2014/01/31 10:11:03  gert
 * add "NO ANSWER" to list of final dial-failed codes
 *
 * Revision 4.77  2014/01/28 20:43:00  gert
 * permit higher max speeds than 14400 by hex-encoding second
 *  parameter to AT+FDCC (33600='D')
 *
 * Revision 4.76  2009/06/12 15:54:02  gert
 * add ATI code for Cisco MICA modems
 *
 * Revision 4.75  2007/10/05 12:53:37  gert
 * On Blatzheim modems, auto-enable modem quirk MQ_NO_XON
 *   (firmware doesn't send Xon character at start of page)
 *
 * Revision 4.74  2007/02/06 17:04:13  gert
 * fix modem ID stuff for Blatzheim
 *
 * Revision 4.73  2007/02/06 16:26:07  gert
 * grab FW version number for Blatzheim modems
 *
 * Revision 4.72  2006/11/03 13:08:50  gert
 * add CVS tag
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

#include "mgetty.h"
#include "policy.h"
#include "fax_lib.h"

Modem_type modem_type = Mt_unknown;	/* uninitialized */

char	fax_remote_id[40];		/* remote FAX id +FTSI */
char	fax_param[1000];		/* transm. parameters +FDCS */
fax_param_t	fax_par_d;		/* fax params detailed */
char	fax_hangup = 0;
int	fax_hangup_code = FHUP_UNKNOWN;	/* hangup cause +FHNG:<xxx> */
int	fax_page_tx_status = 0;		/* +FPTS:<ppm> */
boolean	fax_to_poll = FALSE;		/* there's something to poll */
boolean fax_poll_req = FALSE;		/* poll requested */

boolean	fhs_details = FALSE;		/* +FHS:xxx with detail info */
int	fhs_lc, fhs_blc, fhs_cblc, fhs_lbc;

int	modem_quirks = 0;		/* modem specials */


/* wait for a given modem response string,
 * handle all the various class 2 / class 2.0 status responses
 */

/* copy fax station id, removing quote characters (dangerous for shell!)
 * and leading/trailing whitespace
 */
static void fwf_copy_remote_id _P1( (id), char * id )
{
int w = 0;

    while ( isspace( (uch) *id) || *id == '"' ) id++;	/* skip leading whitespace */

    while ( *id && w < sizeof(fax_remote_id)-1 )
    {
        if ( *id != '"' && *id != '\'' ) fax_remote_id[w++] = *id;
        id++;
    }

    /* remove trailing whitespace */
    while ( w>0 && isspace( (uch)fax_remote_id[w-1]) ) w--;

    fax_remote_id[w]=0;
}

static boolean fwf_timeout = FALSE;

static RETSIGTYPE fwf_sig_alarm(SIG_HDLR_ARGS)      	/* SIGALRM handler */
{
    signal( SIGALRM, fwf_sig_alarm );
    lprintf( L_WARN, "Warning: got alarm signal!" );
    fwf_timeout = TRUE;
}

int fax_wait_for _P2( (s, fd),
		      char * s, int fd )
{
char * line;
int  ix;

    lprintf( L_MESG, "fax_wait_for(%s)", s );

    if ( fax_hangup )
    {
	lputs( L_MESG, ": already hangup!" );
	return ERROR;
    }

    fwf_timeout = FALSE;
    signal( SIGALRM, fwf_sig_alarm );

    alarm( FAX_RESPONSE_TIMEOUT );

    do
    {
	line = mdm_get_line( fd );

	if ( line == NULL )
	{
	    alarm( 0 ); signal( SIGALRM, SIG_DFL );
	    if ( fwf_timeout ) fax_hangup_code = FHUP_TIMEOUT;
	    return ERROR;
	}
	
	lprintf( L_NOISE, "fax_wait_for: string '%s'", line );

	/* find ":" character (or end-of-string) */
	for ( ix=0; line[ix] != 0; ix++ )
	    if ( line[ix] == ':' ) { ix++; break; }
	if ( line[ix] == ' ' ) ix++;

	if ( strncmp( line, "+FTSI:", 6 ) == 0 ||
	     strncmp( line, "+FCSI:", 6 ) == 0 ||
	     strncmp( line, "+FCIG:", 6 ) == 0 ||
	     strncmp( line, "+FTI:", 5 ) == 0 ||
	     strncmp( line, "+FCI:", 5 ) == 0 ||
	     strncmp( line, "+FPI:", 5 ) == 0 )
	{
	    lprintf( L_MESG, "fax_id: '%s'", line );
	    fwf_copy_remote_id( &line[ix] );
	}

	else if ( strncmp( line, "+FNSF:", 6 ) == 0 || 	/* class 2 */
		  strncmp( line, "+FNF:", 5 ) == 0 ||	/* class 2.0 */
		  strncmp( line, "FNF:", 4 ) == 0 )	/* USR bug */
	{
	    fax2_incoming_nsf( &line[ix] );
	}

	else if ( strncmp( line, "+FDCS:", 6 ) == 0 ||
		  strncmp( line, "+FCS:", 5 ) == 0 )
	{
	    lprintf( L_MESG, "transmission par.: '%s'", line );
	    strcpy( fax_param, line );
	    if ( sscanf( &fax_param[ix], "%hd,%hx,%hd,%hd,%hd,%hd,%hd,%hd",
			 &fax_par_d.vr, &fax_par_d.br, &fax_par_d.wd,
			 &fax_par_d.ln, &fax_par_d.df, &fax_par_d.ec,
			 &fax_par_d.bf, &fax_par_d.st ) != 8 )
	    {
		lprintf( L_WARN, "cannot evaluate +FCS-Code!" );
		fax_par_d.vr = 0;
	    }
	}

	else if ( strncmp( line, "+FDIS:", 6 ) == 0 ||
	          strncmp( line, "+FIS:", 5 ) == 0 )
	{
	    short vr, br, wd, ln, df, ec, bf, st, jp=0;
	    char msg[200];

	    if ( sscanf( &line[ix], "%hd,%hx,%hd,%hd,%hd,%hd,%hd,%hd,%hd",
			 &vr, &br, &wd, &ln, &df, &ec, &bf, &st, &jp ) < 8 )
	    {
		lprintf( L_WARN, "cannot evaluate +FIS-Code!" );
	    }

	    /* prettyprint "non-basic" receiver capabilities */
            msg[0] = '\0';
	    if ( vr & 1 ) strcat( msg, " fine" );
	    if ( vr & 2 ) strcat( msg, " sfine/R8" );
	    if ( vr & 4 ) strcat( msg, " sfine/R16" );
	    if ( vr & 0x20 ) strcat( msg, " 400dpi" );
	    if ( vr & 0x40 ) strcat( msg, " 300dpi" );

	    if ( br > 3 ) strcat( msg, br <= 5? " 144": " V34" );
	    if ( ln < 2 ) strcat( msg, ln == 0? " A4": " B4" );
	    if ( df > 0 ) strcat( msg, df == 1? " 2D/MR":
					 df == 2? " 2D/uncomp": " 2D/MMR" );
	    if ( ec > 0 ) strcat( msg, " ECM" );
	    if ( bf > 0 ) strcat( msg, " BFT" );
	    if ( jp > 0 ) strcat( msg, " JPEG" );

	    lprintf( L_MESG, "receiver cap.: '%s' ->%s", line, msg );
	}

	else if ( strncmp( line, "+FHNG:", 6 ) == 0 ||
		  strncmp( line, "+FHS:", 5 ) == 0 )
	{
	    /* hangup. First, set flag to indicate +FHNG: was read.
	     * The SIGHUP signal catcher will check this, and not exit.
	     * Next, reset the action for SIGHUP, to be ignore, so we
	     * (and child processes) are not interrupted while we cleanup.
	     * If the wait_for string is not "OK", return immediately,
	     * since that is all that the modem can send after +FHNG
	     */
	    fax_hangup = 1; /* set this as soon as possible */
	    /* change action for SIGHUP signals to be ignore */
#ifdef SIG_ERR
	    if ( signal( SIGHUP, SIG_IGN ) == SIG_ERR )
	    {
		lprintf( L_WARN, "fax_wait_for: cannot reset SIGHUP handler." );
	    }
#else
	    signal( SIGHUP, SIG_IGN );
#endif
	    lprintf( L_MESG, "connection hangup: '%s'", line );
	    sscanf( &line[ix], "%d", &fax_hangup_code );

	    lprintf( L_NOISE,"(%s)", fax_strerror( fax_hangup_code ));

	    if ( strcmp( s, "OK" ) != 0 ) break;
	}

	else if ( strncmp( line, "+FPTS:", 6 ) == 0 ||
		  strncmp( line, "+FPS:", 5 ) == 0 )
	{
	    /* page transmit status
	     * store into global variable (read by sendfax.c)
	     */
	    lprintf( L_MESG, "page status: %s", line );
	    sscanf( &line[ix], "%d", &fax_page_tx_status );

	    /* evaluate line count, bad line count, ... for reception */
	    fhs_lc = 9999; fhs_blc = fhs_cblc = fhs_lbc = 0;
	    fhs_details = FALSE;

	    if ( line[ix+1] == ',' &&		/* +FPS:s,lc,blc */
		 sscanf( &line[ix+2],
			 ( modem_type == Mt_class2 || 
			  (modem_quirks & MQ_FPS_NOT_HEX) )
			                          ?"%d,%d,%d,%d"
						  :"%x,%x,%x,%x",
		         &fhs_lc, &fhs_blc, &fhs_cblc, &fhs_lbc ) >= 2 )
	    {
		lprintf( L_NOISE, "%d lines received, %d lines bad, %d bytes lost", fhs_lc, fhs_blc, fhs_lbc );
		fhs_details = TRUE;
	    }
	}

	else if ( strcmp( line, "+FPOLL" ) == 0 ||
		  strcmp( line, "+FPO" ) == 0 )
	{
	    /* the other side is telling us that it has a document that
	     * we can poll (with AT+FDR)
	     */
	    lprintf( L_MESG, "got +FPO -> will do polling" );
	    fax_to_poll = TRUE;
	}

	else if ( strncmp( line, "+FDTC:", 6 ) == 0 ||
		  strncmp( line, "+FTC:", 5 ) == 0 )
	{
	    /* we sent a +FLPL=1, and the other side wants to poll
	     * that document now (send it with AT+FDT)
	     */
	    lprintf( L_MESG, "got +FTC -> will send polled document" );
	    fax_poll_req = TRUE;
	    
	    /* we're waiting for a CONNECT here, in response to a
	     * AT+FDR command, but only an OK will come. So, change
	     * expect string to "OK"
	     */
	    lprintf( L_MESG, "fax_wait_for('OK')" );
	    s = "OK";
	}
	
	else
	if ( strcmp( line, "ERROR" ) == 0 ||
	     strcmp( line, "NO CARRIER" ) == 0 ||
	     strcmp( line, "BUSY" ) == 0 ||
	     strcmp( line, "NO ANSWER" ) == 0 ||
	     strcmp( line, "NO DIALTONE" ) == 0 ||
	     strcmp( line, "NO DIAL TONE" ) == 0 )
	{
#if 0		/* not needed right now (fax_send_ppm), problem with USR!! */
	    if ( modem_type == Mt_class2_0 )		/* valid response */
	    {						/* in class 2.0! */
		if ( strcmp( line, "ERROR" ) == 0 )
		{
		    lprintf( L_MESG, "ERROR response" );
		    alarm(0);
		    return NOERROR;			/* !C2 */
		}
	    }
#endif

	    /* in class 2, one of the above codes means total failure */
	    
	    lprintf( L_MESG, "ABORTING: line='%s'", line );
	    fax_hangup = 1;
	    
	    if ( strcmp(line, "BUSY") == 0 ) fax_hangup_code = FHUP_BUSY;
	    else if (strcmp(line, "NO DIALTONE") == 0 ||
		     strcmp(line, "NO DIAL TONE") == 0)
	                                     fax_hangup_code = FHUP_NODIAL;
	    else                             fax_hangup_code = FHUP_ERROR;
	    
	    alarm( 0 ); signal( SIGALRM, SIG_DFL );
	    return ERROR;
	}

    }
    while ( strncmp( s, line, strlen(s) ) != 0 );
    lputs( L_MESG, "** found **" );

    alarm( 0 );
    signal( SIGALRM, SIG_DFL );

    if ( fax_hangup && fax_hangup_code != 0 ) return ERROR;

    return NOERROR;
}

/* (re-) initialize all global/static variables set by faxlib.c
 * necessary if fax state machine runs multiple times, e.g. in vgetty
 */
void faxlib_init _P0( void )
{
    fax_hangup = 0;
    fax_hangup_code = FHUP_UNKNOWN;
    fax_page_tx_status = 0;
    fax_to_poll = fax_poll_req = FALSE;
    fhs_details = FALSE;
    fax_remote_id[0] = '\0';
}

/* send a command string to the modem, terminated with the
 * MODEM_CMD_SUFFIX character / string from policy.h
 */

int fax_send _P2( (send, fd),
		  char * send, int fd )
{
#ifndef CLASS1
# ifdef FAX_COMMAND_DELAY
    delay(FAX_COMMAND_DELAY);
# endif
#endif

    lprintf( L_MESG, "fax_send: '%s'", send );

    if ( write( fd, send, strlen( send ) ) != strlen( send ) ||
	 write( fd, MODEM_CMD_SUFFIX, sizeof(MODEM_CMD_SUFFIX)-1 ) !=
	        ( sizeof(MODEM_CMD_SUFFIX)-1 ) )
    {
	lprintf( L_ERROR, "fax_send: cannot write" );
	return ERROR;
    }

    return NOERROR;
}

/* simple send / expect sequence, but pass "expect"ing through
 * fax_wait_for() to handle all the class 2 fax responses
 */

int fax_command _P3( (send, expect, fd),
		     char * send, char * expect, int fd )
{
    if ( fax_send( send, fd ) == ERROR ) return ERROR;
    return fax_wait_for( expect, fd );
}

/* Couple of routines to set this and that fax parameter, using class 2
 * or 2.0 commands, according to the setting of "modem_type"
 */

/* set local fax id */

int fax_set_l_id _P2( (fd, fax_id), int fd, char * fax_id )
{
    char flid[60];

#ifdef CLASS1
    if ( modem_type == Mt_class1 || modem_type == Mt_class1_0 )
		return fax1_set_l_id( fd, fax_id );
#endif

    if ( modem_type == Mt_class2_0 )
        sprintf( flid, "AT+FLI=\"%.40s\"",  fax_id );
    else
        sprintf( flid, "AT+FLID=\"%.40s\"", fax_id );
    
    if ( mdm_command( flid, fd ) == FAIL )
    {
	lprintf( L_MESG, "cannot set local fax id. Huh?" );
	return ERROR;
    }
    return NOERROR;
}

#define nibble_to_hex(x) ( ((x)>9)? (x)-10+'A': (x)+'0')

/* set resolution, minimum and maximum bit rate */
int fax_set_fdcc _P4( (fd, fine, max, min),
		      int fd, int fine, int max, int min )
{
    char buf[50];

#ifdef CLASS1
    if ( modem_type == Mt_class1 || modem_type == Mt_class1_0 )
		return fax1_set_fdcc( fd, fine, max, min );
#endif

    if ( modem_quirks & MQ_USR_FMINSP )
    {
	/* some early versions of the USR fax firmware got this wrong, (put 
	 * "max" speed into register for "min" speed!!) so don't set speed
	 */
	sprintf( buf, "AT+FCC=%d", fine );
    }
    else				/* standard case, working modem */
    {
	sprintf( buf, "AT%s=%d,%c,0,2,0,0,0,0",
		 (modem_type == Mt_class2_0) ? "+FCC" : "+FDCC",
		 fine, nibble_to_hex( (max/2400)-1 ) );
    }
    
    if ( mdm_command( buf, fd ) == ERROR )
    {
	if ( max > 9600 )
	    return fax_set_fdcc( fd, fine, 9600, min );
	else
	    return ERROR;
    }

    if ( min >= 2400 )
    {
	if ( modem_type == Mt_class2_0 )
	    sprintf( buf, "AT+FMS=%d", (min/2400) -1 );
	else
	    sprintf( buf, "AT+FMINSP=%d", (min/2400) -1 );

	if ( mdm_command( buf, fd ) == ERROR )
	{
	    lprintf( L_WARN, "+FMINSP command failed, ignoring" );
	}
    }
    return NOERROR;
}

/* set modem flow control (for fax mode only)
 *
 * right now, this works only for class 2.0 faxing. Class 2 has
 * no idea of a common flow control command.
 * If hw_flow is set, use RTS/CTS, otherwise, use Xon/Xoff.
 */

int fax_set_flowcontrol _P2( (fd, hw_flow), int fd, int hw_flow )
{
    if ( modem_type == Mt_class2_0 )
    {
	if ( hw_flow )
	{
	    if ( mdm_command( "AT+FLO=2", fd ) == NOERROR ) return NOERROR;
	    lprintf( L_WARN, "modem doesn't like +FLO=2; using Xon/Xoff" );
	}
	return mdm_command( "AT+FLO=1", fd );
    }
    return NOERROR;
}


/* byte swap table used for sending (yeah. Because Rockwell screwed
 * up *that* completely in class 2, we have to have different tables
 * for sending and receiving. Bah.)
 */
unsigned char fax_send_swaptable[256];
unsigned char fax_recv_swaptable[256];

/* set up bit swap table */
void fax_init_swaptable _P2( (direct, byte_tab),
			      int direct, unsigned char byte_tab[] )
{
int i;
    if ( direct ) for ( i=0; i<256; i++ ) byte_tab[i] = i;
    else
      for ( i=0; i<256; i++ )
	     byte_tab[i] = ( ((i & 0x01) << 7) | ((i & 0x02) << 5) |
			     ((i & 0x04) << 3) | ((i & 0x08) << 1) |
			     ((i & 0x10) >> 1) | ((i & 0x20) >> 3) |
			     ((i & 0x40) >> 5) | ((i & 0x80) >> 7) );
}

/* set modem bit order, and initialize bit swap table accordingly */

int faxmodem_bit_order = 0;

int fax_set_bor _P2( (fd, bor), int fd, int bor )
{
    char buf[20];

    faxmodem_bit_order = bor;

    fax_init_swaptable( faxmodem_bit_order & 1, fax_send_swaptable );

    /* the whole bit swap table business is very ugly.  It comes from
     * the way the old Rockwell class 2 code got it wrong (for reception
     * only!), and the way mgetty and all tools have been adapted to that.
     * So, class 2.x uses "1:1" (but uses different AT+FBO=x settings,
     * depending on whether the modem will do it right), and class 1(.x) 
     * has to use "reverse" tables for reception.  Bah.
     */
    if ( modem_type == Mt_class1 || modem_type == Mt_class1_0 )
    {
        fax_init_swaptable( FALSE, fax_recv_swaptable );	/* swap bits */
	return NOERROR;
    }

    fax_init_swaptable( TRUE, fax_recv_swaptable );		/* no swap */
    
    if ( modem_type == Mt_class2_0 )
        sprintf( buf, "AT+FBO=%d", bor );
    else
        sprintf( buf, "AT+FBOR=%d", bor );

    return mdm_command( buf, fd );
}

/* parse NSF (non-standard frames) - like "remote vendor ID", etc.
 *
 * class 2/2.0 deliver NSFs like this: "+FNF:86 40 40 FF 06 42 86 40 40",
 * while class 1 just delivers a binary frame.
 * -> convert to binary, pass on to class 1 handler
 *
 * (some modems deliver "+FNF:0000090008...", so we accept about anything
 * that looks like "pairs of hex digits with or without separators")
 */
#define char_to_hex(ch) ( ( (ch)>='0' && (ch)<='9')? (ch)-'0' : \
				tolower( (uch)(ch) )-'a'+10 )
void fax2_incoming_nsf _P1((nsf_hex), char * nsf_hex )
{
#ifdef FAX_NSF_PARSER
uch nsf_bin[200];
int len;
char *p;

    len = 0;
    p = nsf_hex;
    /* some modems insert extra quotes at the start... (ZyXEL) */
    while( *p != '\0' && ! isxdigit( (uch)*p ) ) p++;

    while( len<sizeof(nsf_bin) && 
               isxdigit( (uch)*p ) && isxdigit( (uch)*(p+1)) )
    {
	nsf_bin[len++] = (uch) ( char_to_hex(*p) << 4 ) +
					char_to_hex(*(p+1));
	p+=2;
	while( *p != '\0' && ! isxdigit( (uch)*p ) ) p++;
    }

    fax1_incoming_nsf( nsf_bin, len );
#endif
}

void fax1_incoming_nsf _P2((nsf_bin, len), uch * nsf_bin, int len)
{
#ifdef FAX_NSF_PARSER
char nsf_print[200];
int i;

    /* normal bit order */
    for(i=0;i<len && i<sizeof(nsf_print)-1;i++)
	nsf_print[i] = isprint(nsf_bin[i])? nsf_bin[i]: '.';
    nsf_print[i] = '\0';

    lprintf( L_NOISE, "NSF(%02d): %s", len, nsf_print );

    for(i=0;i<len && i<sizeof(nsf_print);i++)
	nsf_print[i] = isprint(fax_send_swaptable[nsf_bin[i]])? 
				fax_send_swaptable[nsf_bin[i]]: '.';
    lprintf( L_NOISE, "    rev: %s", nsf_print );

    hylafax_nsf_decode( nsf_bin, len );
#endif
}


/* find out the type of modem connected
 *
 * controlled by the "mclass" parameter ("auto", "cls2", "c2.0", "data")
 */

Modem_type fax_get_modem_type _P2( (fd, mclass), int fd, char * mclass )
{
int rc;
char *mc;

    /* data modem? unknown mclass? handle as "auto" (for sendfax) */
    if ( strcmp( mclass, "cls2" ) != 0 &&
	 strcmp( mclass, "c2.0" ) != 0 &&
	 strcmp( mclass, "auto1") != 0 &&
	 strcmp( mclass, "cls1" ) != 0 &&
	 strcmp( mclass, "c1.0" ) != 0 )
    {
	mclass = "auto";
    }

    /* auto-identify via ATI code */
    if ( strcmp( mclass, "auto" ) == 0 )
    {
	if ( mdm_identify( fd ) != NOERROR )  		/* error? */
			    { mclass = "auto1"; }	/* try auto1 */
    }

    /* "auto1" is a variant of auto-identify, using AT+FCLASS=?
     */
    if ( strcmp( mclass, "auto1" ) == 0 )
    {
	mc = mdm_get_idstring( "AT+FCLASS=?", 1, fd );

	lprintf( L_MESG, "available modem classes: %s", mc );

	if      ( strstr( mc, "2.1" ) != NULL ) { modem_type = Mt_class2_1; }
	else if ( strstr( mc, "2.0" ) != NULL ) { modem_type = Mt_class2_0; }
	else if ( strstr( mc, "2" ) != NULL )   { modem_type = Mt_class2; }
    }

    if ( modem_type != Mt_unknown )
    {
	/* set up modem accordingly */
	switch( modem_type )
	{
	    case Mt_class2_0: 
		rc=mdm_command( "AT+FCLASS=2.0", fd ); 
		break;
	    case Mt_class2:
		rc=mdm_command( "AT+FCLASS=2", fd ); 
		break;
	    default:
		rc=NOERROR;
		break;
	}
	if ( rc == NOERROR ) return modem_type;
    }


    /* not auto-identify, or initialization failed -> try "old way" 
     */

    /* first of all, check for 2.0 */
    if ( strcmp( mclass, "auto" ) == 0 ||
	 strcmp( mclass, "c2.0" ) == 0 )
    {
	if ( mdm_command( "AT+FCLASS=2.0", fd ) == SUCCESS )
	{
	    return Mt_class2_0;
	}
    }

#ifdef CLASS1
    /* if explicitely requested, do class 1 (EXPERIMENTAL) */
    if ( strcmp( mclass, "cls1" ) == 0 )
    {
	if ( mdm_command( "AT+FCLASS=1", fd ) == SUCCESS )
	{
	    return Mt_class1;
	}
    }
    if ( strcmp( mclass, "c1.0" ) == 0 )
    {
	if ( mdm_command( "AT+FCLASS=1.0", fd ) == SUCCESS )
	{
	    return Mt_class1_0;
	}
    }
#endif

    /* not a 2.0 modem (or not allowed to check),
       simply *try* class 2, nothing to loose */

    if ( mdm_command( "AT+FCLASS=2", fd ) == SUCCESS )
    {
	return Mt_class2;
    }

    /* failed. Assume data modem */

    return Mt_data;
}			/* end fax_get_modem_type() */


/* identify unknown modem via ATI code
 *
 * *very* preliminary - I'm experimenting...
 */

int mdm_identify _P1( (fd), int fd )
{
    char * l, *p;
    char * mis = NULL;		/* more verbose modem ID string */

    modem_type=Mt_unknown;

    /* try ATI first, ATI<n> later to sub-divide results
     */
    l = mdm_get_idstring( "ATI", 1, fd );
    lprintf( L_NOISE, "mdm_identify: string '%s'", l );
    
    if ( strcmp( l, "<ERROR>" ) == 0 ) 
    {
	lprintf( L_WARN, "mdm_identify: can't get modem ID" );
	return ERROR;
    }

    /* all-numerical? */
    p = l;
    while( isdigit( (uch)*p ) || isspace( (uch)*p ) ) p++;

    if ( *p == '\0' )		/* all-numeric */
    {
	int mid = atoi(l);	/* numerical modem ID... */

	switch(mid)
	{
	  case 0:		/* empty string */
	    lprintf( L_MESG, "got no modem ID. Hagenuk Speed Dragon?" );
	    mis = mdm_get_idstring( "ATI0", 1, fd );
	    break;
	  case 1496:
	    lprintf( L_MESG, "ZyXEL 1496 detected" ); 
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI1", 2, fd );
	    break;
	  case 2864:
	    lprintf( L_MESG, "ZyXEL 2864(D) detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI1", 2, fd );
	    break;
	  case 28641:
	  case 28642:
	    lprintf( L_MESG, "ZyXEL 2864I(D) detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI1", 2, fd );
	    break;
	  case 33604:
	    lprintf( L_MESG, "ZyXEL U-336E detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI1", 2, fd );
	    break;
	  case 1281:
	  case 1291:
	  case 1293:
	    lprintf( L_MESG, "ZyXEL Omni.NET detected" );
	    modem_type=Mt_data;				/* has no fax mode */
	    mis = mdm_get_idstring( "ATI1", 1, fd );
	    break;
	  case 1292:
	    lprintf( L_MESG, "ZyXEL Omni.NET LCD+M detected" );
	    modem_type=Mt_class2;			/* rockwell based */
	    mis = mdm_get_idstring( "ATI1", 1, fd );
	    break;
	  case 1500:
	  case 1501:
	  case 1507:
	    lprintf( L_MESG, "ZyXEL Omni 56K (Plus/Pro) detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI1", 2, fd );
	    break;
	  case 1503:
	    lprintf( L_MESG, "ZyXEL U-90E (?) detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI1", 2, fd );
	    break;
	  case 1444:
	    lprintf( L_MESG, "USR Courier/Sportster v32bis detected (assuming non-fax capable)" );
	    modem_type=Mt_data;
	    break;
	  case 1445:
	    lprintf( L_MESG, "USR Courier/Sportster v32bis detected (buggy fax implementation)" );
	    modem_type=Mt_class2_0;
	    modem_quirks |= MQ_USR_FMINSP | MQ_FPS_NOT_HEX;
	    break;
	  case 2886:
	  case 3361:
	  case 3362:
	  case 3366:
	  case 3367:
	    lprintf( L_MESG, "USR Courier/Sportster V.34(+) detected" );
	    modem_type=Mt_class2_0;
	    modem_quirks |= MQ_FPS_NOT_HEX;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    break;
	  case 5601:
	  case 5607:
	    lprintf( L_MESG, "USR Courier/Sportster 56k detected" );
	    modem_type=Mt_class2_0;
	    modem_quirks |= MQ_FPS_NOT_HEX;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    break;
	  case 6401:
	    lprintf( L_MESG, "USR I-Modem detected" );
	    modem_type=Mt_class2_0;
	    modem_quirks |= MQ_FPS_NOT_HEX;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    break;
	  case 62:	/* sure? */
	  case 962:
	    lprintf( L_MESG, "Dr. Neuhaus Smarty detected (?)" );
	    modem_type=Mt_class2;	/* now do ATI9! */
	    modem_quirks |= MQ_NEED2;
	    mis = mdm_get_idstring( "ATI9", 1, fd );
	    break;
          case 932:	/* Thomas Schuett, info@thomas-schuett.de */
	    lprintf( L_MESG, "Zoom MX/S detected" );
	    modem_type=Mt_class2;
	    modem_quirks |= MQ_NEED2;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    break;	      
	  case 961:
	    lprintf( L_MESG, "Zoltrix 14400 faxmodem detected (?)" );
	    modem_type=Mt_class2;
	    mis = mdm_get_idstring( "ATI4", 1, fd );
	    break;
	  case 144:     /* UMC 1440 baud modem (not sure) */
	    lprintf( L_MESG, "UMC92144EF modem detected(?)" );
	    modem_type=Mt_class2;
	    mis = mdm_get_idstring( "ATI4", 1, fd );
	    modem_quirks |= MQ_NEED2;
	    break;
	  case 184:	/* sure? */
	    lprintf( L_MESG, "Telebit FastBlazer detected" );
	    modem_type=Mt_data;
	    break;
	  case 149: /* sure? */
	    lprintf( L_MESG, "Intel 14.4E/400e detected (?)" );
	    modem_type=Mt_unknown;
	    break;
	  case 247: /* use ATI2 for further distinction */
	    lprintf( L_MESG, "Multitech MT1432BA/MT1932ZDX/MT2834ZDX detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI2", 1, fd );
	    modem_quirks |= MQ_FBOR_OK;
	    break;
	  case 251: /* sure? */
	    lprintf( L_MESG, "Discovery 2400 AM detected" );
	    modem_type=Mt_data;
	    break;
	  case 288: /* Fred Wendorf */
	    lprintf( L_MESG, "Trust Communicator 28 K8 detected" );
	    modem_type=Mt_class2;
	    modem_quirks |= MQ_NEED2;
	    break;
	  case 641: /* sure? */
	    lprintf( L_MESG, "ELSA MicroLink ISDN/TLpro detected" );
	    modem_type=Mt_data;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    break;
	  case 643:	/* ATI6/ATI3 for model/firmware info */
	    lprintf( L_MESG, "ELSA MicroLink ISDN/TLV.34 detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    break;
	  case 645:	/* ATI6/ATI3 for model/firmware info */
	    lprintf( L_MESG, "ELSA MicroLink Internet II detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    break;
	  case 282:	/* ATI6/ATI3 for model/firmware info */
	    lprintf( L_MESG, "ELSA MicroLink 28.8/56K series detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    break;
	  case 140:	/* ATI6/ATI3 for model/firmware info */
	    lprintf( L_MESG, "ELSA MicroLink 14.4TQ series detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    break;
	  case 249:
	  case 14400: /* further distinction necessary (ATI3,4,6)! */
	  case 28800:
	  case 33600:
	  case 56000:
	    lprintf( L_MESG, "Generic Rockwell modem (%d)", mid );
	    modem_type=Mt_class2;
	    modem_quirks |= MQ_NO_LQC;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    if ( mid == 28800 && mis[0] == '\0' )	/* no ATI3 code */
	    {
		lprintf( L_MESG, "Sounds more like Dr.Neuhaus Cybermod" );
		modem_quirks |= MQ_NEED2;
	    }
	    else					/* "Version 6.00" */
	      if ( mid == 28800 && strncmp( mis, "Version", 7 ) == 0 )
	    {
		lprintf( L_MESG, "Could be a Hayes Optima/Accura modem" );
	        mis = mdm_get_idstring( "ATI7", 2, fd );
		modem_quirks |= MQ_NEED2;
		break;
	    }
	    mis = mdm_get_idstring( "ATI4", 1, fd );
	    break;
	  case 2884:
	    lprintf( L_MESG, "sounds like a Microcom DeskPorte Fast+" );
	    modem_type=Mt_class2_0;
	    modem_quirks |= MQ_NO_LQC;		/* +FPS: broken */
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	    break;
	  case 336:				/* one report only */
	    lprintf( L_MESG, "could be a CompuTime RalleyCom 336" );
	    break;
	  case 242:				/* one report only */
	    lprintf( L_MESG, "could be a Tornado III FM-144VBIS" );
	    modem_type=Mt_class2;
	    modem_quirks |= MQ_NO_XON;
	    break;
	  default:
	    lprintf( L_MESG, "unknown numerical modem id %d", mid );
	    break;
	}
    }
    else		/* non-numeric modem id string */
    {
	lprintf( L_MESG, "non-numeric ID string: '%s'", l );

	/* "Elink 310 Version 1.25" */
	/* "Elink 34-3 P1 Version 1.64" */
	if ( strncmp( l, "Elink", 5 ) == 0 )
	{
	    lprintf( L_MESG, "Elink detected" );
	    if ( strncmp( l+6, "34-3" , 4 ) == 0 )
		modem_type=Mt_class2;
	    else
		modem_type=Mt_data;
	}
	else if ( strncmp( l, "Eicon ISDN Modem", 16 ) == 0 )
	{
	    lprintf( L_MESG, "Diehl/Eicon ISDN (assuming DIVA card with class 2 fax)" );
	    modem_type=Mt_class2;
	}
	else if ( strncmp( l, "Linux ISDN", 10 ) == 0 )
	{
	    lprintf( L_MESG, "ISDN4Linux detected" );
	    modem_type=Mt_data;
	}
	/* got this from bruce@hn.pl.net */
	else if ( strncmp( l, "1.03", 4 ) == 0 )
	{
	    lprintf( L_MESG, "Ellcon 14.4+Voice detected" );
	    modem_type=Mt_data;
	}
	/* got this from Matt Atkins, matta@cl-sys.com */
	else if ( strncmp( l, "1.0", 3 ) == 0 )
	{
	    lprintf( L_MESG, "Cirrus Logic Communicator 56 detected" );
	    modem_type=Mt_unknown;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	}
	/* got this from Andreas Muck, <andi@koala.rhein-neckar.de>
	 * and Frank Damgaard <frda@post3.tele.dk>
	 */
	else if ( strncmp( l, "5607A", 5 ) == 0 ||
	          strncmp( l, "5607B", 5 ) == 0 )
	{
	    lprintf( L_MESG, "USR Courier/Sportster V90 (national variant?) detected" );
	    modem_type=Mt_class2_0;
	    modem_quirks |= MQ_FPS_NOT_HEX;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	}
	/* grrr, another one of those - Bill Nugent <whn@topelo.lopi.com> */
	else if ( strncmp( l, "MT5600ZDXV", 10 ) == 0 )
	{
	    lprintf( L_MESG, "Multitech MT5600ZDXV detected" );
	    modem_type=Mt_class2;
	}
	/* and yet another one :-( - Nokia, sigh */
	else if ( strncmp( l, "Nokia ", 6 ) == 0 )
	{
	    lprintf( L_MESG, "Nokia GSM telephone detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	}
	/* and yet another one - will they never end? Xavier Roche */
	else if ( strncmp( l, "LT V.90", 7 ) == 0 )
	{
	    lprintf( L_MESG, "Multitech MT5634Z internal detected" );
	    modem_type=Mt_class2;
	    modem_quirks |= MQ_NEED2;
	}
	else if ( strncmp( l, "LT V.92", 7 ) == 0 )	/* gert */
	{
	    lprintf( L_MESG, "Multitech MT5634ZBA-V92 detected" );
	    modem_type=Mt_class2_0;
	    modem_quirks |= MQ_FPS_NOT_HEX;
	}
	else if ( strncmp( l, "SIEMENS", 7 ) == 0 )	/* gert */
	{
	    lprintf( L_MESG, "possibly Siemens GSM modem?  assuming class 2" );
	    modem_type=Mt_class2;
	}
	else if ( strncmp( l, "BM-33k6/ISDN pro", 16 ) == 0 )	/* gert */
	{
	    lprintf( L_MESG, "Blatzheim ISDN modem, buggy class 2.0, force class 2" );
	    modem_type=Mt_class2;
	    modem_quirks |= MQ_NO_XON;
	    mis = mdm_get_idstring( "ATI3", 1, fd );
	}
	/* MICA modems built into Cisco routers/access servers, accessed
	 * via network.  MICA versions 2.9.1.0-2.9.5.0 are unable to fax.
	 */
	else if ( strncmp( l, "Modem product ID = 242", 22 ) == 0 ) /* gert */
	{
	    lprintf( L_MESG, "Cisco MICA modem detected" );
	    modem_type=Mt_class2_0;
	    mis = mdm_get_idstring( "ATI4", 1, fd );
	}
    }

    if ( mis != NULL ) 
	lprintf( L_MESG, "additional info: '%s'", mis );
    if ( modem_quirks )
	lprintf( L_MESG, "modem quirks: %04x", modem_quirks );

    return NOERROR;
}
