2
0

Support for persisting login/question type & stateful types, null checking, cleanup and rewrite of CLI state.

This commit is contained in:
Maarten Billemont 2017-08-22 18:18:24 -04:00
parent f83cdacab8
commit f2f8747126
6 changed files with 337 additions and 228 deletions

View File

@ -24,9 +24,14 @@
MPMasterKey mpw_masterKey(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) { MPMasterKey mpw_masterKey(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
if (fullName && !strlen( fullName ))
fullName = NULL;
if (masterPassword && !strlen( masterPassword ))
masterPassword = NULL;
trc( "-- mpw_masterKey (algorithm: %u)\n", algorithmVersion ); trc( "-- mpw_masterKey (algorithm: %u)\n", algorithmVersion );
trc( "fullName: %s\n", fullName ); trc( "fullName: %s\n", fullName );
trc( "masterPassword.id: %s\n", mpw_id_buf( masterPassword, strlen( masterPassword ) ) ); trc( "masterPassword.id: %s\n", masterPassword? mpw_id_buf( masterPassword, strlen( masterPassword ) ): NULL );
if (!fullName || !masterPassword) if (!fullName || !masterPassword)
return NULL; return NULL;
@ -49,6 +54,11 @@ MPSiteKey mpw_siteKey(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter, MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion) { const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion) {
if (siteName && !strlen( siteName ))
siteName = NULL;
if (keyContext && !strlen( keyContext ))
keyContext = NULL;
trc( "-- mpw_siteKey (algorithm: %u)\n", algorithmVersion ); trc( "-- mpw_siteKey (algorithm: %u)\n", algorithmVersion );
trc( "siteName: %s\n", siteName ); trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter ); trc( "siteCounter: %d\n", siteCounter );
@ -78,6 +88,13 @@ const char *mpw_siteResult(
const MPResultType resultType, const char *resultParam, const MPResultType resultType, const char *resultParam,
const MPAlgorithmVersion algorithmVersion) { const MPAlgorithmVersion algorithmVersion) {
if (siteName && !strlen( siteName ))
siteName = NULL;
if (keyContext && !strlen( keyContext ))
keyContext = NULL;
if (resultParam && !strlen( resultParam ))
resultParam = NULL;
MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion ); MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
if (!siteKey) if (!siteKey)
return NULL; return NULL;
@ -142,28 +159,35 @@ const char *mpw_siteResult(
const char *mpw_siteState( const char *mpw_siteState(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter, MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext, const MPKeyPurpose keyPurpose, const char *keyContext,
const MPResultType resultType, const char *state, const MPResultType resultType, const char *resultParam,
const MPAlgorithmVersion algorithmVersion) { const MPAlgorithmVersion algorithmVersion) {
if (siteName && !strlen( siteName ))
siteName = NULL;
if (keyContext && !strlen( keyContext ))
keyContext = NULL;
if (resultParam && !strlen( resultParam ))
resultParam = NULL;
MPSiteKey siteKey = mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext ); MPSiteKey siteKey = mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
if (!siteKey) if (!siteKey)
return NULL; return NULL;
trc( "-- mpw_siteState (algorithm: %u)\n", algorithmVersion ); trc( "-- mpw_siteState (algorithm: %u)\n", algorithmVersion );
trc( "resultType: %d (%s)\n", resultType, mpw_nameForType( resultType ) ); trc( "resultType: %d (%s)\n", resultType, mpw_nameForType( resultType ) );
trc( "state: %s\n", state ); trc( "resultParam: %s\n", resultParam );
if (!masterKey || !state) if (!masterKey || !resultParam)
return NULL; return NULL;
switch (algorithmVersion) { switch (algorithmVersion) {
case MPAlgorithmVersion0: case MPAlgorithmVersion0:
return mpw_siteState_v0( masterKey, siteKey, resultType, state ); return mpw_siteState_v0( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion1: case MPAlgorithmVersion1:
return mpw_siteState_v1( masterKey, siteKey, resultType, state ); return mpw_siteState_v1( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion2: case MPAlgorithmVersion2:
return mpw_siteState_v2( masterKey, siteKey, resultType, state ); return mpw_siteState_v2( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion3: case MPAlgorithmVersion3:
return mpw_siteState_v3( masterKey, siteKey, resultType, state ); return mpw_siteState_v3( masterKey, siteKey, resultType, resultParam );
default: default:
err( "Unsupported version: %d\n", algorithmVersion ); err( "Unsupported version: %d\n", algorithmVersion );
return NULL; return NULL;

View File

@ -48,7 +48,8 @@ MPSiteKey mpw_siteKey(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter, MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion); const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion);
/** Encode a password for the site from the given site key. /** Generate a site result token from the given parameters.
* @param resultParam A parameter for the resultType. For stateful result types, the output of mpw_siteState.
* @return A newly allocated string or NULL if an error occurred. */ * @return A newly allocated string or NULL if an error occurred. */
const char *mpw_siteResult( const char *mpw_siteResult(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter, MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
@ -56,12 +57,13 @@ const char *mpw_siteResult(
const MPResultType resultType, const char *resultParam, const MPResultType resultType, const char *resultParam,
const MPAlgorithmVersion algorithmVersion); const MPAlgorithmVersion algorithmVersion);
/** Perform symmetric encryption on a secret token's plainText. /** Encrypt a stateful site token for persistence.
* @return The newly allocated cipherText of the secret token encrypted by the masterKey. */ * @param resultParam A parameter for the resultType. For stateful result types, the desired mpw_siteResult.
* @return A newly allocated string or NULL if an error occurred. */
const char *mpw_siteState( const char *mpw_siteState(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter, MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext, const MPKeyPurpose keyPurpose, const char *keyContext,
const MPResultType resultType, const char *state, const MPResultType resultType, const char *resultParam,
const MPAlgorithmVersion algorithmVersion); const MPAlgorithmVersion algorithmVersion);
#endif // _MPW_ALGORITHM_H #endif // _MPW_ALGORITHM_H

View File

@ -63,8 +63,8 @@ MPMarshalledSite *mpw_marshall_site(
.counter = siteCounter, .counter = siteCounter,
.algorithm = algorithmVersion, .algorithm = algorithmVersion,
.loginName = NULL, .loginContent = NULL,
.loginGenerated = false, .loginType = MPResultTypeTemplateName,
.url = NULL, .url = NULL,
.uses = 0, .uses = 0,
@ -79,12 +79,16 @@ MPMarshalledSite *mpw_marshall_site(
MPMarshalledQuestion *mpw_marshal_question( MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword) { MPMarshalledSite *site, const char *keyword) {
if (!keyword || !mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count )) if (!mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count ))
return NULL; return NULL;
if (!keyword)
keyword = "";
MPMarshalledQuestion *question = &site->questions[site->questions_count - 1]; MPMarshalledQuestion *question = &site->questions[site->questions_count - 1];
*question = (MPMarshalledQuestion){ *question = (MPMarshalledQuestion){
.keyword = strdup( keyword ), .keyword = strdup( keyword ),
.content = NULL,
.type = MPResultTypeTemplatePhrase,
}; };
return question; return question;
} }
@ -113,9 +117,13 @@ bool mpw_marshal_free(
for (size_t s = 0; s < user->sites_count; ++s) { for (size_t s = 0; s < user->sites_count; ++s) {
MPMarshalledSite *site = &user->sites[s]; MPMarshalledSite *site = &user->sites[s];
success &= mpw_free_string( site->name ); success &= mpw_free_string( site->name );
success &= mpw_free_string( site->content );
success &= mpw_free_string( site->loginContent );
success &= mpw_free_string( site->url );
for (size_t q = 0; q < site->questions_count; ++q) { for (size_t q = 0; q < site->questions_count; ++q) {
MPMarshalledQuestion *question = &site->questions[q]; MPMarshalledQuestion *question = &site->questions[q];
success &= mpw_free_string( question->keyword ); success &= mpw_free_string( question->keyword );
success &= mpw_free_string( question->content );
} }
success &= mpw_free( site->questions, sizeof( MPMarshalledQuestion ) * site->questions_count ); success &= mpw_free( site->questions, sizeof( MPMarshalledQuestion ) * site->questions_count );
} }
@ -177,7 +185,7 @@ static bool mpw_marshall_write_flat(
if (!site->name || !strlen( site->name )) if (!site->name || !strlen( site->name ))
continue; continue;
const char *content = NULL; const char *content = NULL, *loginContent = NULL;
if (!user->redacted) { if (!user->redacted) {
// Clear Text // Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) { if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
@ -185,19 +193,25 @@ static bool mpw_marshall_write_flat(
return false; return false;
} }
if (site->type & MPResultTypeClassTemplate) content = mpw_siteResult( masterKey, site->name, site->counter,
content = mpw_siteResult( masterKey, site->name, site->counter, MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm ); loginContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, site->loginContent, site->algorithm );
} }
else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) else {
// Redacted // Redacted
content = strdup( site->content ); if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
content = strdup( site->content );
if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent ))
loginContent = strdup( site->loginContent );
}
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", 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, dateString, (long)site->uses, (long)site->type, (long)site->algorithm, (long)site->counter,
site->loginName?: "", site->name, content?: "" ); loginContent?: "", site->name, content?: "" );
mpw_free_string( content ); mpw_free_string( content );
mpw_free_string( loginContent );
} }
mpw_free( masterKey, MPMasterKeySize ); mpw_free( masterKey, MPMasterKeySize );
@ -239,15 +253,15 @@ static bool mpw_marshall_write_json(
// Section: "user" // Section: "user"
json_object *json_user = json_object_new_object(); json_object *json_user = json_object_new_object();
json_object_object_add( json_file, "user", json_user ); 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, "avatar", json_object_new_int( (int32_t)user->avatar ) );
json_object_object_add( json_user, "full_name", json_object_new_string( user->fullName ) ); json_object_object_add( json_user, "full_name", json_object_new_string( user->fullName ) );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &user->lastUsed ) )) if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &user->lastUsed ) ))
json_object_object_add( json_user, "last_used", json_object_new_string( dateString ) ); json_object_object_add( json_user, "last_used", json_object_new_string( dateString ) );
json_object_object_add( json_user, "key_id", json_object_new_string( mpw_id_buf( masterKey, MPMasterKeySize ) ) ); json_object_object_add( json_user, "key_id", json_object_new_string( mpw_id_buf( masterKey, MPMasterKeySize ) ) );
json_object_object_add( json_user, "algorithm", json_object_new_int( (int)user->algorithm ) ); json_object_object_add( json_user, "algorithm", json_object_new_int( (int32_t)user->algorithm ) );
json_object_object_add( json_user, "default_type", json_object_new_int( (int)user->defaultType ) ); json_object_object_add( json_user, "default_type", json_object_new_int( (int32_t)user->defaultType ) );
// Section "sites" // Section "sites"
json_object *json_sites = json_object_new_object(); json_object *json_sites = json_object_new_object();
@ -257,7 +271,7 @@ static bool mpw_marshall_write_json(
if (!site->name || !strlen( site->name )) if (!site->name || !strlen( site->name ))
continue; continue;
const char *content = NULL; const char *content = NULL, *loginContent = NULL;
if (!user->redacted) { if (!user->redacted) {
// Clear Text // Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) { if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
@ -265,26 +279,31 @@ static bool mpw_marshall_write_json(
return false; return false;
} }
if (site->type & MPResultTypeClassTemplate) content = mpw_siteResult( masterKey, site->name, site->counter,
content = mpw_siteResult( masterKey, site->name, site->counter, MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm ); loginContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, site->loginContent, site->algorithm );
} }
else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) else {
// Redacted // Redacted
content = strdup( site->content ); if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
content = strdup( site->content );
if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent ))
loginContent = strdup( site->loginContent );
}
json_object *json_site = json_object_new_object(); json_object *json_site = json_object_new_object();
json_object_object_add( json_sites, site->name, json_site ); 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, "type", json_object_new_int( (int32_t)site->type ) );
json_object_object_add( json_site, "counter", json_object_new_int( (int)site->counter ) ); json_object_object_add( json_site, "counter", json_object_new_int( (int32_t)site->counter ) );
json_object_object_add( json_site, "algorithm", json_object_new_int( (int)site->algorithm ) ); json_object_object_add( json_site, "algorithm", json_object_new_int( (int32_t)site->algorithm ) );
if (content) if (content)
json_object_object_add( json_site, "password", json_object_new_string( content ) ); json_object_object_add( json_site, "password", json_object_new_string( content ) );
if (site->loginName) if (loginContent)
json_object_object_add( json_site, "login_name", json_object_new_string( site->loginName ) ); json_object_object_add( json_site, "login_name", json_object_new_string( loginContent ) );
json_object_object_add( json_site, "login_generated", json_object_new_boolean( site->loginGenerated ) ); json_object_object_add( json_site, "login_type", json_object_new_int( (int32_t)site->loginType ) );
json_object_object_add( json_site, "uses", json_object_new_int( (int)site->uses ) ); json_object_object_add( json_site, "uses", json_object_new_int( (int32_t)site->uses ) );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) )) 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_object_add( json_site, "last_used", json_object_new_string( dateString ) );
@ -297,13 +316,18 @@ static bool mpw_marshall_write_json(
json_object *json_site_question = json_object_new_object(); 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 );
json_object_object_add( json_site_question, "type", json_object_new_int( (int32_t)question->type ) );
if (!user->redacted) { if (!user->redacted) {
// Clear Text // Clear Text
const char *answer = mpw_siteResult( masterKey, site->name, MPCounterValueInitial, const char *answerContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeRecovery, question->keyword, MPResultTypeTemplatePhrase, NULL, site->algorithm ); MPKeyPurposeRecovery, question->keyword, question->type, question->content, site->algorithm );
if (answer) json_object_object_add( json_site_question, "answer", json_object_new_string( answerContent ) );
json_object_object_add( json_site_question, "answer", json_object_new_string( answer ) ); }
else {
// Redacted
if (site->type & MPSiteFeatureExportContent && question->content && strlen( question->content ))
json_object_object_add( json_site_question, "answer", json_object_new_string( question->content ) );
} }
} }
@ -567,23 +591,28 @@ static MPMarshalledUser *mpw_marshall_read_flat(
return NULL; return NULL;
} }
site->loginName = siteLoginName? strdup( siteLoginName ): NULL;
site->uses = (unsigned int)atoi( str_uses ); site->uses = (unsigned int)atoi( str_uses );
site->lastUsed = siteLastUsed; site->lastUsed = siteLastUsed;
if (siteContent && strlen( siteContent )) { if (!user->redacted) {
if (!user->redacted) { // Clear Text
// Clear Text if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) { *error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; return NULL;
return NULL; }
}
if (siteContent && strlen( siteContent ))
site->content = mpw_siteState( masterKey, site->name, site->counter, site->content = mpw_siteState( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm ); MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
} if (siteLoginName && strlen( siteLoginName ))
else site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
// Redacted MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm );
}
else {
// Redacted
if (siteContent && strlen( siteContent ))
site->content = strdup( siteContent ); site->content = strdup( siteContent );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = strdup( siteLoginName );
} }
} }
else { else {
@ -732,7 +761,7 @@ static MPMarshalledUser *mpw_marshall_read_json(
MPCounterValue siteCounter = (MPCounterValue)value; MPCounterValue siteCounter = (MPCounterValue)value;
const char *siteContent = mpw_get_json_string( json_site.val, "password", NULL ); const char *siteContent = mpw_get_json_string( json_site.val, "password", NULL );
const char *siteLoginName = mpw_get_json_string( json_site.val, "login_name", NULL ); const char *siteLoginName = mpw_get_json_string( json_site.val, "login_name", NULL );
bool siteLoginGenerated = mpw_get_json_boolean( json_site.val, "login_generated", false ); MPResultType siteLoginType = (MPResultType)mpw_get_json_int( json_site.val, "login_type", MPResultTypeTemplateName );
unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 ); unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 );
str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL ); str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL );
time_t siteLastUsed = mpw_mktime( str_lastUsed ); time_t siteLastUsed = mpw_mktime( str_lastUsed );
@ -750,31 +779,51 @@ static MPMarshalledUser *mpw_marshall_read_json(
return NULL; return NULL;
} }
site->loginName = siteLoginName? strdup( siteLoginName ): NULL; site->loginType = siteLoginType;
site->loginGenerated = siteLoginGenerated;
site->url = siteURL? strdup( siteURL ): NULL; site->url = siteURL? strdup( siteURL ): NULL;
site->uses = siteUses; site->uses = siteUses;
site->lastUsed = siteLastUsed; site->lastUsed = siteLastUsed;
if (siteContent && strlen( siteContent )) { if (!user->redacted) {
if (!user->redacted) { // Clear Text
// Clear Text if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) { *error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." };
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; return NULL;
return NULL; }
}
if (siteContent && strlen( siteContent ))
site->content = mpw_siteState( masterKey, site->name, site->counter, site->content = mpw_siteState( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm ); MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
} if (siteLoginName && strlen( siteLoginName ))
else site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
// Redacted MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm );
}
else {
// Redacted
if (siteContent && strlen( siteContent ))
site->content = strdup( siteContent ); site->content = strdup( siteContent );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = strdup( siteLoginName );
} }
json_object_iter json_site_question; json_object_iter json_site_question;
json_object *json_site_questions = mpw_get_json_section( json_site.val, "questions" ); json_object *json_site_questions = mpw_get_json_section( json_site.val, "questions" );
json_object_object_foreachC( json_site_questions, json_site_question ) json_object_object_foreachC( json_site_questions, json_site_question ) {
mpw_marshal_question( site, json_site_question.key ); MPMarshalledQuestion *question = mpw_marshal_question( site, json_site_question.key );
const char *answerContent = mpw_get_json_string( json_site_question.val, "answer", NULL );
question->type = (MPResultType)mpw_get_json_int( json_site_question.val, "type", MPResultTypeTemplatePhrase );
if (!user->redacted) {
// Clear Text
if (answerContent && strlen( answerContent ))
question->content = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeRecovery, question->keyword, question->type, answerContent, site->algorithm );
}
else {
// Redacted
if (answerContent && strlen( answerContent ))
question->content = strdup( answerContent );
}
}
} }
json_object_put( json_file ); json_object_put( json_file );

