/*
 * Copyright (C) 2010-2017, Gaetan Bisson <bisson@archlinux.org>.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * StirFS. The secure, transparent and irresistible filesystem.
 *
 * StirFS is an encrypted filesystem for FUSE; its design focuses are
 * minimalism, flexibility, and security. Minimalism should be obvious at the
 * view of this single, short 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 -I/usr/include/fuse3 -lfuse3 -lcrypto -o stirfs stirfs.c
 */

/* 
 * INTERNALS 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: pad them with just enough NULL bytes to
 * get a length divisible by the AES block size (128 bits), encrypt by AES256
 * in CBC mode, then xor the last block into the first one, encrypt again (to
 * ensure two-way diffusion), and finally base64-encode.
 *
 * The first block of each file is a nonce; following blocks consist of actual
 * data (with offsets shifted) encrypted by AES256 in (modified) CTR mode.
 */

/*
 * DESIGN NOTES
 *
 * PATH_MAX could be superseded by dynamic allocation, since FUSE now supports
 * arbitrarily-long pathnames, but that would complicate the code for virtually
 * no benefit.
 *
 * The CTR mode of operation is elegant, but leaks information when distinct
 * plain texts (such as versions of the same file) are encrypted using the same
 * key and nonce. Here, stir_encblock() uses a novel mode of operation which is
 * more secure than CTR but preserves its flexibility at the byte level.
 */


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


#define _DEFAULT_SOURCE

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

#define FUSE_USE_VERSION 32

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


/* ****************************************************************************
 *
 * STIRFS INTERNAL STRUCTURES
 *
 */


/* FUSE context data */
struct stir_ctx {
	char *root;    /* root backend directory */
	AES_KEY *enc;  /* AES encryption key     */
	AES_KEY *dec;  /* AES decryption key     */
};

/* Access the FUSE context data */
#define CTX ((struct stir_ctx *)fuse_get_context()->private_data)

/* FUSE file handler data */
struct stir_fh {
	intptr_t fd;  /* backend file descriptor */
	char *nonce;  /* file nonce or NULL      */
};

/* Access the FUSE file handler data */
#define FH ((struct stir_fh *)(intptr_t)fi->fh)

/* Create the FUSE file handler data */
#define MKFH { fi->fh = (intptr_t)malloc(sizeof(struct stir_fh)); }


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


/* Helper for base64 encoding */
const char stir_encode_aux[] =
	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,-";

/* Helper for base64 decoding */
const int stir_decode_aux[] = {
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  0, 62, 63, -1, -1,
	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
	-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
};

/* Base64 encode n characters of buf into str */
void stir_encode (char *str, const unsigned char *buf, const int n) {
	int i, j, x;
	for (i=0,j=0; i<n; i+=3,j+=4) {
		x  =           buf[i+0]<<16;
		x += (i+1<n) ? buf[i+1]<<8 : 0;
		x += (i+2<n) ? buf[i+2]<<0 : 0;
		str[j+0] =           stir_encode_aux[(x>>18)&63];
		str[j+1] =           stir_encode_aux[(x>>12)&63];
		str[j+2] = (i+1<n) ? stir_encode_aux[(x>> 6)&63] : '+';
		str[j+3] = (i+2<n) ? stir_encode_aux[(x>> 0)&63] : '+';
	}
	str[j] = '\0';
}

/* Base64 decode n characters of str into m characters of buf */
void stir_decode (unsigned char *buf, const char *str, const int n, int *m) {
	int i, j, x;
	for (i=0,j=0; str[j] && j<n; i+=3,j+=4) {
		x  = stir_decode_aux[(int)str[j+0]]<<18;
		x += stir_decode_aux[(int)str[j+1]]<<12;
		x += stir_decode_aux[(int)str[j+2]]<< 6;
		x += stir_decode_aux[(int)str[j+3]]<< 0;
		buf[i+0] = (x>>16)&255;
		buf[i+1] = (x>> 8)&255;
		buf[i+2] = (x>> 0)&255;
	}
	*m = i - (str[j-2]=='+' ? 2 : str[j-1]=='+' ? 1 : 0);
}

/* Xor block Y into X */
#define BLOCK_XOR(X,Y) { \
	int i; \
	for (i=0; i<AES_BLOCK_SIZE; i+=sizeof(size_t)) \
		*(size_t *)(X+i) ^= *(size_t *)(Y+i); \
}

