2
0

Restructure, split up mpw cli from mpw core.

This commit is contained in:
Maarten Billemont 2014-12-19 23:15:32 -05:00
parent 2dbada3c7c
commit 672b28a5b7
6 changed files with 305 additions and 260 deletions

View File

@ -217,7 +217,8 @@ mpw() {
(( mpw_color )) && CFLAGS+=( -DCOLOR ) LDFLAGS+=( -l"curses" )
cc "${CFLAGS[@]}" -c types.c -o types.o "$@"
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "types.o" mpw.c -o mpw "$@"
cc "${CFLAGS[@]}" -c mpw.c -o mpw.o "$@"
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "types.o" "mpw.o" mpw-cli.c -o mpw "$@"
echo "done! Now run ./install or use ./mpw"
}

207
MasterPassword/C/mpw-cli.c Normal file
View File

@ -0,0 +1,207 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "types.h"
#include "mpw.h"
#if defined(READLINE)
#include <readline/readline.h>
#elif defined(EDITLINE)
#include <histedit.h>
#endif
#define MP_env_fullname "MP_FULLNAME"
#define MP_env_sitetype "MP_SITETYPE"
#define MP_env_sitecounter "MP_SITECOUNTER"
static void usage() {
fprintf( stderr, "Usage: mpw [-u name] [-t type] [-c counter] site\n\n" );
fprintf( stderr, " -u name Specify the full name of the user.\n"
" Defaults to %s in env.\n\n", MP_env_fullname );
fprintf( stderr, " -t type Specify the password's template.\n"
" Defaults to %s in env or 'long' for password, 'name' for login.\n"
" x, max, maximum | 20 characters, contains symbols.\n"
" l, long | Copy-friendly, 14 characters, contains symbols.\n"
" m, med, medium | Copy-friendly, 8 characters, contains 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\n", MP_env_sitetype );
fprintf( stderr, " -c counter The value of the counter.\n"
" Defaults to %s in env or '1'.\n\n", MP_env_sitecounter );
fprintf( stderr, " -v variant The kind of content to generate.\n"
" Defaults to 'password'.\n"
" p, password | The password to log in with.\n"
" l, login | The username to log in as.\n"
" a, answer | The answer to a security question.\n\n" );
fprintf( stderr, " -C context A variant-specific context.\n"
" Defaults to empty.\n"
" -v p, password | Doesn't currently use a context.\n"
" -v l, login | Doesn't currently use a context.\n"
" -v a, answer | Empty for a universal site answer or\n"
" | the most significant word(s) of the question.\n\n" );
fprintf( stderr, " ENVIRONMENT\n\n"
" MP_FULLNAME | The full name of the user.\n"
" MP_SITETYPE | The default password template.\n"
" MP_SITECOUNTER | The default counter value.\n\n" );
exit( 0 );
}
static char *homedir(const char *filename) {
char *homedir = NULL;
struct passwd *passwd = getpwuid( getuid() );
if (passwd)
homedir = passwd->pw_dir;
if (!homedir)
homedir = getenv( "HOME" );
if (!homedir)
homedir = getcwd( NULL, 0 );
char *homefile = NULL;
asprintf( &homefile, "%s/%s", homedir, filename );
return homefile;
}
static char *getlinep(const char *prompt) {
char *buf = NULL;
size_t bufSize = 0;
ssize_t lineSize;
fprintf( stderr, "%s", prompt );
fprintf( stderr, " " );
if ((lineSize = getline( &buf, &bufSize, stdin )) < 0) {
free( buf );
return NULL;
}
buf[lineSize - 1] = 0;
return buf;
}
int main(int argc, char *const argv[]) {
// Read the environment.
char *fullName = getenv( MP_env_fullname );
const char *masterPassword = NULL;
const char *siteName = NULL;
MPSiteType siteType = MPSiteTypeGeneratedLong;
const char *siteTypeString = getenv( MP_env_sitetype );
MPSiteVariant siteVariant = MPSiteVariantPassword;
const char *siteVariantString = NULL;
const char *siteContextString = NULL;
uint32_t siteCounter = 1;
const char *siteCounterString = getenv( MP_env_sitecounter );
// Read the options.
for (int opt; (opt = getopt( argc, argv, "u:t:c:v:C:h" )) != -1;)
switch (opt) {
case 'u':
fullName = optarg;
break;
case 't':
siteTypeString = optarg;
break;
case 'c':
siteCounterString = optarg;
break;
case 'v':
siteVariantString = optarg;
break;
case 'C':
siteContextString = optarg;
break;
case 'h':
usage();
break;
case '?':
switch (optopt) {
case 'u':
fprintf( stderr, "Missing full name to option: -%c\n", optopt );
break;
case 't':
fprintf( stderr, "Missing type name to option: -%c\n", optopt );
break;
case 'c':
fprintf( stderr, "Missing counter value to option: -%c\n", optopt );
break;
default:
fprintf( stderr, "Unknown option: -%c\n", optopt );
}
return 1;
default:
abort();
}
if (optind < argc)
siteName = argv[optind];
// Convert and validate input.
if (!fullName) {
if (!(fullName = getlinep( "Your full name:" ))) {
fprintf( stderr, "Missing full name.\n" );
return 1;
}
}
if (!siteName) {
if (!(siteName = getlinep( "Site name:" ))) {
fprintf( stderr, "Missing site name.\n" );
return 1;
}
}
if (siteCounterString)
siteCounter = atoi( siteCounterString );
if (siteCounter < 1) {
fprintf( stderr, "Invalid site counter: %d\n", siteCounter );
return 1;
}
if (siteVariantString)
siteVariant = VariantWithName( siteVariantString );
if (siteVariant == MPSiteVariantLogin)
siteType = MPSiteTypeGeneratedName;
if (siteVariant == MPSiteVariantAnswer)
siteType = MPSiteTypeGeneratedPhrase;
if (siteTypeString)
siteType = TypeWithName( siteTypeString );
// Read the master password.
char *mpwConfigPath = homedir( ".mpw" );
if (!mpwConfigPath) {
fprintf( stderr, "Couldn't resolve path for configuration file: %d\n", errno );
return 1;
}
trc( "mpwConfigPath: %s\n", mpwConfigPath );
FILE *mpwConfig = fopen( mpwConfigPath, "r" );
free( mpwConfigPath );
if (mpwConfig) {
char *line = NULL;
size_t linecap = 0;
while (getline( &line, &linecap, mpwConfig ) > 0) {
char *lineData = line;
if (strcmp( strsep( &lineData, ":" ), fullName ) == 0) {
masterPassword = strcpy( malloc( strlen( lineData ) ), strsep( &lineData, "\n" ) );
break;
}
}
free( line );
}
while (!masterPassword)
masterPassword = getpass( "Your master password: " );
// Summarize operation.
fprintf( stderr, "%s's password for %s:\n[ %s ]: ", fullName, siteName, Identicon( fullName, masterPassword ) );
// Output the password.
uint8_t *masterKey = mpw_masterKeyForUser( fullName, masterPassword );
char *sitePassword = mpw_passwordForSite( masterKey, siteName, siteType, siteCounter, siteVariant, siteContextString );
fprintf( stdout, "%s\n", sitePassword );
return 0;
}

