diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..49525ad --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +CC = gcc +CFLAGS = -O2 -s -D_GNU_SOURCE +LDLIBS = +PREFIX = +BINDIR = $(PREFIX)/usr/bin + +all: cbsi + @echo "All done!" + +cbsi: buffer.o cbsi.o error.o mimetype.o subshell.o + $(CC) $(CFLAGS) $(LDLIBS) buffer.o cbsi.o error.o mimetype.o subshell.o -o cbsi + +buffer.o: buffer.c + $(CC) $(CFLAGS) -c buffer.c + +cbsi.o: cbsi.c + $(CC) $(CFLAGS) -c cbsi.c + +error.o: error.c + $(CC) $(CFLAGS) -c error.c + +mimetype.o: mimetype.c + $(CC) $(CFLAGS) -c mimetype.c + +subshell.o: subshell.c + $(CC) $(CFLAGS) -c subshell.c + +install: + mkdir -p $(BINDIR) + cp cbsi $(BINDIR) + @echo "All done!" + +uninstall: + @rm -f $(BINDIR)/cbsi + @echo "All done!" + +clean: + rm -f cbsi *.o + @echo "All done!" diff --git a/README b/README new file mode 100644 index 0000000..5cb8615 --- /dev/null +++ b/README @@ -0,0 +1,34 @@ +CGI Bash Shell Interface +======================== + +CBSI is a small program that makes CGI programming in the Bash Shell a lot +easier, or at least more tolerable. It is intended for Kagera Firmware, where +other languages are too big. + +It parses POST and GET requests in addiction to COOKIES, placing elements as +`name=value` pairs into the environment for the CGI script to use. + +It combines three features into an engine: + * It opens a shell, and translates all text into printable statements. All + text within <% ... %> constructs are passed verbatim to the shell. + * It opens a shell, and prints the shell expression. Everything within + <%: ... %> tags are supposed to be parsed into 'echo' statement. + * It supports multilanguage and allows to easily put all texts within + <%@ ... %> tags to be quick and easily translated into chosen language. + +Additional parameters that can be passed to CBSI: + * --language-dir=pathspec - specifies a directory where CBSI should look for + scripts translations. Default value is + '/usr/lib/kagera/langs' + * --shell=pathspec - specifies an alternate bash-like shell to use. + This defaults to '/bin/sh' + * --translation=filename - specifies a dictionary filename to be loaded in + addiction to common.dic from language directory. + The extension is added automatically. + * --upload-dir=pathspec - this specifies a temporary directory where all + uploaded files are created. Defaults to '/tmp' + * --upload-limit=limit - allows a mime-encoded file up to limit KB to be + uploaded. The default value is 0KB (no limit). + + +This software is based on Haserl . diff --git a/README.md b/README.md deleted file mode 100644 index 8fcfa46..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -cbsi -==== - -CGI Bash Shell Interface diff --git a/buffer.c b/buffer.c new file mode 100644 index 0000000..342894d --- /dev/null +++ b/buffer.c @@ -0,0 +1,126 @@ +/** + * @PROJECT CGI Bash Shell Interface + * @COPYRIGHT See COPYING in the top level directory + * @FILE buffer.c + * @PURPOSE CBSI Buffers + * @DEVELOPERS Nathan Angelacos + * Rafal Kupiec + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "buffer.h" + +void buffer_add(buffer_t* buf, const void* data, unsigned long size) { + unsigned long newsize; + unsigned long index; + + if((buf->ptr + size) >= buf->limit) { + index = (buf->limit - buf->data); + newsize = index; + while(newsize <= index + size) { + newsize += 1024; + } + index = buf->ptr - buf->data; + buf->data = realloc(buf->data, newsize); + buf->limit = buf->data + newsize; + buf->ptr = buf->data + index; + } + memcpy(buf->ptr, data, size); + buf->ptr += size; +} + +void buffer_destroy(buffer_t* buf) { + if(buf->data) { + free(buf->data); + } + buffer_init(buf); +} + +void buffer_init(buffer_t* buf) { + buf->data = NULL; + buf->ptr = NULL; + buf->limit = NULL; +} + +void buffer_reset(buffer_t* buf) { + if(buf->data) { + buf->ptr = buf->data; + } else { + buf->ptr = NULL; + } +} + +void sbuffer_destroy(sbuffer_t* sbuf) { + free(sbuf->buf); +} + +int sbuffer_init(sbuffer_t* sbuf, int size) { + sbuf->maxsize = size; + sbuf->buf = malloc (sbuf->maxsize); + sbuf->maxsize -= 1; + sbuf->fh = 0; + sbuf->eof = 0; + sbuf->len = 0; + sbuf->ptr = sbuf->buf; + sbuf->bufsize = 0; + sbuf->maxread = 0; + sbuf->nrread = 0; + return (sbuf->buf != NULL); +} + +int sbuffer_read(sbuffer_t* sbuf, char* matchstr) { + int len, pos; + int r; + + if((sbuf->eof) && (sbuf->ptr > sbuf->buf)) { + return 0; + } + if((sbuf->bufsize == 0) || (sbuf->ptr >= (sbuf->buf + sbuf->bufsize - strlen (matchstr)))) { + len = sbuf->bufsize - (sbuf->ptr - sbuf->buf); + if(len) { + memmove(sbuf->buf, sbuf->ptr, len); + } + sbuf->ptr = sbuf->buf; + sbuf->bufsize = len; + if(fcntl(sbuf->fh, F_GETFL) == -1) { + r = 0; + } else { + size_t n = sbuf->maxsize - len; + if(sbuf->maxread && sbuf->maxread < sbuf->nrread + n) { + n = sbuf->maxread - sbuf->nrread; + } + r = read(sbuf->fh, sbuf->buf + len, n); + } + if(r == 0 || (r < 0 && errno != EINTR)) { + sbuf->eof = -1; + } else { + sbuf->bufsize += (r > 0) ? r : 0; + sbuf->nrread += (r > 0) ? r : 0; + } + } + pos = 0; + len = sbuf->bufsize - (int) (sbuf->ptr - sbuf->buf) - strlen(matchstr); + while(memcmp(matchstr, sbuf->ptr + pos, strlen(matchstr)) && (pos < len)) { + pos++; + } + if(pos < len) { + sbuf->len = pos; + sbuf->segment = sbuf->ptr; + sbuf->ptr = sbuf->segment + pos + strlen(matchstr); + return -1; + } + if(sbuf->eof) { + len += strlen(matchstr); + } + sbuf->segment = sbuf->ptr; + sbuf->len = len; + sbuf->ptr += sbuf->len; + return (sbuf->eof) ? (-1) : (0); +} diff --git a/buffer.h b/buffer.h new file mode 100644 index 0000000..f625511 --- /dev/null +++ b/buffer.h @@ -0,0 +1,40 @@ +/** + * @PROJECT CGI Bash Shell Interface + * @COPYRIGHT See COPYING in the top level directory + * @FILE buffer.h + * @PURPOSE CBSI Buffers + * @DEVELOPERS Nathan Angelacos + * Rafal Kupiec + */ + +#ifndef __BUFFER_H +#define __BUFFER_H + +typedef struct { + unsigned char* data; + unsigned char* ptr; + unsigned char* limit; +} buffer_t; + +typedef struct { + int fh; + unsigned char* buf; + unsigned char* ptr; + unsigned char* segment; + size_t len; + size_t maxsize; + size_t bufsize; + size_t maxread; + size_t nrread; + int eof; +} sbuffer_t; + +void buffer_add(buffer_t* buf, const void* data, unsigned long size); +void buffer_destroy(buffer_t* buf); +void buffer_init(buffer_t* buf); +void buffer_reset(buffer_t* buf); +void sbuffer_destroy(sbuffer_t* sbuf); +int sbuffer_init(sbuffer_t* sbuf, int size); +int sbuffer_read(sbuffer_t* sbuf, char* matchstr); + +#endif diff --git a/cbsi.c b/cbsi.c new file mode 100644 index 0000000..4cecb1c --- /dev/null +++ b/cbsi.c @@ -0,0 +1,658 @@ +/** + * @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("WEBUI_LANGUAGE") != NULL) { + language = strdup(getenv("WEBUI_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; +} diff --git a/cbsi.h b/cbsi.h new file mode 100644 index 0000000..fc75ea5 --- /dev/null +++ b/cbsi.h @@ -0,0 +1,74 @@ +/** + * @PROJECT CGI Bash Shell Interface + * @COPYRIGHT See COPYING in the top level directory + * @FILE cbsi.h + * @PURPOSE Common CBSI + * @DEVELOPERS Nathan Angelacos + * Rafal Kupiec + */ + +#ifndef __CBSI_H +#define __CBSI_H + +typedef struct { + unsigned long uploadkb; + char* shell; + char* langdir; + char* translation; + char* uploaddir; + char* uploadhandler; + char* file_prefix; + char* filename_prefix; + char* get_prefix; + char* post_prefix; + char* cookie_prefix; + char* null_prefix; + char* cbsi_prefix; + token_t* uploadlist; + int debug; + int acceptall; + int silent; +} cbsi_t; + +typedef struct lstr { + char* msgid; + char* msgstr; + struct lstr* next; +} lstr; + +cbsi_t global; +char buffer[TRANS_BUF]; +char* language; +lstr* ltable[HASH_BUF]; +int translations; + +int argc_argv(char* instr, argv_t** argv); +void assignGlobalStartupValues(void); +int BecomeUser(uid_t uid, gid_t gid); +void cbsiflags(list_t* env); +void cleanup(void); +void CookieVars(list_t* env); +int count_lines(char* instr, size_t len, char* where); +char* find_whitespace (char* instr); +void free_list_chain(list_t* env); +unsigned short generateHash(char* str); +void loadDictionary(const char* filename); +void lowercase(char* instr); +list_t* myputenv(list_t* cur, char* str, char* prefix); +int parseCommandLine(int argc, char* argv[]); +void prepareDictionary(); +int ReadCGIPOSTValues(list_t* env); +int ReadCGIQueryString(list_t* env); +void readenv(list_t* env); +void sessionid(list_t* env); +char* skip_whitespace (char* instr); +char* trim(char* str); +void unescape_url(char* url); +void unlink_uploadlist(void); +void uppercase(char* instr); +list_t* wcversion(list_t* env); +char x2c(char* what); +void* xmalloc(size_t size); +void* xrealloc(void* buf, size_t size); + +#endif diff --git a/common.h b/common.h new file mode 100644 index 0000000..2e031db --- /dev/null +++ b/common.h @@ -0,0 +1,38 @@ +/** + * @PROJECT CGI Bash Shell Interface + * @COPYRIGHT See COPYING in the top level directory + * @FILE common.h + * @PURPOSE Common CBSI + * @DEVELOPERS Nathan Angelacos + * Rafal Kupiec + */ + +#ifndef __COMMON_H +#define __COMMON_H + +#define ALLOC_CHUNK 10 + +#define CBSI_VERSION "0.91" + +#define STDIN 0 +#define STDOUT 1 +#define STDERR 2 + +#define HASH_BUF 1536 +#define TRANS_BUF 2048 + +#define LANGDIR "/usr/lib/kagera/langs" +#define SUBSHELL_CMD "/bin/sh" +#define TEMPDIR "/tmp" + +typedef struct { + char* string; + unsigned char quoted; +} argv_t; + +typedef struct { + char* buf; + void* next; +} list_t; + +#endif diff --git a/error.c b/error.c new file mode 100644 index 0000000..9517e64 --- /dev/null +++ b/error.c @@ -0,0 +1,57 @@ +/** + * @PROJECT CGI Bash Shell Interface + * @COPYRIGHT See COPYING in the top level directory + * @FILE error.c + * @PURPOSE Error handling + * @DEVELOPERS Nathan Angelacos + * Rafal Kupiec + */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "buffer.h" +#include "subshell.h" +#include "cbsi.h" +#include "error.h" + +char* g_err_msg[] = { + "", + "Memory Allocation Failure", + "Unable to open file %s", + "%c> before <%c", + "Missing %c>", + "Unknown operation", + "Unable to start subshell", + "Unspecified Error", +}; + +void die_with_error(char* msg) { + fprintf(stderr, "Error: %s\n", msg); + exit(-1); +} + +void die_with_message(void* sp, char* where, const char* s, ...) { + script_t* script = sp; + va_list p; + FILE* fo = stderr; + + fo = stdout; + fprintf(fo, "HTTP/1.0 500 Server Error\nContent-Type: text/html\n\nCBSI Error
\n");
+	va_start(p, s);
+	vfprintf(fo, s, p);
+	va_end(p);
+	if(where && sp) {
+		fprintf(fo, " near line %d of %s\n",
+		count_lines(script->buf, script->size, where),
+		script->name);
+	}
+	printf("\n");
+	fprintf(fo, "
\n"); + exit(-1); +} diff --git a/error.h b/error.h new file mode 100644 index 0000000..197a8e8 --- /dev/null +++ b/error.h @@ -0,0 +1,19 @@ +/** + * @PROJECT CGI Bash Shell Interface + * @COPYRIGHT See COPYING in the top level directory + * @FILE error.h + * @PURPOSE Error handling + * @DEVELOPERS Nathan Angelacos + * Rafal Kupiec + */ + +#ifndef __ERROR_H +#define __ERROR_H + +enum error_types { E_NO_ERROR, E_MALLOC_FAIL, E_FILE_OPEN_FAIL, E_END_BEFORE_BEGIN, E_NO_END_MARKER , E_NO_OP, E_SUBSHELL_FAIL, E_WHATEVER }; +extern char *g_err_msg[]; + +void die_with_error(char* msg); +void die_with_message(void* sp, char* where, const char* s, ...); + +#endif diff --git a/mimetype.c b/mimetype.c new file mode 100644 index 0000000..c7a4295 --- /dev/null +++ b/mimetype.c @@ -0,0 +1,346 @@ +/** + * @PROJECT CGI Bash Shell Interface + * @COPYRIGHT See COPYING in the top level directory + * @FILE mimetype.c + * @PURPOSE Mimetype and file upload handler + * @DEVELOPERS Nathan Angelacos + * Rafal Kupiec + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "buffer.h" +#include "error.h" +#include "subshell.h" +#include "buffer.h" +#include "mimetype.h" +#include "cbsi.h" + +void empty_stdin(void) { + char c[2000]; + while(read(STDIN_FILENO, &c, 2000)) { + } +} + +void mime_var_init(mime_var_t* obj) { + obj->name = NULL; + obj->filename = NULL; + obj->type = NULL; + obj->tempname = NULL; + buffer_init(&(obj->value)); + obj->fh = 0; +} + +void mime_var_destroy(mime_var_t* obj) { + int status; + struct sigaction new_action; + + if(obj->name) { + free(obj->name); + obj->name = NULL; + } + if(obj->filename) { + free(obj->filename); + obj->filename = NULL; + } + if(obj->type) { + free(obj->type); + obj->type = NULL; + } + if(obj->tempname) { + free(obj->tempname); + obj->tempname = NULL; + } + buffer_destroy(&(obj->value)); + if(obj->fh) { + close(abs(obj->fh)); + obj->fh = 0; + } +} + +char* mime_substr(char* start, int len) { + char* ptr; + + if(!start) { + return NULL; + } + if(len < 0) { + return NULL; + } + ptr = xmalloc(len + 2); + memcpy(ptr, start, len); + return ptr; +} + +void mime_tag_add(mime_var_t* obj, char* str) { + char* a = NULL; + char* b = NULL; + static char* tag[] = { "name=\"", "filename=\"", "Content-Type: " }; + + a = strcasestr(str, tag[0]); + if(a) { + a += strlen(tag[0]); + b = strchr(a, '"'); + if(!obj->name) { + obj->name = mime_substr(a, b - a); + } + } + a = strcasestr(str, tag[1]); + if(a) { + a += strlen(tag[1]); + b = strchr(a, '"'); + if(!obj->filename) { + obj->filename = mime_substr(a, b - a); + } + } + a = strcasestr(str, tag[2]); + if(a) { + a += strlen(tag[2]); + b = a + strlen(a); + if(!obj->type) { + obj->type = mime_substr(a, b - a); + } + } +} + +void mime_var_putenv(list_t* env, mime_var_t* obj) { + buffer_t buf; + + buffer_init(&buf); + if(obj->filename) { + buffer_add(&buf, obj->name, strlen(obj->name)); + buffer_add(&buf, "=", 1); + buffer_add(&buf, (char*) obj->value.data, + strlen((char*) obj->value.data) + 1); + myputenv(env, (char*) buf.data, global.file_prefix); + buffer_reset(&buf); + buffer_add(&buf, obj->name, strlen(obj->name)); + buffer_add(&buf, "=", 1); + buffer_add(&buf, obj->filename, strlen(obj->filename) + 1); + myputenv(env, (char*) buf.data, global.filename_prefix); + buffer_reset (&buf); + } else if(obj->name) { + buffer_add(&(obj->value), "", 1); + buffer_add(&buf, obj->name, strlen(obj->name)); + buffer_add(&buf, "=", 1); + buffer_add(&buf, (char*) obj->value.data, + strlen((char*) obj->value.data) + 1); + myputenv(env, (char*) buf.data, global.file_prefix); + buffer_reset(&buf); + } + buffer_destroy(&buf); +} + +void mime_exec(mime_var_t* obj, char* fifo) { + int pid; + char* type; + char* filename; + char* name; + char *c; + int fh; + struct sigaction new_action; + + pid = fork(); + if(pid == -1) { + empty_stdin(); + die_with_message(NULL, NULL, g_err_msg[E_SUBSHELL_FAIL]); + } + if(pid == 0) { + if(obj->type) { + type = xmalloc(13 + strlen(obj->type) + 1); + sprintf(type, "CONTENT_TYPE=%s", obj->type); + putenv(type); + } + if(obj->filename) { + filename = xmalloc (9 + strlen(obj->filename) + 1); + sprintf(filename, "FILENAME=%s", obj->filename); + putenv(filename); + } + if(obj->name) { + name = xmalloc(5 + strlen(obj->name) + 1); + sprintf(name, "NAME=%s", obj->name); + putenv(name); + } + fh = open(fifo, O_RDONLY); + while(read(fh, &c, 1)) { + } + exit(-1); + } else { + new_action.sa_handler = SIG_IGN; + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = 0; + sigaction(SIGPIPE, &new_action, NULL); + } +} + +void mime_var_open_target(mime_var_t* obj) { + char* tmpname; + token_t* curtoken; + curtoken = global.uploadlist; + int ok; + + if(global.uploadkb == 0) { + empty_stdin(); + die_with_message(NULL, NULL, "File uploads are not allowed."); + } + ok = -1; + tmpname = xmalloc(strlen(global.uploaddir) + 8); + strcpy(tmpname, global.uploaddir); + strcat(tmpname, "/XXXXXX"); + obj->fh = mkstemp(tmpname); + if(obj->fh == -1) { + ok = 0; + } + buffer_add(&(obj->value), tmpname, strlen(tmpname)); + if(!ok) { + empty_stdin(); + die_with_message(NULL, NULL, g_err_msg[E_FILE_OPEN_FAIL], tmpname); + } + curtoken = push_token_on_list(curtoken, NULL, tmpname, strlen(tmpname) + 1); + if(global.uploadlist == NULL) { + global.uploadlist = curtoken; + } +} + +void mime_var_writer(mime_var_t* obj, char* str, int len) { + int err; + + if(!obj->filename) { + buffer_add(&(obj->value), str, len); + } + if((!obj->fh) && (obj->filename)) { + mime_var_open_target(obj); + } + if(obj->fh > 0) { + err = write(obj->fh, str, len); + if(err == -1) { + obj->fh = abs(obj->fh) * -1; + } + } +} + +int rfc2388_handler(list_t* env) { + enum mime_state_t { DISCARD, BOUNDARY, HEADER, CONTENT }; + int state; + int i, x; + unsigned long max_len, content_length; + sbuffer_t sbuf; + char* crlf = "\r\n"; + char* boundary; + char* str; + buffer_t buf; + mime_var_t var; + + str = getenv("CONTENT_TYPE"); + i = strlen(str) - 9; + while((i >= 0) && (memcmp("boundary=", str + i, 9))) { + i--; + } + if(i == -1) { + empty_stdin(); + die_with_message(NULL, NULL, "No Mime Boundary Information Found"); + } + i = i + 9; + if(str[i] == '"') { + i++; + } + boundary = xmalloc(strlen(str + i) + 5); + memcpy(boundary, crlf, 2); + memcpy(boundary + 2, "--", 2); + memcpy(boundary + 4, str + i, strlen(str + i) + 1); + if((i > 0) && (str[i - 1] == '"')) { + while((boundary[i]) && (boundary[i] != '"')) { + i++; + } + boundary[i] = '\0'; + } + max_len = ((global.uploadkb == 0) ? 2048 : abs(global.uploadkb)) * 1024; + content_length = 0; + sbuffer_init(&sbuf, 1024 * 128); + sbuf.fh = STDIN; + if(getenv("CONTENT_LENGTH")) { + sbuf.maxread = strtoul(getenv("CONTENT_LENGTH"), NULL, 10); + } + buffer_init(&buf); + buffer_add(&buf, "", 1); + buffer_reset(&buf); + state = DISCARD; + str = boundary + 2; + do { + x = sbuffer_read(&sbuf, str); + content_length += sbuf.len; + if(content_length >= max_len && global.uploadkb != -1) { + empty_stdin(); + free(boundary); + sbuffer_destroy(&sbuf); + buffer_destroy(&buf); + if(var.name) { + mime_var_destroy(&var); + } + die_with_message(NULL, NULL, "Attempted to send content larger than allowed limits."); + } + switch(state) { + case DISCARD: + if(x) { + state = BOUNDARY; + str = crlf; + buffer_reset(&buf); + } + break; + case BOUNDARY: + if(x) { + buffer_add(&buf, sbuf.segment, sbuf.len); + if(!memcmp(buf.data, boundary + 2, 2)) { + str = boundary + 2; + state = DISCARD; + } else { + buffer_reset(&buf); + mime_var_init(&var); + state = HEADER; + str = crlf; + } + } else { + buffer_add(&buf, sbuf.segment, sbuf.len); + } + break; + case HEADER: + buffer_add(&buf, sbuf.segment, sbuf.len); + if(x) { + if(sbuf.len == 0) { + buffer_reset(&buf); + state = CONTENT; + str = boundary; + } else { + buffer_add(&buf, "", 1); + mime_tag_add(&var, (char*) buf.data); + buffer_reset(&buf); + } + } + break; + case CONTENT: + mime_var_writer(&var, (char*) sbuf.segment, sbuf.len); + if(x) { + buffer_reset(&buf); + mime_var_putenv(env, &var); + mime_var_destroy(&var); + state = BOUNDARY; + str = crlf; + } + break; + } + } while(!sbuf.eof); + free(boundary); + sbuffer_destroy(&sbuf); + buffer_destroy(&buf); + return 0; +} diff --git a/mimetype.h b/mimetype.h new file mode 100644 index 0000000..2951d54 --- /dev/null +++ b/mimetype.h @@ -0,0 +1,33 @@ +/** + * @PROJECT CGI Bash Shell Interface + * @COPYRIGHT See COPYING in the top level directory + * @FILE mimetype.h + * @PURPOSE Mimetype and file upload handler + * @DEVELOPERS Nathan Angelacos + * Rafal Kupiec + */ + +#ifndef __MIMETYPE_H +#define __MIMETYPE_H + +typedef struct { + char* name; + char* filename; + char* type; + char* tempname; + buffer_t value; + int fh; +} mime_var_t; + +void empty_stdin(void); +void mime_exec(mime_var_t* obj, char* fifo); +char* mime_substr(char* start, int len); +void mime_tag_add(mime_var_t* obj, char* str); +void mime_var_destroy(mime_var_t* obj); +void mime_var_init(mime_var_t* obj); +void mime_var_open_target(mime_var_t* obj); +void mime_var_putenv(list_t* env, mime_var_t* obj); +void mime_var_writer(mime_var_t* obj, char* str, int len); +int rfc2388_handler(list_t* env); + +#endif diff --git a/subshell.c b/subshell.c new file mode 100644 index 0000000..02f79f4 --- /dev/null +++ b/subshell.c @@ -0,0 +1,373 @@ +/** + * @PROJECT CGI Bash Shell Interface + * @COPYRIGHT See COPYING in the top level directory + * @FILE subshell.c + * @PURPOSE Subshell execution + * @DEVELOPERS Nathan Angelacos + * Rafal Kupiec + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "buffer.h" +#include "error.h" +#include "subshell.h" +#include "cbsi.h" + +char open_tag[3] = "<%"; +char close_tag[3] = "%>"; + +const char* g_tag[] = { + "", + "", + ":", + "@", + "" +}; + +token_t* build_token_list(script_t* scriptbuf, token_t* tokenlist) { + char* start; + char* end; + char* curpos; + char* endpos; + token_t* curtoken; + token_t* firsttoken; + + curtoken = tokenlist; + firsttoken = tokenlist; + curpos = scriptbuf->buf + scriptbuf->curpos; + endpos = scriptbuf->buf + scriptbuf->size; + while(curpos < endpos) { + start = strstr(curpos, open_tag); + end = strstr(curpos, close_tag); + if(start && !end) { + die_with_message(scriptbuf, start, g_err_msg[E_NO_END_MARKER], open_tag[1]); + } + if((start > end) || (!start && end)) { + die_with_message(scriptbuf, end, g_err_msg[E_END_BEFORE_BEGIN], open_tag[1], open_tag[1]); + } + if(start && (strstr(start + 1, open_tag) && (strstr (start + 1, open_tag) < end))) + die_with_message(scriptbuf, start, g_err_msg[E_NO_END_MARKER], open_tag[1]); + if(end) { + curtoken = push_token_on_list(curtoken, scriptbuf, curpos, start - curpos); + if(firsttoken == NULL) { + firsttoken = curtoken; + } + curtoken = push_token_on_list(curtoken, scriptbuf, start, end - start); + if(firsttoken == NULL) { + firsttoken = curtoken; + } + curpos = end + 2; + } else { + curtoken = push_token_on_list(curtoken, scriptbuf, curpos, endpos - curpos); + if(firsttoken == NULL) { + firsttoken = curtoken; + } + curpos = endpos; + } + } + return firsttoken; +} + +void free_script_list(script_t* script) { + script_t* next; + + while(script) { + next = script->next; + if(script->name) { + free(script->name); + } + if(script->buf) { + free(script->buf); + } + free(script); + script = next; + } +} + +void free_token_list(token_t* tokenlist) { + token_t* next; + + while(tokenlist) { + next = tokenlist->next; + free(tokenlist); + tokenlist = next; + } +} + +script_t* load_script(char* filename, script_t* scriptlist) { + script_t* scriptbuf; + int scriptfp; + struct stat filestat; + + scriptfp = open(filename, O_NONBLOCK + O_RDONLY); + if(scriptfp == -1) { + die_with_message(NULL, NULL, g_err_msg[E_FILE_OPEN_FAIL], filename); + } + fstat(scriptfp, &filestat); + scriptbuf = (script_t *) xmalloc(sizeof (script_t)); + scriptbuf->name = (char *) xmalloc(strlen (filename) + 1); + scriptbuf->buf = (char *) xmalloc(filestat.st_size + 1); + memset(scriptbuf->name, 0, strlen(filename) + 1); + memcpy(scriptbuf->name, filename, strlen(filename)); + memset(scriptbuf->buf, 0, filestat.st_size + 1); + read(scriptfp, scriptbuf->buf, filestat.st_size); + scriptbuf->size = filestat.st_size; + scriptbuf->uid = filestat.st_uid; + scriptbuf->gid = filestat.st_gid; + scriptbuf->curpos = 0; + scriptbuf->next = NULL; + if(scriptlist != NULL) { + while(scriptlist->next) { + scriptlist = scriptlist->next; + } + scriptlist->next = scriptbuf; + } + if(memcmp(scriptbuf->buf, "#!", 2) == 0) { + while((scriptbuf->curpos < scriptbuf->size) && ((char) scriptbuf->buf[scriptbuf->curpos] != '\n')) { + (scriptbuf->curpos)++; + } + (scriptbuf->curpos)++; + } + close(scriptfp); + return scriptbuf; +} + +void preprocess_token_list(token_t* tokenlist) { + script_t* newscript; + token_t* me; + char* cp; + + me = tokenlist; + while(me) { + if(memcmp(me->buf, open_tag, 2)) { + me->tag = HTML; + } else { + me->tag = NOOP; + me->buf[me->len] = '\0'; + cp = me->buf + 2; + if(memcmp(cp, g_tag[ECHO], 1) == 0) { + me->tag = ECHO; + me->buf = find_whitespace(me->buf); + me->len = strlen(me->buf); + } else if(memcmp(cp, g_tag[TRANSLATE], 1) == 0) { + me->tag = TRANSLATE; + me->buf = find_whitespace(me->buf); + me->len = strlen(me->buf); + } + if(isspace(*cp)) { + me->tag = RUN; + me->buf = cp; + } + if(me->tag == NOOP) { + die_with_message(me->script, cp, g_err_msg[E_NO_OP]); + } + me->len = strlen(me->buf); + } + me = me->next; + } +} + +token_t* process_token_list(buffer_t* buf, token_t* token) { + char *c; + + buffer_init(buf); + subshell_exec(buf, "\n"); + while(token) { + switch(token->tag) { + case HTML: + c = token->buf; + while((c < (token->buf + token->len)) && (isspace(*c))) { + c++; + } + if(c != token->buf + token->len) { + subshell_echo (buf, token->buf, token->len); + } + break; + case RUN: + subshell_exec(buf, token->buf); + subshell_exec(buf, "\n"); + break; + case ECHO: + subshell_eval(buf, token->buf, token->len); + break; + case TRANSLATE: + subshell_translate(buf, token->buf, token->len); + break; + default: + break; + } + token = token->next; + } + return token; +} + +token_t* push_token_on_list(token_t* tokenlist, script_t* scriptbuf, char* start, size_t len) { + token_t* me; + token_t* next; + + if(len == 0) { + return tokenlist; + } + me = (token_t*) xmalloc(sizeof(token_t)); + if(tokenlist == NULL) { + next = NULL; + } else { + next = tokenlist->next; + tokenlist->next = me; + } + me->next = next; + me->script = scriptbuf; + me->buf = start; + me->len = len; + return me; +} + +void subshell_destroy(void) { + int status; + waitpid(subshell_pid, &status, 0); +} + +void subshell_doscript(buffer_t* script, char* name) { + static char postfix[] = "\nexit\n"; + + write(subshell_pipe[PARENT_OUT], script->data, script->ptr - script->data); + write(subshell_pipe[PARENT_OUT], postfix, strlen(postfix)); + return; +} + +void subshell_echo(buffer_t* buf, char* str, size_t len) { + static char echo_start[] = "printf '%s' '"; + static char echo_quote[] = "'\\''"; + static char echo_end[] = "'\n"; + const size_t maxlen = 3096; + size_t pos; + + if(len == 0) { + return; + } + pos = 0; + buffer_add(buf, echo_start, strlen(echo_start)); + while(pos < len) { + if (str[pos] == '\'') { + buffer_add(buf, echo_quote, strlen(echo_quote)); + } else { + buffer_add(buf, str + pos, 1); + } + pos++; + if((pos % maxlen) == 0) { + buffer_add(buf, echo_end, strlen(echo_end)); + buffer_add(buf, echo_start, strlen(echo_start)); + } + } + buffer_add(buf, echo_end, strlen(echo_end)); +} + +void subshell_eval(buffer_t* buf, char* str, size_t len) { + static char echo_start[] = "echo -n "; + static char echo_end[] = "\n"; + + if(len == 0) { + return; + } + str = trim(str); + if(!*str) { + return; + } + buffer_add(buf, echo_start, strlen(echo_start)); + buffer_add(buf, str, len); + buffer_add(buf, echo_end, strlen(echo_end)); +} + +void subshell_exec(buffer_t* buf, char* str) { + buffer_add (buf, str, strlen (str)); + return; +} + +void subshell_setup (char* shell, list_t* env) { + int retcode = 0; + int count; + argv_t* argv; + char* av[20]; + list_t* next; + + if(shell == NULL) { + return; + } + retcode = pipe(&subshell_pipe[PARENT_IN]); + if(retcode == 0) { + subshell_pid = fork(); + if(subshell_pid == -1) { + die_with_message(NULL, NULL, g_err_msg[E_SUBSHELL_FAIL]); + } + if(subshell_pid == 0) { + dup2(subshell_pipe[PARENT_IN], STDIN_FILENO); + close(subshell_pipe[PARENT_IN]); + close(subshell_pipe[PARENT_OUT]); + count = argc_argv(shell, &argv); + if(count > 19) { + av[19] = "\0"; + count = 18; + } + while(count >= 0) { + av[count] = argv[count].string; + count--; + } + while(env) { + next = env->next; + putenv(env->buf); + env = next; + } + execv(argv[0].string, av); + free(argv); + die_with_message(NULL, NULL, g_err_msg[E_SUBSHELL_FAIL]); + } else { + close(subshell_pipe[PARENT_IN]); + } + } +} + +void subshell_translate(buffer_t* buf, char* str, size_t len) { + static char echo_start[] = "echo -n \""; + static char echo_end[] = "\"\n"; + short hash; + lstr* i; + char* text = NULL; + + if(len == 0) { + return; + } + str = trim(str); + if(!*str) { + return; + } + if(language != NULL && translations > 0) { + hash = generateHash(str); + i = ltable[hash]; + while(text == NULL && i != NULL) { + if(strcmp(str, i->msgid) == 0) { + text = i->msgstr; + } else { + i = i->next; + } + } + } + if(text == NULL) { + text = str; + } + buffer_add(buf, echo_start, strlen(echo_start)); + buffer_add(buf, text, strlen(text)); + buffer_add(buf, echo_end, strlen(echo_end)); +} diff --git a/subshell.h b/subshell.h new file mode 100644 index 0000000..ee6847b --- /dev/null +++ b/subshell.h @@ -0,0 +1,52 @@ +/** + * @PROJECT CGI Bash Shell Interface + * @COPYRIGHT See COPYING in the top level directory + * @FILE subshell.h + * @PURPOSE Subshell execution + * @DEVELOPERS Nathan Angelacos + * Rafal Kupiec + */ + +#ifndef __SUBSHELL_H +#define __SUBSHELL_H + +static int subshell_pid; +static int subshell_pipe[2]; + +enum pipe_t { PARENT_IN, PARENT_OUT }; +enum tag_t { HTML, RUN, ECHO, TRANSLATE, NOOP }; + +typedef struct { + char* name; + int size; + uid_t uid; + gid_t gid; + char* buf; + size_t curpos; + void* next; +} script_t; + +typedef struct { + script_t* script; + enum tag_t tag; + size_t len; + char* buf; + void* next; +} token_t; + +token_t* build_token_list(script_t* scriptbuf, token_t* tokenlist); +void free_script_list(script_t* script); +void free_token_list(token_t* tokenlist); +script_t* load_script(char* filename, script_t* scriptlist); +void preprocess_token_list(token_t* tokenlist); +token_t* process_token_list(buffer_t* buf, token_t* tokenlist); +token_t* push_token_on_list(token_t* tokenlist, script_t* scriptbuf, char* start, size_t len); +void subshell_destroy(void); +void subshell_doscript(buffer_t* script, char* name); +void subshell_echo(buffer_t* buf, char* str, size_t len); +void subshell_eval(buffer_t* buf, char* str, size_t len); +void subshell_exec(buffer_t* buf, char* str); +void subshell_setup(char* shell, list_t* env); +void subshell_translate(buffer_t* buf, char* str, size_t len); + +#endif