/*
 * Copyright (C) 2010-2011 Gaetan Bisson. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * StirFS. The secure, transparent and irresistible filesystem.
 *
 * StirFS is an encrypted filesystem for FUSE on Linux; it is designed for
 * minimalism, flexibility, and security. Minimalism should be obvious at the
 * view of this single, small C file; flexibility means StirFS just encrypts
 * filenames and blocks, and relies on a host filesystem for the rest; security
 * stems from strong cryptographic primitives and modes of operation.
 *
 * Compile with:
 *
 *   cc -O2 -D_FILE_OFFSET_BITS=64 -lfuse -lssl -o stirfs stirfs.c
 */

/* 
 * DESIGN OVERVIEW
 *
 * An AES256 master key is derived from the input password by computing the
 * SHA256 digest of its concatenation with a fixed salt value.
 *
 * Filenames are encrypted as follows: first pad with zeroes to ensure the
 * length is a multiple of the AES block size (128 bits), then encrypt by
 * AES256 in CBC mode, then xor the last block into the first one and encrypt
 * again (to ensure two-way diffusion), and finally base64-encode.
 *
 * Each file's first 128 bits are a nonce; following blocks consist of actual
 * data (with offsets shifted) encrypted by AES256 in (modified) CTR mode.
 */

/*
 * DESIGN NOTES
 *
 * We have no use for the strn*() family of string functions since they are
 * impractical and irrelevant here from a security standpoint; from a stability
 * standpoint, if the user wants very long paths, let them increase PATH_MAX.
 *
 * PATH_MAX could be superseded by dynamic allocation, since FUSE now supports
 * arbitrarily-long pathnames, but it certainly wouldn't be as efficient.
 *
 * The CTR mode of operation is quite elegant; however, it leaks information
 * when encrypting two different plain-texts using the same key+nonce. Here, we
 * use a novel mode of operation that operates at the byte-level and preserves
 * this flexibility while being more secure; see stir_cblock().
 * 
 * We use "uintptr_t" for counter arithmetic, efficient on both 32- and 64-bit
 * platforms; as a drawback, bits after the first 2^32 are encrypted
 * differently on these architectures.
 *
 * Uninitialized data should be presented as NULL bytes by stir_read().
 * Possibly FUSE takes care of that on its own, otherwise this might cause
 * problems with some applications (that I don't know of).
 */


/* ****************************************************************************
 *
 * HEADERS
 *
 */


#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/xattr.h>
#include <time.h>

#define __USE_UNIX98
#include <unistd.h>

#define FUSE_USE_VERSION 26
#include <fuse.h>
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif

#include <openssl/aes.h>
#include <openssl/sha.h>

// An uintptr_t object must fit within AES_BLOCK_SIZE bytes.
char check[sizeof(uintptr_t)>AES_BLOCK_SIZE?-1:0];


/* ****************************************************************************
 *
 * STIRFS INTERNAL STRUCTURES
 *
 * Our FUSE context consists of the rootdir, encryption and decryption keys.
 * Our FUSE file handlers consist of a backend file descriptor and file nonce.
 * 
 */


struct stir_ctx {
	char *root;
	AES_KEY *ekey;
	AES_KEY *dkey;
};

#define CTX ((struct stir_ctx*)fuse_get_context()->private_data)

struct stir_fh {
	uintptr_t fd;
	char *nonce;
};

#define FH ((struct stir_fh *)(uintptr_t) fi->fh)
#define MKFH \
	struct stir_fh *fh = malloc(sizeof(struct stir_fh)); \
	fi->fh = (uintptr_t) fh


/* ****************************************************************************
 *
 * STIRFS INTERNAL PRIMITIVES
 *
 */


