/*
 * encode_keychange.c
 *
 * Collect information to build a KeyChange encoding, per the textual
 * convention given in RFC 2274, Section 5.  Compute the value and
 * dump to stdout as a string of hex nibbles.
 *
 *
 * Passphrase material may come from many sources.  The following are
 * checked in order (see get_user_passphrases()):
 *      - Prompt always if -f is given.
 *      - Commandline arguments.
 *      - PASSPHRASE_FILE.
 *      - Prompts on stdout.   Use -P to turn off prompt tags.
 *
 *
 * FIX  Better name?
 * FIX  Change encode_keychange() to take random bits?
 * FIX  QUITFUN not quite appropriate here...
 * FIX  This is slow...
 */

#include <net-snmp/net-snmp-config.h>

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#if HAVE_WINSOCK_H
#include <winsock.h>
#endif

#include <net-snmp/net-snmp-includes.h>

#include <stdlib.h>

/*
 * Globals, &c...
 */
char           *local_progname;
char           *local_passphrase_filename;

#define NL	"\n"

#define USAGE	"Usage: %s [-fhPvV] -t (md5|sha1) [-O \"<old_passphrase>\"][-N \"<new_passphrase>\"][-E [0x]<engineID>]"

#define OPTIONLIST	"E:fhN:O:Pt:vVD"

#define PASSPHRASE_DIR		".snmp"
        /*
         * Rooted at $HOME.
         */
#define PASSPHRASE_FILE		"passphrase.ek"
        /*
         * Format: two lines containing old and new passphrases, nothing more.
         *      
         * XXX  Add creature comforts like: comments and 
         *      tokens identifying passphrases, separate directory check,
         *      check in current directory (?), traverse a path of
         *      directories (?)...
         * FIX  Better name?
         */


int             forcepassphrase = 0,    /* Always prompt for passphrases. */
                promptindicator = 1,    /* Output an indicator that input
                                         *   is requested.                */
                visible = 0,    /* Echo passphrases to terminal.  */
                verbose = 0;    /* Output progress to stderr.     */
size_t          engineid_len = 0;

u_char         *engineid = NULL;        /* Both input & final binary form. */
char           *newpass = NULL, *oldpass = NULL;

char           *transform_type_input = NULL;

const oid      *transform_type = NULL;  /* Type of HMAC hash to use.      */



/*
 * Prototypes.
 */
void            usage_to_file(FILE * ofp);
void            usage_synopsis(FILE * ofp);
int             get_user_passphrases(void);
int             snmp_ttyecho(const int fd, const int echo);
char           *snmp_getpassphrase(const char *prompt, int fvisible);

#ifdef WIN32
#define HAVE_GETPASS 1
char           *getpass(const char *prompt);
int             isatty(int);
int             _cputs(const char *);
int             _getch(void);
#endif

/*******************************************************************-o-******
 */
