
/****************************************************************************/
/*                                                                          */
/*      NNstat -- Internet Statistics Collection Package                    */
/*                                                                          */
/*            Written by: Bob Braden & Annette DeSchon                      */
/*            USC Information Sciences Institute                            */
/*            Marina del Rey, California                                    */
/*                                                                          */
/*      Copyright (c) 1991 University of Southern California.               */
/*      All rights reserved.                                                */
/*                                                                          */
/*      Redistribution and use in source and binary forms are permitted     */
/*      provided that the above copyright notice and this paragraph are     */
/*      duplicated in all such forms and that any documentation,            */
/*      advertising materials, and other materials related to such          */
/*      distribution and use acknowledge that the software was              */
/*      developed by the University of Southern California, Information     */
/*      Sciences Institute.  The name of the University may not be used     */
/*      to endorse or promote products derived from this software           */
/*      without specific prior written permission.                          */
/*      THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR        */
/*      IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED      */
/*      WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR          */
/*      PURPOSE.                                                            */
/*                                                                          */
/****************************************************************************/

static char rcsid[]=
  "$Header: cmds.c,v 1.7 93/10/11 18:41:16 mogul Exp $";
 
/*                      cmds.c
 *
 *  This file contains the routines to parse user commands.  Used by
 *  statspy to parse local commands, and by rspy to parse commands to be
 *  sent to remote statspy. 
 *
 */
 
/* CHANGES:
 *   29Nov88 ISI: Truncate excessively long names
 *   13Oct89 Merit: Add access control command: 'restrict'
 *   17Oct89 AKS/RTB: Add 'subnet' command
 *     Rel 3.0:
 *   ISI: Parse language extensions: Boolean expressions, symif and select.
 *   ISI: Add file command, for reading data locally into a file.
 *   ISI: Virtual wall clock when testing with input from a file.
 *   ISI: Clean up error msg handling for remote operations
 *   SGI: Add INCLUDE command, to include src text.
 *   ISI: More syntax checking on parms of restrict, subnet commands.
 *   ISI: More feedback on command execution.
 *   Aug93/DECWRL: Alpha/OSF port.  XDR is a pointer type.
 *
 */  
  
#include <stdio.h>
#include <netdb.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <rpc/rpc.h>

#include "stat.h"
#include "scan.h"
#include "sobj.h"
    
extern char *field_name[];
extern boolean Read_SOBJ(), Clear_SOBJ(), Detach_SOBJ();
extern boolean doInvoke(), doIf(), doSymIf(), doNull();
extern boolean Show_Fields(), Show_restrict(), Do_Subnet();
extern int  Xdebug;
extern time_t CurrTime;
extern char *SOBJ_error;
extern char *TraceFile;

#define ERR  -1 
    
char   Field1[MAX_FLDNAME], Field2[MAX_FLDNAME];
char   ObjName[MAX_OBJNAME];
extern char *strtcpy();
extern char rd_filename[]; /* name of file to rcv display from read cmd, or nul */

#define MAX_ENUMS 1024  /* Max size list of enum object specs.  If it
     is exceeded (very unlikely), list displayed by "enum ?" will be
     truncated without comment.          */

    /* 
     *    Command scanner -- scan a single command read from stdin,
     *             and then return.
     * 
     *    fpin = file pointer for command input
     *    xpout = file handle for output
     *      In rspy:    XDR write handle.
     *      In statspy: NULL (output to local console).
     *
     */
