/***************************************************************************** * 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/CipherV1.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 #include #include #include #include #include #include #include #include using namespace encfs; using gnu::autosprintf; using std::cerr; using std::cin; using std::cout; using std::endl; using std::string; using std::vector; 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")}, {"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"); 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; cout << _("Block modes:\n"); for (const string &name : BlockCipher::GetRegistry().GetAll()) { auto props = BlockCipher::GetRegistry().GetProperties(name.c_str()); cout << _("Implementation: ") << name << "\n"; cout << "\t" << _("Provider: ") << props->library << "\n"; cout << "\t" << _("Block cipher: ") << props->cipher << " / " << props->mode << "\n"; cout << "\t" << _("Key Sizes: ") << props->keySize << "\n"; } cout << "\n"; cout << _("Stream modes:\n"); for (const string &name : StreamCipher::GetRegistry().GetAll()) { auto props = StreamCipher::GetRegistry().GetProperties(name.c_str()); cout << _("Implementation: ") << name << "\n"; cout << "\t" << _("Provider: ") << props->library << "\n"; cout << "\t" << _("Stream cipher: ") << props->cipher << " / " << props->mode << "\n"; cout << "\t" << _("Key Sizes: ") << props->keySize << "\n"; } cout << "\n"; cout << _("MAC modes:\n"); for (const string &name : MAC::GetRegistry().GetAll()) { auto props = MAC::GetRegistry().GetProperties(name.c_str()); cout << _("Implementation: ") << name << "\n"; cout << "\t" << _("Provider: ") << props->library << "\n"; cout << "\t" << _("Hash mode: ") << props->hashFunction << " / " << props->mode << "\n"; cout << "\t" << _("Block size: ") << props->blockSize << "\n"; } cout << "\n"; cout << _("PBKDF modes:\n"); for (const string &name : PBKDF::GetRegistry().GetAll()) { auto props = PBKDF::GetRegistry().GetProperties(name.c_str()); cout << _("Implementation: ") << name << "\n"; cout << "\t" << _("Provider: ") << props->library << "\n"; cout << "\t" << _("Mode: ") << props->mode << "\n"; } cout << "\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); 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 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; 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 &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, "/"); 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; } // ask for existing password cout << _("Enter current Encfs password\n"); if (annotate) cerr << "$PROMPT$ passwd" << endl; CipherKey userKey = getUserKey(config, useStdin); if (!userKey.valid()) 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; } cipher->setKey(userKey); // 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(), true); if (!volumeKey.valid()) { cout << _("Invalid password\n"); return EXIT_FAILURE; } cipher->setKey(volumeKey); // 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.valid()) { int encodedKeySize = cipher->encodedKeySize(); unsigned char *keyBuf = new unsigned char[encodedKeySize]; // encode volume key with new user key cipher->setKey(userKey); cipher->writeKey(volumeKey, keyBuf); userKey.reset(); cipher->setKey(volumeKey); 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; #ifdef DECLARE_VARIABLE google::ParseCommandLineFlags(&argc, &argv, true); #endif google::InitGoogleLogging(argv[0]); google::InstallFailureSignalHandler(); #ifdef LOCALEDIR setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif bool isThreaded = false; CipherV1::init(isThreaded); if (argc < 2) { usage(argv[0]); return EXIT_FAILURE; } // 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); } CipherV1::shutdown(isThreaded); return EXIT_FAILURE; }