int
main(int argc, char **argv)
{
    int             rval = SNMPERR_SUCCESS;
    size_t          oldKu_len = SNMP_MAXBUF_SMALL,
        newKu_len = SNMP_MAXBUF_SMALL,
        oldkul_len = SNMP_MAXBUF_SMALL,
        newkul_len = SNMP_MAXBUF_SMALL, keychange_len = SNMP_MAXBUF_SMALL;

    char           *s = NULL;
    u_char          oldKu[SNMP_MAXBUF_SMALL],
        newKu[SNMP_MAXBUF_SMALL],
        oldkul[SNMP_MAXBUF_SMALL],
        newkul[SNMP_MAXBUF_SMALL], keychange[SNMP_MAXBUF_SMALL];

    int             i;
    int             arg = 1;

    local_progname = argv[0];
    local_passphrase_filename = (char *) malloc(sizeof(PASSPHRASE_DIR) +
                                                sizeof(PASSPHRASE_FILE) +
                                                4);
    if (!local_passphrase_filename) {
        fprintf(stderr, "%s: out of memory!", local_progname);
        exit(-1);
    }
    sprintf(local_passphrase_filename, "%s/%s", PASSPHRASE_DIR,
            PASSPHRASE_FILE);



    /*
     * Parse.
     */
    for (; (arg < argc) && (argv[arg][0] == '-'); arg++) {
        switch (argv[arg][1]) {
        case 'D':
            snmp_set_do_debugging(1);
            break;
        case 'E':
            engineid = (u_char *) argv[++arg];
            break;
        case 'f':
            forcepassphrase = 1;
            break;
        case 'N':
            newpass = argv[++arg];
            break;
        case 'O':
            oldpass = argv[++arg];
            break;
        case 'P':
            promptindicator = 0;
            break;
        case 't':
            transform_type_input = argv[++arg];
            break;
        case 'v':
            verbose = 1;
            break;
        case 'V':
            visible = 1;
            break;
        case 'h':
            rval = 0;
        default:
            usage_to_file(stdout);
            exit(rval);
        }
    }

    if (!transform_type_input) {
        fprintf(stderr, "The -t option is mandatory.\n");
        usage_synopsis(stdout);
        exit(1000);
    }



    /*
     * Convert and error check transform_type.
     */
#ifndef DISABLE_MD5
    if (!strcmp(transform_type_input, "md5")) {
        transform_type = usmHMACMD5AuthProtocol;

    } else
#endif
        if (!strcmp(transform_type_input, "sha1")) {
        transform_type = usmHMACSHA1AuthProtocol;

    } else {
        fprintf(stderr,
                "Unrecognized hash transform: \"%s\".\n",
                transform_type_input);
        usage_synopsis(stderr);
        QUITFUN(rval = SNMPERR_GENERR, main_quit);
    }

    if (verbose) {
        fprintf(stderr, "Hash:\t\t%s\n",
#ifndef DISABLE_MD5
                (transform_type == usmHMACMD5AuthProtocol)
                ? "usmHMACMD5AuthProtocol" :
#endif
                "usmHMACSHA1AuthProtocol"
            );
    }



    /*
     * Build engineID.  Accept hex engineID as the bits
     * "in-and-of-themselves", otherwise create an engineID with the
     * given string as text.
     *
     * If no engineID is given, lookup the first IP address for the
     * localhost and use that (see setup_engineID()).
     */
    if (engineid && (tolower(*(engineid + 1)) == 'x')) {
        engineid_len = hex_to_binary2(engineid + 2,
                                      strlen((char *) engineid) - 2,
                                      (char **) &engineid);
        DEBUGMSGTL(("encode_keychange", "engineIDLen: %d\n",
                    engineid_len));
    } else {
        engineid_len = setup_engineID(&engineid, (char *) engineid);

    }

#ifdef SNMP_TESTING_CODE
    if (verbose) {
        fprintf(stderr, "EngineID:\t%s\n",
                /*
                 * XXX = 
                 */ dump_snmpEngineID(engineid, &engineid_len));
    }
#endif


    /*
     * Get passphrases from user.
     */
    rval = get_user_passphrases();
    QUITFUN(rval, main_quit);

    if (strlen(oldpass) < USM_LENGTH_P_MIN) {
        fprintf(stderr, "Old passphrase must be greater than %d "
                "characters in length.\n", USM_LENGTH_P_MIN);
        QUITFUN(rval = SNMPERR_GENERR, main_quit);

    } else if (strlen(newpass) < USM_LENGTH_P_MIN) {
        fprintf(stderr, "New passphrase must be greater than %d "
                "characters in length.\n", USM_LENGTH_P_MIN);
        QUITFUN(rval = SNMPERR_GENERR, main_quit);
    }

    if (verbose) {
        fprintf(stderr,
                "Old passphrase:\t%s\nNew passphrase:\t%s\n",
                oldpass, newpass);
    }



    /*
     * Compute Ku and Kul's from old and new passphrases, then
     * compute the keychange string & print it out.
     */
    rval = sc_init();
    QUITFUN(rval, main_quit);


    rval = generate_Ku(transform_type, USM_LENGTH_OID_TRANSFORM,
                       (u_char *) oldpass, strlen(oldpass),
                       oldKu, &oldKu_len);
    QUITFUN(rval, main_quit);


    rval = generate_Ku(transform_type, USM_LENGTH_OID_TRANSFORM,
                       (u_char *) newpass, strlen(newpass),
                       newKu, &newKu_len);
    QUITFUN(rval, main_quit);


    DEBUGMSGTL(("encode_keychange", "EID (%d): ", engineid_len));
    for (i = 0; i < (int) engineid_len; i++)
        DEBUGMSGTL(("encode_keychange", "%02x", (int) (engineid[i])));
    DEBUGMSGTL(("encode_keychange", "\n"));

    DEBUGMSGTL(("encode_keychange", "old Ku (%d) (from %s): ", oldKu_len,
                oldpass));
    for (i = 0; i < (int) oldKu_len; i++)
        DEBUGMSGTL(("encode_keychange", "%02x", (int) (oldKu[i])));
    DEBUGMSGTL(("encode_keychange", "\n"));

    rval = generate_kul(transform_type, USM_LENGTH_OID_TRANSFORM,
                        engineid, engineid_len,
                        oldKu, oldKu_len, oldkul, &oldkul_len);
    QUITFUN(rval, main_quit);


    DEBUGMSGTL(("encode_keychange", "generating old Kul (%d) (from Ku): ",
                oldkul_len));
    for (i = 0; i < (int) oldkul_len; i++)
        DEBUGMSGTL(("encode_keychange", "%02x", (int) (oldkul[i])));
    DEBUGMSGTL(("encode_keychange", "\n"));

    rval = generate_kul(transform_type, USM_LENGTH_OID_TRANSFORM,
                        engineid, engineid_len,
                        newKu, newKu_len, newkul, &newkul_len);
    QUITFUN(rval, main_quit);

    DEBUGMSGTL(("encode_keychange", "generating new Kul (%d) (from Ku): ",
                oldkul_len));
    for (i = 0; i < (int) newkul_len; i++)
        DEBUGMSGTL(("encode_keychange", "%02x", newkul[i]));
    DEBUGMSGTL(("encode_keychange", "\n"));

    rval = encode_keychange(transform_type, USM_LENGTH_OID_TRANSFORM,
                            oldkul, oldkul_len,
                            newkul, newkul_len, keychange, &keychange_len);
    QUITFUN(rval, main_quit);



    binary_to_hex(keychange, keychange_len, &s);
    printf("%s%s\n", (verbose) ? "KeyChange string:\t" : "",    /* XXX stdout */
           s);


    /*
     * Cleanup.
     */
  main_quit:
    snmp_call_callbacks(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_SHUTDOWN,
                        NULL);


    SNMP_ZERO(oldpass, strlen(oldpass));
    SNMP_ZERO(newpass, strlen(newpass));

    SNMP_ZERO(oldKu, oldKu_len);
    SNMP_ZERO(newKu, newKu_len);

    SNMP_ZERO(oldkul, oldkul_len);
    SNMP_ZERO(newkul, newkul_len);

    SNMP_ZERO(s, strlen(s));

    return rval;

}                               /* end main() */