Scan_Cmd(fpin, xpout)
    FILE *fpin; 
    XDR  *xpout;
    {
#ifdef	__osf__
    unsigned char *rp;
#else
    char *rp;
#endif
    char Ebuf[MAX_ENUMS];
    int interval, i, LastToken;
    u_int32 token;
    struct restrict_parm Rparm; 
    struct subnet_parm Sparm;
    extern u_long line_number;
    
    yyin = fpin;   /* Set LEX file pointer */
    LastToken = LEXCALL;
    while (1) {
        if (TraceFile == NULL)
             CurrTime = time(0);
                        
        switch (LastToken) {
        
        case EOFTOKEN:
            return(CMD_EOF);
                    
        case ';':
            return(CMD_OK);
            
        case READ:
            /* read [ <title-spec>  | ? ]   
             */
            if ((LastToken = LEXCALL) == '?') 
                ObjName[0] = '\0';  /* read object names (&attributes) */
            else if (LastToken == IDTOKEN) 
                strtcpy(ObjName, yytext, MAX_OBJNAME);
            else break;
            if (!Read_SOBJ(xpout, ObjName, 0)) {
                printf("%s\n", SOBJ_error);
                return(CMD_ERR);
            }         
            return(CMD_OK);                     
                        
        case CLEAR:
            /* clear [ <title-spec> ] 
             */
            if ((LastToken = LEXCALL) == IDTOKEN) 
                strtcpy(ObjName, yytext, MAX_OBJNAME);
            else break;
            if (!Clear_SOBJ(xpout, ObjName)) {
                printf("%s\n", SOBJ_error);
                return(CMD_ERR);
            }   
            return(CMD_OK);
                        
        case READCL:
            /* readclear [ <title-spec>  ]   
             */
            if ((LastToken = LEXCALL) == IDTOKEN) {
                strtcpy(ObjName, yytext, MAX_OBJNAME);
            }
            else break;
            if (!Read_SOBJ(xpout, ObjName, 1))  {
                printf("%s\n", SOBJ_error);
                return(CMD_ERR);
            }   
            return(CMD_OK);             
            
        case SHOW:
            /*  show [ <field-spec>  | ? ]
             */
            if ((LastToken = LEXCALL) == '?')  
                ObjName[0] = '\0';  /* show field names, only */
            else if (LastToken == IDTOKEN) 
                strtcpy(ObjName, yytext, MAX_OBJNAME);
            else break;
            if (!Show_Fields(xpout, ObjName))  {
                printf("%s\n", SOBJ_error);
                return(CMD_ERR);
            }   
            return(CMD_OK);
                        
        case DETACH:
            /* detach [ <title-spec> ]  
             */
            if ((LastToken = LEXCALL) == IDTOKEN) {
                strtcpy(ObjName, yytext, MAX_OBJNAME);
                if (!Detach_SOBJ(xpout, ObjName))  {
                    printf("%s\n", SOBJ_error);
                    return(CMD_ERR);
                }   
                return(CMD_OK);
            }
            break;
            
        case ATTACH:
            /* 
             *   ATTACH { <attach list> }
             *
             *   This operation is atomic... as we parse the <attach list>,
             *   we build list of invocations and create objects as needed,
             *   but we don't install these invocations until the whole
             *   thing completes successfully.
             *
             */
            if (LEXCALL != '{') {
                printf("Bad Attach parm: no '{'\n");
                return(CMD_ERR);
            }   
            if (!Init_attach()) {
                printf("%s\n", SOBJ_error);         
                return(CMD_ERR);
            }
            LastToken = scan_Slist();
            
            if (LastToken != '}') {
                if (LastToken != ERR)
                    SOBJ_error =  "Missing '}'";
                CleanUp(0);
                printf("%s\n", SOBJ_error);         
                return(CMD_ERR);
            }           
                /* Install the invocations now... */
            if (!Install_it()) {;           
                printf("%s\n", SOBJ_error);         
                return(CMD_ERR);
            }
            printf("OK: attach\n");
            return(CMD_OK);
                
        case ENUM:
            /* 
             *   ENUM  { <enum list> } | ENUM ?
             */             
            if ((LastToken = LEXCALL) == '?') {
                Ebuf[0] = '\0';
                show_enums(Ebuf, sizeof(Ebuf));
                printf("%s\n", Ebuf);
                return(CMD_OK);
            }           
            if (LastToken != '{') {
                printf( "Bad Enum parm: no {\n");
                return(CMD_ERR);
            }
            if ((i = scan_enum(fpin)) == 0)  {
                printf("Unknown name: %s\n", yytext);
                return(CMD_ERR);
            }           
            else if (i < 0)  break; 
            printf("OK: enum\n");  
            return(CMD_OK);
                    
        case RESTRICT:
            /*
             *  RESTRICT {READONLY | READWRITE} ipaddr ipmask
             *    or
             *  RESTRICT ?
             */
            token = LEXCALL;
            if (token == '?') {
                    /* Print restrict table */
                Show_restrict();
                return(CMD_OK);
            }
            if (token != READONLY && token != READWRITE) {
                printf("Improper Restrict mode: %s\n", yytext);
                return(CMD_ERR);
            }
            if (!scan_pair(LEXCALL, &Rparm)) /* scan addr, mask pair */
                return(CMD_ERR);
            if (!Do_restrict((token == READWRITE), Rparm))
                return(CMD_ERR);
            printf("OK: restrict\n");
            return(CMD_OK);

        case SUBNET:
            /*
             *  SUBNET <net addr>, <mask>
             *
             *  Add specified (<net addr>, <mask>) pair to subnet list.
             *  If an IP address matches <net addr>, the specified <mask>
             *  will be used to define the corresponding subnet pseudo-field,
             *  instead of the default network class.
             *
             *  SUBNET ?
             *
             */
            if ((LastToken = LEXCALL) == '?')
                    /* Show subnets currently defined */
                Sparm.addr =  0;
            else if (!scan_pair(LastToken, &Sparm)) 
                 return(CMD_ERR);
            if (!Do_Subnet(xpout, Sparm))   {
                printf("%s\n", SOBJ_error);
                return(CMD_ERR);
            }
            return(CMD_OK);

        case INCLUDE: {  /* include <filename> -- include parm text here.
                          *    => Call Scan_Cmd() recursively.
                          *    Code supplied by Andrew Cherenson of SGI
                          */
            FILE *incfp;
            int rc;

            LastToken = LEXCALL;
            if (LastToken == STRING) {
                /* suppress " " */
                yytext[yyleng-1] = '\0';
                incfp = fopen(yytext+1, "r");
            }
            else if (LastToken == IDTOKEN) {
                incfp = fopen(yytext, "r");
            }
            else break;
            if (incfp) { 
                u_long old_line_num = line_number;
                line_number = -1;   /* XXX */
                while ((rc = Scan_Cmd(incfp, xpout)) == CMD_OK);
                if (rc != CMD_EOF) {
                    fprintf(stderr, "%s: INCLUDE FILE ERROR ON LINE %d\n",
                    ObjName, line_number);
                    exit(1);
                }
                fclose(incfp);
                line_number = old_line_num;
            } else {  /* can't open file */
                fprintf(stderr,
                "CONFIG FILE ERROR ON LINE %d: can't open '%s'\n", 
                line_number, ObjName);
                exit(1);
            }
            return(CMD_OK);
        }
             
        case '?':   
        case HELP:
            Do_help();
            return(CMD_OK);
                    
        default:
            switch (*yytext) {
            
            case 'p':
                /* poll <time> <title-spec> 
                 *         -- issues 'read <title>' every <time> secs.
                 *
                 *         -- poll 0  turns off polling.
                 */
                
                if (LEXCALL == NUMBER) {
                    interval = strtol(yytext, &rp, 0);
                    if ((LastToken = LEXCALL) != IDTOKEN)
                        rp = NULL;
                    else
                        rp = yytext;
                    SetPoll(interval, rp);
                }
                else 
                    SetPoll(-1, NULL);
                return(CMD_OK);
        
            case 'q':
                /* quit
                */
                exit(0);
                
            case 'h':
                /* host <hostname>: Set host for rspy (only)
                 */
                LastToken = LEXCALL;
                if (LastToken != IDTOKEN && LastToken != IPADDR) break;
                Do_host(yytext);
                return(CMD_OK);
                
            case 'f':
                /* file "<filename>": set display file for rspy, statspy.
                 *   (file "" returns output to local terminal)
                 */
                LastToken = LEXCALL;
                if (LastToken != STRING) break;
                yytext[strlen(yytext)-1] = '\0';  /* must delete quotation marks */
                Do_file(yytext+1);
                return(CMD_OK);
                        
            default:
                break;
            }  /* end of little switch */   
        };  /* end of big switch */
 
        printf("Syntax error at: %s\n", yytext);
        return(CMD_ERR);
    }
} /* Scan_Cmd() */

    /* 
     *  Scan Statement list:  
     *   <S-list> ::=  <Statement> | <S-list> <Statement>
     *
     *  Returns last token scanned, or error token.
     *
     */
