From: Jann Horn Date: Mon, 12 Nov 2012 16:02:52 +0000 (+0100) Subject: version one X-Git-Url: http://git.thejh.net/?a=commitdiff_plain;h=44ce233ab82694ae1c85d0969ec3dd89d04c4992;p=cwebfiles.git version one --- 44ce233ab82694ae1c85d0969ec3dd89d04c4992 diff --git a/cgistuff.c b/cgistuff.c new file mode 100644 index 0000000..ac816e2 --- /dev/null +++ b/cgistuff.c @@ -0,0 +1,121 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include "cwebfiles.h" +#include +#include +#include +#include +#include + +extern char **environ; + +// don't use before you've authenticated successfully! +struct cgi_serialized_session_status session; + +// when we call this function, we usually leak memory - not an issue +static char *session_file_path(char *cookie) { + char *file = malloc(strlen(SESSION_FOLDER)+COOKIE_LENGTH+1); + memcpy(file, SESSION_FOLDER, strlen(SESSION_FOLDER)); + memcpy(file+strlen(SESSION_FOLDER), cookie, COOKIE_LENGTH); + file[strlen(SESSION_FOLDER)+COOKIE_LENGTH] = '\0'; + return file; +} + +int authenticate_by_cookie(char *cookie) { + // check cookie string for conformity + if (strlen(cookie) != COOKIE_LENGTH) return 1; + if (!checkhex(cookie, COOKIE_LENGTH)) return 2; + + // read session data + int fd = open(session_file_path(cookie), O_RDONLY); + + // verify that the session exists + if (fd == -1) return 3; + int res = read(fd, &session, sizeof(session)); + close(fd); + + // verify that the session file is valid + if (res != sizeof(session)) return 4; + session.user_name[32] = 0; + + // verify that the session is still active + time_t cur_time = time(NULL); + if (session.start_time + SESSION_LENGTH < cur_time) return 5; + if (session.start_time > cur_time) return 6; + + // verify that the username and the userid still match + struct passwd *user_data = getpwuid(session.uid); + if (user_data == NULL) return 7; + if (strcmp(user_data->pw_name, session.user_name) != 0) return 8; + + // everything seems fine! :) + return 0; +} + +void persist_session(char *cookie) { + char *file = session_file_path(cookie); + int fd = open(file, O_RDWR|O_EXCL|O_CREAT, S_IRUSR|S_IWUSR); + if (fd == -1) senderr("can't create session file", MYFAULT); + + errno = 0; + int res = write(fd, &session, sizeof(session)); + char errstr[128]; + if (res != sizeof(session)) { + snprintf(errstr, 128, "can't write all session data (%i of %i): %s", (int)res, (int)sizeof(session), strerror(errno)); + } + close(fd); + if (res != sizeof(session)) { + unlink(file); + senderr(errstr, MYFAULT); + } +} + +void senderr(char *errmsg, bool myfault) { + if (myfault) { + puts("Status: 500 CGI error"); + } else { + puts("Status: 400 CGI error"); + } + puts("Content-Type: text/plain;charset=utf8"); + puts("X-Frame-Options: DENY"); + puts(""); + puts("An error occured while processing your request:"); + puts(errmsg); + exit(0); +} + +// may send an error response and exit +void grabrand(unsigned char *out, size_t len) { + int randfd = open(RANDDEV, O_RDONLY); + if (randfd == -1) senderr("can't open random device", MYFAULT); + if (read(randfd, out, len) != len) senderr("reading random data failed", MYFAULT); + close(randfd); +} + +void login_and_setup() { + char *cookies = getenv("HTTP_COOKIE"); + environ = NULL; + setuid(0); + + // Note that this doesn't validate whether this is actually a cookie name or + // part of a value - but that shouldn't be a problem. + // Note: Redo if I feel like doing it. + if (cookies == NULL) senderr("no cookies sent", NOTMYFAULT); + char *session_cookie = strstr(cookies, COOKIE_NAME"="); + if (session_cookie == NULL) senderr("session cookie not present", NOTMYFAULT); + session_cookie += strlen(COOKIE_NAME"="); + if (strlen(session_cookie) < COOKIE_LENGTH) senderr("invalid session cookie (error code 1)", NOTMYFAULT); + session_cookie[COOKIE_LENGTH] = '\0'; + int res = authenticate_by_cookie(session_cookie); + if (res != 0) { + char errstr[128]; + snprintf(errstr, 128, "invalid session cookie (error code 2-%i)", res); + senderr(errstr, NOTMYFAULT); + } +} diff --git a/checkstatus.c b/checkstatus.c new file mode 100644 index 0000000..c620e1f --- /dev/null +++ b/checkstatus.c @@ -0,0 +1,13 @@ +#include +#include "cwebfiles.h" + +int main() { + FASTOUTPUT + + login_and_setup(); + puts("Status: 200 you're authenticated"); + puts("X-Frame-Options: DENY"); + puts("Content-Type: text/plain;charset=utf8"); + puts(""); + puts("OK"); +} diff --git a/compile b/compile new file mode 100755 index 0000000..68ca0e5 --- /dev/null +++ b/compile @@ -0,0 +1,6 @@ +#!/bin/bash +for cgi in login checkstatus listdir; do + diet gcc -O3 -std=gnu99 -Wall -Werror -g -o $cgi $cgi.c cgistuff.c hex.c -lcrypt + sudo chown root:root $cgi + sudo chmod u+s $cgi +done diff --git a/cwebfiles.h b/cwebfiles.h new file mode 100644 index 0000000..cada2eb --- /dev/null +++ b/cwebfiles.h @@ -0,0 +1,39 @@ +#include +#include +#include + +#define SESSION_FOLDER "/var/run/cwebfiles/sessions/" +#define SESSION_LENGTH 60 * 60 * 3 /* unit is seconds; we allow three-hour-sessions */ + +#define COOKIE_NAME "WEBFILESESS" +#define COOKIE_LENGTH 64 + +#define RANDDEV "/dev/urandom" + +#define MYFAULT true +#define NOTMYFAULT false + +struct cgi_serialized_session_status { + // "man useradd" says 32 chars is the length limit + char user_name[33]; + uid_t uid; + time_t start_time; +}; + +extern struct cgi_serialized_session_status session; + +void unhex(unsigned char *out, char *in, size_t in_len); +void hex(char *out, unsigned char *in, size_t in_len); +bool checkhex(char *p, size_t l); + +int authenticate_by_cookie(char *cookie); +void persist_session(char *cookie); +void senderr(char *errmsg, bool myfault); +void grabrand(unsigned char *out, size_t len); +void login_and_setup(); + +#define OUTBUFSIZE 64*1024 +#define FASTOUTPUT \ + char outbuf[OUTBUFSIZE]; \ + setvbuf(stdout, outbuf, _IOFBF, OUTBUFSIZE); \ + diff --git a/hex.c b/hex.c new file mode 100644 index 0000000..150d4de --- /dev/null +++ b/hex.c @@ -0,0 +1,35 @@ +#include "cwebfiles.h" + +// these routines don't respect nullbytes! + +// we use fast hex. start from 'a'. no checks performed (apart from length validity). +// out must be half the size of in +void unhex(unsigned char *out, char *in, size_t in_len) { + in_len = in_len & ~1; // input length mod 2 must be 0 - mask out the last bit + char *in_end = in + in_len; + while (in != in_end) { + unsigned char in1 = *(in++), in2 = *(in++); + *(out++) = (in1 - 'a') << 4 | (in2 - 'a'); + } +} + +// out must be twice the size of in +void hex(char *out, unsigned char *in, size_t in_len) { + unsigned char *in_end = in+in_len; + while (in != in_end) { + *(out++) = (*in >> 4) + 'a'; + *(out++) = (*in & 0xf) + 'a'; + in++; + } +} + +bool checkhex(char *p, size_t l) { + if ((l&1) == 1) return false; + char *e = p+l; + while (p= 'a'+16) return false; + p++; + } + return true; +} diff --git a/listdir.c b/listdir.c new file mode 100644 index 0000000..bfbd47c --- /dev/null +++ b/listdir.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include "cwebfiles.h" +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + FASTOUTPUT + + char *encpath = getenv("QUERY_STRING"); + login_and_setup(); // will clear the environment + if (encpath == NULL) senderr("no query string received", NOTMYFAULT); + + // OK, we're authenticated. Drop our fsuid to the user's. + // Yes, there's no error indication apart from this kludge. Sucks. + setfsuid(session.uid); + if (setfsuid(session.uid) != session.uid) senderr("setfsuid failed", MYFAULT); + + // Do what the user wants us to do. + char path[strlen(encpath)/2+1]; + unhex((unsigned char *)path, encpath, strlen(encpath)); + path[strlen(encpath)/2] = 0; + if (chdir(path)) { + char errstr[128]; + snprintf(errstr, 128, "can't access that path: %s", strerror(errno)); + senderr(errstr, NOTMYFAULT); + } + DIR *dir = opendir("."); + if (dir == NULL) { + char errstr[128]; + snprintf(errstr, 128, "can't open that path for reading: %s", strerror(errno)); + senderr(errstr, NOTMYFAULT); + } + + // The directory was opened successfully - send our success header. + puts("Status: 200 here's your listing"); + puts("X-Frame-Options: DENY"); + puts("Content-Type: text/plain;charset=utf8"); + puts(""); + + // send one line per file + struct dirent *dent; + char encfilename[NAME_MAX*2+1]; + while ((dent = readdir(dir)) != NULL) { + size_t name_len = strlen(dent->d_name); + hex(encfilename, (unsigned char *)dent->d_name, name_len); + encfilename[name_len*2] = 0; + struct stat st; + // if we can't really see the file anyway, skip it + if (lstat(dent->d_name, &st)) continue; + printf("%s %lu %u\n", encfilename, (unsigned long)st.st_size, (unsigned int)st.st_mode); + } +} diff --git a/login.c b/login.c new file mode 100644 index 0000000..96b95bc --- /dev/null +++ b/login.c @@ -0,0 +1,68 @@ +#define _GNU_SOURCE + +#include +#include +#include "cwebfiles.h" +#include +#include +#include +#include +#include +#include +#include +#include + +extern char **environ; + +#define BADAUTH "invalid user/pass" + +int main() { + FASTOUTPUT + environ = NULL; + setuid(0); + + char buf[1024]; + int res = read(0, buf, 1023); + if (res == -1) senderr("couldn't read POST data", NOTMYFAULT); + buf[res] = 0; + char *nl = strchr(buf, '\r'); + if (nl != NULL) *nl = 0; + char *user = buf; + char *pass = strchr(buf, ':'); + if (pass == NULL) senderr("can't find user-pass-seperator", NOTMYFAULT); + *pass = 0; + pass++; + + // verify password + struct spwd *shadow_entry = getspnam(user); + struct passwd *passwd_entry = getpwnam(user); + if (passwd_entry == NULL || shadow_entry == NULL) senderr(BADAUTH, NOTMYFAULT); + char *enc_pass = crypt(pass, shadow_entry->sp_pwdp); + if (enc_pass == NULL) senderr(BADAUTH, NOTMYFAULT); + if (strcmp(enc_pass, shadow_entry->sp_pwdp) != 0) senderr(BADAUTH, NOTMYFAULT); + + // now create a new session + // create a cookie + unsigned char bincookie[COOKIE_LENGTH/2]; + grabrand(bincookie, COOKIE_LENGTH/2); + char cookie[COOKIE_LENGTH+1]; + hex(cookie, bincookie, COOKIE_LENGTH/2); + cookie[COOKIE_LENGTH] = 0; + + // set session data + strncpy(session.user_name, user, 33); + session.start_time = time(NULL); + session.uid = passwd_entry->pw_uid; + + // write out session data to the fs + persist_session(cookie); + + // phew, all done! tell the user everything is fine + puts("Status: 200 login successful"); + puts("X-Frame-Options: DENY"); + puts("Content-Type: text/plain;charset=utf8"); + // TODO XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX SET SECURE FLAG! + printf("Set-Cookie: " COOKIE_NAME "=%s; HttpOnly\n", cookie); + puts(""); + puts("OK"); +} diff --git a/octalmodes b/octalmodes new file mode 100644 index 0000000..e4e5627 --- /dev/null +++ b/octalmodes @@ -0,0 +1,23 @@ + S_IFMT 0170000 bit mask for the file type bit fields + S_IFSOCK 0140000 socket + S_IFLNK 0120000 symbolic link + S_IFREG 0100000 regular file + S_IFBLK 0060000 block device + S_IFDIR 0040000 directory + S_IFCHR 0020000 character device + S_IFIFO 0010000 FIFO + S_ISUID 0004000 set UID bit + S_ISGID 0002000 set-group-ID bit (see below) + S_ISVTX 0001000 sticky bit (see below) + S_IRWXU 00700 mask for file owner permissions + S_IRUSR 00400 owner has read permission + S_IWUSR 00200 owner has write permission + S_IXUSR 00100 owner has execute permission + S_IRWXG 00070 mask for group permissions + S_IRGRP 00040 group has read permission + S_IWGRP 00020 group has write permission + S_IXGRP 00010 group has execute permission + S_IRWXO 00007 mask for permissions for others (not in group) + S_IROTH 00004 others have read permission + S_IWOTH 00002 others have write permission + S_IXOTH 00001 others have execute permission diff --git a/octalmodes_to_js b/octalmodes_to_js new file mode 100644 index 0000000..107470b --- /dev/null +++ b/octalmodes_to_js @@ -0,0 +1 @@ +cat octalmodes | sed 's|^ *|var |g' | sed 's| *| = parseInt("|' | sed 's| *|",8) // |' diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..1e6adcc --- /dev/null +++ b/static/index.html @@ -0,0 +1,179 @@ + + + + + + + + +