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..d02fd48 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +CC = gcc +CFLAGS = -O2 -s -D_GNU_SOURCE +LDLIBS = -lcrypt -lcyassl -lm +PREFIX = +BINDIR = $(PREFIX)/usr/sbin + +ifeq ($(CROSS_BUILD), 1) + DEFS:=-DCROSS_BUILD +else + DEFS:= +endif + +all: kuhttpd cert + @echo "All done!" + +kuhttpd: kuhttpd.o match.o dateparse.o + $(CC) $(CFLAGS) $(LDLIBS) kuhttpd.o match.o dateparse.o -o kuhttpd + +kuhttpd.o: kuhttpd.c + $(CC) $(CFLAGS) $(DEFS) -c kuhttpd.c + +match.o: match.c + $(CC) $(CFLAGS) $(DEFS) -c match.c + +dateparse.o: dateparse.c + $(CC) $(CFLAGS) $(DEFS) -c dateparse.c + +cert: kuhttpd.pem +kuhttpd.pem: kuhttpd.cnf + openssl req -batch -new -x509 -days 7500 -nodes -config kuhttpd.cnf -out kuhttpd.pem -keyout kuhttpd.pem + openssl x509 -subject -dates -fingerprint -noout -in kuhttpd.pem + chmod 600 kuhttpd.pem + +install: + mkdir -p $(BINDIR) + cp kuhttpd $(BINDIR) + @echo "All done!" + +uninstall: + @rm -f $(BINDIR)/kuhttpd + @echo "All done!" + +clean: + rm -f kuhttpd kuhttpd.pem kuhttpd.rnd *.o + @echo "All done!" diff --git a/README b/README new file mode 100644 index 0000000..9fa8f4a --- /dev/null +++ b/README @@ -0,0 +1,21 @@ +Kagera Micro HTTP Daemon +======================== + +KUHTTP Daemon is a micro HTTP server optimised for size and low and medium +traffic sites. It is dedicated to serve configuration sites on Linux-based +routers as well as some additional pages like Captive Portals. It implements +all the basic features of an HTTP server, including: + + * GET, HEAD, and POST methods + * CGI process communication + * Multilanguage CGI + * Basic authentication + * IPv6 and SSL/HTTPS communication + * Directory listings + * The common MIME types + * Trailing-slash redirection + * Security against ".." filename snooping + * Standard logging + + +KUHTTP Daemon is based on mini_httpd: . diff --git a/README.md b/README.md deleted file mode 100644 index 44bcfaf..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -kuhttpd -======= - -Kagera uHTTP Daemon diff --git a/dateparse.c b/dateparse.c new file mode 100644 index 0000000..0549c6d --- /dev/null +++ b/dateparse.c @@ -0,0 +1,187 @@ +/** + * @PROJECT Kagera uHTTP Daemon + * @COPYRIGHT See COPYING in the top level directory + * @FILE dateparse.c + * @PURPOSE Parses string dates into internal form + * @DEVELOPERS Rafal Kupiec + * Jef Poskanzer + */ + +#include +#include +#include +#include +#include +#include + +#include "dateparse.h" + +time_t dateparse(char* str) { + struct tm tm; + char* cp; + char str_mon[500], str_wday[500]; + int tm_sec, tm_min, tm_hour, tm_mday, tm_year; + long tm_mon, tm_wday; + + (void) memset((char*) &tm, 0, sizeof(struct tm)); + for(cp = str; *cp == ' ' || *cp == '\t'; ++cp) { + continue; + } + if(sscanf(cp, "%d-%400[a-zA-Z]-%d %d:%d:%d GMT", &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec) == 6 && scan_mon(str_mon, &tm_mon)) { + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } else if(sscanf(cp, "%d %400[a-zA-Z] %d %d:%d:%d GMT", &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec) == 6 && scan_mon(str_mon, &tm_mon)) { + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } else if(sscanf(cp, "%d:%d:%d GMT %d-%400[a-zA-Z]-%d", &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon, &tm_year) == 6 && scan_mon(str_mon, &tm_mon)) { + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + } else if(sscanf(cp, "%d:%d:%d GMT %d %400[a-zA-Z] %d", &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon, &tm_year) == 6 && scan_mon(str_mon, &tm_mon)) { + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + } else if(sscanf(cp, "%400[a-zA-Z], %d-%400[a-zA-Z]-%d %d:%d:%d GMT", str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec) == 7 && scan_wday(str_wday, &tm_wday) && scan_mon(str_mon, &tm_mon)) { + tm.tm_wday = tm_wday; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } else if(sscanf(cp, "%400[a-zA-Z], %d %400[a-zA-Z] %d %d:%d:%d GMT", str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec) == 7 && scan_wday(str_wday, &tm_wday) && scan_mon(str_mon, &tm_mon)) { + tm.tm_wday = tm_wday; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } else if(sscanf(cp, "%400[a-zA-Z] %400[a-zA-Z] %d %d:%d:%d GMT %d", str_wday, str_mon, &tm_mday, &tm_hour, &tm_min, &tm_sec, &tm_year) == 7 && scan_wday(str_wday, &tm_wday) && scan_mon(str_mon, &tm_mon)) { + tm.tm_wday = tm_wday; + tm.tm_mon = tm_mon; + tm.tm_mday = tm_mday; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + tm.tm_year = tm_year; + } else { + return (time_t) -1; + } + if(tm.tm_year > 1900) { + tm.tm_year -= 1900; + } else if(tm.tm_year < 70) { + tm.tm_year += 100; + } + return tm_to_time(&tm); +} + +static int is_leap(int year) { + return year % 400? (year % 100 ? (year % 4 ? 0 : 1) : 0) : 1; +} + +static void pound_case(char* str) { + for(; *str != '\0'; ++str) { + if(isupper((int) *str)) { + *str = tolower((int) *str); + } + } +} + +static int scan_mon(char* str_mon, long* tm_monP) { + static struct strlong mon_tab[] = { + { "jan", 0 }, { "january", 0 }, + { "feb", 1 }, { "february", 1 }, + { "mar", 2 }, { "march", 2 }, + { "apr", 3 }, { "april", 3 }, + { "may", 4 }, + { "jun", 5 }, { "june", 5 }, + { "jul", 6 }, { "july", 6 }, + { "aug", 7 }, { "august", 7 }, + { "sep", 8 }, { "september", 8 }, + { "oct", 9 }, { "october", 9 }, + { "nov", 10 }, { "november", 10 }, + { "dec", 11 }, { "december", 11 }, + }; + static int sorted = 0; + if(!sorted) { + (void) qsort(mon_tab, sizeof(mon_tab) / sizeof(struct strlong), sizeof(struct strlong), (int(*)(const void*, const void*)) strlong_compare); + sorted = 1; + } + pound_case(str_mon); + return strlong_search(str_mon, mon_tab, sizeof(mon_tab) / sizeof(struct strlong), tm_monP); +} + +static int scan_wday(char* str_wday, long* tm_wdayP) { + static struct strlong wday_tab[] = { + { "sun", 0 }, { "sunday", 0 }, + { "mon", 1 }, { "monday", 1 }, + { "tue", 2 }, { "tuesday", 2 }, + { "wed", 3 }, { "wednesday", 3 }, + { "thu", 4 }, { "thursday", 4 }, + { "fri", 5 }, { "friday", 5 }, + { "sat", 6 }, { "saturday", 6 }, + }; + static int sorted = 0; + if(!sorted) { + (void) qsort(wday_tab, sizeof(wday_tab) / sizeof(struct strlong), sizeof(struct strlong), (int(*)(const void*, const void*)) strlong_compare); + sorted = 1; + } + pound_case(str_wday); + return strlong_search(str_wday, wday_tab, sizeof(wday_tab) / sizeof(struct strlong), tm_wdayP); +} + +static int strlong_compare(char *v1, char *v2) { + return strcmp(((struct strlong*) v1)->s, ((struct strlong*) v2)->s); +} + +static int strlong_search(char* str, struct strlong* tab, int n, long* lP) { + int i, h, l, r; + l = 0; + h = n - 1; + for(;;) { + i = (h + l) / 2; + r = strcmp(str, tab[i].s); + if(r < 0) { + h = i - 1; + } else if(r > 0) { + l = i + 1; + } else { + *lP = tab[i].l; + return 1; + } + if(h < l) { + return 0; + } + } +} + +static time_t tm_to_time(struct tm* tmP) { + time_t t; + static int monthtab[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + t = (tmP->tm_year - 70) * 365; + t += (tmP->tm_year - 69) / 4; + t += monthtab[tmP->tm_mon]; + if(tmP->tm_mon >= 2 && is_leap(tmP->tm_year + 1900)) { + ++t; + } + t += tmP->tm_mday - 1; + t = t * 24 + tmP->tm_hour; + t = t * 60 + tmP->tm_min; + t = t * 60 + tmP->tm_sec; + return t; +} diff --git a/dateparse.h b/dateparse.h new file mode 100644 index 0000000..61ebb5a --- /dev/null +++ b/dateparse.h @@ -0,0 +1,27 @@ +/** + * @PROJECT Kagera uHTTP Daemon + * @COPYRIGHT See COPYING in the top level directory + * @FILE dateparse.h + * @PURPOSE Parses string dates into internal form + * @DEVELOPERS Rafal Kupiec + * Jef Poskanzer + */ + +#ifndef __DATEPARSE_H +#define __DATEPARSE_H + +struct strlong { + char* s; + long l; +}; + +extern time_t dateparse(char* str); +static int is_leap(int year); +static void pound_case(char* str); +static int scan_mon(char* str_mon, long* tm_monP); +static int scan_wday(char* str_wday, long* tm_wdayP); +static int strlong_compare(char *v1, char *v2); +static int strlong_search(char* str, struct strlong* tab, int n, long* lP); +static time_t tm_to_time(struct tm* tmP); + +#endif diff --git a/kuhttpd.c b/kuhttpd.c new file mode 100644 index 0000000..e054806 --- /dev/null +++ b/kuhttpd.c @@ -0,0 +1,2279 @@ +/** + * @PROJECT Kagera uHTTP Daemon + * @COPYRIGHT See COPYING in the top level directory + * @FILE kuhttpd.c + * @PURPOSE HTTP Server + * @DEVELOPERS Eric Bishop + * Rafal Kupiec + * Jef Poskanzer + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef CROSS_BUILD + #include +#endif + +#include "kuhttpd.h" +#include "dateparse.h" +#include "match.h" +#include "mime_enc.h" +#include "mime_typ.h" + +static void add_headers(int s, char* title, char* extra_header, char* me, char* mt, off_t b, time_t mod) { + time_t now; + char timebuf[100]; + char buf[10000]; + int buflen; + const char* rfc1123_fmt = "%a, %d %b %Y %H:%M:%S GMT"; + + status = s; + bytes = b; + make_log_entry(); + start_response(); + buflen = snprintf(buf, sizeof(buf), "%s %d %s\015\012", protocol, status, title); + add_to_response(buf, buflen); + buflen = snprintf(buf, sizeof(buf), "Server: %s\015\012", SERVER_SOFTWARE); + add_to_response(buf, buflen); + now = time((time_t*) 0); + (void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&now)); + buflen = snprintf(buf, sizeof(buf), "Date: %s\015\012", timebuf); + add_to_response(buf, buflen); + buflen = snprintf(buf, sizeof(buf), "Expires: %s\015\012", timebuf); + add_to_response(buf, buflen); + if(mod != (time_t) -1) { + (void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&mod)); + buflen = snprintf(buf, sizeof(buf), "Last-Modified: %s\015\012", timebuf); + add_to_response(buf, buflen); + } + if(extra_header != (char*) 0 && extra_header[0] != '\0') { + buflen = snprintf(buf, sizeof(buf), "%s\015\012", extra_header); + add_to_response(buf, buflen); + } + if(me != (char*) 0 && me[0] != '\0') { + buflen = snprintf(buf, sizeof(buf), "Content-Encoding: %s\015\012", me); + add_to_response(buf, buflen); + } + if(mt != (char*) 0 && mt[0] != '\0') { + buflen = snprintf(buf, sizeof(buf), "Content-Type: %s\015\012", mt); + add_to_response(buf, buflen); + } + if(bytes >= 0) { + buflen = snprintf(buf, sizeof(buf), "Content-Length: %lld\015\012", (long long int) bytes); + add_to_response(buf, buflen); + } + if(p3p != (char*) 0 && p3p[0] != '\0') { + buflen = snprintf(buf, sizeof(buf), "P3P: %s\015\012", p3p); + add_to_response(buf, buflen); + } + buflen = snprintf(buf, sizeof(buf), "\015\012"); + add_to_response(buf, buflen); +} + +static void add_to_buf(char** bufP, size_t* bufsizeP, size_t* buflenP, char* str, size_t len) { + if(*bufsizeP == 0) { + *bufsizeP = len + 500; + *buflenP = 0; + *bufP = (char*) e_malloc(*bufsizeP); + } else if(*buflenP + len >= *bufsizeP) { + *bufsizeP = *buflenP + len + 500; + *bufP = (char*) e_realloc((void*) *bufP, *bufsizeP); + } + (void) memmove(&((*bufP)[*buflenP]), str, len); + *buflenP += len; + (*bufP)[*buflenP] = '\0'; +} + +static void add_to_request(char* str, size_t len) { + add_to_buf(&request, &request_size, &request_len, str, len); +} + +static void add_to_response(char* str, size_t len) { + add_to_buf(&response, &response_size, &response_len, str, len); +} + +static void auth_check(char* dirname, int is_ssl) { + char authpath[10000]; + char realmName[500]; + struct stat sb; + char authinfo[500]; + char* authpass; + char* colon; + static char line[10000]; + int l; + FILE* fp; + char* cryp; + + if(dirname[strlen(dirname) - 1] == '/') { + (void) snprintf(authpath, sizeof(authpath), "%s%s", dirname, AUTH_FILE); + } else { + (void) snprintf(authpath, sizeof(authpath), "%s/%s", dirname, AUTH_FILE); + } + if(stat(authpath, &sb) < 0) { + if(defaultRealmName == NULL || defaultRealmPasswordFile == NULL) { + return; + } else { + snprintf(authpath, sizeof(authpath), "%s", defaultRealmPasswordFile); + } + } + if(strcmp(authpath, defaultRealmPasswordFile) == 0) { + snprintf(realmName, sizeof(realmName), "%s", defaultRealmName); + } else { + snprintf(realmName, sizeof(realmName), "%s", dirname); + } + if(authorization == (char*) 0) { + send_authenticate(realmName, is_ssl); + } + if(strncmp(authorization, "Basic ", 6) != 0) { + send_authenticate(realmName, is_ssl); + } + l = b64_decode(&(authorization[6]), (unsigned char*) authinfo, sizeof(authinfo) - 1); + authinfo[l] = '\0'; + authpass = strchr(authinfo, ':'); + if(authpass == (char*) 0) { + send_authenticate(realmName, is_ssl); + } + *authpass++ = '\0'; + colon = strchr(authpass, ':'); + if(colon != (char*) 0) { + *colon = '\0'; + } + fp = fopen(authpath, "r"); + if(fp == (FILE*) 0) { + syslog(LOG_ERR, "%.80s auth file %.80s could not be opened - %m", ntoa(&client_addr), authpath); + send_error(403, "Forbidden", "", "File is protected.", is_ssl); + } + while(fgets(line, sizeof(line), fp) != (char*) 0) { + l = strlen(line); + if(line[l - 1] == '\n') { + line[l - 1] = '\0'; + } + cryp = strchr(line, ':'); + if(cryp == (char*) 0) { + continue; + } + *cryp++ = '\0'; + if(strcmp(line, authinfo) == 0) { + (void) fclose(fp); + if(strcmp(crypt(authpass, cryp), cryp) == 0) { + remoteuser = line; + return; + } else { + send_authenticate(realmName, is_ssl); + } + } + } + (void) fclose(fp); + send_authenticate(realmName, is_ssl); +} + +static int b64_decode(const char* str, unsigned char* space, int size) { + const char* cp; + int space_idx, phase; + int d, prev_d = 0; + unsigned char c; + + space_idx = 0; + phase = 0; + for(cp = str; *cp != '\0'; ++cp) { + d = b64_decode_table[(int) *cp]; + if(d != -1) { + switch(phase) { + case 0: + ++phase; + break; + case 1: + c = ((prev_d << 2) | ((d & 0x30) >> 4)); + if(space_idx < size) { + space[space_idx++] = c; + } + ++phase; + break; + case 2: + c = (((prev_d & 0xf) << 4) | ((d & 0x3c) >> 2)); + if(space_idx < size) { + space[space_idx++] = c; + } + ++phase; + break; + case 3: + c = (((prev_d & 0x03) << 6) | d); + if(space_idx < size) { + space[space_idx++] = c; + } + phase = 0; + break; + } + prev_d = d; + } + } + return space_idx; +} + +static char* build_env(char* fmt, char* arg) { + char* cp; + int size; + static char* buf; + static int maxbuf = 0; + + size = strlen(fmt) + strlen(arg); + if(size > maxbuf) { + if(maxbuf == 0) { + maxbuf = MAX(200, size + 100); + buf = (char*) e_malloc(maxbuf); + } else { + maxbuf = MAX(maxbuf * 2, size * 5 / 4); + buf = (char*) e_realloc((void*) buf, maxbuf); + } + } + (void) snprintf(buf, maxbuf, fmt, arg); + cp = e_strdup(buf); + return cp; +} + +static void cgi_interpose_input(int wfd, int is_ssl) { + size_t c; + ssize_t r, r2; + char buf[1024]; + + c = request_len - request_idx; + if(c > 0) { + if(write(wfd, &(request[request_idx]), c) != c) { + return; + } + } + while(c < content_length) { + r = my_read(buf, MIN( sizeof(buf), content_length - c), is_ssl); + if(r < 0 && (errno == EINTR || errno == EAGAIN)) { + sleep(1); + continue; + } + if(r <= 0) { + return; + } + for(;;) { + r2 = write(wfd, buf, r); + if(r2 < 0 && (errno == EINTR || errno == EAGAIN)) { + sleep(1); + continue; + } + if(r2 != r) { + return; + } + break; + } + c += r; + } + post_post_garbage_hack(is_ssl); +} + +static void cgi_interpose_output(int rfd, int parse_headers, int is_ssl) { + ssize_t r, r2; + char buf[1024]; + + if(!parse_headers) { + char http_head[] = "HTTP/1.0 200 OK\015\012"; + (void) my_write(http_head, sizeof(http_head), is_ssl); + } else { + size_t headers_size, headers_len; + char* headers; + char* br; + int status; + char* title; + char* cp; + headers_size = 0; + add_to_buf(&headers, &headers_size, &headers_len, (char*) 0, 0); + for(;;) { + r = read(rfd, buf, sizeof(buf)); + if(r < 0 && (errno == EINTR || errno == EAGAIN)) { + sleep(1); + continue; + } + if(r <= 0) { + br = &(headers[headers_len]); + break; + } + add_to_buf(&headers, &headers_size, &headers_len, buf, r); + if((br = strstr(headers, "\015\012\015\012")) != (char*) 0 || (br = strstr(headers, "\012\012")) != (char*) 0) { + break; + } + } + if(headers[0] == '\0') { + return; + } + status = 200; + if((cp = strstr(headers, "Status:")) != (char*) 0 && cp < br && (cp == headers || *(cp-1) == '\012')) { + cp += 7; + cp += strspn(cp, " \t"); + status = atoi(cp); + } + if((cp = strstr(headers, "Location:")) != (char*) 0 && cp < br && (cp == headers || *(cp-1) == '\012')) { + status = 302; + } + switch(status) { + case 200: + title = "OK"; + break; + case 302: + title = "Found"; + break; + case 304: + title = "Not Modified"; + break; + case 400: + title = "Bad Request"; + break; + case 401: + title = "Unauthorized"; + break; + case 403: + title = "Forbidden"; + break; + case 404: + title = "Not Found"; + break; + case 408: + title = "Request Timeout"; + break; + case 500: + title = "Internal Error"; + break; + case 501: + title = "Not Implemented"; + break; + case 503: + title = "Service Temporarily Overloaded"; + break; + default: + title = "Something"; + break; + } + (void) snprintf(buf, sizeof(buf), "HTTP/1.0 %d %s\015\012", status, title); + (void) my_write(buf, strlen(buf), is_ssl); + if(strstr(headers, "Server:") == NULL) { + char line[200]; + sprintf(line, "Server: %s\015\012", SERVER_SOFTWARE); + (void) my_write(line, strlen(line), is_ssl); + } + if(strstr(headers, "Date:") == NULL) { + char line[200]; + char timebuf[100]; + time_t now = time((time_t*) 0); + const char* rfc1123_fmt = "%a, %d %b %Y %H:%M:%S GMT"; + (void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&now)); + sprintf(line, "Date: %s\015\012", timebuf); + (void) my_write(line, strlen(line), is_ssl); + if(strstr(headers, "Expires:") == NULL) { + sprintf(line, "Expires: %s\015\012", timebuf); + (void) my_write(line, strlen(line), is_ssl); + } + } else if(strstr(headers, "Expires:") == NULL) { + char* line = "Expires: Thu, 01 Jan 1970 00:00:00 GMT\015\012"; + (void) my_write(line, strlen(line), is_ssl); + } + (void) my_write(headers, headers_len, is_ssl); + } + for(;;) { + r = read(rfd, buf, sizeof(buf)); + if(r < 0 && (errno == EINTR || errno == EAGAIN)) { + sleep(1); + continue; + } + if(r <= 0) { + goto done; + } + for(;;) { + r2 = my_write(buf, r, is_ssl); + if(r2 < 0 && (errno == EINTR || errno == EAGAIN)) { + sleep(1); + continue; + } + if(r2 != r) { + goto done; + } + break; + } + } +done: + shutdown(conn_fd, SHUT_WR); +} + +static void check_referer(int is_ssl) { + char* cp; + + if(url_pattern == (char*) 0) { + return; + } + if(really_check_referer()) { + return; + } + cp = hostname; + if(cp == (char*) 0) { + cp = ""; + } + syslog(LOG_INFO, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"", ntoa(&client_addr), cp, path, referer); + send_error(403, "Forbidden", "", "You must supply a local referer.", is_ssl); +} + +static void clear_ndelay(int fd) { + int flags, newflags; + + flags = fcntl(fd, F_GETFL, 0); + if(flags != -1) { + newflags = flags & ~ (int) O_NDELAY; + if(newflags != flags) { + (void) fcntl(fd, F_SETFL, newflags); + } + } +} + +static void de_dotdot(char* file) { + char* cp; + char* cp2; + int l; + + while((cp = strstr(file, "//")) != (char*) 0) { + for(cp2 = cp + 2; *cp2 == '/'; ++cp2) { + continue; + } + (void) strcpy(cp + 1, cp2); + } + while(strncmp(file, "./", 2) == 0) { + (void) strcpy(file, file + 2); + } + while((cp = strstr( file, "/./")) != (char*) 0) { + (void) strcpy(cp, cp + 2); + } + for(;;) { + while(strncmp(file, "../", 3) == 0) { + (void) strcpy( file, file + 3 ); + } + cp = strstr(file, "/../"); + if(cp == (char*) 0) { + break; + } + for(cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2) { + continue; + } + (void) strcpy(cp2 + 1, cp + 4); + } + while((l = strlen(file)) > 3 && strcmp((cp = file + l - 3), "/..") == 0) { + for(cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2) { + continue; + } + if(cp2 < file) { + break; + } + *cp2 = '\0'; + } +} + +static void do_cgi(int is_ssl, unsigned short conn_port) { + char** argp; + char** envp; + int parse_headers; + char* binary; + char* directory; + + if(method != METHOD_GET && method != METHOD_POST) { + send_error(501, "Not Implemented", "", "That method is not implemented for CGI.", is_ssl); + } + if(conn_fd == STDIN_FILENO || conn_fd == STDOUT_FILENO || conn_fd == STDERR_FILENO) { + int newfd = dup2(conn_fd, STDERR_FILENO + 1); + if(newfd >= 0) { + conn_fd = newfd; + } + } + envp = make_envp(is_ssl, conn_port); + argp = make_argp(); + if((method == METHOD_POST && request_len > request_idx) || is_ssl) { + int p[2]; + int r; + if(pipe(p) < 0) { + send_error(500, "Internal Error", "", "Something unexpected went wrong making a pipe.", is_ssl); + } + r = fork(); + if(r < 0) { + send_error(500, "Internal Error", "", "Something unexpected went wrong forking an interposer.", is_ssl); + } + if(r == 0) { + (void) close(p[0]); + cgi_interpose_input(p[1], is_ssl); + exit(0); + } + (void) close(p[1]); + if(p[0] != STDIN_FILENO) { + (void) dup2(p[0], STDIN_FILENO); + (void) close(p[0]); + } + } else { + if(conn_fd != STDIN_FILENO) { + (void) dup2(conn_fd, STDIN_FILENO); + } + } + if(strncmp(argp[0], "nph-", 4) == 0) { + parse_headers = 0; + } else { + parse_headers = 1; + } + if(parse_headers || is_ssl) { + int p[2]; + int r; + if(pipe(p) < 0) { + send_error(500, "Internal Error", "", "Something unexpected went wrong making a pipe.", is_ssl); + } + r = fork(); + if(r < 0) { + send_error(500, "Internal Error", "", "Something unexpected went wrong forking an interposer.", is_ssl); + } + if(r == 0) { + (void) close(p[1]); + cgi_interpose_output(p[0], parse_headers, is_ssl); + exit(0); + } + (void) close(p[0]); + if(p[1] != STDOUT_FILENO) { + (void) dup2(p[1], STDOUT_FILENO); + } + if(p[1] != STDERR_FILENO) { + (void) dup2(p[1], STDERR_FILENO); + } + if(p[1] != STDOUT_FILENO && p[1] != STDERR_FILENO) { + (void) close(p[1]); + } + } else { + if(conn_fd != STDOUT_FILENO) { + (void) dup2(conn_fd, STDOUT_FILENO); + } + if(conn_fd != STDERR_FILENO) { + (void) dup2(conn_fd, STDERR_FILENO); + } + } + if(logfp != (FILE*) 0) { + (void) fclose(logfp); + } + closelog(); + if(nice(CGI_NICE) < 0) { ; } + directory = e_strdup(file); + binary = strrchr(directory, '/'); + if(binary == (char*) 0) { + binary = file; + } else { + *binary++ = '\0'; + if(chdir(directory) < 0) { ; } + } + (void) signal(SIGPIPE, SIG_DFL); + (void) execve(binary, argp, envp); + send_error(500, "Internal Error", "", "Something unexpected went wrong running a CGI program.", is_ssl); +} + +static void do_dir(int is_ssl) { + char buf[10000]; + size_t buflen; + char* contents; + size_t contents_size, contents_len; + int n, i; + struct dirent **dl; + char* name_info; + + if(pathinfo != (char*) 0) { + send_error(404, "Not Found", "", "File not found.", is_ssl); + } + auth_check(file, is_ssl); + check_referer(is_ssl); + n = scandir(file, &dl, NULL, alphasort); + if(n < 0) { + syslog(LOG_INFO, "%.80s Directory \"%.80s\" is protected", ntoa(&client_addr), path); + send_error(403, "Forbidden", "", "Directory is protected.", is_ssl); + } + contents_size = 0; + buflen = snprintf(buf, sizeof(buf), "\nIndex of %s\n\n