inline static void stir_encode (char *res, unsigned char *buf, int n) {
	char c[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,-";
	int i,j,x;
	for (i=0,j=0; i<n; i+=3,j+=4) {
		x = buf[i]<<16;
		if (i+1<n) x += buf[i+1]<<8;
		if (i+2<n) x += buf[i+2];
		res[j] = c[(x>>18)&63];
		res[j+1] = c[(x>>12)&63];
		res[j+2] = c[(x>>6)&63];
		res[j+3] = c[x&63];
		if (i+2>n) res[j+2] = '+';
		if (i+3>n) res[j+3] = '+';
	}
	res[j] = 0;
}

inline static void stir_decode (unsigned char *res, char *buf, int *n) {
	int d (char c) { return c>96 ? c-97 : c>64 ? c-39 : c>47 ? c+4 : c>43 ? c+18 : 0; }
	int m = strlen(buf);
	*n = buf[m-2]=='+' ? 3*m/4-2 : buf[m-1]=='+' ? 3*m/4-1 : 3*m/4;
	int i,j,x;
	for (i=0,j=0; j<m; i+=3,j+=4) {
		x = d(buf[j])<<18;
		x += d(buf[j+1])<<12;
		x += d(buf[j+2])<<6;
		x += d(buf[j+3]);
		res[i] = (x>>16)&255;
		if (i+1<*n) res[i+1] = (x>>8)&255;
		if (i+2<*n) res[i+2] = x&255;
	}
}

inline static int stir_cbc_encrypt (char *cbuf, char *buf, AES_KEY *key, int l) {
	memcpy(cbuf, buf, l);
	memset(cbuf+l, 0, AES_BLOCK_SIZE-l%AES_BLOCK_SIZE);
	int n=0, i;
	while (l>n) {
		if (n) for (i=0; i<AES_BLOCK_SIZE; i++) cbuf[n+i] ^= cbuf[i+n-AES_BLOCK_SIZE];
		AES_encrypt(cbuf+n, cbuf+n, key);
		n += AES_BLOCK_SIZE;
	}
	return n;
}

inline static void stir_cbc_decrypt (char *buf, char *cbuf, AES_KEY *key, int l) {
	int n=0, i;
	while (l>n) {
		AES_decrypt(cbuf+n, buf+n, key);
		if (n) for (i=0; i<AES_BLOCK_SIZE; i++) buf[n+i] ^= cbuf[i+n-AES_BLOCK_SIZE];
		n += AES_BLOCK_SIZE;
	}
}

inline static void stir_cname (char *cname, char *name) {
	if (!strcmp(name,"") || !strcmp(name,".") || !strcmp(name,"..")) {
		strcpy(cname, name);
		return;
	}
	int i,n;
	char xname[PATH_MAX], yname[PATH_MAX];
	n = stir_cbc_encrypt(xname, name, CTX->ekey, strlen(name)+1);
	if (n>AES_BLOCK_SIZE) for (i=0; i<AES_BLOCK_SIZE; i++) xname[i] ^= xname[n-AES_BLOCK_SIZE+i];
	stir_cbc_encrypt(yname, xname, CTX->ekey, n);
	stir_encode(cname, yname, n);
}

inline static void stir_name (char *name, char *cname) {
	if (!strcmp(cname,"") || !strcmp(cname,".") || !strcmp(cname,"..")) {
		strcpy(name, cname);
		return;
	}
	int i,n;
	char xname[PATH_MAX], yname[PATH_MAX];
	stir_decode(yname, cname, &n);
	stir_cbc_decrypt(xname, yname, CTX->dkey, n);
	if (n>AES_BLOCK_SIZE) for (i=0; i<AES_BLOCK_SIZE; i++) xname[i] ^= xname[n-AES_BLOCK_SIZE+i];
	stir_cbc_decrypt(name, xname, CTX->dkey, n);
}

inline static void stir_cpath (char *cpath, const char *path) {
	char rpath[PATH_MAX];
	strcpy(rpath, path);
	strcpy(cpath, "");
	char *tok, *rem=rpath;
	while (tok=strsep(&rem,"/")) {
		stir_cname(cpath+strlen(cpath), tok);
		strcat(cpath, "/");
	}
	cpath[strlen(cpath)-1] = 0;
}

inline static void stir_path (char *path, const char *cpath) {
	char rpath[PATH_MAX];
	strcpy(rpath, cpath);
	strcpy(path, "");
	char *tok, *rem=rpath;
	while (tok=strsep(&rem,"/")) {
		stir_name(path+strlen(path), tok);
		strcat(path, "/");
	}
	path[strlen(path)-1] = 0;
}

static void stir_root_cpath (char *rpath, const char *path) {
	strcpy(rpath, CTX->root);
	strcat(rpath, "/");
	stir_cpath(rpath+strlen(rpath), path);
}

inline static void stir_cblock (char *cbuf, char *buf, size_t count, off_t off, AES_KEY *key, char *nonce) {
	char ivc[AES_BLOCK_SIZE], mask[2*AES_BLOCK_SIZE];
	memcpy(ivc, nonce, AES_BLOCK_SIZE);
	*(uintptr_t *)ivc += off/AES_BLOCK_SIZE;
	uintptr_t c=0, d=off;
	char *e;
	while (c<count) {
		if (!(d%AES_BLOCK_SIZE) || !c) {
			AES_encrypt(ivc, mask, key);
			memcpy(mask+AES_BLOCK_SIZE, mask, AES_BLOCK_SIZE);
			(*(uintptr_t *)ivc)++;
		}
		e = mask + d%AES_BLOCK_SIZE;
		cbuf[c] = ( ( ( ( ( ( ( ( ( ( ( ( buf[c]
			) ^ e[2] ) + e[3] ) ^ e[5] ) + e[7] ) ^ e[11] ) + e[13]
			) ^ e[1] ) + e[4] ) ^ e[6] ) + e[9] ) ^ e[10] ) + e[14]
		;
		c++;
		d++;
	}
}

inline static void stir_block (char *buf, char *cbuf, size_t count, off_t off, AES_KEY *key, char *nonce) {
	char ivc[AES_BLOCK_SIZE], mask[2*AES_BLOCK_SIZE];
	memcpy(ivc, nonce, AES_BLOCK_SIZE);
	*(uintptr_t *)ivc += off/AES_BLOCK_SIZE;
	uintptr_t c=0, d=off;
	char *e;
	while (c<count) {
		if (!(d%AES_BLOCK_SIZE) || !c) {
			AES_encrypt(ivc, mask, key);
			memcpy(mask+AES_BLOCK_SIZE, mask, AES_BLOCK_SIZE);
			(*(uintptr_t *)ivc)++;
		}
		e = mask + d%AES_BLOCK_SIZE;
		buf[c] = ( ( ( ( ( ( ( ( ( ( ( ( cbuf[c]
			) - e[14] ) ^ e[10] ) - e[9] ) ^ e[6] ) - e[4] ) ^ e[1]
			) - e[13] ) ^ e[11] ) - e[7] ) ^ e[5] ) - e[3] ) ^ e[2]
		;
		c++;
		d++;
	}
}


/* ****************************************************************************
 *
 * SITRFS FUSE PRIMITIVES
 *
 * cf. http://fuse.sourceforge.net/doxygen/structfuse__operations.html
 *
 */


// Get file system statistics
int stir_statfs (const char *path, struct statvfs *stat) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = statvfs(rpath, stat);
	if (r) return -errno;
	return 0;
}

