encfs/util/encfsctl.cpp
Valient Gough f00ef5c6d3 break out cipher primitives, add unit tests
git-svn-id: http://encfs.googlecode.com/svn/trunk@94 db9cf616-1c43-0410-9cb8-a902689de0d6
2013-03-05 06:32:27 +00:00

887 lines
22 KiB
C++

/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software; you can distribute it and/or modify it under
* the terms of the GNU General Public License (GPL), as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include "fs/encfs.h"
#include "base/autosprintf.h"
#include "base/config.h"
#include "base/Error.h"
#include "base/i18n.h"
#include "cipher/Cipher.h"
#include "cipher/BlockCipher.h"
#include "cipher/MAC.h"
#include "cipher/StreamCipher.h"
#include "fs/FileUtils.h"
#include "fs/Context.h"
#include "fs/FileNode.h"
#include "fs/DirNode.h"
#include <glog/logging.h>
#include <iostream>
#include <string>
#include <cstdio>
#include <list>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_SSL
#define NO_DES
#include <openssl/ssl.h>
#endif
using namespace std;
using namespace gnu;
using namespace encfs;
static int showInfo( int argc, char **argv );
static int showVersion( int argc, char **argv );
static int showCiphers( int argc, char **argv );
static int chpasswd( int argc, char **argv );
static int chpasswdAutomaticly( int argc, char **argv );
static int cmd_ls( int argc, char **argv );
static int cmd_decode( int argc, char **argv );
static int cmd_encode( int argc, char **argv );
static int cmd_showcruft( int argc, char **argv );
static int cmd_cat( int argc, char **argv );
static int cmd_export( int argc, char **argv );
static int cmd_showKey( int argc, char **argv );
struct CommandOpts
{
const char *name;
int minOptions;
int maxOptions;
int (*func)(int argc, char **argv);
const char *argStr;
const char *usageStr;
} commands[] =
{
{"info", 1, 1, showInfo, "(root dir)",
// xgroup(usage)
gettext_noop(" -- show information (Default command)")},
{"showKey", 1, 1, cmd_showKey, "(root dir)",
// xgroup(usage)
gettext_noop(" -- show key")},
{"passwd", 1, 1, chpasswd, "(root dir)",
// xgroup(usage)
gettext_noop(" -- change password for volume")},
{"autopasswd", 1, 1, chpasswdAutomaticly, "(root dir)",
// xgroup(usage)
gettext_noop(" -- change password for volume, taking password"
" from standard input.\n\tNo prompts are issued.")},
{"ls", 1, 2, cmd_ls, 0,0},
{"showcruft", 1, 1, cmd_showcruft, "(root dir)",
// xgroup(usage)
gettext_noop(" -- show undecodable filenames in the volume")},
{"cat", 2, 2, cmd_cat, "(root dir) path",
// xgroup(usage)
gettext_noop(" -- decodes the file and cats it to standard out")},
{"decode", 1, 100, cmd_decode, "[--extpass=prog] (root dir) [encoded-name ...]",
// xgroup(usage)
gettext_noop(" -- decodes name and prints plaintext version")},
{"encode", 1, 100, cmd_encode, "[--extpass=prog] (root dir) [plaintext-name ...]",
// xgroup(usage)
gettext_noop(" -- encodes a filename and print result")},
{"export", 2, 2, cmd_export, "(root dir) path",
// xgroup(usage)
gettext_noop(" -- decrypts a volume and writes results to path")},
{"--ciphers", 0, 0, showCiphers, "",
// xgroup(usage)
gettext_noop(" -- show available ciphers")},
{"--version", 0, 0, showVersion, "",
// xgroup(usage)
gettext_noop(" -- print version number and exit")},
{0,0,0,0,0,0}
};
static
void usage(const char *name)
{
cerr << autosprintf(_("encfsctl version %s"), VERSION) << "\n"
<< _("Usage:\n")
// displays usage commands, eg "./encfs (root dir) ..."
// xgroup(usage)
<< autosprintf(_("%s (root dir)\n"
" -- displays information about the filesystem, or \n"), name);
int offset = 0;
while(commands[offset].name != 0)
{
if( commands[offset].argStr != 0 )
{
cerr << "encfsctl " << commands[offset].name << " "
<< commands[offset].argStr << "\n"
<< gettext( commands[offset].usageStr ) << "\n";
}
++offset;
}
cerr << "\n"
// xgroup(usage)
<< autosprintf(_("Example: \n%s info ~/.crypt\n"), name)
<< "\n";
}
static bool checkDir( string &rootDir )
{
if( !isDirectory( rootDir.c_str() ))
{
cout << autosprintf(_("directory %s does not exist.\n"),
rootDir.c_str());
return false;
}
if(rootDir[ rootDir.length()-1 ] != '/')
rootDir.append("/");
return true;
}
static int showVersion( int argc, char **argv )
{
(void)argc;
(void)argv;
// xgroup(usage)
cout << autosprintf(_("encfsctl version %s"), VERSION) << "\n";
return EXIT_SUCCESS;
}
static int showCiphers( int argc, char **argv )
{
(void)argc;
(void)argv;
list<string> names = BlockCipher::GetRegistry().GetAll();
for (const string& name : names) {
auto props = BlockCipher::GetRegistry().GetProperties(name.c_str());
cout << _("Implementation: ") << name << "\n";
cout << "\t" << _("Block cipher: ") << props->cipher << " / " << props->mode
<< " ( " << autosprintf(_("via %s"), props->library.c_str()) << " )\n";
cout << "\t" << _("Key Sizes: ") << props->keySize << "\n";
}
names = StreamCipher::GetRegistry().GetAll();
for (const string& name : names) {
auto props = StreamCipher::GetRegistry().GetProperties(name.c_str());
cout << _("Implementation: ") << name << "\n";
cout << "\t" << _("Stream cipher: ") << props->cipher << " / " << props->mode
<< " ( " << autosprintf(_("via %s"), props->library.c_str()) << " )\n";
cout << "\t" << _("Key Sizes: ") << props->keySize << "\n";
}
names = MessageAuthenticationCode::GetRegistry().GetAll();
for (const string& name : names) {
auto props = MessageAuthenticationCode::GetRegistry()
.GetProperties(name.c_str());
cout << _("Implementation: ") << name << "\n";
cout << "\t" << _("HMAC: ") << props->hashFunction << " / " << props->mode
<< " ( " << autosprintf(_("via %s"), props->library.c_str()) << " )\n";
cout << "\t" << _("Block size: ") << props->blockSize << "\n";
}
return EXIT_SUCCESS;
}
static int showInfo( int argc, char **argv )
{
(void)argc;
string rootDir = argv[1];
if( !checkDir( rootDir ))
return EXIT_FAILURE;
EncfsConfig config;
ConfigType type = readConfig( rootDir, config );
// show information stored in config..
switch(type)
{
case Config_None:
// xgroup(diag)
cout << _("Unable to load or parse config file\n");
return EXIT_FAILURE;
case Config_Prehistoric:
// xgroup(diag)
cout << _("A really old EncFS filesystem was found. \n"
"It is not supported in this EncFS build.\n");
return EXIT_FAILURE;
case Config_V3:
// xgroup(diag)
cout << "\n" << autosprintf(_("Version 3 configuration; "
"created by %s\n"), config.creator().c_str());
break;
case Config_V4:
// xgroup(diag)
cout << "\n" << autosprintf(_("Version 4 configuration; "
"created by %s\n"), config.creator().c_str());
break;
case Config_V5:
case Config_V6:
case Config_V7:
// xgroup(diag)
cout << "\n" << autosprintf(_("Version %i configuration; "
"created by %s (revision %i)\n"),
type,
config.creator().c_str(),
config.revision());
break;
}
showFSInfo( config );
return EXIT_SUCCESS;
}
static RootPtr initRootInfo(int &argc, char ** &argv)
{
RootPtr result;
shared_ptr<EncFS_Opts> opts( new EncFS_Opts() );
opts->createIfNotFound = false;
opts->checkKey = false;
static struct option long_options[] = {
{"extpass", 1, 0, 'p'},
{0,0,0,0}
};
for(;;)
{
int option_index = 0;
int res = getopt_long( argc, argv, "",
long_options, &option_index);
if(res == -1)
break;
switch(res)
{
case 'p':
opts->passwordProgram.assign(optarg);
break;
default:
LOG(WARNING) << "getopt error: " << res;
break;
}
}
argc -= optind;
argv += optind;
if(argc == 0)
{
cerr << _("Incorrect number of arguments") << "\n";
} else
{
opts->rootDir = string( argv[0] );
--argc;
++argv;
if(checkDir( opts->rootDir ))
result = initFS( NULL, opts );
if(!result)
cerr << _("Unable to initialize encrypted filesystem - check path.\n");
}
return result;
}
static RootPtr initRootInfo(const char* crootDir)
{
string rootDir(crootDir);
RootPtr result;
if(checkDir( rootDir ))
{
shared_ptr<EncFS_Opts> opts( new EncFS_Opts() );
opts->rootDir = rootDir;
opts->createIfNotFound = false;
opts->checkKey = false;
result = initFS( NULL, opts );
}
if(!result)
cerr << _("Unable to initialize encrypted filesystem - check path.\n");
return result;
}
static int cmd_showKey( int argc, char **argv )
{
RootPtr rootInfo = initRootInfo(argv[1]);
if(!rootInfo)
return EXIT_FAILURE;
else
{
// encode with itself
string b64Key = rootInfo->cipher->encodeAsString(
rootInfo->volumeKey, rootInfo->volumeKey );
cout << b64Key << "\n";
return EXIT_SUCCESS;
}
}
static int cmd_decode( int argc, char **argv )
{
RootPtr rootInfo = initRootInfo(argc, argv);
if(!rootInfo)
return EXIT_FAILURE;
if(argc > 0)
{
for(int i=0; i<argc; ++i)
{
string name = rootInfo->root->plainPath( argv[i] );
cout << name << "\n";
}
} else
{
char buf[PATH_MAX+1];
while(cin.getline(buf,PATH_MAX))
{
cout << rootInfo->root->plainPath( buf ) << "\n";
}
}
return EXIT_SUCCESS;
}
static int cmd_encode( int argc, char **argv )
{
RootPtr rootInfo = initRootInfo(argc, argv);
if(!rootInfo)
return EXIT_FAILURE;
if(argc > 0)
{
for(int i=0; i<argc; ++i)
{
string name = rootInfo->root->cipherPathWithoutRoot(argv[i]);
cout << name << "\n";
}
} else
{
char buf[PATH_MAX+1];
while(cin.getline(buf,PATH_MAX))
{
cout << rootInfo->root->cipherPathWithoutRoot( buf ) << "\n";
}
}
return EXIT_SUCCESS;
}
static int cmd_ls( int argc, char **argv )
{
(void)argc;
RootPtr rootInfo = initRootInfo(argv[1]);
if(!rootInfo)
return EXIT_FAILURE;
// show files in directory
{
DirTraverse dt = rootInfo->root->openDir("/");
if(dt.valid())
{
for(string name = dt.nextPlaintextName(); !name.empty();
name = dt.nextPlaintextName())
{
shared_ptr<FileNode> fnode =
rootInfo->root->lookupNode( name.c_str(), "encfsctl-ls" );
struct stat stbuf;
fnode->getAttr( &stbuf );
struct tm stm;
localtime_r( &stbuf.st_mtime, &stm );
stm.tm_year += 1900;
// TODO: when I add "%s" to the end and name.c_str(), I get a
// seg fault from within strlen. Why ???
printf("%11i %4i-%02i-%02i %02i:%02i:%02i %s\n",
int(stbuf.st_size),
int(stm.tm_year), int(stm.tm_mon), int(stm.tm_mday),
int(stm.tm_hour), int(stm.tm_min), int(stm.tm_sec),
name.c_str());
}
}
}
return EXIT_SUCCESS;
}
// apply an operation to every block in the file
template<typename T>
int processContents( const shared_ptr<EncFS_Root> &rootInfo,
const char *path, T &op )
{
int errCode = 0;
shared_ptr<FileNode> node = rootInfo->root->openNode( path, "encfsctl",
O_RDONLY, &errCode );
if(!node)
{
// try treating filename as an enciphered path
string plainName = rootInfo->root->plainPath( path );
node = rootInfo->root->lookupNode( plainName.c_str(), "encfsctl" );
if(node)
{
errCode = node->open( O_RDONLY );
if(errCode < 0)
node.reset();
}
}
if(!node)
{
cerr << "unable to open " << path << "\n";
return errCode;
} else
{
unsigned char buf[512];
int blocks = (node->getSize() + sizeof(buf)-1) / sizeof(buf);
// read all the data in blocks
for(int i=0; i<blocks; ++i)
{
int bytes = node->read(i*sizeof(buf), buf, sizeof(buf));
int res = op(buf, bytes);
if(res < 0)
return res;
}
}
return 0;
}
class WriteOutput
{
int _fd;
public:
WriteOutput(int fd) { _fd = fd; }
~WriteOutput() { close(_fd); }
int operator()(const void *buf, int count)
{
return (int)write(_fd, buf, count);
}
};
static int cmd_cat( int argc, char **argv )
{
(void)argc;
RootPtr rootInfo = initRootInfo(argv[1]);
if(!rootInfo)
return EXIT_FAILURE;
const char *path = argv[2];
WriteOutput output(STDOUT_FILENO);
int errCode = processContents( rootInfo, path, output );
return errCode;
}
static int copyLink(const struct stat &stBuf,
const shared_ptr<EncFS_Root> &rootInfo,
const string &cpath, const string &destName )
{
vector<char> buf(stBuf.st_size+1, 0);
int res = ::readlink( cpath.c_str(), &buf[0], stBuf.st_size );
if(res == -1)
{
cerr << "unable to readlink of " << cpath << "\n";
return EXIT_FAILURE;
}
buf[res] = '\0';
string decodedLink = rootInfo->root->plainPath(&buf[0]);
res = ::symlink( decodedLink.c_str(), destName.c_str() );
if(res == -1)
{
cerr << "unable to create symlink for " << cpath
<< " to " << decodedLink << "\n";
}
return EXIT_SUCCESS;
}
static int copyContents(const shared_ptr<EncFS_Root> &rootInfo,
const char* encfsName, const char* targetName)
{
shared_ptr<FileNode> node =
rootInfo->root->lookupNode( encfsName, "encfsctl" );
if(!node)
{
cerr << "unable to open " << encfsName << "\n";
return EXIT_FAILURE;
} else
{
struct stat st;
if(node->getAttr(&st) != 0)
return EXIT_FAILURE;
if((st.st_mode & S_IFLNK) == S_IFLNK)
{
string d = rootInfo->root->cipherPath(encfsName);
char linkContents[PATH_MAX+2];
if(readlink (d.c_str(), linkContents, PATH_MAX + 1) <= 0)
{
cerr << "unable to read link " << encfsName << "\n";
return EXIT_FAILURE;
}
symlink(rootInfo->root->plainPath(linkContents).c_str(),
targetName);
} else
{
int outfd = creat(targetName, st.st_mode);
WriteOutput output(outfd);
processContents( rootInfo, encfsName, output );
}
}
return EXIT_SUCCESS;
}
static bool endsWith(const string &str, char ch)
{
if(str.empty())
return false;
else
return str[str.length()-1] == ch;
}
static int traverseDirs(const shared_ptr<EncFS_Root> &rootInfo,
string volumeDir, string destDir)
{
if(!endsWith(volumeDir, '/'))
volumeDir.append("/");
if(!endsWith(destDir, '/'))
destDir.append("/");
// Lookup directory node so we can create a destination directory
// with the same permissions
{
struct stat st;
shared_ptr<FileNode> dirNode =
rootInfo->root->lookupNode( volumeDir.c_str(), "encfsctl" );
if(dirNode->getAttr(&st))
return EXIT_FAILURE;
mkdir(destDir.c_str(), st.st_mode);
}
// show files in directory
DirTraverse dt = rootInfo->root->openDir(volumeDir.c_str());
if(dt.valid())
{
for(string name = dt.nextPlaintextName(); !name.empty();
name = dt.nextPlaintextName())
{
// Recurse to subdirectories
if(name != "." && name != "..")
{
string plainPath = volumeDir + name;
string cpath = rootInfo->root->cipherPath(plainPath.c_str());
string destName = destDir + name;
int r = EXIT_SUCCESS;
struct stat stBuf;
if( !lstat( cpath.c_str(), &stBuf ))
{
if( S_ISDIR( stBuf.st_mode ) )
{
traverseDirs(rootInfo, (plainPath + '/').c_str(),
destName + '/');
} else if( S_ISLNK( stBuf.st_mode ))
{
r = copyLink( stBuf, rootInfo, cpath, destName );
} else
{
r = copyContents(rootInfo, plainPath.c_str(),
destName.c_str());
}
} else
{
r = EXIT_FAILURE;
}
if(r != EXIT_SUCCESS)
return r;
}
}
}
return EXIT_SUCCESS;
}
static int cmd_export( int argc, char **argv )
{
(void)argc;
RootPtr rootInfo = initRootInfo(argv[1]);
if(!rootInfo)
return EXIT_FAILURE;
string destDir = argv[2];
// if the dir doesn't exist, then create it (with user permission)
if(!checkDir(destDir) && !userAllowMkdir(destDir.c_str(), 0700))
return EXIT_FAILURE;
return traverseDirs(rootInfo, "/", destDir);
}
int showcruft( const shared_ptr<EncFS_Root> &rootInfo, const char *dirName )
{
int found = 0;
DirTraverse dt = rootInfo->root->openDir( dirName );
if(dt.valid())
{
bool showedDir = false;
for(string name = dt.nextInvalid(); !name.empty();
name = dt.nextInvalid())
{
string cpath = rootInfo->root->cipherPath( dirName );
cpath += '/';
cpath += name;
if(!showedDir)
{
// just before showing a list of files in a directory
cout << autosprintf(_("In directory %s: \n"), dirName);
showedDir = true;
}
++found;
cout << cpath << "\n";
}
// now go back and look for directories to recurse into..
dt = rootInfo->root->openDir( dirName );
if(dt.valid())
{
for(string name = dt.nextPlaintextName(); !name.empty();
name = dt.nextPlaintextName())
{
if( name == "." || name == "..")
continue;
string plainPath = dirName;
plainPath += '/';
plainPath += name;
string cpath = rootInfo->root->cipherPath( plainPath.c_str() );
if(isDirectory( cpath.c_str() ))
found += showcruft( rootInfo, plainPath.c_str() );
}
}
}
return found;
}
/*
iterate recursively through the filesystem and print out names of files
which have filenames which cannot be decoded with the given key..
*/
static int cmd_showcruft( int argc, char **argv )
{
(void)argc;
RootPtr rootInfo = initRootInfo(argv[1]);
if(!rootInfo)
return EXIT_FAILURE;
int filesFound = showcruft( rootInfo, "/" );
cout << autosprintf("Found %i invalid file(s).", filesFound) << "\n";
return EXIT_SUCCESS;
}
static int do_chpasswd( bool useStdin, bool annotate, int argc, char **argv )
{
(void)argc;
string rootDir = argv[1];
if( !checkDir( rootDir ))
return EXIT_FAILURE;
EncfsConfig config;
ConfigType cfgType = readConfig( rootDir, config );
if(cfgType == Config_None)
{
cout << _("Unable to load or parse config file\n");
return EXIT_FAILURE;
}
// instanciate proper cipher
shared_ptr<Cipher> cipher = getCipher(config);
if(!cipher)
{
cout << autosprintf(_("Unable to find specified cipher \"%s\"\n"),
config.cipher().name().c_str());
return EXIT_FAILURE;
}
// ask for existing password
cout << _("Enter current Encfs password\n");
if (annotate)
cerr << "$PROMPT$ passwd" << endl;
CipherKey userKey = getUserKey( config, useStdin );
if(!userKey)
return EXIT_FAILURE;
// decode volume key using user key -- at this point we detect an incorrect
// password if the key checksum does not match (causing readKey to fail).
CipherKey volumeKey = cipher->readKey(
(const unsigned char *)config.key().ciphertext().data(), userKey );
if(!volumeKey)
{
cout << _("Invalid password\n");
return EXIT_FAILURE;
}
// Now, get New user key..
userKey.reset();
cout << _("Enter new Encfs password\n");
// create new key
if( useStdin )
{
if (annotate)
cerr << "$PROMPT$ new_passwd" << endl;
}
userKey = getNewUserKey( config, useStdin, string(), string() );
// re-encode the volume key using the new user key and write it out..
int result = EXIT_FAILURE;
if(userKey)
{
int encodedKeySize = cipher->encodedKeySize();
unsigned char *keyBuf = new unsigned char[ encodedKeySize ];
// encode volume key with new user key
cipher->writeKey( volumeKey, keyBuf, userKey );
userKey.reset();
EncryptedKey *key = config.mutable_key();
key->set_ciphertext( keyBuf, encodedKeySize );
delete[] keyBuf;
if(saveConfig( rootDir, config ))
{
// password modified -- changes volume key of filesystem..
cout << _("Volume Key successfully updated.\n");
result = EXIT_SUCCESS;
} else
{
cout << _("Error saving modified config file.\n");
}
} else
{
cout << _("Error creating key\n");
}
volumeKey.reset();
return result;
}
static int chpasswd( int argc, char **argv )
{
return do_chpasswd( false, false, argc, argv );
}
static int chpasswdAutomaticly( int argc, char **argv )
{
return do_chpasswd( true, false, argc, argv );
}
int main(int argc, char **argv)
{
FLAGS_logtostderr = 1;
FLAGS_minloglevel = 1;
google::InitGoogleLogging(argv[0]);
google::InstallFailureSignalHandler();
#ifdef LOCALEDIR
setlocale( LC_ALL, "" );
bindtextdomain( PACKAGE, LOCALEDIR );
textdomain( PACKAGE );
#endif
#ifdef HAVE_SSL
SSL_load_error_strings();
SSL_library_init();
#endif
if(argc < 2)
{
usage( argv[0] );
return EXIT_FAILURE;
}
if(argc == 2 && !(*argv[1] == '-' && *(argv[1]+1) == '-'))
{
// default command when only 1 argument given -- treat the argument as
// a directory..
return showInfo( argc, argv );
} else
{
// find the specified command
int offset = 0;
while(commands[offset].name != 0)
{
if(!strcmp( argv[1], commands[offset].name ))
break;
++offset;
}
if(commands[offset].name == 0)
{
cerr << autosprintf(_("invalid command: \"%s\""), argv[1]) << "\n";
} else
{
if((argc-2 < commands[offset].minOptions) ||
(argc-2 > commands[offset].maxOptions))
{
cerr << autosprintf(
_("Incorrect number of arguments for command \"%s\""),
argv[1]) << "\n";
} else
return (*commands[offset].func)( argc-1, argv+1 );
}
}
return EXIT_FAILURE;
}