/** * @PROJECT CGI Bash Shell Interface * @COPYRIGHT See COPYING in the top level directory * @FILE cbsi.c * @PURPOSE Common CBSI * @DEVELOPERS Nathan Angelacos * Rafal Kupiec */ #include #include #include #include #include #include #include #include #include #include #include #include #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; }