// Get attributes from an open file
int stir_fgetattr (const char *path, struct stat *buf, struct fuse_file_info *fi) {
	int r = fstat(FH->fd, buf);
	if (r) return -errno;
	if (S_ISREG(buf->st_mode)) buf->st_size -= AES_BLOCK_SIZE;
	return 0;
}

// Get file attributes
int stir_getattr (const char *path, struct stat *buf) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = lstat(rpath, buf);
	if (r) return -errno;
	if (S_ISREG(buf->st_mode)) buf->st_size -= AES_BLOCK_SIZE;
	return 0;
}

// Change the size of an open file
int stir_ftruncate (const char *path, off_t off, struct fuse_file_info *fi) {
	int r = ftruncate(FH->fd, off+AES_BLOCK_SIZE);
	if (r) return -errno;
	return 0;
}

// Change the size of a file
int stir_truncate (const char *path, off_t off) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = truncate(rpath, off+AES_BLOCK_SIZE);
	if (r) return -errno;
	return 0;
}

// Check file access permissions
int stir_access (const char *path, int mode) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = access(rpath, mode);
	if (r) return -errno;
	return 0;
}

// Change the permission bits of a file
int stir_chmod (const char *path, mode_t mode) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = chmod(rpath, mode);
	if (r) return -errno;
	return 0;
}