int scan_Slist()
    {
    boolean  Begin = TRUE;     
    int  Token = LEXCALL;
    
    if (!doNull()) return(ERR);  /* Start of list */
    while (1) { 
        switch(Token) {
                    
        case '{':
        case IF:
        case SYMIF:
        case RECORD:
        case SELECT:
        case ';':
            Begin = FALSE;          
            if ((Token = scan_Statement(Token)) == ERR)  {
                return(ERR);
            }                           
                /* Append (list of) SOPC's to previous list... */
            if (!doAppend()) return(ERR);
            continue;
            
        default:
            if (Begin) {
                attach_error("Cannot start with", yytext);
                return(ERR);
            }
            return(Token);
        }
    }
} /* scan_Slist() */

    
    /* 
     *  Scan:  <Statement> ::= 
     *     record <record-invoke> ; |
     *     [sym]if <if-invoke> <Statement> |
     *     [sym]if <if-invoke> <Statement> else <Statement>  |
     *     select <field> { case <value-list>: <S-list>; ...
     *                                           [default: <S-list>] } |
     *     { <S-list> } |
     *     ;
     *
     *    First token is passed as parameter, and routine returns last
     *    token as result.  This provides one-token lookahead.
     */
int scan_Statement(Token)
    int Token;
    {
    boolean issym = FALSE;
    struct invokes Invokes;
    int dummy;
        
    if (Xdebug)
        printf("scan_Statement: token= %d text = %s\n", Token, yytext);
            
    switch (Token ) {
    
    case  ';':
        if (!doNull()) return(ERR);
        break;
                                
    case RECORD:
        if ((Token = scan_invoke(LEXCALL, &dummy, &Invokes)) == ERR) 
            return(ERR);
        if (!doInvoke(Record, &Invokes)) 
            return(ERR);
        if (Token != ';') {
            SOBJ_error = "Missing ; after record";
            return(ERR); 
        } 
        break;
        
    case SYMIF:
        issym = TRUE;
    case IF:            
        if ((Token = scan_cond()) == ERR)   
            return(ERR);
        if ((Token = scan_Statement(Token)) == ERR)   
            return(ERR);
                    
        if (Token == ELSE) {            
            if ((Token = scan_Statement(LEXCALL)) == ERR)  
                return(ERR);
        }   
        else  
            if (!doNull()) return(ERR);
            
        if (issym)
            {
            if (!doSymIf()) return(ERR);
        }
        else
            if (!doIf(FALSE)) return(ERR);
        return(Token);
     
    case SELECT:
        if (!scan_select(&Invokes) || !doInvoke(SelInvoke, &Invokes))
            return(ERR);
        if (!scan_cases(&Invokes) || !doSelect())
            return(ERR);
        break;
                      
    case '{':
        if ((Token = scan_Slist()) == ERR)  return(ERR);
        if (Token != '}')  { 
            SOBJ_error = "Missing }";
            return(ERR);
        }
        break;
        
    default:               
        attach_error("Syntax error at", yytext) ;       
        return(ERR);
    }
    return(LEXCALL);                
} /* scan_Statement() */


