/*
pwsafe - commandline tool compatible with Counterpane's Passwordsafe
Copyright (C) 2004-2005 Nicolas S. Dade
$Id: pwsafe.cpp,v 1.56 2005/09/30 10:30:56 ndade Exp $
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, 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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if HAVE_SIGNAL_H
#include <signal.h>
#endif
#if HAVE_GETOPT_H // freebsd for example doesn't have getopt.h but includes getopt() inside unistd.h
#include <getopt.h>
#endif
#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <errno.h>
#include <pwd.h>
#include <regex.h>
#if HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#include <limits.h>
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#if HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#if HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#include <string>
#include <map>
#include <set>
#include <vector>
#include <algorithm>
#include <memory>
#include <fstream>
#include "system.h"
#include <termios.h>
#if WITH_READLINE
// fix a few things that system.h setup and that readline.h isn't going to like
#undef ISDIGIT
#undef IN_CTYPE_DOMAIN
#if READLINE_H_NEEDS_EXTERN_C
extern "C" {
#endif
#include <readline/readline.h>
#if READLINE_H_NEEDS_EXTERN_C
} // terminate extern "C"
#endif
#include <curses.h>
#ifdef erase
// some imbecile C programers #define erase() in [n]curses.h, which breaks std::<container>::erase(...)
#undef erase
#endif
#else // WITH_READLINE
// our cheap substitute for readline
static char* readline(const char*);
#endif // WITH_READLINE
#ifndef HAS_GETOPT_LONG
// our cheap substitute for getopt_long
// for testing we might have included a getopt.h that did include getopt_long, so
#ifdef no_argument
#undef no_argument
#undef required_argument
#undef optional_argument
#endif
struct long_option {
const char* name;
int has_arg;
int* flag;
int val;
};
static const int no_argument = 0;
static const int required_argument = 1;
// we don't support optional_argument in our cheap getopt_long
static int getopt_long(int, char*const[], const char*, const long_option*, int*);
#else
typedef struct option long_option;
#endif
#include <netinet/in.h> // for ntohl() to figure out the endianess
#include <openssl/sha.h>
#include <openssl/blowfish.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#ifndef X_DISPLAY_MISSING
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xmu/Atoms.h>
#include <X11/Xmu/WinUtil.h>
#endif
#ifndef HAVE_SOCKLEN_T
typedef int socklen_t;
#endif
#ifndef HAS_GETLINE
static ssize_t getline(char**,size_t*,FILE*);
#endif
// ---- secalloc and secstring classes ------------------------------------
// an (SGI style) allocator class that allocates from secure (non-swapable) storage
class secalloc {
public:
static size_t pagesize;
const static size_t alignsize;
private:
struct Pool {
Pool* next;
char* top;
char* bottom;
char* level;
Pool(size_t);
~Pool();
};
static Pool* pools;
public:
explicit secalloc();
static void init();
static void cleanup();
static void* allocate(size_t);
static void deallocate(void*, size_t);
static void* reallocate(void*, size_t, size_t);
bool operator==(const secalloc&) const { return true; }
bool operator!=(const secalloc&) const { return false; }
// a struct that takes care of calling secalloc::cleanup() when it is destroyed (usefull to ensure that cleanup() is always called)
struct Cleanup {
Cleanup() { secalloc::init(); }
~Cleanup() { secalloc::cleanup(); }
};
};
static secalloc::Cleanup cleanup_secalloc; // so secalloc::cleanup() is always called. Carefull, this must be the first global object so that global secstrings are destroyed first
// There are 4 different allocator interfaces I know of used by g++. g++ 2.9, 3.0, 3.x, x>=1, and 3.4.2.
// I used to try and use std::basic_string, but now I give up and implement my own stupid string
// class. Thank goodness for standards :-(
// a string class for handling strings that must not be swapped out---we use the secure allocator
class secstring {
public:
typedef int size_type;
static const size_type npos = -1;
private:
char* txt;
size_type len; // length of text
size_type res; // length of buffer
static char null_string;
void construct(const char*, size_type, const char*, size_type);
void deallocate() {
if (txt != &null_string)
secalloc::deallocate(txt,res+1);
}
public:
secstring() : txt(&null_string), len(0), res(0) {}
secstring(const secstring&);
secstring(const char*);
secstring(const char*, size_type);
secstring(const char*, const char*);
secstring(const char*, size_type, const char*, size_type);
~secstring();
bool operator == (const secstring&) const;
bool operator != (const secstring& s) const { return ! operator==(s); }
bool operator < (const secstring&) const;
size_type find(char);
size_type find_first_not_of(char);
size_type find_last_not_of(char);
const char& operator[] (size_t i) const { return txt[i]; }
char& operator[] (size_t i) { return txt[i]; }
const char* c_str() const { return txt; }
const char* data() const { return txt; }
size_t length() const { return len; }
bool empty() const { return len == 0; }
secstring& assign(const char*, size_type);
secstring& assign(const char* t) { return assign(t,strlen(t)); }
secstring& operator = (const char* t) { return assign(t); }
secstring& operator = (const secstring& s) { return assign(s.c_str(),s.len); }
secstring& append(const char*, size_type);
secstring& operator += (char c) { return append(&c,1); }
secstring& operator += (const char* t) { return append(t,strlen(t)); }
secstring& operator += (const secstring& s) { return append(s.c_str(),s.len); }
secstring substr(size_type, size_type);
void erase() { operator=(&null_string); }
void reserve(size_type);
void resize(size_type);
typedef const char* const_iterator;
const_iterator begin() const { return txt; }
const_iterator end() const { return txt+len; }
};
char secstring::null_string = '\0';
secstring::secstring(const secstring& s) : txt(&null_string), len(0), res(0) {
assign(s.c_str(),s.length());
}
secstring::secstring(const char* t) : txt(&null_string), len(0), res(0) {
assign(t);
}
secstring::secstring(const char* t, size_type l) : txt(&null_string), len(0), res(0) {
assign(t,l);
}
secstring::secstring(const char* t1, const char* t2) : txt(&null_string), len(0), res(0) {
construct(t1,strlen(t1),t2,strlen(t2));
}
secstring::secstring(const char* t1, size_type l1, const char* t2, size_type l2) : txt(&null_string), len(0), res(0) {
construct(t1,l1,t2,l2);
}
void secstring::construct(const char* t1, size_type l1, const char* t2, size_type l2) {
res = len = l1 + l2;
txt = reinterpret_cast<char*>(secalloc::allocate(res+1));
memcpy(txt,t1,l1);
memcpy(txt+l1,t2,l2);
txt[len] = '\0';
}
secstring::~secstring() {
deallocate();
}
secstring& secstring::assign(const char* t, size_type l) {
if (t != txt) {
deallocate();
res = len = l;
txt = reinterpret_cast<char*>(secalloc::allocate(res+1));
memcpy(txt,t,len);
txt[len] = '\0';
}
return *this;
}
secstring& secstring::append(const char* t, size_type l) {
if (len+l > res)
reserve(len+l);
memcpy(txt+len,t,l);
len += l;
txt[len] = '\0';
return *this;
}
secstring secstring::substr(size_type s, size_type e) {
if (e == npos)
e = len;
return secstring(txt+s,e-s);
}
void secstring::reserve(size_type r) {
if (res < r) {
char* t = reinterpret_cast<char*>(secalloc::allocate(r+1));
memcpy(t,txt,len+1);
deallocate();
txt = t;
res = r;
}
}
void secstring::resize(size_type r) {
reserve(r);
len = r;
txt[r] = '\0';
}
bool secstring::operator==(const secstring& s) const {
return this == &s ||
(len == s.len &&
memcmp(txt,s.txt,len) == 0);
}
bool secstring::operator<(const secstring& s) const {
return strcmp(txt,s.txt) < 0;
}
secstring::size_type secstring::find(char c) {
char* p = strchr(txt,c);
return p ? p-txt : npos;
}
secstring::size_type secstring::find_first_not_of(char c) {
for (size_type p = 0; p < len; p++)
if (txt[p] != c)
return p;
return npos;
}
secstring::size_type secstring::find_last_not_of(char c) {
for (size_type p = len-1; p >= 0; p--)
if (txt[p] != c)
return p;
return npos;
}
secstring operator+(const secstring& t1, const secstring& t2) {
return secstring(t1.c_str(),t1.length(),t2.c_str(),t2.length());
}
secstring operator+(const char* t1, const secstring& t2) {
return secstring(t1,strlen(t1),t2.c_str(),t2.length());
}
secstring operator+(const secstring& t1, const char* t2) {
return secstring(t1.c_str(),t1.length(),t2,strlen(t2));
}
secstring operator+(const secstring& t1, char c) {
return secstring(t1.c_str(),t1.length(),&c,1);
}
// ------ end of fixups for various systems; on to the real program ------
//#define INTERACTIVE_MODE // enable experimental interactive mode
// The name the program was run with, stripped of any leading path
const char *program_name = "pwsafe"; // make sure program_name always points to something valid so we can use it in constructors of globals
uid_t saved_uid;
gid_t saved_gid;
// database version
enum Version { VERSION_UNKNOWN, VERSION_1_7, VERSION_2_0 };
const static char*const VERSION_NAME[] = { "<unknown>", "1.7", "2.0" };
// Option flags and variables
const char* arg_dbname = NULL;
Version arg_dbversion = VERSION_UNKNOWN;
const char* arg_mergedb = NULL;
const char* arg_name = NULL;
enum OP {
OP_NOP, OP_CREATEDB, OP_EXPORTDB, OP_MERGEDB, OP_PASSWD, OP_LIST, OP_EMIT, OP_ADD, OP_EDIT, OP_DELETE,
#ifdef INTERACTIVE_MODE
OP_INTERACT,
#endif
};
OP arg_op = OP_NOP;
//const char* arg_config = NULL;
bool arg_casesensative = false;
bool arg_echo = false;
const char* arg_output = NULL;
FILE* outfile = NULL; // will be arg_output() or stdout
bool arg_username = false;
bool arg_password = false;
bool arg_details = false;
int arg_verbose = 0;
int arg_debug = 0;
#ifndef X_DISPLAY_MISSING
bool arg_xclip = false;
const char* arg_display = NULL;
const char* arg_selection = "both"; // by default copy to primary X selection and clipboard
typedef std::set<std::string> arg_ignore_t;
arg_ignore_t arg_ignore;
static Display* xdisplay = NULL;
#endif
static long_option const long_options[] =
{
// commands
{"createdb", no_argument, 0, 'C'},
{"exportdb", no_argument, 0, 'E'&31},
{"mergedb", required_argument, 0, 'M'&31},
{"passwd", no_argument, 0, 'P'},
{"list", no_argument, 0, 'L'},
{"add", no_argument, 0, 'a'},
{"edit", no_argument, 0, 'e'},
{"delete", no_argument, 0, 'D'},
#ifdef INTERACTIVE_MODE
{"interact", no_argument, 0, 'I'&31},
#endif
// options
// {"config", required_argument, 0, 'F'},
{"file", required_argument, 0, 'f'},
{"case", no_argument, 0 ,'I'},
// options controlling what is outputted
{"long", no_argument, 0, 'l'},
{"username", no_argument, 0, 'u'},
{"password", no_argument, 0, 'p'},
// options controlling where output goes
{"echo", no_argument, 0, 'E'},
{"output", required_argument, 0, 'o'},
{"dbversion", required_argument, 0, 'V'&31},
#ifndef X_DISPLAY_MISSING
{"xclip", no_argument, 0, 'x'},
{"display", required_argument, 0,'d'},
{"selection", required_argument, 0,'s'},
{"ignore", required_argument, 0,'G'},
#endif
// standard stuff
{"quiet", no_argument, 0, 'q'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
{NULL, 0, NULL, 0}
};
static void usage(bool fail);
static int parse(int argc, char **argv);
static const char* pwsafe_strerror(int err); // decodes errno's as well as our negative error codes
#define PWSAFE_ERR_INVALID_DB -1
static char get1char(const char* prompt, int def_val=-1);
static bool getyn(const char* prompt, int def_val=-1);
static inline char get1char(const std::string& prompt, int def_val=-1) { return get1char(prompt.c_str(), def_val); }
static inline char get1char(const secstring& prompt, int def_val=-1) { return get1char(prompt.c_str(), def_val); }
static inline bool getyn(const std::string& prompt, int def_val=-1) { return getyn(prompt.c_str(), def_val); }
static inline bool getyn(const secstring& prompt, int def_val=-1) { return getyn(prompt.c_str(), def_val); }
struct FailEx {}; // thrown to unwind, cleanup and cause main to return 1
struct ExitEx { const int rc; explicit ExitEx(int c) : rc(c) {} }; // thrown to unwind and exit() with rc
// a blowfish data block (8 bytes)
class Block {
private:
BF_LONG block[2];
static void makeLE(unsigned char[8]);
public:
operator BF_LONG*() { return block; }
Block() {}
~Block();
void zero();
void putInt32AndType(int32_t, uint8_t);
int32_t getInt32() const;
uint8_t getType() const;
Block& operator ^=(const Block&);
void read(const unsigned char*, int len);
void write(unsigned char[8]) const;
bool read(FILE*);
bool write(FILE*) const;
};
class DB {
private:
// the file header, which is kept in secalloc just like the secstrings
struct Header {
unsigned char random[8];
unsigned char hash[SHA_DIGEST_LENGTH]; // 20
unsigned char salt[SHA_DIGEST_LENGTH]; // 20
unsigned char iv[8];
Header();
~Header();
void zero();
bool create();
bool resalt();
bool read(FILE*);
bool write(FILE*) const;
// overload new and delete to the Header is kept in secalloc's memory
void* operator new(size_t n) { return secalloc::allocate(n); }
void operator delete(void* p,size_t n) { secalloc::deallocate(p,n); }
};
Header* header;
// the crypto context (exists only when read/writing the database). also kept in secalloc memory
struct Context {
Block cbc;
BF_KEY bf;
const Version& version; // typically points back to DB's Version
Context(const Header&, const secstring& pw, const Version&);
~Context();
// overload new and delete so Context is kept in secalloc's memory
void* operator new(size_t n) { return secalloc::allocate(n); }
void operator delete(void* p,size_t n) { secalloc::deallocate(p,n); }
};
struct Entry {
public:
typedef std::vector< std::pair<unsigned int,secstring> > extras_t;
private:
// the name+login fields are saved as one string in the file for historical reasons (login was added after 1.0), seperated by magic characters we hope you won't use in a name
const static char SPLIT_CHAR = '\xAD';
const static char*const SPLIT_STR; // = " \xAD "
const static char DEFAULT_USER_CHAR = '\xA0';
// version 2 field types
enum Type { NAME=0, UUID=0x1, GROUP = 0x2, TITLE = 0x3, USER = 0x4, NOTES = 0x5, PASSWORD = 0x6,
// future fields: CTIME = 0x7, MTIME = 0x8, ATIME = 0x9, LTIME = 0xa, POLICY = 0xb,
END = 0xff};
static bool read(FILE*, Context&, uint8_t& type, secstring&);
static bool write(FILE*, Context&, uint8_t type, const secstring&);
static bool write(FILE*, Context&, const extras_t&);
public:
const static char*const MAGIC_V2_NAME; // = " !!!Version 2 File Format!!! ..."
const static char*const MAGIC_V2_PASSWORD; // = "2.0"
static secstring the_default_login;
secstring name;
secstring login;
bool default_login;
secstring password;
secstring notes;
// new v2.0 values
secstring uuid; // I exploit the fact that std::string can contain '\0'
secstring group;
// unknown v2.0+ values are stored as binary, so when the file is saved we can restore them (hopefully this doesn't lead to inconsistencies)
extras_t extras;
static void Init(); // computes the_default_login
Entry();
bool read(FILE*, Context&);
bool write(FILE*, Context&) const;
bool operator!=(const Entry&) const;
bool operator==(const Entry& e) const { return !operator!=(e); }
int diff(const Entry&, secstring& summary) const;
secstring diff(const Entry&) const;
secstring groupname() const;
};
typedef std::map<secstring, Entry> entries_t;
entries_t entries;
typedef std::vector<const Entry*> matches_t;
secstring passphrase;
Version version;
secstring v2_preferences;
bool opened; // true after open() has succeeded
bool changed; // unsaved changes have been made
bool backedup; // true after backup() has succeeded
bool overwritten; // true once we start overwriting dbname
bool getkey(bool test, const char* prompt1="Enter passphrase", const char* prompt2="Reenter passphrase"); // get/verify passphrase
bool testkey(const secstring&);
void hashkey(const secstring&, unsigned char test_hash[]);
bool add(const Entry&); // add entry into database
bool del(const Entry&); // remove entry from database
bool find(matches_t&, const char* regex); // find all entries matching regex
const Entry& find1(const char* regex); // find the one entry either == regex or matching; throw FailEx if 0 or >1 match
public:
const std::string dbname_str;
const char*const dbname;
static void Init();
DB(const char* dbname, Version=VERSION_UNKNOWN);
~DB();
static void createdb(const char* dbname);
bool open(const secstring* pw_to_try=NULL); // call getkey(), read file into entries map
void exportdb();
void mergedb(DB&);
void passwd();
void list(const char* regex);
void emit(const char* regex, bool username, bool password);
void add(const char* name);
void edit(const char* regex);
void del(const char* name);
bool is_changed() const { return changed; }
bool backup(); // create ~ file
bool save(); // write out db file (please backup() first if appropriate)
bool restore(); // copy ~ file back to original (only if an earlier call to backup() suceeded)
static const secstring& defaultlogin() { return Entry::the_default_login; }
};
#ifdef INTERACTIVE_MODE
void interactive(DB& db) {
if (!db.open())
throw FailEx();
const char* db_shortname = strrchr(db.dbname, '/');
if (!db_shortname)
db_shortname = db.dbname;
else
db_shortname++;
char* cmdline = NULL;
size_t cmdline_buflen = 0;
while (true) {
printf("pwsafe:%s> ",db_shortname);
fflush(stdout);
ssize_t cmdlen = getline(&cmdline, &cmdline_buflen, stdin);
if (cmdlen == -1)
throw ExitEx(1);
// break cmdline into argc/argv
int argc = 1;
int argv_len = 1;
char** argv = reinterpret_cast<char**>(malloc(sizeof(argv[0])*(argv_len+1))); // +1 for terminating NULL
if (!argv) {
free(cmdline);
throw ExitEx(1);
}
argv[0] = "pwsafe";
{
char* p = cmdline;
while (p-cmdline < cmdlen && *p != '\0' && *p != '\n') {
// advance to the next non-blank char
while (p-cmdline < cmdlen && *p != '\0' && *p != '\n' && isspace(*p))
++p;
// if we reached the end, stop
if (p-cmdline >= cmdlen || *p == '\0' || *p == '\n')
break;
// make sure there's room in argv[argc]
if (argc >= argv_len) {
int new_argv_len = argv_len*2;
char** new_argv = reinterpret_cast<char**>(realloc(argv,sizeof(argv[0]) * (new_argv_len+1))); // +1 for terminating NULL
if (!new_argv) {
free(argv);
free(cmdline);
throw ExitEx(1);
}
argv = new_argv;
argv_len = new_argv_len;
}
{ // terminate the argument, and handle enclosing "" and '' too, as well as \ sequences
char terminator = ' ';
if (*p == '\'' || *p == '"')
terminator = *p++;
char* q = p;
argv[argc++] = p;
while (p-cmdline < cmdlen && *p != '\0' && *p != '\n' && *p != terminator) {
if (*p == '\\') {
p++;
if (p-cmdline >= cmdlen || *p == '\0' || *p == '\n')
break;
}
*q++ = *p++;
}
// q points just beyond the last char
// skip over the final "" or ''
if (p-cmdline < cmdlen && *p == terminator)
++p;
// terminate the string (which might overwrite the final "" or '')
*q = '\0';
}
}
// terminate argv; getopt_long() expects this
argv[argc] = NULL;
}
// reset some args to default values
arg_mergedb = NULL;
arg_name = NULL;
arg_op = OP_NOP;
arg_casesensative = false;
arg_echo = false;
arg_output = NULL;
arg_username = false;
arg_password = false;
arg_details = false;
#ifndef X_DISPLAY_MISSING
arg_xclip = false;
arg_selection = "both"; // by default copy to primary X selection and clipboard
// leave arg_ignore alone
#endif
// now execute argv
try {
try {
int idx = parse(argc, argv);
if (arg_op == OP_LIST && (arg_username || arg_password))
// this is actually an OP_EMIT and not an OP_LIST
arg_op = OP_EMIT;
if (idx != argc) {
if ((arg_op == OP_LIST || arg_op == OP_EMIT || arg_op == OP_ADD || arg_op == OP_EDIT || arg_op == OP_DELETE) && idx+1 == argc) {
arg_name = argv[idx];
} else {
fprintf(stderr, "%s - Too many arguments\n", program_name);
usage(true);
}
}
if (!arg_dbname) {
// $PWSAFE_DATABASE and $HOME weren't set and -f wasn't used; we have no idea what we should be opening
fprintf(stderr, "$HOME wasn't set; --file must be used\n");
throw FailEx();
}
if (!arg_name && (arg_op == OP_EMIT || arg_op == OP_EDIT || arg_op == OP_DELETE)) {
fprintf(stderr, "An entry must be specified\n");
throw FailEx();
}
if (arg_name && !arg_casesensative) {
// automatically be case sensative of arg_name contains any uppercase chars
const char* p = arg_name;
while (*p)
if (isupper(*p++)) {
arg_casesensative = true;
break;
}
}
#ifndef X_DISPLAY_MISSING
if (arg_xclip && !XDisplayName(arg_display)) {
fprintf(stderr, "$DISPLAY isn't set; use --display\n");
throw FailEx();
}
#endif
// mess around with stdout and outfile so they are intelligently selected
// what we want is usages like "pwsafe | less" to work correctly
if (arg_output) {
outfile = fopen(arg_output,"w");
} else if (!isatty(STDOUT_FILENO) && isatty(STDERR_FILENO)) {
// if stdout is not a tty but stderr is, use stderr to interact with the user, but still write the output to stdout
dup2(STDOUT_FILENO,3);
dup2(STDERR_FILENO,STDOUT_FILENO);
outfile = fdopen(3,"w");
} else {
// use stdout
outfile = fdopen(dup(STDOUT_FILENO),"w");
}
if (!outfile) {
fprintf(stderr, "Can't open %s: %s\n", arg_output, strerror(errno));
throw FailEx();
}
// from this point on stdout points to something we can interact with the user on, and outfile points to where we should put our output
#ifndef X_DISPLAY_MISSING
if (arg_verbose >= 0 && (arg_password || arg_username) && (arg_echo || arg_xclip))
printf("Going to %s %s to %s\n", arg_xclip?"copy":"print", arg_password&&arg_username?"login and password":arg_password?"password":"login", arg_xclip?"X selection":"stdout");
#else
if (arg_verbose >= 0 && (arg_password || arg_username) && (arg_echo))
printf("Going to print %s to stdout\n", arg_password&&arg_username?"login and password":arg_password?"password":"login");
#endif
switch (arg_op) {
case OP_EXPORTDB:
case OP_MERGEDB:
case OP_PASSWD:
case OP_LIST:
case OP_EMIT:
case OP_ADD:
case OP_EDIT:
case OP_DELETE:
{
try {
switch (arg_op) {
case OP_EXPORTDB:
db.exportdb();
break;
case OP_MERGEDB:
{
DB db2(arg_mergedb);
db.mergedb(db2);
}
break;
case OP_PASSWD:
db.passwd();
break;
case OP_LIST:
db.list(arg_name);
break;
case OP_EMIT:
db.emit(arg_name, arg_username, arg_password);
break;
case OP_ADD:
db.add(arg_name);
if (!arg_name) {
// let them add more than one without having to reenter the passphrase
while (getyn("Add another? [n] ", false))
db.add(NULL);
}
break;
case OP_EDIT:
db.edit(arg_name);
break;
case OP_DELETE:
db.del(arg_name);
break;
}
// backup and save if changes have occured
if (db.is_changed()) {
if (arg_verbose > 0) printf("saving changes to %s\n", db.dbname);
if (!(db.backup() && db.save()))
throw FailEx();
}
} catch (const FailEx&) {
// try and restore database from backup if a backup was successfully created
db.restore();
throw;
}
}
break;
}
// first try and close outfile with error checking
if (outfile) {
if (fclose(outfile)) {
fprintf(stderr, "Can't write/close output: %s", strerror(errno));
outfile = NULL;
throw FailEx();
}
outfile = NULL;
}
// and we are done
throw ExitEx(0);
} catch (const FailEx&) {
throw ExitEx(1);
}
} catch (const ExitEx& ex) {
if (outfile)
fclose(outfile);
break;
}
}
}
#endif // INTERACTIVE_MODE
int main(int argc, char **argv) {
program_name = strrchr(argv[0], '/');
if (!program_name)
program_name = argv[0];
else
program_name++;
try {
try {
saved_uid = geteuid();
saved_gid = getegid();
// if we are running suid, drop privileges now; we use seteuid() instead of setuid() so the saved uid remains root and we can become root again in order to mlock()
if (saved_uid != getuid() || saved_gid != getgid()) {
setegid(getgid());
seteuid(getuid());
}
#if WITH_READLINE
rl_readline_name = const_cast<char*>(program_name); // so readline() can parse its config files and handle if (pwsafe) sections; some older readline's type rl_readline_name as char*, hence the const_cast
#endif // WITH_READLINE
// be nice and paranoid
umask(0077);
// init some arguments
{
// use $PWSAFE_DATABASE (which might be a full path or just a filename relative to home), and fall back on ".pwsafe.dat"
const char* datname = getenv("PWSAFE_DATABASE");
if (!datname)
datname = ".pwsafe.dat";
const char* home = getenv("HOME");
if (home && datname[0] != '/') {
char* dbname = reinterpret_cast<char*>(malloc(strlen(home)+1+strlen(datname)+1));
strcpy(dbname, home);
strcat(dbname, "/");
strcat(dbname, datname);
arg_dbname = dbname;
} else {
// datname is already an absolute path
arg_dbname = datname;
}
#ifndef X_DISPLAY_MISSING
if (isatty(STDOUT_FILENO) && (arg_display = XDisplayName(NULL)))
arg_xclip = true;
else
#endif
arg_echo = true;
}
int idx = parse(argc, argv);
#ifndef X_DISPLAY_MISSING
// if no --ignore was specified, use the default
if (arg_ignore.empty()) {
const char* ig = getenv("PWSAFE_IGNORE");
if (!ig) ig = "xclipboard:klipper:wmcliphist";
while (*ig) {
const char*const q = ig;
while (*ig && *ig != ';') ++ig;
arg_ignore.insert(arg_ignore_t::value_type(q,ig-q));
while (*ig == ';') ++ig;
}
}
#endif
if (arg_op == OP_NOP)
// assume --list
arg_op = OP_LIST;
if (arg_op == OP_LIST && (arg_username || arg_password))
// this is actually an OP_EMIT and not an OP_LIST
arg_op = OP_EMIT;
if (idx != argc) {
if ((arg_op == OP_LIST || arg_op == OP_EMIT || arg_op == OP_ADD || arg_op == OP_EDIT || arg_op == OP_DELETE) && idx+1 == argc) {
arg_name = argv[idx];
} else {
fprintf(stderr, "%s - Too many arguments\n", program_name);
usage(true);
}
}
if (!arg_dbname) {
// $PWSAFE_DATABASE and $HOME weren't set and -f wasn't used; we have no idea what we should be opening
fprintf(stderr, "$HOME wasn't set; --file must be used\n");
throw FailEx();
}
if (!arg_name && (arg_op == OP_EMIT || arg_op == OP_EDIT || arg_op == OP_DELETE)) {
fprintf(stderr, "An entry must be specified\n");
throw FailEx();
}
if (arg_name && !arg_casesensative) {
// automatically be case sensative of arg_name contains any uppercase chars
const char* p = arg_name;
while (*p)
if (isupper(*p++)) {
arg_casesensative = true;
break;
}
}
#ifndef X_DISPLAY_MISSING
if (arg_xclip && !XDisplayName(arg_display)) {
fprintf(stderr, "$DISPLAY isn't set; use --display\n");
throw FailEx();
}
#endif
// mess around with stdout and outfile so they are intelligently selected
// what we want is usages like "pwsafe | less" to work correctly
if (arg_output) {
outfile = fopen(arg_output,"w");
} else if (!isatty(STDOUT_FILENO) && isatty(STDERR_FILENO)) {
// if stdout is not a tty but stderr is, use stderr to interact with the user, but still write the output to stdout
dup2(STDOUT_FILENO,3);
dup2(STDERR_FILENO,STDOUT_FILENO);
outfile = fdopen(3,"w");
} else {
// use stdout
outfile = fdopen(dup(STDOUT_FILENO),"w");
}
if (!outfile) {
fprintf(stderr, "Can't open %s: %s\n", arg_output, strerror(errno));
throw FailEx();
}
// from this point on stdout points to something we can interact with the user on, and outfile points to where we should put our output
// seed the random number generator
char rng_filename[1024];
if (RAND_file_name(rng_filename,sizeof(rng_filename))) {
int rc;
if (!strlen(rng_filename)) {
rc = RAND_load_file("/dev/urandom",1024);
} else {
rc = RAND_load_file(rng_filename,-1);
}
if (rc) {
if (arg_verbose > 0) printf("rng seeded with %d bytes from %s\n", rc, rng_filename);
} else {
if (arg_verbose >= -1) // two -q/--quiet's will turn this msg off
fprintf(stderr, "WARNING: %s unable to seed rng from %s\n", program_name, rng_filename);
}
} else {
if (arg_verbose >= -1)
fprintf(stderr, "WARNING: %s unable to seed rng. Check $RANDFILE.\n", program_name);
}
#ifndef X_DISPLAY_MISSING
if (arg_verbose >= 0 && (arg_password || arg_username) && (arg_echo || arg_xclip))
printf("Going to %s %s to %s\n", arg_xclip?"copy":"print", arg_password&&arg_username?"login and password":arg_password?"password":"login", arg_xclip?"X selection":"stdout");
#else
if (arg_verbose >= 0 && (arg_password || arg_username) && (arg_echo))
printf("Going to print %s to stdout\n", arg_password&&arg_username?"login and password":arg_password?"password":"login");
#endif
DB::Init();
switch (arg_op) {
case OP_NOP:
fprintf (stderr, "%s - No command specified\n", program_name);
usage(true);
break;
case OP_CREATEDB:
DB::createdb(arg_dbname);
break;
case OP_EXPORTDB:
case OP_MERGEDB:
case OP_PASSWD:
case OP_LIST:
case OP_EMIT:
case OP_ADD:
case OP_EDIT:
case OP_DELETE:
#ifdef INTERACTIVE_MODE
case OP_INTERACT:
#endif
{
DB db(arg_dbname);
try {
switch (arg_op) {
#ifdef INTERACTIVE_MODE
case OP_INTERACT:
interactive(db);
break;
#endif
case OP_EXPORTDB:
db.exportdb();
break;
case OP_MERGEDB:
{
DB db2(arg_mergedb);
db.mergedb(db2);
}
break;
case OP_PASSWD:
db.passwd();
break;
case OP_LIST:
db.list(arg_name);
break;
case OP_EMIT:
db.emit(arg_name, arg_username, arg_password);
break;
case OP_ADD:
db.add(arg_name);
if (!arg_name) {
// let them add more than one without having to reenter the passphrase
while (getyn("Add another? [n] ", false))
db.add(NULL);
}
break;
case OP_EDIT:
db.edit(arg_name);
break;
case OP_DELETE:
db.del(arg_name);
break;
// cases OP_NOP and OP_CREATEDB were handled earlier, above
}
// backup and save if changes have occured
if (db.is_changed()) {
if (arg_verbose > 0) printf("saving changes to %s\n", db.dbname);
if (!(db.backup() && db.save()))
throw FailEx();
}
} catch (const FailEx&) {
// try and restore database from backup if a backup was successfully created
db.restore();
throw;
}
}
break;
}
// first try and close outfile with error checking
if (outfile) {
if (fclose(outfile)) {
fprintf(stderr, "Can't write/close output: %s", strerror(errno));
outfile = NULL;
throw FailEx();
}
outfile = NULL;
}
// save the rng seed for next time
if (rng_filename[0]) {
int rc = RAND_write_file(rng_filename);
if (arg_verbose > 0) printf("wrote %d bytes to %s\n", rc, rng_filename);
} // else they already got an error above when we tried to read rng_filename
// and we are done
throw ExitEx(0);
} catch (const FailEx&) {
throw ExitEx(1);
}
} catch (const ExitEx& ex) {
#ifndef X_DISPLAY_MISSING
if (xdisplay)
XCloseDisplay(xdisplay);
#endif
if (outfile)
fclose(outfile);
return ex.rc;
}
}
// Set all the option flags according to the switches specified.
// Return the index of the first non-option argument.
static int parse(int argc, char **argv) {
int c;
while ((c = getopt_long (argc, argv,
"l" // long listing
"a" // add
"e" // edit
// "F:" // config
"f:" // file
"I" // case sensative
"E" // echo
"o:" // output
"u" // username
"p" // password
#ifndef X_DISPLAY_MISSING
"x" // xclip
"d:" // display
"s:" // x selection
"G:" // ignore
#endif
"q" // quiet
"v" // verbose
"g" // debug
"h" // help
"V", // version
long_options, (int *) 0)) != EOF)
{
switch (c) {
case 'C':
if (arg_op == OP_NOP)
arg_op = OP_CREATEDB;
else
usage(true);
break;
case 'E'&31:
if (arg_op == OP_NOP)
arg_op = OP_EXPORTDB;
else
usage(true);
break;
case 'M'&31:
if (arg_op == OP_NOP) {
arg_op = OP_MERGEDB;
arg_mergedb = optarg;
} else
usage(true);
break;
case 'P':
if (arg_op == OP_NOP)
arg_op = OP_PASSWD;
else
usage(true);
break;
case 'L':
if (arg_op == OP_NOP)
arg_op = OP_LIST;
else
usage(true);
break;
case 'a':
if (arg_op == OP_NOP)
arg_op = OP_ADD;
else
usage(true);
break;
case 'e':
if (arg_op == OP_NOP)
arg_op = OP_EDIT;
else
usage(true);
break;
case 'D':
if (arg_op == OP_NOP)
arg_op = OP_DELETE;
else
usage(true);
break;
#ifdef INTERACTIVE_MODE
case 'I'&31:
if (arg_op == OP_NOP)
arg_op = OP_INTERACT;
else
usage(true);
break;
#endif
// case 'F':
// arg_config = optarg;
// break;
case 'f':
arg_dbname = optarg;
break;
case 'I':
arg_casesensative = true;
break;
case 'l':
if (arg_op == OP_NOP || arg_op == OP_LIST) {
arg_op = OP_LIST;
arg_details = true;
} else
usage(true);
break;
case 'V'&31:
switch (strtol(optarg, 0, 10)) {
case 1: arg_dbversion = VERSION_1_7; break;
case 2: arg_dbversion = VERSION_2_0; break;
default: usage(true);
}
break;
case 'o':
arg_output = optarg;
// fall through into 'E' since -o implies -e
case 'E':
arg_echo = true;
#ifndef X_DISPLAY_MISSING
arg_xclip = false;
#endif
break;
case 'u':
arg_username = true;
break;
case 'p':
arg_password = true;
break;
#ifndef X_DISPLAY_MISSING
case 'd':
arg_display = optarg;
arg_xclip = true; arg_echo = false; // -d implies -x
break;
case 's':
arg_selection = optarg; // we can't parse it until we open X
// -s implies -x, so no 'break'
case 'x':
arg_xclip = true; arg_echo = false;
break;
case 'G':
arg_ignore.insert(optarg);
break;
#endif
case 'q':
arg_verbose--;
break;
case 'v':
arg_verbose++;
break;
case 'g': // think gcc -g (since -d is taken)
arg_debug++;
break;
case 'V':
printf("pwsafe %s\n", VERSION);
throw ExitEx(0);
case 'h':
usage(false);
throw ExitEx(0);
case ':':
case '?':
// the message getopt() printed out is good enough
throw FailEx();
default:
usage(true);
}
}
return optind;
}
static void usage(bool fail) {
if (!fail)
printf("%s - commandline tool compatible with Counterpane's Passwordsafe\n", program_name);
else
fprintf(stderr,"\n");
fprintf(fail?stderr:stdout,"Usage: %s [OPTION] command [ARG]\n", program_name);
fprintf(fail?stderr:stdout,
"Options:\n"
// " -F, --config=CONFIG_FILE specify a configuration (defaults is ~/.pwsaferc + /etc/pwsaferc)\n"
" -f, --file=DATABASE_FILE specify the database file (default is ~/.pwsafe.dat)\n"
" -I, --case perform case sensative matching\n"
" -l long listing (show username & notes)\n"
" -u, --username emit username of listed account\n"
" -p, --password emit password of listed account\n"
" -E, --echo force echoing of entry to stdout\n"
" -o, --output=FILE redirect output to file (implies -E)\n"
" --dbversion=[1|2] specify database file version (default is 2)\n"
#ifndef X_DISPLAY_MISSING
" -x, --xclip force copying of entry to X selection\n"
" -d, --display=XDISPLAY override $DISPLAY (implies -x)\n"
" -s, --selection={Primary,Secondary,Clipboard,Both} select the X selection effected (implies -x)\n"
" -G, --ignore=NAME@HOST add NAME@HOST to set of windows that don't receive the selection. Either NAME or @HOST can be omitted. (default is xclipboard, wmcliphist and klipper)\n"
#endif
" -q, --quiet print no extra information\n"
" -v, --verbose print more information (can be repeated)\n"
" -h, --help display this help and exit\n"
" -V, --version output version information and exit\n"
"Commands:\n"
" --createdb create an empty database\n"
" --exportdb dump database as text\n"
" --mergedb=DATABASE_FILE2 merge entries from FILE2 into database\n"
" --passwd change database passphrase\n"
" [--list] [REGEX] list all [matching] entries. If -u and/or -p are given, only one entry may match\n"
" -a, --add [NAME] add an entry\n"
" -e, --edit REGEX edit an entry\n"
" --delete NAME delete an entry\n"
);
if (fail)
throw FailEx();
}
static const char* pwsafe_strerror(int err) {
switch (err) {
case 0:
return "Truncated pwsafe database file";
case PWSAFE_ERR_INVALID_DB:
return "Invalid pwsafe database file";
default:
return strerror(err);
}
}
#if WITH_READLINE
// there are 4 variations of readline.h out there, each of which needs a different declaration to compile cleanly
#if READLINE_H_NEEDS_EXTERN_C
extern "C" {
static dummy_completion()
#elif READLINE_H_LACKS_TYPES_FOR_CALLBACKS
static int dummy_completion()
#elif READLINE_H_USES_NO_CONST
static char* dummy_completion(char*, int)
#else
static char* dummy_completion(const char*, int)
#endif
{
return 0;
}
#if READLINE_H_NEEDS_EXTERN_C
} // extern "C"
#endif
#endif // WITH_READLINE
// get a password from the user
static secstring getpw(const char*const prompt) {
// turn off echo
struct termios tio;
tcgetattr(STDIN_FILENO, &tio);
{
struct termios new_tio = tio;
new_tio.c_lflag &= ~(ECHO);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_tio); // FLUSH so they don't get into the habit of typing ahead their passphrase
}
#if WITH_READLINE
rl_completion_entry_function = dummy_completion; // we don't need readline doing any tab completion (and especially not filenames)
#endif
#if READLINE_H_USES_NO_CONST
char* x = readline(const_cast<char*>(prompt));
#else
char* x = readline(prompt);
#endif
// restore echo
tcsetattr(STDIN_FILENO, TCSANOW, &tio);
// echo a linefeed since the user's <Enter> was not echoed
printf("\n");
// see what readline returned
if (x) {
secstring xx(x);
memset(x,0,strlen(x));
free(x);
return xx;
} else {
// EOF/^d; abort
throw FailEx();
}
}
static inline secstring getpw(const std::string& prompt) { return getpw(prompt.c_str()); }
static secstring gettxt(const char*const prompt, const secstring& default_="") {
#if WITH_READLINE
rl_completion_entry_function = dummy_completion; // we don't need readline doing any tab completion (and especially not filenames)
#endif
#if READLINE_H_USES_NO_CONST
char* x = readline(const_cast<char*>(prompt));
#else
char* x = readline(prompt);
#endif
if (x) {
secstring xx(x);
memset(x,0,strlen(x));
free(x);
if (xx.empty())
return default_; // since default is also empty by default, this works out nicely when default is not used
else
return xx;
} else {
// EOF/^d; abort
throw FailEx();
}
}
static inline secstring gettxt(const std::string& prompt, const secstring& default_="") { return gettxt(prompt.c_str(), default_); }
static inline secstring gettxt(const secstring& prompt, const secstring& default_="") { return gettxt(prompt.c_str(), default_); }
static char get1char(const char*const prompt, const int def_val) {
struct termios tio;
tcgetattr(STDIN_FILENO, &tio);
{
termios new_tio = tio;
new_tio.c_lflag &= ~(ICANON);
// now that we turn ICANON off we *must* set VMIN=1 or on sparc the read() buffers 4 at a time
new_tio.c_cc[VMIN] = 1;
new_tio.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
}
while (true) {
printf("%s",prompt);
fflush(stdout);
char x;
ssize_t rc = read(STDIN_FILENO,&x,1);
if (rc == 1) {
switch (x) {
case '\r':
printf("\n");
// fall through to '\n'
case '\n':
if (def_val >= 0) {
tcsetattr(STDIN_FILENO, TCSANOW, &tio);
return def_val;
}
// else there is no default and the user must press a proper char
break;
default:
printf("\n");
tcsetattr(STDIN_FILENO, TCSANOW, &tio);
return x;
}
// if we get this far the user didn't answer, and we loop and reprompt them
}
else {
tcsetattr(STDIN_FILENO, TCSANOW, &tio);
throw FailEx();
}
}
}
static bool getyn(const char*const prompt, const int def_val) {
while (true) {
char c = get1char(prompt, def_val>0?'y':def_val==0?'n':-1);
switch (tolower(c)) {
case 'y':
return true;
case 'n':
return false;
// default: prompt again until we get a good answer
}
}
}
static secstring random_password() {
// here I implement the 'easyvision' mode of pwsafe 1.9.x where the resulting ascii has nice legibility properties for those who copy these by hand
const static char all_alphanum[] = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789";
const static char easyvision_alphanum[] = "abcdefghijkmnopqrstuvwxyz"
"ABCDEFGHJKLMNPQRTUVWXY"
"346789";
const static char easyvision_symbol[] = "+-=_@#$%^&<>/~\\?";
const static char hex_only[] = "0123456789abcdef";
const static char digits_only[] = "0123456789";
int entropy_needed = 20*8; // enough for proper initialization of a SHA1 hash, and enough for proper direct keying of 128-bit block ciphers
int type = 0;
while (true) {
const char* type_name = "";
const char* sets[2] = { "", "" };
int entropy_per_char;
bool one_char_from_each_type = true;
switch (type) {
case 0:
type_name = "alpha/digit/symbol";
sets[0] = all_alphanum;
sets[1] = easyvision_symbol;
entropy_per_char = 628; // 100 * log2(26+26+10+16) = log2(78); best case
break;
case 1:
type_name = "alpha/digit";
sets[0] = all_alphanum;
entropy_per_char = 595;
break;
case 2:
type_name = "easy-to-read alpha/digit";
sets[0] = easyvision_alphanum;
entropy_per_char = 555; // 100 * log2(25+22+10) = log2(57); worse case
break;
case 3:
type_name = "easy-to-read alpha/digit/symbol";
sets[0] = easyvision_alphanum;
sets[1] = easyvision_symbol;
entropy_per_char = 597;
break;
case 4:
type_name = "digits only";
sets[0] = digits_only;
entropy_per_char = 332; // 100 * log2(10)
one_char_from_each_type = false;
break;
case 5:
type_name = "hex digits only";
sets[0] = hex_only;
entropy_per_char = 400; // 100 * log2(16)
one_char_from_each_type = false;
break;
default:
// wrap around back to type 0
type = 0;
continue;
}
const int set0_chars = strlen(sets[0]);
const int total_chars = set0_chars + strlen(sets[1]);
// But we are not going to generate all possible passwords because we are going to exclude those that don't have at least one char from each type, so that reduces the entropy_per_char
// if originally we had 2^(num_chars * entropy_per_char) possible passwords, and we exclude (in the worst case) (and double-counting those passwords that have two types of char missing)
// (57-25)/57 ^ num_chars + (57-22)/57 ^ num_chars + (57-10)/57 ^ num_chars of these, we reduce the bits of entropy per char by
// log2(57)-log2(57-25) + log2(57)-log2(57-22) + log2(57)-log2(57-10) = 1.82 pessimist bits/char
if (one_char_from_each_type)
entropy_per_char -= 182;
const int num_chars = 1+100*entropy_needed/entropy_per_char; // we want 20*8 bits of entropy in our password (thus good enough to create good SHA1 hashes/to key 128-bit key secret key algo's); +1 is in lou of rounding the division properly
secstring pw;
bool got_upper, got_lower, got_num, got_sym;
do {
pw.erase();
got_upper = false, got_lower = false, got_num = false, got_sym = false;
for (int i=0; i<num_chars; i++) {
unsigned char idx;
do {
if (!RAND_bytes(&idx,1)) {
fprintf(stderr, "Can't get random data: %s\n", ERR_error_string(ERR_get_error(), NULL));
throw FailEx();
}
idx &= 0x7f; // might as well strip off the upper bit since total_chars is never more than 64, and such a stripping doesn't change the distribution
} while (idx >= total_chars);
char c;
if (idx < set0_chars)
c = sets[0][idx];
else
c = sets[1][idx-set0_chars];
pw += c;
if (islower(c)) got_lower = true;
else if (isupper(c)) got_upper = true;
else if (isdigit(c)) got_num = true;
else got_sym = true;
}
} while (one_char_from_each_type && (!got_lower || !got_upper || !got_num || (sets[1][0] && !got_sym))); // some systems want one of each type of char in the password, so might as well do it all the time, even though it is a tiny bit less random this way (but we already took that into account in entropy_per_char)
// see what the user thinks of this one
char ent_buf[24];
snprintf(ent_buf, sizeof(ent_buf), "%d", entropy_needed);
ent_buf[sizeof(ent_buf)-1] = '\0';
char len_buf[24];
snprintf(len_buf, sizeof(len_buf), "%d", pw.length());
len_buf[sizeof(len_buf)-1] = '\0';
switch (tolower(get1char("Use "+pw+"\ntype "+type_name+", length "+len_buf+", "+ent_buf+" bits of entropy [y/N/ /+/-/q/?] ? ", 'n'))) {
case 'y':
return pw;
case 'q':
return "";
case ' ':
type++;
break;
case '-':
if (entropy_needed > 128)
entropy_needed -= 32;
else if (entropy_needed > 64)
entropy_needed -= 16;
else if (entropy_needed > 32)
entropy_needed -= 8;
// else you can't go any lower
break;
case '+': case '=':
if (entropy_needed < 64)
entropy_needed += 8;
else if (entropy_needed < 128)
entropy_needed += 16; // so we can hit 112, the magic number for WEP keys
else
entropy_needed += 32;
break;
case '?': case 'h':
printf("Commands:\n"
" Y Yes, accept this password\n"
" N No, generate another password of same type\n"
" <space> Cycle through password types\n"
" - Lower the entropy & password length\n"
" + Raise the entropy & password length\n"
" Q Quit\n"
" ? Help\n");
continue;
// default: show another password
}
}
}
static secstring enter_password(const char* prompt1, const char* prompt2) {
while (true) {
secstring pw1 = getpw(prompt1);
if (pw1.empty()) {
if (getyn("Generate random password? [y] ", true)) {
pw1 = random_password();
if (!pw1.empty())
return pw1;
else
continue; // back to entering by hand for them (perhaps they want to copy only a subset of the original pw)
} // else let them have an empty password, though they'll have to enter it twice
}
secstring pw2 = getpw(prompt2);
if (pw1 == pw2) {
return pw1;
break;
}
printf("Passwords do not match\n");
}
}
// print txt to outfile / copy to X selection
static void emit(const secstring& name, const char*const what, const secstring& txt) {
if (arg_echo) {
if (arg_verbose >= 0 && isatty(fileno(outfile)))
fprintf(outfile,"%s for %s: ", what, name.c_str()); // if we are printing to the tty then we can be more verbose
fprintf(outfile,"%s\n", txt.c_str());
}
#ifndef X_DISPLAY_MISSING
else if (arg_xclip) {
if (!xdisplay) // only open X once, since it is slow
xdisplay = XOpenDisplay(arg_display);
if (!xdisplay) {
fprintf(stderr,"Can't open display: %s\n", XDisplayName(arg_display));
throw FailEx();
}
static const Atom CLIPBOARD = XA_CLIPBOARD(xdisplay); // optimize by fetching this one only once
Atom xsel1 = 0, xsel2 = 0;
int num_sel = 1;
switch (tolower(arg_selection[0])) {
case 'b': case '0': xsel1 = XA_PRIMARY; xsel2 = CLIPBOARD; num_sel = 2; break;
case 'p': case '1': xsel1 = XA_PRIMARY; break;
case 's': case '2': xsel1 = XA_SECONDARY; break;
case 'c': xsel1 = CLIPBOARD; break;
default:
fprintf(stderr,"Unsupported selection: %s\n", arg_selection);
throw FailEx();
}
char* stxt1 = XGetAtomName(xdisplay,xsel1);
char* stxt2 = (xsel2?XGetAtomName(xdisplay,xsel2):NULL);
Window xwin = XCreateSimpleWindow(xdisplay, DefaultRootWindow(xdisplay), 0,0,1,1,0,0,0);
XSelectInput(xdisplay, xwin, PropertyChangeMask);
{ // the X11 ICCCM section 3.2.1 says we must synthesize an event in order to get a timestamp to use with XSetSelectionOwner() instead of using CurrentTime, so we will take the time from the WM_COMMAND event generated inside XSetWMProperties()
const char*const argv[2] = { program_name, NULL }; // lie about our argv so that we don't expose any semi-sensative commandline options
XTextProperty winname = { reinterpret_cast<unsigned char*>(const_cast<char*>(program_name)), XA_STRING, 8, strlen(program_name) };
XSetWMProperties(xdisplay, xwin, &winname,NULL, const_cast<char**>(argv),1, NULL,NULL,NULL); // also init's WM_CLIENT_MACHINE
}
Time timestamp = 0;
Window prev_requestor = 0, prevprev_requestor = 0;
const int xfd = ConnectionNumber(xdisplay);
struct termios tio;
tcgetattr(STDIN_FILENO, &tio);
{
termios new_tio = tio;
new_tio.c_lflag &= ~(ICANON);
// now that we turn ICANON off we *must* set VMIN=1 or on sparc the read() buffers 4 at a time
new_tio.c_cc[VMIN] = 1;
new_tio.c_cc[VTIME] = 0;
// turn off echo too; no need to show them the char they pressed
new_tio.c_lflag &= ~(ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
}
fd_set in;
FD_ZERO(&in);
bool done = false;
while (xsel1 || xsel2) {
if (timestamp && FD_ISSET(STDIN_FILENO, &in)) {
char x;
ssize_t rc = read(STDIN_FILENO,&x,1);
done |= (rc == 1);
}
if (done) {
// we are done
if (xsel1) {
XSetSelectionOwner(xdisplay, xsel1, None, timestamp);
xsel1 = 0;
}
if (xsel2) {
XSetSelectionOwner(xdisplay, xsel2, None, timestamp);
xsel2 = 0;
}
}
while (XPending(xdisplay) > 0) {
XEvent xev;
XNextEvent(xdisplay,&xev);
if (xev.type == PropertyNotify) {
if (!timestamp && xev.xproperty.window == xwin && xev.xproperty.state == PropertyNewValue && xev.xproperty.atom == XA_WM_COMMAND) {
timestamp = xev.xproperty.time; // save away the timestamp; that's all we really wanted
XSetSelectionOwner(xdisplay, xsel1, xwin, timestamp);
if (xsel2)
XSetSelectionOwner(xdisplay, xsel2, xwin, timestamp);
if (xsel2 && XGetSelectionOwner(xdisplay, xsel2) != xwin) {
fprintf(stderr, "Unable to own X selection %s\n", stxt2);
xsel2 = 0;
num_sel--;
}
if (XGetSelectionOwner(xdisplay, xsel1) != xwin) {
fprintf(stderr, "Unable to own X selection %s\n", stxt1);
xsel1 = xsel2;
if (stxt1) XFree(stxt1);
stxt1 = stxt2;
xsel2 = 0; stxt2 = NULL;
num_sel--;
}
// let the user know
if (xsel1 && xsel2) {
if (arg_verbose >= 0)
printf("You are ready to paste the %s for %s from %s and %s\nPress any key when done\n", what, name.c_str(), stxt1, stxt2);
} else if (xsel1) {
if (arg_verbose >= 0)
printf("You are ready to paste the %s for %s from %s\nPress any key when done\n", what, name.c_str(), stxt1);
}
}
}
else if (xev.type == SelectionRequest) {
Atom prop = xev.xselectionrequest.property;
if (prop == None)
prop = xev.xselectionrequest.target; // an old-style client
bool fakeout = false;
// don't answer if the timestamp is too early or too late
if (!timestamp || (xev.xselectionrequest.time!=CurrentTime && xev.xselectionrequest.time < timestamp))
fakeout = true;
// don't answer if we don't actually own it
if ((!xsel1 || xev.xselectionrequest.selection != xsel1) && (!xsel2 || xev.xselectionrequest.selection != xsel2))
fakeout = true;
// don't answer if we aren't the owner
if (xev.xselectionrequest.owner != xwin)
fakeout = true;
if (!fakeout) {
// see what they want exactly
if (xev.xselectionrequest.target == XA_TARGETS(xdisplay)) {
// tell them what we can supply
const Atom targets[] = { XA_TARGETS(xdisplay), XA_TIMESTAMP(xdisplay), XA_TEXT(xdisplay), XA_STRING };
XChangeProperty(xdisplay, xev.xselectionrequest.requestor, prop, XA_TARGETS(xdisplay), 32, PropModeReplace, reinterpret_cast<const unsigned char*>(&targets), sizeof(targets)/sizeof(targets[0]));
}
else if (xev.xselectionrequest.target == XA_TIMESTAMP(xdisplay)) {
XChangeProperty(xdisplay, xev.xselectionrequest.requestor, prop, XA_TIMESTAMP(xdisplay), 32, PropModeReplace, reinterpret_cast<const unsigned char*>(×tamp), 1);
}
else if (xev.xselectionrequest.target == XA_TEXT(xdisplay) ||
xev.xselectionrequest.target == XA_STRING) {
// be very verbose about who is asking for the selection---it could catch a clipboard sniffer
const char*const selection = xev.xselectionrequest.selection == xsel1 ? stxt1 : stxt2; // we know xselectionrequest.selection is xsel1 or xsel2 already, so no need to be more paranoid
// walk up the tree looking for a client window
Window w = xev.xselectionrequest.requestor;
while (true) {
XTextProperty tp = { value: NULL };
int rc = XGetTextProperty(xdisplay, w, &tp, XA_WM_COMMAND);
if (tp.value) XFree(tp.value), tp.value = NULL;
if (!rc) {
rc = XGetWMName(xdisplay, w, &tp);
if (tp.value) XFree(tp.value), tp.value = NULL;
}
if (rc)
break;
Window p = XmuClientWindow(xdisplay, w);
if (w != p)
break; // this means we've found it
Window parent;
Window root;
Window* children = NULL;
unsigned int numchildren;
if (XQueryTree(xdisplay, w, &root, &parent, &children, &numchildren) && children) // unfortunately you can't pass in NULLs to indicate you don't care about the children
XFree(children);
if (parent == root)
break; // we shouldn't go any further or we will read the properties of the root
w = parent;
}
const char* requestor = "<unknown>";
XTextProperty nm = { value: NULL };
if ((XGetWMName(xdisplay, w, &nm) && nm.encoding == XA_STRING && nm.format == 8 && nm.value) ||
(((nm.value?(XFree(nm.value),nm.value=NULL):0), XGetTextProperty(xdisplay, w, &nm, XA_WM_COMMAND)) && nm.encoding == XA_STRING && nm.format == 8 && nm.value)) // try getting WM_COMMAND if we can't get WM_NAME
requestor = reinterpret_cast<const char*>(nm.value);
const char* host = "<unknown>";
XTextProperty cm = { value: NULL };
if (XGetWMClientMachine(xdisplay, w, &cm) && cm.encoding == XA_STRING && cm.format == 8)
host = reinterpret_cast<const char*>(cm.value);
if (arg_ignore.find(requestor) != arg_ignore.end() ||
arg_ignore.find(std::string("@")+host) != arg_ignore.end() ||
arg_ignore.find(requestor+std::string("@")+host) != arg_ignore.end()) {
fakeout = true;
}
if (xev.xselectionrequest.requestor != prev_requestor && xev.xselectionrequest.requestor != prevprev_requestor) { // programs like KDE's Klipper re-request every second, so it isn't very useful to print out multiple times
if (!fakeout) {
if (arg_verbose >= 0)
printf("Sending %s for %s to %s@%s via %s\n", what, name.c_str(), requestor, host, selection);
} else if (arg_verbose > 0)
printf("Ignoring request from %s@%s\n", requestor, host);
}
if (nm.value) XFree(nm.value);
if (cm.value) XFree(cm.value);
if (!fakeout) {
XChangeProperty(xdisplay, xev.xselectionrequest.requestor, prop, XA_STRING, 8, PropModeReplace, reinterpret_cast<const unsigned char*>(txt.c_str()), txt.length());
done = true;
}
prevprev_requestor = prev_requestor;
prev_requestor = xev.xselectionrequest.requestor;
}
else {
// a target I don't handle
fakeout = true;
}
}
if (fakeout)
prop = None; // indicate no answer
XEvent resp;
resp.xselection.property = prop;
resp.xselection.type = SelectionNotify;
resp.xselection.display = xev.xselectionrequest.display;
resp.xselection.requestor = xev.xselectionrequest.requestor;
resp.xselection.selection = xev.xselectionrequest.selection;
resp.xselection.target = xev.xselectionrequest.target;
resp.xselection.time = xev.xselectionrequest.time;
XSendEvent(xdisplay, xev.xselectionrequest.requestor, 0,0, &resp);
}
else if (xev.type == SelectionClear) {
// some other program is taking control of the selection, so we are done
// don't answer if the timestamp is too early or too late
if (!timestamp || (xev.xselectionclear.time != CurrentTime && xev.xselectionclear.time < timestamp)) {
// ignore it; timestamp is out of bounds
} else {
if (xsel1 && xev.xselectionclear.selection == xsel1) {
if (xsel1 != CLIPBOARD && xsel2) { // a clipboard manager application will always take control of the XA_CLIPBOARD immediately, so don't worry about that
XSetSelectionOwner(xdisplay, xsel2, None, timestamp);
xsel2 = 0;
}
xsel1 = 0;
}
if (xsel1 && xev.xselectionclear.selection == xsel2) {
if (xsel2 != CLIPBOARD && xsel1) {
XSetSelectionOwner(xdisplay, xsel1, None, timestamp);
xsel1 = 0;
}
xsel2 = 0;
}
}
} else {
// it is some event we don't care about
}
}
// wait for either a keystroke or an x event
if (!done && (xsel1 || xsel2)) {
FD_ZERO(&in);
FD_SET(STDIN_FILENO, &in);
FD_SET(xfd, &in);
int rc = select(std::max(STDIN_FILENO, xfd)+1, &in, NULL, NULL, NULL);
if (rc < 0 ) {
tcsetattr(STDIN_FILENO, TCSANOW, &tio);
throw FailEx();
}
if (rc == 0)
FD_ZERO(&in);
}
}
tcsetattr(STDIN_FILENO, TCSANOW, &tio);
if (arg_verbose > 1) printf("X selection%s cleared\n",(num_sel>1?"s":""));
if (stxt1) XFree(stxt1);
if (stxt2) XFree(stxt2);
}
#endif // X_DISPLAY_MISSING
}
// pretty print notes to tty, prefixing each line with "> "
static void emit_notes(const secstring& notes) {
if (!notes.empty()) {
const char* p = notes.c_str();
while (*p) {
const char* q = p;
while (*q && *q != '\n' && *q != '\r') q++;
fwrite("> ", 1, 2, outfile);
fwrite(p, 1, q-p, outfile);
fwrite("\n", 1, 1, outfile);
while (*q && (*q == '\n' || *q == '\r')) q++;
p = q;
}
}
}
// ---- Block class -------------------------------
Block::~Block() {
zero();
}
void Block::zero() {
memset(block,0,sizeof(block));
}
void Block::makeLE(unsigned char b[8]) {
if (0x1234 == ntohl(0x1234)) {
// this is a big-endian system; put the 8 bytes data in little-endian order
for (int j=0; j<2; j++) {
std::swap(b[j],b[3-j]);
std::swap(b[4+j],b[7-j]);
}
}
}
Block& Block::operator ^=(const Block& b) {
block[0] ^= b.block[0];
block[1] ^= b.block[1];
return *this;
}
void Block::putInt32AndType(int32_t x, uint8_t t) {
block[0] = x;
block[1] = t; // because we are always byte-ordered correctly, we can just do this
}
int32_t Block::getInt32() const {
return block[0]; // because we are always byte-ordered correctly, we can just do this
}
uint8_t Block::getType() const {
return block[1] & 0xff; // because we are always byte-ordered correctly, we can just do this
}
void Block::read(const unsigned char* data, int len) {
if (static_cast<size_t>(len) < sizeof(block))
memset(block,0,sizeof(block));
memcpy(block,data,std::min(int(sizeof(block)),len));
makeLE(reinterpret_cast<unsigned char*>(block));
}
void Block::write(unsigned char data[8]) const {
memcpy(data,block,8);
makeLE(data);
}
bool Block::read(FILE* f) {
unsigned char data[8];
errno = 0;
const bool rc = fread(data, 1,sizeof(data), f) == sizeof(data);
if (rc)
read(data,8);
return rc;
}
bool Block::write(FILE* f) const {
unsigned char data[8];
write(data);
bool rc = fwrite(data, 1,sizeof(data), f) == sizeof(data);
return rc;
}
// ---- DB class -------------------------------------------------------
void DB::Init() {
Entry::Init();
}
DB::DB(const char* n, Version v) :
version(v),
opened(false), changed(false), backedup(false), overwritten(false),
dbname_str(n), dbname(dbname_str.c_str())
{
header = new Header();
}
DB::~DB() {
delete header;
}
void DB::createdb(const char* dbname) {
if (arg_verbose > 0) printf("creating %s\n", dbname);
// be sure not to overwrite an existing file
struct stat s;
if (!stat(dbname, &s)) {
fprintf(stderr, "%s already exists\n", dbname);
throw FailEx();
}
DB db(dbname, arg_dbversion);
if (!(db.header->create() && db.getkey(false) && db.save()))
throw FailEx();
}
// format a uuid into text
static secstring formatuuid(const secstring& uuid) {
unsigned char uuid_array[16];
char buf[16*3];
memcpy(uuid_array, uuid.c_str(), sizeof(uuid_array));
snprintf(buf, sizeof(buf),"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
uuid_array[0], uuid_array[1], uuid_array[2], uuid_array[3],
uuid_array[4], uuid_array[5], uuid_array[6], uuid_array[7],
uuid_array[8], uuid_array[9], uuid_array[10], uuid_array[11],
uuid_array[12], uuid_array[13], uuid_array[14], uuid_array[15]);
return buf;
}
static secstring xmlescape(const secstring& s) {
// escape any non-xml characters and enclose the whole string in ""
secstring out;
out.reserve(s.length());
out += '"';
for (secstring::const_iterator i=s.begin(); i!=s.end(); ++i) {
const unsigned char c = *i;
if (c<' ' || c==0x7f) {
// control character; emit in octal
char buf[10];
snprintf(buf,sizeof(buf),"\\%03o",c);
out += buf;
memset(buf,0,sizeof(buf));
} else switch (c) {
case '"': out += """; break;
case '&': out += "&"; break;
case '<': out += "<"; break;
case '>': out += ">"; break;
case '\\': out += "\\\\"; break;
default: out += c;
}
}
out += '"';
return out;
}
void DB::exportdb() {
matches_t matches;
if (open()) {
fprintf(outfile,"# passwordsafe version %s database\n", VERSION_NAME[version]);
if (version == VERSION_1_7) fprintf(outfile, "%s\t%s\t%s\t%s\n", "name", "login", "passwd", "notes");
else fprintf(outfile,"%s\t%s\t%s\t%s\t%s\t%s\n", "uuid", "group", "name", "login", "passwd", "notes");
for (entries_t::const_iterator i=entries.begin(); i!=entries.end(); ++i) {
const Entry& e = i->second;
if (version == VERSION_1_7)
fprintf(outfile,"%s\t%s\t%s\t%s\n", xmlescape(e.name).c_str(), xmlescape(!e.default_login?e.login:"[default]").c_str(), xmlescape(e.password).c_str(), xmlescape(e.notes).c_str());
else
fprintf(outfile,"%s\t%s\t%s\t%s\t%s\t%s\n", xmlescape(formatuuid(e.uuid)).c_str(), xmlescape(e.group).c_str(), xmlescape(e.name).c_str(), xmlescape(!e.default_login?e.login:"[default]").c_str(), xmlescape(e.password).c_str(), xmlescape(e.notes).c_str());
}
} else
throw FailEx();
}
bool DB::add(const Entry& e) {
secstring gn = e.groupname();
if (entries.find(gn) != entries.end())
return false;
entries.insert(entries_t::value_type(gn,e));
changed = true;
if (arg_verbose > 0) printf("added %s\n", gn.c_str());
return true;
}
bool DB::del(const Entry& e) {
for (entries_t::iterator i = entries.begin(); i != entries.end(); ++i) {
if (&i->second == &e) {
if (arg_verbose > 0) printf("deleted %s\n", e.groupname().c_str()); // print this out before we delete it
entries.erase(i);
changed = true;
return true;
}
}
return false;
}
void DB::mergedb(DB& db2) {
if (arg_verbose > 0) printf("merging %s into %s\n", db2.dbname, dbname);
if (open() && db2.open(&passphrase)) { // try opening 2nd db using first's passphrase, since if we're merging the same db the passphrase is often identical
int num_merged = 0, num_skipped = 0, num_dup = 0;
for (entries_t::const_iterator i=db2.entries.begin(); i!=db2.entries.end(); ++i) {
const Entry& e = i->second;
bool done = false;
for (entries_t::iterator j=entries.begin(); j!=entries.end(); ++j) {
const Entry& f = j->second;
if (e == f) {
if (arg_verbose > 0) printf("skipping duplicate entry %s\n", e.groupname().c_str());
num_dup++;
done = true;
break;
}
if ((e.uuid == f.uuid && !e.uuid.empty()) ||
e.groupname() == f.groupname()) {
// this is the same entry, but the contents are different
secstring summary;
f.diff(e,summary);
while (!done) {
switch (tolower(get1char("Entry "+e.groupname()+" differs ("+summary+"). Overwrite ? [y/N/d/?/q] ", 'n'))) {
case 'y':
del(f);
add(e);
num_merged++;
done = true;
break;
case 'n':
num_skipped++;
done = true;
break;
case 'd': case '?':
printf("%s", f.diff(e).c_str());
break;
case 'q':
throw FailEx();
}
}
break;
}
}
if (!done) {
add(e);
num_merged++;
}
}
// if the user specified the dbversion then consider that to be a change, so the use can mergedb a
// database with itself in order to change version (yeah, it's kinda a hack, but it's also kinda unixy)
if (arg_dbversion != version)
changed = true;
if (arg_verbose >= 0)
printf("Merged %d entries; skipped %d; %d duplicates.\n", num_merged, num_skipped, num_dup);
} else
throw FailEx();
}
void DB::passwd() {
if (arg_verbose > 0) printf("rekeying %s\n", dbname);
if (!(open()
&& header->create()
&& getkey(false, "Enter new passphrase", "Reenter new passphrase")))
throw FailEx();
}
bool DB::getkey(bool test, const char* prompt1, const char* prompt2) {
while (true) {
const secstring pw = getpw(prompt1+std::string(" for ")+dbname_str+": ");
if (!test) {
const secstring pw2 = getpw(prompt2+std::string(" for ")+dbname_str+": ");
if (pw != pw2) {
printf("Passphrases do not match\n");
continue;
}
}
if (test) {
// see if pw is correct
if (testkey(pw)) {
passphrase = pw;
return true;
} else {
printf("Passphrase is incorrect\n");
continue;
}
} else {
// initialize hash correctly
hashkey(pw, header->hash);
passphrase = pw;
changed = true; // since we've set/changed the passphrase, the db is changed
return true;
}
}
}
bool DB::testkey(const secstring& pw) {
unsigned char test_hash[sizeof(header->hash)];
hashkey(pw,test_hash);
if (memcmp(test_hash, header->hash, sizeof(header->hash)) == 0) {
passphrase = pw;
return true;
} else {
return false;
}
}
void DB::hashkey(const secstring& pw, unsigned char test_hash[]) {
// generate test hash from random and passphrase
// I am mystified as to why Bruce uses these extra 2 zero bytes in the hashes
SHA_CTX sha;
SHA1_Init(&sha);
SHA1_Update(&sha, header->random, sizeof(header->random));
const static unsigned char zeros[2] = { 0,0 };
SHA1_Update(&sha, zeros, 2);
SHA1_Update(&sha, pw.data(), pw.length());
unsigned char temp_key[SHA_DIGEST_LENGTH];
SHA1_Final(temp_key, &sha);
BF_KEY bf;
BF_set_key(&bf, sizeof(temp_key), temp_key);
Block block;
block.read(header->random, sizeof(header->random));
// to mimic passwordsafe I use BF_encrypt() directly, but that means I have to pretend that I am on a little-endian machine b/c passwordsafe assumes a i386
for (int i=0; i<1000; ++i)
BF_encrypt(block,&bf);
unsigned char hash_data[8];
block.write(hash_data);
// Now comes a sad part: I have to hack to mimic the original passwordsafe which contains what I believe
// is a bug. passwordsafe used its own blowfish and sha1 libraries, and its version of SHA1Final()
// memset the sha context to 0's. However the passwordsafe code went ahead and performed a
// SHA1Update on that zero'ed context. This of course did not crash anything, but it is not
// a real sha hash b/c the initial state of a real sha1 is not all zeros. Also we end up only
// hashing 8 bytes of stuff, so there are not 20 bytes of randomness in the result.
// The good thing is we are hashing something which is already well hashed, so I doubt this
// opened up any holes. But it does show that one should always step the program in a debugger
// and watch what the variables are doing; sometimes it is eye opening!
memset(&sha,0,sizeof(sha));
SHA1_Update(&sha, hash_data, sizeof(hash_data));
SHA1_Update(&sha, zeros, 2);
SHA1_Final(test_hash, &sha);
memset(&sha,0,sizeof(sha));
memset(&bf,0,sizeof(bf));
memset(temp_key,0,sizeof(temp_key));
}
bool DB::open(const secstring* pw_to_try) {
if (opened)
return true;
FILE* file = fopen(dbname, "rb");
if (!file) {
fprintf(stderr,"Can't open %s: %s\n", dbname, strerror(errno));
return false;
}
if (!header->read(file)) {
fprintf(stderr,"Can't read %s: %s\n", dbname, pwsafe_strerror(errno));
fclose(file);
return false;
}
if ((pw_to_try && testkey(*pw_to_try)) || getkey(true)) {
// load the rest of the file
Context*const ctxt = new Context(*header, passphrase, version); // so context resides in secure memory
try {
errno = 0; // because successfull reads don't clear errno but it might be non-zero due to earlier failures (like backup file not existing)
while (!feof(file)) {
Entry e;
if (e.read(file,*ctxt)) {
// version 2 files are destinguished by a magic starting entry
bool skip = false;
if (version == VERSION_UNKNOWN) {
version = (e.name == e.MAGIC_V2_NAME /* password save 2.05 does not check e.password, so I don't either && e.password == "2.0"*/) ? VERSION_2_0 : VERSION_1_7;
if (arg_verbose > 0) printf("loading version %s database\n", VERSION_NAME[version]);
if (version != VERSION_1_7) {
v2_preferences = e.notes; // save preferences away so we can rewrite them when saving the file
skip = true;
}
}
if (!skip) entries.insert(entries_t::value_type(e.groupname(),e));
} else {
if (errno || !feof(file)) {
delete ctxt;
fprintf(stderr,"Can't read %s: %s\n", dbname, pwsafe_strerror(errno));
fclose(file);
return false;
}
}
}
} catch (...) {
delete ctxt;
throw;
}
delete ctxt;
}
if (fclose(file)) {
fprintf(stderr, "Can't close %s: %s\n", dbname, strerror(errno));
return false;
}
if (version == VERSION_UNKNOWN) {
// assume empty files are v1.7, since a version 2.0 "empty" file would have contained the magic v2.0 entry
version = VERSION_1_7;
}
if (arg_verbose > 1) printf("read in %u entries\n", entries.size());
opened = true;
return true;
}
bool DB::backup() {
char buf[1024];
const std::string backupname_str = dbname_str+'~';
const char*const backupname = backupname_str.c_str();
if (arg_verbose > 0) printf("backing up %s to %s\n", dbname, backupname);
FILE* f = fopen(dbname, "rb");
if (!f) {
fprintf(stderr,"Can't open %s: %s\n", dbname, strerror(errno));
return false;
}
FILE* b = fopen(backupname, "wb");
if (!b) {
fprintf(stderr,"Can't open %s: %s\n", backupname, strerror(errno));
fclose(f);
return false;
}
while (true) {
size_t rc = fread(buf,1,sizeof(buf),f);
if (rc) {
size_t rc2 = fwrite(buf,1,rc,b);
if (rc != rc2) {
fprintf(stderr,"Can't write %s: %s\n", backupname, strerror(errno));
fclose(f);
fclose(b);
return false;
}
} else {
if (ferror(f)) {
fprintf(stderr,"Can't read %s: %s\n", dbname, strerror(errno));
fclose(f);
fclose(b);
return false;
} else
break;
}
}
fclose(f);
if (fclose(b)) {
fprintf(stderr,"Can't write %s: %s\n", backupname, strerror(errno));
return false;
}
backedup = true;
return true;
}
bool DB::restore() {
if (!overwritten) {
fprintf(stderr, "%s unchanged\n", dbname);
return true;
}
const std::string backupname_str = dbname_str+'~';
const char*const backupname = backupname_str.c_str();
if (!backedup) {
fprintf(stderr, "No backup of %s was created\nUNABLE TO RESTORE %s from %s\n", dbname, dbname, backupname);
return false;
}
if (unlink(dbname) && errno != ENOENT) {
fprintf(stderr, "unlink of %s failed: %s\nUNABLE TO RESTORE %s from %s\n", dbname, strerror(errno), dbname, backupname);
return false;
}
if (rename(backupname, dbname)) {
fprintf(stderr, "rename of %s to %s failed: %s\nUNABLE TO RESTORE %s from %s\n", backupname, dbname, strerror(errno), dbname, backupname);
return false;
}
fprintf(stderr, "Successfully restored %s from backup\n", dbname);
backedup = false; // the backup no longer exists
overwritten = false; // and db file is no longer overwritten
// leave changed alone, since changes, if they exist, are still in RAM
return true;
}
bool DB::save() {
Version saveversion = (arg_dbversion != VERSION_UNKNOWN ? arg_dbversion : version); // if the user specifies a dbversion then we convert
if (arg_verbose > 0) printf("writing %s version %s\n", dbname, VERSION_NAME[saveversion]);
// if this is a version change, then ask
if (saveversion != version &&
!getyn(std::string("Confirm overwriting version ")+VERSION_NAME[version]+" database "+dbname+" with a version "+VERSION_NAME[saveversion]+" database file ? "))
return false;
// we use a new salt and IV every time we save
if (!header->resalt())
return false;
FILE* f = fopen(dbname, "wb");
if (!f) {
fprintf(stderr,"Can't open %s: %s\n", dbname, strerror(errno));
return false;
}
overwritten = true; // we've now overwritten the db file and if the save() fails we need to restore from backup
if (!header->write(f)) {
fprintf(stderr,"Can't write %s: %s\n", dbname, pwsafe_strerror(errno));
fclose(f);
return false;
}
Context*const ctxt = new Context(*header, passphrase, saveversion);
try {
if (saveversion != VERSION_1_7) {
// write the magic entry
Entry e;
e.name = Entry::MAGIC_V2_NAME;
e.password = Entry::MAGIC_V2_PASSWORD;
e.notes = v2_preferences;
saveversion = VERSION_1_7; // temporarily true, since first entry is always written in v1.9 style
e.write(f,*ctxt);
saveversion = VERSION_2_0;
}
for (entries_t::const_iterator i=entries.begin(); i!=entries.end(); i++) {
if (!i->second.write(f,*ctxt)) {
delete ctxt;
fprintf(stderr,"Can't write %s: %s\n", dbname, pwsafe_strerror(errno));
fclose(f);
return false;
}
}
} catch (...) {
delete ctxt;
throw;
}
delete ctxt;
if (fclose(f)) {
fprintf(stderr,"Can't write/close %s: %s\n", dbname, strerror(errno));
return false;
}
changed = false;
return true;
}
bool DB::find(matches_t& matches, const char* regex_str /* might be NULL */) {
if (arg_verbose > 0) printf("searching %s for %s\n", dbname, regex_str?regex_str:"<all>");
regex_t regex;
if (regex_str) {
int rc = regcomp(®ex, regex_str, (arg_casesensative?0:REG_ICASE)|REG_NOSUB|REG_EXTENDED);
if (rc) {
size_t len = regerror(rc, ®ex, NULL, 0);
char*const msg = reinterpret_cast<char*>(malloc(len));
if (msg) {
regerror(rc, ®ex, msg, len);
fprintf(stderr,"%s\n", msg);
free(msg);
} else
fprintf(stderr,"Out of memory\n");
regfree(®ex);
throw FailEx();
}
}
for (entries_t::const_iterator i=entries.begin(); i!=entries.end(); ++i) {
const Entry& e = i->second;
if (!regex_str || !regexec(®ex, e.groupname().c_str(), 0,NULL, 0))
matches.push_back(&e);
}
if (regex_str)
regfree(®ex);
return true;
}
const DB::Entry& DB::find1(const char* regex) {
// first see if there is a perfect match for regex, treating regex as a literal string (and not a regex at all)
{
// first-first, try with a case sensative comparison even though they didn't ask for that
for (entries_t::const_iterator i=entries.begin(); i!=entries.end(); ++i) {
const Entry& e = i->second;
if (strcmp(regex,e.groupname().c_str()) == 0) {
return e;
}
}
}
// since that didn't work, try a case insensative comparison if that is a possibility
if (!arg_casesensative) {
matches_t matches;
for (entries_t::const_iterator i=entries.begin(); i!=entries.end(); ++i) {
const Entry& e = i->second;
if (strcasecmp(regex,e.groupname().c_str()) == 0) {
matches.push_back(&e);
}
}
if (matches.size() == 1) // >1 might match b/c we are matching case insensative and they only differ by case
return *matches.front();
}
matches_t matches;
if (find(matches, regex)) {
if (matches.size() == 0) {
printf("No matching entries\n");
throw FailEx();
}
if (matches.size() > 1) {
printf("More than one matching entry: ");
size_t count = 0;
for (matches_t::const_iterator i=matches.begin(); i!=matches.end() && count < 3; ++i, ++count)
printf("%s%s", (count?", ":""), (*i)->groupname().c_str());
if (count != matches.size())
printf(", ... (%u more) ", matches.size()-3);
printf(".\n");
throw FailEx();
}
return *matches.front();
} else
throw FailEx();
}
void DB::list(const char* regex /* might be NULL */) {
matches_t matches;
if (open() && find(matches, regex)) {
for (matches_t::const_iterator i=matches.begin(); i!=matches.end(); ++i) {
const Entry& e = **i;
if (arg_details) {
// print out the name
fprintf(outfile,"%s", e.groupname().c_str());
// append the login if it exists
if (!e.login.empty())
fprintf(outfile," - %s\n", e.login.c_str());
else if (e.default_login)
fprintf(outfile," - [%s]\n", e.the_default_login.c_str());
else
fprintf(outfile,"\n");
// print out the notes, prefixing each line with "> "
emit_notes(e.notes);
if (arg_verbose > 0) {
if (!e.uuid.empty())
// print out the UUID too
fprintf(outfile, "%s\n", formatuuid(e.uuid).c_str());
if (!e.extras.empty())
fprintf(outfile, "and %u unknown extra fields\n", e.extras.size());
}
} else
// just print out the name
fprintf(outfile,"%s\n", e.groupname().c_str());
}
}
}
void DB::emit(const char* regex, bool username, bool password) {
if (open()) {
const Entry& e = find1(regex);
if (!arg_echo && arg_details)
// if we're not emit()ing to stdout, then print notes before sending login/password to X clipboard.
// this way if the notes contain a URL, the user can cut/paste that too
emit_notes(e.notes);
if (username)
::emit(e.groupname(), "username", e.default_login?e.the_default_login:e.login);
if (password)
::emit(e.groupname(), "password", e.password);
if (arg_echo && arg_details)
// if we didn't emit the notes above, do it now
emit_notes(e.notes);
}
}
void DB::add(const char* name /* might be NULL */) {
if (arg_verbose > 0) printf("adding %s%sto %s\n", (name?name:""),(name?" ":""), dbname);
if (open()) {
Entry e;
if (name) {
if (version != VERSION_1_7) {
// if the argument contains a single '.' that isn't the first or last char, use that to split group and name
const char* dot = strchr(name,'.');
if (dot && dot != name && dot[1] != '\0' && strrchr(name,'.') == dot) {
e.name.assign(dot+1);
e.group.assign(name, dot-name);
} else
e.name = name;
} else
e.name = name;
}
while (true) {
if (e.name.empty())
e.name = gettxt("name: ");
if (version != VERSION_1_7 && e.group.empty())
e.group = gettxt("group [<none>]: ");
if (entries.find(e.groupname()) != entries.end()) {
fprintf(stderr,"%s already exists\n", e.groupname().c_str());
if (name)
throw FailEx();
e.name.erase();
e.group.erase();
} else if (!e.name.empty())
break;
}
e.login = gettxt("username: ");
if (e.login.empty())
e.default_login = getyn("use default username ("+e.the_default_login+") ? [n] ", false);
e.password = enter_password("password [return for random]: ", "password again: ");
e.notes = gettxt("notes: ");
entries.insert(entries_t::value_type(e.groupname(),e));
changed = true;
} else
throw FailEx();
}
void DB::edit(const char* regex) {
if (open()) {
const Entry& e_orig = find1(regex);
Entry e = e_orig; // make a local copy to edit
while (true) {
e.name = gettxt("name: ["+e_orig.name+"] ", e_orig.name);
if (version != VERSION_1_7)
e.group = gettxt("group: ["+e_orig.group+"] ", e_orig.group);
if ((e.name == e_orig.name && e.group == e_orig.group) ||
entries.find(e.groupname()) == entries.end()) // e.name cannot be empty b/c if the user entered an empty string they got the old name
break;
printf("%s already exists\n", e.groupname().c_str());
}
if (e.default_login)
e.default_login = getyn("keep default username ("+e_orig.the_default_login+") ? [y]", true);
if (!e.default_login) {
e.login = gettxt("username: ["+e_orig.login+"] ", e_orig.login);
if (e.login.empty() && !e_orig.default_login) // no point in asking if they just disabled default login
e.default_login = getyn("use default username ("+e_orig.the_default_login+") ? [n]", false);
}
while (true) {
if (getyn("change password ? [n] ", false)) {
secstring new_pw = enter_password("new password: [return for random]", "new password again: ");
if (new_pw.empty() && !e.password.empty()) {
if (!getyn("Confirm changing to an empty password ? [n] "))
continue;
}
e.password = new_pw;
}
break;
}
e.notes = gettxt("notes: [<keep same>] ", e_orig.notes);
if (e_orig != e) {
typedef std::vector<std::string> changes_t;
changes_t changes;
if (e_orig.group != e.group) changes.push_back("group");
if (e_orig.name != e.name) changes.push_back("name");
if (e_orig.default_login != e.default_login ||
(!e_orig.default_login && !e.default_login && e_orig.login != e.login))
changes.push_back("login");
if (e_orig.password != e.password)
changes.push_back("password");
if (e_orig.notes != e.notes)
changes.push_back("notes");
std::string prompt = "Confirm changing ";
for (changes_t::const_iterator i=changes.begin(); i!=changes.end(); ++i) {
if (i != changes.begin()) prompt += ", ";
prompt += *i;
}
prompt += " ? [y]";
if (getyn(prompt, true)) {
entries.erase(entries.find(e_orig.groupname()));
entries.insert(entries_t::value_type(e.groupname(),e));
changed = true;
} else
printf("Changes abandoned\n");
}
else
printf("No change\n");
}
}
void DB::del(const char* name) {
if (arg_verbose > 0) printf("deleting %s from %s\n", name, dbname);
if (open()) {
entries_t::iterator i = entries.find(name);
if (i == entries.end()) {
fprintf(stderr,"%s not found\n", name);
throw FailEx();
}
entries.erase(i);
changed = true;
}
}
// ----- DB::Header class ----------------------------------------------
DB::Header::Header() {
zero();
}
DB::Header::~Header() {
zero();
}
void DB::Header::zero() {
memset(random,0,sizeof(random));
memset(hash,0,sizeof(hash));
memset(salt,0,sizeof(salt));
memset(iv,0,sizeof(iv));
}
bool DB::Header::create() {
if (!RAND_bytes(random, sizeof(random)) ||
!RAND_bytes(salt, sizeof(salt)) ||
!RAND_bytes(iv, sizeof(iv))) {
fprintf(stderr,"Can't get random number: %s\n", ERR_error_string(ERR_get_error(), NULL));
return false;
}
memset(hash,0,sizeof(hash));
return true;
}
bool DB::Header::resalt() {
// new salt, and new iv too while we are at it
if (!RAND_bytes(salt, sizeof(salt)) ||
!RAND_bytes(iv, sizeof(iv))) {
fprintf(stderr,"Can't get random number: %s\n", ERR_error_string(ERR_get_error(), NULL));
return false;
}
return true;
}
bool DB::Header::read(FILE* f) {
if (fread(random, 1,sizeof(random), f) != sizeof(random) ||
fread(hash, 1,sizeof(hash), f) != sizeof(hash) ||
fread(salt, 1,sizeof(salt), f) != sizeof(salt) ||
fread(iv, 1,sizeof(iv), f) != sizeof(iv)) {
return false;
}
return true;
}
bool DB::Header::write(FILE* f) const {
if (fwrite(random, 1,sizeof(random), f) != sizeof(random) ||
fwrite(hash, 1,sizeof(hash), f) != sizeof(hash) ||
fwrite(salt, 1,sizeof(salt), f) != sizeof(salt) ||
fwrite(iv, 1,sizeof(iv), f) != sizeof(iv)) {
return false;
}
return true;
}
// ----- DB::Context class --------------------------------------------
DB::Context::Context(const Header& h, const secstring& pw, const Version& v) :
version(v)
{
cbc.read(h.iv,sizeof(h.iv));
SHA_CTX sha;
SHA1_Init(&sha);
SHA1_Update(&sha, pw.data(), pw.length());
SHA1_Update(&sha, h.salt, sizeof(h.salt));
unsigned char key[SHA_DIGEST_LENGTH];
SHA1_Final(key, &sha);
BF_set_key(&bf, sizeof(key), key);
memset(&sha,0,sizeof(sha));
memset(&key,0,sizeof(key));
}
DB::Context::~Context() {
memset(&bf,0,sizeof(bf));
}
// ----- DB::Entry class ----------------------------------------------
const char*const DB::Entry::SPLIT_STR = " \xAD ";
const char*const DB::Entry::MAGIC_V2_NAME = " !!!Version 2 File Format!!! "
"Please upgrade to PasswordSafe 2.0"
" or later";
const char*const DB::Entry::MAGIC_V2_PASSWORD = "2.0";
secstring DB::Entry::the_default_login;
void DB::Entry::Init() {
const char* dl = getenv("PWSAFE_DEFAULT_USER");
if (!dl) {
dl = getenv("USER");
if (!dl) {
dl = getenv("LOGNAME");
if (!dl) {
// fine, we'll go get LOGNAME from the pwdatabase
const struct passwd*const pw = getpwuid(getuid());
if (pw) {
dl = pw->pw_name;
} else {
// no USER, no LOGNAME, no /etc/passwd entry for this UID; they're on their own now
dl = "";
}
}
}
}
the_default_login = dl;
}
DB::Entry::Entry() : default_login(false) {
// ok; no-op
}
bool DB::Entry::operator!=(const Entry& e) const {
return uuid != e.uuid ||
group != e.group ||
name != e.name ||
default_login != e.default_login ||
(!default_login && !e.default_login && login != e.login) ||
password != e.password ||
notes != e.notes;
}
int DB::Entry::diff(const Entry& e, secstring& summary) const {
int n = 0;
if (uuid != e.uuid) {
summary += "uuid, ";
n++;
}
if (group != e.group) {
summary += "group, ";
n++;
}
if (name != e.name) {
summary += "name, ";
n++;
}
if (default_login != e.default_login) {
summary += "login, ";
n++;
}
if (!default_login && !e.default_login && login != e.login) {
summary += "login, ";
n++;
}
if (password != e.password) {
summary += "password, ";
n++;
}
if (notes != e.notes) {
summary += "notes, ";
n++;
}
if (n > 0) {
// strip off trailing ", "
summary = summary.substr(0, summary.length()-2);
}
return n;
}
secstring DB::Entry::diff(const Entry& e) const {
secstring s;
if (uuid != e.uuid)
s += "UUID -- " + uuid + "\n"
"UUID ++ " + e.uuid + "\n";
if (group != e.group)
s += "GROUP -- \"" + group + "\"\n"
"GROUP ++ \"" + e.group + "\"\n";
if (name != e.name)
s += "NAME -- \"" + name + "\"\n"
"NAME ++ \"" + e.name + "\"\n";
if (default_login != e.default_login) {
s += "DEFAULT LOGIN -- ";
s += (default_login ? "yes" : "no");
s += "\n"
"DEFAULT LOGIN ++ ";
s += (e.default_login ? "yes" : "no");
s += "\n";
}
if (!default_login && !e.default_login && login != e.login)
s += "LOGIN -- \"" + login + "\"\n"
"LOGIN ++ \"" + e.login + "\"\n";
if (password != e.password)
s += "PASSWORD -- <not shown>\n"
"PASSWORD ++ <not shown>\n";
if (notes != e.notes)
s += "NOTES -- \"" + notes + "\"\n"
"NOTES ++ \"" + e.notes + "\"\n";
return s;
}
secstring DB::Entry::groupname() const {
// prefix the name with the group, if it exists
return group.empty() ? name : group+'.'+name;
}
bool DB::Entry::read(FILE* f, DB::Context& c) {
bool rc = true;
uint8_t type;
if (c.version > VERSION_1_7) {
int max_fields = 255;
do {
secstring s;
rc &= read(f,c,type,s);
if (rc) {
switch (type) {
case UUID: uuid=s; break;
case GROUP: group=s; break;
case TITLE: name=s; break;
case USER: login=s; break;
case NOTES: notes=s; break;
case PASSWORD: password=s; break;
case END: break;
default:
if (arg_verbose > 0) printf("reading field of unknown type %u\n", static_cast<int>(type));
extras.push_back(extras_t::value_type(type,s));
}
}
} while (rc && type != END && --max_fields);
if (!max_fields) {
fprintf(stderr, "Too many fields in database entry. Is database corrupt?\n");
return false;
}
} else {
// read a version 1.7 entry
secstring name_login;
rc = read(f,c,type,name_login) &&
read(f,c,type,password) &&
read(f,c,type,notes);
if (rc) {
// split name_login if it contains the magic split char
secstring::size_type p = name_login.find(SPLIT_CHAR);
if (p != name_login.npos && p>0) {
name = name_login.substr(0,p);
login = name_login.substr(p+1,name_login.npos);
} else {
p = name_login.find(DEFAULT_USER_CHAR);
if (p != name_login.npos && p>0) {
// this entry uses the default login. this is not part of the database; instead it is part of the configuration, or in our case, $USER
name = name_login.substr(0,p);
login = the_default_login;
default_login = true;
} else {
// no magic split chars; assume this is a very old database that contains no login field
name = name_login;
}
}
// and trim any extra whitespace from the end of name and the begining of login
p = name.find_last_not_of(' ');
if (p != name.npos)
name = name.substr(0,p+1);
else
name.erase(); // nothing left of name
p = login.find_first_not_of(' ');
if (p != login.npos)
login = login.substr(p,login.npos);
else
login.erase(); // nothing left of login
}
}
if (arg_verbose > 2 && rc)
printf("read in entry %s\n", groupname().c_str());
return rc;
}
bool DB::Entry::write(FILE* f, DB::Context& c) const {
if (arg_verbose > 2)
printf("writing entry %s\n", name.c_str());
// remove 'false' and create a entry named zzz to deliberate cause failure to write
if (false && name == "zzz") {
fprintf(stderr, "Deliberately failing to save entry zzz\n");
return false;
}
if (c.version != VERSION_1_7) {
secstring save_uuid = uuid;
if (uuid.empty()) {
// we must have read in a v1.7 file; create a uuid on the fly
// NOTE: instead of creating a per-rfc UUID which includes hardware-identificators like your 1st NIC's MAC address,
// I make it completely random. I like this better, and given the size of the UUID collisions won't be a problem.
unsigned char buf[16];
if (!RAND_bytes(buf,sizeof(buf))) {
fprintf(stderr, "Can't get random data: %s\n", ERR_error_string(ERR_get_error(), NULL));
throw FailEx();
}
save_uuid.assign(reinterpret_cast<const char*>(buf),sizeof(buf));
memset(buf,0,sizeof(buf));
}
return write(f,c,UUID,save_uuid) &&
write(f,c,GROUP,group) &&
write(f,c,TITLE,name) &&
write(f,c,USER,login) &&
write(f,c,PASSWORD,password) &&
write(f,c,NOTES,notes) &&
write(f,c,extras) &&
write(f,c,END,"");
} else {
// here I follow the same wierd login encoding as passwordsafe 1.9, including inserting extra spaces as well as the SPLIT_CHAR between name and login
// it doesnt look like anything depends on those spaces, but...
secstring name_login;
if (!group.empty())
name_login = group + '.'; // passwordsafe 2.0 prepends the v2.0 group when writing a v1.7 file, so I do it too
if (default_login) // this this first so that if the_default_login is "" we still get it right (so here I don't follow passwordsafe)
name_login += name + DEFAULT_USER_CHAR;
else if (login.empty())
name_login += name;
else
name_login += name + SPLIT_STR + login;
return write(f,c,NAME,name_login) &&
write(f,c,PASSWORD,password) &&
write(f,c,NOTES,notes);
}
}
bool DB::Entry::read(FILE* f, DB::Context& c, uint8_t& type, secstring& str) {
str.erase();
Block block;
if (!block.read(f))
return false;
Block copy = block;
BF_decrypt(block, &c.bf);
block ^= c.cbc;
c.cbc = copy;
const int32_t len = block.getInt32();
type = block.getType();
block.zero();
copy.zero();
if (len < 0) {
// set errno to something smart
errno = PWSAFE_ERR_INVALID_DB;
return false;
}
// make sure len isn't completely nuts (we could also compare with remaining file length...)
if (len > 64*1024) {
errno = PWSAFE_ERR_INVALID_DB;
return false;
}
int numblocks = (len+8-1)/8;
if (numblocks == 0)
numblocks = 1;
str.resize(len);
for (int i=0; i<numblocks; i++) {
if (!block.read(f))
return false;
copy = block;
BF_decrypt(block, &c.bf);
block ^= c.cbc;
unsigned char data[8];
block.write(data);
for (int j=0; j<8 && i*8+j<len; j++)
str[i*8+j] = data[j];
c.cbc = copy;
}
return true;
}
bool DB::Entry::write(FILE* f, DB::Context& c, const extras_t& extras) {
bool rc = true;
for (extras_t::const_iterator i=extras.begin(); rc && i!=extras.end(); ++i)
rc &= write(f,c,i->first,i->second);
return rc;
}
bool DB::Entry::write(FILE* f, DB::Context& c, uint8_t type, const secstring& str) {
const unsigned char*const data = reinterpret_cast<const unsigned char*>(str.data());
int numblocks = (str.length()+8-1)/8;
if (numblocks == 0)
numblocks = 1; // always have one block, even if it is all zero's
{ // write the string's length
Block block;
block.putInt32AndType(str.length(), (c.version != VERSION_1_7 ? type : 0));
block ^= c.cbc;
BF_encrypt(block, &c.bf);
c.cbc = block;
if (!block.write(f))
return false;
}
// then blocks of data; the last one padded with zero's
for (int i=0; i<numblocks; i++) {
Block block;
block.read(data+i*8, std::min(8, int(str.length())-i*8));
block ^= c.cbc;
BF_encrypt(block, &c.bf);
c.cbc = block;
if (!block.write(f))
return false;
}
return true;
}
// ---- secalloc class ---------------------------------------
secalloc::Pool* secalloc::pools = NULL;
size_t secalloc::pagesize = 0;
const size_t secalloc::alignsize = std::max(sizeof(double),
#if HAVE_LONG_LONG
sizeof(long long)
#else
sizeof(long)
#endif
);
secalloc::Pool::Pool(size_t n) : next(0), top(0), bottom(0), level(0) {
char*const z = 0;
const size_t pagesize = secalloc::pagesize;
char*const p = reinterpret_cast<char*>(malloc(pagesize+n+pagesize)); // make sure we get something that is page-aligned
if (!p) {
fprintf(stderr, "Out of memory\n");
throw FailEx();
}
bottom = p;
level = z + ((bottom-z+pagesize-1) & ~(pagesize-1)); // round bottom up to a page boundary
top = z + ((bottom-z+pagesize+n+pagesize) & ~(pagesize-1)); // round top down to a page boundary
// mark level..top as non-swapabble
int rc = mlock(level,top-level);
// Redhat FC3 returns ENOMEM if not root, not EPERM, so dont bother checking for EPERM error from mlock(); treat any error to mean 'try mlock() against as SUID user'
if (rc && (saved_uid != geteuid() || saved_gid != getegid())) {
// try again as root (or whoever saved_uid really is)
if (saved_uid != geteuid())
seteuid(saved_uid);
if (saved_gid != getegid())
setegid(saved_gid);
rc = mlock(level,top-level);
setegid(getgid());
seteuid(getuid());
}
if (rc) {
static bool reported = false;
if (!reported) {
if (arg_verbose >= 0)
fprintf(stderr, "WARNING: %s unable to use secure ram (need to be setuid root)\n", program_name);
reported = true;
}
}
}
secalloc::Pool::~Pool() {
char*const z = 0;
const size_t pagesize = secalloc::pagesize;
memset(bottom, 0, top-bottom); // clear it once more, just in case everything wasn't properly deallocate()ed
char*const l = z + ((bottom-z+pagesize-1) & ~(pagesize-1)); // recalculate original value we passed to mlock()
munlock(l, top-l); // might fail; that's ok if it does
free(bottom);
}
secalloc::secalloc() {
init();
}
void secalloc::init() {
if (pagesize == 0) { // initialize pagesize the first time we are called
pagesize = getpagesize();
if (pagesize == (size_t)-1 || pagesize == 0) {
const char errstr[] = "Error: can't compute kernel MMU page size\n";
write(STDERR_FILENO, errstr, sizeof(errstr)); // at the point when init() is called, stderr is not necessarily setup
throw FailEx();
}
}
}
void secalloc::cleanup() {
while (pools) {
Pool* p = pools;
pools = p->next;
delete p;
}
}
void* secalloc::allocate(size_t n) {
if (!pools || static_cast<size_t>(pools->top - pools->level) < n) {
// need a new pool
Pool* p = new (std::nothrow) Pool(std::max(n, static_cast<size_t>(16*pagesize)));
if (!p) {
fprintf(stderr, "Error: %s out of memory\n", program_name);
throw FailEx();
}
p->next = pools;
pools = p;
}
void* p = pools->level;
pools->level += (n+alignsize-1) & ~(alignsize-1);
return p;
}
void secalloc::deallocate(void* p, size_t n) {
memset(p,0,n);
}
void* secalloc::reallocate(void* p, size_t old_n, size_t new_n) {
void* new_p = allocate(new_n);
memcpy(new_p, p, std::min(old_n, new_n));
deallocate(p, old_n);
return new_p;
}
// ----- cheap readline() substitute for without-readline builds ----------------------
#ifndef WITH_READLINE
// a cheap function with the same api as the real readline()
static char* readline(const char* prompt) {
printf("%s", prompt);
fflush(stdout);
static secstring saved;
int buflen = saved.length() + 100;
int bufpos = saved.length();
char* buf = reinterpret_cast<char*>(malloc(buflen+1));
if (!buf)
throw FailEx();
memcpy(buf, saved.data(), saved.length());
buf[saved.length()] = '\0';
while (!strchr(buf,'\n')) {
const int rc = ::read(STDIN_FILENO, buf+bufpos, buflen);
if (rc == -1) {
fprintf(stderr, "Error: %s read(STDIN) failed: %s\n", program_name, strerror(errno));
memset(buf,0,buflen);
free(buf);
throw FailEx();
}
bufpos += rc;
if (bufpos == buflen && !strchr(buf,'\n')) {
// we needed a bigger buffer
char* new_buf = reinterpret_cast<char*>(malloc(2*buflen+1));
if (!new_buf) {
fprintf(stderr, "Error: %s out of memory\n", program_name);
memset(buf,0,buflen);
free(buf);
}
memcpy(new_buf, buf, bufpos);
memset(buf, 0, buflen);
free(buf);
buf = new_buf;
buflen *= 2;
}
}
int len = strchr(buf,'\n') - buf;
saved.assign(buf+len+1, bufpos-(len+1));
buf[len] = '\0';
return buf;
}
#endif // WITH_READLINE
// --- cheap getopt_long() substitute ----------------------------------------------------------
#ifndef HAS_GETOPT_LONG
// a cheap substitute for getopt_long() that doesn't support optional arguments, nor does it reorder argv[] to put non-options last
static int getopt_long(int argc, char*const argv[], const char* short_opts, const long_option* lopts, int* flag) {
if (optind >= argc)
// nothing left at all
return -1;
const char*const p = argv[optind];
if (p[0] != '-' || p[1] != '-')
// not a long option. since we don't reorder argv[] we let getopt() have a crack at it
return getopt(argc, argv, short_opts);
while (lopts && lopts->name) {
if (strcmp(lopts->name, p+2) == 0) {
// we have a match
optind++;
if (lopts->has_arg == required_argument) {
if (optind >= argc) {
fprintf(stderr, "option `%s' requires an argument\n", p);
return ':';
}
optarg = argv[optind++];
}
if (lopts->flag) {
*lopts->flag = lopts->val;
return 0;
}
else
return lopts->val;
} else
lopts++;
}
// not an option that matches
fprintf(stderr, "unrecognized option `%s'\n", p);
return '?';
}
#endif // HAS_GETOPT_LONG
#ifndef HAS_GETLINE
// a cheap substitute
static ssize_t getline(char** bufp,size_t* buflenp,FILE* file) {
char* buf = *bufp;
size_t buflen = *buflenp;
ssize_t len = 0;
if (!buf) {
if (buflen == 0) buflen = 128;
buf = reinterpret_cast<char*>(malloc(buflen));
if (!buf) return -1;
*bufp = buf;
*buflenp = buflen;
}
while (true) {
if (!fgets(buf+len, buflen-len, file))
break; // we've reached EOF
len += strlen(buf+len);
if (buf[len-1] == '\n')
break; // we've reached a \n
// we need a bigger buffer
buflen *= 2;
buf = reinterpret_cast<char*>(realloc(buf,buflen));
if (!buf) return -1;
*bufp = buf;
*buflenp = buflen;
}
return len;
}
#endif // HAS_GETLINE
syntax highlighted by Code2HTML, v. 0.9.1