View File

@ -1,10 +1,7 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
@ -13,288 +10,116 @@
#include <scrypt/crypto_scrypt.h>
#include "types.h"
#if defined(READLINE)
#include <readline/readline.h>
#elif defined(EDITLINE)
#include <histedit.h>
#endif
#define MP_N 32768
#define MP_r 8
#define MP_p 2
#define MP_dkLen 64
#define MP_hash PearlHashSHA256
#define MP_env_fullname "MP_FULLNAME"
#define MP_env_sitetype "MP_SITETYPE"
#define MP_env_sitecounter "MP_SITECOUNTER"
void mpw_bufPush(void *buffer, size_t *bufferSize, void *pushBuffer, size_t pushSize) {
static void usage() {
fprintf(stderr, "Usage: mpw [-u name] [-t type] [-c counter] site\n\n");
fprintf(stderr, " -u name Specify the full name of the user.\n"
" Defaults to %s in env.\n\n", MP_env_fullname);
fprintf(stderr, " -t type Specify the password's template.\n"
" Defaults to %s in env or 'long' for password, 'name' for login.\n"
" x, max, maximum | 20 characters, contains symbols.\n"
" l, long | Copy-friendly, 14 characters, contains symbols.\n"
" m, med, medium | Copy-friendly, 8 characters, contains 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\n", MP_env_sitetype);
fprintf(stderr, " -c counter The value of the counter.\n"
" Defaults to %s in env or '1'.\n\n", MP_env_sitecounter);
fprintf(stderr, " -v variant The kind of content to generate.\n"
" Defaults to 'password'.\n"
" p, password | The password to log in with.\n"
" l, login | The username to log in as.\n"
" a, answer | The answer to a security question.\n\n");
fprintf(stderr, " -C context A variant-specific context.\n"
" Defaults to empty.\n"
" -v p, password | Doesn't currently use a context.\n"
" -v l, login | Doesn't currently use a context.\n"
" -v a, answer | Empty for a universal site answer or\n"
" | the most significant word(s) of the question.\n\n");
fprintf(stderr, " ENVIRONMENT\n\n"
" MP_FULLNAME | The full name of the user.\n"
" MP_SITETYPE | The default password template.\n"
" MP_SITECOUNTER | The default counter value.\n\n");
exit(0);
void *pushDst = buffer + *bufferSize;
*bufferSize += pushSize;
realloc( buffer, *bufferSize );
memcpy( pushDst, pushBuffer, pushSize );
}
static char *homedir(const char *filename) {
char *homedir = NULL;
struct passwd* passwd = getpwuid(getuid());
if (passwd)
homedir = passwd->pw_dir;
if (!homedir)
homedir = getenv("HOME");
if (!homedir)
homedir = getcwd(NULL, 0);
char *homefile = NULL;
asprintf(&homefile, "%s/%s", homedir, filename);
return homefile;
}
static char *getlinep(const char *prompt) {
char *buf = NULL;
size_t bufSize = 0;
ssize_t lineSize;
fprintf(stderr, "%s", prompt);
fprintf(stderr, " ");
if ((lineSize = getline(&buf, &bufSize, stdin)) < 0) {
free(buf);
return NULL;
}
buf[lineSize - 1]=0;
return buf;
}
int main(int argc, char *const argv[]) {
// Read the environment.
char *fullName = getenv( MP_env_fullname );
const char *masterPassword = NULL;
const char *siteName = NULL;
MPSiteType siteType = MPSiteTypeGeneratedLong;
const char *siteTypeString = getenv( MP_env_sitetype );
MPSiteVariant siteVariant = MPSiteVariantPassword;
const char *siteVariantString = NULL;
const char *siteContextString = NULL;
uint32_t siteCounter = 1;
const char *siteCounterString = getenv( MP_env_sitecounter );
// Read the options.
for (int opt; (opt = getopt(argc, argv, "u:t:c:v:C:h")) != -1;)
switch (opt) {
case 'u':
fullName = optarg;
break;
case 't':
siteTypeString = optarg;
break;
case 'c':
siteCounterString = optarg;
break;
case 'v':
siteVariantString = optarg;
break;
case 'C':
siteContextString = optarg;
break;
case 'h':
usage();
break;
case '?':
switch (optopt) {
case 'u':
fprintf(stderr, "Missing full name to option: -%c\n", optopt);
break;
case 't':
fprintf(stderr, "Missing type name to option: -%c\n", optopt);
break;
case 'c':
fprintf(stderr, "Missing counter value to option: -%c\n", optopt);
break;
default:
fprintf(stderr, "Unknown option: -%c\n", optopt);
}
return 1;
default:
abort();
}
if (optind < argc)
siteName = argv[optind];
// Convert and validate input.
if (!fullName) {
if (!(fullName = getlinep("Your full name:"))) {
fprintf(stderr, "Missing full name.\n");
return 1;
}
}
trc("fullName: %s\n", fullName);
if (!siteName) {
if (!(siteName = getlinep("Site name:"))) {
fprintf(stderr, "Missing site name.\n");
return 1;
}
}
if (siteCounterString)
siteCounter = atoi( siteCounterString );
if (siteCounter < 1) {
fprintf(stderr, "Invalid site counter: %d\n", siteCounter);
return 1;
}
if (siteVariantString)
siteVariant = VariantWithName( siteVariantString );
if (siteVariant == MPSiteVariantLogin)
siteType = MPSiteTypeGeneratedName;
if (siteVariant == MPSiteVariantAnswer)
siteType = MPSiteTypeGeneratedPhrase;
if (siteTypeString)
siteType = TypeWithName( siteTypeString );
// Read the master password.
char *mpwConfigPath = homedir(".mpw");
if (!mpwConfigPath) {
fprintf(stderr, "Couldn't resolve path for configuration file: %d\n", errno);
return 1;
}
trc("mpwConfigPath: %s\n", mpwConfigPath);
FILE *mpwConfig = fopen(mpwConfigPath, "r");
free(mpwConfigPath);
if (mpwConfig) {
char *line = NULL;
size_t linecap = 0;
while (getline(&line, &linecap, mpwConfig) > 0) {
char *lineData = line;
if (strcmp(strsep(&lineData, ":"), fullName) == 0) {
masterPassword = strcpy(malloc(strlen(lineData)), strsep(&lineData, "\n"));
break;
}
}
free(line);
}
while (!masterPassword)
masterPassword = getpass( "Your master password: " );
trc("masterPassword: %s\n", masterPassword);
// Summarize operation.
fprintf(stderr, "%s's password for %s:\n[ %s ]: ", fullName, siteName, Identicon( fullName, masterPassword ));
struct timeval startTime;
if (gettimeofday(&startTime, NULL) != 0) {
fprintf(stderr, "Could not get time: %d\n", errno);
return 1;
}
uint8_t *mpw_masterKeyForUser(const char *fullName, const char *masterPassword) {
// Calculate the master key salt.
const char *mpKeyScope = ScopeForVariant( MPSiteVariantPassword );
trc("key scope: %s\n", mpKeyScope);
const uint32_t n_fullNameLength = htonl( strlen( fullName ) );
const size_t masterKeySaltLength = strlen( mpKeyScope ) + sizeof( n_fullNameLength ) + strlen( fullName );
char *masterKeySalt = (char *)malloc( masterKeySaltLength );
if (!masterKeySalt) {
fprintf(stderr, "Could not allocate master key salt: %d\n", errno);
return 1;
}
if (!masterKeySalt)
ftl( "Could not allocate master key salt: %d\n", errno );
char *mKS = masterKeySalt;
memcpy(mKS, mpKeyScope, strlen(mpKeyScope)); mKS += strlen(mpKeyScope);
memcpy(mKS, &n_fullNameLength, sizeof(n_fullNameLength)); mKS += sizeof(n_fullNameLength);
memcpy(mKS, fullName, strlen(fullName)); mKS += strlen(fullName);
if (mKS - masterKeySalt != masterKeySaltLength)
abort();
memcpy( mKS, mpKeyScope, strlen( mpKeyScope ) );
mKS += strlen( mpKeyScope );
memcpy( mKS, &n_fullNameLength, sizeof( n_fullNameLength ) );
mKS += sizeof( n_fullNameLength );
memcpy( mKS, fullName, strlen( fullName ) );
mKS += strlen( fullName );
trc( "fullName: %s\n", fullName );
trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope );
trc( "masterKeySalt ID: %s\n", IDForBuf( masterKeySalt, masterKeySaltLength ) );
if (mKS - masterKeySalt != masterKeySaltLength)
ftl( "Unexpected master key salt length." );
// Calculate the master key.
uint8_t *masterKey = (uint8_t *)malloc( MP_dkLen );
if (!masterKey) {
fprintf( stderr, "Could not allocate master key: %d\n", errno );
return 1;
abort();
}
if (crypto_scrypt( (const uint8_t *)masterPassword, strlen(masterPassword), (const uint8_t *)masterKeySalt, masterKeySaltLength, MP_N, MP_r, MP_p, masterKey, MP_dkLen ) < 0) {
if (crypto_scrypt( (const uint8_t *)masterPassword, strlen( masterPassword ), (const uint8_t *)masterKeySalt, masterKeySaltLength, MP_N,
MP_r, MP_p, masterKey, MP_dkLen ) < 0) {
fprintf( stderr, "Could not generate master key: %d\n", errno );
return 1;
abort();
}
memset( masterKeySalt, 0, masterKeySaltLength );
free( masterKeySalt );
struct timeval endTime;
if (gettimeofday(&endTime, NULL) != 0) {
fprintf(stderr, "Could not get time: %d\n", errno);
return 1;
trc( "masterKey ID: %s\n", IDForBuf( masterKey, MP_dkLen ) );
return masterKey;
}
long long secs = (endTime.tv_sec - startTime.tv_sec);
long long usecs = (endTime.tv_usec - startTime.tv_usec);
double elapsed = secs + usecs / 1000000.0;
trc("masterKey ID: %s (derived in %.2fs)\n", IDForBuf(masterKey, MP_dkLen), elapsed);
char *mpw_passwordForSite(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
const MPSiteVariant siteVariant, const char *siteContext) {
// Calculate the site seed.
trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter );
trc("siteVariant: %d (%s)\n", siteVariant, siteVariantString);
trc("siteType: %d (%s)\n", siteType, siteTypeString);
trc( "siteVariant: %d\n", siteVariant );
trc( "siteType: %d\n", siteType );
const char *siteScope = ScopeForVariant( siteVariant );
trc("site scope: %s, context: %s\n", siteScope, siteContextString == NULL? "<empty>": siteContextString);
trc( "site scope: %s, context: %s\n", siteScope, siteContext == NULL? "<empty>": siteContext );
const uint32_t n_siteNameLength = htonl( strlen( siteName ) );
const uint32_t n_siteCounter = htonl( siteCounter );
const uint32_t n_siteContextLength = siteContextString == NULL? 0: htonl(strlen(siteContextString));
const uint32_t n_siteContextLength = siteContext == NULL? 0: htonl( strlen( siteContext ) );
size_t sitePasswordInfoLength = strlen( siteScope ) + sizeof( n_siteNameLength ) + strlen( siteName ) + sizeof( n_siteCounter );
if (siteContextString)
sitePasswordInfoLength += sizeof(n_siteContextLength) + strlen(siteContextString);
if (siteContext)
sitePasswordInfoLength += sizeof( n_siteContextLength ) + strlen( siteContext );
char *sitePasswordInfo = (char *)malloc( sitePasswordInfoLength );
if (!sitePasswordInfo) {
fprintf( stderr, "Could not allocate site seed: %d\n", errno );
return 1;
abort();
}
char *sPI = sitePasswordInfo;
memcpy(sPI, siteScope, strlen(siteScope)); sPI += strlen(siteScope);
memcpy(sPI, &n_siteNameLength, sizeof(n_siteNameLength)); sPI += sizeof(n_siteNameLength);
memcpy(sPI, siteName, strlen(siteName)); sPI += strlen(siteName);
memcpy(sPI, &n_siteCounter, sizeof(n_siteCounter)); sPI += sizeof(n_siteCounter);
if (siteContextString) {
memcpy(sPI, &n_siteContextLength, sizeof(n_siteContextLength)); sPI += sizeof(n_siteContextLength);
memcpy(sPI, siteContextString, strlen(siteContextString)); sPI += strlen(siteContextString);
memcpy( sPI, siteScope, strlen( siteScope ) );
sPI += strlen( siteScope );
memcpy( sPI, &n_siteNameLength, sizeof( n_siteNameLength ) );
sPI += sizeof( n_siteNameLength );
memcpy( sPI, siteName, strlen( siteName ) );
sPI += strlen( siteName );
memcpy( sPI, &n_siteCounter, sizeof( n_siteCounter ) );
sPI += sizeof( n_siteCounter );
if (siteContext) {
memcpy( sPI, &n_siteContextLength, sizeof( n_siteContextLength ) );
sPI += sizeof( n_siteContextLength );
memcpy( sPI, siteContext, strlen( siteContext ) );
sPI += strlen( siteContext );
}
if (sPI - sitePasswordInfo != sitePasswordInfoLength)
abort();
trc("seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n", siteScope, Hex(&n_siteNameLength, sizeof(n_siteNameLength)), siteName, Hex(&n_siteCounter, sizeof(n_siteCounter)), Hex(&n_siteContextLength, sizeof(n_siteContextLength)), siteContextString);
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n", siteScope,
Hex( &n_siteNameLength, sizeof( n_siteNameLength ) ), siteName, Hex( &n_siteCounter, sizeof( n_siteCounter ) ),
Hex( &n_siteContextLength, sizeof( n_siteContextLength ) ), siteContext );
trc( "sitePasswordInfo ID: %s\n", IDForBuf( sitePasswordInfo, sitePasswordInfoLength ) );
uint8_t sitePasswordSeed[32];
HMAC_SHA256_Buf( masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoLength, sitePasswordSeed );
memset(masterKey, 0, MP_dkLen);
memset( sitePasswordInfo, 0, sitePasswordInfoLength );
free(masterKey);
free( sitePasswordInfo );
trc( "sitePasswordSeed ID: %s\n", IDForBuf( sitePasswordSeed, 32 ) );
// Determine the template.
const char *template = TemplateForType( siteType, sitePasswordSeed[0] );
trc("type %s, template: %s\n", siteTypeString, template);
trc( "type %d, template: %s\n", siteType, template );
if (strlen( template ) > 32)
abort();
@ -302,11 +127,10 @@ int main(int argc, char *const argv[]) {
char *sitePassword = (char *)calloc( strlen( template ) + 1, sizeof( char ) );
for (int c = 0; c < strlen( template ); ++c) {
sitePassword[c] = CharacterFromClass( template[c], sitePasswordSeed[c + 1] );
trc("class %c, index %u (0x%02X) -> character: %c\n", template[c], sitePasswordSeed[c + 1], sitePasswordSeed[c + 1], sitePassword[c]);
trc( "class %c, index %u (0x%02X) -> character: %c\n", template[c], sitePasswordSeed[c + 1], sitePasswordSeed[c + 1],
sitePassword[c] );
}
memset( sitePasswordSeed, 0, sizeof( sitePasswordSeed ) );
// Output the password.
fprintf( stdout, "%s\n", sitePassword );
return 0;
return sitePassword;
}

6
MasterPassword/C/mpw.h Normal file
View File

@ -0,0 +1,6 @@
uint8_t *mpw_masterKeyForUser(
const char *fullName, const char *masterPassword);
char *mpw_passwordForSite(
const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
const MPSiteVariant siteVariant, const char *siteContext);

View File

@ -48,6 +48,7 @@ typedef enum {
#else
#define trc(...) do {} while (0)
#endif
#define ftl(...) do { fprintf( stderr, "Could not allocate master key salt: %d\n", errno ); abort(); } while (0)
const MPSiteVariant VariantWithName(const char *variantName);
const char *ScopeForVariant(MPSiteVariant variant);

View File

@ -46,6 +46,7 @@
93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; };
93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; };
93D39AA4A0BE66A872CCC02E /* NSPersistentStore+PearlMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D397F4BAFFF7CF3F1B21A4 /* NSPersistentStore+PearlMigration.h */; };
93D39B35D4B8E87ADEC05246 /* mpw-cli.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D398121C8F063A3637144E /* mpw-cli.c */; };
93D39B429C67A62E29DC02DA /* MPRootSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */; };
93D39B76DD5AB108BA8928E8 /* UIScrollView+PearlAdjustInsets.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */; };
93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */; };
@ -498,8 +499,10 @@
93D3979016BF0C5B29D1340D /* distribute */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = distribute; sourceTree = "<group>"; };
93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogsViewController.m; sourceTree = "<group>"; };
93D397F4BAFFF7CF3F1B21A4 /* NSPersistentStore+PearlMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPersistentStore+PearlMigration.h"; sourceTree = "<group>"; };
93D398121C8F063A3637144E /* mpw-cli.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-cli.c"; sourceTree = "<group>"; };
93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlNavigationController.h; sourceTree = "<group>"; };
93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; };
93D3990D850D76A94C6B7A4D /* mpw.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpw.h; sourceTree = "<group>"; };
93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPWebViewController.m; sourceTree = "<group>"; };
93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRootSegue.m; sourceTree = "<group>"; };
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoachmarkViewController.m; sourceTree = "<group>"; };
@ -1612,6 +1615,8 @@
93D3979016BF0C5B29D1340D /* distribute */,
93D39245A478883C672818F3 /* mpw.bashrc */,
93D39B70138D0E28F7D5E86B /* mpw-bench.c */,
93D398121C8F063A3637144E /* mpw-cli.c */,
93D3990D850D76A94C6B7A4D /* mpw.h */,
);
name = C;
path = ../../C;
@ -3626,6 +3631,7 @@
93D39B429C67A62E29DC02DA /* MPRootSegue.m in Sources */,
93D392FD5E2052F7D7DB3774 /* NSString+MPMarkDown.m in Sources */,
93D395B715D15F2B56F2A2EE /* types.c in Sources */,
93D39B35D4B8E87ADEC05246 /* mpw-cli.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};