/*******************************************************************-o-******
 */
void
usage_synopsis(FILE * ofp)
{
    fprintf(ofp, USAGE "\n\
\n\
    -E [0x]<engineID>		EngineID used for kul generation.\n\
    -f				Force passphrases to be read from stdin.\n\
    -h				Help.\n\
    -N \"<new_passphrase>\"	Passphrase used to generate new Ku.\n\
    -O \"<old_passphrase>\"	Passphrase used to generate old Ku.\n\
    -P				Turn off prompt indicators.\n\
    -t md5 | sha1		HMAC hash transform type.\n\
    -v				Verbose.\n\
    -V				Visible.  Echo passphrases to terminal.\n\
		" NL, local_progname);

}                               /* end usage_synopsis() */

void
usage_to_file(FILE * ofp)
{
    char           *s;

    usage_synopsis(ofp);

    fprintf(ofp, "\n\
    Only -t is mandatory.  The transform is used to convert P=>Ku, convert\n\
    Ku=>Kul, and to hash the old Kul with the random bits.\n\
\n\
    Passphrase will be taken from the first successful source as follows:\n\
	a) Commandline options,\n\
	b) The file \"%s/%s\",\n\
	c) stdin  -or-  User input from the terminal.\n\
\n\
    -f will require reading from the stdin/terminal, ignoring a) and b).\n\
    -P will prevent prompts for passphrases to stdout from being printed.\n\
\n\
    <engineID> is intepreted as a hex string when preceeded by \"0x\",\n\
    otherwise it is created to contain \"text\".  If nothing is given,\n\
    <engineID> is constructed from the first IP address for the local host.\n\
		" NL, (s = getenv("HOME")) ? s : "$HOME", local_passphrase_filename);


    /*
     * FIX -- make this possible?
     * -r [0x]<random_bits> Random bits used in KeyChange XOR.
     * 
     * <engineID> and <random_bits> are intepreted as hex strings when
     * preceeded by \"0x\", otherwise <engineID> is created to contain \"text\"
     * and <random_bits> are the same as the ascii input.
     * 
     * <random_bits> will be generated by SCAPI if not given.  If value is
     * too long, it will be truncated; if too short, the remainder will be
     * filled in with zeros.
     */

}                               /* end usage() */