/*
 *  Scan conditional expression:
 *
 *    <condition> ::= <C-term> | <condition> OR <C-term>
 */
int scan_cond()
    {
    int token = scan_Cterm();
    
    while (token == OR) {
        if ((token = scan_Cterm()) == ERR || !doOr()) 
            return(ERR);
    }
    return(token);
}

/*
 *   <C-term> ::= <C-primary> | <C-term> AND <C-primary>
 *
 */
int scan_Cterm()
    {
    int token = scan_Cprim();
    
    while (token == AND) {
        if ((token = scan_Cprim()) == ERR || !doAnd()) 
            return(ERR);
    }
    return(token);
}
 
 /*
  *    <C-primary> ::= <if-invoke> | ( <condition> )
  *
  */
int scan_Cprim()
    {
    struct invokes Invokes;
    boolean ifnot;
    int token = LEXCALL;
    
    if (token == '(') {
        token = scan_cond();
        if (token != ')') {
            SOBJ_error = "Missing )";
            return(ERR);
        }
        return(LEXCALL);
    }
    
    if ((token = scan_invoke(token, &ifnot, &Invokes)) == ERR)
        return(ERR);
    if (!doInvoke((ifnot)? NotIfInv: IfInvoke, &Invokes)) 
        return(ERR);
    return(token);
}        
       
    
    
