aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLouis Burda <quent.burda@gmail.com>2021-05-29 14:24:31 +0200
committerLouis Burda <quent.burda@gmail.com>2021-05-29 14:24:31 +0200
commit13b65f01132c41be9ab8d9f92c2c5ca605c366d8 (patch)
tree74bd5b4dee779e4600d416adf4abcd4f621addab /src
parent62d99253144a14648c4da1c2a60c01e7b06ef02c (diff)
downloadenowars5-service-stldoctor-13b65f01132c41be9ab8d9f92c2c5ca605c366d8.tar.gz
enowars5-service-stldoctor-13b65f01132c41be9ab8d9f92c2c5ca605c366d8.zip
changed repo structure and commited releease files such that default docker-compose worklow commands work in testvm
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore4
-rw-r--r--src/Makefile21
-rw-r--r--src/main.c376
-rw-r--r--src/msgs/cat_flag5
-rw-r--r--src/msgs/welcome2
-rw-r--r--src/patches/flagstore1.diff17
-rw-r--r--src/patches/flagstore2.diff11
-rw-r--r--src/stlfile.c415
-rw-r--r--src/stlfile.h54
-rw-r--r--src/util.c146
-rw-r--r--src/util.h38
11 files changed, 1089 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..5f14e4d
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,4 @@
+stldoctor
+*.o
+vgcore.*
+safe_*
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..d7732b3
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,21 @@
+CFLAGS = -g -I .
+
+# fortify source code
+CFLAGS += -fPIE -fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2
+LDFLAGS = -Wl,-z,now -Wl,-z,relro
+
+.PHONY: all clean
+
+all: build/stldoctor
+
+clean:
+ rm -rf build
+
+build:
+ mkdir build
+
+build/%.o: %.c %.h | build
+ $(CC) -c -o $@ $< $(CFLAGS) $(LDLIBS)
+
+build/stldoctor: build/$(PREFIX)stlfile.o build/$(PREFIX)util.o $(PREFIX)main.c | build
+ $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS)
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..de2bd48
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,376 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <errno.h>
+
+#include "stlfile.h"
+#include "util.h"
+
+#define MAXFILESIZE 7000
+
+struct command {
+ const char *name;
+ void (*func)(const char *);
+ const char *desc;
+};
+
+int save_submission(struct parseinfo *info, char *data, int len);
+
+void cat_cmd(const char *arg);
+void help_cmd(const char *arg);
+void exit_cmd(const char *arg);
+void echo_cmd(const char *arg);
+void upload_cmd(const char *arg);
+void search_cmd(const char *arg);
+void list_cmd(const char *arg);
+void auth_cmd(const char *arg);
+
+void cleanexit();
+
+struct command commands[] = {
+ { "cat", cat_cmd, "Cat cmd go prrrrr." },
+ { "help", help_cmd, "You already know what this does." },
+ { "exit", exit_cmd, "Closes the session." },
+ { "echo", echo_cmd, "Repeat after me!" },
+ { "upload", upload_cmd, "Upload an STL file to analyze." },
+ { "search", search_cmd, "Search for an STL file by model name." },
+ { "list", list_cmd, "List your uploaded files." },
+ { "auth", auth_cmd, "Login to upload files to a private dir." }
+};
+
+struct parseinfo cached;
+char *resultdir;
+int echo = 0, loggedin = 0;
+
+int
+save_submission(struct parseinfo *info, char *stldata, int stlsize)
+{
+ DIR *d;
+ FILE *f = NULL;
+ char *dirpath = NULL, *infopath = NULL, *modelpath = NULL;
+
+ if (loggedin)
+ dirpath = aprintf("%s/.%s-%i", resultdir, info->hash, time(NULL));
+ else
+ dirpath = aprintf("%s/%s-%i", resultdir, info->hash, time(NULL));
+ if (mkdir(dirpath, S_IRWXU | S_IRWXG | S_IRWXO)) goto fail;
+
+ modelpath = aprintf("%s/%s", dirpath, "model");
+ if (!(f = fopen(modelpath, "w+"))) goto fail;
+ if (fwrite(stldata, 1, stlsize, f) != stlsize) goto fail;
+ fclose(f);
+ f = NULL;
+
+ infopath = aprintf("%s/%s", dirpath, "info");
+ if (!(f = fopen(infopath, "w+"))) goto fail;
+ if (save_info(info, f) != OK) goto fail;
+ fclose(f);
+ f = NULL;
+
+ free(dirpath);
+ free(modelpath);
+ free(infopath);
+
+ return OK;
+
+fail:
+ if (f) fclose(f);
+
+ if (infopath) remove(infopath);
+ if (modelpath) remove(modelpath);
+ if (dirpath) remove(dirpath);
+
+ free(dirpath);
+ free(modelpath);
+ free(infopath);
+
+ return FAIL;
+}
+
+void
+cat_cmd(const char *arg)
+{
+ if (arg && !strncmp(arg, "flag", 4))
+ dump("msgs/cat_flag");
+ else
+ printf("meow\n");
+}
+
+void
+help_cmd(const char *arg)
+{
+ int i;
+
+ if (arg) {
+ for (i = 0; i < ARRSIZE(commands); i++) {
+ if (!strcmp(commands[i].name, arg)) {
+ printf("%s\n", commands[i].desc);
+ return;
+ }
+ }
+ }
+
+ printf("Available commands:\n");
+ for (i = 0; i < ARRSIZE(commands); i++)
+ printf("%s%s", i ? " " : "", commands[i].name);
+ printf("\n");
+}
+
+void
+exit_cmd(const char *arg)
+{
+ exit(0);
+}
+
+void
+echo_cmd(const char *arg)
+{
+ echo ^= 1;
+ printf("Echo is %s\n", echo ? "enabled" : "disabled");
+}
+
+void
+upload_cmd(const char *arg)
+{
+ const char *bufp;
+ char *end, *contents;
+ size_t len;
+
+ bufp = ask("How large is your file? ");
+ len = strtoul(bufp, &end, 10);
+ if (len <= 0 || len >= MAXFILESIZE || *end) {
+ fprintf(stderr, "Invalid file length!\n");
+ return;
+ }
+
+ printf("Ok! Im listening..\n");
+ contents = checkp(malloc(len + 1));
+ if (fread(contents, 1, len, stdin) != len) {
+ fprintf(stderr, "Hm, I'm missing some bytes.. try again!\n");
+ goto cleanup;
+ }
+ contents[len] = '\0';
+
+ if ((cached.valid = parse_file(&cached, contents, len))) {
+ if (save_submission(&cached, contents, len) != OK)
+ fprintf(stderr, "Failed to save your submission!\n");
+ else
+ printf("Your file was saved with ID %s!\n", cached.hash);
+ }
+
+cleanup:
+ free(contents);
+}
+
+void
+search_cmd(const char *arg)
+{
+ char *end, *scandir = NULL, *infopath = NULL, *modelpath = NULL;
+ int i, which, dirstart, ishidden;
+ const char *hash, *name;
+ struct dirent *de;
+ DIR *d = NULL;
+ FILE *f = NULL;
+ size_t size;
+
+ if (arg && !strcmp(arg, "last")) {
+ if (!cached.valid) {
+ fprintf(stderr, "No cached info report available\n");
+ return;
+ }
+ hash = cached.hash;
+ } else {
+ hash = mhash(arg ? arg : ask("Model name: "), -1);
+ }
+
+ if (!(d = opendir(resultdir))) return;
+
+ dirstart = telldir(d);
+ for (i = 0; (de = readdir(d));) {
+ name = de->d_name;
+ if (loggedin && *name == '.' && !strpfcmp(hash, name + 1)
+ || !loggedin && *name != '.' && !strpfcmp(hash, name)) {
+ printf("%i : %s\n", i, de->d_name);
+ i++;
+ }
+ }
+
+ if (i == 0) {
+ fprintf(stderr, "Sorry, couldnt find a matching scan result!\n");
+ goto cleanup;
+ } else {
+ which = strtoul(ask("Which of these results? "), &end, 10);
+ if (which >= i || which < 0 || *end) {
+ fprintf(stderr, "Invalid index!\n");
+ goto cleanup;
+ }
+ }
+
+ seekdir(d, dirstart);
+ for (i = 0; (de = readdir(d));) {
+ name = de->d_name;
+ if (loggedin && *name == '.' && !strpfcmp(hash, name + 1)
+ || !loggedin && *name != '.' && !strpfcmp(hash, name)) {
+ if (i == which) {
+ scandir = aprintf("%s/%s", resultdir, de->d_name);
+ break;
+ }
+ i++;
+ }
+ }
+
+ /* file got cleaned up during race condition by background task */
+ if (!scandir) {
+ fprintf(stderr, "Selected result spontaneously combusted!\n");
+ goto cleanup;
+ }
+
+ infopath = aprintf("%s/%s", scandir, "info");
+ if (!(f = fopen(infopath, "r"))) goto cleanup;
+ free_info(&cached);
+ if (load_info(&cached, f) != OK) goto cleanup;
+ fclose(f);
+ f = NULL;
+
+ print_info(&cached);
+
+ if (strchr(ask("Download the model? "), 'y')) {
+ modelpath = aprintf("%s/%s", scandir, "model");
+ if (!(f = fopen(modelpath, "r"))) goto cleanup;
+ fseek(f, 0, SEEK_END);
+ size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ if (size > MAXFILESIZE) goto cleanup;
+ printf("Here you go.. (%liB)\n", size);
+ while ((i = getc(f)) != EOF)
+ putc(i, stdout);
+ fclose(f);
+ f = NULL;
+ }
+
+cleanup:
+ if (f) fclose(f);
+ closedir(d);
+ free(scandir);
+ free(infopath);
+ free(modelpath);
+}
+
+void
+list_cmd(const char *arg)
+{
+ struct dirent *de;
+ struct parseinfo info;
+ char *path;
+ FILE *f;
+ DIR *d;
+
+ if (!loggedin) {
+ fprintf(stderr, "Not logged in!\n");
+ return;
+ }
+
+ if (!(d = opendir(resultdir))) return;
+
+ while ((de = readdir(d))) {
+ if (*de->d_name == '.' && !strchr(".", de->d_name[1])) {
+ printf(">> %s\n", de->d_name);
+ path = aprintf("%s/%s/info", resultdir, de->d_name);
+ if ((f = fopen(path, "r"))) {
+ if (load_info(&info, f) != OK)
+ fprintf(stderr, "Failed to read saved file info!\n");
+ else
+ print_info(&info);
+ fclose(f);
+ }
+ free(path);
+ }
+ }
+}
+
+void
+auth_cmd(const char *arg)
+{
+ const char *hash;
+ char *ndir;
+ int ret;
+
+ if (loggedin) {
+ fprintf(stderr, "Already logged in!\n");
+ return;
+ }
+
+ hash = mhash(arg ? arg : ask("Enter a password: "), -1);
+ ndir = aprintf("%s/.%s", resultdir, hash);
+ ret = mkdir(ndir, S_IRWXU | S_IRWXG | S_IRWXO);
+ if (!ret) {
+ printf("Success!\n");
+ } else if (ret && errno == EEXIST) {
+ printf("Success!\nWelcome back!\n");
+ } else {
+ fprintf(stderr, "Auth failed!\n");
+ return;
+ }
+
+ free(resultdir);
+ resultdir = ndir;
+ loggedin = 1;
+ cached.valid = 0;
+}
+
+void
+cleanexit()
+{
+ printf("see you later!\n");
+ free_info(&cached);
+ free(resultdir);
+}
+
+int
+main()
+{
+ const char *cmd;
+ char *cp, *arg;
+ int exit, i, cmdlen;
+
+ if (!(resultdir = checkp(strdup(getenv("RESULTDIR"))))) {
+ fprintf(stderr, "RESULTDIR not defined\n");
+ return 1;
+ }
+
+ setvbuf(stdin, NULL, _IONBF, 0);
+ setvbuf(stdout, NULL, _IONBF, 0);
+ setvbuf(stderr, NULL, _IONBF, 0);
+
+ atexit(cleanexit);
+
+ dump("msgs/welcome");
+
+ exit = 0;
+ while (!exit) {
+ errno = 0;
+ cmd = ask("$ ");
+ if (!*cmd && errno == EBADMSG) break;
+ if (!*cmd) continue;
+
+ cp = strchr(cmd, ' ');
+ arg = cp ? cp + 1 : NULL;
+ cmdlen = cp ? cp - cmd : strlen(cmd);
+
+ for (i = 0; i < ARRSIZE(commands); i++) {
+ if (!strncmp(commands[i].name, cmd, cmdlen)
+ && cmdlen == strlen(commands[i].name)) {
+ commands[i].func(arg);
+ break;
+ }
+ }
+
+ if (i == ARRSIZE(commands) && strlen(cmd) != 0)
+ fprintf(stderr, "No such command!\n");
+ }
+}
diff --git a/src/msgs/cat_flag b/src/msgs/cat_flag
new file mode 100644
index 0000000..d29ceea
--- /dev/null
+++ b/src/msgs/cat_flag
@@ -0,0 +1,5 @@
+
+ /\_/\ [ENO] _
+ = u u =_______| \\
+ _ w __( \__))
+ c_____>__(_____)__,
diff --git a/src/msgs/welcome b/src/msgs/welcome
new file mode 100644
index 0000000..2f77bfb
--- /dev/null
+++ b/src/msgs/welcome
@@ -0,0 +1,2 @@
+Welcome to STLDoctor!
+Submit a stl file and we'll analyze it!
diff --git a/src/patches/flagstore1.diff b/src/patches/flagstore1.diff
new file mode 100644
index 0000000..f0f8d4a
--- /dev/null
+++ b/src/patches/flagstore1.diff
@@ -0,0 +1,17 @@
+--- a/service/src/safe_util.c
++++ b/service/src/safe_util.c
+@@ -78,13 +78,12 @@ void
+ freadstr(FILE *f, char **dst)
+ {
+ size_t start, len, tmp;
+- char c;
+
+ /* VULN #1: BAD CAST */
+ /* see documentation/README.md for more details */
+
+ start = ftell(f);
+- for (len = 0; (c = fgetc(f)) != EOF && c; len++);
++ for (len = 0; fgetc(f) > 0; len++);
+ fseek(f, start, SEEK_SET);
+
+ *dst = checkp(calloc(1, len + 1));
diff --git a/src/patches/flagstore2.diff b/src/patches/flagstore2.diff
new file mode 100644
index 0000000..b34a0c0
--- /dev/null
+++ b/src/patches/flagstore2.diff
@@ -0,0 +1,11 @@
+--- a/service/src/safe_util.c
++++ b/service/src/safe_util.c
+@@ -58,7 +58,7 @@ mhash(const char *str, int len)
+ srand(v);
+
+ for (bp = buf, i = 0; i < MHASHLEN / 2; i++)
+- bp += sprintf(bp, "%02x", str[i % len] ^ (rand() % 256));
++ bp += sprintf(bp, "%02x", (unsigned char) str[i % len] ^ (rand() % 256));
+
+ return buf;
+ }
diff --git a/src/stlfile.c b/src/stlfile.c
new file mode 100644
index 0000000..88fc430
--- /dev/null
+++ b/src/stlfile.c
@@ -0,0 +1,415 @@
+#include "stlfile.h"
+
+static const char wsset[] = " \r\n\t";
+static const struct {
+ int code;
+ const char *str;
+} kwmap[] = {
+ { KW_SOLID_BEGIN, "solid" },
+ { KW_SOLID_END, "endsolid" },
+ { KW_LOOP_BEGIN, "outer loop" },
+ { KW_LOOP_END, "endloop" },
+ { KW_FACET_BEGIN, "facet normal" },
+ { KW_FACET_END, "endfacet" },
+ { KW_VERTEX, "vertex" },
+};
+
+void
+stack_init(struct stack *stack)
+{
+ stack->cap = 10;
+ stack->data = checkp(malloc(sizeof(int) * stack->cap));
+ stack->count = 0;
+}
+
+void
+stack_push(struct stack *stack, int v)
+{
+ if (stack->count == stack->cap) {
+ stack->cap *= 2;
+ stack->data = checkp(realloc(stack->data, sizeof(int) * stack->cap));
+ }
+
+ stack->data[stack->count] = v;
+ stack->count++;
+}
+
+int
+stack_pop(struct stack *stack)
+{
+ if (stack->count == 0)
+ return -1;
+
+ stack->count--;
+ return stack->data[stack->count];
+}
+
+void
+stack_free(struct stack *stack)
+{
+ free(stack->data);
+}
+
+int
+stack_ind(struct stack *stack, int search)
+{
+ int i;
+ for (i = 0; i < stack->count; i++)
+ if (stack->data[i] == search)
+ return i;
+ return -1;
+}
+
+int
+isws(char c)
+{
+ return c && strchr(wsset, c);
+}
+
+char*
+skipws(char *p)
+{
+ for (; isws(*p); p++);
+ return p;
+}
+
+char*
+consume_arg(char **start, char **end)
+{
+ char *c, *tmp;
+
+ *start = skipws(*start);
+ if (!*start) return NULL;
+ for (c = *start; *c && !isws(*c); c++);
+ tmp = *start;
+ *start = c + 1;
+ *end = c;
+ return tmp;
+}
+
+int
+consume_keyword(char **start)
+{
+ char *bp;
+ int i, len;
+
+ bp = skipws(*start);
+
+ for (i = 0; i < ARRSIZE(kwmap); i++) {
+ len = strlen(kwmap[i].str);
+ if (!strncmp(kwmap[i].str, bp, len) && (!bp[len] || isws(bp[len]))) {
+ // printf("GOT: %s\n", kwmap[i].str);
+ *start = bp + len + (bp[len] ? 1 : 0);
+ return kwmap[i].code;
+ }
+ }
+
+ return KW_UNKNOWN;
+}
+
+#define PARSE_FAIL(...) \
+ do { fprintf(stderr, "FORMAT ERR: " __VA_ARGS__); goto fail; } while (0)
+
+int
+parse_file_ascii(struct parseinfo *info, char *buf, size_t len)
+{
+ char *bp, *arg, *prev, *tmp, *end;
+ struct stack states;
+ float farg;
+ int i, kw;
+
+ stack_init(&states);
+
+ info->type = TYPE_ASCII;
+ info->loopcount = 0;
+
+ memset(info->header, 0, 80);
+
+ for (i = 0; i < 3; i++) {
+ info->bbmin[i] = INFINITY;
+ info->bbmax[i] = -INFINITY;
+ }
+
+ bp = prev = buf;
+ while ((kw = consume_keyword(&bp))) {
+ switch (kw) {
+ case KW_SOLID_BEGIN:
+ stack_push(&states, STATE_SOLID);
+ if (stack_ind(&states, KW_SOLID_BEGIN) != -1)
+ PARSE_FAIL("Multiple nested solids!\n");
+ tmp = bp;
+ if (!consume_keyword(&bp) && (arg = consume_arg(&bp, &end))) {
+ info->solidname = strndup(arg, end - arg);
+ } else {
+ bp = tmp;
+ }
+ break;
+ case KW_SOLID_END:
+ if ((kw = stack_pop(&states)) != STATE_SOLID)
+ PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str);
+ tmp = bp;
+ if (info->solidname && !consume_keyword(&bp)
+ && (arg = consume_arg(&bp, &end))) {
+ if (strncmp(info->solidname, arg, end - arg))
+ PARSE_FAIL("Solid end/begin names do not match!\n");
+ } else {
+ bp = tmp;
+ }
+ break;
+ case KW_LOOP_BEGIN:
+ stack_push(&states, STATE_LOOP);
+ if (stack_ind(&states, KW_LOOP_BEGIN) != -1)
+ PARSE_FAIL("Multiple nested loops!\n");
+ break;
+ case KW_LOOP_END:
+ if ((kw = stack_pop(&states)) != STATE_LOOP)
+ PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str);
+ info->loopcount++;
+ break;
+ case KW_FACET_BEGIN:
+ stack_push(&states, STATE_FACET);
+ if (stack_ind(&states, KW_LOOP_BEGIN) != -1)
+ PARSE_FAIL("Multiple nested facets!\n");
+ for (i = 0; i < 3; i++) {
+ if (!(arg = consume_arg(&bp, &end)))
+ PARSE_FAIL("Facet with less than 3 args!\n");
+ farg = strtof(arg, &tmp);
+ if (!isws(*tmp))
+ PARSE_FAIL("Facet with invalid arg '%s'!\n", arg);
+ }
+ break;
+ case KW_FACET_END:
+ if ((kw = stack_pop(&states)) != STATE_FACET)
+ PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str);
+ break;
+ case KW_VERTEX:
+ for (i = 0; i < 3; i++) {
+ if (!(arg = consume_arg(&bp, &end)))
+ PARSE_FAIL("Vertex with less than 3 args!\n");
+ farg = strtof(arg, &tmp);
+ if (!isws(*tmp))
+ PARSE_FAIL("Vertex with invalid arg '%s'!\n", arg);
+ info->bbmin[i] = MIN(info->bbmin[i], farg);
+ info->bbmax[i] = MAX(info->bbmax[i], farg);
+ }
+ break;
+ case KW_UNKNOWN:
+ prev = skipws(prev);
+ PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, prev);
+ }
+ prev = bp;
+ }
+
+ if (states.count)
+ PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, bp);
+
+ stack_free(&states);
+ return OK;
+
+fail:
+ stack_free(&states);
+ return FAIL;
+}
+
+int
+parse_file_bin(struct parseinfo *info, char *buf, size_t len)
+{
+ char *bp, *end = buf + len;
+ int i, k, m;
+ float v;
+
+ info->type = TYPE_BIN;
+
+ if (len < 84)
+ PARSE_FAIL("Truncated data! (header missing)\n");
+
+ memcpy(info->header, buf, 80);
+
+ if (strlen(buf + 1))
+ info->solidname = checkp(strdup(buf + 1));
+
+ bp = buf + 80;
+ info->loopcount = le32toh(*(uint32_t*)bp);
+
+ if (!info->loopcount) {
+ memset(info->bbmax, 0, sizeof(float) * 3);
+ memset(info->bbmin, 0, sizeof(float) * 3);
+ return OK;
+ }
+
+ for (i = 0; i < 3; i++) {
+ info->bbmin[i] = INFINITY;
+ info->bbmax[i] = -INFINITY;
+ }
+
+ for (i = 0; i < info->loopcount; i++) {
+ if (bp + 50 > end)
+ PARSE_FAIL("Truncated data! (loops missing)\n");
+ bp += 12;
+ for (k = 0; k < 3; k++, bp += 12) {
+ for (m = 0; m < 3; m++) {
+ v = fle32toh(*(float*)(bp + 4 * m));
+ info->bbmin[m] = MIN(info->bbmin[m], v);
+ info->bbmax[m] = MAX(info->bbmax[m], v);
+ }
+ }
+ bp += 2;
+ }
+
+ return OK;
+
+fail:
+ return FAIL;
+}
+
+int
+parse_file(struct parseinfo *info, char *buf, size_t len)
+{
+ int status;
+ const char *resp;
+ char *bp;
+
+ if (info->valid) free_info(info);
+
+ if (len < 7) {
+ fprintf(stderr, "File too small!\n");
+ return FAIL;
+ }
+
+ info->filesize = len;
+
+ /* check bin vs ascii with first keyword */
+ for (bp = buf; isws(*bp); bp++);
+ status = !strncmp("solid", bp, 5) && isws(bp[5])
+ ? parse_file_ascii(info, buf, len)
+ : parse_file_bin(info, buf, len);
+ if (status == FAIL) return FAIL;
+
+ if (!info->solidname) info->solidname = checkp(strdup(""));
+
+ if (!info->modelname) {
+ resp = ask("Please enter your model name: ");
+ if (strlen(resp) < 4) {
+ fprintf(stderr, "Model name is too short!\n");
+ return FAIL;
+ }
+ info->modelname = checkp(strdup(resp));
+ }
+
+ info->hash = checkp(strdup(mhash(info->modelname, -1)));
+
+ return OK;
+}
+
+int
+save_info(struct parseinfo *info, FILE *f)
+{
+ size_t nwrote = 0;
+ int i;
+
+ nwrote += fwrite(&info->type, sizeof(int), 1, f);
+ nwrote += fwrite(&info->loopcount, sizeof(int), 1, f);
+ nwrote += fwrite(&info->filesize, sizeof(unsigned), 1, f);
+
+ for (i = 0; i < 3; i++) {
+ nwrote += fwrite(&info->bbmin[i], sizeof(float), 1, f);
+ nwrote += fwrite(&info->bbmax[i], sizeof(float), 1, f);
+ }
+
+ nwrote += fwrite(info->header, 80, 1, f);
+
+ if (nwrote != 10) return FAIL;
+
+ fputstr(f, info->solidname);
+ fputstr(f, info->hash);
+ fputstr(f, info->modelname);
+
+ return OK;
+}
+
+int
+load_info(struct parseinfo *info, FILE *f)
+{
+ size_t nread = 0;
+ int i;
+
+ nread += fread(&info->type, sizeof(int), 1, f);
+ nread += fread(&info->loopcount, sizeof(int), 1, f);
+ nread += fread(&info->filesize, sizeof(unsigned), 1, f);
+
+ for (i = 0; i < 3; i++) {
+ nread += fread(&info->bbmin[i], sizeof(float), 1, f);
+ nread += fread(&info->bbmax[i], sizeof(float), 1, f);
+ }
+
+ nread += fread(info->header, 80, 1, f);
+
+ if (nread != 10) return FAIL;
+
+ freadstr(f, &info->solidname);
+ freadstr(f, &info->hash);
+ freadstr(f, &info->modelname);
+
+ info->valid = 1;
+
+ return OK;
+}
+
+void
+print_info(struct parseinfo *info)
+{
+ int i, k;
+
+#define FILTERCHAR(c) ((c) >= 32 ? (c) : ' ')
+
+ printf(" === Model info === \n");
+
+ printf(" File Size: %u\n", info->filesize);
+
+ if (info->type == TYPE_BIN) {
+ printf(" Header:\n");
+ for (i = 0; i < 80; i += k) {
+ printf(" ");
+ for (k = 0; k < MIN(80 - i, 20); k++)
+ printf(" %02x", (uint8_t) info->header[i+k]);
+ printf(" | ");
+ for (k = 0; k < MIN(80 - i, 20); k++)
+ printf("%c", FILTERCHAR(info->header[i+k]));
+ printf("\n");
+ }
+ }
+
+ printf(" Model ID: %s\n", info->hash);
+ printf(" Model Name: %s\n", info->modelname);
+ printf(" Solid Name: %s\n", info->solidname);
+ printf(" Triangle Count: %i\n", info->loopcount);
+ printf(" Bounding Box Size: %.2f x %.2f x %.2f\n",
+ info->bbmax[0] - info->bbmin[0],
+ info->bbmax[1] - info->bbmin[1],
+ info->bbmax[2] - info->bbmin[2]);
+ printf(" Bounding Box Origin: %.2f x %.2f x %.2f\n",
+ info->bbmin[0], info->bbmin[1], info->bbmin[2]);
+
+ printf(" ================== \n");
+}
+
+void
+free_info(struct parseinfo *info)
+{
+ NULLFREE(info->hash);
+ NULLFREE(info->modelname);
+ NULLFREE(info->solidname);
+ info->valid = 0;
+}
+
+float fle32toh(float v)
+{
+ union {
+ uint32_t u;
+ float f;
+ } conv;
+
+ conv.f = v;
+ conv.u = le32toh(conv.u);
+ return conv.f;
+}
diff --git a/src/stlfile.h b/src/stlfile.h
new file mode 100644
index 0000000..d321282
--- /dev/null
+++ b/src/stlfile.h
@@ -0,0 +1,54 @@
+#ifndef STLFILE_H
+#define STLFILE_H
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <endian.h>
+#include <math.h>
+
+#include "util.h"
+
+enum {
+ KW_INVALID = -1,
+ KW_UNKNOWN,
+ KW_SOLID_BEGIN,
+ KW_SOLID_END,
+ KW_FACET_BEGIN,
+ KW_FACET_END,
+ KW_LOOP_BEGIN,
+ KW_LOOP_END,
+ KW_VERTEX
+};
+
+enum {
+ STATE_SOLID,
+ STATE_FACET,
+ STATE_LOOP
+};
+
+enum {
+ TYPE_ASCII,
+ TYPE_BIN
+};
+
+struct stack {
+ int *data;
+ size_t count, cap;
+};
+
+struct parseinfo {
+ char header[80], *hash, *modelname, *solidname;
+ uint32_t loopcount;
+ unsigned filesize;
+ float bbmin[3], bbmax[3];
+ int type, valid;
+};
+
+int parse_file(struct parseinfo *info, char *buf, size_t len);
+int save_info(struct parseinfo *info, FILE *f);
+int load_info(struct parseinfo *info, FILE *f);
+void print_info(struct parseinfo *info);
+void free_info(struct parseinfo *info);
+
+#endif /* STLFILE_H */
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..ce22c4e
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,146 @@
+#include "util.h"
+
+void*
+checkp(void *p)
+{
+ if (!p) die("pointer assertion failed, OOM?\n");
+ return p;
+}
+
+void*
+die(const char *fmtstr, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmtstr);
+ vfprintf(stderr, fmtstr, ap);
+ va_end(ap);
+
+ exit(EXIT_FAILURE);
+}
+
+char*
+aprintf(const char *fmtstr, ...)
+{
+ va_list ap, cpy;
+ size_t nb;
+ char *str;
+
+ va_copy(cpy, ap);
+
+ va_start(cpy, fmtstr);
+ nb = vsnprintf(NULL, 0, fmtstr, cpy);
+ va_end(cpy);
+
+ if (nb <= 0) die("Invalid fmtstr!\n");
+ str = checkp(malloc(nb+1));
+
+ va_start(ap, fmtstr);
+ nb = vsnprintf(str, nb+1, fmtstr, ap);
+ va_end(ap);
+
+ return str;
+}
+
+const char*
+mhash(const char *str, int len)
+{
+ static char buf[MHASHLEN + 1];
+ int i, k, v;
+ char c, *bp;
+
+ /* VULN #2: BUFFER OVERFLOW */
+ /* see documentation/README.md for more details */
+
+ if (len == -1) len = strlen(str);
+
+ for (v = 0, i = 0; i < len; i++) v += str[i];
+
+ srand(v);
+ for (bp = buf, i = 0; i < MHASHLEN / 2; i++)
+ bp += sprintf(bp, "%02x", str[i % len] ^ (rand() % 256));
+
+ return buf;
+}
+
+int
+checkalph(const char *str, const char *alph)
+{
+ int i;
+
+ for (i = 0; i < strlen(str); i++)
+ if (str[i] && !strchr(alph, str[i])) return 0;
+
+ return 1;
+}
+
+void
+freadstr(FILE *f, char **dst)
+{
+ size_t start, len, tmp;
+ char c;
+
+ /* VULN #1: BAD CAST */
+ /* see documentation/README.md for more details */
+
+ start = ftell(f);
+ for (len = 0; (c = fgetc(f)) != EOF && c; len++);
+ fseek(f, start, SEEK_SET);
+
+ *dst = checkp(calloc(1, len + 1));
+ tmp = fread(*dst, len, 1, f);
+ fgetc(f);
+}
+
+void
+fputstr(FILE *f, char *s)
+{
+ fprintf(f, "%s", s);
+ fputc(0, f);
+}
+
+const char*
+ask(const char *fmtstr, ...)
+{
+ static char linebuf[256];
+ va_list ap;
+ int fail;
+
+ va_start(ap, fmtstr);
+ vprintf(fmtstr, ap);
+ va_end(ap);
+
+ fail = !fgets(linebuf, sizeof(linebuf), stdin);
+
+ if (!fail && *linebuf) {
+ if (linebuf[strlen(linebuf)-1] == '\n')
+ linebuf[strlen(linebuf)-1] = '\0';
+ if (echo) printf("%s\n", linebuf);
+ }
+
+ if (fail) errno = EBADMSG;
+
+ return fail ? "" : linebuf;
+}
+
+void
+dump(const char *filename)
+{
+ char buf[256];
+ FILE *f;
+ int nb;
+
+ if (!(f = fopen(filename, "r"))) return;
+
+ while ((nb = fread(buf, 1, sizeof(buf) - 1, f)))
+ printf("%.*s\n", nb, buf);
+
+ fclose(f);
+}
+
+int
+strpfcmp(const char *prefix, const char *str)
+{
+ return strncmp(prefix, str, strlen(prefix));
+}
+
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..c0e9064
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,38 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define ARRSIZE(x) (sizeof(x)/sizeof((x)[0]))
+#define MIN(x,y) ((x) > (y) ? (y) : (x))
+#define MAX(x,y) ((x) < (y) ? (y) : (x))
+
+#define NULLFREE(p) do { free(p); p = NULL; } while (0)
+
+#define MHASHLEN 40
+
+enum { FAIL = 0, OK = 1 };
+
+void* checkp(void *p);
+void* die(const char *fmtstr, ...);
+char* aprintf(const char *fmtstr, ...);
+
+const char* mhash(const char *filename, int len);
+int checkalph(const char *str, const char *alph);
+
+void freadstr(FILE *f, char **dst);
+void fputstr(FILE *f, char *s);
+
+const char* ask(const char *fmtstr, ...);
+void dump(const char *filepath);
+int strpfcmp(const char *prefix, const char *str);
+
+float fle32toh(float v);
+
+extern int echo;
+
+#endif /* UTIL_H */