/* Encrypt l characters of buf in CBC mode */
void stir_encbc (unsigned char *buf, const int l) {
	unsigned char *p;
	for (p=buf; p<buf+l; p+=AES_BLOCK_SIZE) {
		if (p>buf) BLOCK_XOR(p, p-AES_BLOCK_SIZE);
		AES_encrypt(p, p, CTX->enc);
	}
}

/* Decrypt l characters of buf in CBC mode */
void stir_decbc (unsigned char *buf, const int l) {
	unsigned char *p;
	for (p=buf+l-AES_BLOCK_SIZE; p>=buf; p-=AES_BLOCK_SIZE) {
		AES_decrypt(p, p, CTX->dec);
		if (p>buf) BLOCK_XOR(p, p-AES_BLOCK_SIZE);
	}
}

/* Variations in length */
#define ENCODE_LEN(n) ((((n)+2)/3)*4)
#define DECODE_LEN(n) ((((n)+3)/4)*3)
#define BLOCKS_LEN(n) ((((n)/AES_BLOCK_SIZE)+1)*AES_BLOCK_SIZE)

/* Encrypt n characters of basename dec into enc */
void stir_encbase (char *enc, const char *dec, const int n) {
	int m;
	unsigned char tmp[PATH_MAX];
	if (!strcmp(dec,"") || !strcmp(dec,".") || !strcmp(dec,"..")) {
		strcpy(enc, dec);
		return;
	}
	m = BLOCKS_LEN(n);
	memcpy(tmp, dec, n);
	memset(tmp+n, 0, m-n);
	stir_encbc(tmp, m);
	if (m>AES_BLOCK_SIZE) BLOCK_XOR(tmp, tmp+m-AES_BLOCK_SIZE);
	stir_encbc(tmp, m);
	stir_encode(enc, tmp, m);
}

/* Decrypt n characters of basename enc into dec */
void stir_decbase (char *dec, const char *enc, const int n) {
	int m;
	unsigned char *tmp;
	if (!strcmp(enc,"") || !strcmp(enc,".") || !strcmp(enc,"..")) {
		strcpy(dec, enc);
		return;
	}
	tmp = (unsigned char *)dec;
	stir_decode(tmp, enc, n, &m);
	stir_decbc(tmp, m);
	if (m>AES_BLOCK_SIZE) BLOCK_XOR(tmp, tmp+m-AES_BLOCK_SIZE);
	stir_decbc(tmp, m);
}


/* Encrypt path dec into at most m characters of enc */
void stir_encpath (char *enc, const char *dec, int m) {
	char *h, *t;
	*enc = '\0';
	t = (char *)dec;
	do {
		while (*t=='/') {
			strcat(enc, "/");
			m--;
			t++;
		}
		h = t;
		while (*t && *t!='/') t++;
		m -= ENCODE_LEN(BLOCKS_LEN(t-h));
		if (m<0) break;
		stir_encbase(enc+strlen(enc), h, t-h);
	} while (*t);
	if (m<0) *enc = '\0';
}

/* Decrypt path enc into at most m characters of dec */
void stir_decpath (char *dec, const char *enc, int m) {
	char *h, *t;
	*dec = '\0';
	t = (char *)enc;
	do {
		while (*t=='/') {
			strcat(dec, "/");
			m--;
			t++;
		}
		h = t;
		while (*t && *t!='/') t++;
		m -= BLOCKS_LEN(DECODE_LEN(t-h));
		if (m<0) break;
		stir_decbase(dec+strlen(dec), h, t-h);
	} while (*t);
	if (m<0) *dec = '\0';
}

/* Encrypt path dec into absolute path enc */
void stir_abspath (char *enc, const char *dec) {
	int l;
	l = strlen(CTX->root);
	strcpy(enc, CTX->root);
	stir_encpath(enc+l, dec, PATH_MAX-l-1);
}

/* Encryption round on W using X,Y,Z */
#define EROUND(W,X,Y,Z) { \
	W ^= X; \
	W += Y; \
	W = W<<Z | W>>(8-Z); \
}

/* Decryption round on W using X,Y,Z */
#define DROUND(W,X,Y,Z) { \
	W = W<<(8-Z) | W>>Z; \
	W -= Y; \
	W ^= X; \
}

