blob: 8da3980e5778eae43e67407fb5e67bca2aa04cb0 [file] [log] [blame]
/*
* 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 <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 "asn1.h"
#include "snmp_api.h"
#include "tools.h"
#include "scapi.h"
#include "snmpv3.h"
#include "keytools.h"
#include "snmp_debug.h"
#include "callback.h"
#include "../snmplib/transform_oids.h"
#include <stdlib.h>
/*
* Globals, &c...
*/
char *local_progname;
#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_DIR ## "/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;
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];
/*
* 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.
*/
if ( !strcmp(transform_type_input, "md5") ) {
transform_type = usmHMACMD5AuthProtocol;
} else 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",
(transform_type == usmHMACMD5AuthProtocol)
? "usmHMACMD5AuthProtocol"
: "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", PASSPHRASE_FILE);
/* 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 PASSWORD_FILE.
* Sanity check existence and permissions of the path.
* ASSUME for now that PASSWORD_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");
sprintf(path, "%s/%s", s, PASSPHRASE_DIR);
/* 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. */
sprintf(path, "%s/%s", s, PASSPHRASE_FILE);
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);
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);
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);
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); )
{
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 */