Add a w32 compatability layer (WIP)

w32-compat
Christopher Wellons 2017-10-03 21:19:47 -04:00
parent 39bc92455f
commit ec929a6696
3 changed files with 439 additions and 273 deletions

View File

@ -6,11 +6,12 @@ PREFIX = /usr/local
sources = src/enchive.c src/chacha.c src/curve25519-donna.c src/sha256.c sources = src/enchive.c src/chacha.c src/curve25519-donna.c src/sha256.c
objects = $(sources:.c=.o) objects = $(sources:.c=.o)
headers = config.h src/docs.h src/chacha.h src/sha256.h src/optparse.h headers = config.h src/docs.h src/chacha.h src/sha256.h src/optparse.h \
src/w32-compat.h
enchive: $(objects) enchive: $(objects)
$(CC) $(LDFLAGS) -o $@ $(objects) $(LDLIBS) $(CC) $(LDFLAGS) -o $@ $(objects) $(LDLIBS)
src/enchive.o: src/enchive.c config.h src/docs.h src/enchive.o: src/enchive.c config.h src/docs.h src/w32-compat.h
src/chacha.o: src/chacha.c config.h src/chacha.o: src/chacha.c config.h
src/curve25519-donna.o: src/curve25519-donna.c config.h src/curve25519-donna.o: src/curve25519-donna.c config.h
src/sha256.o: src/sha256.c config.h src/sha256.o: src/sha256.c config.h

View File