/* Encrypt count characters of buf, using off and nonce */
void stir_encblock (unsigned char *buf, size_t count, off_t off, char *nonce) {
	size_t c, d;
	unsigned char ivc[AES_BLOCK_SIZE], mask[2*AES_BLOCK_SIZE], *e;
	memcpy(ivc, nonce, AES_BLOCK_SIZE);
	*(size_t *)ivc += off/AES_BLOCK_SIZE;
	for (c=0,d=off; c<count; c++,d++,e++) {
		if (!(d%AES_BLOCK_SIZE) || !c) {
			AES_encrypt(ivc, mask, CTX->enc);
			memcpy(mask+AES_BLOCK_SIZE, mask, AES_BLOCK_SIZE);
			e = mask + d%AES_BLOCK_SIZE;
			(*(size_t *)ivc)++;
		}
		EROUND(buf[c], e[ 2], e[ 1], 7);
		EROUND(buf[c], e[ 3], e[ 4], 6);
		EROUND(buf[c], e[ 5], e[ 6], 4);
		EROUND(buf[c], e[ 7], e[ 9], 3);
		EROUND(buf[c], e[11], e[10], 1);
		EROUND(buf[c], e[13], e[14], 0);
	}
}

/* Decrypt count characters of buf, using off and nonce */
void stir_decblock (unsigned char *buf, size_t count, off_t off, char *nonce) {
	size_t c, d;
	unsigned char ivc[AES_BLOCK_SIZE], mask[2*AES_BLOCK_SIZE], *e;
	memcpy(ivc, nonce, AES_BLOCK_SIZE);
	*(size_t *)ivc += off/AES_BLOCK_SIZE;
	for (c=0,d=off; c<count; c++,d++,e++) {
		if (!(d%AES_BLOCK_SIZE) || !c) {
			AES_encrypt(ivc, mask, CTX->enc);
			memcpy(mask+AES_BLOCK_SIZE, mask, AES_BLOCK_SIZE);
			e = mask + d%AES_BLOCK_SIZE;
			(*(size_t *)ivc)++;
		}
		DROUND(buf[c], e[13], e[14], 0);
		DROUND(buf[c], e[11], e[10], 1);
		DROUND(buf[c], e[ 7], e[ 9], 3);
		DROUND(buf[c], e[ 5], e[ 6], 4);
		DROUND(buf[c], e[ 3], e[ 4], 6);
		DROUND(buf[c], e[ 2], e[ 1], 7);
	}
}


/* ****************************************************************************
 *
 * SITRFS FUSE PRIMITIVES
 *
 * See `struct fuse_operations` from: /usr/include/fuse/fuse.h
 *
 */


/* Get file attributes */
int stir_getattr (const char *path, struct stat *buf, struct fuse_file_info *fi) {
	int r;
	if (fi) {
		r = fstat(FH->fd, buf);
	} else {
		char abs[PATH_MAX];
		stir_abspath(abs, path);
		r = lstat(abs, buf);
	}
	if (S_ISREG(buf->st_mode)) buf->st_size -= AES_BLOCK_SIZE;
	return r ? -errno : 0;
}

/* Read the target of a symbolic link */
int stir_readlink (const char *path, char *buf, size_t size) {
	int r;
	char dec[PATH_MAX], enc[PATH_MAX];
	stir_abspath(dec, path);
	r = readlink(dec, enc, PATH_MAX);
	if (r<0) return -errno;
	enc[r] = '\0';
	stir_decpath(buf, enc, size);
	return 0;
}

/* Create a file node */
int stir_mknod (const char *path, mode_t mode, dev_t dev) {
	int r;
	char abs[PATH_MAX];
	stir_abspath(abs, path);
	r = mknod(abs, mode, dev);
	return r ? -errno : 0;
}

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

/* Remove a file */
int stir_unlink (const char *path) {
	int r;
	char abs[PATH_MAX];
	stir_abspath(abs, path);
	r = unlink(abs);
	return r ? -errno : 0;
}

/* Remove a directory */
int stir_rmdir (const char *path) {
	int r;
	char abs[PATH_MAX];
	stir_abspath(abs, path);
	r = rmdir(abs);
	return r ? -errno : 0;
}

/* Create a symbolic link */
int stir_symlink (const char *buf, const char *path) {
	int r;
	char enc[PATH_MAX], abs[PATH_MAX];
	stir_encpath(enc, buf, PATH_MAX);
	stir_abspath(abs, path);
	r = symlink(enc, abs);
	return r ? -errno : 0;
}