// Change the owner and group of a file
int stir_chown (const char *path, uid_t uid, gid_t gid) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = chown(rpath, uid, gid);
	if (r) return -errno;
	return 0;
}

// Change the access and modification times of a file with nanosecond resolution
int stir_utimens (const char *path, const struct timespec times[2]) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = utimensat(-1, rpath, times, 0);
	if (r) return -errno;
	return 0;
}

// Set extended attributes
int stir_setxattr (const char *path, const char *name, const char *value, size_t size, int flags) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = lsetxattr(rpath, name, value, size, flags);
	if (r) return -errno;
	return 0;
}

// Get extended attributes
int stir_getxattr (const char *path, const char *name, char *value, size_t size) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = lgetxattr(rpath, name, value, size);
	if (r<0) return -errno;
	return r;
}

// List extended attributes
int stir_listxattr (const char *path, char *list, size_t size) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = llistxattr(rpath, list, size);
	if (r<0) return -errno;
	return r;
}

// Remove extended attributes
int stir_removexattr (const char *path, const char *name) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = lremovexattr(rpath, name);
	if (r) return -errno;
	return 0;
}

// Read the target of a symbolic link
int stir_readlink (const char *head, char *path, size_t size) {
	char rhead[PATH_MAX], cpath[PATH_MAX];
	stir_root_cpath(rhead, head);
	int r = readlink(rhead, cpath, PATH_MAX);
	if (r<0) return -errno;
	stir_path(path, cpath);
	return 0;
}

// Create a symbolic link
int stir_symlink (const char *path, const char *head) {
	char cpath[PATH_MAX], rhead[PATH_MAX];
	stir_cpath(cpath, path);
	stir_root_cpath(rhead, head);
	int r = symlink(cpath, rhead);
	if (r) return -errno;
	return 0;
}

// Rename a file
int stir_rename (const char *path, const char *npath) {
	char rpath[PATH_MAX], rnpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	stir_root_cpath(rnpath, npath);
	int r = rename(rpath, rnpath);
	if (r) return -errno;
	return 0;
}

// Create a hard link to a file
int stir_link (const char *path, const char *head) {
	char rpath[PATH_MAX], rhead[PATH_MAX];
	stir_root_cpath(rpath, path);
	stir_root_cpath(rhead, head);
	int r = link(rpath, rhead);
	if (r) return -errno;
	return 0;
}

// Create a directory
int stir_mkdir (const char *path, mode_t mode) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = mkdir(rpath, mode);
	if (r) return -errno;
	return 0;
}

// Remove a file
int stir_unlink (const char *path) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = unlink(rpath);
	if (r) return -errno;
	return 0;
}

// Remove a directory
int stir_rmdir (const char *path) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = rmdir(rpath);
	if (r) return -errno;
	return 0;
}

// Open directory
int stir_opendir (const char *path, struct fuse_file_info *fi) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	MKFH;
	DIR *dp = opendir(rpath);
	FH->fd = (uintptr_t) dp;
	if (!dp) return -errno;
	return 0;
}

