diff --git a/core/c/mpw-marshall.c b/core/c/mpw-marshall.c index 170274a5..38e36801 100644 --- a/core/c/mpw-marshall.c +++ b/core/c/mpw-marshall.c @@ -19,6 +19,7 @@ #include #include +#include #include "mpw-marshall.h" #include "mpw-util.h" @@ -32,7 +33,7 @@ MPMarshalledUser *mpw_marshall_user( return NULL; *user = (MPMarshalledUser){ - .name = strdup( fullName ), + .fullName = strdup( fullName ), .masterPassword = strdup( masterPassword ), .algorithm = algorithmVersion, .redacted = true, @@ -93,16 +94,16 @@ bool mpw_marshal_free( bool success = true; for (size_t s = 0; s < marshalledUser->sites_count; ++s) { - MPMarshalledSite site = marshalledUser->sites[s]; - success &= mpw_free_string( site.name ); - for (size_t q = 0; q < site.questions_count; ++q) { - MPMarshalledQuestion question = site.questions[q]; - success &= mpw_free_string( question.keyword ); + MPMarshalledSite *site = &marshalledUser->sites[s]; + success &= mpw_free_string( site->name ); + for (size_t q = 0; q < site->questions_count; ++q) { + MPMarshalledQuestion *question = &site->questions[q]; + success &= mpw_free_string( question->keyword ); } - success &= mpw_free( site.questions, sizeof( MPMarshalledQuestion ) * site.questions_count ); + success &= mpw_free( site->questions, sizeof( MPMarshalledQuestion ) * site->questions_count ); } success &= mpw_free( marshalledUser->sites, sizeof( MPMarshalledSite ) * marshalledUser->sites_count ); - success &= mpw_free_string( marshalledUser->name ); + success &= mpw_free_string( marshalledUser->fullName ); success &= mpw_free_string( marshalledUser->masterPassword ); success &= mpw_free( marshalledUser, sizeof( MPMarshalledUser ) ); @@ -113,7 +114,7 @@ static bool mpw_marshall_write_flat( char **out, const MPMarshalledUser *user, MPMarshallError *error) { *error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." }; - if (!user->name || !strlen( user->name )) { + if (!user->fullName || !strlen( user->fullName )) { *error = (MPMarshallError){ MPMarshallErrorMissing, "Missing full name." }; return false; } @@ -123,7 +124,7 @@ static bool mpw_marshall_write_flat( } MPMasterKey masterKey = NULL; MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1; - if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->name, user->masterPassword )) { + if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) { *error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; return false; } @@ -141,8 +142,8 @@ static bool mpw_marshall_write_flat( time_t now = time( NULL ); if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &now ) )) mpw_string_pushf( out, "# Date: %s\n", dateString ); - mpw_string_pushf( out, "# User Name: %s\n", user->name ); - mpw_string_pushf( out, "# Full Name: %s\n", user->name ); + mpw_string_pushf( out, "# User Name: %s\n", user->fullName ); + mpw_string_pushf( out, "# Full Name: %s\n", user->fullName ); mpw_string_pushf( out, "# Avatar: %u\n", user->avatar ); mpw_string_pushf( out, "# Key ID: %s\n", mpw_id_buf( masterKey, MPMasterKeySize ) ); mpw_string_pushf( out, "# Algorithm: %d\n", user->algorithm ); @@ -155,34 +156,34 @@ static bool mpw_marshall_write_flat( // Sites. for (size_t s = 0; s < user->sites_count; ++s) { - MPMarshalledSite site = user->sites[s]; - if (!site.name || !strlen( site.name )) + MPMarshalledSite *site = &user->sites[s]; + if (!site->name || !strlen( site->name )) continue; const char *content = NULL; if (!user->redacted) { // Clear Text - if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site.algorithm, user->name, user->masterPassword )) { + if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) { *error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; return false; } - if (site.type & MPPasswordTypeClassGenerated) { - MPSiteKey siteKey = mpw_siteKey( masterKey, site.name, site.counter, MPKeyPurposeAuthentication, NULL, site.algorithm ); - content = mpw_sitePassword( siteKey, site.type, site.algorithm ); + if (site->type & MPPasswordTypeClassGenerated) { + MPSiteKey siteKey = mpw_siteKey( masterKey, site->name, site->counter, MPKeyPurposeAuthentication, NULL, site->algorithm ); + content = mpw_sitePassword( siteKey, site->type, site->algorithm ); mpw_free( siteKey, MPSiteKeySize ); } - else if (site.type & MPSiteFeatureExportContent && site.content && strlen( site.content )) - content = mpw_decrypt( masterKey, site.content, site.algorithm ); + else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) + content = mpw_decrypt( masterKey, site->content, site->algorithm ); } - else if (site.type & MPSiteFeatureExportContent && site.content && strlen( site.content )) + else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) // Redacted - content = strdup( site.content ); + content = strdup( site->content ); - if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site.lastUsed ) )) + if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) )) mpw_string_pushf( out, "%s %8ld %lu:%lu:%lu %25s\t%25s\t%s\n", - dateString, (long)site.uses, (long)site.type, (long)site.algorithm, (long)site.counter, - site.loginName?: "", site.name, content?: "" ); + dateString, (long)site->uses, (long)site->type, (long)site->algorithm, (long)site->counter, + site->loginName?: "", site->name, content?: "" ); mpw_free_string( content ); } mpw_free( masterKey, MPMasterKeySize ); @@ -195,7 +196,7 @@ static bool mpw_marshall_write_json( char **out, const MPMarshalledUser *user, MPMarshallError *error) { *error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." }; - if (!user->name || !strlen( user->name )) { + if (!user->fullName || !strlen( user->fullName )) { *error = (MPMarshallError){ MPMarshallErrorMissing, "Missing full name." }; return false; } @@ -205,7 +206,7 @@ static bool mpw_marshall_write_json( } MPMasterKey masterKey = NULL; MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1; - if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->name, user->masterPassword )) { + if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) { *error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; return false; } @@ -226,7 +227,7 @@ static bool mpw_marshall_write_json( json_object *json_user = json_object_new_object(); json_object_object_add( json_file, "user", json_user ); json_object_object_add( json_user, "avatar", json_object_new_int( (int)user->avatar ) ); - json_object_object_add( json_user, "full_name", json_object_new_string( user->name ) ); + json_object_object_add( json_user, "full_name", json_object_new_string( user->fullName ) ); if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &user->lastUsed ) )) json_object_object_add( json_user, "last_used", json_object_new_string( dateString ) ); @@ -239,59 +240,59 @@ static bool mpw_marshall_write_json( json_object *json_sites = json_object_new_object(); json_object_object_add( json_file, "sites", json_sites ); for (size_t s = 0; s < user->sites_count; ++s) { - MPMarshalledSite site = user->sites[s]; - if (!site.name || !strlen( site.name )) + MPMarshalledSite *site = &user->sites[s]; + if (!site->name || !strlen( site->name )) continue; const char *content = NULL; if (!user->redacted) { // Clear Text - if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site.algorithm, user->name, user->masterPassword )) { + if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) { *error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; return false; } - if (site.type & MPPasswordTypeClassGenerated) { - MPSiteKey siteKey = mpw_siteKey( masterKey, site.name, site.counter, MPKeyPurposeAuthentication, NULL, site.algorithm ); - content = mpw_sitePassword( siteKey, site.type, site.algorithm ); + if (site->type & MPPasswordTypeClassGenerated) { + MPSiteKey siteKey = mpw_siteKey( masterKey, site->name, site->counter, MPKeyPurposeAuthentication, NULL, site->algorithm ); + content = mpw_sitePassword( siteKey, site->type, site->algorithm ); mpw_free( siteKey, MPSiteKeySize ); } - else if (site.type & MPSiteFeatureExportContent && site.content && strlen( site.content )) - content = mpw_decrypt( masterKey, site.content, site.algorithm ); + else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) + content = mpw_decrypt( masterKey, site->content, site->algorithm ); } - else if (site.type & MPSiteFeatureExportContent && site.content && strlen( site.content )) + else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) // Redacted - content = strdup( site.content ); + content = strdup( site->content ); json_object *json_site = json_object_new_object(); - json_object_object_add( json_sites, site.name, json_site ); - json_object_object_add( json_site, "type", json_object_new_int( (int)site.type ) ); - json_object_object_add( json_site, "counter", json_object_new_int( (int)site.counter ) ); - json_object_object_add( json_site, "algorithm", json_object_new_int( (int)site.algorithm ) ); + json_object_object_add( json_sites, site->name, json_site ); + json_object_object_add( json_site, "type", json_object_new_int( (int)site->type ) ); + json_object_object_add( json_site, "counter", json_object_new_int( (int)site->counter ) ); + json_object_object_add( json_site, "algorithm", json_object_new_int( (int)site->algorithm ) ); if (content) json_object_object_add( json_site, "password", json_object_new_string( content ) ); - if (site.loginName) - json_object_object_add( json_site, "login_name", json_object_new_string( site.loginName ) ); - json_object_object_add( json_site, "login_generated", json_object_new_boolean( site.loginGenerated ) ); + if (site->loginName) + json_object_object_add( json_site, "login_name", json_object_new_string( site->loginName ) ); + json_object_object_add( json_site, "login_generated", json_object_new_boolean( site->loginGenerated ) ); - json_object_object_add( json_site, "uses", json_object_new_int( (int)site.uses ) ); - if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site.lastUsed ) )) + json_object_object_add( json_site, "uses", json_object_new_int( (int)site->uses ) ); + if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) )) json_object_object_add( json_site, "last_used", json_object_new_string( dateString ) ); json_object *json_site_questions = json_object_new_object(); json_object_object_add( json_site, "questions", json_site_questions ); - for (size_t q = 0; q < site.questions_count; ++q) { - MPMarshalledQuestion question = site.questions[q]; - if (!question.keyword) + for (size_t q = 0; q < site->questions_count; ++q) { + MPMarshalledQuestion *question = &site->questions[q]; + if (!question->keyword) continue; json_object *json_site_question = json_object_new_object(); - json_object_object_add( json_site_questions, question.keyword, json_site_question ); + json_object_object_add( json_site_questions, question->keyword, json_site_question ); if (!user->redacted) { // Clear Text - MPSiteKey siteKey = mpw_siteKey( masterKey, site.name, 1, MPKeyPurposeRecovery, question.keyword, site.algorithm ); - const char *answer = mpw_sitePassword( siteKey, MPPasswordTypeGeneratedPhrase, site.algorithm ); + MPSiteKey siteKey = mpw_siteKey( masterKey, site->name, 1, MPKeyPurposeRecovery, question->keyword, site->algorithm ); + const char *answer = mpw_sitePassword( siteKey, MPPasswordTypeGeneratedPhrase, site->algorithm ); mpw_free( siteKey, MPSiteKeySize ); if (answer) json_object_object_add( json_site_question, "answer", json_object_new_string( answer ) ); @@ -300,8 +301,8 @@ static bool mpw_marshall_write_json( json_object *json_site_mpw = json_object_new_object(); json_object_object_add( json_site, "_ext_mpw", json_site_mpw ); - if (site.url) - json_object_object_add( json_site_mpw, "url", json_object_new_string( site.url ) ); + if (site->url) + json_object_object_add( json_site_mpw, "url", json_object_new_string( site->url ) ); mpw_free_string( content ); } @@ -699,3 +700,52 @@ MPMarshalledUser *mpw_marshall_read( *error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported input format: %u", inFormat ) }; return NULL; } + +const MPMarshallFormat mpw_formatWithName( + const char *formatName) { + + // Lower-case to standardize it. + size_t stdFormatNameSize = strlen( formatName ); + char stdFormatName[stdFormatNameSize + 1]; + for (size_t c = 0; c < stdFormatNameSize; ++c) + stdFormatName[c] = (char)tolower( formatName[c] ); + stdFormatName[stdFormatNameSize] = '\0'; + + if (strncmp( mpw_nameForFormat( MPMarshallFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0) + return MPMarshallFormatFlat; + if (strncmp( mpw_nameForFormat( MPMarshallFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0) + return MPMarshallFormatJSON; + + dbg( "Not a format name: %s\n", stdFormatName ); + return (MPMarshallFormat)ERR; +} + +const char *mpw_nameForFormat( + const MPMarshallFormat format) { + + switch (format) { + case MPMarshallFormatFlat: + return "flat"; + case MPMarshallFormatJSON: + return "json"; + default: { + dbg( "Unknown format: %d\n", format ); + return NULL; + } + } +} + +const char *mpw_marshall_format_extension( + const MPMarshallFormat format) { + + switch (format) { + case MPMarshallFormatFlat: + return "mpsites"; + case MPMarshallFormatJSON: + return "mpsites.json"; + default: { + dbg( "Unknown format: %d\n", format ); + return NULL; + } + } +} diff --git a/core/c/mpw-marshall.h b/core/c/mpw-marshall.h index afd708f7..b26061cd 100644 --- a/core/c/mpw-marshall.h +++ b/core/c/mpw-marshall.h @@ -30,6 +30,8 @@ typedef enum( unsigned int, MPMarshallFormat ) { MPMarshallFormatFlat, /** Generate a name for identification. */ MPMarshallFormatJSON, + + MPMarshallFormatDefault = MPMarshallFormatJSON, }; typedef enum( unsigned int, MPMarshallErrorType ) { @@ -76,7 +78,7 @@ typedef struct MPMarshalledSite { } MPMarshalledSite; typedef struct MPMarshalledUser { - const char *name; + const char *fullName; const char *masterPassword; MPAlgorithmVersion algorithm; bool redacted; @@ -93,9 +95,6 @@ typedef struct MPMarshalledUser { bool mpw_marshall_write( char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *marshalledUser, MPMarshallError *error); - -//// Unmarshalling. - MPMarshalledUser *mpw_marshall_read( char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error); @@ -111,4 +110,19 @@ MPMarshalledQuestion *mpw_marshal_question( bool mpw_marshal_free( MPMarshalledUser *marshalledUser); +//// Format. + +/** + * @return The purpose represented by the given name. + */ +const MPMarshallFormat mpw_formatWithName( + const char *formatName); +/** + * @return The standard name for the given purpose. + */ +const char *mpw_nameForFormat( + const MPMarshallFormat format); +const char *mpw_marshall_format_extension( + const MPMarshallFormat format); + #endif // _MPW_MARSHALL_H diff --git a/platform-independent/cli-c/cli/mpw-cli.c b/platform-independent/cli-c/cli/mpw-cli.c index ac6e8c9a..3ff6678e 100644 --- a/platform-independent/cli-c/cli/mpw-cli.c +++ b/platform-independent/cli-c/cli/mpw-cli.c @@ -22,7 +22,7 @@ static void usage() { inf( "" - "Usage: mpw [-u name] [-t type] [-c counter] [-a algorithm] [-p purpose] [-C context] [-v|-q] [-h] site\n\n" ); + "Usage: mpw [-u name] [-t type] [-c counter] [-a algorithm] [-p purpose] [-C context] [-f|-F format] [-v|-q] [-h] site\n\n" ); inf( "" " -u name Specify the full name of the user.\n" " Defaults to %s in env or prompts.\n\n", MP_env_fullName ); @@ -56,8 +56,14 @@ static void usage() { " -p i, ident | -\n" " -p r, rec | Most significant word in security question.\n\n" ); inf( "" - " -v Increase output verbosity (can be repeated).\n\n" ); + " -f|F format The mpsites format to use for reading/writing site parameters.\n" + " -F forces the use of the given format, -f allows fallback/migration.\n" + " Defaults to json, falls back to plain.\n" + " f, flat | ~/.mpw.d/Full Name.%s\n" + " j, json | ~/.mpw.d/Full Name.%s\n\n", + mpw_marshall_format_extension( MPMarshallFormatFlat ), mpw_marshall_format_extension( MPMarshallFormatJSON ) ); inf( "" + " -v Increase output verbosity (can be repeated).\n" " -q Decrease output verbosity (can be repeated).\n\n" ); inf( "" " ENVIRONMENT\n\n" @@ -108,18 +114,20 @@ int main(int argc, char *const argv[]) { // Master Password defaults. const char *fullName = NULL, *masterPassword = NULL, *siteName = NULL, *keyContext = NULL; + uint32_t siteCounter = 1; MPPasswordType passwordType = MPPasswordTypeDefault; MPKeyPurpose keyPurpose = MPKeyPurposeAuthentication; MPAlgorithmVersion algorithmVersion = MPAlgorithmVersionCurrent; - uint32_t siteCounter = 1; + MPMarshallFormat sitesFormat = MPMarshallFormatDefault; + bool sitesFormatFixed = false; // Read the environment. - const char *fullNameArg = getenv( MP_env_fullName ), *masterPasswordArg = NULL, *siteNameArg = NULL; - const char *passwordTypeArg = NULL, *keyPurposeArg = NULL, *keyContextArg = NULL, *siteCounterArg = NULL; - const char *algorithmVersionArg = getenv( MP_env_algorithm ); + const char *fullNameArg = getenv( MP_env_fullName ), *masterPasswordArg = NULL; + const char *passwordTypeArg = NULL, *siteCounterArg = NULL, *algorithmVersionArg = getenv( MP_env_algorithm ); + const char *keyPurposeArg = NULL, *keyContextArg = NULL, *sitesFormatArg = NULL, *siteNameArg = NULL; // Read the command-line options. - for (int opt; (opt = getopt( argc, argv, "u:P:t:c:V:a:C:vqh" )) != EOF;) + for (int opt; (opt = getopt( argc, argv, "u:P:t:c:a:p:C:f:F:vqh" )) != EOF;) switch (opt) { case 'u': fullNameArg = optarg; @@ -134,15 +142,23 @@ int main(int argc, char *const argv[]) { case 'c': siteCounterArg = optarg; break; - case 'p': - keyPurposeArg = optarg; - break; case 'a': algorithmVersionArg = optarg; break; + case 'p': + keyPurposeArg = optarg; + break; case 'C': keyContextArg = optarg; break; + case 'f': + sitesFormatArg = optarg; + sitesFormatFixed = false; + break; + case 'F': + sitesFormatArg = optarg; + sitesFormatFixed = true; + break; case 'v': ++mpw_verbosity; break; @@ -177,12 +193,13 @@ int main(int argc, char *const argv[]) { // Empty strings unset the argument. fullNameArg = fullNameArg && strlen( fullNameArg )? fullNameArg: NULL; masterPasswordArg = masterPasswordArg && strlen( masterPasswordArg )? masterPasswordArg: NULL; - siteNameArg = siteNameArg && strlen( siteNameArg )? siteNameArg: NULL; passwordTypeArg = passwordTypeArg && strlen( passwordTypeArg )? passwordTypeArg: NULL; - keyPurposeArg = keyPurposeArg && strlen( keyPurposeArg )? keyPurposeArg: NULL; - keyContextArg = keyContextArg && strlen( keyContextArg )? keyContextArg: NULL; siteCounterArg = siteCounterArg && strlen( siteCounterArg )? siteCounterArg: NULL; algorithmVersionArg = algorithmVersionArg && strlen( algorithmVersionArg )? algorithmVersionArg: NULL; + keyPurposeArg = keyPurposeArg && strlen( keyPurposeArg )? keyPurposeArg: NULL; + keyContextArg = keyContextArg && strlen( keyContextArg )? keyContextArg: NULL; + sitesFormatArg = sitesFormatArg && strlen( sitesFormatArg )? sitesFormatArg: NULL; + siteNameArg = siteNameArg && strlen( siteNameArg )? siteNameArg: NULL; // Determine fullName, siteName & masterPassword. if (!(fullNameArg && (fullName = strdup( fullNameArg ))) && @@ -198,96 +215,90 @@ int main(int argc, char *const argv[]) { if (!(masterPasswordArg && (masterPassword = strdup( masterPasswordArg )))) while (!masterPassword || !strlen( masterPassword )) masterPassword = getpass( "Your master password: " ); + if (sitesFormatArg) { + sitesFormat = mpw_formatWithName( sitesFormatArg ); + if (ERR == sitesFormat) { + ftl( "Invalid sites format: %s\n", sitesFormatArg ); + return EX_USAGE; + } + } // Find the user's sites file. - FILE *mpwSites = NULL; - MPMarshallFormat mpwSitesFormat = MPMarshallFormatJSON; - char *mpwSitesPath = mpw_path( fullName, "mpsites.json" ); - if (!mpwSitesPath || !(mpwSites = fopen( mpwSitesPath, "r" ))) { - dbg( "Couldn't open configuration file:\n %s: %s\n", mpwSitesPath, strerror( errno ) ); - free( mpwSitesPath ); - mpwSitesFormat = MPMarshallFormatFlat; - mpwSitesPath = mpw_path( fullName, "mpsites" ); - if (!mpwSitesPath || !(mpwSites = fopen( mpwSitesPath, "r" ))) - dbg( "Couldn't open configuration file:\n %s: %s\n", mpwSitesPath, strerror( errno ) ); + FILE *sitesFile = NULL; + char *sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) ); + if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" ))) { + dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) ); + free( sitesPath ); + + // Try to fall back to the flat format. + if (!sitesFormatFixed) { + sitesFormat = MPMarshallFormatFlat; + sitesPath = mpw_path( fullName, mpw_marshall_format_extension( sitesFormat ) ); + if (!sitesPath || !(sitesFile = fopen( sitesPath, "r" ))) + dbg( "Couldn't open configuration file:\n %s: %s\n", sitesPath, strerror( errno ) ); + } } // Read the user's sites file. - if (mpwSites) { + MPMarshalledUser *user = NULL; + MPMarshalledSite *site = NULL; + if (!sitesFile) { + free( sitesPath ); + sitesPath = NULL; + } + else { // Read file. size_t readAmount = 4096, bufSize = 0, bufOffset = 0, readSize = 0; char *buf = NULL; while ((mpw_realloc( &buf, &bufSize, readAmount )) && - (bufOffset += (readSize = fread( buf + bufOffset, 1, readAmount, mpwSites ))) && + (bufOffset += (readSize = fread( buf + bufOffset, 1, readAmount, sitesFile ))) && (readSize == readAmount)); - if (ferror( mpwSites )) - wrn( "Error while reading configuration file:\n %s: %d\n", mpwSitesPath, ferror( mpwSites ) ); - fclose( mpwSites ); + if (ferror( sitesFile )) + wrn( "Error while reading configuration file:\n %s: %d\n", sitesPath, ferror( sitesFile ) ); + fclose( sitesFile ); // Parse file. MPMarshallError marshallError = { MPMarshallSuccess }; - MPMarshalledUser *user = mpw_marshall_read( buf, mpwSitesFormat, masterPassword, &marshallError ); + user = mpw_marshall_read( buf, sitesFormat, masterPassword, &marshallError ); mpw_free( buf, bufSize ); if (!user || marshallError.type != MPMarshallSuccess) { if (marshallError.type == MPMarshallErrorMasterPassword) { - ftl( "Incorrect master password according to configuration:\n %s: %s\n", mpwSitesPath, marshallError.description ); + ftl( "Incorrect master password according to configuration:\n %s: %s\n", sitesPath, marshallError.description ); return EX_DATAERR; } else - err( "Couldn't parse configuration file:\n %s: %s\n", mpwSitesPath, marshallError.description ); + err( "Couldn't parse configuration file:\n %s: %s\n", sitesPath, marshallError.description ); + mpw_marshal_free( user ); + user = NULL; + free( sitesPath ); + sitesPath = NULL; } else { // Load defaults. mpw_free_string( fullName ); mpw_free_string( masterPassword ); - fullName = strdup( user->name ); + fullName = strdup( user->fullName ); masterPassword = strdup( user->masterPassword ); algorithmVersion = user->algorithm; passwordType = user->defaultType; for (size_t s = 0; s < user->sites_count; ++s) { - MPMarshalledSite site = user->sites[s]; - if (strcmp( siteName, site.name ) == 0) { - passwordType = site.type; - siteCounter = site.counter; - algorithmVersion = site.algorithm; - break; + site = &user->sites[s]; + if (strcmp( siteName, site->name ) != 0) { + site = NULL; + continue; } + + passwordType = site->type; + siteCounter = site->counter; + algorithmVersion = site->algorithm; + break; } - - // Current format is not current, write out a new current format config file. - if (mpwSitesFormat != MPMarshallFormatJSON) { - mpwSitesPath = mpw_path( fullName, "mpsites.json" ); - if (!mpwSitesPath || !(mpwSites = fopen( mpwSitesPath, "w" ))) - wrn( "Couldn't create updated configuration file:\n %s: %s\n", mpwSitesPath, strerror( errno ) ); - - else { - buf = NULL; - if (!mpw_marshall_write( &buf, MPMarshallFormatJSON, user, &marshallError ) || marshallError.type != MPMarshallSuccess) - wrn( "Couldn't encode updated configuration file:\n %s: %s\n", mpwSitesPath, marshallError.description ); - - else if (fwrite( buf, sizeof( char ), strlen( buf ), mpwSites ) != strlen( buf )) - wrn( "Error while writing updated configuration file:\n %s: %d\n", mpwSitesPath, ferror( mpwSites ) ); - - mpw_free_string( buf ); - fclose( mpwSites ); - } - } - mpw_marshal_free( user ); } } - free( mpwSitesPath ); // Parse default/config-overriding command-line parameters. - if (algorithmVersionArg && strlen( algorithmVersionArg )) { - int algorithmVersionInt = atoi( algorithmVersionArg ); - if (algorithmVersionInt < MPAlgorithmVersionFirst || algorithmVersionInt > MPAlgorithmVersionLast) { - ftl( "Invalid algorithm version: %s\n", algorithmVersionArg ); - return EX_USAGE; - } - algorithmVersion = (MPAlgorithmVersion)algorithmVersionInt; - } - if (siteCounterArg && strlen( siteCounterArg )) { + if (siteCounterArg) { long long int siteCounterInt = atoll( siteCounterArg ); if (siteCounterInt < 0 || siteCounterInt > UINT32_MAX) { ftl( "Invalid site counter: %s\n", siteCounterArg ); @@ -295,28 +306,47 @@ int main(int argc, char *const argv[]) { } siteCounter = (uint32_t)siteCounterInt; } - if (keyPurposeArg && strlen( keyPurposeArg )) { + if (algorithmVersionArg) { + int algorithmVersionInt = atoi( algorithmVersionArg ); + if (algorithmVersionInt < MPAlgorithmVersionFirst || algorithmVersionInt > MPAlgorithmVersionLast) { + ftl( "Invalid algorithm version: %s\n", algorithmVersionArg ); + return EX_USAGE; + } + algorithmVersion = (MPAlgorithmVersion)algorithmVersionInt; + } + if (keyPurposeArg) { keyPurpose = mpw_purposeWithName( keyPurposeArg ); if (ERR == keyPurpose) { ftl( "Invalid purpose: %s\n", keyPurposeArg ); return EX_USAGE; } } - if (keyPurpose == MPKeyPurposeIdentification) - passwordType = MPPasswordTypeGeneratedName; - if (keyPurpose == MPKeyPurposeRecovery) - passwordType = MPPasswordTypeGeneratedPhrase; - if (passwordTypeArg && strlen( passwordTypeArg )) { + char *purposeResult = "password"; + switch (keyPurpose) { + case MPKeyPurposeAuthentication: + break; + case MPKeyPurposeIdentification: { + passwordType = MPPasswordTypeGeneratedName; + purposeResult = "login"; + break; + } + case MPKeyPurposeRecovery: { + passwordType = MPPasswordTypeGeneratedPhrase; + purposeResult = "answer"; + break; + } + } + if (passwordTypeArg) { passwordType = mpw_typeWithName( passwordTypeArg ); if (ERR == passwordType) { ftl( "Invalid type: %s\n", passwordTypeArg ); return EX_USAGE; } } - if (keyContextArg && strlen( keyContextArg )) + if (keyContextArg) keyContext = strdup( keyContextArg ); - // Summarize operation. + // Operation summary. const char *identicon = mpw_identicon( fullName, masterPassword ); if (!identicon) wrn( "Couldn't determine identicon.\n" ); @@ -324,6 +354,8 @@ int main(int argc, char *const argv[]) { dbg( "fullName : %s\n", fullName ); trc( "masterPassword : %s\n", masterPassword ); dbg( "identicon : %s\n", identicon ); + dbg( "sitesFormat : %s%s\n", mpw_nameForFormat( sitesFormat ), sitesFormatFixed? " (fixed)": "" ); + dbg( "sitesPath : %s\n", sitesPath ); dbg( "siteName : %s\n", siteName ); dbg( "siteCounter : %u\n", siteCounter ); dbg( "keyPurpose : %s (%u)\n", mpw_nameForPurpose( keyPurpose ), keyPurpose ); @@ -331,10 +363,12 @@ int main(int argc, char *const argv[]) { dbg( "passwordType : %s (%u)\n", mpw_nameForType( passwordType ), passwordType ); dbg( "algorithmVersion : %u\n", algorithmVersion ); dbg( "-----------------\n\n" ); - inf( "%s's password for %s:\n[ %s ]: ", fullName, siteName, identicon ); + inf( "%s's %s for %s:\n[ %s ]: ", fullName, purposeResult, siteName, identicon ); mpw_free_string( identicon ); + if (sitesPath) + free( sitesPath ); - // Output the password. + // Determine master key. MPMasterKey masterKey = mpw_masterKey( fullName, masterPassword, algorithmVersion ); mpw_free_string( masterPassword ); @@ -344,19 +378,68 @@ int main(int argc, char *const argv[]) { return EX_SOFTWARE; } - MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion ); - const char *sitePassword = mpw_sitePassword( siteKey, passwordType, algorithmVersion ); + // Output the result. + if (passwordType & MPPasswordTypeClassGenerated) { + MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion ); + const char *sitePassword = mpw_sitePassword( siteKey, passwordType, algorithmVersion ); + mpw_free( siteKey, MPSiteKeySize ); + if (!sitePassword) { + ftl( "Couldn't derive site password.\n" ); + mpw_free( masterKey, MPMasterKeySize ); + return EX_SOFTWARE; + } + + fprintf( stdout, "%s\n", sitePassword ); + mpw_free_string( sitePassword ); + } + else if (site && site->content) { + const char *sitePassword = mpw_decrypt( masterKey, site->content, algorithmVersion ); + if (!sitePassword) { + ftl( "Couldn't decrypt site password.\n" ); + mpw_free( masterKey, MPMasterKeySize ); + return EX_SOFTWARE; + } + + fprintf( stdout, "%s\n", sitePassword ); + mpw_free_string( sitePassword ); + } mpw_free( masterKey, MPMasterKeySize ); - mpw_free( siteKey, MPSiteKeySize ); mpw_free_string( siteName ); mpw_free_string( keyContext ); - if (!sitePassword) { - ftl( "Couldn't derive site password.\n" ); - return EX_SOFTWARE; - } - fprintf( stdout, "%s\n", sitePassword ); - mpw_free_string( sitePassword ); + // Update the mpsites file. + if (user) { + if (site) { + site->type = passwordType; + site->counter = siteCounter; + site->algorithm = algorithmVersion; + site->lastUsed = user->lastUsed = time( NULL ); + site->uses++; + } + + if (!sitesFormatFixed) + sitesFormat = MPMarshallFormatDefault; + + sitesPath = mpw_path( user->fullName, mpw_marshall_format_extension( sitesFormat ) ); + dbg( "Updating: %s (%s)\n", sitesPath, mpw_nameForFormat( sitesFormat ) ); + if (!sitesPath || !(sitesFile = fopen( sitesPath, "w" ))) + wrn( "Couldn't create updated configuration file:\n %s: %s\n", sitesPath, strerror( errno ) ); + + else { + char *buf = NULL; + MPMarshallError marshallError = { MPMarshallSuccess }; + 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 ) ); + + mpw_free_string( buf ); + fclose( sitesFile ); + } + free( sitesPath ); + mpw_marshal_free( user ); + } return 0; }