/* Rename a file */
int stir_rename (const char *old, const char *new, unsigned int flags) {
	int r;
	char absold[PATH_MAX], absnew[PATH_MAX];
	stir_abspath(absold, old);
	stir_abspath(absnew, new);
	r = rename(absold, absnew);
	return r ? -errno : 0;
}

/* Create a hard link to a file */
int stir_link (const char *buf, const char *path) {
	int r;
	char enc[PATH_MAX], abs[PATH_MAX];
	stir_abspath(enc, buf);
	stir_abspath(abs, path);
	r = link(enc, abs);
	return r ? -errno : 0;
}

/* Change the permission bits of a file */
int stir_chmod (const char *path, mode_t mode, struct fuse_file_info *fi) {
	int r;
	if (fi) {
		r = fchmod(FH->fd, mode);
	} else {
		char abs[PATH_MAX];
		stir_abspath(abs, path);
		r = chmod(abs, mode);
	}
	return r ? -errno : 0;
}

/* Change the owner and group of a file */
int stir_chown (const char *path, uid_t uid, gid_t gid, struct fuse_file_info *fi) {
	int r;
	if (fi) {
		r = fchown(FH->fd, uid, gid);
	} else {
		char abs[PATH_MAX];
		stir_abspath(abs, path);
		r = chown(abs, uid, gid);
	}
	return r ? -errno : 0;
}

/* Change the size of a file */
int stir_truncate (const char *path, off_t off, struct fuse_file_info *fi) {
	int r;
	if (fi) {
		r = ftruncate(FH->fd, off+AES_BLOCK_SIZE);
	} else {
		char abs[PATH_MAX];
		stir_abspath(abs, path);
		r = truncate(abs, off+AES_BLOCK_SIZE);
	}
	return r ? -errno : 0;
}

/* File open operation */
int stir_open (const char *path, struct fuse_file_info *fi) {
	int oflag, fd, r;
	char abs[PATH_MAX];
	struct stat buf;
	oflag = fi->flags & ~(O_TRUNC|O_CREAT|O_EXCL);
	if (oflag&O_WRONLY) oflag ^= O_WRONLY|O_RDWR;
	stir_abspath(abs, path);
	fd = open(abs, oflag);
	if (fd<0) return -errno;
	MKFH;
	FH->fd = fd;
	fstat(fd, &buf);
	if (S_ISREG(buf.st_mode)) {
		FH->nonce = malloc(AES_BLOCK_SIZE);
		r = pread(fd, FH->nonce, AES_BLOCK_SIZE, 0);
		if (r<AES_BLOCK_SIZE) return -EIO;
	} else FH->nonce = NULL;
	if (fi->flags&O_TRUNC) ftruncate(fd, AES_BLOCK_SIZE);
	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) {
	int r;
	r = pread(FH->fd, buf, count, off+AES_BLOCK_SIZE);
	if (r<0) return -errno;
	if (FH->nonce) stir_decblock((unsigned char *)buf, r, off, FH->nonce);
	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) {
	int r;
	unsigned char *enc;
	enc = malloc(count);
	memcpy(enc, buf, count);
	if (FH->nonce) stir_encblock(enc, count, off, FH->nonce);
	r = pwrite(FH->fd, enc, count, off+AES_BLOCK_SIZE);
	free(enc);
	return r<0 ? -errno : r;
}

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

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

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

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

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

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

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

/* Remove extended attributes */
int stir_removexattr (const char *path, const char *name) {
	int r;
	char abs[PATH_MAX];
	stir_abspath(abs, path);
	r = lremovexattr(abs, name);
	return r ? -errno : 0;
}

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

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

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

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

/* Initialize filesystem */
void *stir_init (struct fuse_conn_info *conn, struct fuse_config *cfg) {
	cfg->nullpath_ok = 1;
	cfg->use_ino = 1;
	return CTX;
}

/* Clean up filesystem */
void stir_destroy (void *ctx) {
	free(CTX->root);
	memset(CTX->enc, 0, sizeof(AES_KEY));
	memset(CTX->dec, 0, sizeof(AES_KEY));
	free(CTX->enc);
	free(CTX->dec);
	free(ctx);
}

/* Check file access permissions */
int stir_access (const char *path, int mode) {
	int r;
	char abs[PATH_MAX];
	stir_abspath(abs, path);
	r = access(abs, mode);
	return r ? -errno : 0;
}

