cbsi/cbsi.c

659 lines
14 KiB
C
Raw Normal View History

2013-06-28 18:50:13 +02:00
/**
* @PROJECT CGI Bash Shell Interface
* @COPYRIGHT See COPYING in the top level directory
* @FILE cbsi.c
* @PURPOSE Common CBSI
* @DEVELOPERS Nathan Angelacos <nangel@users.sourceforge.net>
* Rafal Kupiec <belliash@asiotec.eu.org>
*/
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <getopt.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <grp.h>
#include "common.h"
#include "error.h"
#include "buffer.h"
#include "subshell.h"
#include "mimetype.h"
#include "cbsi.h"
struct option ga_long_options[] = {
{"language-dir", required_argument, 0, 'l'},
{"translation", required_argument, 0, 't'},
{"upload-limit", required_argument, 0, 'u'},
{"upload-dir", required_argument, 0, 'd'},
{"shell", required_argument, 0, 's'},
{0, 0, 0, 0}
};
const char *gs_short_options = "+u:d:l:t:s:";
int argc_argv(char* instr, argv_t** argv) {
char quote = '\0';
int arg_count = 0;
enum state_t { WHITESPACE, WORDSPACE, TOKENSTART } state = WHITESPACE;
argv_t* argv_array = NULL;
int argc_slots = 0;
size_t len, pos;
len = strlen(instr);
pos = 0;
while(pos < len) {
switch(*instr) {
case '"':
case '\'':
if(state == WHITESPACE) {
quote = *instr;
state = TOKENSTART;
if(*(instr + 1) == quote) {
quote = '\0';
*instr = '\0';
argv_array[arg_count].quoted = -1;
} else {
instr++;
pos++;
}
} else {
if(*instr == quote) {
argv_array[arg_count - 1].quoted = -1;
quote = '\0';
*instr = '\0';
state = WHITESPACE;
}
}
break;
case '\\':
if((quote) && (*(instr + 1) == quote)) {
memmove(instr, instr + 1, strlen(instr));
len--;
} else {
if(state == WHITESPACE) {
state = TOKENSTART;
}
}
break;
case ' ':
case '\t':
case '\r':
case '\n':
if((state == WORDSPACE) && (quote == '\0')) {
state = WHITESPACE;
*instr = '\0';
}
break;
case '\0':
break;
default:
if(state == WHITESPACE) {
state = TOKENSTART;
}
}
if(state == TOKENSTART) {
arg_count++;
if(arg_count > argc_slots) {
argc_slots += ALLOC_CHUNK;
argv_array = (argv_t*) xrealloc(argv_array, sizeof(argv_t) * (argc_slots + ALLOC_CHUNK));
}
if(argv_array == NULL) {
return -1;
}
argv_array[arg_count - 1].string = instr;
argv_array[arg_count].quoted = 0;
state = WORDSPACE;
}
instr++;
pos++;
}
argv_array[arg_count].string = NULL;
*argv = argv_array;
return arg_count;
}
void assignGlobalStartupValues() {
global.uploadkb = -1;
global.shell = SUBSHELL_CMD;
global.langdir = LANGDIR;
global.translation = NULL;
global.uploaddir = TEMPDIR;
global.uploadlist = NULL;
global.file_prefix = "FILE_";
global.filename_prefix = "FILENAME_";
global.get_prefix = "GET_";
global.post_prefix = "POST_";
global.cbsi_prefix = "CBSI_";
global.cookie_prefix = "COOKIE_";
global.null_prefix = "";
}
int BecomeUser(uid_t uid, gid_t gid) {
if(getuid() == 0) {
setgroups(1, &gid);
}
setgid(gid);
setgid(getgid());
setuid(uid);
setuid(getuid());
return 0;
}
void cbsiflags(list_t* env) {
char buf[200];
snprintf(buf, 200, "UPLOAD_DIR=%s", global.uploaddir);
myputenv(env, buf, global.cbsi_prefix);
snprintf(buf, 200, "UPLOAD_LIMIT=%lu", global.uploadkb);
myputenv(env, buf, global.cbsi_prefix);
snprintf(buf, 200, "SHELL=%s", global.shell);
myputenv(env, buf, global.cbsi_prefix);
}
void cleanup(void) {
int i;
if(global.uploadlist) {
unlink_uploadlist();
free_token_list(global.uploadlist);
global.uploadlist = NULL;
}
if(language != NULL && translations > 0) {
for (i = 0; i < HASH_BUF; ++i) {
if(ltable[i]) {
free(ltable[i]->msgid);
free(ltable[i]->msgstr);
free(ltable[i]);
}
}
}
}
void CookieVars(list_t* env) {
char* qs;
char* token;
if(getenv("HTTP_COOKIE") != NULL) {
qs = strdup(getenv("HTTP_COOKIE"));
} else {
return;
}
token = strtok(qs, ";");
while(token) {
while(token[0] == ' ') {
token++;
}
myputenv(env, token, global.cookie_prefix);
token = strtok(NULL, ";");
}
free(qs);
}
int count_lines(char* instr, size_t len, char* where) {
size_t line = 1;
while((where > instr) && (len)) {
if(*instr == '\n') {
line++;
}
len--;
instr++;
}
return line;
}
char* find_whitespace(char* instr) {
while(!isspace(*instr) && *instr) {
instr++;
}
return instr;
}
void free_list_chain(list_t* list) {
list_t *next;
while(list) {
next = list->next;
free(list->buf);
free(list);
list = next;
}
}
unsigned short generateHash(char* str) {
unsigned long hash = 5381;
int c;
while(c = *str++) {
hash = ((hash << 5) + hash) + c;
}
return hash % HASH_BUF;
}
void loadDictionary(const char* filename) {
char* b;
FILE* fp;
short hash;
char msgid[TRANS_BUF];
char msgstr[TRANS_BUF];
lstr* p;
lstr* s;
sprintf(buffer, "%s/%s/%s.dic", global.langdir, language, filename);
if((fp = fopen(buffer, "r")) != NULL) {
memset(ltable, 0, sizeof(lstr*) * HASH_BUF);
memset(msgid, '\0', TRANS_BUF);
memset(msgstr, '\0', TRANS_BUF);
while(!feof(fp) && (fgets(buffer, TRANS_BUF - 1, fp) != NULL)) {
b = skip_whitespace(buffer);
if((!*b) || (*b == '#')) {
continue;
}
if(strstr(b, "msgid") != NULL) {
b = trim(b + 5);
strncpy(msgid, b, strlen(b) + 1);
} else if(strstr(b, "msgstr") != NULL) {
b = trim(b + 6);
strncpy(msgstr, b, strlen(b) + 1);
} else {
continue;
}
if(msgid[0] != 0 && msgstr[0] != 0) {
hash = generateHash(msgid);
s = malloc(sizeof(lstr));
s->msgid = (char*) malloc(strlen(msgid) + 1);
s->msgstr = (char*) malloc(strlen(msgstr) + 1);
strcpy(s->msgid, msgid);
strcpy(s->msgstr, msgstr);
s->next = NULL;
if(ltable[hash] == NULL) {
ltable[hash] = s;
} else {
for(p = ltable[hash]; p->next != NULL; p = p->next);
p->next = s;
}
translations++;
msgid[0] = 0;
msgstr[0] = 0;
}
}
fclose(fp);
}
}
void lowercase(char* instr) {
while(*instr != '\0') {
*instr = tolower (*instr);
instr++;
}
}
int main(int argc, char* argv[]) {
token_t* tokenchain = NULL;
buffer_t script_text;
script_t* scriptchain;
int retval = 0;
char* filename = NULL;
argv_t* av = NULL;
char** av2 = argv;
int av2c = argc;
int command;
int count;
list_t* env = NULL;
if(atexit(cleanup) != 0) {
die_with_message(NULL, NULL, "atexit() failed");
}
assignGlobalStartupValues();
buffer_init(&script_text);
switch(argc) {
case 1:
puts("This is CBSI version " CBSI_VERSION "\nThis program runs as a CGI interface, not interactively.\n");
return 0;
break;
default:
command = argc_argv(argv[1], &av);
if(command > 1) {
av2c = argc - 1 + command;
av2 = xmalloc(sizeof(char*) * av2c);
av2[0] = argv[0];
for(count = 1; count <= command; count++) {
av2[count] = av[count - 1].string;
}
for(; count < av2c; count++) {
av2[count] = argv[count - command + 1];
}
}
parseCommandLine(av2c, av2);
free(av);
if(av2 != argv) {
free(av2);
}
if(optind < av2c) {
filename = av2[optind];
} else {
die_with_message(NULL, NULL, "No script file specified");
}
break;
}
scriptchain = load_script(filename, NULL);
BecomeUser(scriptchain->uid, scriptchain->gid);
env = wcversion(env);
readenv(env);
sessionid(env);
cbsiflags(env);
prepareDictionary();
tokenchain = build_token_list(scriptchain, NULL);
preprocess_token_list(tokenchain);
CookieVars(env);
ReadCGIPOSTValues(env);
ReadCGIQueryString(env);
process_token_list(&script_text, tokenchain);
subshell_setup(global.shell, env);
subshell_doscript(&script_text, scriptchain->name);
subshell_destroy();
buffer_destroy(&script_text);
free_token_list(tokenchain);
free_list_chain(env);
free_script_list(scriptchain);
return 0;
}
list_t* myputenv(list_t* cur, char* str, char* prefix) {
list_t* prev = NULL;
size_t keylen;
char* entry = NULL;
char* temp = NULL;
int array = 0;
int len;
temp = memchr(str, '=', strlen(str));
if(temp == 0) {
return cur;
}
keylen = (size_t) (temp - str);
if(memcmp(str + keylen - 2, "[]", 2) == 0) {
keylen = keylen - 2;
array = 1;
}
entry = xmalloc(strlen (str) + strlen(prefix) + 1);
entry[0] = '\0';
if(strlen(prefix)) {
strncat(entry, prefix, strlen(prefix));
}
if(array == 1) {
strncat(entry, str, keylen);
strcat(entry, str + keylen + 2);
} else {
strcat(entry, str);
}
len = keylen + strlen(prefix) + 1;
while(cur != NULL) {
if(memcmp(cur->buf, entry, len) == 0) {
if(array == 1) {
temp = xmalloc(strlen(cur->buf) + strlen(entry) - len + 2);
memmove(temp, cur->buf, strlen(cur->buf) + 1);
strcat(temp, "\n");
strcat(temp, str + keylen + 3);
free(entry);
entry = temp;
}
free(cur->buf);
if(prev != NULL) {
prev->next = cur->next;
}
free(cur);
cur = prev;
}
prev = cur;
if(cur) {
cur = (list_t*) cur->next;
}
}
cur = xmalloc(sizeof(list_t));
cur->buf = entry;
if(prev != NULL) {
prev->next = cur;
}
return cur;
}
int parseCommandLine(int argc, char *argv[]) {
int c;
int option_index = 0;
optopt = 0;
optind = 0;
while((c = getopt_long(argc, argv, gs_short_options, ga_long_options, &option_index)) != -1) {
switch(c) {
case 's':
global.shell = optarg;
break;
case 'u':
global.uploadkb = atoi (optarg);
break;
case 'l':
global.langdir = optarg;
break;
case 't':
global.translation = optarg;
break;
case 'd':
global.uploaddir = optarg;
break;
}
}
return optind;
}
void prepareDictionary() {
translations = 0;
2013-07-05 23:10:17 +02:00
if(getenv("WWW_LANGUAGE") != NULL) {
language = strdup(getenv("WWW_LANGUAGE"));
2013-06-28 18:50:13 +02:00
if(global.translation != NULL) {
loadDictionary(global.translation);
} else {
loadDictionary("common");
}
}
}
int ReadCGIPOSTValues(list_t* env) {
size_t content_length = 0;
size_t max_len;
size_t i, j, x;
sbuffer_t sbuf;
buffer_t token;
unsigned char* data;
const char* CONTENT_LENGTH = "CONTENT_LENGTH";
if((getenv(CONTENT_LENGTH) == NULL) || (strtoul(getenv(CONTENT_LENGTH), NULL, 10) == 0)) {
return 0;
}
if(getenv("CONTENT_TYPE")) {
if(strncasecmp(getenv("CONTENT_TYPE"), "multipart/form-data", 19) == 0) {
i = rfc2388_handler(env);
return i;
}
}
sbuffer_init(&sbuf, 32768);
sbuf.fh = STDIN;
if(getenv(CONTENT_LENGTH)) {
sbuf.maxread = strtoul(getenv(CONTENT_LENGTH), NULL, 10);
}
buffer_init(&token);
max_len = ((global.uploadkb == 0) ? 2048 : abs(global.uploadkb)) * 1024;
do {
x = sbuffer_read(&sbuf, "&");
content_length += sbuf.len;
if(content_length >= max_len && global.uploadkb != -1) {
die_with_message(NULL, NULL, "Attempted to send content larger than allowed limits.");
}
if((x == 0) || (token.data)) {
buffer_add(&token, (char*) sbuf.segment, sbuf.len);
}
if(x) {
data = sbuf.segment;
sbuf.segment[sbuf.len] = '\0';
if(token.data) {
buffer_add(&token, sbuf.segment + sbuf.len, 1);
data = token.data;
}
j = strlen((char*) data);
for(i = 0; i <= j; i++) {
if(data[i] == '+') {
data[i] = ' ';
}
}
unescape_url((char*) data);
myputenv(env, (char*) data, global.post_prefix);
if(token.data) {
buffer_reset(&token);
}
}
} while(!sbuf.eof);
sbuffer_destroy(&sbuf);
buffer_destroy(&token);
return 0;
}
int ReadCGIQueryString(list_t* env) {
char* qs;
char* token;
int i;
if(getenv("QUERY_STRING") != NULL) {
qs = strdup(getenv("QUERY_STRING"));
} else {
return 0;
}
for(i = 0; qs[i]; i++) {
if(qs[i] == '+') {
qs[i] = ' ';
}
}
token = strtok(qs, "&;");
while(token) {
unescape_url(token);
myputenv(env, token, global.get_prefix);
token = strtok(NULL, "&;");
}
free(qs);
return 0;
}
void readenv(list_t* env) {
extern char** environ;
int count = 0;
while(environ[count] != NULL) {
myputenv(env, environ[count], global.null_prefix);
count++;
}
}
void sessionid(list_t* env) {
char session[29];
sprintf(session, "SESSIONID=%x%x", getpid(), (int) time(NULL));
myputenv(env, session, global.cbsi_prefix);
}
char* skip_whitespace(char* instr) {
while(isspace(*instr) && *instr) {
instr++;
}
return instr;
}
char* trim(char* str) {
char* end;
while(isspace(*str) || *str == '"') {
str++;
}
if(*str == 0) {
return str;
}
end = str + strlen(str) - 1;
while(end > str && (isspace(*end) || *end == '"')) {
end--;
}
*(end + 1) = 0;
return str;
}
void unescape_url(char* url) {
int i, j;
for(i = 0, j = 0; url[j]; ++i, ++j) {
if((url[i] = url[j]) != '%') {
continue;
}
if(!url[j + 1] || !url[j + 2]) {
break;
}
url[i] = x2c(&url[j + 1]);
j += 2;
}
url[i] = '\0';
}
void unlink_uploadlist() {
token_t* me;
me = global.uploadlist;
while(me) {
unlink(me->buf);
free(me->buf);
me = me->next;
}
}
void uppercase(char* instr) {
while(*instr != '\0') {
*instr = toupper(*instr);
instr++;
}
}
list_t* wcversion(list_t* env) {
char version[200];
sprintf(version, "VERSION=%s", CBSI_VERSION);
return(myputenv(env, version, global.cbsi_prefix));
}
char x2c(char* what) {
char digit;
digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0'));
digit *= 16;
digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0'));
return digit;
}
void* xmalloc(size_t size) {
void* buf;
if((buf = malloc(size)) == NULL) {
die_with_message(NULL, NULL, g_err_msg[E_MALLOC_FAIL]);
}
memset(buf, 0, size);
return buf;
}
void* xrealloc(void* buf, size_t size) {
if((buf = realloc(buf, size)) == NULL) {
die_with_message(NULL, NULL, g_err_msg[E_MALLOC_FAIL]);
}
return buf;
}