net-snmp/apps/encode_keychange.c

792 lines
21 KiB
C
Raw Normal View History

2022-06-27 15:01:12 +08:00
/*
* 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. */
size_t transform_type_len = 0;
/*
* 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, auth_type;
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.
*/
auth_type = usm_lookup_auth_type(transform_type_input);
transform_type = sc_get_auth_oid( auth_type, &transform_type_len );
if (NULL == transform_type) {
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", sc_get_auth_name(auth_type));
}
/*
* 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);
if ((ssize_t)engineid_len < 0)
exit(1);
}
#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, transform_type_len,
(u_char *) oldpass, strlen(oldpass),
oldKu, &oldKu_len);
QUITFUN(rval, main_quit);
rval = generate_Ku(transform_type, transform_type_len,
(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, transform_type_len,
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, transform_type_len,
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, transform_type_len,
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 preceded 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 */