// Read directory
int stir_readdir (const char *path, void *buf, fuse_fill_dir_t fill, off_t off, struct fuse_file_info *fi) {
	char name[PATH_MAX];
	DIR *dp = (DIR *)FH->fd;
	struct dirent *de;
	struct stat st;
	seekdir(dp, off);
	while (de=readdir(dp)) {
		stir_name(name, de->d_name);
		memset(&st, 0, sizeof(st));
		st.st_ino = de->d_ino;
		st.st_mode = DTTOIF(de->d_type);
		if (fill(buf,name,&st,telldir(dp))) break;
	}
	return 0;
}

// Release directory
int stir_releasedir (const char *path, struct fuse_file_info *fi) {
	int r = closedir((DIR *)FH->fd);
	if (r) return -errno;
	free(FH);
	return 0;
}

// Release an open file
int stir_release (const char *path, struct fuse_file_info *fi) {
	int r = close(FH->fd);
	if (r) return -errno;
	free(FH->nonce);
	free(FH);
	return 0;
}

// [FUSE operations structure reference] No creation (O_CREAT, O_EXCL) and by
// default also no truncation (O_TRUNC) flags will be passed to open().

// File open operation
int stir_open (const char *path, struct fuse_file_info *fi) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	MKFH;
	if (fi->flags&O_WRONLY) fi->flags ^= O_WRONLY|O_RDWR;
	int fd = open(rpath, fi->flags);
	FH->fd = fd;
	if (fd<0) return -errno;
	char *nonce;
	struct stat stbuf;
	fstat(fd, &stbuf);
	if (S_ISREG(stbuf.st_mode)) {
		nonce = malloc(AES_BLOCK_SIZE);
		int r = pread(fd, nonce, AES_BLOCK_SIZE, 0);
		if (r<AES_BLOCK_SIZE) return -EIO;
	} else nonce = NULL;
	FH->nonce = nonce;
	return 0;
}

// Create and open a file
int stir_create (const char *path, mode_t mode, struct fuse_file_info *fi) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	MKFH;
	int fd = open(rpath, fi->flags, mode);
	FH->fd = fd;
	if (fd<0) return -errno;
	char *nonce;
	if (S_ISREG(mode)) {
		nonce = malloc(SHA256_DIGEST_LENGTH);
		int rnd[] = { time(NULL), rand(), clock(), rand() };
		SHA256((char *)rnd, sizeof(rnd), nonce);
		int r = pwrite(fd, nonce, AES_BLOCK_SIZE, 0);
		if (r<AES_BLOCK_SIZE) return -EIO;
	} else nonce = NULL;
	FH->nonce = nonce;
	return 0;
}

// Create a file node
int stir_mknod (const char *path, mode_t mode, dev_t dev) {
	char rpath[PATH_MAX];
	stir_root_cpath(rpath, path);
	int r = mknod(rpath,mode,dev);
	if (r) return -errno;
	if (S_ISREG(mode)) {
		char nonce[SHA256_DIGEST_LENGTH];
		int rnd[] = { time(NULL), rand(), clock(), rand() };
		SHA256((char *)rnd, sizeof(rnd), nonce);
		int fr = open(rpath, O_WRONLY);
		int r = pwrite(fr, nonce, AES_BLOCK_SIZE, 0);
		if (r<AES_BLOCK_SIZE) return -EIO;
		close(fr);
	}
	return 0;
}

// Read data from an open file
int stir_read (const char *path, char *buf, size_t count, off_t off, struct fuse_file_info *fi) {
	char *cbuf = malloc(count);
	int r = pread(FH->fd, cbuf, count, off+AES_BLOCK_SIZE);
	if (r<0) return -errno;
	if (!FH->nonce) memcpy(buf, cbuf, count);
	else stir_block(buf, cbuf, count, off, CTX->ekey, FH->nonce);
	free(cbuf);
	return r;
}

// Write data to an open file
int stir_write (const char *path, const char *buf, size_t count, off_t off, struct fuse_file_info *fi) {
	char *cbuf = malloc(count);
	if (!FH->nonce) memcpy(cbuf, buf, count);
	else stir_cblock(cbuf, (char *)buf, count, off, CTX->ekey, FH->nonce);
	int r = pwrite(FH->fd, cbuf, count, off+AES_BLOCK_SIZE);
	if (r<0) return -errno;
	free(cbuf);
	return r;
}