/*
 * this defined for HPUX aCC because the aCC doesn't drop the 
 */
/*
 * snmp_parse_args.c functionality if compile with -g, PKY 
 */

void
usage(void)
{
    usage_to_file(stdout);
}





/*******************************************************************-o-******
 * get_user_passphrases
 *
 * Returns:
 *	SNMPERR_SUCCESS		Success.
 *	SNMPERR_GENERR		Otherwise.
 *
 *
 * Acquire new and old passphrases from the user:
 *
 *	+ Always prompt if 'forcepassphrase' is set.
 *	+ Use given arguments if they are defined.
 *	+ Otherwise read file format from PASSPHRASE_FILE.
 *		Sanity check existence and permissions of the path.
 *		ASSUME for now that PASSPHRASE_FILE is rooted only at $HOME.
 *	+ Otherwise prompt user for passphrase(s).
 *		Echo input if 'visible' is set.
 *		Turning off 'promptindicator' makes piping in input cleaner.
 *
 * NOTE Only using forcepassphrase mandates taking both passphrases
 * from the same source.  Otherwise processing continues until both 
 * passphrases are defined.
 */
int
get_user_passphrases(void)
{
    int             rval = SNMPERR_SUCCESS;
    size_t          len;

    char           *obuf = NULL, *nbuf = NULL;

    char            path[SNMP_MAXBUF], buf[SNMP_MAXBUF], *s = NULL;

    struct stat     statbuf;
    FILE           *fp;



    /*
     * Allow prompts to the user to override all other sources.
     * Nothing to do otherwise if oldpass and newpass are already defined.
     */
    if (forcepassphrase)
        goto get_user_passphrases_prompt;
    if (oldpass && newpass)
        goto get_user_passphrases_quit;



    /*
     * Read passphrases out of PASSPHRASE_FILE.  Sanity check the
     * path for existence and access first.  Refuse to read
     * if the permissions are wrong.
     */
    s = getenv("HOME");
    snprintf(path, sizeof(path), "%s/%s", s, PASSPHRASE_DIR);
    path[ sizeof(path)-1 ] = 0;

    /*
     * Test directory. 
     */
    if (stat(path, &statbuf) < 0) {
        fprintf(stderr, "Cannot access directory \"%s\".\n", path);
        QUITFUN(rval = SNMPERR_GENERR, get_user_passphrases_quit);
#ifndef WIN32
    } else if (statbuf.st_mode & (S_IRWXG | S_IRWXO)) {
        fprintf(stderr,
                "Directory \"%s\" is accessible by group or world.\n",
                path);
        QUITFUN(rval = SNMPERR_GENERR, get_user_passphrases_quit);
#endif                          /* !WIN32 */
    }

    /*
     * Test file. 
     */
    snprintf(path, sizeof(path), "%s/%s", s, local_passphrase_filename);
    path[ sizeof(path)-1 ] = 0;
    if (stat(path, &statbuf) < 0) {
        fprintf(stderr, "Cannot access file \"%s\".\n", path);
        QUITFUN(rval = SNMPERR_GENERR, get_user_passphrases_quit);
#ifndef WIN32
    } else if (statbuf.st_mode & (S_IRWXG | S_IRWXO)) {
        fprintf(stderr,
                "File \"%s\" is accessible by group or world.\n", path);
        QUITFUN(rval = SNMPERR_GENERR, get_user_passphrases_quit);
#endif                          /* !WIN32 */
    }

    /*
     * Open the file. 
     */
    if ((fp = fopen(path, "r")) == NULL) {
        fprintf(stderr, "Cannot open \"%s\".", path);
        QUITFUN(rval = SNMPERR_GENERR, get_user_passphrases_quit);
    }

    /*
     * Read 1st line. 
     */
    if (!fgets(buf, sizeof(buf), fp)) {
        if (verbose) {
            fprintf(stderr, "Passphrase file \"%s\" is empty...\n", path);
        }
        goto get_user_passphrases_prompt;

    } else if (!oldpass) {
        len = strlen(buf);
        if (buf[len - 1] == '\n')
            buf[--len] = '\0';
        oldpass = (char *) calloc(1, len + 1);
        if (oldpass)
            memcpy(oldpass, buf, len + 1);
    }
    /*
     * Read 2nd line. 
     */
    if (!fgets(buf, sizeof(buf), fp)) {
        if (verbose) {
            fprintf(stderr, "Only one line in file \"%s\"...\n", path);
        }

    } else if (!newpass) {
        len = strlen(buf);
        if (buf[len - 1] == '\n')
            buf[--len] = '\0';
        newpass = (char *) calloc(1, len + 1);
        if (newpass)
            memcpy(newpass, buf, len + 1);
    }

    if (oldpass && newpass)
        goto get_user_passphrases_quit;



    /*
     * Prompt the user for passphrase entry.  Visible prompts
     * may be omitted, and invisible entry may turned off.
     */
  get_user_passphrases_prompt:
    if (forcepassphrase) {
        oldpass = newpass = NULL;
    }

    if (!oldpass) {
        oldpass = obuf
            = snmp_getpassphrase((promptindicator) ? "Old passphrase: " :
                                 "", visible);
    }
    if (!newpass) {
        newpass = nbuf
            = snmp_getpassphrase((promptindicator) ? "New passphrase: " :
                                 "", visible);
    }



    /*
     * Check that both passphrases were defined.
     */
    if (oldpass && newpass) {
        goto get_user_passphrases_quit;
    } else {
        rval = SNMPERR_GENERR;
    }


  get_user_passphrases_quit:
    SNMP_ZERO(buf, SNMP_MAXBUF);

    if (obuf != oldpass) {
        SNMP_ZERO(obuf, strlen(obuf));
        SNMP_FREE(obuf);
    }
    if (nbuf != newpass) {
        SNMP_ZERO(nbuf, strlen(nbuf));
        SNMP_FREE(nbuf);
    }

    return rval;

}                               /* end get_user_passphrases() */

