/***************************************************************************** * Author: Valient Gough * ***************************************************************************** * 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 "fs/FileUtils.h" #include "fs/Context.h" #include "fs/FileNode.h" #include "fs/DirNode.h" #include #include #include #include #include #include #include #include #ifdef HAVE_SSL #define NO_DES #include #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 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")}, {"--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() )) { cerr << 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) cerr << autosprintf(_("encfsctl version %s"), VERSION) << "\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 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 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; iroot->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; iroot->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 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 int processContents( const shared_ptr &rootInfo, const char *path, T &op ) { int errCode = 0; shared_ptr 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; iread(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 &rootInfo, const string &cpath, const string &destName ) { vector 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 &rootInfo, const char* encfsName, const char* targetName) { shared_ptr 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 &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 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 &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, "/" ); cerr << 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 = 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; }