/* Create and open a file */
int stir_create (const char *path, mode_t mode, struct fuse_file_info *fi) {
	int fd, r, rnd[4];
	char abs[PATH_MAX];
	stir_abspath(abs, path);
	fd = open(abs, fi->flags, mode);
	if (fd<0) return -errno;
	MKFH;
	FH->fd = fd;
	if (S_ISREG(mode)) {
		rnd[0]=time(NULL);
		rnd[1]=rand();
		rnd[2]=clock();
		rnd[3]=rand();
		FH->nonce = malloc(SHA256_DIGEST_LENGTH);
		SHA256((unsigned char *)rnd, sizeof(rnd), (unsigned char *)FH->nonce);
		r = pwrite(fd, FH->nonce, AES_BLOCK_SIZE, 0);
		if (r<AES_BLOCK_SIZE) return -EIO;
	} else FH->nonce = NULL;
	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], struct fuse_file_info *fi) {
	int r;
	if (fi) {
		r = futimens(FH->fd, times);
	} else {
		char abs[PATH_MAX];
		stir_abspath(abs, path);
		r = utimensat(-1, abs, times, 0);
	}
	return r ? -errno : 0;
}


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


/* FUSE file system operation structure */
struct fuse_operations stir_op = {
	stir_getattr,
	stir_readlink,
	stir_mknod,
	stir_mkdir,
	stir_unlink,
	stir_rmdir,
	stir_symlink,
	stir_rename,
	stir_link,
	stir_chmod,
	stir_chown,
	stir_truncate,
	stir_open,
	stir_read,
	stir_write,
	stir_statfs,
	stir_flush,
	stir_release,
	stir_fsync,
	stir_setxattr,
	stir_getxattr,
	stir_listxattr,
	stir_removexattr,
	stir_opendir,
	stir_readdir,
	stir_releasedir,
	stir_fsyncdir,
	stir_init,
	stir_destroy,
	stir_access,
	stir_create,
	NULL, /* lock, not implemented */
	stir_utimens,
	NULL, /* bmap, not implemented */
	NULL, /* ioctl, not implemented */
	NULL, /* poll, not implemented */
	NULL, /* write_buf, not implemented */
	NULL, /* read_buf, not implemented */
	NULL, /* flock, not implemented */
	NULL, /* fallocate, not implemented */
};

/* Stupid salt for key derivation */
#define SALT "{StirFS}{StirFS}"

/* Derive keys from password */
void stir_keys (AES_KEY *enc, AES_KEY *dec) {
	char passwd[PATH_MAX];
	struct termios term, pass;
	unsigned char hash[SHA256_DIGEST_LENGTH];

	tcgetattr(0, &term);
	pass = term;
	pass.c_lflag &= ~ECHO;
	tcsetattr(0, TCSANOW, &pass);
	printf("Password: ");
	while (!fgets(passwd, PATH_MAX-strlen(SALT), stdin));
	passwd[strlen(passwd)-1] = '\0';
	printf("\n");
	tcsetattr(0, TCSANOW, &term);

	strcat(passwd, SALT);
	SHA256((unsigned char *)passwd, strlen(passwd)+1, hash);
	AES_set_encrypt_key(hash, 256, enc);
	AES_set_decrypt_key(hash, 256, dec);
	memset(passwd, 0, PATH_MAX);
}

/* Run FUSE */
int main (int argc, char *argv[]) {
	struct stir_ctx *ctx;

	if (argc<3) {
		char *argh[] = { "stirfs <rootdir>", "-h" };
		printf("\
StirFS. The secure, transparent and irresistible filesystem.\n\
Copyright (C) 2010-2017 Gaetan Bisson. All rights reserved.\n\
Version 2.0; compiled "__DATE__".\n\
\n\
Mounts an encrypted filesystem in mountpoint using rootdir as backend.\n\
\n\
");
		return fuse_main(2, argh, &stir_op, NULL);
	}

	ctx = malloc(sizeof(struct stir_ctx));
	ctx->root = realpath(argv[1], NULL);
	if (!ctx->root) return -1;
	strcat(ctx->root, "/");

	ctx->enc = malloc(sizeof(AES_KEY));
	ctx->dec = malloc(sizeof(AES_KEY));
	stir_keys(ctx->enc, ctx->dec);

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