/*******************************************************************-o-******
 * snmp_ttyecho
 *
 * Parameters:
 *	fd	Descriptor of terminal on which to toggle echoing.
 *	echo	TRUE if echoing should be on; FALSE otherwise.
 *      
 * Returns:
 *	Previous value of echo setting.
 *
 *
 * FIX	Put HAVE_TCGETATTR in autoconf?
 */
#ifndef HAVE_GETPASS
#ifdef HAVE_TCGETATTR
#include <termios.h>
int
snmp_ttyecho(const int fd, const int echo)
{
    struct termios  tio;
    int             was_echo;


    if (!isatty(fd))
        return (-1);
    tcgetattr(fd, &tio);
    was_echo = (tio.c_lflag & ECHO) != 0;
    if (echo)
        tio.c_lflag |= (ECHO | ECHONL);
    else
        tio.c_lflag &= ~(ECHO | ECHONL);
    tcsetattr(fd, TCSANOW, &tio);

    return (was_echo);

}                               /* end snmp_ttyecho() */

#else
#include <sgtty.h>
int
snmp_ttyecho(const int fd, const int echo)
{
    struct sgttyb   ttyparams;
    int             was_echo;


    if (!isatty(fd))
        was_echo = -1;
    else {
        ioctl(fd, TIOCGETP, &ttyparams);
        was_echo = (ttyparams.sg_flags & ECHO) != 0;
        if (echo)
            ttyparams.sg_flags = ttyparams.sg_flags | ECHO;
        else
            ttyparams.sg_flags = ttyparams.sg_flags & ~ECHO;
        ioctl(fd, TIOCSETP, &ttyparams);
    }

    return (was_echo);

}                               /* end snmp_ttyecho() */
#endif                          /* HAVE_TCGETATTR */
#endif                          /* HAVE_GETPASS */




