4 * Collect information to build a KeyChange encoding, per the textual
5 * convention given in RFC 2274, Section 5. Compute the value and
6 * dump to stdout as a string of hex nibbles.
9 * Passphrase material may come from many sources. The following are
10 * checked in order (see get_user_passphrases()):
11 * - Prompt always if -f is given.
12 * - Commandline arguments.
14 * - Prompts on stdout. Use -P to turn off prompt tags.
18 * FIX Change encode_keychange() to take random bits?
19 * FIX QUITFUN not quite appropriate here...
23 #include <net-snmp/net-snmp-config.h>
27 #include <sys/types.h>
37 #ifdef HAVE_NETINET_IN_H
38 #include <netinet/in.h>
45 #include <net-snmp/net-snmp-includes.h>
53 char *local_passphrase_filename
;
57 #define USAGE "Usage: %s [-fhPvV] -t (md5|sha1) [-O \"<old_passphrase>\"][-N \"<new_passphrase>\"][-E [0x]<engineID>]"
59 #define OPTIONLIST "E:fhN:O:Pt:vVD"
61 #define PASSPHRASE_DIR ".snmp"
65 #define PASSPHRASE_FILE "passphrase.ek"
67 * Format: two lines containing old and new passphrases, nothing more.
69 * XXX Add creature comforts like: comments and
70 * tokens identifying passphrases, separate directory check,
71 * check in current directory (?), traverse a path of
77 int forcepassphrase
= 0, /* Always prompt for passphrases. */
78 promptindicator
= 1, /* Output an indicator that input
80 visible
= 0, /* Echo passphrases to terminal. */
81 verbose
= 0; /* Output progress to stderr. */
82 size_t engineid_len
= 0;
84 u_char
*engineid
= NULL
; /* Both input & final binary form. */
85 char *newpass
= NULL
, *oldpass
= NULL
;
87 char *transform_type_input
= NULL
;
89 const oid
*transform_type
= NULL
; /* Type of HMAC hash to use. */
96 void usage_to_file(FILE * ofp
);
97 void usage_synopsis(FILE * ofp
);
98 int get_user_passphrases(void);
99 int snmp_ttyecho(const int fd
, const int echo
);
100 char *snmp_getpassphrase(const char *prompt
, int fvisible
);
103 #define HAVE_GETPASS 1
104 char *getpass(const char *prompt
);
106 int _cputs(const char *);
110 /*******************************************************************-o-******
113 main(int argc
, char **argv
)
115 int rval
= SNMPERR_SUCCESS
;
116 size_t oldKu_len
= SNMP_MAXBUF_SMALL
,
117 newKu_len
= SNMP_MAXBUF_SMALL
,
118 oldkul_len
= SNMP_MAXBUF_SMALL
,
119 newkul_len
= SNMP_MAXBUF_SMALL
, keychange_len
= SNMP_MAXBUF_SMALL
;
122 u_char oldKu
[SNMP_MAXBUF_SMALL
],
123 newKu
[SNMP_MAXBUF_SMALL
],
124 oldkul
[SNMP_MAXBUF_SMALL
],
125 newkul
[SNMP_MAXBUF_SMALL
], keychange
[SNMP_MAXBUF_SMALL
];
130 local_progname
= argv
[0];
131 local_passphrase_filename
= (char *) malloc(sizeof(PASSPHRASE_DIR
) +
132 sizeof(PASSPHRASE_FILE
) +
134 if (!local_passphrase_filename
) {
135 fprintf(stderr
, "%s: out of memory!", local_progname
);
138 sprintf(local_passphrase_filename
, "%s/%s", PASSPHRASE_DIR
,
146 for (; (arg
< argc
) && (argv
[arg
][0] == '-'); arg
++) {
147 switch (argv
[arg
][1]) {
149 snmp_set_do_debugging(1);
152 engineid
= (u_char
*) argv
[++arg
];
158 newpass
= argv
[++arg
];
161 oldpass
= argv
[++arg
];
167 transform_type_input
= argv
[++arg
];
178 usage_to_file(stdout
);
183 if (!transform_type_input
) {
184 fprintf(stderr
, "The -t option is mandatory.\n");
185 usage_synopsis(stdout
);
192 * Convert and error check transform_type.
194 if (!strcmp(transform_type_input
, "md5")) {
195 transform_type
= usmHMACMD5AuthProtocol
;
197 } else if (!strcmp(transform_type_input
, "sha1")) {
198 transform_type
= usmHMACSHA1AuthProtocol
;
202 "Unrecognized hash transform: \"%s\".\n",
203 transform_type_input
);
204 usage_synopsis(stderr
);
205 QUITFUN(rval
= SNMPERR_GENERR
, main_quit
);
209 fprintf(stderr
, "Hash:\t\t%s\n",
210 (transform_type
== usmHMACMD5AuthProtocol
)
211 ? "usmHMACMD5AuthProtocol" : "usmHMACSHA1AuthProtocol");
217 * Build engineID. Accept hex engineID as the bits
218 * "in-and-of-themselves", otherwise create an engineID with the
219 * given string as text.
221 * If no engineID is given, lookup the first IP address for the
222 * localhost and use that (see setup_engineID()).
224 if (engineid
&& (tolower(*(engineid
+ 1)) == 'x')) {
225 engineid_len
= hex_to_binary2(engineid
+ 2,
226 strlen((char *) engineid
) - 2,
227 (char **) &engineid
);
228 DEBUGMSGTL(("encode_keychange", "engineIDLen: %d\n",
231 engineid_len
= setup_engineID(&engineid
, (char *) engineid
);
235 #ifdef SNMP_TESTING_CODE
237 fprintf(stderr
, "EngineID:\t%s\n",
240 */ dump_snmpEngineID(engineid
, &engineid_len
));
246 * Get passphrases from user.
248 rval
= get_user_passphrases();
249 QUITFUN(rval
, main_quit
);
251 if (strlen(oldpass
) < USM_LENGTH_P_MIN
) {
252 fprintf(stderr
, "Old passphrase must be greater than %d "
253 "characters in length.\n", USM_LENGTH_P_MIN
);
254 QUITFUN(rval
= SNMPERR_GENERR
, main_quit
);
256 } else if (strlen(newpass
) < USM_LENGTH_P_MIN
) {
257 fprintf(stderr
, "New passphrase must be greater than %d "
258 "characters in length.\n", USM_LENGTH_P_MIN
);
259 QUITFUN(rval
= SNMPERR_GENERR
, main_quit
);
264 "Old passphrase:\t%s\nNew passphrase:\t%s\n",
271 * Compute Ku and Kul's from old and new passphrases, then
272 * compute the keychange string & print it out.
275 QUITFUN(rval
, main_quit
);
278 rval
= generate_Ku(transform_type
, USM_LENGTH_OID_TRANSFORM
,
279 (u_char
*) oldpass
, strlen(oldpass
),
281 QUITFUN(rval
, main_quit
);
284 rval
= generate_Ku(transform_type
, USM_LENGTH_OID_TRANSFORM
,
285 (u_char
*) newpass
, strlen(newpass
),
287 QUITFUN(rval
, main_quit
);
290 DEBUGMSGTL(("encode_keychange", "EID (%d): ", engineid_len
));
291 for (i
= 0; i
< (int) engineid_len
; i
++)
292 DEBUGMSGTL(("encode_keychange", "%02x", (int) (engineid
[i
])));
293 DEBUGMSGTL(("encode_keychange", "\n"));
295 DEBUGMSGTL(("encode_keychange", "old Ku (%d) (from %s): ", oldKu_len
,
297 for (i
= 0; i
< (int) oldKu_len
; i
++)
298 DEBUGMSGTL(("encode_keychange", "%02x", (int) (oldKu
[i
])));
299 DEBUGMSGTL(("encode_keychange", "\n"));
301 rval
= generate_kul(transform_type
, USM_LENGTH_OID_TRANSFORM
,
302 engineid
, engineid_len
,
303 oldKu
, oldKu_len
, oldkul
, &oldkul_len
);
304 QUITFUN(rval
, main_quit
);
307 DEBUGMSGTL(("encode_keychange", "generating old Kul (%d) (from Ku): ",
309 for (i
= 0; i
< (int) oldkul_len
; i
++)
310 DEBUGMSGTL(("encode_keychange", "%02x", (int) (oldkul
[i
])));
311 DEBUGMSGTL(("encode_keychange", "\n"));
313 rval
= generate_kul(transform_type
, USM_LENGTH_OID_TRANSFORM
,
314 engineid
, engineid_len
,
315 newKu
, newKu_len
, newkul
, &newkul_len
);
316 QUITFUN(rval
, main_quit
);
318 DEBUGMSGTL(("encode_keychange", "generating new Kul (%d) (from Ku): ",
320 for (i
= 0; i
< (int) newkul_len
; i
++)
321 DEBUGMSGTL(("encode_keychange", "%02x", newkul
[i
]));
322 DEBUGMSGTL(("encode_keychange", "\n"));
324 rval
= encode_keychange(transform_type
, USM_LENGTH_OID_TRANSFORM
,
326 newkul
, newkul_len
, keychange
, &keychange_len
);
327 QUITFUN(rval
, main_quit
);
331 binary_to_hex(keychange
, keychange_len
, &s
);
332 printf("%s%s\n", (verbose
) ? "KeyChange string:\t" : "", /* XXX stdout */
340 snmp_call_callbacks(SNMP_CALLBACK_LIBRARY
, SNMP_CALLBACK_SHUTDOWN
,
344 SNMP_ZERO(oldpass
, strlen(oldpass
));
345 SNMP_ZERO(newpass
, strlen(newpass
));
347 SNMP_ZERO(oldKu
, oldKu_len
);
348 SNMP_ZERO(newKu
, newKu_len
);
350 SNMP_ZERO(oldkul
, oldkul_len
);
351 SNMP_ZERO(newkul
, newkul_len
);
353 SNMP_ZERO(s
, strlen(s
));
362 /*******************************************************************-o-******
365 usage_synopsis(FILE * ofp
)
367 fprintf(ofp
, USAGE
"\n\
369 -E [0x]<engineID> EngineID used for kul generation.\n\
370 -f Force passphrases to be read from stdin.\n\
372 -N \"<new_passphrase>\" Passphrase used to generate new Ku.\n\
373 -O \"<old_passphrase>\" Passphrase used to generate old Ku.\n\
374 -P Turn off prompt indicators.\n\
375 -t md5 | sha1 HMAC hash transform type.\n\
377 -V Visible. Echo passphrases to terminal.\n\
378 " NL
, local_progname
);
380 } /* end usage_synopsis() */
383 usage_to_file(FILE * ofp
)
390 Only -t is mandatory. The transform is used to convert P=>Ku, convert\n\
391 Ku=>Kul, and to hash the old Kul with the random bits.\n\
393 Passphrase will be taken from the first successful source as follows:\n\
394 a) Commandline options,\n\
395 b) The file \"%s/%s\",\n\
396 c) stdin -or- User input from the terminal.\n\
398 -f will require reading from the stdin/terminal, ignoring a) and b).\n\
399 -P will prevent prompts for passphrases to stdout from being printed.\n\
401 <engineID> is intepreted as a hex string when preceeded by \"0x\",\n\
402 otherwise it is created to contain \"text\". If nothing is given,\n\
403 <engineID> is constructed from the first IP address for the local host.\n\
404 " NL
, (s
= getenv("HOME")) ? s
: "$HOME", local_passphrase_filename
);
408 * FIX -- make this possible?
409 * -r [0x]<random_bits> Random bits used in KeyChange XOR.
411 * <engineID> and <random_bits> are intepreted as hex strings when
412 * preceeded by \"0x\", otherwise <engineID> is created to contain \"text\"
413 * and <random_bits> are the same as the ascii input.
415 * <random_bits> will be generated by SCAPI if not given. If value is
416 * too long, it will be truncated; if too short, the remainder will be
417 * filled in with zeros.
424 * this defined for HPUX aCC because the aCC doesn't drop the
427 * snmp_parse_args.c functionality if compile with -g, PKY
433 usage_to_file(stdout
);
440 /*******************************************************************-o-******
441 * get_user_passphrases
444 * SNMPERR_SUCCESS Success.
445 * SNMPERR_GENERR Otherwise.
448 * Acquire new and old passphrases from the user:
450 * + Always prompt if 'forcepassphrase' is set.
451 * + Use given arguments if they are defined.
452 * + Otherwise read file format from PASSPHRASE_FILE.
453 * Sanity check existence and permissions of the path.
454 * ASSUME for now that PASSPHRASE_FILE is rooted only at $HOME.
455 * + Otherwise prompt user for passphrase(s).
456 * Echo input if 'visible' is set.
457 * Turning off 'promptindicator' makes piping in input cleaner.
459 * NOTE Only using forcepassphrase mandates taking both passphrases
460 * from the same source. Otherwise processing continues until both
461 * passphrases are defined.
464 get_user_passphrases(void)
466 int rval
= SNMPERR_SUCCESS
;
469 char *obuf
= NULL
, *nbuf
= NULL
;
471 char path
[SNMP_MAXBUF
], buf
[SNMP_MAXBUF
], *s
= NULL
;
479 * Allow prompts to the user to override all other sources.
480 * Nothing to do otherwise if oldpass and newpass are already defined.
483 goto get_user_passphrases_prompt
;
484 if (oldpass
&& newpass
)
485 goto get_user_passphrases_quit
;
490 * Read passphrases out of PASSPHRASE_FILE. Sanity check the
491 * path for existence and access first. Refuse to read
492 * if the permissions are wrong.
495 snprintf(path
, sizeof(path
), "%s/%s", s
, PASSPHRASE_DIR
);
496 path
[ sizeof(path
)-1 ] = 0;
501 if (stat(path
, &statbuf
) < 0) {
502 fprintf(stderr
, "Cannot access directory \"%s\".\n", path
);
503 QUITFUN(rval
= SNMPERR_GENERR
, get_user_passphrases_quit
);
505 } else if (statbuf
.st_mode
& (S_IRWXG
| S_IRWXO
)) {
507 "Directory \"%s\" is accessible by group or world.\n",
509 QUITFUN(rval
= SNMPERR_GENERR
, get_user_passphrases_quit
);
516 snprintf(path
, sizeof(path
), "%s/%s", s
, local_passphrase_filename
);
517 path
[ sizeof(path
)-1 ] = 0;
518 if (stat(path
, &statbuf
) < 0) {
519 fprintf(stderr
, "Cannot access file \"%s\".\n", path
);
520 QUITFUN(rval
= SNMPERR_GENERR
, get_user_passphrases_quit
);
522 } else if (statbuf
.st_mode
& (S_IRWXG
| S_IRWXO
)) {
524 "File \"%s\" is accessible by group or world.\n", path
);
525 QUITFUN(rval
= SNMPERR_GENERR
, get_user_passphrases_quit
);
532 if ((fp
= fopen(path
, "r")) == NULL
) {
533 fprintf(stderr
, "Cannot open \"%s\".", path
);
534 QUITFUN(rval
= SNMPERR_GENERR
, get_user_passphrases_quit
);
540 if (!fgets(buf
, sizeof(buf
), fp
)) {
542 fprintf(stderr
, "Passphrase file \"%s\" is empty...\n", path
);
544 goto get_user_passphrases_prompt
;
546 } else if (!oldpass
) {
548 if (buf
[len
- 1] == '\n')
550 oldpass
= (char *) calloc(1, len
+ 1);
552 memcpy(oldpass
, buf
, len
+ 1);
557 if (!fgets(buf
, sizeof(buf
), fp
)) {
559 fprintf(stderr
, "Only one line in file \"%s\"...\n", path
);
562 } else if (!newpass
) {
564 if (buf
[len
- 1] == '\n')
566 newpass
= (char *) calloc(1, len
+ 1);
568 memcpy(newpass
, buf
, len
+ 1);
571 if (oldpass
&& newpass
)
572 goto get_user_passphrases_quit
;
577 * Prompt the user for passphrase entry. Visible prompts
578 * may be omitted, and invisible entry may turned off.
580 get_user_passphrases_prompt
:
581 if (forcepassphrase
) {
582 oldpass
= newpass
= NULL
;
587 = snmp_getpassphrase((promptindicator
) ? "Old passphrase: " :
592 = snmp_getpassphrase((promptindicator
) ? "New passphrase: " :
599 * Check that both passphrases were defined.
601 if (oldpass
&& newpass
) {
602 goto get_user_passphrases_quit
;
604 rval
= SNMPERR_GENERR
;
608 get_user_passphrases_quit
:
609 SNMP_ZERO(buf
, SNMP_MAXBUF
);
611 if (obuf
!= oldpass
) {
612 SNMP_ZERO(obuf
, strlen(obuf
));
615 if (nbuf
!= newpass
) {
616 SNMP_ZERO(nbuf
, strlen(nbuf
));
622 } /* end get_user_passphrases() */
624 /*******************************************************************-o-******
628 * fd Descriptor of terminal on which to toggle echoing.
629 * echo TRUE if echoing should be on; FALSE otherwise.
632 * Previous value of echo setting.
635 * FIX Put HAVE_TCGETATTR in autoconf?
638 #ifdef HAVE_TCGETATTR
641 snmp_ttyecho(const int fd
, const int echo
)
650 was_echo
= (tio
.c_lflag
& ECHO
) != 0;
652 tio
.c_lflag
|= (ECHO
| ECHONL
);
654 tio
.c_lflag
&= ~(ECHO
| ECHONL
);
655 tcsetattr(fd
, TCSANOW
, &tio
);
659 } /* end snmp_ttyecho() */
664 snmp_ttyecho(const int fd
, const int echo
)
666 struct sgttyb ttyparams
;
673 ioctl(fd
, TIOCGETP
, &ttyparams
);
674 was_echo
= (ttyparams
.sg_flags
& ECHO
) != 0;
676 ttyparams
.sg_flags
= ttyparams
.sg_flags
| ECHO
;
678 ttyparams
.sg_flags
= ttyparams
.sg_flags
& ~ECHO
;
679 ioctl(fd
, TIOCSETP
, &ttyparams
);
684 } /* end snmp_ttyecho() */
685 #endif /* HAVE_TCGETATTR */
686 #endif /* HAVE_GETPASS */
691 /*******************************************************************-o-******
695 * *prompt (May be NULL.)
696 * bvisible TRUE means echo back user input.
699 * Pointer to newly allocated, null terminated string containing
704 * Prompt stdin for a string (or passphrase). Return a copy of the
705 * input in a null terminated string.
707 * FIX Put HAVE_GETPASS in autoconf.
710 snmp_getpassphrase(const char *prompt
, int bvisible
)
716 static char buffer
[SNMP_MAXBUF
];
722 * Query stdin for a passphrase.
726 return getpass((prompt
) ? prompt
: "");
730 fputs((prompt
) ? prompt
: "", ofp
);
733 ti
= snmp_ttyecho(0, 0);
736 fgets(buffer
, sizeof(buffer
), stdin
);
739 ti
= snmp_ttyecho(0, ti
);
745 * Copy the input and zero out the read-in buffer.
747 len
= strlen(buffer
);
748 if (buffer
[len
- 1] == '\n')
749 buffer
[--len
] = '\0';
751 bufp
= (char *) calloc(1, len
+ 1);
753 memcpy(bufp
, buffer
, len
+ 1);
755 SNMP_ZERO(buffer
, SNMP_MAXBUF
);
760 } /* end snmp_getpassphrase() */
765 snmp_ttyecho(const int fd
, const int echo
)
771 * stops at the first newline, carrier return, or backspace.
772 * WARNING! _getch does NOT read <Ctrl-C>
775 getpass(const char *prompt
)
777 static char pbuf
[128];
781 for (ch
= 0, lim
= 0; ch
!= '\n' && lim
< sizeof(pbuf
)-1;) {
782 ch
= _getch(); /* look ma, no echo ! */
783 if (ch
== '\r' || ch
== '\n' || ch
== '\b')