2014-12-20 04:15:32 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
2017-08-03 15:05:37 +00:00
|
|
|
#include <sysexits.h>
|
2014-12-20 04:15:32 +00:00
|
|
|
|
2017-08-31 19:30:42 +00:00
|
|
|
#include "mpw-cli-util.h"
|
2014-12-20 19:30:34 +00:00
|
|
|
#include "mpw-algorithm.h"
|
|
|
|
#include "mpw-util.h"
|
2017-07-16 01:13:49 +00:00
|
|
|
#include "mpw-marshall.h"
|
2014-12-20 19:30:34 +00:00
|
|
|
|
2017-08-31 15:49:36 +00:00
|
|
|
/** Output the program's usage documentation. */
|
2014-12-20 04:15:32 +00:00
|
|
|
static void usage() {
|
|
|
|
|
2017-08-08 04:00:14 +00:00
|
|
|
inf( ""
|
|
|
|
" Master Password v%s\n"
|
2017-08-30 23:04:22 +00:00
|
|
|
"--------------------------------------------------------------------------------\n"
|
|
|
|
" https://masterpasswordapp.com\n\n", stringify_def( MP_VERSION ) );
|
2017-07-16 01:13:49 +00:00
|
|
|
inf( ""
|
2017-08-31 19:32:58 +00:00
|
|
|
"\nUSAGE\n\n"
|
2017-08-30 23:04:22 +00:00
|
|
|
" mpw [-u|-U full-name] [-m fd] [-t pw-type] [-P value] [-c counter]\n"
|
|
|
|
" [-a version] [-p purpose] [-C context] [-f|-F format] [-R 0|1]\n"
|
|
|
|
" [-v|-q] [-h] site-name\n\n" );
|
2017-07-16 01:13:49 +00:00
|
|
|
inf( ""
|
2017-08-08 00:27:08 +00:00
|
|
|
" -u full-name Specify the full name of the user.\n"
|
|
|
|
" -u checks the master password against the config,\n"
|
2017-08-07 22:57:10 +00:00
|
|
|
" -U allows updating to a new master password.\n"
|
2017-08-08 04:00:14 +00:00
|
|
|
" Defaults to %s in env or prompts.\n\n", MP_ENV_fullName );
|
2017-08-30 23:04:22 +00:00
|
|
|
dbg( ""
|
2017-08-22 15:37:18 +00:00
|
|
|
" -M master-pw Specify the master password of the user.\n"
|
2017-08-30 23:04:22 +00:00
|
|
|
" Passing secrets as arguments is unsafe, for use in testing only.\n" );
|
|
|
|
inf( ""
|
|
|
|
" -m fd Read the master password of the user from a file descriptor.\n"
|
|
|
|
" Tip: don't send extra characters like newlines such as by using\n"
|
|
|
|
" echo in a pipe. Consider printf instead.\n\n" );
|
2017-07-16 01:13:49 +00:00
|
|
|
inf( ""
|
2017-08-08 00:27:08 +00:00
|
|
|
" -t pw-type Specify the password's template.\n"
|
|
|
|
" Defaults to 'long' (-p a), 'name' (-p i) or 'phrase' (-p r).\n"
|
|
|
|
" x, maximum | 20 characters, contains symbols.\n"
|
|
|
|
" l, long | Copy-friendly, 14 characters, symbols.\n"
|
|
|
|
" m, medium | Copy-friendly, 8 characters, symbols.\n"
|
|
|
|
" b, basic | 8 characters, no symbols.\n"
|
|
|
|
" s, short | Copy-friendly, 4 characters, no symbols.\n"
|
|
|
|
" i, pin | 4 numbers.\n"
|
|
|
|
" n, name | 9 letter name.\n"
|
|
|
|
" p, phrase | 20 character sentence.\n"
|
2017-08-10 16:30:42 +00:00
|
|
|
" K, key | encryption key (set key size -s bits).\n"
|
|
|
|
" P, personal | saved personal password (save with -s pw).\n\n" );
|
2017-08-22 15:37:18 +00:00
|
|
|
inf( ""
|
|
|
|
" -P value The parameter value.\n"
|
|
|
|
" -p i | The login name for the site.\n"
|
2017-08-30 23:04:22 +00:00
|
|
|
" -t K | The bit size of the key to generate (eg. 256).\n"
|
2017-08-22 15:37:18 +00:00
|
|
|
" -t P | The personal password to encrypt.\n\n" );
|
2017-07-16 01:13:49 +00:00
|
|
|
inf( ""
|
2017-08-07 22:57:10 +00:00
|
|
|
" -c counter The value of the counter.\n"
|
|
|
|
" Defaults to 1.\n\n" );
|
2017-07-16 01:13:49 +00:00
|
|
|
inf( ""
|
2017-08-08 00:27:08 +00:00
|
|
|
" -a version The algorithm version to use, %d - %d.\n"
|
|
|
|
" Defaults to %s in env or %d.\n\n",
|
2017-08-08 04:00:14 +00:00
|
|
|
MPAlgorithmVersionFirst, MPAlgorithmVersionLast, MP_ENV_algorithm, MPAlgorithmVersionCurrent );
|
2017-07-16 01:13:49 +00:00
|
|
|
inf( ""
|
2017-08-07 22:57:10 +00:00
|
|
|
" -p purpose The purpose of the generated token.\n"
|
2017-08-08 00:27:08 +00:00
|
|
|
" Defaults to 'auth'.\n"
|
2017-08-07 22:57:10 +00:00
|
|
|
" a, auth | An authentication token such as a password.\n"
|
|
|
|
" i, ident | An identification token such as a username.\n"
|
|
|
|
" r, rec | A recovery token such as a security answer.\n\n" );
|
2017-07-16 01:13:49 +00:00
|
|
|
inf( ""
|
2017-08-07 22:57:10 +00:00
|
|
|
" -C context A purpose-specific context.\n"
|
|
|
|
" Defaults to empty.\n"
|
2017-08-08 00:27:08 +00:00
|
|
|
" -p a | -\n"
|
|
|
|
" -p i | -\n"
|
|
|
|
" -p r | Most significant word in security question.\n\n" );
|
2017-07-16 01:13:49 +00:00
|
|
|
inf( ""
|
2017-08-07 22:57:10 +00:00
|
|
|
" -f|F format The mpsites format to use for reading/writing site parameters.\n"
|
2017-08-08 00:27:08 +00:00
|
|
|
" -F forces the use of the given format,\n"
|
2017-08-07 22:57:10 +00:00
|
|
|
" -f allows fallback/migration.\n"
|
2017-08-13 01:57:47 +00:00
|
|
|
" Defaults to %s in env or json, falls back to plain.\n"
|
|
|
|
" n, none | No file\n"
|
2017-08-07 22:57:10 +00:00
|
|
|
" f, flat | ~/.mpw.d/Full Name.%s\n"
|
|
|
|
" j, json | ~/.mpw.d/Full Name.%s\n\n",
|
2017-08-13 01:57:47 +00:00
|
|
|
MP_ENV_format, mpw_marshall_format_extension( MPMarshallFormatFlat ), mpw_marshall_format_extension( MPMarshallFormatJSON ) );
|
2017-07-16 01:13:49 +00:00
|
|
|
inf( ""
|
2017-08-07 22:57:10 +00:00
|
|
|
" -R redacted Whether to save the mpsites in redacted format or not.\n"
|
|
|
|
" Defaults to 1, redacted.\n\n" );
|
2017-07-16 01:13:49 +00:00
|
|
|
inf( ""
|
2017-08-07 22:57:10 +00:00
|
|
|
" -v Increase output verbosity (can be repeated).\n"
|
|
|
|
" -q Decrease output verbosity (can be repeated).\n\n" );
|
|
|
|
inf( ""
|
2017-08-31 19:32:58 +00:00
|
|
|
"\nENVIRONMENT\n\n"
|
2017-08-30 23:04:22 +00:00
|
|
|
" %-12s The full name of the user (see -u).\n"
|
|
|
|
" %-12s The default algorithm version (see -a).\n"
|
2017-08-31 17:42:03 +00:00
|
|
|
" %-12s The default mpsites format (see -f).\n"
|
|
|
|
" %-12s The askpass program to use for prompting the user.\n\n",
|
|
|
|
MP_ENV_fullName, MP_ENV_algorithm, MP_ENV_format, MP_ENV_askpass );
|
2014-12-20 04:15:32 +00:00
|
|
|
exit( 0 );
|
|
|
|
}
|
|
|
|
|
2017-08-31 15:49:36 +00:00
|
|
|
/** ========================================================================
|
|
|
|
* MAIN */
|
2017-08-28 21:58:57 +00:00
|
|
|
int main(const int argc, char *const argv[]) {
|
2014-12-20 04:15:32 +00:00
|
|
|
|
2017-08-22 22:18:24 +00:00
|
|
|
// CLI defaults.
|
|
|
|
bool allowPasswordUpdate = false, sitesFormatFixed = false;
|
2017-07-16 01:13:49 +00:00
|
|
|
|
|
|
|
// Read the environment.
|
2017-08-30 23:04:22 +00:00
|
|
|
const char *fullNameArg = NULL, *masterPasswordFDArg = NULL, *masterPasswordArg = NULL, *siteNameArg = NULL;
|
2017-08-10 16:30:42 +00:00
|
|
|
const char *resultTypeArg = NULL, *resultParamArg = NULL, *siteCounterArg = NULL, *algorithmVersionArg = NULL;
|
2017-08-07 22:57:10 +00:00
|
|
|
const char *keyPurposeArg = NULL, *keyContextArg = NULL, *sitesFormatArg = NULL, *sitesRedactedArg = NULL;
|
2017-08-08 04:00:14 +00:00
|
|
|
fullNameArg = mpw_getenv( MP_ENV_fullName );
|
|
|
|
algorithmVersionArg = mpw_getenv( MP_ENV_algorithm );
|
2017-08-13 01:57:47 +00:00
|
|
|
sitesFormatArg = mpw_getenv( MP_ENV_format );
|
2014-12-20 04:15:32 +00:00
|
|
|
|
2017-07-16 01:13:49 +00:00
|
|
|
// Read the command-line options.
|
2017-08-30 23:04:22 +00:00
|
|
|
for (int opt; (opt = getopt( argc, argv, "u:U:m:M:t:P:c:a:p:C:f:F:R:vqh" )) != EOF;)
|
2014-12-20 04:15:32 +00:00
|
|
|
switch (opt) {
|
|
|
|
case 'u':
|
2017-08-08 00:27:08 +00:00
|
|
|
fullNameArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
2017-08-07 21:42:38 +00:00
|
|
|
allowPasswordUpdate = false;
|
|
|
|
break;
|
|
|
|
case 'U':
|
2017-08-08 00:27:08 +00:00
|
|
|
fullNameArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
2017-08-07 21:42:38 +00:00
|
|
|
allowPasswordUpdate = true;
|
2014-12-20 04:15:32 +00:00
|
|
|
break;
|
2017-08-30 23:04:22 +00:00
|
|
|
case 'm':
|
|
|
|
masterPasswordFDArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
|
|
|
break;
|
2017-08-10 16:30:42 +00:00
|
|
|
case 'M':
|
2017-07-16 01:13:49 +00:00
|
|
|
// Passing your master password via the command-line is insecure. Testing purposes only.
|
2017-08-08 00:27:08 +00:00
|
|
|
masterPasswordArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
2015-01-20 02:20:25 +00:00
|
|
|
break;
|
2014-12-20 04:15:32 +00:00
|
|
|
case 't':
|
2017-08-10 16:30:42 +00:00
|
|
|
resultTypeArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
|
|
|
break;
|
|
|
|
case 'P':
|
|
|
|
resultParamArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
2014-12-20 04:15:32 +00:00
|
|
|
break;
|
|
|
|
case 'c':
|
2017-08-08 00:27:08 +00:00
|
|
|
siteCounterArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
2014-12-20 04:15:32 +00:00
|
|
|
break;
|
2016-01-04 19:52:05 +00:00
|
|
|
case 'a':
|
2017-08-08 00:27:08 +00:00
|
|
|
algorithmVersionArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
|
|
|
break;
|
2017-08-06 03:19:24 +00:00
|
|
|
case 'p':
|
2017-08-08 00:27:08 +00:00
|
|
|
keyPurposeArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
2017-08-06 03:19:24 +00:00
|
|
|
break;
|
2014-12-20 04:15:32 +00:00
|
|
|
case 'C':
|
2017-08-08 00:27:08 +00:00
|
|
|
keyContextArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
2014-12-20 04:15:32 +00:00
|
|
|
break;
|
2017-08-06 03:19:24 +00:00
|
|
|
case 'f':
|
2017-08-08 00:27:08 +00:00
|
|
|
sitesFormatArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
2017-08-06 03:19:24 +00:00
|
|
|
sitesFormatFixed = false;
|
|
|
|
break;
|
|
|
|
case 'F':
|
2017-08-08 00:27:08 +00:00
|
|
|
sitesFormatArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
2017-08-06 03:19:24 +00:00
|
|
|
sitesFormatFixed = true;
|
|
|
|
break;
|
2017-08-07 22:57:10 +00:00
|
|
|
case 'R':
|
2017-08-08 00:27:08 +00:00
|
|
|
sitesRedactedArg = optarg && strlen( optarg )? strdup( optarg ): NULL;
|
2017-08-07 22:57:10 +00:00
|
|
|
break;
|
2016-01-04 19:52:05 +00:00
|
|
|
case 'v':
|
|
|
|
++mpw_verbosity;
|
|
|
|
break;
|
|
|
|
case 'q':
|
|
|
|
--mpw_verbosity;
|
|
|
|
break;
|
2014-12-20 04:15:32 +00:00
|
|
|
case 'h':
|
|
|
|
usage();
|
|
|
|
break;
|
|
|
|
case '?':
|
|
|
|
switch (optopt) {
|
|
|
|
case 'u':
|
2014-12-20 19:30:34 +00:00
|
|
|
ftl( "Missing full name to option: -%c\n", optopt );
|
2017-08-03 15:05:37 +00:00
|
|
|
return EX_USAGE;
|
2014-12-20 04:15:32 +00:00
|
|
|
case 't':
|
2014-12-20 19:30:34 +00:00
|
|
|
ftl( "Missing type name to option: -%c\n", optopt );
|
2017-08-03 15:05:37 +00:00
|
|
|
return EX_USAGE;
|
2014-12-20 04:15:32 +00:00
|
|
|
case 'c':
|
2014-12-20 19:30:34 +00:00
|
|
|
ftl( "Missing counter value to option: -%c\n", optopt );
|
2017-08-03 15:05:37 +00:00
|
|
|
return EX_USAGE;
|
2014-12-20 04:15:32 +00:00
|
|
|
default:
|
2014-12-20 19:30:34 +00:00
|
|
|
ftl( "Unknown option: -%c\n", optopt );
|
2017-08-03 15:05:37 +00:00
|
|
|
return EX_USAGE;
|
2014-12-20 04:15:32 +00:00
|
|
|
}
|
|
|
|
default:
|
2017-08-04 13:36:03 +00:00
|
|
|
ftl( "Unexpected option: %c\n", opt );
|
2017-08-03 15:05:37 +00:00
|
|
|
return EX_USAGE;
|
2014-12-20 04:15:32 +00:00
|
|
|
}
|
2017-08-23 04:01:23 +00:00
|
|
|
if (optind < argc && argv[optind])
|
2017-08-08 00:27:08 +00:00
|
|
|
siteNameArg = strdup( argv[optind] );
|
2017-07-16 01:13:49 +00:00
|
|
|
|
2017-07-23 01:38:53 +00:00
|
|
|
// Determine fullName, siteName & masterPassword.
|
2017-08-22 22:38:36 +00:00
|
|
|
const char *fullName = NULL, *masterPassword = NULL, *siteName = NULL;
|
2017-08-30 23:04:22 +00:00
|
|
|
if ((!fullName || !strlen( fullName )) && fullNameArg)
|
|
|
|
fullName = strdup( fullNameArg );
|
2017-08-31 15:49:36 +00:00
|
|
|
if (!fullName || !strlen( fullName ))
|
|
|
|
do {
|
|
|
|
fullName = mpw_getline( "Your full name:" );
|
|
|
|
} while (fullName && !strlen( fullName ));
|
|
|
|
if (!fullName || !strlen( fullName )) {
|
|
|
|
ftl( "Missing full name.\n" );
|
|
|
|
mpw_free_strings( &fullName, &masterPassword, &siteName, NULL );
|
|
|
|
return EX_DATAERR;
|
|
|
|
}
|
2017-08-30 23:04:22 +00:00
|
|
|
if ((!masterPassword || !strlen( masterPassword )) && masterPasswordFDArg) {
|
|
|
|
FILE *masterPasswordFile = fdopen( atoi( masterPasswordFDArg ), "r" );
|
|
|
|
if (!masterPasswordFile)
|
|
|
|
wrn( "Error opening master password FD %s: %s\n", masterPasswordFDArg, strerror( errno ) );
|
|
|
|
else {
|
|
|
|
masterPassword = mpw_read_file( masterPasswordFile );
|
|
|
|
if (ferror( masterPasswordFile ))
|
|
|
|
wrn( "Error reading master password from %s: %d\n", masterPasswordFDArg, ferror( masterPasswordFile ) );
|
|
|
|
}
|
2017-08-01 20:50:50 +00:00
|
|
|
}
|
2017-08-30 23:04:22 +00:00
|
|
|
if ((!masterPassword || !strlen( masterPassword )) && masterPasswordArg)
|
|
|
|
masterPassword = strdup( masterPasswordArg );
|
2017-08-31 15:49:36 +00:00
|
|
|
if (!masterPassword || !strlen( masterPassword ))
|
|
|
|
do {
|
|
|
|
masterPassword = mpw_getpass( "Your master password: " );
|
|
|
|
} while (masterPassword && !strlen( masterPassword ));
|
|
|
|
if (!masterPassword || !strlen( masterPassword )) {
|
|
|
|
ftl( "Missing master password.\n" );
|
|
|
|
mpw_free_strings( &fullName, &masterPassword, &siteName, NULL );
|
|
|
|
return EX_DATAERR;
|
|
|
|
}
|
2017-08-30 23:04:22 +00:00
|
|
|
if ((!siteName || !strlen( siteName )) && siteNameArg)
|
|
|
|
siteName = strdup( siteNameArg );
|
2017-08-31 15:49:36 +00:00
|
|
|
if (!siteName || !strlen( siteName ))
|
|
|
|
do {
|
|
|
|
siteName = mpw_getline( "Site name:" );
|
|
|
|
} while (siteName && !strlen( siteName ));
|
|
|
|
if (!siteName || !strlen( siteName )) {
|
|
|
|
ftl( "Missing site name.\n" );
|
|
|
|
mpw_free_strings( &fullName, &masterPassword, &siteName, NULL );
|
|
|
|
return EX_DATAERR;
|
|
|
|
}
|
2017-08-22 22:38:36 +00:00
|
|
|
MPMarshallFormat sitesFormat = MPMarshallFormatDefault;
|
2017-08-06 03:19:24 +00:00
|
|
|
if (sitesFormatArg) {
|
|
|
|
sitesFormat = mpw_formatWithName( sitesFormatArg );
|
2017-08-08 00:27:08 +00:00
|
|
|
if (ERR == (int)sitesFormat) {
|
2017-08-06 03:19:24 +00:00
|
|
|
ftl( "Invalid sites format: %s\n", sitesFormatArg );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_strings( &fullName, &masterPassword, &siteName, NULL );
|
2017-08-06 03:19:24 +00:00
|
|
|
return EX_USAGE;
|
|
|
|
}
|
|
|
|
}
|
2017-08-23 04:53:14 +00:00
|
|
|
MPKeyPurpose keyPurpose = MPKeyPurposeAuthentication;
|
|
|
|
if (keyPurposeArg) {
|
|
|
|
keyPurpose = mpw_purposeWithName( keyPurposeArg );
|
|
|
|
if (ERR == (int)keyPurpose) {
|
|
|
|
ftl( "Invalid purpose: %s\n", keyPurposeArg );
|
|
|
|
return EX_USAGE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const char *keyContext = NULL;
|
|
|
|
if (keyContextArg)
|
|
|
|
keyContext = strdup( keyContextArg );
|
2017-07-16 01:13:49 +00:00
|
|
|
|
2017-07-23 03:45:54 +00:00
|
|
|
// Find the user's sites file.
|
2017-08-06 03:19:24 +00:00
|
|
|
FILE *sitesFile = NULL;
|
2017-08-28 21:58:57 +00:00
|
|
|
const char *sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) );
|
2017-08-06 03:19:24 +00:00
|
|
|
if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" ))) {
|
|
|
|
dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) );
|
|
|
|
|
|
|
|
// Try to fall back to the flat format.
|
|
|
|
if (!sitesFormatFixed) {
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &sitesPath );
|
2017-08-13 01:57:47 +00:00
|
|
|
sitesPath = mpw_path( fullName, mpw_marshall_format_extension( MPMarshallFormatFlat ) );
|
|
|
|
if (sitesPath && (sitesFile = fopen( sitesPath, "r" )))
|
|
|
|
sitesFormat = MPMarshallFormatFlat;
|
|
|
|
else
|
2017-08-06 03:19:24 +00:00
|
|
|
dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) );
|
|
|
|
}
|
2017-07-23 03:45:54 +00:00
|
|
|
}
|
|
|
|
|
2017-08-22 22:18:24 +00:00
|
|
|
// Load the user object from file.
|
2017-08-06 03:19:24 +00:00
|
|
|
MPMarshalledUser *user = NULL;
|
|
|
|
MPMarshalledSite *site = NULL;
|
2017-08-22 22:18:24 +00:00
|
|
|
MPMarshalledQuestion *question = NULL;
|
2017-08-23 04:01:23 +00:00
|
|
|
if (!sitesFile)
|
|
|
|
mpw_free_string( &sitesPath );
|
|
|
|
|
2017-08-06 03:19:24 +00:00
|
|
|
else {
|
2017-07-23 03:45:54 +00:00
|
|
|
// Read file.
|
2017-08-30 23:04:22 +00:00
|
|
|
char *sitesInputData = mpw_read_file( sitesFile );
|
2017-08-06 03:19:24 +00:00
|
|
|
if (ferror( sitesFile ))
|
|
|
|
wrn( "Error while reading configuration file:\n %s: %d\n", sitesPath, ferror( sitesFile ) );
|
|
|
|
fclose( sitesFile );
|
2017-07-23 03:45:54 +00:00
|
|
|
|
|
|
|
// Parse file.
|
2017-08-13 12:50:16 +00:00
|
|
|
MPMarshallInfo *sitesInputInfo = mpw_marshall_read_info( sitesInputData );
|
|
|
|
MPMarshallFormat sitesInputFormat = sitesFormatArg? sitesFormat: sitesInputInfo->format;
|
2017-08-11 01:29:59 +00:00
|
|
|
MPMarshallError marshallError = { .type = MPMarshallSuccess };
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_marshal_info_free( &sitesInputInfo );
|
2017-08-13 01:57:47 +00:00
|
|
|
user = mpw_marshall_read( sitesInputData, sitesInputFormat, masterPassword, &marshallError );
|
2017-08-07 21:42:38 +00:00
|
|
|
if (marshallError.type == MPMarshallErrorMasterPassword) {
|
|
|
|
// Incorrect master password.
|
|
|
|
if (!allowPasswordUpdate) {
|
2017-08-06 03:19:24 +00:00
|
|
|
ftl( "Incorrect master password according to configuration:\n %s: %s\n", sitesPath, marshallError.description );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_marshal_free( &user );
|
2017-08-30 23:04:22 +00:00
|
|
|
mpw_free_strings( &sitesInputData, &sitesPath, &fullName, &masterPassword, &siteName, NULL );
|
2017-08-03 15:05:37 +00:00
|
|
|
return EX_DATAERR;
|
2017-08-04 13:36:03 +00:00
|
|
|
}
|
2017-08-07 21:42:38 +00:00
|
|
|
|
|
|
|
// Update user's master password.
|
|
|
|
while (marshallError.type == MPMarshallErrorMasterPassword) {
|
|
|
|
inf( "Given master password does not match configuration.\n" );
|
|
|
|
inf( "To update the configuration with this new master password, first confirm the old master password.\n" );
|
|
|
|
|
2017-08-08 00:27:08 +00:00
|
|
|
const char *importMasterPassword = NULL;
|
2017-08-07 21:42:38 +00:00
|
|
|
while (!importMasterPassword || !strlen( importMasterPassword ))
|
|
|
|
importMasterPassword = mpw_getpass( "Old master password: " );
|
|
|
|
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_marshal_free( &user );
|
2017-08-13 01:57:47 +00:00
|
|
|
user = mpw_marshall_read( sitesInputData, sitesInputFormat, importMasterPassword, &marshallError );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &importMasterPassword );
|
2017-08-07 21:42:38 +00:00
|
|
|
}
|
|
|
|
if (user) {
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &user->masterPassword );
|
2017-08-07 21:42:38 +00:00
|
|
|
user->masterPassword = strdup( masterPassword );
|
|
|
|
}
|
|
|
|
}
|
2017-08-30 23:04:22 +00:00
|
|
|
mpw_free_string( &sitesInputData );
|
|
|
|
|
2017-08-07 21:42:38 +00:00
|
|
|
if (!user || marshallError.type != MPMarshallSuccess) {
|
|
|
|
err( "Couldn't parse configuration file:\n %s: %s\n", sitesPath, marshallError.description );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_marshal_free( &user );
|
|
|
|
mpw_free_string( &sitesPath );
|
2017-08-03 05:13:15 +00:00
|
|
|
}
|
2017-07-16 01:13:49 +00:00
|
|
|
}
|
2017-08-22 22:18:24 +00:00
|
|
|
if (!user)
|
|
|
|
user = mpw_marshall_user( fullName, masterPassword, MPAlgorithmVersionCurrent );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_strings( &fullName, &masterPassword, NULL );
|
2017-07-16 01:13:49 +00:00
|
|
|
|
2017-08-22 22:18:24 +00:00
|
|
|
// Load the site object.
|
|
|
|
for (size_t s = 0; s < user->sites_count; ++s) {
|
|
|
|
site = &user->sites[s];
|
2017-08-23 04:53:14 +00:00
|
|
|
if (strcmp( siteName, site->name ) == 0)
|
|
|
|
// Found.
|
|
|
|
break;
|
|
|
|
site = NULL;
|
2017-08-06 03:19:24 +00:00
|
|
|
}
|
2017-08-22 22:18:24 +00:00
|
|
|
if (!site)
|
|
|
|
site = mpw_marshall_site( user, siteName, MPResultTypeDefault, MPCounterValueDefault, user->algorithm );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &siteName );
|
2017-08-22 22:18:24 +00:00
|
|
|
|
2017-08-23 04:53:14 +00:00
|
|
|
// Load the question object.
|
|
|
|
switch (keyPurpose) {
|
|
|
|
case MPKeyPurposeAuthentication:
|
|
|
|
case MPKeyPurposeIdentification:
|
|
|
|
break;
|
|
|
|
case MPKeyPurposeRecovery:
|
|
|
|
for (size_t q = 0; q < site->questions_count; ++q) {
|
|
|
|
question = &site->questions[q];
|
|
|
|
if ((!keyContext && !strlen( question->keyword )) ||
|
|
|
|
(keyContext && strcmp( keyContext, question->keyword ) != 0))
|
|
|
|
// Found.
|
2017-08-22 22:18:24 +00:00
|
|
|
break;
|
2017-08-23 04:53:14 +00:00
|
|
|
question = NULL;
|
|
|
|
}
|
|
|
|
if (!question)
|
|
|
|
question = mpw_marshal_question( site, keyContext );
|
|
|
|
break;
|
2017-08-22 22:18:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize purpose-specific operation parameters.
|
|
|
|
MPResultType resultType = MPResultTypeDefault;
|
|
|
|
MPCounterValue siteCounter = MPCounterValueDefault;
|
|
|
|
const char *purposeResult = NULL, *resultState = NULL;
|
2017-08-06 03:19:24 +00:00
|
|
|
switch (keyPurpose) {
|
2017-08-22 22:18:24 +00:00
|
|
|
case MPKeyPurposeAuthentication: {
|
|
|
|
purposeResult = "password";
|
|
|
|
resultType = site->type;
|
2017-08-23 04:01:23 +00:00
|
|
|
resultState = site->content? strdup( site->content ): NULL;
|
2017-08-22 22:18:24 +00:00
|
|
|
siteCounter = site->counter;
|
2017-08-06 03:19:24 +00:00
|
|
|
break;
|
2017-08-22 22:18:24 +00:00
|
|
|
}
|
2017-08-06 03:19:24 +00:00
|
|
|
case MPKeyPurposeIdentification: {
|
|
|
|
purposeResult = "login";
|
2017-08-22 22:18:24 +00:00
|
|
|
resultType = site->loginType;
|
2017-08-23 04:01:23 +00:00
|
|
|
resultState = site->loginContent? strdup( site->loginContent ): NULL;
|
2017-08-22 22:18:24 +00:00
|
|
|
siteCounter = MPCounterValueInitial;
|
2017-08-06 03:19:24 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MPKeyPurposeRecovery: {
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &keyContext );
|
2017-08-06 03:19:24 +00:00
|
|
|
purposeResult = "answer";
|
2017-08-23 04:01:23 +00:00
|
|
|
keyContext = question->keyword? strdup( question->keyword ): NULL;
|
2017-08-22 22:18:24 +00:00
|
|
|
resultType = question->type;
|
2017-08-23 04:01:23 +00:00
|
|
|
resultState = question->content? strdup( question->content ): NULL;
|
2017-08-22 22:18:24 +00:00
|
|
|
siteCounter = MPCounterValueInitial;
|
2017-08-06 03:19:24 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-08-22 22:18:24 +00:00
|
|
|
|
|
|
|
// Override operation parameters from command-line arguments.
|
2017-08-10 16:30:42 +00:00
|
|
|
if (resultTypeArg) {
|
|
|
|
resultType = mpw_typeWithName( resultTypeArg );
|
|
|
|
if (ERR == (int)resultType) {
|
|
|
|
ftl( "Invalid type: %s\n", resultTypeArg );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_marshal_free( &user );
|
|
|
|
mpw_free_strings( &sitesPath, &resultState, &keyContext, NULL );
|
2017-08-04 13:36:03 +00:00
|
|
|
return EX_USAGE;
|
|
|
|
}
|
2017-08-22 22:18:24 +00:00
|
|
|
|
|
|
|
if (!(resultType & MPSiteFeatureAlternative)) {
|
|
|
|
switch (keyPurpose) {
|
|
|
|
case MPKeyPurposeAuthentication:
|
|
|
|
site->type = resultType;
|
|
|
|
break;
|
|
|
|
case MPKeyPurposeIdentification:
|
|
|
|
site->loginType = resultType;
|
|
|
|
break;
|
|
|
|
case MPKeyPurposeRecovery:
|
|
|
|
question->type = resultType;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-08-04 13:36:03 +00:00
|
|
|
}
|
2017-08-22 22:18:24 +00:00
|
|
|
if (siteCounterArg) {
|
|
|
|
long long int siteCounterInt = atoll( siteCounterArg );
|
|
|
|
if (siteCounterInt < MPCounterValueFirst || siteCounterInt > MPCounterValueLast) {
|
|
|
|
ftl( "Invalid site counter: %s\n", siteCounterArg );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_marshal_free( &user );
|
|
|
|
mpw_free_strings( &sitesPath, &resultState, &keyContext, NULL );
|
2017-08-22 22:18:24 +00:00
|
|
|
return EX_USAGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (keyPurpose) {
|
|
|
|
case MPKeyPurposeAuthentication:
|
|
|
|
siteCounter = site->counter = (MPCounterValue)siteCounterInt;
|
|
|
|
break;
|
|
|
|
case MPKeyPurposeIdentification:
|
|
|
|
case MPKeyPurposeRecovery:
|
|
|
|
// NOTE: counter for login & question is not persisted.
|
|
|
|
break;
|
|
|
|
}
|
2017-08-10 16:30:42 +00:00
|
|
|
}
|
2017-08-22 22:18:24 +00:00
|
|
|
const char *resultParam = NULL;
|
|
|
|
if (resultParamArg)
|
|
|
|
resultParam = strdup( resultParamArg );
|
|
|
|
if (algorithmVersionArg) {
|
|
|
|
int algorithmVersionInt = atoi( algorithmVersionArg );
|
|
|
|
if (algorithmVersionInt < MPAlgorithmVersionFirst || algorithmVersionInt > MPAlgorithmVersionLast) {
|
|
|
|
ftl( "Invalid algorithm version: %s\n", algorithmVersionArg );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_marshal_free( &user );
|
|
|
|
mpw_free_strings( &sitesPath, &resultState, &keyContext, &resultParam, NULL );
|
2017-08-22 22:18:24 +00:00
|
|
|
return EX_USAGE;
|
|
|
|
}
|
|
|
|
site->algorithm = (MPAlgorithmVersion)algorithmVersionInt;
|
2017-08-08 00:27:08 +00:00
|
|
|
}
|
2017-08-22 22:18:24 +00:00
|
|
|
if (sitesRedactedArg)
|
|
|
|
user->redacted = strcmp( sitesRedactedArg, "1" ) == 0;
|
|
|
|
else if (!user->redacted)
|
|
|
|
wrn( "Sites configuration is not redacted. Use -R 1 to change this.\n" );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_strings( &fullNameArg, &masterPasswordArg, &siteNameArg, NULL );
|
|
|
|
mpw_free_strings( &resultTypeArg, &resultParamArg, &siteCounterArg, &algorithmVersionArg, NULL );
|
|
|
|
mpw_free_strings( &keyPurposeArg, &keyContextArg, &sitesFormatArg, &sitesRedactedArg, NULL );
|
2014-12-20 04:15:32 +00:00
|
|
|
|
2017-08-06 03:19:24 +00:00
|
|
|
// Operation summary.
|
2017-08-22 22:18:24 +00:00
|
|
|
const char *identicon = mpw_identicon( user->fullName, user->masterPassword );
|
2017-07-16 01:13:49 +00:00
|
|
|
if (!identicon)
|
|
|
|
wrn( "Couldn't determine identicon.\n" );
|
|
|
|
dbg( "-----------------\n" );
|
2017-08-22 22:18:24 +00:00
|
|
|
dbg( "fullName : %s\n", user->fullName );
|
|
|
|
trc( "masterPassword : %s\n", user->masterPassword );
|
2017-07-16 01:13:49 +00:00
|
|
|
dbg( "identicon : %s\n", identicon );
|
2017-08-06 03:19:24 +00:00
|
|
|
dbg( "sitesFormat : %s%s\n", mpw_nameForFormat( sitesFormat ), sitesFormatFixed? " (fixed)": "" );
|
|
|
|
dbg( "sitesPath : %s\n", sitesPath );
|
2017-08-22 22:18:24 +00:00
|
|
|
dbg( "siteName : %s\n", site->name );
|
2017-07-16 01:13:49 +00:00
|
|
|
dbg( "siteCounter : %u\n", siteCounter );
|
2017-08-10 16:30:42 +00:00
|
|
|
dbg( "resultType : %s (%u)\n", mpw_nameForType( resultType ), resultType );
|
|
|
|
dbg( "resultParam : %s\n", resultParam );
|
2017-08-01 18:34:15 +00:00
|
|
|
dbg( "keyPurpose : %s (%u)\n", mpw_nameForPurpose( keyPurpose ), keyPurpose );
|
|
|
|
dbg( "keyContext : %s\n", keyContext );
|
2017-08-22 22:18:24 +00:00
|
|
|
dbg( "algorithmVersion : %u\n", site->algorithm );
|
2017-07-16 01:13:49 +00:00
|
|
|
dbg( "-----------------\n\n" );
|
2017-08-22 22:18:24 +00:00
|
|
|
inf( "%s's %s for %s:\n[ %s ]: ", user->fullName, purposeResult, site->name, identicon );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_strings( &identicon, &sitesPath, NULL );
|
2014-12-20 04:15:32 +00:00
|
|
|
|
2017-08-06 03:19:24 +00:00
|
|
|
// Determine master key.
|
2017-08-01 12:31:39 +00:00
|
|
|
MPMasterKey masterKey = mpw_masterKey(
|
2017-08-22 22:18:24 +00:00
|
|
|
user->fullName, user->masterPassword, site->algorithm );
|
2017-08-01 20:50:50 +00:00
|
|
|
if (!masterKey) {
|
2017-08-04 13:36:03 +00:00
|
|
|
ftl( "Couldn't derive master key.\n" );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_marshal_free( &user );
|
|
|
|
mpw_free_strings( &resultState, &keyContext, &resultParam, NULL );
|
2017-08-03 15:05:37 +00:00
|
|
|
return EX_SOFTWARE;
|
2017-08-01 20:50:50 +00:00
|
|
|
}
|
2014-12-20 19:30:34 +00:00
|
|
|
|
2017-08-22 22:18:24 +00:00
|
|
|
// Update state.
|
|
|
|
if (resultParam && resultType & MPResultTypeClassStateful) {
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &resultState );
|
2017-08-22 22:18:24 +00:00
|
|
|
if (!(resultState = mpw_siteState( masterKey, site->name, siteCounter,
|
|
|
|
keyPurpose, keyContext, resultType, resultParam, site->algorithm ))) {
|
|
|
|
ftl( "Couldn't encrypt site result.\n" );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free( &masterKey, MPMasterKeySize );
|
|
|
|
mpw_marshal_free( &user );
|
|
|
|
mpw_free_strings( &resultState, &keyContext, &resultParam, NULL );
|
2017-08-22 22:18:24 +00:00
|
|
|
return EX_SOFTWARE;
|
2017-08-22 15:37:18 +00:00
|
|
|
}
|
2017-08-22 22:18:24 +00:00
|
|
|
inf( "(state) %s => ", resultState );
|
2017-08-22 15:37:18 +00:00
|
|
|
|
2017-08-22 22:18:24 +00:00
|
|
|
switch (keyPurpose) {
|
|
|
|
case MPKeyPurposeAuthentication: {
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &site->content );
|
|
|
|
site->content = strdup( resultState );
|
2017-08-22 22:18:24 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MPKeyPurposeIdentification: {
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &site->loginContent );
|
|
|
|
site->loginContent = strdup( resultState );
|
2017-08-22 22:18:24 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-08-07 21:42:38 +00:00
|
|
|
|
2017-08-22 22:18:24 +00:00
|
|
|
case MPKeyPurposeRecovery: {
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &question->content );
|
|
|
|
question->content = strdup( resultState );
|
2017-08-22 22:18:24 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-08-06 03:19:24 +00:00
|
|
|
}
|
|
|
|
|
2017-08-22 22:18:24 +00:00
|
|
|
// resultParam is consumed.
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &resultParam );
|
2017-08-06 03:19:24 +00:00
|
|
|
}
|
2017-08-08 00:27:08 +00:00
|
|
|
|
2017-08-22 22:18:24 +00:00
|
|
|
// Second phase resultParam defaults to state.
|
|
|
|
if (!resultParam && resultState)
|
|
|
|
resultParam = strdup( resultState );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &resultState );
|
2017-08-22 22:18:24 +00:00
|
|
|
|
|
|
|
// Generate result.
|
|
|
|
const char *result = mpw_siteResult( masterKey, site->name, siteCounter,
|
|
|
|
keyPurpose, keyContext, resultType, resultParam, site->algorithm );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free( &masterKey, MPMasterKeySize );
|
|
|
|
mpw_free_strings( &keyContext, &resultParam, NULL );
|
2017-08-22 22:18:24 +00:00
|
|
|
if (!result) {
|
|
|
|
ftl( "Couldn't generate site result.\n" );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_marshal_free( &user );
|
2017-08-22 22:18:24 +00:00
|
|
|
return EX_SOFTWARE;
|
2017-08-06 03:19:24 +00:00
|
|
|
}
|
2017-08-22 22:18:24 +00:00
|
|
|
fprintf( stdout, "%s\n", result );
|
|
|
|
if (site->url)
|
2017-08-07 22:57:10 +00:00
|
|
|
inf( "See: %s\n", site->url );
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &result );
|
2014-12-20 04:15:32 +00:00
|
|
|
|
2017-08-22 22:18:24 +00:00
|
|
|
// Update usage metadata.
|
|
|
|
site->lastUsed = user->lastUsed = time( NULL );
|
|
|
|
site->uses++;
|
2017-08-06 03:19:24 +00:00
|
|
|
|
2017-08-22 22:18:24 +00:00
|
|
|
// Update the mpsites file.
|
|
|
|
if (sitesFormat != MPMarshallFormatNone) {
|
2017-08-06 03:19:24 +00:00
|
|
|
if (!sitesFormatFixed)
|
|
|
|
sitesFormat = MPMarshallFormatDefault;
|
|
|
|
sitesPath = mpw_path( user->fullName, mpw_marshall_format_extension( sitesFormat ) );
|
2017-08-22 22:18:24 +00:00
|
|
|
|
2017-08-06 03:19:24 +00:00
|
|
|
dbg( "Updating: %s (%s)\n", sitesPath, mpw_nameForFormat( sitesFormat ) );
|
2017-08-28 21:58:57 +00:00
|
|
|
if (!sitesPath || !mpw_mkdirs( sitesPath ) || !(sitesFile = fopen( sitesPath, "w" )))
|
2017-08-06 03:19:24 +00:00
|
|
|
wrn( "Couldn't create updated configuration file:\n %s: %s\n", sitesPath, strerror( errno ) );
|
|
|
|
|
|
|
|
else {
|
|
|
|
char *buf = NULL;
|
2017-08-11 01:29:59 +00:00
|
|
|
MPMarshallError marshallError = { .type = MPMarshallSuccess };
|
2017-08-06 03:19:24 +00:00
|
|
|
if (!mpw_marshall_write( &buf, sitesFormat, user, &marshallError ) || marshallError.type != MPMarshallSuccess)
|
|
|
|
wrn( "Couldn't encode updated configuration file:\n %s: %s\n", sitesPath, marshallError.description );
|
|
|
|
|
|
|
|
else if (fwrite( buf, sizeof( char ), strlen( buf ), sitesFile ) != strlen( buf ))
|
|
|
|
wrn( "Error while writing updated configuration file:\n %s: %d\n", sitesPath, ferror( sitesFile ) );
|
|
|
|
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &buf );
|
2017-08-06 03:19:24 +00:00
|
|
|
fclose( sitesFile );
|
|
|
|
}
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_free_string( &sitesPath );
|
2017-08-06 03:19:24 +00:00
|
|
|
}
|
2017-08-23 04:01:23 +00:00
|
|
|
mpw_marshal_free( &user );
|
2016-11-03 14:31:07 +00:00
|
|
|
|
2014-12-20 04:15:32 +00:00
|
|
|
return 0;
|
|
|
|
}
|