659 rivejä
14 KiB
C
659 rivejä
14 KiB
C
/**
|
|
* @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;
|
|
if(getenv("WWW_LANGUAGE") != NULL) {
|
|
language = strdup(getenv("WWW_LANGUAGE"));
|
|
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;
|
|
}
|