/*******************************************************************-o-******
 * snmp_getpassphrase
 *
 * Parameters:
 *	*prompt		(May be NULL.)
 *	 bvisible	TRUE means echo back user input.
 *      
 * Returns:
 *	Pointer to newly allocated, null terminated string containing
 *		passphrase  -OR-
 *	NULL on error.
 *
 *
 * Prompt stdin for a string (or passphrase).  Return a copy of the 
 * input in a null terminated string.
 *
 * FIX	Put HAVE_GETPASS in autoconf.
 */
char           *
snmp_getpassphrase(const char *prompt, int bvisible)
{
    int             ti = 0;
    size_t          len;

    char           *bufp = NULL;
    static char     buffer[SNMP_MAXBUF];

    FILE           *ofp = stdout;


    /*
     * Query stdin for a passphrase.
     */
#ifdef HAVE_GETPASS
    if (isatty(0)) {
        return getpass((prompt) ? prompt : "");
    }
#endif

    fputs((prompt) ? prompt : "", ofp);

    if (!bvisible) {
        ti = snmp_ttyecho(0, 0);
    }

    fgets(buffer, sizeof(buffer), stdin);

    if (!bvisible) {
        ti = snmp_ttyecho(0, ti);
        fputs("\n", ofp);
    }


    /*
     * Copy the input and zero out the read-in buffer.
     */
    len = strlen(buffer);
    if (buffer[len - 1] == '\n')
        buffer[--len] = '\0';

    bufp = (char *) calloc(1, len + 1);
    if (bufp)
        memcpy(bufp, buffer, len + 1);

    SNMP_ZERO(buffer, SNMP_MAXBUF);


    return bufp;

}                               /* end snmp_getpassphrase() */

#ifdef WIN32

int
snmp_ttyecho(const int fd, const int echo)
{
    return 0;
}

/*
 * stops at the first newline, carrier return, or backspace.
 * WARNING! _getch does NOT read <Ctrl-C>
 */
char           *
getpass(const char *prompt)
{
    static char     pbuf[128];
    int             ch, lim;

    _cputs(prompt);
    for (ch = 0, lim = 0; ch != '\n' && lim < sizeof(pbuf)-1;) {
        ch = _getch();          /* look ma, no echo ! */
        if (ch == '\r' || ch == '\n' || ch == '\b')
            break;
        pbuf[lim++] = ch;
    }
    pbuf[lim] = '\0';
    puts("\n");

    return pbuf;
}
#endif                          /* WIN32 */