@ -5,6 +5,18 @@
#include <stdarg.h> #include <stdarg.h>
#include <errno.h> #include <errno.h>
#ifdef _WIN32
#include "w32-compat.h"
#else
#include <poll.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/socket.h>
#endif
#include "docs.h" #include "docs.h"
#include "sha256.h" #include "sha256.h"
#include "chacha.h" #include "chacha.h"
@ -24,55 +36,9 @@ static int global_agent_timeout = 0;
static const char enchive_suffix[] = ".enchive"; static const char enchive_suffix[] = ".enchive";
static struct { static const char *cleanup_pubfile;
char *name; static const char *cleanup_secfile;
FILE *file; static const char *cleanup_outfile;
} cleanup[2];
/**
* Register a file for deletion should fatal() be called.
*/
static void
cleanup_register(FILE *file, char *name)
{
if (file) {
unsigned i;
for (i = 0; i < sizeof(cleanup) / sizeof(*cleanup); i++) {
if (!cleanup[i].name) {
cleanup[i].name = name;
cleanup[i].file = file;
return;
}
}
}
abort();
}
/**
* Update cleanup registry to indicate FILE has been closed.
*/
static void
cleanup_closed(FILE *file)
{
unsigned i;
for (i = 0; i < sizeof(cleanup) / sizeof(*cleanup); i++) {
if (file == cleanup[i].file)
cleanup[i].file = 0;
return;
}
abort();
}
/**
* Free resources held by the cleanup registry.
*/
static void
cleanup_free(void)
{
size_t i;
for (i = 0; i < sizeof(cleanup) / sizeof(*cleanup); i++)
free(cleanup[i].name);
}
/** /**
* Print a message, cleanup, and exit the program with a failure code. * Print a message, cleanup, and exit the program with a failure code.
@ -80,18 +46,17 @@ cleanup_free(void)
static void static void
fatal(const char *fmt, ...) fatal(const char *fmt, ...)
{ {
unsigned i;
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
fprintf(stderr, "enchive: "); fprintf(stderr, "enchive: ");
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
fputc('\n', stderr); fputc('\n', stderr);
for (i = 0; i < sizeof(cleanup) / sizeof(*cleanup); i++) { if (cleanup_pubfile)
if (cleanup[i].file) remove(cleanup_pubfile);
fclose(cleanup[i].file); if (cleanup_secfile)
if (cleanup[i].name) remove(cleanup_secfile);
remove(cleanup[i].name); if (cleanup_outfile)
} remove(cleanup_outfile);
va_end(ap); va_end(ap);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -164,6 +129,21 @@ joinstr(int n, ...)
return str; return str;
} }
static ssize_t
full_read(int fd, void *buf, size_t len)
{
size_t z = 0;
while (z < len) {
ssize_t r = read(fd, (char *)buf + z, len - z);
if (r == -1)
return -1;
if (r == 0)
break;
z += r;
}
return z;
}
/** /**
* Read the protection key from a key agent identified by its IV. * Read the protection key from a key agent identified by its IV.
*/ */
@ -175,11 +155,6 @@ static int agent_read(u8 *key, const u8 *id);
static int agent_run(const u8 *key, const u8 *id); static int agent_run(const u8 *key, const u8 *id);
#if ENCHIVE_OPTION_AGENT #if ENCHIVE_OPTION_AGENT
#include <poll.h>
#include <unistd.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/socket.h>
/** /**
* Fill ADDR with a unix domain socket name for the agent. * Fill ADDR with a unix domain socket name for the agent.
@ -299,6 +274,7 @@ agent_read(u8 *key, const u8 *id)
{ {
(void)key; (void)key;
(void)id; (void)id;
(void)warning;
return 0; return 0;
} }
@ -408,118 +384,46 @@ storage_directory(char *file)
/** /**
* Read a passphrase directly from the keyboard without echo. * Read a passphrase directly from the keyboard without echo.
*/ */
static void get_passphrase(char *buf, size_t len, char *prompt);
/**
* Read a passphrase without any fanfare (fallback).
*/
static void
get_passphrase_dumb(char *buf, size_t len, char *prompt)
{
size_t passlen;
warning("reading passphrase from stdin with echo");
fputs(prompt, stderr);
fflush(stderr);
if (!fgets(buf, len, stdin))
fatal("could not read passphrase");
passlen = strlen(buf);
if (buf[passlen - 1] < ' ')
buf[passlen - 1] = 0;
}
#if defined(__unix__) || defined(__APPLE__)
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
static void static void
get_passphrase(char *buf, size_t len, char *prompt) get_passphrase(char *buf, size_t len, char *prompt)
{ {
int tty = open("/dev/tty", O_RDWR); struct termios old, new;
if (tty == -1) { ssize_t z;
get_passphrase_dumb(buf, len, prompt); int tty;
tty = open("/dev/tty", O_RDWR);
if (tty == -1)
fatal("could not open /dev/tty -- %s", strerror(errno));
if (write(tty, prompt, strlen(prompt)) != (ssize_t)strlen(prompt))
fatal("could not write to /dev/tty -- %s", strerror(errno));
if (tcgetattr(tty, &old) == -1)
fatal("tcgetattr() -- %s", strerror(errno));
new = old;
new.c_lflag &= ~ECHO;
if (tcsetattr(tty, TCSANOW, &new) == -1)
fatal("tcsetattr() -- %s", strerror(errno));
z = read(tty, buf, len);
(void)tcsetattr(tty, TCSANOW, &old); /* don't care if this fails */
(void)write(tty, "\n", 1); /* don't care if this fails */
if (z == -1) {
fatal("error readin /dev/tty -- %s", strerror(errno));
} else if (z == 0) {
buf[z] = 0;
} else { } else {
char newline = '\n'; char *end;
size_t i = 0; buf[z] = 0;
struct termios old, new; if ((end = strchr(buf, '\r')))
if (write(tty, prompt, strlen(prompt)) == -1) *end = 0;
fatal("error asking for passphrase"); else if ((end = strchr(buf, '\n')))
tcgetattr(tty, &old); *end = 0;
new = old;
new.c_lflag &= ~ECHO;
tcsetattr(tty, TCSANOW, &new);
errno = 0;
while (i < len - 1 && read(tty, buf + i, 1) == 1) {
if (buf[i] == '\n' || buf[i] == '\r')
break;
i++;
}
buf[i] = 0;
tcsetattr(tty, TCSANOW, &old);
if (write(tty, &newline, 1) == -1)
fatal("error asking for passphrase");
close(tty);
if (errno)
fatal("could not read passphrase from /dev/tty");
} }
close(tty);
} }
#elif defined(_WIN32)
#include <windows.h>
static void
get_passphrase(char *buf, size_t len, char *prompt)
{
DWORD orig;
HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
if (!GetConsoleMode(in, &orig)) {
get_passphrase_dumb(buf, len, prompt);
} else {
size_t passlen;
SetConsoleMode(in, orig & ~ENABLE_ECHO_INPUT);
fputs(prompt, stderr);
if (!fgets(buf, len, stdin))
fatal("could not read passphrase");
fputc('\n', stderr);
passlen = strlen(buf);
if (buf[passlen - 1] < ' ')
buf[passlen - 1] = 0;
}
}
#else
static void
get_passphrase(char *buf, size_t len, char *prompt)
{
get_passphrase_dumb(buf, len, prompt);
}
#endif
/**
* Create/truncate a file with paranoid permissions using OS calls.
*/
static FILE *secure_creat(const char *file);
#if defined(__unix__) || defined(__APPLE__)
#include <unistd.h>
static FILE *
secure_creat(const char *file)
{
int fd = open(file, O_CREAT | O_WRONLY, 00600);
if (fd == -1)
return 0;
return fdopen(fd, "wb");
}
#else
static FILE *
secure_creat(const char *file)
{
return fopen(file, "wb");
}
#endif
/** /**
* Initialize a SHA-256 context for HMAC-SHA256. * Initialize a SHA-256 context for HMAC-SHA256.
* All message data will go into the resulting context. * All message data will go into the resulting context.
@ -607,36 +511,17 @@ key_derive(const char *passphrase, u8 *buf, int iexp, const u8 *salt)
* Get secure entropy suitable for key generation from OS. * Get secure entropy suitable for key generation from OS.
* Abort the program if the entropy could not be retrieved. * Abort the program if the entropy could not be retrieved.
*/ */
static void secure_entropy(void *buf, size_t len);
#if defined(__unix__) || defined(__APPLE__)
static void static void
secure_entropy(void *buf, size_t len) secure_entropy(void *buf, size_t len)
{ {
FILE *r = fopen("/dev/urandom", "rb"); int fd = open("/dev/urandom", O_RDONLY);
if (!r) if (fd == -1)
fatal("failed to open %s", "/dev/urandom"); fatal("failed to open %s", "/dev/urandom");
if (!fread(buf, len, 1, r)) if (read(fd, buf, len) != (ssize_t)len)
fatal("failed to gather entropy"); fatal("failed to gather entropy");
fclose(r); close(fd);
} }
#elif defined(_WIN32)
#include <windows.h>
static void
secure_entropy(void *buf, size_t len)
{
HCRYPTPROV h = 0;
DWORD type = PROV_RSA_FULL;
DWORD flags = CRYPT_VERIFYCONTEXT | CRYPT_SILENT;
if (!CryptAcquireContext(&h, 0, 0, type, flags) ||
!CryptGenRandom(h, len, buf))
fatal("failed to gather entropy");
CryptReleaseContext(h, 0);
}
#endif
/** /**
* Generate a brand new Curve25519 secret key from system entropy. * Generate a brand new Curve25519 secret key from system entropy.
*/ */
@ -672,7 +557,7 @@ compute_shared(u8 *sh, const u8 *s, const u8 *p)
* Encrypt from file to file using key/iv, aborting on any error. * Encrypt from file to file using key/iv, aborting on any error.
*/ */
static void static void
symmetric_encrypt(FILE *in, FILE *out, const u8 *key, const u8 *iv) symmetric_encrypt(int in, int out, const u8 *key, const u8 *iv)
{ {
static u8 buffer[2][CHACHA_BLOCKLENGTH * 1024]; static u8 buffer[2][CHACHA_BLOCKLENGTH * 1024];
u8 mac[SHA256_BLOCK_SIZE]; u8 mac[SHA256_BLOCK_SIZE];
@ -684,34 +569,33 @@ symmetric_encrypt(FILE *in, FILE *out, const u8 *key, const u8 *iv)
hmac_init(hmac, key); hmac_init(hmac, key);
for (;;) { for (;;) {
size_t z = fread(buffer[0], 1, sizeof(buffer[0]), in); ssize_t z = full_read(in, buffer[0], sizeof(buffer[0]));
if (!z) { if (z < 0)
if (ferror(in)) fatal("error reading plaintext file -- %s", strerror(errno));
fatal("error reading plaintext file"); if (z == 0)
break; break;
}
sha256_update(hmac, buffer[0], z); sha256_update(hmac, buffer[0], z);
chacha_encrypt_bytes(ctx, buffer[0], buffer[1], z); chacha_encrypt_bytes(ctx, buffer[0], buffer[1], z);
if (!fwrite(buffer[1], z, 1, out)) if (write(out, buffer[1], z) != z)
fatal("error writing ciphertext file"); fatal("error writing ciphertext file -- %s", strerror(errno));
if (z < sizeof(buffer[0])) if (z < (ssize_t)sizeof(buffer[0]))
break; break;
} }
hmac_final(hmac, key, mac); hmac_final(hmac, key, mac);
if (!fwrite(mac, sizeof(mac), 1, out)) if (write(out, mac, sizeof(mac)) != sizeof(mac))
fatal("error writing checksum to ciphertext file"); fatal("error writing checksum to ciphertext file -- %s",
if (fflush(out)) strerror(errno));
fatal("error flushing to ciphertext file -- %s", strerror(errno));
} }
/** /**
* Decrypt from file to file using key/iv, aborting on any error. * Decrypt from file to file using key/iv, aborting on any error.
*/ */
static void static void
symmetric_decrypt(FILE *in, FILE *out, const u8 *key, const u8 *iv) symmetric_decrypt(int in, int out, const u8 *key, const u8 *iv)
{ {
ssize_t r;
static u8 buffer[2][CHACHA_BLOCKLENGTH * 1024 + SHA256_BLOCK_SIZE]; static u8 buffer[2][CHACHA_BLOCKLENGTH * 1024 + SHA256_BLOCK_SIZE];
u8 mac[SHA256_BLOCK_SIZE]; u8 mac[SHA256_BLOCK_SIZE];
SHA256_CTX hmac[1]; SHA256_CTX hmac[1];
@ -722,39 +606,34 @@ symmetric_decrypt(FILE *in, FILE *out, const u8 *key, const u8 *iv)
hmac_init(hmac, key); hmac_init(hmac, key);
/* Always keep SHA256_BLOCK_SIZE bytes in the buffer. */ /* Always keep SHA256_BLOCK_SIZE bytes in the buffer. */
if (!(fread(buffer[0], SHA256_BLOCK_SIZE, 1, in))) { r = full_read(in, buffer[0], SHA256_BLOCK_SIZE);
if (ferror(in)) if (r < 0)
fatal("cannot read ciphertext file"); fatal("cannot read ciphertext file -- %s", strerror(errno));
else if (r != SHA256_BLOCK_SIZE)
fatal("ciphertext file too short"); fatal("ciphertext file too short");
}
for (;;) { for (;;) {
u8 *p = buffer[0] + SHA256_BLOCK_SIZE; u8 *p = buffer[0] + SHA256_BLOCK_SIZE;
size_t z = fread(p, 1, sizeof(buffer[0]) - SHA256_BLOCK_SIZE, in); ssize_t z = full_read(in, p, sizeof(buffer[0]) - SHA256_BLOCK_SIZE);
if (!z) { if (z < 0)
if (ferror(in)) fatal("error reading ciphertext file");
fatal("error reading ciphertext file"); else if (z == 0)
break; break;
}
chacha_encrypt_bytes(ctx, buffer[0], buffer[1], z); chacha_encrypt_bytes(ctx, buffer[0], buffer[1], z);
sha256_update(hmac, buffer[1], z); sha256_update(hmac, buffer[1], z);
if (!fwrite(buffer[1], z, 1, out)) if (write(out, buffer[1], z) != z)
fatal("error writing plaintext file"); fatal("error writing plaintext file -- %s", strerror(errno));
/* Move last SHA256_BLOCK_SIZE bytes to the front. */ /* Move last SHA256_BLOCK_SIZE bytes to the front. */
memmove(buffer[0], buffer[0] + z, SHA256_BLOCK_SIZE); memmove(buffer[0], buffer[0] + z, SHA256_BLOCK_SIZE);
if (z < sizeof(buffer[0]) - SHA256_BLOCK_SIZE) if (z < (ssize_t)sizeof(buffer[0]) - SHA256_BLOCK_SIZE)
break; break;
} }
hmac_final(hmac, key, mac); hmac_final(hmac, key, mac);
if (memcmp(buffer[0], mac, sizeof(mac)) != 0) if (memcmp(buffer[0], mac, sizeof(mac)) != 0)
fatal("checksum mismatch!"); fatal("checksum mismatch!");
if (fflush(out))
fatal("error flushing to plaintext file -- %s", strerror(errno));
} }
/** /**
@ -781,16 +660,14 @@ default_secfile(void)
static void static void
write_pubkey(char *file, u8 *key) write_pubkey(char *file, u8 *key)
{ {
FILE *f = fopen(file, "wb"); int fd = open(file, O_CREAT | O_WRONLY, 0600);
if (!f) if (fd == -1)
fatal("failed to open key file for writing '%s' -- %s", fatal("failed to open key file for writing '%s' -- %s",
file, strerror(errno)); file, strerror(errno));
cleanup_register(f, file); cleanup_pubfile = file;
if (!fwrite(key, 32, 1, f)) if (write(fd, key, 32) != 32)
fatal("failed to write key file '%s'", file); fatal("failed to write key file '%s'", file);
cleanup_closed(f); close(fd);
if (fclose(f))
fatal("failed to flush key file '%s' -- %s", file, strerror(errno));
} }
/* Layout of secret key file */ /* Layout of secret key file */
@ -806,7 +683,7 @@ write_pubkey(char *file, u8 *key)
static void static void
write_seckey(char *file, const u8 *seckey, int iexp) write_seckey(char *file, const u8 *seckey, int iexp)
{ {
FILE *secfile; int secfd;
chacha_ctx cha[1]; chacha_ctx cha[1];
SHA256_CTX sha[1]; SHA256_CTX sha[1];
u8 buf[8 + 1 + 3 + 20 + 32] = {0}; /* entire file contents */ u8 buf[8 + 1 + 3 + 20 + 32] = {0}; /* entire file contents */
@ -856,15 +733,13 @@ write_seckey(char *file, const u8 *seckey, int iexp)
memcpy(buf_seckey, seckey, 32); memcpy(buf_seckey, seckey, 32);
} }
secfile = secure_creat(file); secfd = open(file, O_CREAT | O_WRONLY, 0600);
if (!secfile) if (secfd == -1)
fatal("failed to open key file for writing '%s'", file); fatal("failed to open key file for writing '%s'", file);
cleanup_register(secfile, file); cleanup_secfile = file;
if (!fwrite(buf, sizeof(buf), 1, secfile)) if (write(secfd, buf, sizeof(buf)) != sizeof(buf))
fatal("failed to write key file '%s'", file); fatal("failed to write key file '%s'", file);
cleanup_closed(secfile); close(secfd);
if (fclose(secfile))
fatal("failed to flush key file '%s' -- %s", file, strerror(errno));
} }
/** /**
@ -1198,8 +1073,8 @@ command_archive(struct optparse *options)
/* Options */ /* Options */
char *infile; char *infile;
char *outfile; char *outfile;
FILE *in = stdin; int in = STDIN_FILENO;
FILE *out = stdout; int out = STDOUT_FILENO;
char *pubfile = dupstr(global_pubkey); char *pubfile = dupstr(global_pubkey);
int delete = 0; int delete = 0;
@ -1229,8 +1104,8 @@ command_archive(struct optparse *options)
infile = optparse_arg(options); infile = optparse_arg(options);
if (infile) { if (infile) {
in = fopen(infile, "rb"); in = open(infile, O_RDONLY);
if (!in) if (in == -1)
fatal("could not open input file '%s' -- %s", fatal("could not open input file '%s' -- %s",
infile, strerror(errno)); infile, strerror(errno));
} }
@ -1241,11 +1116,11 @@ command_archive(struct optparse *options)
outfile = joinstr(2, infile, enchive_suffix); outfile = joinstr(2, infile, enchive_suffix);
} }
if (outfile) { if (outfile) {
out = fopen(outfile, "wb"); out = open(outfile, O_CREAT | O_WRONLY, 0600);
if (!out) if (out == -1)
fatal("could not open output file '%s' -- %s", fatal("could not open output file '%s' -- %s",
outfile, strerror(errno)); outfile, strerror(errno));
cleanup_register(out, outfile); cleanup_outfile = outfile;
} }
/* Generare ephemeral keypair. */ /* Generare ephemeral keypair. */
@ -1258,22 +1133,16 @@ command_archive(struct optparse *options)
sha256_update(sha, shared, sizeof(shared)); sha256_update(sha, shared, sizeof(shared));
sha256_final(sha, iv); sha256_final(sha, iv);
iv[0] += (unsigned)ENCHIVE_FORMAT_VERSION; iv[0] += (unsigned)ENCHIVE_FORMAT_VERSION;
if (!fwrite(iv, 8, 1, out)) if (write(out, iv, 8) != 8)
fatal("failed to write IV to archive"); fatal("failed to write IV to archive");
if (!fwrite(epublic, sizeof(epublic), 1, out)) if (write(out, epublic, sizeof(epublic)) != sizeof(epublic))
fatal("failed to write ephemeral key to archive"); fatal("failed to write ephemeral key to archive");
symmetric_encrypt(in, out, shared, iv); symmetric_encrypt(in, out, shared, iv);
if (in != stdin) close(in);
fclose(in); close(out);
if (out != stdout) {
cleanup_closed(out);
fclose(out); /* already flushed */
}
if (delete && infile) if (delete && infile)
remove(infile); remove(infile);
} }
static void static void
@ -1287,8 +1156,8 @@ command_extract(struct optparse *options)
/* Options */ /* Options */
char *infile; char *infile;
char *outfile; char *outfile;
FILE *in = stdin; int in = STDIN_FILENO;
FILE *out = stdout; int out = STDOUT_FILENO;
char *secfile = dupstr(global_seckey); char *secfile = dupstr(global_seckey);
int delete = 0; int delete = 0;
@ -1318,8 +1187,8 @@ command_extract(struct optparse *options)
infile = optparse_arg(options); infile = optparse_arg(options);
if (infile) { if (infile) {
in = fopen(infile, "rb"); in = open(infile, O_RDONLY);
if (!in) if (in == -1)
fatal("could not open input file '%s' -- %s", fatal("could not open input file '%s' -- %s",
infile, strerror(errno)); infile, strerror(errno));
} }
@ -1335,17 +1204,18 @@ command_extract(struct optparse *options)
outfile[len - slen] = 0; outfile[len - slen] = 0;
} }
if (outfile) { if (outfile) {
out = fopen(outfile, "wb"); out = open(outfile, O_CREAT | O_WRONLY, 0600);
if (!out) if (out == -1)
fatal("could not open output file '%s' -- %s", fatal("could not open output file '%s' -- %s",
infile, strerror(errno)); infile, strerror(errno));
cleanup_register(out, outfile); cleanup_outfile = outfile;
} }
if (!(fread(iv, sizeof(iv), 1, in))) if (full_read(in, iv, sizeof(iv)) != sizeof(iv))
fatal("failed to read IV from archive"); fatal("failed to read IV from archive -- %s", strerror(errno));
if (!(fread(epublic, sizeof(epublic), 1, in))) if (full_read(in, epublic, sizeof(epublic)) != sizeof(epublic))
fatal("failed to read ephemeral key from archive"); fatal("failed to read ephemeral key from archive -- %s",
strerror(errno));
compute_shared(shared, secret, epublic); compute_shared(shared, secret, epublic);
/* Validate key before processing the file. */ /* Validate key before processing the file. */
@ -1358,13 +1228,8 @@ command_extract(struct optparse *options)
symmetric_decrypt(in, out, shared, iv); symmetric_decrypt(in, out, shared, iv);
if (in != stdin) close(in);
fclose(in); close(out);
if (out != stdout) {
cleanup_closed(out);
fclose(out); /* already flushed */
}
if (delete && infile) if (delete && infile)
remove(infile); remove(infile);
} }
@ -1481,6 +1346,5 @@ main(int argc, char **argv)
break; break;
} }
cleanup_free();
return 0; return 0;
} }