Index of %s


\n", file, file);
+	add_to_buf(&contents, &contents_size, &contents_len, buf, buflen);
+	for(i = 0; i < n; ++i) {
+		if(strcmp(dl[i]->d_name, ".") == 0) {
+			continue;
+		}
+		name_info = file_details(file, dl[i]->d_name);
+		add_to_buf(&contents, &contents_size, &contents_len, name_info, strlen(name_info));
+	}
+	buflen = snprintf(buf, sizeof(buf), "

\n%s\n\n\n", SERVER_SOFTWARE); + add_to_buf(&contents, &contents_size, &contents_len, buf, buflen); + add_headers(200, "Ok", "", "", "text/html; charset=%s", contents_len, sb.st_mtime); + if(method != METHOD_HEAD) { + add_to_response(contents, contents_len); + } + send_response(is_ssl); +} + +static void do_file(int is_ssl, unsigned short conn_port) { + char buf[10000]; + char mime_encodings[500]; + const char* mime_type; + char fixed_mime_type[500]; + char* cp; + int fd; + + (void) strncpy(buf, file, sizeof(buf)); + cp = strrchr(buf, '/'); + if(cp == (char*) 0) { + (void) strcpy(buf, "."); + } else { + *cp = '\0'; + } + auth_check(buf, is_ssl); + if(strcmp(file, AUTH_FILE) == 0 || (strcmp(&(file[strlen(file) - sizeof(AUTH_FILE) + 1]), AUTH_FILE) == 0 && file[strlen(file) - sizeof(AUTH_FILE)] == '/')) { + syslog(LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve an auth file", ntoa(&client_addr), path); + send_error(403, "Forbidden", "", "File is protected.", is_ssl); + } + if(defaultRealmName != NULL && defaultRealmPasswordFile != NULL) { + if(strcmp(file, defaultRealmPasswordFile) == 0 || strcmp(&(file[strlen(file) - sizeof(defaultRealmPasswordFile) + 1]), defaultRealmPasswordFile) == 0 && file[strlen(file) - sizeof(defaultRealmPasswordFile)] == '/') { + syslog(LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve an auth file", ntoa(&client_addr), path); + send_error(403, "Forbidden", "", "File is protected.", is_ssl); + } + } + check_referer(is_ssl); + if(cgi_pattern != (char*) 0 && match(cgi_pattern, file)) { + do_cgi(is_ssl, conn_port); + return; + } else if(pathinfo != (char*) 0) { + send_error(404, "Not Found", "", "File not found.", is_ssl); + } + fd = open(file, O_RDONLY); + if(fd < 0) { + syslog(LOG_INFO, "%.80s File \"%.80s\" is protected", ntoa(&client_addr), path); + send_error(403, "Forbidden", "", "File is protected.", is_ssl); + } + mime_type = figure_mime(file, mime_encodings, sizeof(mime_encodings)); + (void) snprintf(fixed_mime_type, sizeof(fixed_mime_type), mime_type, charset); + if(if_modified_since != (time_t) -1 && if_modified_since >= sb.st_mtime) { + add_headers(304, "Not Modified", "", mime_encodings, fixed_mime_type, (off_t) -1, sb.st_mtime); + send_response(is_ssl); + return; + } + add_headers(200, "Ok", "", mime_encodings, fixed_mime_type, sb.st_size, sb.st_mtime); + send_response(is_ssl); + if(method == METHOD_HEAD) { + return; + } + if(sb.st_size > 0) { + send_via_write(fd, sb.st_size, is_ssl); + } + (void) close(fd); +} + +static void* e_malloc(size_t size) { + void* ptr; + + ptr = malloc(size); + if(ptr == (void*) 0) { + syslog(LOG_CRIT, "out of memory"); + (void) fprintf(stderr, "%s: out of memory\n", argv0); + exit(1); + } + return ptr; +} + +static void* e_realloc(void* optr, size_t size) { + void* ptr; + + ptr = realloc(optr, size); + if(ptr == (void*) 0) { + syslog(LOG_CRIT, "out of memory"); + (void) fprintf(stderr, "%s: out of memory\n", argv0); + exit(1); + } + return ptr; +} + +static char* e_strdup(char* ostr) { + char* str; + + str = strdup(ostr); + if(str == (char*) 0) { + syslog(LOG_CRIT, "out of memory copying a string"); + (void) fprintf(stderr, "%s: out of memory copying a string\n", argv0); + exit(1); + } + return str; +} + +static int ext_compare(struct mime_entry* a, struct mime_entry* b) { + return strcmp(a->ext, b->ext); +} + +static const char* figure_mime(char* name, char* me, size_t me_size) { + char* prev_dot; + char* dot; + char* ext; + int me_indexes[100], n_me_indexes; + size_t ext_len, me_len; + int i, top, bot, mid; + int r; + const char* default_type = "application/octet-stream "; + const char* type; + + n_me_indexes = 0; + for(prev_dot = &name[strlen(name)]; ; prev_dot = dot) { + for(dot = prev_dot - 1; dot >= name && *dot != '.'; --dot); + if(dot < name) { + type = default_type; + goto done; + } + ext = dot + 1; + ext_len = prev_dot - ext; + for(i = 0; i < n_enc_tab; ++i) { + if(ext_len == enc_tab[i].ext_len && strncasecmp(ext, enc_tab[i].ext, ext_len) == 0) { + if(n_me_indexes < sizeof(me_indexes)/sizeof(*me_indexes)) { + me_indexes[n_me_indexes] = i; + ++n_me_indexes; + } + goto next; + } + } + break; + next: ; + } + top = n_typ_tab - 1; + bot = 0; + while(top >= bot) { + mid = (top + bot) / 2; + r = strncasecmp(ext, typ_tab[mid].ext, ext_len); + if(r < 0) { + top = mid - 1; + } else if(r > 0) { + bot = mid + 1; + } else if(ext_len < typ_tab[mid].ext_len) { + top = mid - 1; + } else if(ext_len > typ_tab[mid].ext_len) { + bot = mid + 1; + } else { + type = typ_tab[mid].val; + goto done; + } + } + type = default_type; + done: + me[0] = '\0'; + me_len = 0; + for(i = n_me_indexes - 1; i >= 0; --i) { + if(me_len + enc_tab[me_indexes[i]].val_len + 1 < me_size) { + if(me[0] != '\0') { + (void) strcpy(&me[me_len], ","); + ++me_len; + } + (void) strcpy(&me[me_len], enc_tab[me_indexes[i]].val); + me_len += enc_tab[me_indexes[i]].val_len; + } + } + return type; +} + +static char* file_details(const char* dir, const char* name) { + struct stat sb; + char f_time[20]; + static char encname[1000]; + static char buf[2000]; + + (void) snprintf(buf, sizeof(buf), "%s/%s", dir, name); + if(lstat(buf, &sb) < 0) + return "???"; + (void) strftime(f_time, sizeof(f_time), "%d-%b-%Y %H:%M", localtime(&sb.st_mtime)); + strencode(encname, sizeof(encname), name); + (void) snprintf(buf, sizeof(buf), "%-50s %15s %14lld\n", encname, name, f_time, (long long int) sb.st_size); + return buf; +} + +static char* get_method_str(int m) { + switch(m) { + case METHOD_GET: + return "GET"; + case METHOD_HEAD: + return "HEAD"; + case METHOD_POST: + return "POST"; + default: + return "UNKNOWN"; + } +} + +static int get_pathinfo(void) { + int r; + + pathinfo = &file[strlen(file)]; + for(;;) { + do { + --pathinfo; + if(pathinfo <= file) { + pathinfo = (char*) 0; + return -1; + } + } while(*pathinfo != '/'); + *pathinfo = '\0'; + r = stat(file, &sb); + if(r >= 0) { + ++pathinfo; + return r; + } else { + *pathinfo = '/'; + } + } +} + +static char* get_request_line(void) { + int i; + char c; + + for(i = request_idx; request_idx < request_len; ++request_idx) { + c = request[request_idx]; + if(c == '\012' || c == '\015') { + request[request_idx] = '\0'; + ++request_idx; + if(c == '\015' && request_idx < request_len && request[request_idx] == '\012') { + request[request_idx] = '\0'; + ++request_idx; + } + return &(request[i]); + } + } + return (char*) 0; +} + +static void handle_read_timeout(int sig, int is_ssl) { + syslog(LOG_INFO, "%.80s connection timed out reading", ntoa(&client_addr)); + send_error(408, "Request Timeout", "", "No request appeared within a reasonable time period.", is_ssl); +} + +static void handle_read_timeout_sig(int sig) { + handle_read_timeout(0,0); +} + +static void handle_request(int is_ssl, unsigned short conn_port) { + char* method_str; + char* line; + char* cp; + int r, file_len, i; + const char* index_names[] = {"default.html", "default.htm", "index.html", "index.htm", "index.cgi", "index.sh" }; + + (void) signal(SIGALRM, handle_read_timeout_sig); + (void) alarm(READ_TIMEOUT); + + remoteuser = (char*) 0; + method = METHOD_UNKNOWN; + path = (char*) 0; + file = (char*) 0; + pathinfo = (char*) 0; + query = ""; + protocol = (char*) 0; + status = 0; + bytes = -1; + authorization = (char*) 0; + content_type = (char*) 0; + content_length = -1; + cookie = (char*) 0; + host = (char*) 0; + if_modified_since = (time_t) -1; + referer = ""; + useragent = ""; + +#ifdef TCP_NOPUSH + r = 1; + (void) setsockopt(conn_fd, IPPROTO_TCP, TCP_NOPUSH, (void*) &r, sizeof(r)); +#endif + if(is_ssl) { + ssl = SSL_new(ssl_ctx); + SSL_set_fd(ssl, conn_fd); + int accept_ret = SSL_accept(ssl); + if(accept_ret <= 0) { + int e = SSL_get_error(ssl, accept_ret); + if(e != VERSION_ERROR) { + syslog(LOG_CRIT, "error: can't initialize ssl connection, error = %d\n", e); + } + exit(1); + } + } + start_request(); + for(;;) { + char buf[10000]; + int r = my_read( buf, sizeof(buf), is_ssl ); + if(r < 0 && (errno == EINTR || errno == EAGAIN)) { + continue; + } + if(r <= 0) { + break; + } + (void) alarm(READ_TIMEOUT); + add_to_request(buf, r); + if(strstr(request, "\015\012\015\012") != (char*) 0 || strstr(request, "\012\012") != (char*) 0) { + break; + } + } + method_str = get_request_line(); + if(method_str == (char*) 0) { + send_error(400, "Bad Request", "", "Can't parse request.", is_ssl); + } + path = strpbrk(method_str, " \t\012\015"); + if(path == (char*) 0) { + send_error(400, "Bad Request", "", "Can't parse request.", is_ssl); + } + *path++ = '\0'; + path += strspn(path, " \t\012\015"); + protocol = strpbrk(path, " \t\012\015"); + if(protocol == (char*) 0) { + send_error(400, "Bad Request", "", "Can't parse request.", is_ssl); + } + *protocol++ = '\0'; + protocol += strspn(protocol, " \t\012\015"); + query = strchr(path, '?'); + if(query == (char*) 0) { + query = ""; + } else { + *query++ = '\0'; + } + while((line = get_request_line()) != (char*) 0) { + if(line[0] == '\0') { + break; + } else if(strncasecmp(line, "Authorization:", 14) == 0) { + cp = &line[14]; + cp += strspn(cp, " \t"); + authorization = cp; + } else if(strncasecmp(line, "Content-Length:", 15) == 0) { + cp = &line[15]; + cp += strspn(cp, " \t"); + content_length = atol(cp); + } else if(strncasecmp(line, "Content-Type:", 13) == 0) { + cp = &line[13]; + cp += strspn(cp, " \t"); + content_type = cp; + } else if(strncasecmp(line, "Cookie:", 7) == 0) { + cp = &line[7]; + cp += strspn(cp, " \t"); + cookie = cp; + } else if(strncasecmp(line, "Host:", 5) == 0) { + cp = &line[5]; + cp += strspn(cp, " \t"); + host = cp; + if(strchr(host, '/') != (char*) 0 || host[0] == '.') { + send_error(400, "Bad Request", "", "Can't parse request.", is_ssl); + } + } else if(strncasecmp(line, "If-Modified-Since:", 18) == 0) { + cp = &line[18]; + cp += strspn(cp, " \t"); + if_modified_since = dateparse(cp); + } else if(strncasecmp(line, "Referer:", 8) == 0) { + cp = &line[8]; + cp += strspn(cp, " \t"); + referer = cp; + } else if(strncasecmp(line, "User-Agent:", 11) == 0) { + cp = &line[11]; + cp += strspn(cp, " \t"); + useragent = cp; + } + } + if(strcasecmp(method_str, get_method_str(METHOD_GET)) == 0) { + method = METHOD_GET; + } else if(strcasecmp(method_str, get_method_str(METHOD_HEAD)) == 0) { + method = METHOD_HEAD; + } else if(strcasecmp(method_str, get_method_str(METHOD_POST)) == 0) { + method = METHOD_POST; + } else { + send_error(501, "Not Implemented", "", "That method is not implemented.", is_ssl); + } + strdecode(path, path); + if(path[0] != '/') { + send_error(400, "Bad Request", "", "Bad filename.", is_ssl); + } + file = &(path[1]); + de_dotdot(file); + if(file[0] == '\0') { + file = "./"; + } + if(file[0] == '/' || (file[0] == '.' && file[1] == '.' && (file[2] == '\0' || file[2] == '/'))) { + send_error(400, "Bad Request", "", "Illegal filename.", is_ssl); + } + + (void) signal(SIGALRM, handle_write_timeout); + (void) alarm(WRITE_TIMEOUT); + r = stat(file, &sb); + if(r < 0) { + r = get_pathinfo(); + } + if(r < 0) { + if(pageNotFoundFile != NULL && host != NULL) { + send_redirect("", host, pageNotFoundFile, is_ssl); + } else { + send_error(404, "Not Found", "", "File not found.", is_ssl); + } + } + file_len = strlen( file ); + if(!S_ISDIR(sb.st_mode)) { + while(file[file_len - 1] == '/') { + file[file_len - 1] = '\0'; + --file_len; + } + do_file(is_ssl, conn_port); + } else { + char idx[10000]; + unsigned char found_index = 0; + if(file[file_len - 1] != '/' && pathinfo == (char*) 0) { + char location[10000]; + if(query[0] != '\0') { + (void) snprintf(location, sizeof(location), "Location: %s/?%s", path, query); + } else { + (void) snprintf(location, sizeof(location), "Location: %s/", path); + send_error(302, "Found", location, "Directories must end with a slash.", is_ssl); + } + } + if(defaultPageFile != NULL) { + (void) snprintf(idx, sizeof(idx), "%s%s", file, defaultPageFile); + if(stat( idx, &sb ) >= 0) { + file = idx; + do_file(is_ssl, conn_port); + found_index = 1; + } + } + for(i = 0; i < (sizeof(index_names) / sizeof(char*)) && found_index == 0; ++i) { + (void) snprintf(idx, sizeof(idx), "%s%s", file, index_names[i]); + if(stat(idx, &sb) >= 0) { + file = idx; + do_file(is_ssl, conn_port); + found_index = 1; + } + } + if(found_index == 0) { + if(allowDirectoryListing == 0) { + if(pageNotFoundFile != NULL && host != NULL) { + send_redirect("", host, pageNotFoundFile, is_ssl); + } else { + send_error(404, "Not Found", "", "File not found.", is_ssl); + } + } else { + do_dir(is_ssl); + } + } + } + if(is_ssl) { + SSL_free(ssl); + } +} + +static void handle_sigchld(int sig) { + const int oerrno = errno; + pid_t pid; + int status; + + (void) signal(SIGCHLD, handle_sigchld); + for(;;) { + pid = waitpid((pid_t) -1, &status, WNOHANG); + if((int) pid == 0) { + break; + } + if((int) pid < 0) { + if(errno == EINTR || errno == EAGAIN) { + continue; + } + if(errno != ECHILD) { + syslog(LOG_ERR, "child wait - %m"); + perror("child wait"); + } + break; + } + } + errno = oerrno; +} + +static void handle_sighup(int sig) { + const int oerrno = errno; + + (void) signal(SIGHUP, handle_sighup); + got_hup = 1; + errno = oerrno; +} + +static void handle_sigterm(int sig) { + syslog(LOG_NOTICE, "exiting due to signal %d", sig); + (void) fprintf(stderr, "%s: exiting due to signal %d\n", argv0, sig); + closelog(); + exit(1); +} + +static void handle_write_timeout(int sig) { + syslog(LOG_INFO, "%.80s connection timed out writing", ntoa(&client_addr)); + exit(1); +} + +static int hexit(char c) { + if(c >= '0' && c <= '9') { + return c - '0'; + } + if(c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + if(c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + return 0; +} + +static void init_mime(void) { + int i; + + qsort(enc_tab, n_enc_tab, sizeof(*enc_tab), (int(*)(const void*, const void*)) ext_compare); + qsort(typ_tab, n_typ_tab, sizeof(*typ_tab), (int(*)(const void*, const void*)) ext_compare); + + for(i = 0; i < n_enc_tab; ++i) { + enc_tab[i].ext_len = strlen(enc_tab[i].ext); + enc_tab[i].val_len = strlen(enc_tab[i].val); + } + for(i = 0; i < n_typ_tab; ++i) { + typ_tab[i].ext_len = strlen(typ_tab[i].ext); + typ_tab[i].val_len = strlen(typ_tab[i].val); + } +} + +static int initialize_listen_socket(usockaddr* usaP) { + int listen_fd; + int i; + + if(!sockaddr_check(usaP)) { + syslog(LOG_ERR, "unknown sockaddr family on listen socket - %d", usaP->sa.sa_family); + (void) fprintf(stderr, "%s: unknown sockaddr family on listen socket - %d\n", argv0, usaP->sa.sa_family); + return -1; + } + listen_fd = socket(usaP->sa.sa_family, SOCK_STREAM, 0); + if(listen_fd < 0) { + if(usaP->sa.sa_family == AF_INET6 && (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT || errno == ESOCKTNOSUPPORT || EPFNOSUPPORT || EAFNOSUPPORT)) { + /* IPv6 not compiled into kernel, no big deal, don't print errors */ + } else { + syslog(LOG_CRIT, "socket %.80s - %m", ntoa(usaP)); + perror("socket"); + } + return -1; + } + + (void) fcntl(listen_fd, F_SETFD, 1); + i = 1; + if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &i, sizeof(i)) < 0) { + syslog(LOG_CRIT, "setsockopt SO_REUSEADDR - %m"); + perror("setsockopt SO_REUSEADDR"); + return -1; + } + if(bind(listen_fd, &usaP->sa, sockaddr_len(usaP)) < 0) { + syslog(LOG_CRIT, "bind %.80s - %m", ntoa(usaP)); + perror("bind"); + return -1; + } + if(listen(listen_fd, 1024) < 0) { + syslog(LOG_CRIT, "listen - %m"); + perror("listen"); + return -1; + } + return listen_fd; +} + +static void lookup_hostname(usockaddr* usa4P, usockaddr* usa4sP, size_t sa4_len, int* gotv4P, int* gotv4sP, usockaddr* usa6P, usockaddr* usa6sP, size_t sa6_len, int* gotv6P, int* gotv6sP) { + int port_index; + unsigned short port_list[4]; + port_list[0] = port; + port_list[1] = sslPort; + port_list[2] = 0; + + *gotv6P = 0; + *gotv6sP = 0; + *gotv4P = 0; + *gotv4sP = 0; + for(port_index=0; port_list[port_index] != 0; port_index++) { + struct addrinfo hints; + char portstr[10]; + int gaierr; + struct addrinfo* ai; + struct addrinfo* ai2; + struct addrinfo* aiv6; + struct addrinfo* aiv4; + (void) memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + (void) snprintf(portstr, sizeof(portstr), "%d", (int) port_list[port_index]); + if((gaierr = getaddrinfo(hostname, portstr, &hints, &ai)) != 0) { + syslog(LOG_CRIT, "getaddrinfo %.80s - %s", hostname, gai_strerror(gaierr)); + (void) fprintf(stderr, "%s: getaddrinfo %.80s - %s\n", argv0, hostname, gai_strerror(gaierr)); + exit(1); + } + aiv6 = (struct addrinfo*) 0; + aiv4 = (struct addrinfo*) 0; + for(ai2 = ai; ai2 != (struct addrinfo*) 0; ai2 = ai2->ai_next) { + switch(ai2->ai_family) { + case AF_INET6: + if(aiv6 == (struct addrinfo*) 0) { + aiv6 = ai2; + } + break; + case AF_INET: + if(aiv4 == (struct addrinfo*) 0) { + aiv4 = ai2; + } + break; + } + } + if(aiv6 != (struct addrinfo*) 0) { + if(sa6_len < aiv6->ai_addrlen) { + syslog(LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", hostname, (unsigned long) sa6_len, (unsigned long) aiv6->ai_addrlen); + (void) fprintf(stderr, "%s: %.80s - sockaddr too small (%lu < %lu)\n", argv0, hostname, (unsigned long) sa6_len, (unsigned long) aiv6->ai_addrlen); + exit(1); + } + if(port_index==0) { + *gotv6P = 1; + (void) memset(usa6P, 0, sa6_len); + (void) memmove(usa6P, aiv6->ai_addr, aiv6->ai_addrlen); + } else { + *gotv6sP = 1; + (void) memset(usa6sP, 0, sa6_len); + (void) memmove(usa6sP, aiv6->ai_addr, aiv6->ai_addrlen); + } + } + if(aiv4 != (struct addrinfo*) 0) { + if(sa4_len < aiv4->ai_addrlen) { + syslog(LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", hostname, (unsigned long) sa4_len, (unsigned long) aiv4->ai_addrlen); + (void) fprintf(stderr, "%s: %.80s - sockaddr too small (%lu < %lu)\n", argv0, hostname, (unsigned long) sa4_len, (unsigned long) aiv4->ai_addrlen); + exit(1); + } + if(port_index == 0) { + *gotv4P = 1; + (void) memset(usa4P, 0, sa4_len); + (void) memmove(usa4P, aiv4->ai_addr, aiv4->ai_addrlen); + } else { + *gotv4sP = 1; + (void) memset(usa4sP, 0, sa4_len); + (void) memmove(usa4sP, aiv4->ai_addr, aiv4->ai_addrlen); + } + *gotv4P = 1; + } + freeaddrinfo(ai); + } +} + +int main(int argc, char** argv) { + int argn; + struct passwd* pwd; + uid_t uid = 32767; + gid_t gid = 32767; + usockaddr host_addr4; + usockaddr host_addr6; + int gotv4, gotv4s, gotv6, gotv6s; + fd_set lfdset; + int maxfd; + usockaddr usa; + int sz, r; + char* cp; + usockaddr host_addr4s; + usockaddr host_addr6s; + + argv0 = argv[0]; + debug = 0; + port = 0; + dir = (char*) 0; + data_dir = (char*) 0; + cgi_pattern = DEFAULT_CGI_PATTERN; + url_pattern = (char*) 0; + no_empty_referers = 0; + local_pattern = (char*) 0; + charset = DEFAULT_CHARSET; + language = DEFAULT_LANGUAGE; + p3p = (char*) 0; + max_age = -1; + user = DEFAULT_USER; + hostname = (char*) 0; + logfile = (char*) 0; + pidfile = (char*) 0; + logfp = (FILE*) 0; + do_ssl = 0; + certfile = DEFAULT_CERTFILE; + cipher = (char*) 0; + + defaultRealmName = NULL; + defaultRealmPasswordFile = NULL; + defaultPageFile = NULL; + pageNotFoundFile = NULL; + allowDirectoryListing = 1; + sslPort = 0; + argn = 1; + + while(argn < argc && argv[argn][0] == '-') { + if(strcmp( argv[argn], "-V" ) == 0) { + (void) printf("%s\n", SERVER_SOFTWARE); + exit(0); + } else if(strcmp(argv[argn], "-C") == 0 && argn + 1 < argc) { + ++argn; + read_config(argv[argn]); + } else if(strcmp(argv[argn], "-D") == 0) { + debug = 1; + } else if(strcmp(argv[argn], "-S") == 0) { + do_ssl = 1; + } else if(strcmp(argv[argn], "-E") == 0 && argn + 1 < argc) { + ++argn; + certfile = argv[argn]; + } else if(strcmp(argv[argn], "-Y") == 0 && argn + 1 < argc) { + ++argn; + cipher = argv[argn]; + } else if(strcmp(argv[argn], "-p") == 0 && argn + 1 < argc) { + ++argn; + port = (unsigned short) atoi(argv[argn]); + } else if(strcmp(argv[argn], "-d") == 0 && argn + 1 < argc) { + ++argn; + dir = argv[argn]; + } else if(strcmp(argv[argn], "-dd") == 0 && argn + 1 < argc) { + ++argn; + data_dir = argv[argn]; + } else if(strcmp(argv[argn], "-c") == 0 && argn + 1 < argc) { + ++argn; + cgi_pattern = argv[argn]; + } else if(strcmp(argv[argn], "-u") == 0 && argn + 1 < argc) { + ++argn; + user = argv[argn]; + } else if(strcmp(argv[argn], "-h") == 0 && argn + 1 < argc) { + ++argn; + hostname = argv[argn]; + } else if(strcmp(argv[argn], "-l") == 0 && argn + 1 < argc) { + ++argn; + logfile = argv[argn]; + } else if(strcmp(argv[argn], "-i") == 0 && argn + 1 < argc) { + ++argn; + pidfile = argv[argn]; + } else if(strcmp(argv[argn], "-T") == 0 && argn + 1 < argc) { + ++argn; + charset = argv[argn]; + } else if(strcmp(argv[argn], "-L") == 0 && argn + 1 < argc) { + ++argn; + language = argv[argn]; + } else if(strcmp(argv[argn], "-P") == 0 && argn + 1 < argc) { + ++argn; + p3p = argv[argn]; + } else if(strcmp(argv[argn], "-M") == 0 && argn + 1 < argc) { + ++argn; + max_age = atoi(argv[argn]); + } else if(strcmp(argv[argn], "-DRN") == 0 && argn + 1 < argc) { + ++argn; + defaultRealmName = argv[argn]; + } else if(strcmp(argv[argn], "-DRP") == 0 && argn + 1 < argc) { + ++argn; + defaultRealmPasswordFile = argv[argn]; + } else if(strcmp(argv[argn], "-DPF") == 0 && argn + 1 < argc) { + ++argn; + defaultPageFile = argv[argn]; + } else if(strcmp(argv[argn], "-PNF") == 0 && argn + 1 < argc) { + ++argn; + pageNotFoundFile = argv[argn]; + } else if(strcmp(argv[argn], "-ADL") == 0 && argn + 1 < argc) { + ++argn; + if(strcmp(argv[argn], "1") == 0 || strcmp(argv[argn], "true") == 0 || strcmp(argv[argn], "TRUE") == 0 || strcmp(argv[argn], "yes") == 0 || strcmp(argv[argn], "YES") == 0) { + allowDirectoryListing = 1; + } else { + allowDirectoryListing = 0; + } + } else if(strcmp(argv[argn], "-SP") == 0 && argn + 1 < argc) { + ++argn; + sslPort = (unsigned short) atoi(argv[argn]); + } else { + usage(); + } + ++argn; + } + if(argn != argc) { + usage(); + } + + cp = strrchr(argv0, '/'); + if(cp != (char*) 0) { + ++cp; + } else { + cp = argv0; + } + openlog(cp, LOG_NDELAY|LOG_PID, LOG_DAEMON); + + if(port == 0) { + if(sslPort != 0 && do_ssl) { + port = sslPort; + sslPort = 0; + } + if(do_ssl) { + port = DEFAULT_HTTPS_PORT; + } else { + port = DEFAULT_HTTP_PORT; + } + } + + if(getuid() == 0) { + pwd = getpwnam(user); + if(pwd == (struct passwd*) 0) { + syslog(LOG_CRIT, "unknown user - '%s'", user); + (void) fprintf(stderr, "%s: unknown user - '%s'\n", argv0, user); + exit(1); + } + uid = pwd->pw_uid; + gid = pwd->pw_gid; + } + + if(logfile != (char*) 0) { + logfp = fopen(logfile, "a"); + if(logfp == (FILE*) 0) { + syslog(LOG_CRIT, "%s - %m", logfile); + perror(logfile); + exit(1); + } + if(logfile[0] != '/') { + syslog(LOG_WARNING, "logfile is not an absolute path, you may not be able to re-open it"); + (void) fprintf(stderr, "%s: logfile is not an absolute path, you may not be able to re-open it\n", argv0); + } + if(getuid() == 0) { + if(fchown(fileno(logfp), uid, gid) < 0) { + syslog(LOG_WARNING, "fchown logfile - %m"); + perror("fchown logfile"); + } + } + } + + lookup_hostname(&host_addr4, &host_addr4s, sizeof(struct sockaddr_in), &gotv4, &gotv4s, &host_addr6, &host_addr6s, sizeof(struct sockaddr_in6), &gotv6, &gotv6s); + if(hostname == (char*) 0) { + (void) gethostname(hostname_buf, sizeof(hostname_buf)); + hostname = hostname_buf; + } + if(!( gotv4 || gotv4s || gotv6 || gotv6s)) { + syslog(LOG_CRIT, "can't find any valid address"); + (void) fprintf(stderr, "%s: can't find any valid address\n", argv0); + exit(1); + } + + listen6_fd = -1; + listen6s_fd = -1; + listen4_fd = -1; + listen4s_fd = -1; + if(gotv6) { + listen6_fd = initialize_listen_socket(&host_addr6); + } + if(gotv6s) { + listen6s_fd = initialize_listen_socket(&host_addr6s); + } + if(gotv4) { + listen4_fd = initialize_listen_socket(&host_addr4); + } + if(gotv4s) { + listen4s_fd = initialize_listen_socket(&host_addr4s); + } + + if(listen4_fd == -1 && listen6_fd == -1 && listen4s_fd == -1 && listen6s_fd == -1) { + syslog(LOG_CRIT, "can't bind to any address"); + (void) fprintf(stderr, "%s: can't bind to any address\n", argv0); + exit(1); + } + + if(do_ssl) { + ssl_ctx = SSL_CTX_new(TLSv1_server_method()); + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, 0); + if(certfile[0] != '\0') { + if(SSL_CTX_use_certificate_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) != SSL_SUCCESS || SSL_CTX_use_PrivateKey_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) != SSL_SUCCESS) { + syslog(LOG_CRIT, "can't load certificate and/or private key\n"); + exit(1); + } + } + if(cipher != (char*) 0) { + if(SSL_CTX_set_cipher_list(ssl_ctx, cipher) <= 0) { + syslog(LOG_CRIT, "can't load certificate and/or private key\n"); + exit(1); + } + } + } + + if(!debug) { + if(daemon(1, 0) < 0) { + syslog(LOG_CRIT, "daemon - %m"); + perror("daemon"); + exit(1); + } + } else { + (void) setsid(); + } + + if(pidfile != (char*) 0) { + FILE* pidfp = fopen(pidfile, "w"); + if(pidfp == (FILE*) 0) { + syslog(LOG_CRIT, "%s - %m", pidfile); + perror(pidfile); + exit(1); + } + (void) fprintf(pidfp, "%d\n", (int) getpid()); + (void) fclose(pidfp); + } + + tzset(); + + if(getuid() == 0) { + if(setgroups(0, (gid_t*) 0) < 0) { + syslog(LOG_CRIT, "setgroups - %m"); + perror("setgroups"); + exit(1); + } + if(setgid(gid) < 0) { + syslog(LOG_CRIT, "setgid - %m"); + perror("setgid"); + exit(1); + } + if(initgroups(user, gid ) < 0) { + syslog(LOG_ERR, "initgroups - %m"); + perror("initgroups"); + } + } + + if(dir != (char*) 0) { + if(chdir(dir) < 0) { + syslog(LOG_CRIT, "chdir - %m"); + perror("chdir"); + exit(1); + } + } + if(getcwd(cwd, sizeof(cwd) - 1) == NULL) { ; } + if(cwd[strlen(cwd) - 1] != '/') { + (void) strcat( cwd, "/" ); + } + if(data_dir != (char*) 0) { + if(chdir(data_dir) < 0) { + syslog(LOG_CRIT, "data_dir chdir - %m"); + perror("data_dir chdir"); + exit(1); + } + } + if(getuid() == 0) { + if(setuid(uid) < 0) { + syslog(LOG_CRIT, "setuid - %m"); + perror("setuid"); + exit(1); + } + } + + (void) signal(SIGTERM, handle_sigterm); + (void) signal(SIGINT, handle_sigterm); + (void) signal(SIGUSR1, handle_sigterm); + (void) signal(SIGHUP, handle_sighup); + (void) signal(SIGCHLD, handle_sigchld); + (void) signal(SIGPIPE, SIG_IGN); + got_hup = 0; + init_mime(); + + if(hostname == (char*) 0) { + syslog(LOG_NOTICE, "%.80s starting on port %d", SERVER_SOFTWARE, (int) port); + } else { + syslog(LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE, hostname, (int) port); + } + + for(;;) { + int is_ssl; + unsigned short conn_port; + if(got_hup) { + reopen_logfile(); + got_hup = 0; + } + FD_ZERO(&lfdset); + maxfd = -1; + if(listen4_fd != -1) { + FD_SET(listen4_fd, &lfdset); + if(listen4_fd > maxfd) { + maxfd = listen4_fd; + } + } + if(listen4s_fd != -1) { + FD_SET(listen4s_fd, &lfdset); + if(listen4s_fd > maxfd) { + maxfd = listen4s_fd; + } + } + if(listen6_fd != -1) { + FD_SET(listen6_fd, &lfdset); + if(listen6_fd > maxfd) { + maxfd = listen6_fd; + } + } + if(listen6s_fd != -1) { + FD_SET(listen6s_fd, &lfdset); + if(listen6s_fd > maxfd) { + maxfd = listen6s_fd; + } + } + if(select(maxfd + 1, &lfdset, (fd_set*) 0, (fd_set*) 0, (struct timeval*) 0) < 0) { + if(errno == EINTR || errno == EAGAIN) { + continue; + } + syslog(LOG_CRIT, "select - %m"); + perror("select"); + exit(1); + } + is_ssl= 0; + conn_port = port; + if(do_ssl) { + if(sslPort == 0) { + is_ssl= 1; + } else if(listen4s_fd != -1 && FD_ISSET(listen4s_fd, &lfdset)) { + is_ssl = 1; + conn_port = sslPort; + } else if(listen6s_fd != -1 && FD_ISSET(listen6s_fd, &lfdset)) { + is_ssl = 1; + conn_port = sslPort; + } + } + sz = sizeof(usa); + if(listen4_fd != -1 && FD_ISSET(listen4_fd, &lfdset)) { + conn_fd = accept(listen4_fd, &usa.sa, &sz); + } else if(listen4s_fd != -1 && FD_ISSET(listen4s_fd, &lfdset)) { + conn_fd = accept(listen4s_fd, &usa.sa, &sz); + } else if(listen6_fd != -1 && FD_ISSET(listen6_fd, &lfdset)) { + conn_fd = accept(listen6_fd, &usa.sa, &sz); + } else if(listen6s_fd != -1 && FD_ISSET(listen6s_fd, &lfdset)) { + conn_fd = accept(listen6s_fd, &usa.sa, &sz); + } else { + syslog(LOG_CRIT, "select failure"); + (void) fprintf(stderr, "%s: select failure\n", argv0); + exit(1); + } + if(conn_fd < 0) { + if(errno == EINTR || errno == EAGAIN) { + continue; + } +#ifdef EPROTO + if(errno == EPROTO) { + continue; + } +#endif + syslog(LOG_CRIT, "accept - %m"); + perror("accept"); + exit(1); + } + r = fork(); + if(r < 0) { + syslog(LOG_CRIT, "fork - %m"); + perror("fork"); + exit(1); + } + if(r == 0) { + client_addr = usa; + if(listen4_fd != -1) { + (void) close(listen4_fd); + } + if(listen4s_fd != -1) { + (void) close(listen4s_fd); + } + if(listen6_fd != -1) { + (void) close(listen6_fd); + } + if(listen6s_fd != -1) { + (void) close(listen6s_fd); + } + handle_request(is_ssl, conn_port); + exit(0); + } + (void) close(conn_fd); + } +} + +static char** make_argp(void) { + char** argp; + int argn; + char* cp1; + char* cp2; + + argp = (char**) malloc((strlen(query) + 2) * sizeof(char*)); + if(argp == (char**) 0) { + return (char**) 0; + } + argp[0] = strrchr(file, '/'); + if(argp[0] != (char*) 0) { + ++argp[0]; + } else { + argp[0] = file; + } + argn = 1; + if(strchr(query, '=') == (char*) 0) { + for(cp1 = cp2 = query; *cp2 != '\0'; ++cp2) { + if(*cp2 == '+') { + *cp2 = '\0'; + strdecode(cp1, cp1); + argp[argn++] = cp1; + cp1 = cp2 + 1; + } + } + if(cp2 != cp1) { + strdecode(cp1, cp1); + argp[argn++] = cp1; + } + } + argp[argn] = (char*) 0; + return argp; +} + +static char** make_envp(int is_ssl, unsigned short conn_port) { + static char* envp[50]; + int envn; + char* cp; + char buf[256]; + + envn = 0; + envp[envn++] = build_env("PATH=%s", CGI_PATH); + envp[envn++] = build_env("LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH); + envp[envn++] = build_env("SERVER_SOFTWARE=%s", SERVER_SOFTWARE); + envp[envn++] = build_env("WEBUI_LANGUAGE=%s", language); + cp = hostname; + if(cp != (char*) 0) { + envp[envn++] = build_env("SERVER_NAME=%s", cp); + } + envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1"; + envp[envn++] = "SERVER_PROTOCOL=HTTP/1.0"; + (void) snprintf(buf, sizeof(buf), "%d", (int) conn_port); + envp[envn++] = build_env("SERVER_PORT=%s", buf); + envp[envn++] = build_env("REQUEST_METHOD=%s", get_method_str(method)); + envp[envn++] = build_env("SCRIPT_NAME=%s", path); + if(pathinfo != (char*) 0) { + envp[envn++] = build_env("PATH_INFO=/%s", pathinfo); + (void) snprintf(buf, sizeof(buf), "%s%s", cwd, pathinfo); + envp[envn++] = build_env("PATH_TRANSLATED=%s", buf); + } + if(query[0] != '\0') { + envp[envn++] = build_env("QUERY_STRING=%s", query); + } + envp[envn++] = build_env("REMOTE_ADDR=%s", ntoa(&client_addr)); + if(referer[0] != '\0') { + envp[envn++] = build_env("HTTP_REFERER=%s", referer); + } + if(useragent[0] != '\0') { + envp[envn++] = build_env("HTTP_USER_AGENT=%s", useragent); + } + if(cookie != (char*) 0) { + envp[envn++] = build_env("HTTP_COOKIE=%s", cookie); + } + if(host != (char*) 0) { + envp[envn++] = build_env("HTTP_HOST=%s", host); + } + if(content_type != (char*) 0) { + envp[envn++] = build_env("CONTENT_TYPE=%s", content_type); + } + if(content_length != -1) { + (void) snprintf(buf, sizeof(buf), "%lu", (unsigned long) content_length); + envp[envn++] = build_env("CONTENT_LENGTH=%s", buf); + } + if(remoteuser != (char*) 0) { + envp[envn++] = build_env("REMOTE_USER=%s", remoteuser); + } + if(authorization != (char*) 0) { + envp[envn++] = build_env("AUTH_TYPE=%s", "Basic"); + } + if(getenv("TZ") != (char*) 0) { + envp[envn++] = build_env("TZ=%s", getenv("TZ")); + } + envp[envn] = (char*) 0; + return envp; +} + +static void make_log_entry(void) { + char* ru; + char url[500]; + char bytes_str[40]; + time_t now; + struct tm* t; + const char* cernfmt_nozone = "%d/%b/%Y:%H:%M:%S"; + char date_nozone[100]; + int zone; + char sign; + char date[100]; + + if(logfp == (FILE*) 0) { + return; + } + if(protocol == (char*) 0) { + protocol = "UNKNOWN"; + } + if(path == (char*) 0) { + path = ""; + } + if(remoteuser != (char*) 0) { + ru = remoteuser; + } else { + ru = "-"; + } + now = time((time_t*) 0); + (void) snprintf(url, sizeof(url), "%s", path); + if(bytes >= 0) { + (void) snprintf(bytes_str, sizeof(bytes_str), "%lld", (long long int) bytes ); + } else { + (void) strcpy(bytes_str, "-"); + } + t = localtime(&now); + (void) strftime(date_nozone, sizeof(date_nozone), cernfmt_nozone, t); + zone = t->tm_gmtoff / 60L; + if(zone >= 0) { + sign = '+'; + } else { + sign = '-'; + zone = -zone; + } + zone = (zone / 60) * 100 + zone % 60; + (void) snprintf(date, sizeof(date), "%s %c%04d", date_nozone, sign, zone); + (void) fprintf(logfp, "%.80s - %.80s [%s] \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"\n", ntoa(&client_addr), ru, date, get_method_str(method), url, protocol, status, bytes_str, referer, useragent); + (void) fflush(logfp); +} + +static ssize_t my_read(char* buf, size_t size, int is_ssl) { + if(is_ssl) { + return SSL_read(ssl, buf, size); + } else { + return read(conn_fd, buf, size); + } +} + +static ssize_t my_write(char* buf, size_t size, int is_ssl) { + if(is_ssl) { + return SSL_write(ssl, buf, size); + } else { + return write(conn_fd, buf, size); + } +} + +static void no_value_required(char* name, char* value) { + if(value != (char*) 0) { + (void) fprintf(stderr, "%s: no value required for %s option\n", argv0, name); + exit(1); + } +} + +static char* ntoa(usockaddr* usaP) { + static char str[200]; + + if(getnameinfo(&usaP->sa, sockaddr_len(usaP), str, sizeof(str), 0, 0, NI_NUMERICHOST) != 0) { + str[0] = '?'; + str[1] = '\0'; + } else if(IN6_IS_ADDR_V4MAPPED(&usaP->sa_in6.sin6_addr) && strncmp(str, "::ffff:", 7) == 0) { + (void) strcpy(str, &str[7]); + } + return str; +} + +static void post_post_garbage_hack(int is_ssl) { + char buf[2]; + + if(is_ssl) { + return; + } + set_ndelay(conn_fd); + if(read(conn_fd, buf, sizeof(buf)) < 0) { ; }; + clear_ndelay(conn_fd); +} + +static void read_config(char* filename) { + FILE* fp; + char line[10000]; + char* cp; + char* cp2; + char* name; + char* value; + + fp = fopen(filename, "r"); + if(fp == (FILE*) 0) { + syslog(LOG_CRIT, "%s - %m", filename); + perror(filename); + exit(1); + } + while(fgets(line, sizeof(line), fp) != (char*) 0) { + if((cp = strchr(line, '#')) != (char*) 0) { + *cp = '\0'; + } + cp = line; + cp += strspn(cp, " \t\012\015"); + while(*cp != '\0') { + cp2 = cp + strcspn(cp, " \t\012\015"); + while(*cp2 == ' ' || *cp2 == '\t' || *cp2 == '\012' || *cp2 == '\015') { + *cp2++ = '\0'; + } + name = cp; + value = strchr(name, '='); + if(value != (char*) 0) { + *value++ = '\0'; + } + if(strcasecmp(name, "debug") == 0) { + no_value_required(name, value); + debug = 1; + } else if(strcasecmp(name, "port") == 0) { + value_required(name, value); + port = (unsigned short) atoi(value); + } else if(strcasecmp(name, "dir") == 0) { + value_required(name, value); + dir = e_strdup(value); + } else if(strcasecmp(name, "data_dir") == 0) { + value_required(name, value); + data_dir = e_strdup(value); + } else if(strcasecmp(name, "user") == 0) { + value_required(name, value); + user = e_strdup(value); + } else if(strcasecmp(name, "cgipat") == 0) { + value_required(name, value); + cgi_pattern = e_strdup(value); + } else if(strcasecmp(name, "urlpat") == 0) { + value_required(name, value); + url_pattern = e_strdup(value); + } else if(strcasecmp(name, "noemptyreferers") == 0) { + value_required(name, value); + no_empty_referers = 1; + } else if(strcasecmp(name, "localpat") == 0) { + value_required(name, value); + local_pattern = e_strdup(value); + } else if(strcasecmp(name, "host") == 0) { + value_required(name, value); + hostname = e_strdup(value); + } else if(strcasecmp(name, "logfile") == 0) { + value_required(name, value); + logfile = e_strdup(value); + } else if(strcasecmp(name, "pidfile") == 0) { + value_required(name, value); + pidfile = e_strdup(value); + } else if(strcasecmp(name, "charset") == 0) { + value_required(name, value); + charset = e_strdup(value); + } else if(strcasecmp(name, "p3p") == 0) { + value_required(name, value); + p3p = e_strdup(value); + } else if(strcasecmp(name, "max_age") == 0) { + value_required(name, value); + max_age = atoi(value); + } else if(strcasecmp(name, "default_realm_name") == 0) { + value_required(name, value); + defaultRealmName = e_strdup(value); + } else if(strcasecmp(name, "default_realm_password_file") == 0) { + value_required(name, value); + defaultRealmPasswordFile = e_strdup(value); + } else if(strcasecmp(name, "default_page_file") == 0) { + value_required(name, value); + defaultPageFile = e_strdup(value); + } else if(strcasecmp(name, "page_not_found_file") == 0) { + value_required(name, value); + pageNotFoundFile = e_strdup(value); + } else if(strcasecmp(name, "allow_directory_listing") == 0) { + value_required(name, value); + if(strcmp(value, "1") == 0 || strcmp(value, "true") == 0 || strcmp(value, "TRUE") == 0 || strcmp(value, "yes") == 0 || strcmp(value, "YES") == 0) { + allowDirectoryListing = 1; + } else { + allowDirectoryListing = 0; + } + } else if(strcasecmp(name, "ssl") == 0) { + no_value_required(name, value); + do_ssl = 1; + } else if(strcasecmp(name, "certfile") == 0) { + value_required(name, value); + certfile = e_strdup(value); + } else if(strcasecmp(name, "cipher") == 0) { + value_required(name, value); + cipher = e_strdup(value); + } else { + (void) fprintf(stderr, "%s: unknown config option '%s'\n", argv0, name); + exit(1); + } + cp = cp2; + cp += strspn(cp, " \t\012\015"); + } + } + (void) fclose(fp); +} + +static int really_check_referer(void) { + char* cp1; + char* cp2; + char* cp3; + char* refhost; + char *lp; + + if(referer == (char*) 0 || referer[0] == '\0' || (cp1 = strstr(referer, "//")) == (char*) 0) { + if(no_empty_referers && match(url_pattern, path)) { + return 0; + } else { + return 1; + } + } + cp1 += 2; + for(cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2) { + continue; + } + refhost = (char*) e_malloc(cp2 - cp1 + 1); + for(cp3 = refhost; cp1 < cp2; ++cp1, ++cp3) { + if(isupper(*cp1)) { + *cp3 = tolower(*cp1); + } else { + *cp3 = *cp1; + } + } + *cp3 = '\0'; + if(local_pattern != (char*) 0) { + lp = local_pattern; + } else { + lp = hostname; + if(lp == (char*) 0) { + return 1; + } + } + if(!match(lp, refhost) && match(url_pattern, path)) { + return 0; + } else { + return 1; + } +} + +static void reopen_logfile(void) { + if(logfp != (FILE*) 0) { + (void) fclose(logfp); + logfp = (FILE*) 0; + } + if(logfile != (char*) 0) { + syslog(LOG_NOTICE, "re-opening logfile"); + logfp = fopen(logfile, "a"); + if(logfp == (FILE*) 0) { + syslog(LOG_CRIT, "%s - %m", logfile); + perror(logfile); + exit(1); + } + } +} + +static void send_authenticate(char* realm, int is_ssl) { + char header[10000]; + + (void) snprintf(header, sizeof(header), "WWW-Authenticate: Basic realm=\"%s\"", realm); + send_error(401, "Unauthorized", header, "Authorization required.", is_ssl); +} + +static void send_error(int s, char* title, char* extra_header, char* text, int is_ssl) { + add_headers(s, title, extra_header, "", "text/html; charset=%s", (off_t) -1, (time_t) -1); + send_error_body(s, title, text); + send_error_tail(); + send_response(is_ssl); + SSL_free(ssl); + exit(1); +} + +static void send_error_body(int s, char* title, char* text) { + char filename[1000]; + char buf[10000]; + int buflen; + + (void) snprintf(filename, sizeof(filename), "%s/err%d.html", ERR_DIR, s); + if(send_error_file(filename)) { + return; + } + buflen = snprintf(buf, sizeof(buf), "\nError %d - %s\n\n

Error %d

\n", s, title, s); + add_to_response(buf, buflen); + buflen = snprintf(buf, sizeof(buf), "%s\n", text); + add_to_response(buf, buflen); +} + +static int send_error_file(char* filename) { + FILE* fp; + char buf[1000]; + size_t r; + + fp = fopen(filename, "r"); + if(fp == (FILE*) 0) { + return 0; + } + for(;;) { + r = fread(buf, 1, sizeof(buf), fp); + if(r == 0) { + break; + } + add_to_response(buf, r); + } + (void) fclose(fp); + return 1; +} + +static void send_error_tail(void) { + char buf[500]; + int buflen; + + if(match("**MSIE**", useragent)) { + int n; + buflen = snprintf(buf, sizeof(buf), "\n"); + add_to_response(buf, buflen); + } + buflen = snprintf(buf, sizeof(buf), "
\n%s\n\n\n", SERVER_SOFTWARE); + add_to_response(buf, buflen); +} + +static void send_redirect(char* extra_header, char* hostname, char* new_location, int is_ssl) { + int extra_length = 0; + char extra_header_buf[5000]; + const char *sep = new_location[0] == '/' ? "" : "/"; + const char *proto = is_ssl == 1 ? "https://" : "http://"; + + extra_header = extra_header == NULL ? "" : extra_header; + if(strcmp(extra_header, "") == 0) { + sprintf(extra_header_buf, "Location: %s%s%s%s", proto, hostname, sep, new_location); + } else { + sprintf(extra_header_buf, "%s\r\nLocation: %s%s%s%s", extra_header, proto, hostname, sep, new_location); + } + add_headers(301, "Moved Permanently", extra_header_buf, "", "text/html; charset=%s", (off_t) -1, (time_t) -1); + send_error_body(301, "Moved Permanently", "Moved Permanently"); + send_error_tail(); + send_response(is_ssl); + SSL_free(ssl); + exit(1); +} + +static void send_response(is_ssl) { + (void) my_write(response, response_len, is_ssl); +} + +static void send_via_write(int fd, off_t size, int is_ssl) { + if(size <= SIZE_T_MAX) { + size_t size_size = (size_t) size; + void* ptr = mmap(0, size_size, PROT_READ, MAP_PRIVATE, fd, 0); + if(ptr != (void*) -1) { + (void) my_write(ptr, size_size, is_ssl); + (void) munmap(ptr, size_size); + } +#ifdef MADV_SEQUENTIAL + (void) madvise(ptr, size_size, MADV_SEQUENTIAL); +#endif + } else { + char buf[30000]; + ssize_t r, r2; + for(;;) { + r = read(fd, buf, sizeof(buf)); + if(r < 0 && (errno == EINTR || errno == EAGAIN)) { + sleep(1); + continue; + } + if(r <= 0) { + return; + } + for(;;) { + r2 = my_write(buf, r, is_ssl); + if(r2 < 0 && (errno == EINTR || errno == EAGAIN)) { + sleep(1); + continue; + } + if(r2 != r) { + return; + } + break; + } + } + } +} + +static void set_ndelay(int fd) { + int flags, newflags; + + flags = fcntl(fd, F_GETFL, 0); + if(flags != -1) { + newflags = flags | (int) O_NDELAY; + if(newflags != flags) { + (void) fcntl(fd, F_SETFL, newflags); + } + } +} + +static int sockaddr_check(usockaddr* usaP) { + switch (usaP->sa.sa_family) { + case AF_INET: + return 1; + case AF_INET6: + return 1; + default: + return 0; + } +} + +static size_t sockaddr_len(usockaddr* usaP) { + switch(usaP->sa.sa_family) { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + default: + return 0; + } +} + +static void start_request(void) { + request_size = 0; + request_idx = 0; +} + +static void start_response(void) { + response_size = 0; +} + +static void strdecode(char* to, char* from) { + for(; *from != '\0'; ++to, ++from) { + if(from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) { + *to = hexit(from[1]) * 16 + hexit(from[2]); + from += 2; + } else { + *to = *from; + } + } + *to = '\0'; +} + +static void strencode(char* to, size_t tosize, const char* from) { + int tolen; + + for(tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) { + if(isalnum(*from) || strchr("/_.-~", *from) != (char*) 0) { + *to = *from; + ++to; + ++tolen; + } else { + (void) sprintf(to, "%%%02x", (int) *from & 0xff); + to += 3; + tolen += 3; + } + } + *to = '\0'; +} + +static void usage(void) { + (void) fprintf(stderr, "Usage: %s [-C configfile] [-D] [-S use ssl, if no ssl port is specified all connections will be SSL] [-E certfile] [-SP ssl port] [-Y cipher] [-p port] [-d dir] [-dd data_dir] [-c cgipat] [-u user] [-h hostname] [-l logfile] [-i pidfile] [-T charset] [-L language] [-P P3P] [-M maxage] [-DRN default realm name] [-DRP default realm password file] [-DPF default page file] [-PNF Page to load when 404 Not Found error occurs] \n", argv0); + exit(1); +} + +static void value_required(char* name, char* value) { + if(value == (char*) 0) { + (void) fprintf(stderr, "%s: value required for %s option\n", argv0, name); + exit(1); + } +} diff --git a/kuhttpd.cnf b/kuhttpd.cnf new file mode 100644 index 0000000..9b405e3 --- /dev/null +++ b/kuhttpd.cnf @@ -0,0 +1,23 @@ +RANDFILE = kuhttpd.rnd + +[req] +default_bits = 1024 +encrypt_key = yes +distinguished_name = req_dn +x509_extensions = cert_type + +[req_dn] +countryName = Country Name (2 letter code) +countryName_default = EU +countryName_min = 2 +countryName_max = 2 +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = European Union +localityName = European Union +organizationName = Organization Name (eg, company) +organizationName_default = Asio Software Technologies +commonName = Common Name (FQDN of your server) +commonName_default = Kagera Router Management Utility + +[cert_type] +nsCertType = server diff --git a/kuhttpd.h b/kuhttpd.h new file mode 100644 index 0000000..7a26bd9 --- /dev/null +++ b/kuhttpd.h @@ -0,0 +1,198 @@ +/** + * @PROJECT Kagera uHTTP Daemon + * @COPYRIGHT See COPYING in the top level directory + * @FILE kuhttpd.h + * @PURPOSE HTTP Server + * @DEVELOPERS Rafal Kupiec + * Jef Poskanzer + */ + +#ifndef __KUHTTPD_H +#define __KUHTTPD_H + +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 +#define SIZE_T_MAX 2147483647L + +#define ERR_DIR "errors" +#define DEFAULT_HTTP_PORT 80 +#define DEFAULT_HTTPS_PORT 443 +#define DEFAULT_CERTFILE "kuhttpd.pem" +#define DEFAULT_CHARSET "UTF-8" +#define DEFAULT_LANGUAGE "english" +#define DEFAULT_CGI_PATTERN "cgi-bin/**|**.cgi|**.sh" +#define DEFAULT_USER "nobody" +#define CGI_NICE 10 +#define CGI_PATH "/usr/local/bin:/usr/ucb:/bin:/usr/bin:/usr/local/sbin:/sbin:/usr/sbin" +#define CGI_LD_LIBRARY_PATH "/usr/local/lib:/usr/lib:/lib" +#define AUTH_FILE ".htpasswd" +#define READ_TIMEOUT 60 +#define WRITE_TIMEOUT 300 + +#define METHOD_UNKNOWN 0 +#define METHOD_GET 1 +#define METHOD_HEAD 2 +#define METHOD_POST 3 + +#define SERVER_SOFTWARE "Kagera uHTTP Daemon 1.4" + +struct mime_entry { + char* ext; + size_t ext_len; + char* val; + size_t val_len; +}; + +typedef union { + struct sockaddr sa; + struct sockaddr_in sa_in; + struct sockaddr_in6 sa_in6; + struct sockaddr_storage sa_stor; +} usockaddr; + +static int b64_decode_table[256] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +}; + +static unsigned char allowDirectoryListing; +static char* argv0; +static char* data_dir; +static char* certfile; +static char* cgi_pattern; +static char* charset; +static char* language; +static char* cipher; +static char cwd[MAXPATHLEN]; +static int debug; +static char* defaultPageFile; +static char* defaultRealmName; +static char* defaultRealmPasswordFile; +static char* dir; +static int do_ssl; +static int got_hup; +static char* hostname; +static char hostname_buf[500]; +static int listen4_fd, listen6_fd; +static int listen4s_fd, listen6s_fd; +static char* local_pattern; +static char* logfile; +static FILE* logfp; +static int max_age; +static int no_empty_referers; +static char* p3p; +static char* pageNotFoundFile; +static char* pidfile; +static unsigned short port; +static SSL_CTX* ssl_ctx; +static unsigned short sslPort; +static char* url_pattern; +static char* user; + +static off_t bytes; +static usockaddr client_addr; +static int conn_fd; +static char* file; +static int method; +static char* path; +static char* pathinfo; +static char* protocol; +static char* query; +static char* request; +static size_t request_size, request_len, request_idx; +struct stat sb; +static SSL* ssl; +static int status; + +static char* authorization; +static size_t content_length; +static char* content_type; +static char* cookie; +static char* host; +static time_t if_modified_since; +static char* referer; +static char* remoteuser; +static char* response; +static size_t response_size, response_len; +static char* useragent; + +static void add_headers(int s, char* title, char* extra_header, char* me, char* mt, off_t b, time_t mod); +static void add_to_buf(char** bufP, size_t* bufsizeP, size_t* buflenP, char* str, size_t len); +static void add_to_request(char* str, size_t len); +static void add_to_response(char* str, size_t len); +static void auth_check(char* dirname, int is_ssl); +static int b64_decode(const char* str, unsigned char* space, int size); +static char* build_env(char* fmt, char* arg); +static void cgi_interpose_input(int wfd, int is_ssl); +static void cgi_interpose_output(int rfd, int parse_headers, int is_ssl); +static void check_referer(int is_ssl); +static void clear_ndelay(int fd); +extern char* crypt(const char* key, const char* setting); +static void de_dotdot(char* file); +static void do_cgi(int is_ssl, unsigned short port); +static void do_dir(int is_ssl); +static void do_file(int is_ssl, unsigned short conn_port); +static void* e_malloc(size_t size); +static void* e_realloc(void* optr, size_t size); +static char* e_strdup(char* ostr); +static const char* figure_mime(char* name, char* me, size_t me_size); +static char* file_details(const char* dir, const char* name); +static char* get_method_str(int m); +static int get_pathinfo(void); +static char* get_request_line(void); +static void handle_read_timeout(int sig, int is_ssl); +static void handle_read_timeout_sig(int sig); +static void handle_request(int is_ssl, unsigned short conn_port); +static void handle_sigchld(int sig); +static void handle_sighup(int sig); +static void handle_sigterm(int sig); +static void handle_write_timeout(int sig); +static int hexit(char c); +static void init_mime(void); +static int initialize_listen_socket(usockaddr* usaP); +static void lookup_hostname(usockaddr* usa4P, usockaddr* usa4sP, size_t sa4_len, int* gotv4P, int* gotv4sP, usockaddr* usa6P, usockaddr* usa6sP, size_t sa6_len, int* gotv6P, int* gotv6sP); +static char** make_argp(void); +static char** make_envp(int is_sl, unsigned short port); +static void make_log_entry(void); +static char* ntoa(usockaddr* usaP); +static ssize_t my_read(char* buf, size_t size, int is_ssl); +static ssize_t my_write(char* buf, size_t size, int is_ssl); +static void no_value_required(char* name, char* value); +static void post_post_garbage_hack(int is_ssl); +static void read_config(char* filename); +static int really_check_referer(void); +static void reopen_logfile(void); +static void send_authenticate(char* realm, int is_ssl); +static void send_error(int s, char* title, char* extra_header, char* text, int is_ssl); +static void send_error_body(int s, char* title, char* text); +static int send_error_file(char* filename); +static void send_error_tail(void); +static void send_redirect(char* extra_header, char* hostname, char* new_location, int is_ssl); +static void send_response(int is_ssl); +static void send_via_write(int fd, off_t size, int is_ssl); +static void set_ndelay(int fd); +static int sockaddr_check(usockaddr* usaP); +static size_t sockaddr_len(usockaddr* usaP); +static void start_request(void); +static void start_response(void); +static void strdecode(char* to, char* from); +static void strencode(char* to, size_t tosize, const char* from); +static void usage(void); +static void value_required(char* name, char* value); + +#endif diff --git a/match.c b/match.c new file mode 100644 index 0000000..e14ff41 --- /dev/null +++ b/match.c @@ -0,0 +1,60 @@ +/** + * @PROJECT Kagera uHTTP Daemon + * @COPYRIGHT See COPYING in the top level directory + * @FILE match.c + * @PURPOSE Simple shell-style filename matcher + * @DEVELOPERS Rafal Kupiec + * Jef Poskanzer + */ + +#include + +#include "match.h" + +int match(const char* pattern, const char* string) { + const char* or; + + for(;;) { + or = strchr( pattern, '|' ); + if(or == (char*) 0) { + return match_one(pattern, strlen(pattern), string); + } + if(match_one( pattern, or - pattern, string)) { + return 1; + } + pattern = or + 1; + } +} + +static int match_one(const char* pattern, int patternlen, const char* string) { + const char* p; + for(p = pattern; p - pattern < patternlen; ++p, ++string) { + if(*p == '?' && *string != '\0') { + continue; + } + if(*p == '*') { + int i, pl; + ++p; + if(*p == '*') { + ++p; + i = strlen(string); + } else { + i = strcspn(string, "/"); + } + pl = patternlen - (p - pattern); + for(; i >= 0; --i) { + if(match_one(p, pl, &(string[i]))) { + return 1; + } + } + return 0; + } + if(*p != *string) { + return 0; + } + } + if(*string == '\0') { + return 1; + } + return 0; +} diff --git a/match.h b/match.h new file mode 100644 index 0000000..7fe8eb7 --- /dev/null +++ b/match.h @@ -0,0 +1,16 @@ +/** + * @PROJECT Kagera uHTTP Daemon + * @COPYRIGHT See COPYING in the top level directory + * @FILE match.h + * @PURPOSE Simple shell-style filename matcher + * @DEVELOPERS Rafal Kupiec + * Jef Poskanzer + */ + +#ifndef __MATCH_H +#define __MATCH_H + +extern int match(const char* pattern, const char* string); +static int match_one(const char* pattern, int patternlen, const char* string); + +#endif diff --git a/mime_enc.h b/mime_enc.h new file mode 100644 index 0000000..26d608b --- /dev/null +++ b/mime_enc.h @@ -0,0 +1,20 @@ +/** + * @PROJECT Kagera uHTTP Daemon + * @COPYRIGHT See COPYING in the top level directory + * @FILE mime_enc.h + * @PURPOSE MIME encodings list + * @DEVELOPERS Rafal Kupiec + */ + +#ifndef __MIME_ENC_H +#define __MIME_ENC_H + +static struct mime_entry enc_tab[] = { + { "Z", 0, "compress", 0 }, + { "gz", 0, "gzip", 0 }, + { "uu", 0, "x-uuencode", 0 }, +}; + +static const int n_enc_tab = sizeof(enc_tab) / sizeof(*enc_tab); + +#endif diff --git a/mime_typ.h b/mime_typ.h new file mode 100644 index 0000000..4ea653c --- /dev/null +++ b/mime_typ.h @@ -0,0 +1,207 @@ +/** + * @PROJECT Kagera uHTTP Daemon + * @COPYRIGHT See COPYING in the top level directory + * @FILE mime_typ.h + * @PURPOSE MIME types list + * @DEVELOPERS Rafal Kupiec + */ + +#ifndef __MIME_TYP_H +#define __MIME_TYP_H + +static struct mime_entry typ_tab[] = { + { "a", 0, "application/octet-stream", 0 }, + { "aab", 0, "application/x-authorware-bin", 0 }, + { "aam", 0, "application/x-authorware-map", 0 }, + { "aas", 0, "application/x-authorware-seg", 0 }, + { "ai", 0, "application/postscript", 0 }, + { "aif", 0, "audio/x-aiff", 0 }, + { "aifc", 0, "audio/x-aiff", 0 }, + { "aiff", 0, "audio/x-aiff", 0 }, + { "asc", 0, "text/plain", 0 }, + { "asf", 0, "video/x-ms-asf", 0 }, + { "asx", 0, "video/x-ms-asf", 0 }, + { "au", 0, "audio/basic", 0 }, + { "avi", 0, "video/x-msvideo", 0 }, + { "bcpio", 0, "application/x-bcpio", 0 }, + { "bin", 0, "application/octet-stream", 0 }, + { "bmp", 0, "image/bmp", 0 }, + { "cdf", 0, "application/x-netcdf", 0 }, + { "class", 0, "application/x-java-vm", 0 }, + { "cpio", 0, "application/x-cpio", 0 }, + { "cpt", 0, "application/mac-compactpro", 0 }, + { "crl", 0, "application/x-pkcs7-crl", 0 }, + { "crt", 0, "application/x-x509-ca-cert", 0 }, + { "csh", 0, "application/x-csh", 0 }, + { "css", 0, "text/css", 0 }, + { "dcr", 0, "application/x-director", 0 }, + { "dir", 0, "application/x-director", 0 }, + { "djv", 0, "image/vnd.djvu", 0 }, + { "djvu", 0, "image/vnd.djvu", 0 }, + { "dll", 0, "application/octet-stream", 0 }, + { "dms", 0, "application/octet-stream", 0 }, + { "doc", 0, "application/msword", 0 }, + { "dtd", 0, "text/xml", 0 }, + { "dump", 0, "application/octet-stream", 0 }, + { "dvi", 0, "application/x-dvi", 0 }, + { "dxr", 0, "application/x-director", 0 }, + { "eps", 0, "application/postscript", 0 }, + { "etx", 0, "text/x-setext", 0 }, + { "exe", 0, "application/octet-stream", 0 }, + { "ez", 0, "application/andrew-inset", 0 }, + { "fgd", 0, "application/x-director", 0 }, + { "fh", 0, "image/x-freehand", 0 }, + { "fh4", 0, "image/x-freehand", 0 }, + { "fh5", 0, "image/x-freehand", 0 }, + { "fh7", 0, "image/x-freehand", 0 }, + { "fhc", 0, "image/x-freehand", 0 }, + { "gif", 0, "image/gif", 0 }, + { "gtar", 0, "application/x-gtar", 0 }, + { "hdf", 0, "application/x-hdf", 0 }, + { "hqx", 0, "application/mac-binhex40", 0 }, + { "htm", 0, "text/html; charset=%s", 0 }, + { "html", 0, "text/html; charset=%s", 0 }, + { "ice", 0, "x-conference/x-cooltalk", 0 }, + { "ief", 0, "image/ief", 0 }, + { "iges", 0, "model/iges", 0 }, + { "igs", 0, "model/iges", 0 }, + { "iv", 0, "application/x-inventor", 0 }, + { "jar", 0, "application/x-java-archive", 0 }, + { "jfif", 0, "image/jpeg", 0 }, + { "jpe", 0, "image/jpeg", 0 }, + { "jpeg", 0, "image/jpeg", 0 }, + { "jpg", 0, "image/jpeg", 0 }, + { "js", 0, "application/x-javascript", 0 }, + { "kar", 0, "audio/midi", 0 }, + { "latex", 0, "application/x-latex", 0 }, + { "lha", 0, "application/octet-stream", 0 }, + { "lzh", 0, "application/octet-stream", 0 }, + { "m3u", 0, "audio/x-mpegurl", 0 }, + { "man", 0, "application/x-troff-man", 0 }, + { "mathml", 0, "application/mathml+xml", 0 }, + { "me", 0, "application/x-troff-me", 0 }, + { "mesh", 0, "model/mesh", 0 }, + { "mid", 0, "audio/midi", 0 }, + { "midi", 0, "audio/midi", 0 }, + { "mif", 0, "application/vnd.mif", 0 }, + { "mime", 0, "message/rfc822", 0 }, + { "mml", 0, "application/mathml+xml", 0 }, + { "mov", 0, "video/quicktime", 0 }, + { "movie", 0, "video/x-sgi-movie", 0 }, + { "mp2", 0, "audio/mpeg", 0 }, + { "mp3", 0, "audio/mpeg", 0 }, + { "mp4", 0, "video/mp4", 0 }, + { "mpe", 0, "video/mpeg", 0 }, + { "mpeg", 0, "video/mpeg", 0 }, + { "mpg", 0, "video/mpeg", 0 }, + { "mpga", 0, "audio/mpeg", 0 }, + { "ms", 0, "application/x-troff-ms", 0 }, + { "msh", 0, "model/mesh", 0 }, + { "mv", 0, "video/x-sgi-movie", 0 }, + { "mxu", 0, "video/vnd.mpegurl", 0 }, + { "nc", 0, "application/x-netcdf", 0 }, + { "o", 0, "application/octet-stream", 0 }, + { "oda", 0, "application/oda", 0 }, + { "ogg", 0, "application/x-ogg", 0 }, + { "pac", 0, "application/x-ns-proxy-autoconfig", 0 }, + { "pbm", 0, "image/x-portable-bitmap", 0 }, + { "pdb", 0, "chemical/x-pdb", 0 }, + { "pdf", 0, "application/pdf", 0 }, + { "pgm", 0, "image/x-portable-graymap", 0 }, + { "pgn", 0, "application/x-chess-pgn", 0 }, + { "png", 0, "image/png", 0 }, + { "pnm", 0, "image/x-portable-anymap", 0 }, + { "ppm", 0, "image/x-portable-pixmap", 0 }, + { "ppt", 0, "application/vnd.ms-powerpoint", 0 }, + { "ps", 0, "application/postscript", 0 }, + { "qt", 0, "video/quicktime", 0 }, + { "ra", 0, "audio/x-realaudio", 0 }, + { "ram", 0, "audio/x-pn-realaudio", 0 }, + { "ras", 0, "image/x-cmu-raster", 0 }, + { "rdf", 0, "application/rdf+xml", 0 }, + { "rgb", 0, "image/x-rgb", 0 }, + { "rm", 0, "audio/x-pn-realaudio", 0 }, + { "roff", 0, "application/x-troff", 0 }, + { "rpm", 0, "audio/x-pn-realaudio-plugin", 0 }, + { "rss", 0, "application/rss+xml", 0 }, + { "rtf", 0, "text/rtf", 0 }, + { "rtx", 0, "text/richtext", 0 }, + { "sgm", 0, "text/sgml", 0 }, + { "sgml", 0, "text/sgml", 0 }, + { "sh", 0, "application/x-sh", 0 }, + { "shar", 0, "application/x-shar", 0 }, + { "silo", 0, "model/mesh", 0 }, + { "sit", 0, "application/x-stuffit", 0 }, + { "skd", 0, "application/x-koan", 0 }, + { "skm", 0, "application/x-koan", 0 }, + { "skp", 0, "application/x-koan", 0 }, + { "skt", 0, "application/x-koan", 0 }, + { "smi", 0, "application/smil", 0 }, + { "smil", 0, "application/smil", 0 }, + { "snd", 0, "audio/basic", 0 }, + { "so", 0, "application/octet-stream", 0 }, + { "spl", 0, "application/x-futuresplash", 0 }, + { "src", 0, "application/x-wais-source", 0 }, + { "stc", 0, "application/vnd.sun.xml.calc.template", 0 }, + { "std", 0, "application/vnd.sun.xml.draw.template", 0 }, + { "sti", 0, "application/vnd.sun.xml.impress.template", 0 }, + { "stw", 0, "application/vnd.sun.xml.writer.template", 0 }, + { "sv4cpio", 0, "application/x-sv4cpio", 0 }, + { "sv4crc", 0, "application/x-sv4crc", 0 }, + { "svg", 0, "image/svg+xml", 0 }, + { "svgz", 0, "image/svg+xml", 0 }, + { "swf", 0, "application/x-shockwave-flash", 0 }, + { "sxc", 0, "application/vnd.sun.xml.calc", 0 }, + { "sxd", 0, "application/vnd.sun.xml.draw", 0 }, + { "sxg", 0, "application/vnd.sun.xml.writer.global", 0 }, + { "sxi", 0, "application/vnd.sun.xml.impress", 0 }, + { "sxm", 0, "application/vnd.sun.xml.math", 0 }, + { "sxw", 0, "application/vnd.sun.xml.writer", 0 }, + { "t", 0, "application/x-troff", 0 }, + { "tar", 0, "application/x-tar", 0 }, + { "tcl", 0, "application/x-tcl", 0 }, + { "tex", 0, "application/x-tex", 0 }, + { "texi", 0, "application/x-texinfo", 0 }, + { "texinfo", 0, "application/x-texinfo", 0 }, + { "tif", 0, "image/tiff", 0 }, + { "tiff", 0, "image/tiff", 0 }, + { "tr", 0, "application/x-troff", 0 }, + { "tsp", 0, "application/dsptype", 0 }, + { "tsv", 0, "text/tab-separated-values", 0 }, + { "txt", 0, "text/plain; charset=%s", 0 }, + { "ustar", 0, "application/x-ustar", 0 }, + { "vcd", 0, "application/x-cdlink", 0 }, + { "vrml", 0, "model/vrml", 0 }, + { "vx", 0, "video/x-rad-screenplay", 0 }, + { "wav", 0, "audio/x-wav", 0 }, + { "wax", 0, "audio/x-ms-wax", 0 }, + { "wbmp", 0, "image/vnd.wap.wbmp", 0 }, + { "wbxml", 0, "application/vnd.wap.wbxml", 0 }, + { "wm", 0, "video/x-ms-wm", 0 }, + { "wma", 0, "audio/x-ms-wma", 0 }, + { "wmd", 0, "application/x-ms-wmd", 0 }, + { "wml", 0, "text/vnd.wap.wml", 0 }, + { "wmlc", 0, "application/vnd.wap.wmlc", 0 }, + { "wmls", 0, "text/vnd.wap.wmlscript", 0 }, + { "wmlsc", 0, "application/vnd.wap.wmlscriptc", 0 }, + { "wmv", 0, "video/x-ms-wmv", 0 }, + { "wmx", 0, "video/x-ms-wmx", 0 }, + { "wmz", 0, "application/x-ms-wmz", 0 }, + { "wrl", 0, "model/vrml", 0 }, + { "wsrc", 0, "application/x-wais-source", 0 }, + { "wvx", 0, "video/x-ms-wvx", 0 }, + { "xbm", 0, "image/x-xbitmap", 0 }, + { "xht", 0, "application/xhtml+xml", 0 }, + { "xhtml", 0, "application/xhtml+xml", 0 }, + { "xls", 0, "application/vnd.ms-excel", 0 }, + { "xml", 0, "text/xml", 0 }, + { "xpm", 0, "image/x-xpixmap", 0 }, + { "xsl", 0, "text/xml", 0 }, + { "xwd", 0, "image/x-xwindowdump", 0 }, + { "xyz", 0, "chemical/x-xyz", 0 }, + { "zip", 0, "application/zip", 0 }, +}; + +static const int n_typ_tab = sizeof(typ_tab) / sizeof(*typ_tab); + +#endif