--- /dev/null
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <stdio.h>
+#include "cwebfiles.h"
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <errno.h>
+
+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);
+ }
+}
--- /dev/null
+#include <stdio.h>
+#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");
+}
--- /dev/null
+#!/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
--- /dev/null
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#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); \
+
--- /dev/null
+#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<e) {
+ if (*p < 'a') return false;
+ if (*p >= 'a'+16) return false;
+ p++;
+ }
+ return true;
+}
--- /dev/null
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "cwebfiles.h"
+#include <sys/types.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <sys/fsuid.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+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);
+ }
+}
--- /dev/null
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include "cwebfiles.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include <shadow.h>
+#include <pwd.h>
+#include <stdio.h>
+
+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");
+}
--- /dev/null
+ 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
--- /dev/null
+cat octalmodes | sed 's|^ *|var |g' | sed 's| *| = parseInt("|' | sed 's| *|",8) // |'
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+function hex(str) {
+ var res = "";
+ var achar = 'a'.charCodeAt(0)
+ for (var i=0; i<str.length; i++) {
+ var c = str.charCodeAt(i)
+ res += String.fromCharCode((c>>4)+achar) + String.fromCharCode((c&0xf)+achar)
+ }
+ return res
+}
+
+function dehex(str) {
+ var res = ""
+ var achar = 'a'.charCodeAt(0)
+ for (var i=0; i<str.length; i+=2) {
+ var c1 = str.charCodeAt(i)-achar, c2 = str.charCodeAt(i+1)-achar
+ res += String.fromCharCode((c1<<4)+c2)
+ }
+ return res
+}
+
+function cgireq(script, qs, body, cb) {
+ var req = new XMLHttpRequest()
+ req.open((body!=null)?'POST':'GET', '/cgi-bin/cwebfiles/'+script+(qs?'?'+qs:''), !!cb)
+ if (cb) {
+ req.onloadend = function() {
+ cb(req.status, req.responseText)
+ }
+ }
+ req.send(body)
+ if (!cb) {
+ return {status: req.status, data: req.responseText}
+ }
+}
+
+function login(user, pass, cb) {
+ return cgireq('login', null, user+':'+pass, cb)
+}
+
+function listdir(path, cb) {
+ cgireq('listdir', hex(path), null, function(status, data) {
+ if (status != 200) return cb(status, data, null)
+ var listing = data.split('\n').filter(function(line) {
+ return line.trim().length > 0
+ }).map(function(line) {
+ var parts = line.split(' ')
+ return {name: dehex(parts[0]), size: parseInt(parts[1]), mode: parseInt(parts[2])}
+ })
+ cb(status, data, listing)
+ })
+}
+
+var S_IFMT = parseInt("0170000",8) // bit mask for the file type bit fields
+var S_IFSOCK = parseInt("0140000",8) // socket
+var S_IFLNK = parseInt("0120000",8) // symbolic link
+var S_IFREG = parseInt("0100000",8) // regular file
+var S_IFBLK = parseInt("0060000",8) // block device
+var S_IFDIR = parseInt("0040000",8) // directory
+var S_IFCHR = parseInt("0020000",8) // character device
+var S_IFIFO = parseInt("0010000",8) // FIFO
+
+var S_ISUID = parseInt("0004000",8) // set UID bit
+var S_ISGID = parseInt("0002000",8) // set-group-ID bit (see below)
+var S_ISVTX = parseInt("0001000",8) // sticky bit (see below)
+
+var S_IRWXU = parseInt("00700",8) // mask for file owner permissions
+var S_IRUSR = parseInt("00400",8) // owner has read permission
+var S_IWUSR = parseInt("00200",8) // owner has write permission
+var S_IXUSR = parseInt("00100",8) // owner has execute permission
+var S_IRWXG = parseInt("00070",8) // mask for group permissions
+var S_IRGRP = parseInt("00040",8) // group has read permission
+var S_IWGRP = parseInt("00020",8) // group has write permission
+var S_IXGRP = parseInt("00010",8) // group has execute permission
+var S_IRWXO = parseInt("00007",8) // mask for permissions for others (not in group)
+var S_IROTH = parseInt("00004",8) // others have read permission
+var S_IWOTH = parseInt("00002",8) // others have write permission
+var S_IXOTH = parseInt("00001",8) // others have execute permission
+
+
+var typechars = {}
+typechars[S_IFSOCK] = 's'
+typechars[S_IFLNK] = 'l'
+typechars[S_IFREG] = '-'
+typechars[S_IFBLK] = 'b'
+typechars[S_IFDIR] = 'd'
+typechars[S_IFCHR] = 'c'
+typechars[S_IFIFO] = 'p'
+
+function escapehtml(text) {
+ return text.replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+}
+
+var loc = '/'
+
+function dive(hexedname) { __listdir(loc + dehex(hexedname) + '/'); }
+
+function format_entry(entry) {
+ var modeline = ""
+ modeline += typechars[entry.mode&S_IFMT]
+ modeline += (entry.mode&S_IRUSR) ? 'r' : '-'
+ modeline += (entry.mode&S_IWUSR) ? 'w' : '-'
+ modeline += (entry.mode&S_ISUID) ? ((entry.mode&S_IXUSR) ? 's' : 'S') : ((entry.mode&S_IXUSR) ? 'x' : '-')
+ modeline += (entry.mode&S_IRGRP) ? 'r' : '-'
+ modeline += (entry.mode&S_IWGRP) ? 'w' : '-'
+ modeline += (entry.mode&S_ISGID) ? ((entry.mode&S_IXGRP) ? 's' : 'S') : ((entry.mode&S_IXGRP) ? 'x' : '-')
+ modeline += (entry.mode&S_IROTH) ? 'r' : '-'
+ modeline += (entry.mode&S_IWOTH) ? 'w' : '-'
+ modeline += (entry.mode&S_ISVTX) ? ((entry.mode&S_IXOTH) ? 's' : 'S') : ((entry.mode&S_IXOTH) ? 'x' : '-')
+
+ var markred = (entry.mode&(S_ISUID|S_ISGID))
+ if (markred) {
+ modeline = '<span style="color: red">'+modeline+'</span>'
+ }
+
+ var showsize = (entry.mode&S_IFMT) == S_IFREG
+
+ var escaped_name = escapehtml(entry.name)
+
+ var clickcode = ''
+ if ((entry.mode&S_IFMT) == S_IFDIR) clickcode = "dive('"+hex(entry.name)+"')"
+
+ return '<tr><td>'+modeline+'</td><td '+(clickcode?'class="clickme" ':'')+'onclick="'+clickcode+'">'+escaped_name+'</td><td>'+(showsize?entry.size:'')+'</td></tr>'
+}
+
+function renderloc(path) {
+ var parts = path.split('/')
+ var curpath = '/';
+ var linkparts = parts.map(function(dir, i) {
+ var esc = (i==0) ? 'main directory' : escapehtml(dir)
+ if (i != 0) curpath += dir + '/'
+ var hexpath = hex(curpath)
+ var clickcode = "__listdir(dehex('"+hexpath+"'))"
+ return '<span class="clickme" onclick="'+clickcode+'">'+esc+'</span>'
+ })
+ return '<div id="loc">'+linkparts.join(' / ')+'</div>'
+}
+
+function __listdir(path) {
+ listdir(path, function(status, err, listing) {
+ if (status != 200) {
+ alert(err)
+ return
+ }
+ loc = path
+ var body = document.getElementsByTagName('body')[0]
+ body.innerHTML = renderloc(path)
+ + '<table><thead><tr><th>mode</th><th>name</th><th>size</th></tr></thead><tbody>'+listing.filter(function(e) {
+ return e.name != '.' && e.name != '..'
+ }).map(function(e) {
+ return format_entry(e)
+ }).join('')+'</tbody></table>'
+ /*listing.forEach(function(e) {
+ //e.mode = e.mode.toString(8)
+ console.log(Object.keys(e).map(function(k) {
+ return k+':'+e[k]
+ }).join(', '))
+ })*/
+ })
+}
+
+
+ </script>
+ <style>
+ .clickme {
+ text-decoration: underline;
+ color: blue;
+ cursor: pointer;
+ }
+ </style>
+ </head>
+ <body onload="__listdir('/')">
+ </body>
+</html>