View File

@ -59,6 +59,8 @@ typedef struct MPMarshallError {
typedef struct MPMarshalledQuestion { typedef struct MPMarshalledQuestion {
const char *keyword; const char *keyword;
const char *content;
MPResultType type;
} MPMarshalledQuestion; } MPMarshalledQuestion;
typedef struct MPMarshalledSite { typedef struct MPMarshalledSite {
@ -68,8 +70,8 @@ typedef struct MPMarshalledSite {
MPCounterValue counter; MPCounterValue counter;
MPAlgorithmVersion algorithm; MPAlgorithmVersion algorithm;
const char *loginName; const char *loginContent;
bool loginGenerated; MPResultType loginType;
const char *url; const char *url;
unsigned int uses; unsigned int uses;

View File

@ -691,7 +691,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
if ([site isKindOfClass:[MPGeneratedSiteEntity class]]) if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
((MPGeneratedSiteEntity *)site).counter = importSite->counter; ((MPGeneratedSiteEntity *)site).counter = importSite->counter;
site.algorithm = MPAlgorithmForVersion( importSite->algorithm ); site.algorithm = MPAlgorithmForVersion( importSite->algorithm );
site.loginName = importSite->loginName? @(importSite->loginName): nil; site.loginName = importSite->loginContent? @(importSite->loginContent): nil;
site.loginGenerated = importSite->loginGenerated; site.loginGenerated = importSite->loginGenerated;
site.url = importSite->url? @(importSite->url): nil; site.url = importSite->url? @(importSite->url): nil;
site.uses = importSite->uses; site.uses = importSite->uses;
@ -724,7 +724,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
MPMarshalledSite *exportSite = mpw_marshall_site( exportUser, MPMarshalledSite *exportSite = mpw_marshall_site( exportUser,
site.name.UTF8String, site.type, counter, site.algorithm.version ); site.name.UTF8String, site.type, counter, site.algorithm.version );
exportSite->content = content.UTF8String; exportSite->content = content.UTF8String;
exportSite->loginName = site.loginName.UTF8String; exportSite->loginContent = site.loginName.UTF8String;
exportSite->loginGenerated = site.loginGenerated; exportSite->loginGenerated = site.loginGenerated;
exportSite->url = site.url.UTF8String; exportSite->url = site.url.UTF8String;
exportSite->uses = (unsigned int)site.uses; exportSite->uses = (unsigned int)site.uses;

View File

@ -159,14 +159,10 @@ static char *mpw_path(const char *prefix, const char *extension) {
int main(int argc, char *const argv[]) { int main(int argc, char *const argv[]) {
// Master Password defaults. // CLI defaults.
const char *fullName = NULL, *masterPassword = NULL, *siteName = NULL, *resultParam = NULL, *keyContext = NULL;
MPCounterValue siteCounter = MPCounterValueDefault;
MPResultType resultType = MPResultTypeDefault;
MPKeyPurpose keyPurpose = MPKeyPurposeAuthentication;
MPAlgorithmVersion algorithmVersion = MPAlgorithmVersionCurrent;
MPMarshallFormat sitesFormat = MPMarshallFormatDefault; MPMarshallFormat sitesFormat = MPMarshallFormatDefault;
bool allowPasswordUpdate = false, sitesFormatFixed = false, sitesRedacted = true; const char *fullName = NULL, *masterPassword = NULL, *siteName = NULL;
bool allowPasswordUpdate = false, sitesFormatFixed = false;
// Read the environment. // Read the environment.
const char *fullNameArg = NULL, *masterPasswordArg = NULL, *siteNameArg = NULL; const char *fullNameArg = NULL, *masterPasswordArg = NULL, *siteNameArg = NULL;
@ -257,14 +253,14 @@ int main(int argc, char *const argv[]) {
ftl( "Missing full name.\n" ); ftl( "Missing full name.\n" );
return EX_DATAERR; return EX_DATAERR;
} }
if (!(masterPasswordArg && (masterPassword = strdup( masterPasswordArg ))))
while (!masterPassword || !strlen( masterPassword ))
masterPassword = mpw_getpass( "Your master password: " );
if (!(siteNameArg && (siteName = strdup( siteNameArg ))) && if (!(siteNameArg && (siteName = strdup( siteNameArg ))) &&
!(siteName = mpw_getline( "Site name:" ))) { !(siteName = mpw_getline( "Site name:" ))) {
ftl( "Missing site name.\n" ); ftl( "Missing site name.\n" );
return EX_DATAERR; return EX_DATAERR;
} }
if (!(masterPasswordArg && (masterPassword = strdup( masterPasswordArg ))))
while (!masterPassword || !strlen( masterPassword ))
masterPassword = mpw_getpass( "Your master password: " );
if (sitesFormatArg) { if (sitesFormatArg) {
sitesFormat = mpw_formatWithName( sitesFormatArg ); sitesFormat = mpw_formatWithName( sitesFormatArg );
if (ERR == (int)sitesFormat) { if (ERR == (int)sitesFormat) {
@ -281,7 +277,7 @@ int main(int argc, char *const argv[]) {
// Try to fall back to the flat format. // Try to fall back to the flat format.
if (!sitesFormatFixed) { if (!sitesFormatFixed) {
free( sitesPath ); mpw_free_string( sitesPath );
sitesPath = mpw_path( fullName, mpw_marshall_format_extension( MPMarshallFormatFlat ) ); sitesPath = mpw_path( fullName, mpw_marshall_format_extension( MPMarshallFormatFlat ) );
if (sitesPath && (sitesFile = fopen( sitesPath, "r" ))) if (sitesPath && (sitesFile = fopen( sitesPath, "r" )))
sitesFormat = MPMarshallFormatFlat; sitesFormat = MPMarshallFormatFlat;
@ -290,20 +286,21 @@ int main(int argc, char *const argv[]) {
} }
} }
// Read the user's sites file. // Load the user object from file.
MPMarshalledUser *user = NULL; MPMarshalledUser *user = NULL;
MPMarshalledSite *site = NULL; MPMarshalledSite *site = NULL;
MPMarshalledQuestion *question = NULL;
if (!sitesFile) { if (!sitesFile) {
free( sitesPath ); mpw_free_string( sitesPath );
sitesPath = NULL; sitesPath = NULL;
} }
else { else {
// Read file. // Read file.
size_t readAmount = 4096, bufSize = 0, bufOffset = 0, readSize = 0; size_t blockSize = 4096, bufSize = 0, bufOffset = 0, readSize = 0;
char *sitesInputData = NULL; char *sitesInputData = NULL;
while ((mpw_realloc( &sitesInputData, &bufSize, readAmount )) && while ((mpw_realloc( &sitesInputData, &bufSize, blockSize )) &&
(bufOffset += (readSize = fread( sitesInputData + bufOffset, 1, readAmount, sitesFile ))) && (bufOffset += (readSize = fread( sitesInputData + bufOffset, 1, blockSize, sitesFile ))) &&
(readSize == readAmount)); (readSize == blockSize));
if (ferror( sitesFile )) if (ferror( sitesFile ))
wrn( "Error while reading configuration file:\n %s: %d\n", sitesPath, ferror( sitesFile ) ); wrn( "Error while reading configuration file:\n %s: %d\n", sitesPath, ferror( sitesFile ) );
fclose( sitesFile ); fclose( sitesFile );
@ -312,6 +309,7 @@ int main(int argc, char *const argv[]) {
MPMarshallInfo *sitesInputInfo = mpw_marshall_read_info( sitesInputData ); MPMarshallInfo *sitesInputInfo = mpw_marshall_read_info( sitesInputData );
MPMarshallFormat sitesInputFormat = sitesFormatArg? sitesFormat: sitesInputInfo->format; MPMarshallFormat sitesInputFormat = sitesFormatArg? sitesFormat: sitesInputInfo->format;
MPMarshallError marshallError = { .type = MPMarshallSuccess }; MPMarshallError marshallError = { .type = MPMarshallSuccess };
mpw_marshal_info_free( sitesInputInfo );
user = mpw_marshall_read( sitesInputData, sitesInputFormat, masterPassword, &marshallError ); user = mpw_marshall_read( sitesInputData, sitesInputFormat, masterPassword, &marshallError );
if (marshallError.type == MPMarshallErrorMasterPassword) { if (marshallError.type == MPMarshallErrorMasterPassword) {
// Incorrect master password. // Incorrect master password.
@ -319,7 +317,7 @@ int main(int argc, char *const argv[]) {
ftl( "Incorrect master password according to configuration:\n %s: %s\n", sitesPath, marshallError.description ); ftl( "Incorrect master password according to configuration:\n %s: %s\n", sitesPath, marshallError.description );
mpw_marshal_free( user ); mpw_marshal_free( user );
mpw_free( sitesInputData, bufSize ); mpw_free( sitesInputData, bufSize );
free( sitesPath ); mpw_free_string( sitesPath );
return EX_DATAERR; return EX_DATAERR;
} }
@ -334,6 +332,7 @@ int main(int argc, char *const argv[]) {
mpw_marshal_free( user ); mpw_marshal_free( user );
user = mpw_marshall_read( sitesInputData, sitesInputFormat, importMasterPassword, &marshallError ); user = mpw_marshall_read( sitesInputData, sitesInputFormat, importMasterPassword, &marshallError );
mpw_free_string( importMasterPassword );
} }
if (user) { if (user) {
mpw_free_string( user->masterPassword ); mpw_free_string( user->masterPassword );
@ -345,57 +344,30 @@ int main(int argc, char *const argv[]) {
err( "Couldn't parse configuration file:\n %s: %s\n", sitesPath, marshallError.description ); err( "Couldn't parse configuration file:\n %s: %s\n", sitesPath, marshallError.description );
mpw_marshal_free( user ); mpw_marshal_free( user );
user = NULL; user = NULL;
free( sitesPath ); mpw_free_string( sitesPath );
sitesPath = NULL; sitesPath = NULL;
} }
if (user) {
// Load defaults.
mpw_free_string( fullName );
mpw_free_string( masterPassword );
fullName = strdup( user->fullName );
masterPassword = strdup( user->masterPassword );
algorithmVersion = user->algorithm;
resultType = user->defaultType;
sitesRedacted = user->redacted;
if (!sitesRedacted && !sitesRedactedArg)
wrn( "Sites configuration is not redacted. Use -R 1 to change this.\n" );
for (size_t s = 0; s < user->sites_count; ++s) {
site = &user->sites[s];
if (strcmp( siteName, site->name ) != 0) {
site = NULL;
continue;
}
resultType = site->type;
siteCounter = site->counter;
algorithmVersion = site->algorithm;
break;
}
}
} }
if (!user)
user = mpw_marshall_user( fullName, masterPassword, MPAlgorithmVersionCurrent );
mpw_free_string( fullName );
mpw_free_string( masterPassword );
// Parse default/config-overriding command-line parameters. // Load the site object.
if (sitesRedactedArg) for (size_t s = 0; s < user->sites_count; ++s) {
sitesRedacted = strcmp( sitesRedactedArg, "1" ) == 0; site = &user->sites[s];
if (siteCounterArg) { if (strcmp( siteName, site->name ) != 0) {
long long int siteCounterInt = atoll( siteCounterArg ); site = NULL;
if (siteCounterInt < MPCounterValueFirst || siteCounterInt > MPCounterValueLast) { continue;
ftl( "Invalid site counter: %s\n", siteCounterArg );
return EX_USAGE;
} }
siteCounter = (MPCounterValue)siteCounterInt; break;
}
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 (!site)
site = mpw_marshall_site( user, siteName, MPResultTypeDefault, MPCounterValueDefault, user->algorithm );
mpw_free_string( siteName );
// Load the purpose and context / question object.
MPKeyPurpose keyPurpose = MPKeyPurposeAuthentication;
if (keyPurposeArg) { if (keyPurposeArg) {
keyPurpose = mpw_purposeWithName( keyPurposeArg ); keyPurpose = mpw_purposeWithName( keyPurposeArg );
if (ERR == (int)keyPurpose) { if (ERR == (int)keyPurpose) {
@ -403,36 +375,116 @@ int main(int argc, char *const argv[]) {
return EX_USAGE; return EX_USAGE;
} }
} }
char *purposeResult = "password"; const char *keyContext = NULL;
if (keyContextArg) {
keyContext = strdup( keyContextArg );
switch (keyPurpose) {
case MPKeyPurposeAuthentication:
// NOTE: keyContext is not persisted.
break;
case MPKeyPurposeIdentification:
// NOTE: keyContext is not persisted.
break;
case MPKeyPurposeRecovery:
for (size_t q = 0; q < site->questions_count; ++q) {
question = &site->questions[q];
if (strcmp( keyContext, question->keyword ) != 0) {
question = NULL;
continue;
}
break;
}
break;
}
}
if (!question)
question = mpw_marshal_question( site, keyContext );
// Initialize purpose-specific operation parameters.
MPResultType resultType = MPResultTypeDefault;
MPCounterValue siteCounter = MPCounterValueDefault;
const char *purposeResult = NULL, *resultState = NULL;
switch (keyPurpose) { switch (keyPurpose) {
case MPKeyPurposeAuthentication: case MPKeyPurposeAuthentication: {
purposeResult = "password";
resultType = site->type;
resultState = strdup( site->content );
siteCounter = site->counter;
break; break;
}
case MPKeyPurposeIdentification: { case MPKeyPurposeIdentification: {
resultType = MPResultTypeTemplateName;
purposeResult = "login"; purposeResult = "login";
resultType = site->loginType;
resultState = strdup( site->loginContent );
siteCounter = MPCounterValueInitial;
break; break;
} }
case MPKeyPurposeRecovery: { case MPKeyPurposeRecovery: {
resultType = MPResultTypeTemplatePhrase; mpw_free_string( keyContext );
purposeResult = "answer"; purposeResult = "answer";
keyContext = strdup( question->keyword );
resultType = question->type;
resultState = strdup( question->content );
siteCounter = MPCounterValueInitial;
break; break;
} }
} }
// Override operation parameters from command-line arguments.
if (resultTypeArg) { if (resultTypeArg) {
resultType = mpw_typeWithName( resultTypeArg ); resultType = mpw_typeWithName( resultTypeArg );
if (ERR == (int)resultType) { if (ERR == (int)resultType) {
ftl( "Invalid type: %s\n", resultTypeArg ); ftl( "Invalid type: %s\n", resultTypeArg );
return EX_USAGE; return EX_USAGE;
} }
if (!(resultType & MPSiteFeatureAlternative)) {
switch (keyPurpose) {
case MPKeyPurposeAuthentication:
site->type = resultType;
break;
case MPKeyPurposeIdentification:
site->loginType = resultType;
break;
case MPKeyPurposeRecovery:
question->type = resultType;
break;
}
}
} }
if (resultParamArg) { if (siteCounterArg) {
mpw_free_string( resultParam ); long long int siteCounterInt = atoll( siteCounterArg );
if (siteCounterInt < MPCounterValueFirst || siteCounterInt > MPCounterValueLast) {
ftl( "Invalid site counter: %s\n", siteCounterArg );
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;
}
}
const char *resultParam = NULL;
if (resultParamArg)
resultParam = strdup( resultParamArg ); resultParam = strdup( resultParamArg );
if (algorithmVersionArg) {
int algorithmVersionInt = atoi( algorithmVersionArg );
if (algorithmVersionInt < MPAlgorithmVersionFirst || algorithmVersionInt > MPAlgorithmVersionLast) {
ftl( "Invalid algorithm version: %s\n", algorithmVersionArg );
return EX_USAGE;
}
site->algorithm = (MPAlgorithmVersion)algorithmVersionInt;
} }
if (keyContextArg) { if (sitesRedactedArg)
mpw_free_string( keyContext ); user->redacted = strcmp( sitesRedactedArg, "1" ) == 0;
keyContext = strdup( keyContextArg ); else if (!user->redacted)
} wrn( "Sites configuration is not redacted. Use -R 1 to change this.\n" );
mpw_free_string( fullNameArg ); mpw_free_string( fullNameArg );
mpw_free_string( masterPasswordArg ); mpw_free_string( masterPasswordArg );
mpw_free_string( siteNameArg ); mpw_free_string( siteNameArg );
@ -446,120 +498,100 @@ int main(int argc, char *const argv[]) {
mpw_free_string( sitesRedactedArg ); mpw_free_string( sitesRedactedArg );
// Operation summary. // Operation summary.
const char *identicon = mpw_identicon( fullName, masterPassword ); const char *identicon = mpw_identicon( user->fullName, user->masterPassword );
if (!identicon) if (!identicon)
wrn( "Couldn't determine identicon.\n" ); wrn( "Couldn't determine identicon.\n" );
dbg( "-----------------\n" ); dbg( "-----------------\n" );
dbg( "fullName : %s\n", fullName ); dbg( "fullName : %s\n", user->fullName );
trc( "masterPassword : %s\n", masterPassword ); trc( "masterPassword : %s\n", user->masterPassword );
dbg( "identicon : %s\n", identicon ); dbg( "identicon : %s\n", identicon );
dbg( "sitesFormat : %s%s\n", mpw_nameForFormat( sitesFormat ), sitesFormatFixed? " (fixed)": "" ); dbg( "sitesFormat : %s%s\n", mpw_nameForFormat( sitesFormat ), sitesFormatFixed? " (fixed)": "" );
dbg( "sitesPath : %s\n", sitesPath ); dbg( "sitesPath : %s\n", sitesPath );
dbg( "siteName : %s\n", siteName ); dbg( "siteName : %s\n", site->name );
dbg( "siteCounter : %u\n", siteCounter ); dbg( "siteCounter : %u\n", siteCounter );
dbg( "resultType : %s (%u)\n", mpw_nameForType( resultType ), resultType ); dbg( "resultType : %s (%u)\n", mpw_nameForType( resultType ), resultType );
dbg( "resultParam : %s\n", resultParam ); dbg( "resultParam : %s\n", resultParam );
dbg( "keyPurpose : %s (%u)\n", mpw_nameForPurpose( keyPurpose ), keyPurpose ); dbg( "keyPurpose : %s (%u)\n", mpw_nameForPurpose( keyPurpose ), keyPurpose );
dbg( "keyContext : %s\n", keyContext ); dbg( "keyContext : %s\n", keyContext );
dbg( "algorithmVersion : %u\n", algorithmVersion ); dbg( "algorithmVersion : %u\n", site->algorithm );
dbg( "-----------------\n\n" ); dbg( "-----------------\n\n" );
inf( "%s's %s for %s:\n[ %s ]: ", fullName, purposeResult, siteName, identicon ); inf( "%s's %s for %s:\n[ %s ]: ", user->fullName, purposeResult, site->name, identicon );
mpw_free_string( identicon ); mpw_free_string( identicon );
if (sitesPath) if (sitesPath)
free( sitesPath ); mpw_free_string( sitesPath );
// Determine master key. // Determine master key.
MPMasterKey masterKey = mpw_masterKey( MPMasterKey masterKey = mpw_masterKey(
fullName, masterPassword, algorithmVersion ); user->fullName, user->masterPassword, site->algorithm );
mpw_free_string( masterPassword );
mpw_free_string( fullName );
if (!masterKey) { if (!masterKey) {
ftl( "Couldn't derive master key.\n" ); ftl( "Couldn't derive master key.\n" );
return EX_SOFTWARE; return EX_SOFTWARE;
} }
// Output the result. // Update state.
if (keyPurpose == MPKeyPurposeIdentification && site && (resultParam || (!site->loginGenerated && site->loginName))) { if (resultParam && resultType & MPResultTypeClassStateful) {
if (resultParam) { if (!(resultState = mpw_siteState( masterKey, site->name, siteCounter,
mpw_free_string( site->loginName ); keyPurpose, keyContext, resultType, resultParam, site->algorithm ))) {
site->loginGenerated = false; ftl( "Couldn't encrypt site result.\n" );
site->loginName = strdup( resultParam );
}
else if (resultTypeArg)
// TODO: We're not persisting the resultType of the generated login
site->loginGenerated = true;
fprintf( stdout, "%s\n", site->loginName );
}
else if (resultParam && site && resultType & MPResultTypeClassStateful) {
mpw_free_string( site->content );
if (!(site->content = mpw_siteState( masterKey, siteName, siteCounter,
keyPurpose, keyContext, resultType, resultParam, algorithmVersion ))) {
ftl( "Couldn't encrypt site content.\n" );
mpw_free( masterKey, MPMasterKeySize ); mpw_free( masterKey, MPMasterKeySize );
return EX_SOFTWARE; return EX_SOFTWARE;
} }
inf( "(state) %s => ", resultState );
fprintf( stdout, "%s\n", site->content ); switch (keyPurpose) {
} case MPKeyPurposeAuthentication: {
else { mpw_free_string( site->content );
if (!resultParam && site && site->content && resultType & MPResultTypeClassStateful) site->content = resultState;
resultParam = strdup( site->content ); break;
const char *siteResult = mpw_siteResult( masterKey, siteName, siteCounter, }
keyPurpose, keyContext, resultType, resultParam, algorithmVersion ); case MPKeyPurposeIdentification: {
if (!siteResult) { mpw_free_string( site->loginContent );
ftl( "Couldn't generate site result.\n" ); site->loginContent = resultState;
mpw_free( masterKey, MPMasterKeySize ); break;
return EX_SOFTWARE; }
}
case MPKeyPurposeRecovery: {
fprintf( stdout, "%s\n", siteResult ); mpw_free_string( question->content );
mpw_free_string( siteResult ); question->content = resultState;
}
if (site && site->url)
inf( "See: %s\n", site->url );
mpw_free( masterKey, MPMasterKeySize );
mpw_free_string( siteName );
mpw_free_string( resultParam );
mpw_free_string( keyContext );
// Update the mpsites file.
if (user) {
// TODO: Move this up above the summary and replace the mpw lvars by user/site accessors.
if (keyPurpose == MPKeyPurposeAuthentication && !(resultType & MPSiteFeatureAlternative)) {
if (!site)
site = mpw_marshall_site( user, siteName, resultType, siteCounter, algorithmVersion );
else {
site->type = resultType;
site->counter = siteCounter;
site->algorithm = algorithmVersion;
}
}
else if (keyPurpose == MPKeyPurposeRecovery && site && keyContext) {
// TODO: We're not persisting the resultType of the recovery question
MPMarshalledQuestion *question = NULL;
for (size_t q = 0; q < site->questions_count; ++q) {
question = &site->questions[q];
if (strcmp( keyContext, question->keyword ) != 0) {
question = NULL;
continue;
}
break; break;
} }
if (!question)
mpw_marshal_question( site, keyContext );
}
if (site) {
site->lastUsed = user->lastUsed = time( NULL );
site->uses++;
} }
// resultParam is consumed.
mpw_free_string( resultParam );
resultParam = NULL;
}
// Second phase resultParam defaults to state.
if (!resultParam && resultState)
resultParam = strdup( resultState );
mpw_free_string( resultState );
// Generate result.
const char *result = mpw_siteResult( masterKey, site->name, siteCounter,
keyPurpose, keyContext, resultType, resultParam, site->algorithm );
if (!result) {
ftl( "Couldn't generate site result.\n" );
return EX_SOFTWARE;
}
fprintf( stdout, "%s\n", result );
if (site->url)
inf( "See: %s\n", site->url );
mpw_free( masterKey, MPMasterKeySize );
mpw_free_string( keyContext );
mpw_free_string( resultParam );
mpw_free_string( result );
// Update usage metadata.
site->lastUsed = user->lastUsed = time( NULL );
site->uses++;
// Update the mpsites file.
if (sitesFormat != MPMarshallFormatNone) {
if (!sitesFormatFixed) if (!sitesFormatFixed)
sitesFormat = MPMarshallFormatDefault; sitesFormat = MPMarshallFormatDefault;
user->redacted = sitesRedacted;
sitesPath = mpw_path( user->fullName, mpw_marshall_format_extension( sitesFormat ) ); sitesPath = mpw_path( user->fullName, mpw_marshall_format_extension( sitesFormat ) );
dbg( "Updating: %s (%s)\n", sitesPath, mpw_nameForFormat( sitesFormat ) ); dbg( "Updating: %s (%s)\n", sitesPath, mpw_nameForFormat( sitesFormat ) );
if (!sitesPath || !(sitesFile = fopen( sitesPath, "w" ))) if (!sitesPath || !(sitesFile = fopen( sitesPath, "w" )))
wrn( "Couldn't create updated configuration file:\n %s: %s\n", sitesPath, strerror( errno ) ); wrn( "Couldn't create updated configuration file:\n %s: %s\n", sitesPath, strerror( errno ) );
@ -576,9 +608,9 @@ int main(int argc, char *const argv[]) {
mpw_free_string( buf ); mpw_free_string( buf );
fclose( sitesFile ); fclose( sitesFile );
} }
free( sitesPath ); mpw_free_string( sitesPath );
mpw_marshal_free( user );
} }
mpw_marshal_free( user );
return 0; return 0;
} }