--- /dev/null
+OBJS := http.o main.o request.o ssl.o static.o tcp_sockets.o webserver.o ctdlclient.o admin_functions.o room_functions.o util.o caldav_reports.o messages.o ctdlfunctions.o ctdl_commands.o threaded_view.o html2html.o text2html.o
+CFLAGS := -ggdb
+LDFLAGS :=
+
+# link
+webcit: $(OBJS)
+ gcc $(OBJS) $(LDFLAGS) -lcitadel -lpthread -lcrypto -lssl -lexpat -o webcit
+
+# pull in dependency info for *existing* .o files
+-include $(OBJS:.o=.d)
+
+# compile and generate dependency info
+%.o: %.c
+ gcc -c $(CFLAGS) $*.c -o $*.o
+ gcc -MM $(CFLAGS) $*.c > $*.d
+
+# remove compilation products
+clean:
+ rm -f webcit *.o *.d
+
+distclean: clean
+
+# install to target directory
+install:
+ echo Not yet...
--- /dev/null
+
+
+This is WebCit-NG, a complete refactoring of the WebCit server that will
+focus on "REST first" and build around that. The code will be well
+layered with as little spaghetti as possible.
+
+Please don't mess with this yet. I'm only pushing it upstream so it gets
+backed up.
+
+
+DESIGN GOALS:
+-------------
+
+* Hold as little state as possible
+
+* Require NO cleanup. Killing the process lets the OS reclaim all resources.
+
+* As much as possible, resources should be freed by just coming back down the stack.
+
+* Readability of the code is more important than shaving off a few CPU cycles.
+
+* Throw sensitive data such as passwords back and forth in clear text.
+ If you want privacy, encrypt the whole session. Anything else is false security.
+
+
+
+
+REST format URIs will generally take the form of:
+
+ /ctdl/objectClass/[container/]object[/operation]
+
--- /dev/null
+/*
+ * Admin functions
+ *
+ * Copyright (c) 1996-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * /ctdl/a/login is called when a user is trying to log in
+ */
+void try_login(struct http_transaction *h, struct ctdlsession *c)
+{
+ char buf[1024];
+ char auth[AUTH_MAX];
+ char username[256];
+ char password[256];
+ int login_success = 0;
+
+ extract_token(username, h->request_body, 0, '|', sizeof username);
+ extract_token(password, h->request_body, 1, '|', sizeof password);
+
+ snprintf(buf, sizeof buf, "%s:%s", username, password);
+ CtdlEncodeBase64(auth, buf, strlen(buf), 0);
+
+ syslog(LOG_DEBUG, "try_login(username='%s',password=(%d bytes))", username, strlen(password));
+
+ ctdl_printf(c, "LOUT"); // log out, in case we were logged in
+ ctdl_readline(c, buf, sizeof(buf)); // ignore the result
+ memset(c->auth, 0, AUTH_MAX); // if this connection had auth, it doesn't now.
+ memset(c->whoami, 0, 64); // if this connection had auth, it doesn't now.
+
+ login_success = login_to_citadel(c, auth, buf); // Now try logging in to Citadel
+
+ h->response_code = 200; // 'buf' will contain the relevant response
+ h->response_string = strdup("OK");
+ add_response_header(h, strdup("Content-type"), strdup("text/plain"));
+ h->response_body = strdup(buf);
+ h->response_body_length = strlen(h->response_body);
+}
+
+
+/*
+ * /ctdl/a/logout is called when a user is trying to log out. Don't use this as an ajax.
+ */
+void logout(struct http_transaction *h, struct ctdlsession *c)
+{
+ char buf[1024];
+ char auth[AUTH_MAX];
+ char username[256];
+ char password[256];
+ int login_success = 0;
+
+ ctdl_printf(c, "LOUT"); // log out
+ ctdl_readline(c, buf, sizeof(buf)); // ignore the result
+ strcpy(c->auth, "x");
+ //memset(c->auth, 0, AUTH_MAX); // if this connection had auth, it doesn't now.
+ memset(c->whoami, 0, 64); // if this connection had auth, it doesn't now.
+
+ http_redirect(h, "/ctdl/s/index.html"); // go back where we started :)
+}
+
+
+/*
+ * /ctdl/a/whoami returns the name of the currently logged in user, or an empty string if not logged in
+ */
+void whoami(struct http_transaction *h, struct ctdlsession *c)
+{
+ h->response_code = 200;
+ h->response_string = strdup("OK");
+ add_response_header(h, strdup("Content-type"), strdup("text/plain"));
+ h->response_body = strdup(c->whoami);
+ h->response_body_length = strlen(h->response_body);
+}
+
+
+/*
+ * Dispatcher for paths starting with /ctdl/a/
+ */
+void ctdl_a(struct http_transaction *h, struct ctdlsession *c)
+{
+ if (!strcasecmp(h->uri, "/ctdl/a/login")) { // log in
+ try_login(h, c);
+ return;
+ }
+
+ if (!strcasecmp(h->uri, "/ctdl/a/logout")) { // log out
+ logout(h, c);
+ return;
+ }
+
+ if (!strcasecmp(h->uri, "/ctdl/a/whoami")) { // return display name of user
+ whoami(h, c);
+ return;
+ }
+
+ do_404(h); // unknown
+}
--- /dev/null
+
+Method URI Function
+------ ------------------------------ -------------------------------------
+GET /ctdl/a/landing.html Site root redirects to here
+OPTIONS /ctdl/r/ROOMNAME/ returns just what you'd expect
+PROPFIND /ctdl/r/ROOMNAME/ Show a bunch of crap
+GET /ctdl/r/ROOMNAME/ Returns information about the room (name, view, etc.) in JSON format
+GET /ctdl/r/ROOMNAME/msgs.all JSON array of message list in room
+GET /ctdl/r/ROOMNAME/msgs.new JSON array of message list in room (new messages)
+GET /ctdl/c/info Returns a JSON representation of the output of an INFO server command
+POST /ctdl/a/login
+GET /ctdl/a/whoami
--- /dev/null
+/*
+ * This file contains functions which handle all of the CalDAV "REPORT" queries
+ * specified in RFC4791 section 7.
+ *
+ * Copyright (c) 2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * A CalDAV REPORT can only be one type. This is stored in the report_type member.
+ */
+enum cr_type
+{
+ cr_calendar_query,
+ cr_calendar_multiget,
+ cr_freebusy_query
+};
+
+
+/*
+ * Data type for CalDAV Report Parameters.
+ * As we slog our way through the XML we learn what the client is asking for
+ * and build up the contents of this data type.
+ */
+struct cr_parms {
+ int tag_nesting_level; // not needed, just kept for pretty-printing
+ enum cr_type report_type; // which RFC4791 section 7 REPORT are we generating
+ StrBuf *Chardata; // XML chardata in between tags is built up here
+ StrBuf *Hrefs; // list of items requested by a calendar-multiget report
+};
+
+
+/*
+ * XML parser callback
+ */
+void caldav_xml_start(void *data, const char *el, const char **attr)
+{
+ struct cr_parms *crp = (struct cr_parms *)data;
+ int i;
+
+ // syslog(LOG_DEBUG, "CALDAV ELEMENT START: <%s> %d", el, crp->tag_nesting_level);
+
+ for (i=0; attr[i] != NULL; i+=2) {
+ syslog(LOG_DEBUG, " Attribute '%s' = '%s'", attr[i], attr[i+1]);
+ }
+
+ if (!strcasecmp(el, "urn:ietf:params:xml:ns:caldav:calendar-multiget")) {
+ crp->report_type = cr_calendar_multiget;
+ }
+
+ else if (!strcasecmp(el, "urn:ietf:params:xml:ns:caldav:calendar-query")) {
+ crp->report_type = cr_calendar_query;
+ }
+
+ else if (!strcasecmp(el, "urn:ietf:params:xml:ns:caldav:free-busy-query")) {
+ crp->report_type = cr_freebusy_query;
+ }
+
+ ++crp->tag_nesting_level;
+}
+
+
+/*
+ * XML parser callback
+ */
+void caldav_xml_end(void *data, const char *el)
+{
+ struct cr_parms *crp = (struct cr_parms *)data;
+ --crp->tag_nesting_level;
+
+ if (crp->Chardata != NULL) {
+ // syslog(LOG_DEBUG, "CALDAV CHARDATA : %s", ChrPtr(crp->Chardata));
+ }
+ // syslog(LOG_DEBUG, "CALDAV ELEMENT END : <%s> %d", el, crp->tag_nesting_level);
+
+ if ( (!strcasecmp(el, "DAV::href")) || (!strcasecmp(el, "DAV:href")) ) {
+ if (crp->Hrefs == NULL) { // append crp->Chardata to crp->Hrefs
+ crp->Hrefs = NewStrBuf();
+ }
+ else {
+ StrBufAppendBufPlain(crp->Hrefs, HKEY("|"), 0);
+ }
+ StrBufAppendBuf(crp->Hrefs, crp->Chardata, 0);
+ }
+
+ if (crp->Chardata != NULL) { // Tag is closed; chardata is now out of scope.
+ FreeStrBuf(&crp->Chardata); // Free the buffer.
+ crp->Chardata = NULL;
+ }
+}
+
+
+/*
+ * XML parser callback
+ */
+void caldav_xml_chardata(void *data, const XML_Char *s, int len)
+{
+ struct cr_parms *crp = (struct cr_parms *)data;
+
+ if (crp->Chardata == NULL) {
+ crp->Chardata = NewStrBuf();
+ }
+
+ StrBufAppendBufPlain(crp->Chardata, s, len, 0);
+
+ return;
+}
+
+
+/*
+ * Called by caldav_response() to fetch a message (by number) in the current room,
+ * and return only the icalendar data as a StrBuf. Returns NULL if not found.
+ *
+ * NOTE: this function expects that "MSGP text/calendar" was issued at the beginning
+ * of a REPORT operation to set our preferred MIME type to calendar data.
+ */
+StrBuf *fetch_ical(struct ctdlsession *c, long msgnum)
+{
+ char buf[1024];
+ StrBuf *Buf = NULL;
+
+ ctdl_printf(c, "MSG4 %ld", msgnum);
+ ctdl_readline(c, buf, sizeof(buf));
+ if (buf[0] != '1') {
+ return NULL;
+ }
+
+ while (ctdl_readline(c, buf, sizeof(buf)), strcmp(buf, "000")) {
+ if (Buf != NULL) { // already in body
+ StrBufAppendPrintf(Buf, "%s\n", buf);
+ }
+ else if (IsEmptyStr(buf)) { // beginning of body
+ Buf = NewStrBuf();
+ }
+ }
+
+ return Buf;
+
+// webcit[13039]: msgn=53CE87AF-00392161@uncensored.citadel.org
+// webcit[13039]: path=IGnatius T Foobar
+// webcit[13039]: time=1208008800
+// webcit[13039]: from=IGnatius T Foobar
+// webcit[13039]: room=0000000001.Calendar
+// webcit[13039]: node=uncnsrd
+// webcit[13039]: hnod=Uncensored
+// webcit[13039]: exti=040000008200E00074C5B7101A82E0080000000080C728F83E84C801000000000000000010000000E857E0DC57F53947ADF0BB91EE3A502F
+// webcit[13039]: subj==?UTF-8?B?V2VzbGV5J3MgYmlydGhkYXkgcGFydHk=
+// webcit[13039]: ?=
+// webcit[13039]: part=||1||text/calendar|1127||
+// webcit[13039]: text
+// webcit[13039]: Content-type: text/calendar
+// webcit[13039]: Content-length: 1127
+// webcit[13039]: Content-transfer-encoding: 7bit
+// webcit[13039]: X-Citadel-MSG4-Partnum: 1
+// webcit[13039]:
+// webcit[13039]: BEGIN:VCALENDAR
+}
+
+
+/*
+ * Called by caldav_report() to output a single item.
+ * Our policy is to throw away the list of properties the client asked for, and just send everything.
+ */
+caldav_response(struct http_transaction *h, struct ctdlsession *c, StrBuf *ReportOut, StrBuf *ThisHref)
+{
+ long msgnum;
+ StrBuf *Caldata = NULL;
+ char *euid;
+
+ euid = strrchr(ChrPtr(ThisHref), '/');
+ if (euid != NULL) {
+ ++euid;
+ }
+ else {
+ euid = (char *)ChrPtr(ThisHref);
+ }
+
+ char *unescaped_euid = strdup(euid);
+ if (!unescaped_euid) return;
+ unescape_input(unescaped_euid);
+
+ StrBufAppendPrintf(ReportOut, "<D:response>");
+ StrBufAppendPrintf(ReportOut, "<D:href>");
+ StrBufXMLEscAppend(ReportOut, ThisHref, NULL, 0, 0);
+ StrBufAppendPrintf(ReportOut, "</D:href>");
+ StrBufAppendPrintf(ReportOut, "<D:propstat>");
+
+ msgnum = locate_message_by_uid(c, unescaped_euid);
+ free(unescaped_euid);
+ if (msgnum > 0) {
+ Caldata = fetch_ical(c, msgnum);
+ }
+
+ if (Caldata != NULL)
+ {
+ // syslog(LOG_DEBUG, "caldav_response(%s) 200 OK", ChrPtr(ThisHref));
+ StrBufAppendPrintf(ReportOut, "<D:status>");
+ StrBufAppendPrintf(ReportOut, "HTTP/1.1 200 OK");
+ StrBufAppendPrintf(ReportOut, "</D:status>");
+ StrBufAppendPrintf(ReportOut, "<D:prop>");
+ StrBufAppendPrintf(ReportOut, "<D:getetag>");
+ StrBufAppendPrintf(ReportOut, "%ld", msgnum);
+ StrBufAppendPrintf(ReportOut, "</D:getetag>");
+ StrBufAppendPrintf(ReportOut, "<C:calendar-data>");
+ StrBufXMLEscAppend(ReportOut, Caldata, NULL, 0, 0);
+ StrBufAppendPrintf(ReportOut, "</C:calendar-data>");
+ StrBufAppendPrintf(ReportOut, "</D:prop>");
+ FreeStrBuf(&Caldata);
+ Caldata = NULL;
+ }
+ else {
+ // syslog(LOG_DEBUG, "caldav_response(%s) 404 not found", ChrPtr(ThisHref));
+ StrBufAppendPrintf(ReportOut, "<D:status>");
+ StrBufAppendPrintf(ReportOut, "HTTP/1.1 404 not found");
+ StrBufAppendPrintf(ReportOut, "</D:status>");
+ }
+
+ StrBufAppendPrintf(ReportOut, "</D:propstat>");
+ StrBufAppendPrintf(ReportOut, "</D:response>");
+}
+
+
+/*
+ * Called by report_the_room_itself() in room_functions.c when a CalDAV REPORT method
+ * is requested on a calendar room. We fire up an XML Parser to decode the request and
+ * hopefully produce the correct output.
+ */
+void caldav_report(struct http_transaction *h, struct ctdlsession *c)
+{
+ struct cr_parms crp;
+ char buf[1024];
+
+ memset(&crp, 0, sizeof(struct cr_parms));
+
+ XML_Parser xp = XML_ParserCreateNS("UTF-8", ':');
+ if (xp == NULL) {
+ syslog(LOG_INFO, "Cannot create XML parser!");
+ do_404(h);
+ return;
+ }
+
+ XML_SetElementHandler(xp, caldav_xml_start, caldav_xml_end);
+ XML_SetCharacterDataHandler(xp, caldav_xml_chardata);
+ XML_SetUserData(xp, &crp);
+ XML_SetDefaultHandler(xp, NULL); // Disable internal entity expansion to prevent "billion laughs attack"
+ XML_Parse(xp, h->request_body, h->request_body_length, 1);
+ XML_ParserFree(xp);
+
+ if (crp.Chardata != NULL) { // Discard any trailing chardata ... normally nothing here
+ FreeStrBuf(&crp.Chardata);
+ crp.Chardata = NULL;
+ }
+
+ /*
+ * We're going to make a lot of MSG4 calls, and the preferred MIME type we want is "text/calendar".
+ * The iCalendar standard is mature now, and we are no longer interested in text/x-vcal or application/ics.
+ */
+ ctdl_printf(c, "MSGP text/calendar");
+ ctdl_readline(c, buf, sizeof buf);
+
+ /*
+ * Now begin the REPORT.
+ */
+ syslog(LOG_DEBUG, "CalDAV REPORT type is: %d", crp.report_type);
+ StrBuf *ReportOut = NewStrBuf();
+ StrBufAppendPrintf(ReportOut, "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<D:multistatus "
+ "xmlns:D=\"DAV:\" "
+ "xmlns:C=\"urn:ietf:params:xml:ns:caldav\""
+ ">"
+ );
+
+ if (crp.Hrefs != NULL) { // Output all qualifying calendar items!
+ StrBuf *ThisHref = NewStrBuf();
+ const char *pvset = NULL;
+ while (StrBufExtract_NextToken(ThisHref, crp.Hrefs, &pvset, '|') >= 0) {
+ caldav_response(h, c, ReportOut, ThisHref);
+ }
+ FreeStrBuf(&ThisHref);
+ FreeStrBuf(&crp.Hrefs);
+ crp.Hrefs = NULL;
+ }
+
+ StrBufAppendPrintf(ReportOut, "</D:multistatus>\n"); // End the REPORT.
+
+ add_response_header(h, strdup("Content-type"), strdup("text/xml"));
+ h->response_code = 207;
+ h->response_string = strdup("Multi-Status");
+ h->response_body_length = StrLength(ReportOut);
+ h->response_body = SmashStrBuf(&ReportOut);
+}
--- /dev/null
+/*
+ * Copyright (c) 1996-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * /ctdl/c/info returns a JSON representation of the output of an INFO command.
+ */
+void serv_info(struct http_transaction *h, struct ctdlsession *c)
+{
+ char buf[1024];
+
+ ctdl_printf(c, "INFO");
+ ctdl_readline(c, buf, sizeof(buf));
+ if (buf[0] != '1') {
+ do_502(h);
+ return;
+ }
+
+ JsonValue *j = NewJsonObject(HKEY("serv_info"));
+ int i = 0;
+ while (ctdl_readline(c, buf, sizeof(buf)) , strcmp(buf, "000")) switch(i++) {
+ case 0:
+ JsonObjectAppend(j, NewJsonNumber( HKEY("serv_pid"), atol(buf)));
+ break;
+ case 1:
+ JsonObjectAppend(j, NewJsonPlainString( HKEY("serv_nodename"), buf, -1));
+ break;
+ case 2:
+ JsonObjectAppend(j, NewJsonPlainString( HKEY("serv_humannode"), buf, -1));
+ break;
+ case 3:
+ JsonObjectAppend(j, NewJsonPlainString( HKEY("serv_fqdn"), buf, -1));
+ break;
+ case 4:
+ JsonObjectAppend(j, NewJsonPlainString( HKEY("serv_software"), buf, -1));
+ break;
+ case 5:
+ JsonObjectAppend(j, NewJsonNumber( HKEY("serv_rev_level"), atol(buf)));
+ break;
+ case 6:
+ JsonObjectAppend(j, NewJsonPlainString( HKEY("serv_bbs_city"), buf, -1));
+ break;
+ case 7:
+ JsonObjectAppend(j, NewJsonPlainString( HKEY("serv_sysadm"), buf, -1));
+ break;
+ case 14:
+ JsonObjectAppend(j, NewJsonBool( HKEY("serv_supports_ldap"), atoi(buf)));
+ break;
+ case 15:
+ JsonObjectAppend(j, NewJsonBool( HKEY("serv_newuser_disabled"), atoi(buf)));
+ break;
+ case 16:
+ JsonObjectAppend(j, NewJsonPlainString( HKEY("serv_default_cal_zone"), buf, -1));
+ break;
+ case 20:
+ JsonObjectAppend(j, NewJsonBool( HKEY("serv_supports_sieve"), atoi(buf)));
+ break;
+ case 21:
+ JsonObjectAppend(j, NewJsonBool( HKEY("serv_fulltext_enabled"), atoi(buf)));
+ break;
+ case 22:
+ JsonObjectAppend(j, NewJsonPlainString( HKEY("serv_svn_revision"), buf, -1));
+ break;
+ case 23:
+ JsonObjectAppend(j, NewJsonBool( HKEY("serv_supports_openid"), atoi(buf)));
+ break;
+ case 24:
+ JsonObjectAppend(j, NewJsonBool( HKEY("serv_supports_guest"), atoi(buf)));
+ break;
+ }
+
+ StrBuf *sj = NewStrBuf();
+ SerializeJson(sj, j, 1); // '1' == free the source array
+
+ add_response_header(h, strdup("Content-type"), strdup("application/json"));
+ h->response_code = 200;
+ h->response_string = strdup("OK");
+ h->response_body_length = StrLength(sj);
+ h->response_body = SmashStrBuf(&sj);
+}
+
+
+/*
+ * Dispatcher for paths starting with /ctdl/c/
+ */
+void ctdl_c(struct http_transaction *h, struct ctdlsession *c)
+{
+ if (!strcasecmp(h->uri, "/ctdl/c/info")) {
+ serv_info(h, c);
+ }
+ else {
+ do_404(h);
+ }
+}
--- /dev/null
+/*
+ * Functions that handle communication with a Citadel Server
+ *
+ * Copyright (c) 1987-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+struct ctdlsession *cpool = NULL; // linked list of connections to the Citadel server
+pthread_mutex_t cpool_mutex = PTHREAD_MUTEX_INITIALIZER; // Lock it before modifying
+
+
+/*
+ * Read a newline-terminated line of text from the Citadel server.
+ * Implemented in terms of client_read() and is therefore transparent...
+ * Returns the string length or -1 for error.
+ */
+int ctdl_readline(struct ctdlsession *ctdl, char *buf, int maxbytes)
+{
+ int len = 0;
+ int c = 0;
+
+ if (buf == NULL) return;
+
+ while (len < maxbytes) {
+ c = read(ctdl->sock, &buf[len], 1);
+ if (c <= 0) {
+ syslog(LOG_DEBUG, "Socket error or zero-length read");
+ return(-1);
+ }
+ if (buf[len] == '\n') {
+ if ( (len >0) && (buf[len-1] == '\r') ) {
+ --len;
+ }
+ buf[len] = 0;
+ // syslog(LOG_DEBUG, "[ %s", buf);
+ return(len);
+ }
+ ++len;
+ }
+ // syslog(LOG_DEBUG, "[ %s", buf);
+ return(len);
+}
+
+
+/*
+ * Read lines of text from the Citadel server until a 000 terminator is received.
+ * Implemented in terms of ctdl_readline() and is therefore transparent...
+ * Returns a newly allocated StrBuf or NULL for error.
+ */
+StrBuf *ctdl_readtextmsg(struct ctdlsession *ctdl)
+{
+ char buf[1024];
+ StrBuf *sj = NewStrBuf();
+ if (!sj) {
+ return NULL;
+ }
+
+ while ( (ctdl_readline(ctdl, buf, sizeof(buf)) >= 0) && (strcmp(buf, "000")) ) {
+ StrBufAppendPrintf(sj, "%s\n", buf);
+ }
+
+ return sj;
+}
+
+
+/*
+ * Write to the Citadel server. For now we're just wrapping write() in case we
+ * need to add anything else later.
+ */
+ssize_t ctdl_write(struct ctdlsession *ctdl, const void *buf, size_t count) {
+ return write(ctdl->sock, buf, count);
+}
+
+
+/*
+ * printf() type function to send data to the Citadel Server.
+ */
+void ctdl_printf(struct ctdlsession *ctdl, const char *format,...)
+{
+ va_list arg_ptr;
+ StrBuf *Buf = NewStrBuf();
+
+ va_start(arg_ptr, format);
+ StrBufVAppendPrintf(Buf, format, arg_ptr);
+ va_end(arg_ptr);
+
+ // syslog(LOG_DEBUG, "] %s", ChrPtr(Buf));
+ ctdl_write(ctdl, (char *)ChrPtr(Buf), StrLength(Buf));
+ ctdl_write(ctdl, "\n", 1);
+ FreeStrBuf(&Buf);
+}
+
+
+/*
+ * Client side - connect to a unix domain socket
+ */
+int uds_connectsock(char *sockpath)
+{
+ struct sockaddr_un addr;
+ int s;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
+
+ s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s < 0) {
+ syslog(LOG_WARNING, "Can't create socket [%s]: %s", sockpath, strerror(errno));
+ return(-1);
+ }
+
+ if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ syslog(LOG_WARNING, "Can't connect [%s]: %s", sockpath, strerror(errno));
+ close(s);
+ return(-1);
+ }
+ return s;
+}
+
+
+/*
+ * TCP client - connect to a host/port
+ */
+int tcp_connectsock(char *host, char *service)
+{
+ struct in6_addr serveraddr;
+ struct addrinfo hints;
+ struct addrinfo *res = NULL;
+ struct addrinfo *ai = NULL;
+ int rc = (-1);
+ int s = (-1);
+
+ if ((host == NULL) || IsEmptyStr(host))
+ return (-1);
+ if ((service == NULL) || IsEmptyStr(service))
+ return (-1);
+
+ syslog(LOG_DEBUG, "tcp_connectsock(%s,%s)", host, service);
+
+ memset(&hints, 0x00, sizeof(hints));
+ hints.ai_flags = AI_NUMERICSERV;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ /*
+ * Handle numeric IPv4 and IPv6 addresses
+ */
+ rc = inet_pton(AF_INET, host, &serveraddr);
+ if (rc == 1) { /* dotted quad */
+ hints.ai_family = AF_INET;
+ hints.ai_flags |= AI_NUMERICHOST;
+ } else {
+ rc = inet_pton(AF_INET6, host, &serveraddr);
+ if (rc == 1) { /* IPv6 address */
+ hints.ai_family = AF_INET6;
+ hints.ai_flags |= AI_NUMERICHOST;
+ }
+ }
+
+ /* Begin the connection process */
+
+ rc = getaddrinfo(host, service, &hints, &res);
+ if (rc != 0) {
+ syslog(LOG_DEBUG, "%s: %s", host, gai_strerror(rc));
+ freeaddrinfo(res);
+ return(-1);
+ }
+
+ /*
+ * Try all available addresses until we connect to one or until we run out.
+ */
+ for (ai = res; ai != NULL; ai = ai->ai_next) {
+
+ if (ai->ai_family == AF_INET) syslog(LOG_DEBUG, "Trying IPv4");
+ else if (ai->ai_family == AF_INET6) syslog(LOG_DEBUG, "Trying IPv6");
+ else syslog(LOG_WARNING, "This is going to fail.");
+
+ s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (s < 0) {
+ syslog(LOG_WARNING, "socket() failed: %s", strerror(errno));
+ freeaddrinfo(res);
+ return(-1);
+ }
+ rc = connect(s, ai->ai_addr, ai->ai_addrlen);
+ if (rc >= 0) {
+ int fdflags;
+ freeaddrinfo(res);
+
+ fdflags = fcntl(rc, F_GETFL);
+ if (fdflags < 0) {
+ syslog(LOG_ERR,
+ "unable to get socket %d flags! %s",
+ rc,
+ strerror(errno));
+ close(rc);
+ return -1;
+ }
+ fdflags = fdflags | O_NONBLOCK;
+ if (fcntl(rc, F_SETFL, fdflags) < 0) {
+ syslog(LOG_ERR,
+ "unable to set socket %d nonblocking flags! %s",
+ rc,
+ strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ return(s);
+ }
+ else {
+ syslog(LOG_WARNING, "connect() failed: %s", strerror(errno));
+ close(s);
+ }
+ }
+ freeaddrinfo(res);
+ return(-1);
+}
+
+
+/*
+ * Extract from the headers, the username and password the client is attempting to use.
+ * This could be HTTP AUTH or it could be in the cookies.
+ */
+void extract_auth(struct http_transaction *h, char *authbuf, int authbuflen)
+{
+ if (authbuf == NULL) return;
+ authbuf[0] = 0;
+
+ char *authheader = header_val(h, "Authorization");
+ if (authheader) {
+ if (!strncasecmp(authheader, "Basic ", 6)) {
+ safestrncpy(authbuf, &authheader[6], authbuflen);
+ return; // HTTP-AUTH was found -- stop here
+ }
+ }
+
+ char *cookieheader = header_val(h, "Cookie");
+ if (cookieheader) {
+ char *wcauth = strstr(cookieheader, "wcauth=");
+ if (wcauth) {
+ safestrncpy(authbuf, &cookieheader[7], authbuflen);
+ char *semicolon = strchr(authbuf, ';');
+ if (semicolon != NULL) {
+ *semicolon = 0;
+ }
+ if (strlen(authbuf) < 3) { // impossibly small
+ authbuf[0] = 0;
+ }
+ return; // Cookie auth was found -- stop here
+ }
+ }
+
+ // no authorization found in headers ... this is an anonymous session
+}
+
+
+/*
+ * Log in to the Citadel server. Returns 0 on success or nonzero on error.
+ *
+ * 'auth' should be a base64-encoded "username:password" combination (like in http-auth)
+ *
+ * If 'resultbuf' is not NULL, it should be a buffer of at least 1024 characters,
+ * and will be filled with the result from a Citadel server command.
+ */
+int login_to_citadel(struct ctdlsession *c, char *auth, char *resultbuf)
+{
+ char localbuf[1024];
+ char *buf;
+ int buflen;
+ char supplied_username[AUTH_MAX];
+ char supplied_password[AUTH_MAX];
+
+ if (resultbuf != NULL) {
+ buf = resultbuf;
+ }
+ else {
+ buf = localbuf;
+ }
+
+ buflen = CtdlDecodeBase64(buf, auth, strlen(auth));
+ extract_token(supplied_username, buf, 0, ':', sizeof supplied_username);
+ extract_token(supplied_password, buf, 1, ':', sizeof supplied_password);
+ syslog(LOG_DEBUG, "Supplied credentials: username=%s, pwlen=%d", supplied_username, (int)strlen(supplied_password));
+
+ ctdl_printf(c, "USER %s", supplied_username);
+ ctdl_readline(c, buf, 1024);
+ if (buf[0] != '3') {
+ syslog(LOG_DEBUG, "No such user: %s", buf);
+ return(1); // no such user; resultbuf will explain why
+ }
+
+ ctdl_printf(c, "PASS %s", supplied_password);
+ ctdl_readline(c, buf, 1024);
+
+ if (buf[0] == '2') {
+ strcpy(c->auth, auth);
+ extract_token(c->whoami, &buf[4], 0, '|', sizeof c->whoami);
+ syslog(LOG_DEBUG, "Login succeeded: %s", buf);
+ return(0);
+ }
+
+ syslog(LOG_DEBUG, "Login failed: %s", buf);
+ return(1); // login failed; resultbuf will explain why
+}
+
+
+/*
+ * Hunt for, or create, a connection to our Citadel Server
+ */
+struct ctdlsession *connect_to_citadel(struct http_transaction *h) {
+ struct ctdlsession *cptr = NULL;
+ struct ctdlsession *my_session = NULL;
+ int is_new_session = 0;
+ char buf[1024];
+ char auth[AUTH_MAX];
+ int r = 0;
+
+ // Does the request carry a username and password?
+ extract_auth(h, auth, sizeof auth);
+ syslog(LOG_DEBUG, "Session auth: %s", auth); // remove this log when development is done
+
+ // Lock the connection pool while we claim our connection
+ pthread_mutex_lock(&cpool_mutex);
+ if (cpool != NULL) for (cptr = cpool; ((cptr != NULL) && (my_session == NULL)); cptr = cptr->next) {
+ if ( (cptr->is_bound == 0) && (!strcmp(cptr->auth, auth)) ) {
+ my_session = cptr;
+ my_session->is_bound = 1;
+ }
+ }
+ if (my_session == NULL) {
+ syslog(LOG_DEBUG, "No qualifying sessions , starting a new one");
+ my_session = malloc(sizeof(struct ctdlsession));
+ if (my_session != NULL) {
+ memset(my_session, 0, sizeof(struct ctdlsession));
+ is_new_session = 1;
+ my_session->next = cpool;
+ cpool = my_session;
+ my_session->is_bound = 1;
+ }
+ }
+ pthread_mutex_unlock(&cpool_mutex);
+ if (my_session == NULL) {
+ return(NULL); // oh well
+ }
+
+ if (my_session->sock < 3) {
+ is_new_session = 1;
+ }
+ else { // make sure our Citadel session is still good
+ int test_conn;
+ test_conn = ctdl_write(my_session, HKEY("NOOP\n"));
+ if (test_conn < 5) {
+ syslog(LOG_DEBUG, "Citadel session is broken , must reconnect");
+ close(my_session->sock);
+ my_session->sock = 0;
+ is_new_session = 1;
+ }
+ else {
+ test_conn = ctdl_readline(my_session, buf, sizeof(buf));
+ if (test_conn < 1) {
+ syslog(LOG_DEBUG, "Citadel session is broken , must reconnect");
+ close(my_session->sock);
+ my_session->sock = 0;
+ is_new_session = 1;
+ }
+ }
+ }
+
+ if (is_new_session) {
+ strcpy(my_session->room, "");
+ my_session->sock = tcp_connectsock(ctdlhost, ctdlport);
+ ctdl_readline(my_session, buf, sizeof(buf)); // skip past the server greeting banner
+
+ if (!IsEmptyStr(auth)) { // do we need to log in to Citadel?
+ r = login_to_citadel(my_session, auth, NULL); // FIXME figure out what happens if login failed
+ }
+ }
+ ctdl_printf(my_session, "NOOP");
+ ctdl_readline(my_session, buf, sizeof(buf));
+ my_session->last_access = time(NULL);
+ ++my_session->num_requests_handled;
+
+ return(my_session);
+}
+
+
+/*
+ * Release our Citadel Server connection back into the pool.
+ */
+void disconnect_from_citadel(struct ctdlsession *ctdl) {
+ pthread_mutex_lock(&cpool_mutex);
+ ctdl->is_bound = 0;
+ pthread_mutex_unlock(&cpool_mutex);
+}
--- /dev/null
+/*
+ * These utility functions loosely make up a Citadel protocol client library.
+ *
+ * Copyright (c) 2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * Delete one or more messages from the connected Citadel server.
+ * This function expects the session to already be "in" the room from which the messages will be deleted.
+ */
+void ctdl_delete_msgs(struct ctdlsession *c, long *msgnums, int num_msgs)
+{
+ int i = 0;
+ char buf[1024];
+
+ if ( (c == NULL) || (msgnums == NULL) || (num_msgs < 1) )
+ {
+ return;
+ }
+
+ i = 0;
+ strcpy(buf, "DELE ");
+ do {
+ sprintf(&buf[strlen(buf)], "%ld", msgnums[i]);
+ if ( (((i+1)%50)==0) || (i==num_msgs-1)) // delete up to 50 messages with one server command
+ {
+ syslog(LOG_DEBUG, "%s", buf);
+ ctdl_printf(c, "%s", buf);
+ ctdl_readline(c, buf, sizeof(buf));
+ syslog(LOG_DEBUG, "%s", buf);
+ }
+ else {
+ strcat(buf, ",");
+ }
+ } while (++i < num_msgs);
+}
--- /dev/null
+/*
+ * Output an HTML message, modifying it slightly to make sure it plays nice
+ * with the rest of our web framework.
+ *
+ * Copyright (c) 2005-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * Strip surrounding single or double quotes from a string.
+ */
+void stripquotes(char *s)
+{
+ int len;
+
+ if (!s) return;
+
+ len = strlen(s);
+ if (len < 2) return;
+
+ if ( ( (s[0] == '\"') && (s[len-1] == '\"') ) || ( (s[0] == '\'') && (s[len-1] == '\'') ) ) {
+ s[len-1] = 0;
+ strcpy(s, &s[1]);
+ }
+}
+
+
+/*
+ * Check to see if a META tag has overridden the declared MIME character set.
+ *
+ * charset Character set name (left unchanged if we don't do anything)
+ * meta_http_equiv Content of the "http-equiv" portion of the META tag
+ * meta_content Content of the "content" portion of the META tag
+ */
+void extract_charset_from_meta(char *charset, char *meta_http_equiv, char *meta_content)
+{
+ char *ptr;
+ char buf[64];
+
+ if (!charset) return;
+ if (!meta_http_equiv) return;
+ if (!meta_content) return;
+
+ if (strcasecmp(meta_http_equiv, "Content-type")) return;
+
+ ptr = strchr(meta_content, ';');
+ if (!ptr) return;
+
+ safestrncpy(buf, ++ptr, sizeof buf);
+ striplt(buf);
+ if (!strncasecmp(buf, "charset=", 8)) {
+ strcpy(charset, &buf[8]);
+
+ /*
+ * The brain-damaged webmail program in Microsoft Exchange declares
+ * a charset of "unicode" when they really mean "UTF-8". GNU iconv
+ * treats "unicode" as an alias for "UTF-16" so we have to manually
+ * fix this here, otherwise messages generated in Exchange webmail
+ * show up as a big pile of weird characters.
+ */
+ if (!strcasecmp(charset, "unicode")) {
+ strcpy(charset, "UTF-8");
+ }
+
+ /* Remove wandering punctuation */
+ if ((ptr=strchr(charset, '\"'))) *ptr = 0;
+ striplt(charset);
+ }
+}
+
+
+/*
+ * Sanitize and enhance an HTML message for display.
+ * Also convert weird character sets to UTF-8 if necessary.
+ * Also fixup img src="cid:..." type inline images to fetch the image
+ *
+ */
+StrBuf *html2html(const char *supplied_charset, int treat_as_wiki, char *roomname, long msgnum, StrBuf *Source) {
+ char buf[SIZ];
+ char *msg;
+ char *ptr;
+ char *msgstart;
+ char *msgend;
+ StrBuf *converted_msg;
+ int buffer_length = 1;
+ int line_length = 0;
+ int content_length = 0;
+ char new_window[SIZ];
+ int brak = 0;
+ int alevel = 0;
+ int scriptlevel = 0;
+ int script_start_pos = (-1);
+ int i;
+ int linklen;
+ char charset[128];
+ StrBuf *BodyArea = NULL;
+
+ iconv_t ic = (iconv_t)(-1) ;
+ char *ibuf; /* Buffer of characters to be converted */
+ char *obuf; /* Buffer for converted characters */
+ size_t ibuflen; /* Length of input buffer */
+ size_t obuflen; /* Length of output buffer */
+ char *osav; /* Saved pointer to output buffer */
+
+ if (msg == NULL) {
+ return(NULL);
+ }
+
+ StrBuf *Target = NewStrBuf();
+ if (Target == NULL) {
+ return(NULL);
+ }
+
+ safestrncpy(charset, supplied_charset, sizeof charset);
+ sprintf(new_window, "<a target=\"%s\" href=", TARGET);
+
+ content_length = StrLength(Source);
+ msg = (char*) ChrPtr(Source);
+ buffer_length = content_length;
+
+ /* Do a first pass to isolate the message body */
+ ptr = msg + 1;
+ msgstart = msg;
+ msgend = &msg[content_length];
+
+ while (ptr < msgend) {
+
+ /* Advance to next tag */
+ ptr = strchr(ptr, '<');
+ if ((ptr == NULL) || (ptr >= msgend)) break;
+ ++ptr;
+ if ((ptr == NULL) || (ptr >= msgend)) break;
+
+ /*
+ * Look for META tags. Some messages (particularly in
+ * Asian locales) illegally declare a message's character
+ * set in the HTML instead of in the MIME headers. This
+ * is wrong but we have to work around it anyway.
+ */
+ if (!strncasecmp(ptr, "META", 4)) {
+
+ char *meta_start;
+ char *meta_end;
+ int meta_length;
+ char *meta;
+ char *meta_http_equiv;
+ char *meta_content;
+ char *spaceptr;
+
+ meta_start = &ptr[4];
+ meta_end = strchr(ptr, '>');
+ if ((meta_end != NULL) && (meta_end <= msgend)) {
+ meta_length = meta_end - meta_start + 1;
+ meta = malloc(meta_length + 1);
+ safestrncpy(meta, meta_start, meta_length);
+ meta[meta_length] = 0;
+ striplt(meta);
+ if (!strncasecmp(meta, "HTTP-EQUIV=", 11)) {
+ meta_http_equiv = strdup(&meta[11]);
+ spaceptr = strchr(meta_http_equiv, ' ');
+ if (spaceptr != NULL) {
+ *spaceptr = 0;
+ meta_content = strdup(++spaceptr);
+ if (!strncasecmp(meta_content, "content=", 8)) {
+ strcpy(meta_content, &meta_content[8]);
+ stripquotes(meta_http_equiv);
+ stripquotes(meta_content);
+ extract_charset_from_meta(charset, meta_http_equiv, meta_content);
+ }
+ free(meta_content);
+ }
+ free(meta_http_equiv);
+ }
+ free(meta);
+ }
+ }
+
+ /*
+ * Any of these tags cause everything up to and including
+ * the tag to be removed.
+ */
+ if ( (!strncasecmp(ptr, "HTML", 4))
+ ||(!strncasecmp(ptr, "HEAD", 4))
+ ||(!strncasecmp(ptr, "/HEAD", 5))
+ ||(!strncasecmp(ptr, "BODY", 4)) ) {
+ char *pBody = NULL;
+
+ if (!strncasecmp(ptr, "BODY", 4)) {
+ pBody = ptr;
+ }
+ ptr = strchr(ptr, '>');
+ if ((ptr == NULL) || (ptr >= msgend)) break;
+ if ((pBody != NULL) && (ptr - pBody > 4)) {
+ char *src;
+ char *cid_start, *cid_end;
+
+ *ptr = '\0';
+ pBody += 4;
+ while ((isspace(*pBody)) && (pBody < ptr))
+ pBody ++;
+ BodyArea = NewStrBufPlain(NULL, ptr - pBody);
+
+ if (pBody < ptr) {
+ src = strstr(pBody, "cid:");
+ if (src) {
+ cid_start = src + 4;
+ cid_end = cid_start;
+ while ((*cid_end != '"') &&
+ !isspace(*cid_end) &&
+ (cid_end < ptr))
+ cid_end ++;
+
+ /* copy tag and attributes up to src="cid: */
+ StrBufAppendBufPlain(BodyArea, pBody, src - pBody, 0);
+
+ /* add in /webcit/mimepart/<msgno>/CID/
+ trailing / stops dumb URL filters getting excited */
+ StrBufAppendPrintf(BodyArea,
+ "/webcit/mimepart/%ld/",msgnum);
+ StrBufAppendBufPlain(BodyArea, cid_start, cid_end - cid_start, 0);
+
+ if (ptr - cid_end > 0)
+ StrBufAppendBufPlain(BodyArea,
+ cid_end + 1,
+ ptr - cid_end, 0);
+ }
+ else
+ StrBufAppendBufPlain(BodyArea, pBody, ptr - pBody, 0);
+ }
+ *ptr = '>';
+ }
+ ++ptr;
+ if ((ptr == NULL) || (ptr >= msgend)) break;
+ msgstart = ptr;
+ }
+
+ /*
+ * Any of these tags cause everything including and following
+ * the tag to be removed.
+ */
+ if ( (!strncasecmp(ptr, "/HTML", 5)) ||(!strncasecmp(ptr, "/BODY", 5)) ) {
+ --ptr;
+ msgend = ptr;
+ strcpy(ptr, "");
+ }
+
+ ++ptr;
+ }
+ if (msgstart > msg) {
+ strcpy(msg, msgstart);
+ }
+
+ /* Now go through the message, parsing tags as necessary. */
+ converted_msg = NewStrBufPlain(NULL, content_length + 8192);
+
+ /* Convert foreign character sets to UTF-8 if necessary. */
+ if ( (strcasecmp(charset, "us-ascii"))
+ && (strcasecmp(charset, "UTF-8"))
+ && (strcasecmp(charset, ""))
+ ) {
+ syslog(LOG_DEBUG, "Converting %s to UTF-8", charset);
+ ctdl_iconv_open("UTF-8", charset, &ic);
+ if (ic == (iconv_t)(-1) ) {
+ syslog(LOG_WARNING, "%s:%d iconv_open() failed: %s", __FILE__, __LINE__, strerror(errno));
+ }
+ }
+ if (Source == NULL) {
+ if (ic != (iconv_t)(-1) ) {
+ ibuf = msg;
+ ibuflen = content_length;
+ obuflen = content_length + (content_length / 2) ;
+ obuf = (char *) malloc(obuflen);
+ osav = obuf;
+ iconv(ic, &ibuf, &ibuflen, &obuf, &obuflen);
+ content_length = content_length + (content_length / 2) - obuflen;
+ osav[content_length] = 0;
+ free(msg);
+ msg = osav;
+ iconv_close(ic);
+ }
+ }
+ else {
+ if (ic != (iconv_t)(-1) ) {
+ StrBuf *Buf = NewStrBufPlain(NULL, StrLength(Source) + 8096);;
+ StrBufConvert(Source, Buf, &ic);
+ FreeStrBuf(&Buf);
+ iconv_close(ic);
+ msg = (char*)ChrPtr(Source); /* TODO: get rid of this. */
+ }
+ }
+
+ /*
+ * At this point, the message has been stripped down to
+ * only the content inside the <BODY></BODY> tags, and has
+ * been converted to UTF-8 if it was originally in a foreign
+ * character set. The text is also guaranteed to be null
+ * terminated now.
+ */
+
+ if (converted_msg == NULL) {
+ StrBufAppendPrintf(Target, "Error %d: %s<br>%s:%d", errno, strerror(errno), __FILE__, __LINE__);
+ goto BAIL;
+ }
+
+ if (BodyArea != NULL) { // Any attributes that were declared in the <body> tag
+ StrBufAppendBufPlain(converted_msg, HKEY("<div "), 0); // are instead declared in this <div> tag
+ StrBufAppendBuf(converted_msg, BodyArea, 0);
+ StrBufAppendBufPlain(converted_msg, HKEY(">"), 0);
+ }
+ ptr = msg;
+ msgend = strchr(msg, 0);
+ while (ptr < msgend) {
+
+ /* Try to sanitize the html of any rogue scripts */
+ if (!strncasecmp(ptr, "<script", 7)) {
+ if (scriptlevel == 0) {
+ script_start_pos = StrLength(converted_msg);
+ }
+ ++scriptlevel;
+ }
+ if (!strncasecmp(ptr, "</script", 8)) {
+ --scriptlevel;
+ }
+
+ /*
+ * Change mailto: links to WebCit mail, by replacing the
+ * link with one that points back to our mail room. Due to
+ * the way we parse URL's, it'll even handle mailto: links
+ * that have "?subject=" in them.
+ */
+ if (!strncasecmp(ptr, "<a href=\"mailto:", 16)) {
+ content_length += 64;
+ StrBufAppendPrintf(converted_msg, "<a href=\"display_enter?force_room=_MAIL_?recp="); // FIXME make compatible with webcit-ng
+ ptr = &ptr[16];
+ ++alevel;
+ ++brak;
+ }
+ /* Make external links open in a separate window */
+ else if (!strncasecmp(ptr, "<a href=\"", 9)) {
+ ++alevel;
+ ++brak;
+ if ( ((strchr(ptr, ':') < strchr(ptr, '/'))) && ((strchr(ptr, '/') < strchr(ptr, '>')))) {
+ /* open external links to new window */
+ StrBufAppendPrintf(converted_msg, new_window);
+ ptr = &ptr[8];
+ }
+ else if (
+ (treat_as_wiki)
+ && (strncasecmp(ptr, "<a href=\"wiki?", 14))
+ && (strncasecmp(ptr, "<a href=\"dotgoto?", 17))
+ && (strncasecmp(ptr, "<a href=\"knrooms?", 17))
+ ) {
+ content_length += 64;
+ StrBufAppendPrintf(converted_msg, "<a href=\"wiki?go=");
+ //StrBufUrlescAppend(converted_msg, "FIXME ROOM NAME", NULL); // FIXME make compatible with webcit-ng
+ StrBufAppendPrintf(converted_msg, "?page=");
+ ptr = &ptr[9];
+ }
+ else {
+ StrBufAppendPrintf(converted_msg, "<a href=\"");
+ ptr = &ptr[9];
+ }
+ }
+ /* Fixup <img src="cid:... ...> to fetch the mime part */
+ else if (!strncasecmp(ptr, "<img ", 5)) {
+ char *cid_start, *cid_end;
+ char* tag_end=strchr(ptr,'>');
+ char* src;
+ /* FIXME - handle this situation (maybe someone opened an <img cid...
+ * and then ended the message)
+ */
+ if (!tag_end) {
+ syslog(LOG_DEBUG, "tag_end is null and ptr is:");
+ syslog(LOG_DEBUG, "%s", ptr);
+ syslog(LOG_DEBUG, "Theoretical bytes remaining: %d", (int)(msgend - ptr));
+ }
+
+ src=strstr(ptr, "src=\"cid:");
+ ++brak;
+
+ if ( src
+ && isspace(*(src-1))
+ && tag_end
+ && (cid_start=strchr(src,':'))
+ && (cid_end=strchr(cid_start,'"'))
+ && (cid_end < tag_end)
+ ) {
+ /* copy tag and attributes up to src="cid: */
+ StrBufAppendBufPlain(converted_msg, ptr, src - ptr, 0);
+ cid_start++;
+
+ /* add in /webcit/mimepart/<msgno>/CID/
+ trailing / stops dumb URL filters getting excited */
+ StrBufAppendPrintf(converted_msg, " src=\"/ctdl/r/");
+ StrBufXMLEscAppend(converted_msg, NULL, roomname, strlen(roomname), 0);
+ syslog(LOG_DEBUG, "room name is '%s'", roomname);
+ StrBufAppendPrintf(converted_msg, "/%ld/",msgnum);
+ StrBufAppendBufPlain(converted_msg, cid_start, cid_end - cid_start, 0);
+ StrBufAppendBufPlain(converted_msg, "\"", -1, 0);
+ ptr = cid_end+1;
+ }
+ StrBufAppendBufPlain(converted_msg, ptr, tag_end - ptr, 0);
+ ptr = tag_end;
+ }
+
+ /*
+ * Turn anything that looks like a URL into a real link, as long
+ * as it's not inside a tag already
+ */
+ else if ( (brak == 0) && (alevel == 0) &&
+ ( (!strncasecmp(ptr, "http://", 7)) ||
+ (!strncasecmp(ptr, "https://", 8)))) {
+ /* Find the end of the link */
+ int strlenptr;
+ linklen = 0;
+
+ strlenptr = strlen(ptr);
+ for (i=0; i<=strlenptr; ++i) {
+ if ((ptr[i]==0)
+ ||(isspace(ptr[i]))
+ ||(ptr[i]==10)
+ ||(ptr[i]==13)
+ ||(ptr[i]=='(')
+ ||(ptr[i]==')')
+ ||(ptr[i]=='<')
+ ||(ptr[i]=='>')
+ ||(ptr[i]=='[')
+ ||(ptr[i]==']')
+ ||(ptr[i]=='"')
+ ||(ptr[i]=='\'')
+ ) linklen = i;
+ /* entity tag? */
+ if (ptr[i] == '&') {
+ if ((ptr[i+2] ==';') ||
+ (ptr[i+3] ==';') ||
+ (ptr[i+5] ==';') ||
+ (ptr[i+6] ==';') ||
+ (ptr[i+7] ==';'))
+ linklen = i;
+ }
+ if (linklen > 0) break;
+ }
+ if (linklen > 0) {
+ char *ltreviewptr;
+ char *nbspreviewptr;
+ char linkedchar;
+ int len;
+
+ len = linklen;
+ linkedchar = ptr[len];
+ ptr[len] = '\0';
+ /* spot for some subject strings tinymce tends to give us. */
+ ltreviewptr = strchr(ptr, '<');
+ if (ltreviewptr != NULL) {
+ *ltreviewptr = '\0';
+ linklen = ltreviewptr - ptr;
+ }
+
+ nbspreviewptr = strstr(ptr, " ");
+ if (nbspreviewptr != NULL) {
+ /* nbspreviewptr = '\0'; */
+ linklen = nbspreviewptr - ptr;
+ }
+ if (ltreviewptr != 0)
+ *ltreviewptr = '<';
+
+ ptr[len] = linkedchar;
+
+ content_length += (32 + linklen);
+ StrBufAppendPrintf(converted_msg, "%s\"", new_window);
+ StrBufAppendBufPlain(converted_msg, ptr, linklen, 0);
+ StrBufAppendPrintf(converted_msg, "\">");
+ StrBufAppendBufPlain(converted_msg, ptr, linklen, 0);
+ ptr += linklen;
+ StrBufAppendPrintf(converted_msg, "</a>");
+ }
+ }
+ else {
+ StrBufAppendBufPlain(converted_msg, ptr, 1, 0);
+ ptr++;
+ }
+
+ if ((ptr >= msg) && (ptr <= msgend)) {
+ /*
+ * We need to know when we're inside a tag,
+ * so we don't turn things that look like URL's into
+ * links, when they're already links - or image sources.
+ */
+ if ((ptr > msg) && (*(ptr-1) == '<')) {
+ ++brak;
+ }
+ if ((ptr > msg) && (*(ptr-1) == '>')) {
+ --brak;
+ if ((scriptlevel == 0) && (script_start_pos >= 0)) {
+ StrBufCutRight(converted_msg, StrLength(converted_msg) - script_start_pos);
+ script_start_pos = (-1);
+ }
+ }
+ if (!strncasecmp(ptr, "</a>", 3)) --alevel;
+ }
+ }
+
+ if (BodyArea != NULL) {
+ StrBufAppendBufPlain(converted_msg, HKEY("</div>"), 0); // Close the div where we declared attributes copied
+ FreeStrBuf(&BodyArea); // from the original <body> tag
+ }
+
+ /* uncomment these two lines to override conversion */
+ /* memcpy(converted_msg, msg, content_length); */
+ /* output_length = content_length; */
+
+ /* Output our big pile of markup */
+ StrBufAppendBuf(Target, converted_msg, 0);
+
+BAIL: /* A little trailing vertical whitespace... */
+ StrBufAppendPrintf(Target, "<br>\n");
+
+ /* Now give back the memory */
+ FreeStrBuf(&converted_msg);
+ if ((msg != NULL) && (Source == NULL)) free(msg);
+ return(Target);
+}
+
+
+/*
+ * Look for URL's embedded in a buffer and make them linkable. We use a
+ * target window in order to keep the Citadel session in its own window.
+ */
+void UrlizeText(StrBuf* Target, StrBuf *Source, StrBuf *WrkBuf)
+{
+ int len, UrlLen, Offset, TrailerLen;
+ const char *start, *end, *pos;
+
+ FlushStrBuf(Target);
+ start = NULL;
+ len = StrLength(Source);
+ end = ChrPtr(Source) + len;
+ for (pos = ChrPtr(Source); (pos < end) && (start == NULL); ++pos) {
+ if (!strncasecmp(pos, "http://", 7))
+ start = pos;
+ else if (!strncasecmp(pos, "ftp://", 6))
+ start = pos;
+ }
+
+ if (start == NULL) {
+ StrBufAppendBuf(Target, Source, 0);
+ return;
+ }
+ FlushStrBuf(WrkBuf);
+
+ for (pos = ChrPtr(Source) + len; pos > start; --pos) {
+ if ( (!isprint(*pos))
+ || (isspace(*pos))
+ || (*pos == '{')
+ || (*pos == '}')
+ || (*pos == '|')
+ || (*pos == '\\')
+ || (*pos == '^')
+ || (*pos == '[')
+ || (*pos == ']')
+ || (*pos == '`')
+ || (*pos == '<')
+ || (*pos == '>')
+ || (*pos == '(')
+ || (*pos == ')')
+ ) {
+ end = pos;
+ }
+ }
+
+ UrlLen = end - start;
+ StrBufAppendBufPlain(WrkBuf, start, UrlLen, 0);
+
+ Offset = start - ChrPtr(Source);
+ if (Offset != 0)
+ StrBufAppendBufPlain(Target, ChrPtr(Source), Offset, 0);
+ StrBufAppendPrintf(Target, "%ca href=%c%s%c TARGET=%c%s%c%c%s%c/A%c",
+ LB, QU, ChrPtr(WrkBuf), QU, QU, TARGET,
+ QU, RB, ChrPtr(WrkBuf), LB, RB);
+
+ TrailerLen = StrLength(Source) - (end - ChrPtr(Source));
+ if (TrailerLen > 0)
+ StrBufAppendBufPlain(Target, end, TrailerLen, 0);
+}
+
+
+void url(char *buf, size_t bufsize)
+{
+ int len, UrlLen, Offset, TrailerLen, outpos;
+ char *start, *end, *pos;
+ char urlbuf[SIZ];
+ char outbuf[SIZ];
+
+ start = NULL;
+ len = strlen(buf);
+ if (len > bufsize) {
+ syslog(LOG_WARNING, "URL: content longer than buffer!");
+ return;
+ }
+ end = buf + len;
+ for (pos = buf; (pos < end) && (start == NULL); ++pos) {
+ if (!strncasecmp(pos, "http://", 7))
+ start = pos;
+ if (!strncasecmp(pos, "ftp://", 6))
+ start = pos;
+ }
+
+ if (start == NULL)
+ return;
+
+ for (pos = buf+len; pos > start; --pos) {
+ if ( (!isprint(*pos))
+ || (isspace(*pos))
+ || (*pos == '{')
+ || (*pos == '}')
+ || (*pos == '|')
+ || (*pos == '\\')
+ || (*pos == '^')
+ || (*pos == '[')
+ || (*pos == ']')
+ || (*pos == '`')
+ || (*pos == '<')
+ || (*pos == '>')
+ || (*pos == '(')
+ || (*pos == ')')
+ ) {
+ end = pos;
+ }
+ }
+
+ UrlLen = end - start;
+ if (UrlLen > sizeof(urlbuf)){
+ syslog(LOG_WARNING, "URL: content longer than buffer!");
+ return;
+ }
+ memcpy(urlbuf, start, UrlLen);
+ urlbuf[UrlLen] = '\0';
+
+ Offset = start - buf;
+ if ((Offset != 0) && (Offset < sizeof(outbuf)))
+ memcpy(outbuf, buf, Offset);
+ outpos = snprintf(&outbuf[Offset], sizeof(outbuf) - Offset,
+ "%ca href=%c%s%c TARGET=%c%s%c%c%s%c/A%c",
+ LB, QU, urlbuf, QU, QU, TARGET, QU, RB, urlbuf, LB, RB);
+ if (outpos >= sizeof(outbuf) - Offset) {
+ syslog(LOG_WARNING, "URL: content longer than buffer!");
+ return;
+ }
+
+ TrailerLen = len - (end - start);
+ if (TrailerLen > 0)
+ memcpy(outbuf + Offset + outpos, end, TrailerLen);
+ if (Offset + outpos + TrailerLen > bufsize) {
+ syslog(LOG_WARNING, "URL: content longer than buffer!");
+ return;
+ }
+ memcpy (buf, outbuf, Offset + outpos + TrailerLen);
+ *(buf + Offset + outpos + TrailerLen) = '\0';
+}
--- /dev/null
+/*
+ * This module handles HTTP transactions.
+ *
+ * Copyright (c) 1996-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+/*
+ * Write data to the HTTP client. Encrypt if necessary.
+ */
+int client_write(struct client_handle *ch, char *buf, int nbytes)
+{
+ if (is_https) {
+ return client_write_ssl(ch, buf, nbytes);
+ }
+ else {
+ return write(ch->sock, buf, nbytes);
+ }
+}
+
+
+/*
+ * Read data from the HTTP client. Decrypt if necessary.
+ * Returns number of bytes read, or -1 to indicate an error.
+ */
+int client_read(struct client_handle *ch, char *buf, int nbytes)
+{
+ if (is_https) {
+ return client_read_ssl(ch, buf, nbytes);
+ }
+ else {
+ int bytes_received = 0;
+ int bytes_this_block = 0;
+ while (bytes_received < nbytes) {
+ bytes_this_block = read(ch->sock, &buf[bytes_received], nbytes-bytes_received);
+ if (bytes_this_block < 1) {
+ return(-1);
+ }
+ bytes_received += bytes_this_block;
+ }
+ return(nbytes);
+ }
+}
+
+
+/*
+ * Read a newline-terminated line of text from the client.
+ * Implemented in terms of client_read() and is therefore transparent...
+ * Returns the string length or -1 for error.
+ */
+int client_readline(struct client_handle *ch, char *buf, int maxbytes)
+{
+ int len = 0;
+ int c = 0;
+
+ if (buf == NULL) return;
+
+ while (len < maxbytes) {
+ c = client_read(ch, &buf[len], 1);
+ if (c <= 0) {
+ syslog(LOG_DEBUG, "Socket error or zero-length read");
+ return(-1);
+ }
+ if (buf[len] == '\n') {
+ if ( (len >0) && (buf[len-1] == '\r') ) {
+ --len;
+ }
+ buf[len] = 0;
+ return(len);
+ }
+ ++len;
+ }
+ return(len);
+}
+
+
+/*
+ * printf() type function to send data to the web client.
+ */
+void client_printf(struct client_handle *ch, const char *format,...)
+{
+ va_list arg_ptr;
+ StrBuf *Buf = NewStrBuf();
+
+ va_start(arg_ptr, format);
+ StrBufVAppendPrintf(Buf, format, arg_ptr);
+ va_end(arg_ptr);
+
+ client_write(ch, (char *)ChrPtr(Buf), StrLength(Buf));
+ FreeStrBuf(&Buf);
+}
+
+
+/*
+ * Push one new header into the response of an HTTP request.
+ * When completed, the HTTP transaction now owns the memory allocated for key and val.
+ */
+void add_response_header(struct http_transaction *h, char *key, char *val)
+{
+ struct http_header *new_response_header = malloc(sizeof(struct http_header));
+ new_response_header->key = key;
+ new_response_header->val = val;
+ new_response_header->next = h->response_headers;
+ h->response_headers = new_response_header;
+}
+
+
+/*
+ * Entry point for this layer. Socket is connected. If running as an HTTPS
+ * server, SSL has already been negotiated. Now perform one transaction.
+ */
+void perform_one_http_transaction(struct client_handle *ch)
+{
+ char buf[1024];
+ int len;
+ int lines_read = 0;
+ struct http_transaction h;
+ char *c, *d;
+ struct sockaddr_in sin;
+ struct http_header *clh; // general purpose iterator variable
+
+ memset(&h, 0, sizeof h);
+
+ while (len = client_readline(ch, buf, sizeof buf), (len > 0) ) {
+ ++lines_read;
+
+ if (lines_read == 1) { // First line is method and URI.
+ c = strchr(buf, ' '); // IGnore the HTTP-version.
+ if (c == NULL) {
+ h.method = strdup("NULL");
+ h.uri = strdup("/");
+ }
+ else {
+ *c = 0;
+ h.method = strdup(buf);
+ ++c;
+ d = c;
+ c = strchr(d, ' ');
+ if (c != NULL) {
+ *c = 0;
+ }
+ ++c;
+ h.uri = strdup(d);
+ }
+ }
+ else { // Subsequent lines are headers.
+ c = strchr(buf, ':'); // Header line folding is obsolete so we don't support it.
+ if (c != NULL) {
+ struct http_header *new_request_header = malloc(sizeof(struct http_header));
+ *c = 0;
+ new_request_header->key = strdup(buf);
+ ++c;
+ new_request_header->val = strdup(c);
+ striplt(new_request_header->key);
+ striplt(new_request_header->val);
+ new_request_header->next = h.request_headers;
+ h.request_headers = new_request_header;
+#ifdef DEBUG_HTTP
+ syslog(LOG_DEBUG, "{ %s: %s", new_request_header->key, new_request_header->val);
+#endif
+ }
+ }
+
+ }
+
+ // build up the site prefix, such as https://foo.bar.com:4343
+
+ h.site_prefix = malloc(256);
+ strcpy(h.site_prefix, (is_https ? "https://" : "http://"));
+ char *hostheader = header_val(&h, "Host");
+ if (hostheader) {
+ strcat(h.site_prefix, hostheader);
+ }
+ else {
+ strcat(h.site_prefix, "127.0.0.1");
+ }
+ socklen_t llen = sizeof(sin);
+ if (getsockname(ch->sock, (struct sockaddr *)&sin, &llen) >= 0) {
+ sprintf(&h.site_prefix[strlen(h.site_prefix)], ":%d", ntohs(sin.sin_port));
+ }
+
+ // Now try to read in the request body (if one is present)
+
+ if (len < 0) {
+ syslog(LOG_DEBUG, "Client disconnected");
+ }
+ else {
+ syslog(LOG_DEBUG, "< %s %s", h.method, h.uri);
+
+ // If there is a request body, read it now.
+ char *ccl = header_val(&h, "Content-Length");
+ if (ccl) {
+ h.request_body_length = atol(ccl);
+ }
+ if (h.request_body_length > 0) {
+ syslog(LOG_DEBUG, "Reading request body (%ld bytes)", h.request_body_length);
+ h.request_body = malloc(h.request_body_length);
+ client_read(ch, h.request_body, h.request_body_length);
+
+ //write(2, HKEY("\033[31m"));
+ //write(2, h.request_body, h.request_body_length);
+ //write(2, HKEY("\033[0m\n"));
+
+ }
+
+ // Now pass control up to the next layer to perform the request.
+ perform_request(&h);
+
+ // Output the results back to the client.
+ //write(2, HKEY("\033[32m"));
+ //write(2, h.response_body, h.response_body_length);
+ //write(2, HKEY("\033[0m\n"));
+
+ syslog(LOG_DEBUG, "> %03d %s", h.response_code, h.response_string);
+ client_printf(ch, "HTTP/1.1 %03d %s\r\n", h.response_code, h.response_string);
+ client_printf(ch, "Connection: close\r\n");
+ client_printf(ch, "Content-Length: %ld\r\n", h.response_body_length);
+ char *datestring = http_datestring(time(NULL));
+ if (datestring) {
+ client_printf(ch, "Date: %s\r\n", datestring);
+ free(datestring);
+ }
+
+ client_printf(ch, "Content-encoding: identity\r\n"); // change if we gzip/deflate
+ for (clh = h.response_headers; clh != NULL; clh = clh->next) {
+#ifdef DEBUG_HTTP
+ syslog(LOG_DEBUG, "} %s: %s", clh->key, clh->val);
+#endif
+ client_printf(ch, "%s: %s\r\n", clh->key, clh->val);
+ }
+ client_printf(ch, "\r\n");
+ if ((h.response_body_length > 0) && (h.response_body != NULL)) {
+ client_write(ch, h.response_body, h.response_body_length);
+ }
+ }
+
+ // free the transaction memory
+ while (h.request_headers) {
+ if (h.request_headers->key) free(h.request_headers->key);
+ if (h.request_headers->val) free(h.request_headers->val);
+ clh = h.request_headers->next;
+ free(h.request_headers);
+ h.request_headers = clh;
+ }
+ while (h.response_headers) {
+ if (h.response_headers->key) free(h.response_headers->key);
+ if (h.response_headers->val) free(h.response_headers->val);
+ clh = h.response_headers->next;
+ free(h.response_headers);
+ h.response_headers = clh;
+ }
+ if (h.method) free(h.method);
+ if (h.uri) free(h.uri);
+ if (h.request_body) free(h.request_body);
+ if (h.response_string) free(h.response_string);
+ if (h.site_prefix) free(h.site_prefix);
+}
+
+
+/*
+ * Utility function to fetch a specific header from a completely read-in request.
+ * Returns the value of the requested header, or NULL if the specified header was not sent.
+ * The caller does NOT own the memory of the returned pointer, but can count on the pointer
+ * to still be valid through the end of the transaction.
+ */
+char *header_val(struct http_transaction *h, char *requested_header)
+{
+ struct http_header *clh; // general purpose iterator variable
+ for (clh = h->request_headers; clh != NULL; clh = clh->next) {
+ if (!strcasecmp(clh->key, requested_header)) {
+ return(clh->val);
+ }
+ }
+ return(NULL);
+}
--- /dev/null
+/*
+ * Main entry point for the program.
+ *
+ * Copyright (c) 1996-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h" // All other headers are included from this header.
+
+char *ctdlhost = CTDLHOST;
+char *ctdlport = CTDLPORT;
+
+/*
+ * Main entry point for the web server.
+ */
+int main(int argc, char **argv)
+{
+ int webserver_port = WEBSERVER_PORT;
+ char *webserver_interface = WEBSERVER_INTERFACE;
+ int running_as_daemon = 0;
+ int webserver_protocol = WEBSERVER_HTTP;
+ int a;
+ char *pid_file = NULL;
+
+ /* Parse command line */
+ while ((a = getopt(argc, argv, "u:h:i:p:t:T:B:x:g:dD:G:cfsS:Z:v:")) != EOF) switch(a) {
+ case 'u':
+ setuid(atoi(optarg));
+ break;
+ case 'h':
+ break;
+ case 'd':
+ running_as_daemon = 1;
+ break;
+ case 'D':
+ running_as_daemon = 1;
+ pid_file = strdup(optarg);
+ break;
+ case 'g':
+ // FIXME set up the default landing page
+ break;
+ case 'B':
+ case 't':
+ case 'x':
+ case 'T':
+ case 'v':
+ /* The above options are no longer used, but ignored so old scripts don't break */
+ break;
+ case 'i':
+ webserver_interface = optarg;
+ break;
+ case 'p':
+ webserver_port = atoi(optarg);
+ break;
+ case 'Z':
+ // FIXME when gzip is added, disable it if this flag is set
+ break;
+ case 'f':
+ //follow_xff = 1;
+ break;
+ case 'c':
+ break;
+ case 's':
+ is_https = 1;
+ webserver_protocol = WEBSERVER_HTTPS;
+ break;
+ case 'S':
+ is_https = 1;
+ webserver_protocol = WEBSERVER_HTTPS;
+ //ssl_cipher_list = strdup(optarg);
+ break;
+ case 'G':
+ //DumpTemplateI18NStrings = 1;
+ //I18nDump = NewStrBufPlain(HKEY("int templatestrings(void)\n{\n"));
+ //I18nDumpFile = optarg;
+ break;
+ default:
+ fprintf(stderr, "usage:\nwebcit "
+ "[-i ip_addr] [-p http_port] "
+ "[-c] [-f] "
+ "[-d] [-Z] [-G i18ndumpfile] "
+ "[-u uid] [-h homedirectory] "
+ "[-D daemonizepid] [-v] "
+ "[-g defaultlandingpage] [-B basesize] "
+ "[-s] [-S cipher_suites]"
+ "[remotehost [remoteport]]\n");
+ return 1;
+ }
+
+ if (optind < argc) {
+ ctdlhost = argv[optind];
+ if (++optind < argc)
+ ctdlport = argv[optind];
+ }
+
+ /* Start the logger */
+ openlog("webcit", ( running_as_daemon ? (LOG_PID) : (LOG_PID | LOG_PERROR) ), LOG_DAEMON);
+
+ /* Tell 'em who's in da house */
+ syslog(LOG_NOTICE, "MAKE WEBCIT GREAT AGAIN!");
+ syslog(LOG_NOTICE, "Copyright (C) 1996-2017 by the citadel.org team");
+ syslog(LOG_NOTICE, " ");
+ syslog(LOG_NOTICE, "This program is open source software: you can redistribute it and/or");
+ syslog(LOG_NOTICE, "modify it under the terms of the GNU General Public License, version 3.");
+ syslog(LOG_NOTICE, " ");
+ syslog(LOG_NOTICE, "This program is distributed in the hope that it will be useful,");
+ syslog(LOG_NOTICE, "but WITHOUT ANY WARRANTY; without even the implied warranty of");
+ syslog(LOG_NOTICE, "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the");
+ syslog(LOG_NOTICE, "GNU General Public License for more details.");
+ syslog(LOG_NOTICE, " ");
+
+ /* Ensure that we are linked to the correct version of libcitadel */
+ if (libcitadel_version_number() < LIBCITADEL_VERSION_NUMBER) {
+ syslog(LOG_INFO, " You are running libcitadel version %d", libcitadel_version_number() );
+ syslog(LOG_INFO, "WebCit was compiled against version %d", LIBCITADEL_VERSION_NUMBER );
+ return(1);
+ }
+
+ /* Go into the background if we were asked to run as a daemon */
+ if (running_as_daemon) {
+ daemon(0, 0);
+ if (pid_file != NULL) {
+ FILE *pfp = fopen(pid_file, "w");
+ if (pfp) {
+ fprintf(pfp, "%d\n", getpid());
+ fclose(pfp);
+ }
+ }
+ }
+
+ return webserver(webserver_interface, webserver_port, webserver_protocol);
+}
--- /dev/null
+/*
+ * Message base functions
+ *
+ * Copyright (c) 1996-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * Given an encoded UID, translate that to an unencoded Citadel EUID and
+ * then search for it in the current room. Return a message number or -1
+ * if not found.
+ *
+ */
+long locate_message_by_uid(struct ctdlsession *c, char *uid) {
+ char buf[1024];
+
+ ctdl_printf(c, "EUID %s", uid);
+ ctdl_readline(c, buf, sizeof buf);
+ if (buf[0] == '2') {
+ return(atol(&buf[4]));
+
+ }
+
+ /* Ugly hack to handle Mozilla Thunderbird, try stripping ".ics" if present */
+ if (!strcasecmp(&uid[strlen(uid)-4], ".ics")) {
+ safestrncpy(buf, uid, sizeof buf);
+ buf[strlen(buf)-4] = 0;
+ ctdl_printf(c, "EUID %s", buf);
+ ctdl_readline(c, buf, sizeof buf);
+ if (buf[0] == '2') {
+ return(atol(&buf[4]));
+
+ }
+ }
+
+ return(-1);
+}
+
+
+/*
+ * DAV delete an object in a room.
+ */
+void dav_delete_message(struct http_transaction *h, struct ctdlsession *c, long msgnum)
+{
+ ctdl_delete_msgs(c, &msgnum, 1);
+ h->response_code = 204;
+ h->response_string = strdup("no content");
+}
+
+
+/*
+ * GET method directly on a message in a room
+ */
+void dav_get_message(struct http_transaction *h, struct ctdlsession *c, long msgnum)
+{
+ char buf[1024];
+ int in_body = 0;
+ int encoding = 0;
+ StrBuf *Body = NULL;
+
+ ctdl_printf(c, "MSG2 %ld", msgnum);
+ ctdl_readline(c, buf, sizeof buf);
+ if (buf[0] != '1') {
+ do_404(h);
+ return;
+ }
+
+ char *etag = malloc(20);
+ if (etag != NULL) {
+ sprintf(etag, "%ld", msgnum);
+ add_response_header(h, strdup("ETag"), etag); // http_transaction now owns this memory
+ }
+
+ while (ctdl_readline(c, buf, sizeof buf), strcmp(buf,"000"))
+ {
+ if (IsEmptyStr(buf) && (in_body == 0)) {
+ in_body = 1;
+ Body = NewStrBuf();
+ }
+ else if (in_body == 0) {
+ char *k = buf;
+ char *v = strchr(buf, ':');
+ if (v) {
+ *v = 0;
+ ++v;
+ striplt(v); // we now have a key (k) and a value (v)
+ if (
+ (!strcasecmp(k, "content-type")) // fields which can be passed from RFC822 to HTTP as-is
+ || (!strcasecmp(k, "date"))
+ ) {
+ add_response_header(h, strdup(k), strdup(v));
+ }
+ else if (!strcasecmp(k, "content-transfer-encoding")) {
+ if (!strcasecmp(v, "base64")) {
+ encoding = 'b';
+ }
+ else if (!strcasecmp(v, "quoted-printable")) {
+ encoding = 'q';
+ }
+ }
+ }
+ }
+ else if ( (in_body == 1) && (Body != NULL) ) {
+ StrBufAppendPrintf(Body, "%s\n", buf);
+ }
+ }
+
+ h->response_code = 200;
+ h->response_string = strdup("OK");
+
+ if (Body != NULL) {
+ if (encoding == 'q') {
+ h->response_body = malloc(StrLength(Body));
+ if (h->response_body != NULL) {
+ h->response_body_length = CtdlDecodeQuotedPrintable(h->response_body, (char *)ChrPtr(Body), StrLength(Body));
+ }
+ FreeStrBuf(&Body);
+ }
+ else if (encoding == 'b') {
+ h->response_body = malloc(StrLength(Body));
+ if (h->response_body != NULL) {
+ h->response_body_length = CtdlDecodeBase64(h->response_body, ChrPtr(Body), StrLength(Body));
+ }
+ FreeStrBuf(&Body);
+ }
+ else {
+ h->response_body_length = StrLength(Body);
+ h->response_body = SmashStrBuf(&Body);
+ }
+ }
+}
+
+
+/*
+ * PUT a message into a room
+ */
+void dav_put_message(struct http_transaction *h, struct ctdlsession *c, char *euid, long old_msgnum)
+{
+ char buf[1024];
+ char *content_type = NULL;
+ int n;
+ long new_msgnum;
+ char new_euid[1024];
+ char response_string[1024];
+
+ if ( (h->request_body == NULL) || (h->request_body_length < 1) ) {
+ do_404(h); // Refuse to post a null message
+ return;
+ }
+
+ ctdl_printf(c, "ENT0 1|||4|||1|"); // This protocol syntax will give us metadata back after upload
+ ctdl_readline(c, buf, sizeof buf);
+ if (buf[0] != '8') {
+ h->response_code = 502;
+ h->response_string = strdup("bad gateway");
+ add_response_header(h, strdup("Content-type"), strdup("text/plain"));
+ h->response_body = strdup(buf);
+ h->response_body_length = strlen(h->response_body);
+ return;
+ }
+
+ content_type = header_val(h, "Content-type");
+ ctdl_printf(c, "Content-type: %s\r\n", (content_type ? content_type : "application/octet-stream"));
+ ctdl_printf(c, "\r\n");
+ ctdl_write(c, h->request_body, h->request_body_length);
+ if (h->request_body[h->request_body_length] != '\n') {
+ ctdl_printf(c, "\r\n");
+ }
+ ctdl_printf(c, "000");
+
+ /*
+ * Now handle the response from the Citadel server.
+ */
+
+ n = 0;
+ new_msgnum = 0;
+ strcpy(new_euid, "");
+ strcpy(response_string, "");
+
+ while (ctdl_readline(c, buf, sizeof buf), strcmp(buf, "000")) switch(n++) {
+ case 0:
+ new_msgnum = atol(buf);
+ break;
+ case 1:
+ safestrncpy(response_string, buf, sizeof response_string);
+ syslog(LOG_DEBUG, "new_msgnum=%ld (%s)\n", new_msgnum, buf);
+ break;
+ case 2:
+ safestrncpy(new_euid, buf, sizeof new_euid);
+ break;
+ default:
+ break;
+ }
+
+ /* Tell the client what happened. */
+
+ /* Citadel failed in some way? */
+ char *new_location = malloc(1024);
+ if ( (new_msgnum < 0L) || (new_location == NULL) ) {
+ h->response_code = 502;
+ h->response_string = strdup("bad gateway");
+ add_response_header(h, strdup("Content-type"), strdup("text/plain"));
+ h->response_body = strdup(response_string);
+ h->response_body_length = strlen(h->response_body);
+ return;
+ }
+
+ char *etag = malloc(20);
+ if (etag != NULL) {
+ sprintf(etag, "%ld", new_msgnum);
+ add_response_header(h, strdup("ETag"), etag); // http_transaction now owns this memory
+ }
+
+ char esc_room[1024];
+ char esc_euid[1024];
+ urlesc(esc_room, sizeof esc_room, c->room);
+ urlesc(esc_euid, sizeof esc_euid, new_euid);
+ snprintf(new_location, 1024, "/ctdl/r/%s/%s", esc_room, esc_euid);
+ add_response_header(h, strdup("Location"), new_location); // http_transaction now owns this memory
+
+ if (old_msgnum <= 0) {
+ h->response_code = 201; // We created this item for the first time.
+ h->response_string = strdup("created");
+ }
+ else {
+ h->response_code = 204; // We modified an existing item.
+ h->response_string = strdup("no content");
+
+ /* The item we replaced has probably already been deleted by
+ * the Citadel server, but we'll do this anyway, just in case.
+ */
+ ctdl_delete_msgs(c, &old_msgnum, 1);
+ }
+
+}
+
+
+/*
+ * Download a single component of a MIME-encoded message
+ */
+void download_mime_component(struct http_transaction *h, struct ctdlsession *c, long msgnum, char *partnum)
+{
+ char buf[1024];
+ char content_type[1024];
+
+ ctdl_printf(c, "DLAT %ld|%s", msgnum, partnum);
+ ctdl_readline(c, buf, sizeof buf);
+ if (buf[0] != '6') {
+ do_404(h); // too bad, so sad, go away
+ }
+
+ // Server response is going to be: 6XX length|-1|filename|content-type|charset
+ h->response_body_length = extract_int(&buf[4], 0);
+ extract_token(content_type, buf, 3, '|', sizeof content_type);
+
+ h->response_body = malloc(h->response_body_length + 1);
+ int bytes = 0;
+ int thisblock;
+ do {
+ thisblock = read(c->sock, &h->response_body[bytes], (h->response_body_length - bytes));
+ bytes += thisblock;
+ syslog(LOG_DEBUG, "Bytes read: %d of %d", bytes, h->response_body_length);
+ } while ((bytes < h->response_body_length) && (thisblock >= 0));
+ h->response_body[h->response_body_length] = 0; // null terminate it just for good measure
+ syslog(LOG_DEBUG, "content type: %s", content_type);
+
+ add_response_header(h, strdup("Content-type"), strdup(content_type));
+ h->response_code = 200;
+ h->response_string = strdup("OK");
+}
--- /dev/null
+/*
+ * This module sits directly above the HTTP layer. By the time we get here,
+ * an HTTP request has been fully received and parsed. Control is passed up
+ * to this layer to actually perform the request. We then fill in the response
+ * and pass control back down to the HTTP layer to output the response back to
+ * the client.
+ *
+ * Copyright (c) 1996-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * Not found! Wowzers.
+ */
+void do_404(struct http_transaction *h)
+{
+ h->response_code = 404;
+ h->response_string = strdup("NOT FOUND");
+ add_response_header(h, strdup("Content-type"), strdup("text/plain"));
+ h->response_body = strdup("404 NOT FOUND\r\n");
+ h->response_body_length = strlen(h->response_body);
+}
+
+
+/*
+ * Precondition failed (such as if-match)
+ */
+void do_412(struct http_transaction *h)
+{
+ h->response_code = 412;
+ h->response_string = strdup("PRECONDITION FAILED");
+}
+
+
+/*
+ * We throw an HTTP error "502 bad gateway" when we need to connect to Citadel, but can't.
+ */
+void do_502(struct http_transaction *h)
+{
+ h->response_code = 502;
+ h->response_string = strdup("bad gateway");
+ add_response_header(h, strdup("Content-type"), strdup("text/plain"));
+ h->response_body = strdup(_("This program was unable to connect or stay connected to the Citadel server. Please report this problem to your system administrator."));
+ h->response_body_length = strlen(h->response_body);
+}
+
+
+/*
+ * Tell the client to authenticate using HTTP-AUTH (RFC 2617)
+ */
+void request_http_authenticate(struct http_transaction *h)
+{
+ h->response_code = 401;
+ h->response_string = strdup("Unauthorized");
+ add_response_header(h, strdup("WWW-Authenticate"), strdup("Basic realm=\"Citadel Server\""));
+}
+
+
+/*
+ * Easy and fun utility function to throw a redirect.
+ */
+void http_redirect(struct http_transaction *h, char *to_where)
+{
+ syslog(LOG_DEBUG, "Redirecting to: %s", to_where);
+ h->response_code = 302;
+ h->response_string = strdup("Moved Temporarily");
+ add_response_header(h, strdup("Location"), strdup(to_where));
+ add_response_header(h, strdup("Content-type"), strdup("text/plain"));
+ h->response_body = strdup(to_where);
+ h->response_body_length = strlen(h->response_body);
+}
+
+
+/*
+ * perform_request() is the entry point for *every* HTTP transaction.
+ */
+void perform_request(struct http_transaction *h)
+{
+ struct ctdlsession *c;
+
+ // Determine which code path to take based on the beginning of the URI.
+ // This is implemented as a series of strncasecmp() calls rather than a
+ // lookup table in order to make the code more readable.
+
+ if (IsEmptyStr(h->uri)) { // Sanity check
+ do_404(h);
+ return;
+ }
+
+ // Right about here is where we should try to handle anything that doesn't start
+ // with the /ctdl/ prefix.
+ // Root (/) ...
+
+ if ( (!strcmp(h->uri, "/")) && (!strcasecmp(h->method, "GET")) ) {
+ http_redirect(h, "/ctdl/s/index.html");
+ return;
+ }
+
+ // Legacy URI patterns (/readnew?gotoroom=xxx&start_reading_at=yyy) ...
+ // Direct room name (/my%20blog) ...
+
+ // Everything below this line is strictly REST URI patterns.
+
+ if (strncasecmp(h->uri, HKEY("/ctdl/"))) { // Reject non-REST
+ do_404(h);
+ return;
+ }
+
+ if (!strncasecmp(h->uri, HKEY("/ctdl/s/"))) { // Static content
+ output_static(h);
+ return;
+ }
+
+ if (h->uri[7] != '/') {
+ do_404(h);
+ return;
+ }
+
+ // Anything below this line:
+ // 1. Is in the format of /ctdl/?/*
+ // 2. Requires a connection to a Citadel server.
+
+ c = connect_to_citadel(h);
+ if (c == NULL) {
+ do_502(h);
+ return;
+ }
+
+ // WebDAV methods like OPTIONS and PROPFIND *require* a logged-in session,
+ // even if the Citadel server allows anonymous access.
+ if (IsEmptyStr(c->auth)) {
+ if ( (!strcasecmp(h->method, "OPTIONS"))
+ || (!strcasecmp(h->method, "PROPFIND"))
+ || (!strcasecmp(h->method, "REPORT"))
+ || (!strcasecmp(h->method, "DELETE"))
+ ) {
+ request_http_authenticate(h);
+ disconnect_from_citadel(c);
+ return;
+ }
+ }
+
+ // Break down the URI by path and send the request to the appropriate part of the program.
+ //
+ switch(h->uri[6]) {
+ case 'a': // /ctdl/a/ == RESTful path to admin functions
+ ctdl_a(h, c);
+ break;
+ case 'c': // /ctdl/c/ == misc Citadel server commands
+ ctdl_c(h, c);
+ break;
+ case 'r': // /ctdl/r/ == RESTful path to rooms
+ ctdl_r(h, c);
+ break;
+ case 'u': // /ctdl/u/ == RESTful path to users
+ do_404(h);
+ break;
+ default:
+ do_404(h);
+ }
+
+ // Are we in an authenticated session? If so, set a cookie so we stay logged in.
+ if (!IsEmptyStr(c->auth)) {
+ char koekje[AUTH_MAX];
+ char *exp = http_datestring(time(NULL) + (86400 * 30));
+ snprintf(koekje, AUTH_MAX, "wcauth=%s; path=/ctdl/; Expires=%s", c->auth, exp);
+ free(exp);
+ add_response_header(h, strdup("Set-Cookie"), strdup(koekje));
+ }
+
+ // During development we are foiling the browser cache completely. In production we'll be more selective.
+ add_response_header(h, strdup("Cache-Control"), strdup("no-store, must-revalidate"));
+ add_response_header(h, strdup("Pragma"), strdup("no-cache"));
+ add_response_header(h, strdup("Expires"), strdup("0"));
+
+ // Unbind from our Citadel server connection.
+ disconnect_from_citadel(c);
+}
--- /dev/null
+/*
+ * Room functions
+ *
+ * Copyright (c) 1996-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * Return a "zero-terminated" array of message numbers in the current room.
+ * Caller owns the memory and must free it. Returns NULL if any problems.
+ */
+long *get_msglist(struct ctdlsession *c, char *which_msgs)
+{
+ char buf[1024];
+ long *msglist = NULL;
+ int num_msgs = 0;
+ int num_alloc = 0;
+
+ ctdl_printf(c, "MSGS %s", which_msgs);
+ ctdl_readline(c, buf, sizeof(buf));
+ if (buf[0] == '1') do
+ {
+ if (num_msgs >= num_alloc) {
+ if (num_alloc == 0) {
+ num_alloc = 1024;
+ msglist = malloc(num_alloc * sizeof(long));
+ }
+ else {
+ num_alloc *= 2;
+ msglist = realloc(msglist, num_alloc * sizeof(long));
+ }
+ }
+ ctdl_readline(c, buf, sizeof(buf));
+ msglist[num_msgs++] = atol(buf);
+ } while (strcmp(buf, "000")); // this makes the last element a "0" terminator
+ return msglist;
+}
+
+
+/*
+ * Supplied with a list of potential matches from an If-Match: or If-None-Match: header, and
+ * a message number (which we always use as the entity tag in Citadel), return nonzero if the
+ * message number matches any of the supplied tags in the string.
+ */
+int match_etags(char *taglist, long msgnum)
+{
+ int num_tags = num_tokens(taglist, ',');
+ int i=0;
+ char tag[1024];
+
+ if (msgnum <= 0) { // no msgnum? no match.
+ return(0);
+ }
+
+ for (i=0; i<num_tags; ++i) {
+ extract_token(tag, taglist, i, ',', sizeof tag);
+ striplt(tag);
+ char *lq = (strchr(tag, '"'));
+ char *rq = (strrchr(tag, '"'));
+ if (lq < rq) { // has two double quotes
+ strcpy(rq, "");
+ strcpy(tag, ++lq);
+ }
+ striplt(tag);
+ if (!strcmp(tag, "*")) {
+ return(1); // wildcard match
+ }
+ long tagmsgnum = atol(tag);
+ if ( (tagmsgnum > 0) && (tagmsgnum == msgnum) ) { // match
+ return(1);
+ }
+ }
+
+ return(0); // no match
+}
+
+
+/*
+ * Client is requesting a message list
+ */
+void json_msglist(struct http_transaction *h, struct ctdlsession *c, char *which)
+{
+ int i = 0;
+ long *msglist = get_msglist(c, which);
+ JsonValue *j = NewJsonArray(HKEY("msgs"));
+
+ if (msglist != NULL) {
+ for (i=0; msglist[i]>0 ; ++i) {
+ JsonArrayAppend(j, NewJsonNumber( HKEY("m"), msglist[i]));
+ }
+ free(msglist);
+ }
+
+ StrBuf *sj = NewStrBuf();
+ SerializeJson(sj, j, 1); // '1' == free the source array
+
+ add_response_header(h, strdup("Content-type"), strdup("application/json"));
+ h->response_code = 200;
+ h->response_string = strdup("OK");
+ h->response_body_length = StrLength(sj);
+ h->response_body = SmashStrBuf(&sj);
+ return;
+
+
+}
+
+
+/*
+ * Client requested an object in a room.
+ */
+void object_in_room(struct http_transaction *h, struct ctdlsession *c)
+{
+ char buf[1024];
+ long msgnum = (-1);
+ char unescaped_euid[1024];
+
+ extract_token(buf, h->uri, 4, '/', sizeof buf);
+
+ if (!strncasecmp(buf, "msgs.", 5)) { // Client is requesting a list of message numbers
+ json_msglist(h, c, &buf[5]);
+ return;
+ }
+
+ if (!strncasecmp(buf, "threads", 5)) { // Client is requesting a threaded view (still kind of fuzzy here)
+ threaded_view(h, c, &buf[5]);
+ return;
+ }
+
+ if ( (c->room_default_view == VIEW_CALENDAR) // room types where objects are referenced by EUID
+ || (c->room_default_view == VIEW_TASKS)
+ || (c->room_default_view == VIEW_ADDRESSBOOK)
+ ) {
+ safestrncpy(unescaped_euid, buf, sizeof unescaped_euid);
+ unescape_input(unescaped_euid);
+ msgnum = locate_message_by_uid(c, unescaped_euid);
+ }
+ else {
+ msgnum = atol(buf);
+ }
+
+ /*
+ * All methods except PUT require the message to already exist
+ */
+ if ( (msgnum <= 0) && (strcasecmp(h->method, "PUT")) ) {
+ do_404(h);
+ }
+
+ /*
+ * If we get to this point we have a valid message number in an accessible room.
+ */
+ syslog(LOG_DEBUG, "msgnum is %ld, method is %s", msgnum, h->method);
+
+
+ /*
+ * Was the client actually requesting a specific component within the message?
+ */
+ if (num_tokens(h->uri, '/') == 6) {
+ extract_token(buf, h->uri, 5, '/', sizeof buf);
+ if (!IsEmptyStr(buf)) {
+ download_mime_component(h, c, msgnum, buf);
+ return;
+ }
+ }
+
+ /*
+ * Ok, we want a full message, but first let's check for the if[-none]-match headers.
+ */
+ char *if_match = header_val(h, "If-Match");
+ if ( (if_match != NULL) && (!match_etags(if_match, msgnum)) ) {
+ do_412(h);
+ return;
+ }
+
+ char *if_none_match = header_val(h, "If-None-Match");
+ if ( (if_none_match != NULL) && (match_etags(if_none_match, msgnum)) ) {
+ do_412(h);
+ return;
+ }
+
+ /*
+ * DOOOOOO ITTTTT!!!
+ */
+
+ if (!strcasecmp(h->method, "DELETE")) {
+ dav_delete_message(h, c, msgnum);
+ }
+ else if (!strcasecmp(h->method, "GET")) {
+ dav_get_message(h, c, msgnum);
+ }
+ else if (!strcasecmp(h->method, "PUT")) {
+ dav_put_message(h, c, unescaped_euid, msgnum);
+ }
+ else {
+ do_404(h); // Got this far but the method made no sense? Bummer.
+ }
+
+}
+
+
+/*
+ * Called by the_room_itself() when the HTTP method is REPORT
+ */
+void report_the_room_itself(struct http_transaction *h, struct ctdlsession *c)
+{
+ if (c->room_default_view == VIEW_CALENDAR) {
+ caldav_report(h, c); // CalDAV REPORTs ... fmgwac
+ return;
+ }
+
+ do_404(h); // future implementations like CardDAV will require code paths here
+}
+
+
+/*
+ * Called by the_room_itself() when the HTTP method is OPTIONS
+ */
+void options_the_room_itself(struct http_transaction *h, struct ctdlsession *c)
+{
+ h->response_code = 200;
+ h->response_string = strdup("OK");
+ if (c->room_default_view == VIEW_CALENDAR) {
+ add_response_header(h, strdup("DAV"), strdup("1, calendar-access")); // offer CalDAV
+ }
+ else if (c->room_default_view == VIEW_ADDRESSBOOK) {
+ add_response_header(h, strdup("DAV"), strdup("1, addressbook")); // offer CardDAV
+ }
+ else {
+ add_response_header(h, strdup("DAV"), strdup("1")); // ordinary WebDAV for all other room types
+ }
+ add_response_header(h, strdup("Allow"), strdup("OPTIONS, PROPFIND, GET, PUT, REPORT, DELETE"));
+}
+
+
+/*
+ * Called by the_room_itself() when the HTTP method is PROPFIND
+ */
+void propfind_the_room_itself(struct http_transaction *h, struct ctdlsession *c)
+{
+ char *e;
+ long timestamp;
+ int dav_depth = (header_val(h, "Depth") ? atoi(header_val(h, "Depth")) : INT_MAX);
+ syslog(LOG_DEBUG, "Client PROPFIND requested depth: %d", dav_depth);
+ StrBuf *Buf = NewStrBuf();
+
+ StrBufAppendPrintf(Buf, "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<D:multistatus "
+ "xmlns:D=\"DAV:\" "
+ "xmlns:C=\"urn:ietf:params:xml:ns:caldav\""
+ ">"
+ );
+
+ /* Transmit the collection resource */
+ StrBufAppendPrintf(Buf, "<D:response>");
+ StrBufAppendPrintf(Buf, "<D:href>");
+ StrBufXMLEscAppend(Buf, NULL, h->site_prefix, strlen(h->site_prefix), 0);
+ StrBufAppendPrintf(Buf, "/ctdl/r/");
+ StrBufXMLEscAppend(Buf, NULL, c->room, strlen(c->room), 0);
+ StrBufAppendPrintf(Buf, "</D:href>");
+
+ StrBufAppendPrintf(Buf, "<D:propstat>");
+ StrBufAppendPrintf(Buf, "<D:status>HTTP/1.1 200 OK</D:status>");
+ StrBufAppendPrintf(Buf, "<D:prop>");
+ StrBufAppendPrintf(Buf, "<D:displayname>");
+ StrBufXMLEscAppend(Buf, NULL, c->room, strlen(c->room), 0);
+ StrBufAppendPrintf(Buf, "</D:displayname>");
+
+ StrBufAppendPrintf(Buf, "<D:owner />"); // empty owner ought to be legal; see rfc3744 section 5.1
+
+ StrBufAppendPrintf(Buf, "<D:resourcetype><D:collection />");
+ switch(c->room_default_view) {
+ case VIEW_CALENDAR:
+ StrBufAppendPrintf(Buf, "<C:calendar />"); // RFC4791 section 4.2
+ break;
+ }
+ StrBufAppendPrintf(Buf, "</D:resourcetype>");
+
+ int enumerate_by_euid = 0; // nonzero if messages will be retrieved by euid instead of msgnum
+ switch(c->room_default_view) {
+ case VIEW_CALENDAR: // RFC4791 section 5.2
+ StrBufAppendPrintf(Buf, "<C:supported-calendar-component-set><C:comp name=\"VEVENT\"/></C:supported-calendar-component-set>");
+ StrBufAppendPrintf(Buf, "<C:supported-calendar-data>");
+ StrBufAppendPrintf(Buf, "<C:calendar-data content-type=\"text/calendar\" version=\"2.0\"/>");
+ StrBufAppendPrintf(Buf, "</C:supported-calendar-data>");
+ enumerate_by_euid = 1;
+ break;
+ case VIEW_TASKS: // RFC4791 section 5.2
+ StrBufAppendPrintf(Buf, "<C:supported-calendar-component-set><C:comp name=\"VTODO\"/></C:supported-calendar-component-set>");
+ StrBufAppendPrintf(Buf, "<C:supported-calendar-data>");
+ StrBufAppendPrintf(Buf, "<C:calendar-data content-type=\"text/calendar\" version=\"2.0\"/>");
+ StrBufAppendPrintf(Buf, "</C:supported-calendar-data>");
+ enumerate_by_euid = 1;
+ break;
+ case VIEW_ADDRESSBOOK: // FIXME put some sort of CardDAV crapola here when we implement it
+ enumerate_by_euid = 1;
+ break;
+ case VIEW_WIKI: // FIXME invent "WikiDAV" ?
+ enumerate_by_euid = 1;
+ break;
+ }
+
+
+ /* FIXME get the mtime
+ StrBufAppendPrintf(Buf, "<D:getlastmodified>");
+ escputs(datestring);
+ StrBufAppendPrintf(Buf, "</D:getlastmodified>");
+ */
+
+ StrBufAppendPrintf(Buf, "</D:prop>");
+ StrBufAppendPrintf(Buf, "</D:propstat>");
+ StrBufAppendPrintf(Buf, "</D:response>\n");
+
+ // If a depth greater than zero was specified, transmit the collection listing
+ // BEGIN COLLECTION
+ if (dav_depth > 0) {
+ long *msglist = get_msglist(c, "ALL");
+ if (msglist) {
+ int i;
+ for (i=0; (msglist[i] > 0); ++i) {
+ if ((i%10) == 0) syslog(LOG_DEBUG, "PROPFIND enumerated %d messages", i);
+ e = NULL; // EUID gets stored here
+ timestamp = 0;
+
+ char cbuf[1024];
+ ctdl_printf(c, "MSG0 %ld|3", msglist[i]);
+ ctdl_readline(c, cbuf, sizeof(cbuf));
+ if (cbuf[0] == '1') while (ctdl_readline(c, cbuf, sizeof(cbuf)), strcmp(cbuf, "000")) {
+ if ( (enumerate_by_euid) && (!strncasecmp(cbuf, "exti=", 5)) ) {
+ // e = strdup(&cbuf[5]);
+ int elen = (2 * strlen(&cbuf[5]));
+ e = malloc(elen);
+ urlesc(e, elen, &cbuf[5]);
+ }
+ if (!strncasecmp(cbuf, "time=", 5)) {
+ timestamp = atol(&cbuf[5]);
+ }
+ }
+ if (e == NULL) {
+ e = malloc(20);
+ sprintf(e, "%ld", msglist[i]);
+ }
+ StrBufAppendPrintf(Buf, "<D:response>");
+
+ // Generate the 'href' tag for this message
+ StrBufAppendPrintf(Buf, "<D:href>");
+ StrBufXMLEscAppend(Buf, NULL, h->site_prefix, strlen(h->site_prefix), 0);
+ StrBufAppendPrintf(Buf, "/ctdl/r/");
+ StrBufXMLEscAppend(Buf, NULL, c->room, strlen(c->room), 0);
+ StrBufAppendPrintf(Buf, "/");
+ StrBufXMLEscAppend(Buf, NULL, e, strlen(e), 0);
+ StrBufAppendPrintf(Buf, "</D:href>");
+
+ switch(c->room_default_view) {
+ case VIEW_CALENDAR:
+ StrBufAppendPrintf(Buf, "<D:getcontenttype>text/calendar; component=vevent</D:getcontenttype>");
+ break;
+ case VIEW_TASKS:
+ StrBufAppendPrintf(Buf, "<D:getcontenttype>text/calendar; component=vtodo</D:getcontenttype>");
+ break;
+ case VIEW_ADDRESSBOOK:
+ StrBufAppendPrintf(Buf, "<D:getcontenttype>text/x-vcard</D:getcontenttype>");
+ break;
+ }
+
+ StrBufAppendPrintf(Buf, "<D:propstat>");
+ StrBufAppendPrintf(Buf, "<D:status>HTTP/1.1 200 OK</D:status>");
+ StrBufAppendPrintf(Buf, "<D:prop>");
+ if (timestamp > 0) {
+ char *datestring = http_datestring(timestamp);
+ if (datestring) {
+ StrBufAppendPrintf(Buf, "<D:getlastmodified>");
+ StrBufXMLEscAppend(Buf, NULL, datestring, strlen(datestring), 0);
+ StrBufAppendPrintf(Buf, "</D:getlastmodified>");
+ free(datestring);
+ }
+ if (enumerate_by_euid) {
+ StrBufAppendPrintf(Buf, "<D:getetag>\"%ld\"</D:getetag>", msglist[i]);
+ }
+ }
+ StrBufAppendPrintf(Buf, "</D:prop></D:propstat></D:response>\n");
+ free(e);
+ }
+ free(msglist);
+ };
+ }
+ // END COLLECTION
+
+ StrBufAppendPrintf(Buf, "</D:multistatus>\n");
+
+ add_response_header(h, strdup("Content-type"), strdup("text/xml"));
+ h->response_code = 207;
+ h->response_string = strdup("Multi-Status");
+ h->response_body_length = StrLength(Buf);
+ h->response_body = SmashStrBuf(&Buf);
+}
+
+// some good examples here
+// http://blogs.nologin.es/rickyepoderi/index.php?/archives/14-Introducing-CalDAV-Part-I.html
+
+
+/*
+ * Called by the_room_itself() when the HTTP method is PROPFIND
+ */
+void get_the_room_itself(struct http_transaction *h, struct ctdlsession *c)
+{
+ JsonValue *j = NewJsonObject(HKEY("gotoroom"));
+
+ JsonObjectAppend(j, NewJsonPlainString( HKEY("name"), c->room, -1));
+ JsonObjectAppend(j, NewJsonNumber( HKEY("current_view"), c->room_current_view ));
+ JsonObjectAppend(j, NewJsonNumber( HKEY("default_view"), c->room_default_view ));
+ JsonObjectAppend(j, NewJsonNumber( HKEY("new_messages"), c->new_messages ));
+ JsonObjectAppend(j, NewJsonNumber( HKEY("total_messages"), c->total_messages ));
+
+ StrBuf *sj = NewStrBuf();
+ SerializeJson(sj, j, 1); // '1' == free the source array
+
+ add_response_header(h, strdup("Content-type"), strdup("application/json"));
+ h->response_code = 200;
+ h->response_string = strdup("OK");
+ h->response_body_length = StrLength(sj);
+ h->response_body = SmashStrBuf(&sj);
+ return;
+}
+
+
+/*
+ * Handle REST/DAV requests for the room itself (such as /ctdl/r/roomname
+ * or /ctdl/r/roomname/ but *not* specific objects within the room)
+ */
+void the_room_itself(struct http_transaction *h, struct ctdlsession *c)
+{
+ // OPTIONS method on the room itself usually is a DAV client assessing what's here.
+
+ if (!strcasecmp(h->method, "OPTIONS")) {
+ options_the_room_itself(h, c);
+ return;
+ }
+
+ // PROPFIND method on the room itself could be looking for a directory
+
+ if (!strcasecmp(h->method, "PROPFIND")) {
+ propfind_the_room_itself(h, c);
+ return;
+ }
+
+ // REPORT method on the room itself is probably the dreaded CalDAV tower-of-crapola
+
+ if (!strcasecmp(h->method, "REPORT")) {
+ report_the_room_itself(h, c);
+ return;
+ }
+
+ // GET method on the room itself is an API call, possibly from our JavaScript front end
+
+ if (!strcasecmp(h->method, "get")) {
+ get_the_room_itself(h, c);
+ return;
+ }
+
+ // we probably want a "go to this room" for interactive access
+ do_404(h);
+}
+
+
+/*
+ * Dispatcher for "/ctdl/r" and "/ctdl/r/" for the room list
+ */
+void room_list(struct http_transaction *h, struct ctdlsession *c)
+{
+ char buf[1024];
+ char roomname[1024];
+
+ ctdl_printf(c, "LKRA");
+ ctdl_readline(c, buf, sizeof(buf));
+ if (buf[0] != '1') {
+ do_502(h);
+ return;
+ }
+
+ JsonValue *j = NewJsonArray(HKEY("lkra"));
+ while (ctdl_readline(c, buf, sizeof(buf)) , strcmp(buf, "000")) {
+
+ // name|QRflags|QRfloor|QRorder|QRflags2|ra|current_view|default_view|mtime
+ JsonValue *jr = NewJsonObject(HKEY("room"));
+
+ extract_token(roomname, buf, 0, '|', sizeof roomname);
+ JsonObjectAppend(jr, NewJsonPlainString( HKEY("name"), roomname, -1));
+
+ int ra = extract_int(buf, 5);
+ JsonObjectAppend(jr, NewJsonBool( HKEY("known"), (ra && UA_KNOWN)));
+ JsonObjectAppend(jr, NewJsonBool( HKEY("hasnewmsgs"), (ra && UA_HASNEWMSGS)));
+
+ int floor = extract_int(buf, 2);
+ JsonObjectAppend(jr, NewJsonNumber( HKEY("floor"), floor));
+
+ int rorder = extract_int(buf, 3);
+ JsonObjectAppend(jr, NewJsonNumber( HKEY("rorder"), rorder));
+
+ JsonArrayAppend(j, jr); // add the room to the array
+ }
+
+ StrBuf *sj = NewStrBuf();
+ SerializeJson(sj, j, 1); // '1' == free the source array
+
+ add_response_header(h, strdup("Content-type"), strdup("application/json"));
+ h->response_code = 200;
+ h->response_string = strdup("OK");
+ h->response_body_length = StrLength(sj);
+ h->response_body = SmashStrBuf(&sj);
+}
+
+
+/*
+ * Dispatcher for paths starting with /ctdl/r/
+ */
+void ctdl_r(struct http_transaction *h, struct ctdlsession *c)
+{
+ char requested_roomname[128];
+ char buf[1024];
+
+ // All room-related functions require being "in" the room specified. Are we in that room already?
+ extract_token(requested_roomname, h->uri, 3, '/', sizeof requested_roomname);
+ unescape_input(requested_roomname);
+
+ if (IsEmptyStr(requested_roomname)) { // /ctdl/r/
+ room_list(h, c);
+ return;
+ }
+
+ // If not, try to go there.
+ if (strcasecmp(requested_roomname, c->room)) {
+ ctdl_printf(c, "GOTO %s", requested_roomname);
+ ctdl_readline(c, buf, sizeof(buf));
+ if (buf[0] == '2') {
+ // buf[3] will indicate whether any instant messages are waiting
+ extract_token(c->room, &buf[4], 0, '|', sizeof c->room);
+ c->new_messages = extract_int(&buf[4], 1);
+ c->total_messages = extract_int(&buf[4], 2);
+ // 3 (int)info Info flag: set to nonzero if the user needs to read this room's info file
+ // 4 (int)CCC->room.QRflags Various flags associated with this room.
+ // 5 (long)CCC->room.QRhighest The highest message number present in this room
+ // 6 (long)vbuf.v_lastseen The highest message number the user has read in this room
+ // 7 (int)rmailflag Boolean flag: 1 if this is a Mail> room, 0 otherwise.
+ // 8 (int)raideflag Nonzero if user is either Aide or a Room Aide in this room
+ // 9 (int)newmailcount The number of new Mail messages the user has
+ // 10 (int)CCC->room.QRfloor The floor number this room resides on
+ c->room_current_view = extract_int(&buf[4], 11);
+ c->room_default_view = extract_int(&buf[4], 12);
+ // 13 (int)is_trash Boolean flag: 1 if this is the user's Trash folder, 0 otherwise.
+ // 14 (int)CCC->room.QRflags2 More flags associated with this room
+ // 15 (long)CCC->room.QRmtime Timestamp of the last write activity in this room
+ }
+ else {
+ do_404(h);
+ return;
+ }
+ }
+
+ // At this point our Citadel client session is "in" the specified room.
+
+ if (num_tokens(h->uri, '/') == 4) { // /ctdl/r/roomname
+ the_room_itself(h, c);
+ return;
+ }
+
+ extract_token(buf, h->uri, 4, '/', sizeof buf);
+ if (num_tokens(h->uri, '/') == 5) {
+ if (IsEmptyStr(buf)) {
+ the_room_itself(h, c); // /ctdl/r/roomname/ ( same as /ctdl/r/roomname )
+ }
+ else {
+ object_in_room(h, c); // /ctdl/r/roomname/object
+ }
+ return;
+ }
+ if (num_tokens(h->uri, '/') == 6) {
+ object_in_room(h, c); // /ctdl/r/roomname/object/ or possibly /ctdl/r/roomname/object/component
+ return;
+ }
+
+ // If we get to this point, the client specified a valid room but requested an action we don't know how to perform.
+ do_404(h);
+}
--- /dev/null
+/*
+ * Functions in this module handle SSL encryption when WebCit is running
+ * as an HTTPS server.
+ *
+ * Copyright (c) 1996-2016 by the citadel.org team
+ *
+ * This program is open source software. You can redistribute it and/or
+ * modify it under the terms of the GNU General Public License, version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+SSL_CTX *ssl_ctx; /* SSL context */
+pthread_mutex_t **SSLCritters; /* Things needing locking */
+char *ssl_cipher_list = DEFAULT_SSL_CIPHER_LIST;
+void ssl_lock(int mode, int n, const char *file, int line);
+
+
+/*
+ * OpenSSL wants a callback function to identify the currently running thread.
+ * Since we are a pthreads program, we convert the output of pthread_self() to a long.
+ */
+static unsigned long id_callback(void)
+{
+ return (unsigned long) pthread_self();
+}
+
+
+/*
+ * OpenSSL wants a callback function to set and clear various types of locks.
+ * Since we are a pthreads program, we use mutexes.
+ */
+void ssl_lock(int mode, int n, const char *file, int line)
+{
+ if (mode & CRYPTO_LOCK) {
+ pthread_mutex_lock(SSLCritters[n]);
+ }
+ else {
+ pthread_mutex_unlock(SSLCritters[n]);
+ }
+}
+
+
+/*
+ * Initialize ssl engine, load certs and initialize openssl internals
+ */
+void init_ssl(void)
+{
+ const SSL_METHOD *ssl_method;
+ RSA *rsa=NULL;
+ X509_REQ *req = NULL;
+ X509 *cer = NULL;
+ EVP_PKEY *pk = NULL;
+ EVP_PKEY *req_pkey = NULL;
+ X509_NAME *name = NULL;
+ FILE *fp;
+ char buf[SIZ];
+ int rv = 0;
+
+ if (!access("/var/run/egd-pool", F_OK)) {
+ RAND_egd("/var/run/egd-pool");
+ }
+
+ if (!RAND_status()) {
+ syslog(LOG_WARNING, "PRNG not adequately seeded, won't do SSL/TLS");
+ return;
+ }
+
+ SSLCritters = malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t *));
+ if (!SSLCritters) {
+ syslog(LOG_ERR, "citserver: can't allocate memory!!");
+ exit(1);
+ } else {
+ int a;
+
+ for (a = 0; a < CRYPTO_num_locks(); a++) {
+ SSLCritters[a] = malloc(sizeof(pthread_mutex_t));
+ if (!SSLCritters[a]) {
+ syslog(LOG_INFO, "citserver: can't allocate memory!!");
+ exit(1);
+ }
+ pthread_mutex_init(SSLCritters[a], NULL);
+ }
+ }
+
+ /*
+ * Initialize SSL transport layer
+ */
+ SSL_library_init();
+ SSL_load_error_strings();
+ ssl_method = SSLv23_server_method();
+ if (!(ssl_ctx = SSL_CTX_new(ssl_method))) {
+ syslog(LOG_WARNING, "SSL_CTX_new failed: %s", ERR_reason_error_string(ERR_get_error()));
+ return;
+ }
+
+ syslog(LOG_INFO, "Requesting cipher list: %s", ssl_cipher_list);
+ if (!(SSL_CTX_set_cipher_list(ssl_ctx, ssl_cipher_list))) {
+ syslog(LOG_WARNING, "SSL_CTX_set_cipher_list failed: %s", ERR_reason_error_string(ERR_get_error()));
+ return;
+ }
+
+ CRYPTO_set_locking_callback(ssl_lock);
+ CRYPTO_set_id_callback(id_callback);
+
+ /*
+ * Get our certificates in order.
+ * First, create the key/cert directory if it's not there already...
+ */
+ mkdir(CTDL_CRYPTO_DIR, 0700);
+
+ /*
+ * If we still don't have a private key, generate one.
+ */
+ if (access(CTDL_KEY_PATH, R_OK) != 0) {
+ syslog(LOG_INFO, "Generating RSA key pair.\n");
+ rsa = RSA_generate_key(2048, /* modulus size */
+ 65537, /* exponent */
+ NULL, /* no callback */
+ NULL /* no callback */
+ );
+ if (rsa == NULL) {
+ syslog(LOG_WARNING, "Key generation failed: %s", ERR_reason_error_string(ERR_get_error()));
+ }
+ if (rsa != NULL) {
+ fp = fopen(CTDL_KEY_PATH, "w");
+ if (fp != NULL) {
+ chmod(CTDL_KEY_PATH, 0600);
+ if (PEM_write_RSAPrivateKey(fp, /* the file */
+ rsa, /* the key */
+ NULL, /* no enc */
+ NULL, /* no passphr */
+ 0, /* no passphr */
+ NULL, /* no callbk */
+ NULL /* no callbk */
+ ) != 1) {
+ syslog(LOG_WARNING, "Cannot write key: %s", ERR_reason_error_string(ERR_get_error()));
+ unlink(CTDL_KEY_PATH);
+ }
+ fclose(fp);
+ }
+ else {
+ syslog(LOG_WARNING, "Cannot write key: %s", CTDL_KEY_PATH);
+ exit(1);
+ }
+ RSA_free(rsa);
+ }
+ }
+
+ /*
+ * If there is no certificate file on disk, we will be generating a self-signed certificate
+ * in the next step. Therefore, if we have neither a CSR nor a certificate, generate
+ * the CSR in this step so that the next step may commence.
+ */
+ if ( (access(CTDL_CER_PATH, R_OK) != 0) && (access(CTDL_CSR_PATH, R_OK) != 0) ) {
+ syslog(LOG_INFO, "Generating a certificate signing request.");
+
+ /*
+ * Read our key from the file. No, we don't just keep this
+ * in memory from the above key-generation function, because
+ * there is the possibility that the key was already on disk
+ * and we didn't just generate it now.
+ */
+ fp = fopen(CTDL_KEY_PATH, "r");
+ if (fp) {
+ rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
+ fclose(fp);
+ }
+
+ if (rsa) {
+
+ /* Create a public key from the private key */
+ if (pk=EVP_PKEY_new(), pk != NULL) {
+ EVP_PKEY_assign_RSA(pk, rsa);
+ if (req = X509_REQ_new(), req != NULL) {
+ const char *env;
+ /* Set the public key */
+ X509_REQ_set_pubkey(req, pk);
+ X509_REQ_set_version(req, 0L);
+ name = X509_REQ_get_subject_name(req);
+ X509_NAME_add_entry_by_txt(
+ name, "O", MBSTRING_ASC,
+ (unsigned char*)"Citadel Server",
+ -1, -1, 0
+ );
+ X509_NAME_add_entry_by_txt(
+ name, "OU", MBSTRING_ASC,
+ (unsigned char*)"Default Certificate PLEASE CHANGE",
+ -1, -1, 0
+ );
+ X509_NAME_add_entry_by_txt(
+ name, "CN",
+ MBSTRING_ASC,
+ (unsigned char*)"*",
+ -1, -1, 0
+ );
+
+ X509_REQ_set_subject_name(req, name);
+
+ /* Sign the CSR */
+ if (!X509_REQ_sign(req, pk, EVP_md5())) {
+ syslog(LOG_WARNING, "X509_REQ_sign(): error");
+ }
+ else {
+ /* Write it to disk. */
+ fp = fopen(CTDL_CSR_PATH, "w");
+ if (fp != NULL) {
+ chmod(CTDL_CSR_PATH, 0600);
+ PEM_write_X509_REQ(fp, req);
+ fclose(fp);
+ }
+ else {
+ syslog(LOG_WARNING, "Cannot write key: %s", CTDL_CSR_PATH);
+ exit(1);
+ }
+ }
+
+ X509_REQ_free(req);
+ }
+ }
+
+ RSA_free(rsa);
+ }
+
+ else {
+ syslog(LOG_WARNING, "Unable to read private key.");
+ }
+ }
+
+
+ /*
+ * Generate a self-signed certificate if we don't have one.
+ */
+ if (access(CTDL_CER_PATH, R_OK) != 0) {
+ syslog(LOG_INFO, "Generating a self-signed certificate.");
+
+ /* Same deal as before: always read the key from disk because
+ * it may or may not have just been generated.
+ */
+ fp = fopen(CTDL_KEY_PATH, "r");
+ if (fp) {
+ rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
+ fclose(fp);
+ }
+
+ /* This also holds true for the CSR. */
+ req = NULL;
+ cer = NULL;
+ pk = NULL;
+ if (rsa) {
+ if (pk=EVP_PKEY_new(), pk != NULL) {
+ EVP_PKEY_assign_RSA(pk, rsa);
+ }
+
+ fp = fopen(CTDL_CSR_PATH, "r");
+ if (fp) {
+ req = PEM_read_X509_REQ(fp, NULL, NULL, NULL);
+ fclose(fp);
+ }
+
+ if (req) {
+ if (cer = X509_new(), cer != NULL) {
+
+ ASN1_INTEGER_set(X509_get_serialNumber(cer), 0);
+ X509_set_issuer_name(cer, req->req_info->subject);
+ X509_set_subject_name(cer, req->req_info->subject);
+ X509_gmtime_adj(X509_get_notBefore(cer), 0);
+ X509_gmtime_adj(X509_get_notAfter(cer),(long)60*60*24*SIGN_DAYS);
+
+ req_pkey = X509_REQ_get_pubkey(req);
+ X509_set_pubkey(cer, req_pkey);
+ EVP_PKEY_free(req_pkey);
+
+ /* Sign the cert */
+ if (!X509_sign(cer, pk, EVP_md5())) {
+ syslog(LOG_WARNING, "X509_sign(): error");
+ }
+ else {
+ /* Write it to disk. */
+ fp = fopen(CTDL_CER_PATH, "w");
+ if (fp != NULL) {
+ chmod(CTDL_CER_PATH, 0600);
+ PEM_write_X509(fp, cer);
+ fclose(fp);
+ }
+ else {
+ syslog(LOG_WARNING, "Cannot write key: %s", CTDL_CER_PATH);
+ exit(1);
+ }
+ }
+ X509_free(cer);
+ }
+ }
+
+ RSA_free(rsa);
+ }
+ }
+
+ /*
+ * Now try to bind to the key and certificate.
+ * Note that we use SSL_CTX_use_certificate_chain_file() which allows
+ * the certificate file to contain intermediate certificates.
+ */
+ SSL_CTX_use_certificate_chain_file(ssl_ctx, CTDL_CER_PATH);
+ SSL_CTX_use_PrivateKey_file(ssl_ctx, CTDL_KEY_PATH, SSL_FILETYPE_PEM);
+ if ( !SSL_CTX_check_private_key(ssl_ctx) ) {
+ syslog(LOG_WARNING, "Cannot install certificate: %s", ERR_reason_error_string(ERR_get_error()));
+ }
+
+}
+
+
+/*
+ * starts SSL/TLS encryption for the current session.
+ */
+void starttls(struct client_handle *ch) {
+ int retval, bits, alg_bits;
+
+ if (!ssl_ctx) {
+ return;
+ }
+ if (!(ch->ssl_handle = SSL_new(ssl_ctx))) {
+ syslog(LOG_WARNING, "SSL_new failed: %s", ERR_reason_error_string(ERR_get_error()));
+ return;
+ }
+ if (!(SSL_set_fd(ch->ssl_handle, ch->sock))) {
+ syslog(LOG_WARNING, "SSL_set_fd failed: %s", ERR_reason_error_string(ERR_get_error()));
+ SSL_free(ch->ssl_handle);
+ return;
+ }
+ retval = SSL_accept(ch->ssl_handle);
+ if (retval < 1) {
+ long errval;
+ const char *ssl_error_reason = NULL;
+
+ errval = SSL_get_error(ch->ssl_handle, retval);
+ ssl_error_reason = ERR_reason_error_string(ERR_get_error());
+ if (ssl_error_reason == NULL) {
+ syslog(LOG_WARNING, "SSL_accept failed: errval=%ld, retval=%d %s", errval, retval, strerror(errval));
+ }
+ else {
+ syslog(LOG_WARNING, "SSL_accept failed: %s\n", ssl_error_reason);
+ }
+ sleep(1);
+ retval = SSL_accept(ch->ssl_handle);
+ }
+ if (retval < 1) {
+ long errval;
+ const char *ssl_error_reason = NULL;
+
+ errval = SSL_get_error(ch->ssl_handle, retval);
+ ssl_error_reason = ERR_reason_error_string(ERR_get_error());
+ if (ssl_error_reason == NULL) {
+ syslog(LOG_WARNING, "SSL_accept failed: errval=%ld, retval=%d (%s)", errval, retval, strerror(errval));
+ }
+ else {
+ syslog(LOG_WARNING, "SSL_accept failed: %s", ssl_error_reason);
+ }
+ SSL_free(ch->ssl_handle);
+ ch->ssl_handle = NULL;
+ return;
+ }
+ else {
+ syslog(LOG_INFO, "SSL_accept success");
+ }
+ BIO_set_close(ch->ssl_handle->rbio, BIO_NOCLOSE);
+ bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(ch->ssl_handle), &alg_bits);
+ syslog(LOG_INFO, "SSL/TLS using %s on %s (%d of %d bits)",
+ SSL_CIPHER_get_name(SSL_get_current_cipher(ch->ssl_handle)),
+ SSL_CIPHER_get_version(SSL_get_current_cipher(ch->ssl_handle)),
+ bits, alg_bits);
+
+ syslog(LOG_INFO, "SSL started");
+}
+
+
+/*
+ * shuts down the TLS connection
+ */
+void endtls(struct client_handle *ch)
+{
+ syslog(LOG_INFO, "Ending SSL/TLS");
+ if (ch->ssl_handle != NULL) {
+ SSL_shutdown(ch->ssl_handle);
+ SSL_get_SSL_CTX(ch->ssl_handle);
+ SSL_free(ch->ssl_handle);
+ }
+ ch->ssl_handle = NULL;
+}
+
+
+/*
+ * Send binary data to the client encrypted.
+ */
+int client_write_ssl(struct client_handle *ch, char *buf, int nbytes)
+{
+ int retval;
+ int nremain;
+ char junk[1];
+
+ if (ch->ssl_handle == NULL) return(-1);
+
+ nremain = nbytes;
+ while (nremain > 0) {
+ if (SSL_want_write(ch->ssl_handle)) {
+ if ((SSL_read(ch->ssl_handle, junk, 0)) < 1) {
+ syslog(LOG_WARNING, "SSL_read in client_write: %s", ERR_reason_error_string(ERR_get_error()));
+ }
+ }
+ retval = SSL_write(ch->ssl_handle, &buf[nbytes - nremain], nremain);
+ if (retval < 1) {
+ long errval;
+
+ errval = SSL_get_error(ch->ssl_handle, retval);
+ if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
+ sleep(1);
+ continue;
+ }
+ syslog(LOG_WARNING, "SSL_write got error %ld, ret %d", errval, retval);
+ if (retval == -1) {
+ syslog(LOG_WARNING, "errno is %d", errno);
+ endtls(ch);
+ }
+ return -1;
+ }
+ nremain -= retval;
+ }
+ return 0;
+}
+
+
+/*
+ * read data from the encrypted layer.
+ */
+int client_read_ssl(struct client_handle *ch, char *buf, int nbytes)
+{
+ int bytes_read = 0;
+ int rlen = 0;
+ char junk[1];
+
+ if (ch->ssl_handle == NULL) return(-1);
+
+ while (bytes_read < nbytes) {
+ if (SSL_want_read(ch->ssl_handle)) {
+ if ((SSL_write(ch->ssl_handle, junk, 0)) < 1) {
+ syslog(LOG_WARNING, "SSL_write in client_read");
+ }
+ }
+ rlen = SSL_read(ch->ssl_handle, &buf[bytes_read], nbytes-bytes_read);
+ if (rlen < 1) {
+ long errval;
+
+ errval = SSL_get_error(ch->ssl_handle, rlen);
+ if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
+ sleep(1);
+ continue;
+ }
+ syslog(LOG_WARNING, "SSL_read error %ld", errval);
+ endtls(ch);
+ return (-1);
+ }
+ bytes_read += rlen;
+ }
+ return(bytes_read);
+}
--- /dev/null
+/*
+ * Output static content
+ *
+ * Copyright (c) 1996-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * Called from perform_request() to handle the /ctdl/s/ prefix -- always static content.
+ */
+void output_static(struct http_transaction *h)
+{
+ char filename[PATH_MAX];
+ struct stat statbuf;
+
+ snprintf(filename, sizeof filename, "static/%s", &h->uri[8]);
+
+ if (strstr(filename, "../")) { // 100% guaranteed attacker.
+ do_404(h); // Die in a car fire.
+ return;
+ }
+
+ FILE *fp = fopen(filename, "r"); // Try to open the requested file.
+ if (fp == NULL) {
+ do_404(h);
+ return;
+ }
+
+ fstat(fileno(fp), &statbuf);
+ h->response_body_length = statbuf.st_size;
+ h->response_body = malloc(h->response_body_length);
+ if (h->response_body != NULL) {
+ fread(h->response_body, h->response_body_length, 1, fp);
+ }
+ else {
+ h->response_body_length = 0;
+ }
+ fclose(fp); // Content is now in memory.
+
+ h->response_code = 200;
+ h->response_string = strdup("OK");
+ add_response_header(h, strdup("Content-type"), strdup(GuessMimeByFilename(filename, strlen(filename))));
+
+ char *datestring = http_datestring(statbuf.st_mtime);
+ if (datestring) {
+ add_response_header(h, strdup("Last-Modified"), datestring);
+ }
+}
--- /dev/null
+<!DOCTYPE html>
+<html>
+<title>W3.CSS Template</title>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="stylesheet" href="https://www.w3schools.com/lib/w3.css">
+<link rel="stylesheet" href="https://www.w3schools.com/lib/w3-theme-w3schools.css">
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
+<style>
+html,body,h1,h2,h3,h4,h5,h6 {font-family: "Roboto", sans-serif}
+.w3-sidenav a,.w3-sidenav h4 {padding: 12px;}
+.w3-bar a {
+ padding-top: 12px;
+ padding-bottom: 12px;
+}
+</style>
+<body>
+
+<!-- Navbar -->
+<div id="navbar" class="w3-top">
+ <div class="w3-bar w3-theme w3-top w3-left-align w3-large">
+ <a class="w3-bar-item w3-button w3-opennav w3-right w3-hide-large w3-hover-white w3-large w3-theme-l1" href="javascript:void(0)" onclick="w3_open()"><i class="fa fa-bars"></i></a>
+ <span id="ctdl_banner_title" class="w3-bar-item w3-button w3-theme-l1">XXX</span>
+ <a href="#" class="w3-bar-item w3-button w3-hide-small w3-hover-white">Ungoto</a>
+ <a href="#" class="w3-bar-item w3-button w3-hide-small w3-hover-white">Read new</a>
+ <a href="#" class="w3-bar-item w3-button w3-hide-small w3-hover-white">Read all</a>
+ <a href="#" class="w3-bar-item w3-button w3-hide-small w3-hover-white">Enter</a>
+ <a href="#" class="w3-bar-item w3-button w3-hide-small w3-hide-medium w3-hover-white">Skip</a>
+ <a href="#" class="w3-bar-item w3-button w3-hide-small w3-hide-medium w3-hover-white">Goto</a>
+ <a href="#" id="lilo" class="w3-bar-item w3-button w3-hide-small w3-hover-white">Login</a>
+ <span id="current_user" class="w3-bar-item w3-button w3-hide-small w3-hide-medium w3-hover-white">XXX</span>
+
+ </div>
+</div>
+
+<!-- Sidenav -->
+<nav class="w3-sidenav w3-collapse w3-theme-l5 w3-animate-left" style="z-index:3;width:250px;margin-top:43px;" id="sidebar">
+ <a href="javascript:void(0)" onclick="w3_close()" class="w3-right w3-xlarge w3-padding-large w3-hover-black w3-hide-large" title="close menu">
+ <i class="fa fa-remove"></i>
+ </a>
+ <h4><b>Menu</b></h4>
+ <a href="#" class="w3-hover-black">Link</a>
+ <a href="#" class="w3-hover-black">Link</a>
+ <a href="#" class="w3-hover-black">Link</a>
+ <a href="#" class="w3-hover-black">Link</a>
+</nav>
+
+<!-- Overlay effect when opening sidenav on small screens -->
+<div class="w3-overlay w3-hide-large" onclick="w3_close()" style="cursor:pointer" title="close side menu" id="myOverlay"></div>
+
+<!-- Main content: shift it to the right by 250 pixels when the sidenav is visible -->
+<div id="main" class="w3-main" style="margin-left:250px">
+MAIN
+<!-- END MAIN -->
+</div>
+
+<script type="text/javascript" src="js/login.js"></script>
+<script type="text/javascript" src="js/main.js"></script>
+<script type="text/javascript" src="js/views.js"></script>
+<script>
+// Get the Sidenav
+var sidebar = document.getElementById("sidebar");
+
+// Get the DIV with overlay effect
+var overlayBg = document.getElementById("myOverlay");
+
+// Toggle between showing and hiding the sidenav, and add overlay effect
+function w3_open() {
+ if (sidebar.style.display === 'block') {
+ sidebar.style.display = 'none';
+ overlayBg.style.display = "none";
+ } else {
+ sidebar.style.display = 'block';
+ overlayBg.style.display = "block";
+ }
+}
+
+// Close the sidenav with the close button
+function w3_close() {
+ sidebar.style.display = "none";
+ overlayBg.style.display = "none";
+}
+
+alert('yup startup');
+ctdl_startup();
+</script>
+
+</body>
+</html>
+
+
--- /dev/null
+//
+// Copyright (c) 2016-2017 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// 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.
+
+
+function display_login_screen(any_message)
+{
+ document.getElementById("main").innerHTML =
+ "Put the login screen here, dummary<br><br>" +
+ any_message + "<br><br>" +
+ _("User name:") + "<input type=\"text\" id=\"username\"><br>" +
+ _("Password:") + "<input type=\"password\" id=\"password\"><br>" +
+ "<a href=\"javascript:login_button()\">" + _("Log in") + "</a>"
+ ;
+
+ update_banner();
+}
+
+
+function logout()
+{
+ var request = new XMLHttpRequest();
+ request.open("GET", "/ctdl/a/logout", true);
+ request.onreadystatechange = function() {
+ login_result(this.responseText);
+ };
+ request.send();
+ request = null;
+}
+
+
+function login_button(username)
+{
+ parms =
+ document.getElementById("username").value
+ + "|"
+ + document.getElementById("password").value
+ + "|"
+ ;
+
+ var request = new XMLHttpRequest();
+ request.open("POST", "/ctdl/a/login", true);
+ request.onreadystatechange = function() {
+ login_result(this.responseText);
+ };
+ request.send(parms);
+ request = null;
+}
+
+
+function login_result(data)
+{
+ if (data.substring(0,1) == "2") {
+ logged_in = 1;
+ current_user = data.substring(4).split("|")[0];
+ update_banner();
+ document.getElementById("main").innerHTML = "FIXME ok we are logged in as " + current_user + " ... " ;
+ }
+ else {
+ display_login_screen(data.substring(4));
+ }
+}
+
+
+// Detect whether the Citadel session is logged in as a user and update our internal variables accordingly.
+//
+function detect_logged_in()
+{
+ var request = new XMLHttpRequest();
+ request.open("GET", "/ctdl/a/whoami", true);
+ request.onreadystatechange = function() {
+ detect_logged_in_2(this.responseText);
+ };
+ request.send();
+ request = null;
+}
+function detect_logged_in_2(data)
+{
+ if (data.length > 0) {
+ logged_in = 1;
+ current_user = data;
+ }
+ else {
+ logged_in = 0;
+ current_user = _("Not logged in.");
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2016-2017 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// 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.
+
+
+var current_room = "_BASEROOM_";
+var new_messages = 0;
+var total_messages = 0;
+var default_view = 0;
+var current_view = 0;
+var logged_in = 0;
+var current_user = _("Not logged in.");
+var serv_info;
+
+
+// Placeholder for when we add i18n later
+function _(x) {
+ return x;
+}
+
+
+// Generate a random string of the specified length
+// Useful for generating one-time-use div names
+//
+function randomString(length) {
+ var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'.split('');
+
+ if (! length) {
+ length = Math.floor(Math.random() * chars.length);
+ }
+
+ var str = '';
+ for (var i = 0; i < length; i++) {
+ str += chars[Math.floor(Math.random() * chars.length)];
+ }
+ return str;
+}
+
+
+// string escape for html display
+//
+function escapeHTML(text) {
+ 'use strict';
+ return text.replace(/[\"&<>]/g, function (a) {
+ return {
+ '"': '"',
+ '&': '&',
+ '<': '<',
+ '>': '>'
+ }[a];
+ });
+}
+
+
+// string escape for html display
+//
+function escapeHTMLURI(text) {
+ 'use strict';
+ return text.replace(/./g, function (a) {
+ return '%' + a.charCodeAt(0).toString(16);
+ });
+}
+
+
+// string escape for JavaScript string
+//
+function escapeJS(text) {
+ 'use strict';
+ return text.replace(/[\"\']/g, function (a) {
+ return '\\' + a ;
+ });
+}
+
+
+// This is called at the very beginning of the main page load.
+//
+function ctdl_startup() {
+ var request = new XMLHttpRequest();
+ request.open("GET", "/ctdl/c/info", true);
+ request.onreadystatechange = function() {
+ if ((this.readyState === 4) && ((this.status / 100) == 2)) {
+ ctdl_startup_2(JSON.parse(this.responseText));
+ }
+ };
+ request.send();
+ request = null;
+}
+
+// Continuation of ctdl_startup() after serv_info is retrieved
+//
+function ctdl_startup_2(data) {
+ serv_info = data;
+
+ if (data.serv_rev_level < 905) {
+ alert("Citadel server is too old, some functions may not work");
+ }
+
+ update_banner();
+
+ // for now, show a room list in the main div
+ gotoroom("_BASEROOM_");
+ display_room_list();
+}
+
+// Display a room list in the main div.
+//
+function display_room_list() {
+ document.getElementById("sidebar").innerHTML = "<img src=\"/ctdl/s/throbber.gif\" />" ; // show throbber while loading
+
+ var request = new XMLHttpRequest();
+ request.open("GET", "/ctdl/r/", true);
+ request.onreadystatechange = function() {
+ if ((this.readyState === 4) && ((this.status / 100) == 2)) {
+ display_room_list_renderer(JSON.parse(this.responseText));
+ }
+ };
+ request.send();
+ request = null;
+}
+
+// Renderer for display_room_list()
+//
+function display_room_list_renderer(data) {
+ data = data.sort(function(a,b) {
+ if (a.floor != b.floor) {
+ return(a.floor - b.floor);
+ }
+ if (a.rorder != b.rorder) {
+ return(a.rorder - b.rorder);
+ }
+ return(a.name < b.name);
+ });
+
+ new_sidebar_text = "<ul>" ;
+
+ for (var i in data) {
+ if (i > 0) {
+ if (data[i].floor != data[i-1].floor) {
+ new_sidebar_text = new_sidebar_text + "<li class=\"divider\"></li>" ;
+ }
+ }
+ new_sidebar_text = new_sidebar_text +
+ "<li>"
+ + "<a href=\"javascript:gotoroom('" + escapeJS(escapeHTML(data[i].name)) + "');\">"
+ + escapeHTML(data[i].name)
+ + "</a></li>"
+ ;
+ }
+ new_sidebar_text = new_sidebar_text + "</ul>";
+ document.getElementById("sidebar").innerHTML = new_sidebar_text ;
+}
+
+// Update the "banner" div with all relevant info.
+//
+function update_banner() {
+ detect_logged_in();
+ if (current_room) {
+ document.getElementById("ctdl_banner_title").innerHTML = current_room;
+ }
+ else {
+ document.getElementById("ctdl_banner_title").innerHTML = serv_info.serv_humannode;
+ }
+ document.getElementById("current_user").innerHTML = current_user ;
+ if (logged_in) {
+ document.getElementById("lilo").innerHTML = "<a href=\"/ctdl/a/logout\">" + _("Log off") + "</a>" ;
+ }
+ else {
+ document.getElementById("lilo").innerHTML = "<a href=\"javascript:display_login_screen('')\">" + _("Log in") + "</a>" ;
+ }
+}
+
+
+// goto room
+//
+function gotoroom(roomname) {
+ var request = new XMLHttpRequest();
+ request.open("GET", "/ctdl/r/" + escapeHTMLURI(roomname) + "/", true);
+ request.onreadystatechange = function() {
+ if ((this.readyState === 4) && ((this.status / 100) == 2)) {
+ gotoroom_2(JSON.parse(this.responseText));
+ }
+ };
+ request.send();
+ request = null;
+}
+function gotoroom_2(data) {
+ current_room = data.name;
+ new_messages = data.new_messages;
+ total_messages = data.total_messages;
+ current_view = data.current_view;
+ default_view = data.default_view;
+ update_banner();
+ render_room_view();
+}
--- /dev/null
+//
+// Copyright (c) 2016-2017 by the citadel.org team
+//
+// This program is open source software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3.
+//
+// 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.
+
+
+// List of defined views shamelessly swiped from libcitadel headers
+//
+var views = {
+ VIEW_BBS : 0, /* Bulletin board view */
+ VIEW_MAILBOX : 1, /* Mailbox summary */
+ VIEW_ADDRESSBOOK : 2, /* Address book view */
+ VIEW_CALENDAR : 3, /* Calendar view */
+ VIEW_TASKS : 4, /* Tasks view */
+ VIEW_NOTES : 5, /* Notes view */
+ VIEW_WIKI : 6, /* Wiki view */
+ VIEW_CALBRIEF : 7, /* Brief Calendar view */
+ VIEW_JOURNAL : 8, /* Journal view */
+ VIEW_DRAFTS : 9, /* Drafts view */
+ VIEW_BLOG : 10, /* Blog view */
+ VIEW_QUEUE : 11, /* SMTP QUEUE rooms */
+ VIEW_WIKIMD : 12, /* Markdown Wiki view */
+};
+
+
+// This function is the dispatcher that determines the correct view for a room,
+// and calls the correct renderer.
+//
+function render_room_view() {
+
+ switch(current_view) {
+ case views.VIEW_MAILBOX: // FIXME view mail rooms as forums for now
+ case views.VIEW_BBS:
+ threads_readmessages();
+ break;
+ default:
+ document.getElementById("main").innerHTML = "The view for " + current_room + " is " + current_view + " but there is no renderer." ;
+ break;
+ }
+
+}
+
+
+// bbs "threads" view
+// The inner div exists so that if the user clicks away early, the main div doesn't get clobbered when the load completes.
+//
+function threads_readmessages() {
+ var innerdivname = randomString(5);
+ document.getElementById("main").innerHTML = "<div id=\"" + innerdivname + "\"><img src=\"/ctdl/s/throbber.gif\" />" + _("Loading messages from server, please wait") + "</div>" ;
+
+ var request = new XMLHttpRequest();
+ request.open("GET", "/ctdl/r/" + escapeHTMLURI(current_room) + "/" + "threads", true);
+ request.onreadystatechange = function() {
+ if (this.readyState === 4) {
+ if ((this.status / 100) == 2) {
+ document.getElementById(innerdivname).outerHTML = this.responseText;
+ }
+ else {
+ document.getElementById(innerdivname).outerHTML = "ERROR " + this.status ;
+ }
+ }
+ };
+ request.send();
+ request = null;
+}
--- /dev/null
+/*
+ * TCP sockets layer
+ *
+ * Copyright (c) 1987-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+/*
+ * lingering_close() a`la Apache. see
+ * http://httpd.apache.org/docs/2.0/misc/fin_wait_2.html for rationale
+ */
+int lingering_close(int fd)
+{
+ char buf[SIZ];
+ int i;
+ fd_set set;
+ struct timeval tv, start;
+
+ gettimeofday(&start, NULL);
+ if (fd == -1)
+ return -1;
+ shutdown(fd, 1);
+ do {
+ do {
+ gettimeofday(&tv, NULL);
+ tv.tv_sec = SLEEPING - (tv.tv_sec - start.tv_sec);
+ tv.tv_usec = start.tv_usec - tv.tv_usec;
+ if (tv.tv_usec < 0) {
+ tv.tv_sec--;
+ tv.tv_usec += 1000000;
+ }
+ FD_ZERO(&set);
+ FD_SET(fd, &set);
+ i = select(fd + 1, &set, NULL, NULL, &tv);
+ } while (i == -1 && errno == EINTR);
+
+ if (i <= 0)
+ break;
+
+ i = read(fd, buf, sizeof buf);
+ } while (i != 0 && (i != -1 || errno == EINTR));
+
+ return close(fd);
+}
+
+
+/*
+ * This is a generic function to set up a master socket for listening on
+ * a TCP port. The server shuts down if the bind fails. (IPv4/IPv6 version)
+ *
+ * ip_addr IP address to bind
+ * port_number port number to bind
+ * queue_len number of incoming connections to allow in the queue
+ */
+int webcit_tcp_server(const char *ip_addr, int port_number, int queue_len)
+{
+ const char *ipv4broadcast = "0.0.0.0";
+ int IsDefault = 0;
+ struct protoent *p;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_in sin4;
+ int s, i, b;
+ int ip_version = 6;
+
+retry:
+ memset(&sin6, 0, sizeof(sin6));
+ memset(&sin4, 0, sizeof(sin4));
+ sin6.sin6_family = AF_INET6;
+ sin4.sin_family = AF_INET;
+
+ if ( (ip_addr == NULL) /* any IPv6 */
+ || (IsEmptyStr(ip_addr))
+ || (!strcmp(ip_addr, "*"))
+ ) {
+ IsDefault = 1;
+ ip_version = 6;
+ sin6.sin6_addr = in6addr_any;
+ }
+ else if (!strcmp(ip_addr, "0.0.0.0")) /* any IPv4 */
+ {
+ ip_version = 4;
+ sin4.sin_addr.s_addr = INADDR_ANY;
+ }
+ else if ((strchr(ip_addr, '.')) && (!strchr(ip_addr, ':'))) /* specific IPv4 */
+ {
+ ip_version = 4;
+ if (inet_pton(AF_INET, ip_addr, &sin4.sin_addr) <= 0) {
+ syslog(LOG_WARNING, "Error binding to [%s] : %s\n", ip_addr, strerror(errno));
+ return(-1);
+ }
+ }
+ else /* specific IPv6 */
+ {
+ ip_version = 6;
+ if (inet_pton(AF_INET6, ip_addr, &sin6.sin6_addr) <= 0) {
+ syslog(LOG_WARNING, "Error binding to [%s] : %s\n", ip_addr, strerror(errno));
+ return(-1);
+ }
+ }
+
+ if (port_number == 0) {
+ syslog(LOG_WARNING, "Cannot start: no port number specified.\n");
+ return(-1);
+ }
+ sin6.sin6_port = htons((u_short) port_number);
+ sin4.sin_port = htons((u_short) port_number);
+
+ p = getprotobyname("tcp");
+
+ s = socket( ((ip_version == 6) ? PF_INET6 : PF_INET), SOCK_STREAM, (p->p_proto));
+ if (s < 0) {
+ if (IsDefault && (errno == EAFNOSUPPORT))
+ {
+ s = 0;
+ ip_addr = ipv4broadcast;
+ goto retry;
+ }
+ syslog(LOG_WARNING, "Can't create a listening socket: %s\n", strerror(errno));
+ return(-1);
+ }
+ /* Set some socket options that make sense. */
+ i = 1;
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
+
+ if (ip_version == 6) {
+ b = bind(s, (struct sockaddr *) &sin6, sizeof(sin6));
+ }
+ else {
+ b = bind(s, (struct sockaddr *) &sin4, sizeof(sin4));
+ }
+
+ if (b < 0) {
+ syslog(LOG_ERR, "Can't bind: %s\n", strerror(errno));
+ close(s);
+ return(-1);
+ }
+
+ if (listen(s, queue_len) < 0) {
+ syslog(LOG_ERR, "Can't listen: %s\n", strerror(errno));
+ close(s);
+ return(-1);
+ }
+ return (s);
+}
+
+
+/*
+ * Create a Unix domain socket and listen on it
+ * sockpath - file name of the unix domain socket
+ * queue_len - Number of incoming connections to allow in the queue
+ */
+int webcit_uds_server(char *sockpath, int queue_len)
+{
+ struct sockaddr_un addr;
+ int s;
+ int i;
+ int actual_queue_len;
+
+ actual_queue_len = queue_len;
+ if (actual_queue_len < 5) actual_queue_len = 5;
+
+ i = unlink(sockpath);
+ if ((i != 0) && (errno != ENOENT)) {
+ syslog(LOG_WARNING, "webcit: can't unlink %s: %s", sockpath, strerror(errno));
+ return(-1);
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
+
+ s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s < 0) {
+ syslog(LOG_WARNING, "webcit: Can't create a unix domain socket: %s", strerror(errno));
+ return(-1);
+ }
+
+ if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ syslog(LOG_WARNING, "webcit: Can't bind: %s", strerror(errno));
+ close(s);
+ return(-1);
+ }
+
+ if (listen(s, actual_queue_len) < 0) {
+ syslog(LOG_WARNING, "webcit: Can't listen: %s", strerror(errno));
+ close(s);
+ return(-1);
+ }
+
+ chmod(sockpath, 0777);
+ return(s);
+}
--- /dev/null
+/*
+ * Convert text/plain to text/html
+ *
+ * Copyright (c) 2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * Convert a text/plain message to text/html
+ */
+StrBuf *text2html(const char *supplied_charset, int treat_as_wiki, char *roomname, long msgnum, StrBuf *Source)
+{
+ StrBuf *sj = NULL;
+
+ sj = NewStrBuf();
+ if (!sj) {
+ return(sj);
+ }
+
+ StrBufAppendPrintf(sj, "<pre>");
+ StrEscAppend(sj, Source, NULL, 0, 0); // FIXME - add code here to activate links
+ StrBufAppendPrintf(sj, "</pre>\n");
+
+ return(sj);
+}
+
+
+/*
+ * Convert a text/x-citadel-variformat message to text/html
+ */
+StrBuf *variformat2html(StrBuf *Source)
+{
+ StrBuf *Target = NULL;
+
+ Target = NewStrBuf();
+ if (!Target) {
+ return(Target);
+ }
+
+ const char *ptr, *pte;
+ const char *BufPtr = NULL;
+ StrBuf *Line = NewStrBufPlain(NULL, SIZ);
+ StrBuf *Line1 = NewStrBufPlain(NULL, SIZ);
+ StrBuf *Line2 = NewStrBufPlain(NULL, SIZ);
+ int bn = 0;
+ int bq = 0;
+ int i;
+ long len;
+ int intext = 0;
+
+ if (StrLength(Source) > 0)
+ do
+ {
+ StrBufSipLine(Line, Source, &BufPtr);
+ bq = 0;
+ i = 0;
+ ptr = ChrPtr(Line);
+ len = StrLength(Line);
+ pte = ptr + len;
+
+ if ((intext == 1) && (isspace(*ptr))) {
+ StrBufAppendBufPlain(Target, HKEY("<br>"), 0);
+ }
+ intext = 1;
+ if (isspace(*ptr)) {
+ while ((ptr < pte) && ((*ptr == '>') || isspace(*ptr))) {
+ if (*ptr == '>') {
+ bq++;
+ }
+ ptr ++;
+ i++;
+ }
+ }
+
+ /*
+ * Quoted text should be displayed in italics and in a
+ * different colour. This code understands Citadel-style
+ * " >" quotes and will convert to <BLOCKQUOTE> tags.
+ */
+ if (i > 0) StrBufCutLeft(Line, i);
+
+ for (i = bn; i < bq; i++)
+ StrBufAppendBufPlain(Target, HKEY("<blockquote>"), 0);
+ for (i = bq; i < bn; i++)
+ StrBufAppendBufPlain(Target, HKEY("</blockquote>"), 0);
+ bn = bq;
+
+ if (StrLength(Line) == 0)
+ continue;
+
+ /* Activate embedded URL's */
+ UrlizeText(Line1, Line, Line2);
+
+ StrEscAppend(Target, Line1, NULL, 0, 0);
+
+ StrBufAppendBufPlain(Target, HKEY("\n"), 0);
+ }
+ while ((BufPtr != StrBufNOTNULL) &&
+ (BufPtr != NULL));
+
+ for (i = 0; i < bn; i++) {
+ StrBufAppendBufPlain(Target, HKEY("</blockquote>"), 0);
+ }
+ StrBufAppendBufPlain(Target, HKEY("<br>\n"), 0);
+ FreeStrBuf(&Line);
+ FreeStrBuf(&Line1);
+ FreeStrBuf(&Line2);
+ return(Target);
+}
+
+
+
--- /dev/null
+/*
+ * Threaded message view
+ *
+ * Copyright (c) 1996-2017 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+struct mthread {
+ long msgnum;
+ time_t datetime;
+ int threadhash;
+ int refhashes[10];
+ char from[64];
+ int parent;
+};
+
+
+// Renderer for one message in the threaded view
+void thread_render_one_message(struct ctdlsession *c, StrBuf *sj, long msgnum)
+{
+ StrBuf *raw_msg = NULL;
+ StrBuf *sanitized_msg = NULL;
+ char buf[1024];
+ char content_transfer_encoding[1024] = { 0 };
+ char content_type[1024] = { 0 };
+
+ ctdl_printf(c, "MSG4 %ld", msgnum);
+ ctdl_readline(c, buf, sizeof(buf));
+ if (buf[0] == '1') {
+ while ( (ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "text")) && (strcmp(buf, "000")) ) {
+ // citadel header parsing here
+ if (!strncasecmp(buf, "from=", 5)) {
+ StrBufAppendPrintf(sj, "<b>From %s</b><br>", &buf[5]); // FIXME that was temporary
+ }
+ if (!strncasecmp(buf, "part=", 5)) {
+ //StrBufAppendPrintf(sj, "MIME part: %s<br>", &buf[5]); // FIXME that was temporary
+ }
+ }
+ if (!strcmp(buf, "text")) {
+ while ( (ctdl_readline(c, buf, sizeof(buf)) >= 0) && (strcmp(buf, "")) && (strcmp(buf, "000")) ) {
+ // rfc822 header parsing here
+ if (!strncasecmp(buf, "Content-transfer-encoding:", 26)) {
+ strcpy(content_transfer_encoding, &buf[26]);
+ striplt(content_transfer_encoding);
+ }
+ if (!strncasecmp(buf, "Content-type:", 13)) {
+ strcpy(content_type, &buf[13]);
+ striplt(content_type);
+ //StrBufAppendPrintf(sj, "Content-type: %s<br>", content_type); // FIXME that was temporary
+ }
+ }
+ raw_msg = ctdl_readtextmsg(c);
+ }
+ else {
+ raw_msg = NULL;
+ }
+ if (raw_msg) {
+ if (!strcasecmp(content_transfer_encoding, "base64")) {
+ StrBufDecodeBase64(raw_msg);
+ }
+ if (!strcasecmp(content_transfer_encoding, "quoted-printable")) {
+ StrBufDecodeQP(raw_msg);
+ }
+ if (!strncasecmp(content_type, "text/html", 9)) {
+ sanitized_msg = html2html("UTF-8", 0, c->room, msgnum, raw_msg);
+ }
+ else if (!strncasecmp(content_type, "text/plain", 10)) {
+ sanitized_msg = text2html("UTF-8", 0, c->room, msgnum, raw_msg);
+ }
+ else if (!strncasecmp(content_type, "text/x-citadel-variformat", 25)) {
+ sanitized_msg = variformat2html(raw_msg);
+ }
+ else {
+ sanitized_msg = NewStrBufPlain(HKEY("<i>No renderer for this content type</i><br>"));
+ }
+ //sanitized_msg = NewStrBufDup(raw_msg);
+ FreeStrBuf(&raw_msg);
+ if (sanitized_msg) {
+ StrBufAppendBuf(sj, sanitized_msg, 0);
+ FreeStrBuf(&sanitized_msg);
+ }
+ }
+ }
+}
+
+
+void thread_o_print(struct ctdlsession *c, StrBuf *sj, struct mthread *m, int num_msgs, int where_parent_is, int nesting_level)
+{
+ int i = 0;
+ int j = 0;
+ int num_printed = 0;
+
+ for (i=0; i<num_msgs; ++i) {
+ if (m[i].parent == where_parent_is) {
+
+ if (++num_printed == 1) {
+ StrBufAppendPrintf(sj, "<ul>");
+ StrBufAppendPrintf(sj, "<table style=\"border: 1px solid black;\"><tr><td>"); // temporary, for visualization
+ }
+
+ //for (j=nesting_level; j>0; --j) {
+ //StrBufAppendPrintf(sj, " ");
+ //}
+
+ StrBufAppendPrintf(sj, "<li class=\"post\" id=\"post-%ld\">", m[i].msgnum);
+ thread_render_one_message(c, sj, m[i].msgnum);
+ StrBufAppendPrintf(sj, "</li>\r\n");
+ if (i != 0) {
+ thread_o_print(c, sj, m, num_msgs, i, nesting_level+1);
+ }
+ }
+ }
+
+ if (num_printed > 0) {
+ StrBufAppendPrintf(sj, "</td></tr></table>"); // temporary, for visualization
+ StrBufAppendPrintf(sj, "</ul>");
+ }
+}
+
+
+
+void threaded_view(struct http_transaction *h, struct ctdlsession *c, char *which)
+{
+ int num_msgs = 0;
+ int num_alloc = 0;
+ struct mthread *m;
+ char buf[1024];
+ char refs[1024];
+ int i, j, k;
+
+ ctdl_printf(c, "MSGS ALL|||9"); // 9 == headers + thread references
+ ctdl_readline(c, buf, sizeof(buf));
+ if (buf[0] != '1') {
+ do_404(h);
+ return;
+ }
+
+ StrBuf *sj = NewStrBuf();
+ StrBufAppendPrintf(sj, "<html><body>\r\n");
+
+ while (ctdl_readline(c, buf, sizeof buf), strcmp(buf,"000")) {
+
+ ++num_msgs;
+ if (num_msgs > num_alloc) {
+ if (num_alloc == 0) {
+ num_alloc = 100;
+ m = malloc(num_alloc * sizeof(struct mthread));
+ }
+ else {
+ num_alloc *= 2;
+ m = realloc(m, (num_alloc * sizeof(struct mthread)));
+ }
+ }
+
+ memset(&m[num_msgs-1], 0, sizeof(struct mthread));
+ m[num_msgs-1].msgnum = extract_long(buf, 0);
+ m[num_msgs-1].datetime = extract_long(buf, 1);
+ extract_token(m[num_msgs-1].from, buf, 2, '|', sizeof m[num_msgs-1].from);
+ m[num_msgs-1].threadhash = extract_int(buf, 6);
+ extract_token(refs, buf, 7, '|', sizeof refs);
+
+ char *t;
+ char *r = refs;
+ i = 0;
+ while ((t = strtok_r(r, ",", &r))) {
+ if (i == 0) {
+ m[num_msgs-1].refhashes[0] = atoi(t); // always keep the first one
+ }
+ else {
+ memcpy(&m[num_msgs-1].refhashes[1], &m[num_msgs-1].refhashes[2], sizeof(int)*8 ); // shift the rest
+ m[num_msgs-1].refhashes[9] = atoi(t);
+ }
+ ++i;
+ }
+
+ }
+
+ // Sort by thread. I did read jwz's sorting algorithm and it looks pretty good, but jwz is a self-righteous asshole so we do it our way.
+ for (i=0; i<num_msgs; ++i) {
+ for (j=9; (j>=0)&&(m[i].parent==0); --j) {
+ for (k=0; (k<num_msgs)&&(m[i].parent==0); ++k) {
+ if (m[i].refhashes[j] == m[k].threadhash) {
+ m[i].parent = k;
+ }
+ }
+ }
+ }
+
+ // Now render it
+ ctdl_printf(c, "MSGP text/html|text/plain"); // Declare the MIME types we know how to render
+ ctdl_readline(c, buf, sizeof(buf)); // Ignore the response
+ ctdl_printf(c, "MSGP dont_decode"); // Tell the server we will decode base64/etc client-side
+ ctdl_readline(c, buf, sizeof(buf)); // Ignore the response
+ thread_o_print(c, sj, m, num_msgs, 0, 0); // Render threads recursively and recursively
+
+ // haha h0h0
+ if (num_msgs > 0) {
+ free(m);
+ }
+
+ StrBufAppendPrintf(sj, "</body></html>\r\n");
+
+ add_response_header(h, strdup("Content-type"), strdup("text/html; charset=utf-8"));
+ h->response_code = 200;
+ h->response_string = strdup("OK");
+ h->response_body_length = StrLength(sj);
+ h->response_body = SmashStrBuf(&sj);
+ return;
+}
+
+
--- /dev/null
+/*
+ * Utility functions
+ *
+ * Copyright (c) 1996-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h"
+
+
+/*
+ * remove escaped strings from i.e. the url string (like %20 for blanks)
+ */
+int unescape_input(char *buf)
+{
+ unsigned int a, b;
+ char hex[3];
+ long buflen;
+ long len;
+
+ buflen = strlen(buf);
+
+ while ((buflen > 0) && (isspace(buf[buflen - 1]))){
+ buf[buflen - 1] = 0;
+ buflen --;
+ }
+
+ a = 0;
+ while (a < buflen) {
+ if (buf[a] == '+')
+ buf[a] = ' ';
+ if (buf[a] == '%') {
+ /* don't let % chars through, rather truncate the input. */
+ if (a + 2 > buflen) {
+ buf[a] = '\0';
+ buflen = a;
+ }
+ else {
+ hex[0] = buf[a + 1];
+ hex[1] = buf[a + 2];
+ hex[2] = 0;
+ b = 0;
+ b = decode_hex(hex);
+ buf[a] = (char) b;
+ len = buflen - a - 2;
+ if (len > 0)
+ memmove(&buf[a + 1], &buf[a + 3], len);
+
+ buflen -=2;
+ }
+ }
+ a++;
+ }
+ return a;
+}
+
+
+/*
+ * Supplied with a unix timestamp, generate a textual time/date stamp.
+ * Caller owns the returned memory.
+ */
+char *http_datestring(time_t xtime) {
+
+ /* HTTP Months - do not translate - these are not for human consumption */
+ static char *httpdate_months[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ /* HTTP Weekdays - do not translate - these are not for human consumption */
+ static char *httpdate_weekdays[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+
+ struct tm t;
+ long offset;
+ char offsign;
+ int n = 40;
+ char *buf = malloc(n);
+ if (!buf) return(NULL);
+
+ localtime_r(&xtime, &t);
+
+ /* Convert "seconds west of GMT" to "hours/minutes offset" */
+ offset = t.tm_gmtoff;
+ if (offset > 0) {
+ offsign = '+';
+ }
+ else {
+ offset = 0L - offset;
+ offsign = '-';
+ }
+ offset = ( (offset / 3600) * 100 ) + ( offset % 60 );
+
+ snprintf(buf, n, "%s, %02d %s %04d %02d:%02d:%02d %c%04ld",
+ httpdate_weekdays[t.tm_wday],
+ t.tm_mday,
+ httpdate_months[t.tm_mon],
+ t.tm_year + 1900,
+ t.tm_hour,
+ t.tm_min,
+ t.tm_sec,
+ offsign, offset
+ );
+ return(buf);
+}
--- /dev/null
+/*
+ * webcit.h - "header of headers"
+ *
+ * Copyright (c) 1996-2017 by the citadel.org team
+ *
+ * This program is open source software. You can redistribute it and/or
+ * modify it under the terms of the GNU General Public License, version 3.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <syslog.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+#include <sys/poll.h>
+#include <string.h>
+#include <pwd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <pthread.h>
+#include <signal.h>
+#include <syslog.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <iconv.h>
+#include <libcitadel.h>
+#define OPENSSL_NO_KRB5 // Work around RedHat's b0rken OpenSSL includes
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <expat.h>
+#define _(x) x // temporary hack until we add i18n back in
+//#define DEBUG_HTTP // uncomment to debug HTTP headers
+
+/* XML_StopParser is present in expat 2.x */
+#if XML_MAJOR_VERSION > 1
+#define HAVE_XML_STOPPARSER
+#endif
+
+struct client_handle { // this gets passed up the stack from the webserver to the application code
+ int sock;
+ SSL *ssl_handle;
+};
+
+struct http_header { // request and response headers in struct http_transaction use this format
+ struct http_header *next;
+ char *key;
+ char *val;
+};
+
+struct http_transaction { // The lifetime of an HTTP request goes through this data structure.
+ char *method; // The top half is built up by the web server and sent up to the
+ char *uri; // application stack. The second half is built up by the application
+ char *http_version; // stack and sent back down to the web server, which transmits it to
+ char *site_prefix; // the client.
+ struct http_header *request_headers;
+ char *request_body;
+ long request_body_length;
+ int response_code;
+ char *response_string;
+ struct http_header *response_headers;
+ char *response_body;
+ long response_body_length;
+};
+
+#define AUTH_MAX 256 // Maximum length of an HTTP AUTH header or equivalent cookie data
+struct ctdlsession {
+ struct ctdlsession *next;
+ int is_bound; // Nonzero if this record is currently bound to a running thread
+ int sock; // Socket connection to Citadel server
+ char auth[AUTH_MAX]; // Auth string (empty if not logged in)
+ char whoami[64]; // Display name of currently logged in user (empty if not logged in)
+ char room[128]; // What room we are currently in
+ int room_current_view;
+ int room_default_view;
+ int new_messages;
+ int total_messages;
+ time_t last_access; // Timestamp of last request that used this session
+ time_t num_requests_handled;
+};
+
+extern char *ssl_cipher_list;
+extern int is_https; // nonzero if we are an HTTPS server today
+extern char *ctdlhost;
+extern char *ctdlport;
+void init_ssl(void);
+void starttls(struct client_handle *);
+void endtls(struct client_handle *);
+int client_write_ssl(struct client_handle *ch, char *buf, int nbytes);
+int client_read_ssl(struct client_handle *ch, char *buf, int nbytes);
+
+enum {
+ WEBSERVER_HTTP,
+ WEBSERVER_HTTPS,
+ WEBSERVER_UDS
+};
+
+#define TRACE syslog(LOG_DEBUG, "\033[3%dmCHECKPOINT: %s:%d\033[0m", ((__LINE__%6)+1), __FILE__, __LINE__)
+#define SLEEPING 180 // TCP connection timeout
+#define MAX_WORKER_THREADS 32 // Maximum number of worker threads permitted to exist
+#define CTDL_CRYPTO_DIR "keys"
+#define CTDL_KEY_PATH CTDL_CRYPTO_DIR "/webcit.key"
+#define CTDL_CSR_PATH CTDL_CRYPTO_DIR "/webcit.csr"
+#define CTDL_CER_PATH CTDL_CRYPTO_DIR "/webcit.cer"
+#define SIGN_DAYS 3650 // how long our certificate should live
+#define DEFAULT_SSL_CIPHER_LIST "DEFAULT" // See http://openssl.org/docs/apps/ciphers.html
+#define WEBSERVER_PORT 80
+#define WEBSERVER_INTERFACE "*"
+#define CTDLHOST "uncensored.citadel.org"
+#define CTDLPORT "504"
+#define DEVELOPER_ID 0
+#define CLIENT_ID 4
+#define TARGET "webcit01" /* Window target for inline URL's */
+
+void worker_entry(int *pointer_to_master_socket);
+void perform_one_http_transaction(struct client_handle *ch);
+void add_response_header(struct http_transaction *h, char *key, char *val);
+void perform_request(struct http_transaction *);
+void do_404(struct http_transaction *);
+void output_static(struct http_transaction *);
+int uds_connectsock(char *sockpath);
+int tcp_connectsock(char *host, char *service);
+void ctdl_a(struct http_transaction *, struct ctdlsession *);
+void ctdl_r(struct http_transaction *, struct ctdlsession *);
+struct ctdlsession *connect_to_citadel(struct http_transaction *);
+void disconnect_from_citadel(struct ctdlsession *);
+char *header_val(struct http_transaction *h, char *requested_header);
+int unescape_input(char *);
+void http_redirect(struct http_transaction *h, char *to_where);
+char *http_datestring(time_t xtime);
+long *get_msglist(struct ctdlsession *c, char *which_msgs);
+void caldav_report(struct http_transaction *h, struct ctdlsession *c);
+long locate_message_by_uid(struct ctdlsession *c, char *uid);
+void ctdl_delete_msgs(struct ctdlsession *c, long *msgnums, int num_msgs);
+void dav_delete_message(struct http_transaction *h, struct ctdlsession *c, long msgnum);
+void dav_get_message(struct http_transaction *h, struct ctdlsession *c, long msgnum);
+void dav_put_message(struct http_transaction *h, struct ctdlsession *c, char *euid, long old_msgnum);
+ssize_t ctdl_write(struct ctdlsession *ctdl, const void *buf, size_t count);
+int login_to_citadel(struct ctdlsession *c, char *auth, char *resultbuf);
+void threaded_view(struct http_transaction *h, struct ctdlsession *c, char *which);
+StrBuf *ctdl_readtextmsg(struct ctdlsession *ctdl);
+StrBuf *html2html(const char *supplied_charset, int treat_as_wiki, char *roomname, long msgnum, StrBuf *Source);
+void download_mime_component(struct http_transaction *h, struct ctdlsession *c, long msgnum, char *partnum);
+StrBuf *text2html(const char *supplied_charset, int treat_as_wiki, char *roomname, long msgnum, StrBuf *Source);
+StrBuf *variformat2html(StrBuf *Source);
+
+void *mallok(size_t size);
+void phree(void *ptr);
+void *reallok(void *ptr, size_t size);
+
--- /dev/null
+/*
+ * webserver.c
+ *
+ * This module handles the task of setting up a listening socket, accepting
+ * connections, and dispatching active connections onto a pool of worker
+ * threads.
+ *
+ * Copyright (c) 1996-2016 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3.
+ *
+ * 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.
+ */
+
+#include "webcit.h" // All other headers are included from this header.
+
+int num_threads_executing = 1; // Number of worker threads currently bound to a connected client
+int num_threads_existing = 1; // Total number of worker threads that exist right now
+int is_https = 0; // Set to nonzero if we are running as an HTTPS server today.
+static void *original_brk = NULL; // Remember the original program break so we can test for leaks
+
+
+/*
+ * Spawn an additional worker thread into the pool.
+ */
+void spawn_another_worker_thread(int *pointer_to_master_socket)
+{
+ pthread_t th; // Thread descriptor
+ pthread_attr_t attr; // Thread attributes
+
+ /* set attributes for the new thread */
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ pthread_attr_setstacksize(&attr, 1048576); // Large stacks to prevent MIME parser crash on FreeBSD
+
+ /* now create the thread */
+ if (pthread_create(&th, &attr, (void *(*)(void *)) worker_entry, (void *)pointer_to_master_socket) != 0) {
+ syslog(LOG_WARNING, "Can't create thread: %s", strerror(errno));
+ }
+ else {
+ ++num_threads_existing;
+ ++num_threads_executing;
+ }
+
+ /* free up the attributes */
+ pthread_attr_destroy(&attr);
+}
+
+
+/*
+ * Entry point for worker threads
+ */
+void worker_entry(int *pointer_to_master_socket)
+{
+ int master_socket = *pointer_to_master_socket;
+ int i = 0;
+ int fail_this_transaction = 0;
+ struct client_handle ch;
+
+ while(1) {
+ /* Each worker thread blocks on accept() while waiting for something to do. */
+ memset(&ch, 0, sizeof ch);
+ ch.sock = -1;
+ errno = EAGAIN;
+ do {
+ --num_threads_executing;
+ syslog(LOG_DEBUG, "Additional memory allocated since startup: %d bytes", (int)(sbrk(0)-original_brk));
+ syslog(LOG_DEBUG, "Thread 0x%x calling accept() on master socket %d", (unsigned int)pthread_self(), master_socket);
+ ch.sock = accept(master_socket, NULL, 0);
+ if (ch.sock < 0) {
+ syslog(LOG_DEBUG, "accept() : %s", strerror(errno));
+ }
+ ++num_threads_executing;
+ syslog(LOG_DEBUG, "socket %d is awake , threads executing: %d , threads total: %d", ch.sock, num_threads_executing, num_threads_existing);
+ } while ((master_socket > 0) && (ch.sock < 0));
+
+ /* If all threads are executing, spawn more, up to the maximum */
+ if ( (num_threads_executing >= num_threads_existing) && (num_threads_existing <= MAX_WORKER_THREADS) ) {
+ spawn_another_worker_thread(pointer_to_master_socket);
+ }
+
+ /* We have a client. Do some work. */
+
+ /* Set the SO_REUSEADDR socket option */
+ i = 1;
+ setsockopt(ch.sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
+
+ /* If we are an HTTPS server, go crypto now. */
+ if (is_https) {
+ starttls(&ch);
+ if (ch.ssl_handle == NULL) {
+ fail_this_transaction = 1;
+ }
+ }
+ else
+ {
+ int fdflags;
+ fdflags = fcntl(ch.sock, F_GETFL);
+ if (fdflags < 0) {
+ syslog(LOG_WARNING, "unable to get server socket flags! %s", strerror(errno));
+ }
+ }
+
+ /* Perform an HTTP transaction... */
+ if (fail_this_transaction == 0) {
+ perform_one_http_transaction(&ch);
+ }
+
+ /* Shut down SSL/TLS if required... */
+ if (is_https) {
+ endtls(&ch);
+ }
+ /* ...and close the socket. */
+ //syslog(LOG_DEBUG, "Closing socket %d...", ch.sock);
+ //lingering_close(ch.sock);
+ close(ch.sock);
+ syslog(LOG_DEBUG, "Closed socket %d.", ch.sock);
+ }
+}
+
+
+/*
+ * Start up a TCP HTTP[S] server on the requested port
+ */
+int webserver(char *webserver_interface, int webserver_port, int webserver_protocol)
+{
+ int master_socket = (-1) ;
+ original_brk = sbrk(0);
+
+ switch(webserver_protocol) {
+ case WEBSERVER_HTTP:
+ syslog(LOG_DEBUG, "Starting HTTP server on %s:%d", webserver_interface, webserver_port);
+ master_socket = webcit_tcp_server(webserver_interface, webserver_port, 10);
+ break;
+ case WEBSERVER_HTTPS:
+ syslog(LOG_DEBUG, "Starting HTTPS server on %s:%d", webserver_interface, webserver_port);
+ master_socket = webcit_tcp_server(webserver_interface, webserver_port, 10);
+ init_ssl();
+ is_https = 1;
+ break;
+ default:
+ syslog(LOG_ERR, "unknown protocol");
+ ;;
+ }
+
+ if (master_socket < 1) {
+ syslog(LOG_ERR, "Unable to bind the web server listening socket");
+ return(1);
+ }
+
+ syslog(LOG_INFO, "Listening on socket %d", master_socket);
+ signal(SIGPIPE, SIG_IGN);
+
+ worker_entry(&master_socket); // this thread becomes a worker
+ return(0);
+}