/*
 *  Scan Select statement:
 *    <field> [ <objname> ]
 *
 *  Results returned in specified struct invokes.
 */        
boolean scan_select(invokesp)
    struct invokes *invokesp;
    { 
    int token;
     
    invokesp->inv_objname[0] = '\0';  /* no object name */
    invokesp->inv_classno = SELECTOBJ;
    invokesp->inv_nparms = 0;
    invokesp->inv_field2[0] = '\0';
     
    if (LEXCALL != IDTOKEN) {
        SOBJ_error = "Missing select field";
        return(FALSE);
    }
    strtcpy(invokesp->inv_field1, yytext, MAX_FLDNAME);
    if ((token = LEXCALL) == IDTOKEN) {
        strtcpy(invokesp->inv_objname, yytext, MAX_OBJNAME);
        token = LEXCALL;
    }
    if (token != '{') {
        SOBJ_error = "Mising {";
        return(FALSE);
    }
    return(TRUE);
} /* scan_select() */


/*
 *  Scan case list of Select statement:
 *    {  case <value-list>: <S-list>; ...  [default: <S-list>] }
 */        
boolean scan_cases(invokesp)
    struct invokes *invokesp;
    { 
    int token;
    int caseno = 0;
    
    token = LEXCALL;
    while (token != '}') {
        invokesp->inv_nparms = 0;
        switch (token)  {
        case ERR:
            return(FALSE);
                   
        case CASE:
            if (!scan_vlist(LEXCALL, invokesp))
                return(FALSE);
            if (invokesp->inv_nparms == 0) {
                SOBJ_error = "Missing case value";
                return(FALSE);
            }
                /* (Note that ":" is noise char, & is deleted by LEX) */
            token = scan_Statement(LEXCALL);
            if (!doCase(++caseno, invokesp)) return(FALSE);
            break;
            
        case DEFAULT:
            token = scan_Statement(LEXCALL);
            if (!doCase(0, invokesp)) return(FALSE);
            break;
             
        default:
            attach_error("Syntax err in select statement at:", yytext);
            break;
        }
    }
    return(TRUE);
}  /* scan_cases() */

    
    /*
     *  Scan <invocation spec> ::=
     *      <fieldname>... {is|isnot} <objname> |
     *      <fieldname>... {is|isnot} [<objname>] <class> ( <parmlist>)
     *
     *     Parse invocation statement, and build specification structure
     *    *invokesp, as well as global parameter array Vals[].
     *
     *     Return last scanned token (or error token ERR).
     *     If ISNOT appears, set *isnotp to 1, else 0.
     */        
int scan_invoke(Token, isnotp, invokesp) 
    register int Token;
    boolean *isnotp;
    register struct invokes *invokesp;
    {
    
    *isnotp = FALSE;  /* Assume if (true)... */ 
    invokesp->inv_objname[0] = '\0';  /* Assume no object name */ 
    invokesp->inv_field2[0] = '\0';   /* Assume no field 2 */
    invokesp->inv_classno = 0;        /* Assume no class */
    invokesp->inv_nparms = 0;         /* Assume no parms */

    if (Token != IDTOKEN) {
        SOBJ_error = "Missing Field name";
        return(ERR);
    } 
    strtcpy(invokesp->inv_field1, yytext, MAX_FLDNAME);
    if ((Token = LEXCALL) == IDTOKEN) {
        strtcpy(invokesp->inv_field2, yytext, MAX_FLDNAME);
        Token = LEXCALL;
    }
    if (Token == ISNOT) 
        *isnotp = TRUE;  /* if NOT (true)... */
    else if (Token != INIS) {
        attach_error("Syntax error at", yytext);
        return(ERR);
    }
    if ((Token  = LEXCALL) == IDTOKEN) {
        strtcpy(invokesp->inv_objname, yytext, MAX_OBJNAME);
        Token = LEXCALL;
    }
    else if (Token != CLASS) {
        SOBJ_error = "Undefined Object";
        return(ERR);
    }   
    if (Token == CLASS) {
        invokesp->inv_classno = yyval.int_val;       
        if ((Token = LEXCALL) == '(') {
                /* Parameter list in parens.  */ 
            if (!scan_vlist(Token, invokesp))
                return(ERR);
            Token = LEXCALL;
        } 
    }      
    return(Token);
}   /* scan_invoke() */ 
               

