diff options
Diffstat (limited to 'apps/encode_keychange.c')
-rw-r--r-- | apps/encode_keychange.c | 803 |
1 files changed, 803 insertions, 0 deletions
diff --git a/apps/encode_keychange.c b/apps/encode_keychange.c new file mode 100644 index 0000000..5bf8e4c --- /dev/null +++ b/apps/encode_keychange.c @@ -0,0 +1,803 @@ +/* + * 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 + +#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 = 1; + 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; + /* fallthrough */ + 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 NETSNMP_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(SNMPERR_GENERR, main_quit); + } + + if (verbose) { + fprintf(stderr, "Hash:\t\t%s\n", +#ifndef NETSNMP_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: %lu\n", + (unsigned long)engineid_len)); + } else { + engineid_len = setup_engineID(&engineid, (char *) engineid); + + } + +#ifdef NETSNMP_ENABLE_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(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(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 (%lu): ", (unsigned long)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 (%lu) (from %s): ", (unsigned long)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 (%lu) (from Ku): ", + (unsigned long)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 (%lu) (from Ku): ", + (unsigned long)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)); + + memset(oldKu, 0, oldKu_len); + memset(newKu, 0, newKu_len); + + memset(oldkul, 0, oldkul_len); + memset(newkul, 0, 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%s\ + a) Commandline options,\n\ + b) The file \"%s/%s\",\n\ + c) stdin -or- User input from the terminal.\n\n%s\ + " NL, + "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", + (s = getenv("HOME")) ? s : "$HOME", local_passphrase_filename, + "-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 interpreted 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"); + + + /* + * FIX -- make this possible? + * -r [0x]<random_bits> Random bits used in KeyChange XOR. + * + * <engineID> and <random_bits> are interpreted 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 = NULL; + + + + /* + * 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(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(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(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(SNMPERR_GENERR, get_user_passphrases_quit); +#endif /* !WIN32 */ + } + + /* + * Open the file. + */ + if ((fp = fopen(path, "r")) == NULL) { + fprintf(stderr, "Cannot open \"%s\".", path); + QUITFUN(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: + memset(buf, 0, SNMP_MAXBUF); + + if (obuf != oldpass) { + SNMP_ZERO(obuf, strlen(obuf)); + SNMP_FREE(obuf); + } + if (nbuf != newpass) { + SNMP_ZERO(nbuf, strlen(nbuf)); + SNMP_FREE(nbuf); + } + + if (fp) + fclose (fp); + + 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); + } + + bufp = fgets(buffer, sizeof(buffer), stdin); + + if (!bvisible) { + ti = snmp_ttyecho(0, ti); + fputs("\n", ofp); + } + if (!bufp) { + fprintf(stderr, "Aborted...\n"); + exit(1); + } + + + /* + * 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); + + memset(buffer, 0, 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 */ |