301
src/w32-compat.h Normal file
View File

@ -0,0 +1,301 @@
#if !defined(W32_COMPAT_H) && defined(_WIN32)
#define W32_COMPAT_H
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#ifdef _MSC_VER
# pragma comment(lib, "advapi32.lib")
#endif
typedef SSIZE_T ssize_t;
typedef unsigned mode_t;
#define O_CREAT (1u << 0)
#define O_RDONLY (1u << 1)
#define O_WRONLY (1u << 2)
#define O_RDWR (1u << 3)
#define ECHO 1
#define TCSANOW 0
struct termios {
int c_lflag;
};
enum w32_fd {
FD_NONE,
FD_STDIN,
FD_STDOUT,
FD_STDERR,
FD_URANDOM,
FD_TTY,
FD_FILE
};
static struct {
enum w32_fd type;
union {
HANDLE file;
HCRYPTPROV urandom;
struct {
HANDLE in;
HANDLE out;
} con;
} d;
} w32_fds[8] = {
{FD_STDIN, {INVALID_HANDLE_VALUE}},
{FD_STDOUT, {INVALID_HANDLE_VALUE}},
{FD_STDERR, {INVALID_HANDLE_VALUE}}
};
static int
open(const char *pathname, int flags, ...)
{
int fd = -1;
size_t i;
for (i = 0; fd < 0 && i < sizeof(w32_fds) / sizeof(*w32_fds); i++)
if (w32_fds[i].type == FD_NONE)
fd = i;
if (fd == -1) {
errno = EMFILE;
return -1;
}
if (strcmp(pathname, "/dev/urandom") == 0) {
DWORD type = PROV_RSA_FULL;
DWORD flag = CRYPT_VERIFYCONTEXT | CRYPT_SILENT;
HCRYPTPROV *h = &w32_fds[fd].d.urandom;
if (!CryptAcquireContext(h, 0, 0, type, flag)) {
errno = EACCES;
return -1;
}
assert(flags == O_RDONLY);
w32_fds[fd].type = FD_URANDOM;
return fd;
} else if (strcmp(pathname, "/dev/tty") == 0) {
DWORD access = GENERIC_READ | GENERIC_WRITE;
DWORD disp = OPEN_EXISTING;
DWORD flag = FILE_ATTRIBUTE_NORMAL;
HANDLE in = CreateFile("CONIN$", access, 0, 0, disp, flag, 0);
HANDLE out = CreateFile("CONOUT$", access, 0, 0, disp, flag, 0);
if (in == INVALID_HANDLE_VALUE) {
CloseHandle(out);
errno = ENOENT;
return -1;
}
if (out == INVALID_HANDLE_VALUE) {
CloseHandle(in);
errno = ENOENT;
return -1;
}
assert(flags == O_RDWR);
w32_fds[fd].d.con.in = in;
w32_fds[fd].d.con.out = out;
w32_fds[fd].type = FD_TTY;
return fd;
} else {
HANDLE h;
DWORD access;
DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
DWORD disp;
DWORD flag = FILE_ATTRIBUTE_NORMAL;
if (flags & O_CREAT)
disp = CREATE_ALWAYS;
else
disp = OPEN_EXISTING;
if (flags & O_RDWR)
access = GENERIC_READ | GENERIC_WRITE;
else if (flags & O_RDONLY)
access = GENERIC_READ;
else if (flags & O_WRONLY)
access = GENERIC_WRITE;
else
abort();
h = CreateFile(pathname, access, share, 0, disp, flag, 0);
if (h == INVALID_HANDLE_VALUE) {
errno = EACCES;
return -1;
}
w32_fds[fd].d.file = h;
w32_fds[fd].type = FD_FILE;
return fd;
}
}
static int
close(int fd)
{
switch (w32_fds[fd].type) {
case FD_NONE: {
abort();
} break;
case FD_STDIN:
case FD_STDOUT:
case FD_STDERR: {
abort(); /* unimplemented */
} break;
case FD_URANDOM: {
CryptReleaseContext(w32_fds[fd].d.urandom, 0);
} break;
case FD_TTY: {
CloseHandle(w32_fds[fd].d.con.in);
CloseHandle(w32_fds[fd].d.con.out);
} break;
case FD_FILE: {
CloseHandle(w32_fds[fd].d.file);
} break;
}
w32_fds[fd].type = FD_NONE;
return 0;
}
static ssize_t
read(int fd, void *buf, size_t len)
{
switch (w32_fds[fd].type) {
case FD_NONE: {
abort();
}
case FD_STDIN: {
if (w32_fds[fd].d.file == INVALID_HANDLE_VALUE) {
DWORD mode;
w32_fds[fd].d.file = GetStdHandle(STD_INPUT_HANDLE);
assert(!GetConsoleMode(w32_fds[fd].d.file, &mode));
}
} /* FALLTHROUGH */
case FD_FILE: {
DWORD actual;
HANDLE in = w32_fds[fd].d.file;
if (!ReadFile(in, buf, len, &actual, 0)) {
DWORD error = GetLastError();
if (error == ERROR_BROKEN_PIPE)
return 0; /* actually an EOF */
errno = EIO;
return -1;
}
return actual;
} break;
case FD_STDOUT:
case FD_STDERR: {
abort();
} break;
case FD_URANDOM: {
if (!CryptGenRandom(w32_fds[fd].d.urandom, len, buf)) {
errno = EIO;
return -1;
}
return len;
}
case FD_TTY: {
DWORD actual;
BOOL r = ReadConsole(w32_fds[fd].d.con.in, buf, len, &actual, 0);
if (!r) {
errno = EIO;
return -1;
}
return actual;
}
}
abort();
}
static ssize_t
write(int fd, const void *buf, size_t len)
{
switch (w32_fds[fd].type) {
case FD_URANDOM:
case FD_NONE: {
abort();
}
case FD_STDIN: {
abort();
} break;
case FD_STDOUT:
case FD_STDERR: {
if (w32_fds[fd].d.file == INVALID_HANDLE_VALUE) {
DWORD mode;
if (w32_fds[fd].type == FD_STDOUT)
w32_fds[fd].d.file = GetStdHandle(STD_OUTPUT_HANDLE);
else
w32_fds[fd].d.file = GetStdHandle(STD_ERROR_HANDLE);
assert(!GetConsoleMode(w32_fds[fd].d.file, &mode));
}
} /* FALLTHROUGH */
case FD_FILE: {
DWORD actual;
if (!WriteFile(w32_fds[fd].d.file, buf, len, &actual, 0)) {
errno = EIO;
return -1;
}
return actual;
} break;
case FD_TTY: {
DWORD actual;
BOOL r = WriteConsole(w32_fds[fd].d.con.out, buf, len, &actual, 0);
if (!r) {
errno = EIO;
return -1;
}
return actual;
}
}
abort();
}
static int
tcgetattr(int fd, struct termios *s)
{
assert(w32_fds[fd].type == FD_TTY);
s->c_lflag = 1;
return 0;
}
static int
tcsetattr(int fd, int actions, struct termios *s)
{
DWORD orig;
HANDLE in = w32_fds[fd].d.con.in;
assert(w32_fds[fd].type == FD_TTY);
assert(actions == TCSANOW);
if (GetConsoleMode(in, &orig)) {
if (s->c_lflag)
SetConsoleMode(in, orig | ENABLE_ECHO_INPUT);
else
SetConsoleMode(in, orig & ~ENABLE_ECHO_INPUT);
}
return 0;
}
#if 0
static int
mkdir(const char *pathname, mode_t mode)
{
(void)mode;
if (CreateDirectory(pathname, 0))
return 0;
switch (GetLastError()) {
case ERROR_ALREADY_EXISTS: {
errno = EEXIST;
} break;
case ERROR_PATH_NOT_FOUND: {
errno = ENOENT;
} break;
default: {
errno = EFAULT;
}
}
return -1;
}
#endif
#endif