/*
 *      boolean scan_vlist(firsttoken, struct invokes *)
 *
 *  Scan single value, or list of values in parentheses, encoded in any legal 
 *  fashion.  List may contain qouted strings, which refer to labels from 
 *  some enumeration belonging to object objnamep[].
 *    
 *  Copies converted values into dynamically-allocated array,
 *  pointed to by struct invokes, and sets # words in that struct.  
 * 
 */
boolean scan_vlist(token, invokesp)
    int token;
    struct invokes *invokesp;
    {
    int K, N = 0;
    EnumHandle enumh = NULL;  /* assume no name, => no enumeration */
    u_int32 Vals[MAXPARMS*LONGPERVAL];     /* Temp for value string */
    
    if (invokesp->inv_objname[0])  
        enumh = ExistEnumObj(invokesp->inv_objname); 
        
    if (token != '(') {
        if ((N = scan_value(token, enumh, Vals)) < 0) return(FALSE);
        if (N >=  MAXPARMS) {
            SOBJ_error = "Too many parm values";
            return(FALSE);
        }
    }
    else {
        while ((token = LEXCALL) != ')') {
            if ((K = scan_value(token, enumh, &Vals[N])) < 0) 
                return(FALSE);
            if ((N += K) >=  MAXPARMS) {
                SOBJ_error = "Too many parm values";
                return(FALSE);
            }
        }
    }
    if (N) {
        K = N*sizeof(u_int32);   /* WHAT IF K = 0? */
        if ((invokesp->inv_parmp = (u_int32 *) malloc(K)) == NULL) {
            SOBJ_error = "Memory shortage";
            return(FALSE);
        }
        bcopy (Vals, invokesp->inv_parmp, K);
    }
    invokesp->inv_nparms = N;
    return(TRUE);
}  /* scan_vlist() */

/* 
 *  Scan series of values, and return count, or -1 if syntax error.
 *
 */
int scan_value(token, enumh, valp)
    int token;
    EnumHandle enumh;
    u_char *valp;
    {
    int j;
    u_char tempval[MAX_DLENG];
    
    if (token == STRING) {
        yytext[yyleng-1] = '\0'; /* strip off quotation */
        if (enumh == NULL) {
            attach_error("No matching enum for", yytext+1);
            return(-1);
        }
        j = GetEnumVal(enumh, yytext+1, tempval);
    }
    else
        j = VALcnv(token, tempval) ;
    if (j == 0) {
        attach_error("Unknown name", yytext) ;
        return(-1);
    }
    else if (j < 0) {
        attach_error("Syntax error at", yytext) ;
        return(-1);
    }
    bzero(valp, MAX_DLENG);
    bcopy(tempval, valp + (PARMALIGN(j)), j);
    return((j+3)/sizeof(int32));  /* Number of fullwords */
}   /* scan_value() */    
 
    
/*  Given name of file, open/create it and set to append read results to it.
 */
Do_file(txt)
char *txt;
    {  
    if (rd_filename[0] && strcmp(rd_filename, txt)) {  
           /* Different file is already open,  must close it first. */
        fprintf(logfp, "\n");
        fflush(logfp);
        fclose(logfp);
        rd_filename[0] = '\0';
    }
    if (*txt) {
        if (NULL == (logfp = fopen(txt, "a"))) {
            perror("Create file");
            return;
        }
        strcpy(rd_filename, txt);
    }
    else logfp = stdout;
}


/*  
 *  Scan IP (address, mask) pair
 */            
boolean scan_pair(token, parmp)
    int token;
    struct addr_and_mask *parmp;
    { 
    union xlong value;
    int len;
                    
    if (token != IPADDR) {
        printf("Improper IP address: %s\n", yytext);
        return(FALSE);
    }
    len = IPcnv(yytext, &value);
    if (len <= 0 || len > 4) return(FALSE);
    parmp->addr = value.xllong;
    token = LEXCALL;
    if (token != IPADDR &&token != NUMBER) {
        printf("Improper mask: %s\n", yytext);
        return(FALSE);
    }
    len = VALcnv(token, &value);
    if (len < 0||len > 4) return(FALSE);
    parmp->mask = value.xllong;
    return(TRUE);
} /* scan_pair() */