// Possibly flush cached data
int stir_flush (const char *path, struct fuse_file_info *fi) {
	return 0;
}

// Synchronize file contents
int stir_fsync (const char *path, int data, struct fuse_file_info *fi) {
	int r = data ? fdatasync(FH->fd) : fsync(FH->fd);
	if (r) return -errno;
	return 0;
}

// Synchronize directory contents
int stir_fsyncdir (const char *path, int data, struct fuse_file_info *fi) {
	return 0;
}

// Initialize filesystem
void *stir_init (struct fuse_conn_info *conn) {
	return CTX;
}

// Clean up filesystem
void stir_destroy (void *stuff) {
}


/* ****************************************************************************
 *
 * PUTTING IT ALL TOGETHER
 *
 */


// Operation structure to be fed to FUSE
struct fuse_operations stir_oper = {
	.access = stir_access,
//	.bmap = bmap,
	.chmod = stir_chmod,
	.chown = stir_chown,
	.create = stir_create,
	.destroy = stir_destroy,
	.fgetattr = stir_fgetattr,
//	.flag_nullpath_ok = flag_nullpath_ok,
	.flush = stir_flush,
	.fsync = stir_fsync,
	.fsyncdir = stir_fsyncdir,
	.ftruncate = stir_ftruncate,
	.getattr = stir_getattr,
	.getxattr = stir_getxattr,
	.init = stir_init,
//	.ioctl = ioctl,
	.link = stir_link,
	.listxattr = stir_listxattr,
//	.lock = lock,
	.mkdir = stir_mkdir,
	.mknod = stir_mknod,
	.open = stir_open,
	.opendir = stir_opendir,
//	.poll = poll,
	.read = stir_read,
	.readdir = stir_readdir,
	.readlink = stir_readlink,
	.release = stir_release,
	.releasedir = stir_releasedir,
	.removexattr = stir_removexattr,
	.rename = stir_rename,
	.rmdir = stir_rmdir,
	.setxattr = stir_setxattr,
	.statfs = stir_statfs,
	.symlink = stir_symlink,
	.truncate = stir_truncate,
	.unlink = stir_unlink,
	.utimens = stir_utimens,
	.write = stir_write,
};

// Ask for password and run FUSE
int main (int argc, char *argv[]) {
	if (argc<3) {
		printf("\
StirFS. The secure, transparent and irresistible filesystem.\n\
Copyright (C) 2010-2011 Gaetan Bisson. All rights reserved.\n\
Version 1.0; compiled on "__DATE__" at "__TIME__".\n\
\n\
Mounts an encrypted filesystem in mountpoint using rootdir as backend.\n\
\n\
");
		char *argv[] = { "stirfs rootdir", "-h" };
		return fuse_main(2, argv, NULL, NULL);
	}

	char *passwd = getpass("Password: ");
	char *iv = "{StirFS}{StirFS}";
	int n = strlen(passwd);
	int m = strlen(iv)+n+1;
	char *salt = malloc(m);
	strcpy(salt, passwd);
	strcat(salt, iv);
	char hash[SHA256_DIGEST_LENGTH];
	SHA256(salt, m, hash);
	AES_KEY *ekey = malloc(sizeof(AES_KEY));
	AES_KEY *dkey = malloc(sizeof(AES_KEY));
	AES_set_encrypt_key(hash, 256, ekey);
	AES_set_decrypt_key(hash, 256, dkey);
	memset(passwd, 0, n);
	memset(salt, 0, m);
	free(passwd);
	free(salt);

	struct stir_ctx *ctx = malloc(sizeof(struct stir_ctx));
	ctx->root = realpath(argv[1], NULL);
	ctx->ekey = ekey;
	ctx->dkey = dkey;

	argv[1] = "stirfs";
	return fuse_main(argc-1, argv+1, &stir_oper, ctx);
}
